feat(fygaro): payment webhook — record, notify, and flag-gated auto-credit - #472
Merged
Conversation
…redit Fygaro card top-ups previously left no server-side record: the app opened Fygaro's hosted payment button and nothing ever came back to Flash, so every top-up required manual PayPal-email archaeology to attribute and a manual treasury transfer to credit. This implements the missing recording path (TopUp V1 design), mirroring the Bridge webhook server structure. - fygaro-webhook server (default port 4010): raw-body capture, /health, enabled guard, rate limit, HMAC-SHA-256 signature verification per Fygaro's hook spec (Fygaro-Signature t=..,v1=..; Fygaro-Key-ID selects the shared secret; multiple secrets supported for rotation) - /payment handler: attributes the payment via customReference (Flash username), writes the ERPNext audit row (Bridge Transfer Request, provider=Fygaro, Fiat Received) for every payment — including unattributed ones, which alert ops with the payer email instead of being silently dropped — and posts to the Discord ops activity feed - fygaro.credit.enabled (default OFF): when on, USD payments are auto-credited from the bankowner treasury to the user's cash wallet via intraledger send, idempotent on the Fygaro transaction id (ENG-530 pattern), then the audit row is promoted to Completed. Credit failures alert critical and leave the row at Fiat Received for manual follow-up. - config: fygaro block (enabled / webhook.port / webhook.secrets / webhook.timestampSkewMs / credit.enabled), default-off baseline - unit tests: signature verification, enabled guard, and the full payment handler matrix (attribution, dedupe, credit on/off, non-USD, failures) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb
…, hardening Addresses the strict-review findings on the payment webhook: - credit path now serializes deliveries with a RELEASING lock (lockPaymentIdempotencyKey) and uses the audit row's Completed status as the processed marker, instead of consuming a non-releasing timelock before the send. A crash mid-credit releases the lock so the next provider retry re-runs the credit — withPaymentIdempotency keeps the send exactly-once — eliminating the stranded paid-but-uncredited window. Promotion failures now also self-heal on retry. - new isFygaroTopupCompleted helper (degrades to false on lookup failure; a false negative replays the cached send, never double-pays) - unattributed path takes the dedupe timelock before emitting, so Fygaro re-deliveries no longer spam a fresh ops-feed line per attempt - enabled guard optional-chains FygaroConfig (fail closed 503, never a per-request throw); signature verify documents the hex-digest assumption to check against the first real signed payment - NEW: credit-topup.spec.ts — direct tests for the money-moving path: treasury role/wallet resolution, USDT-preference + USD fallback, recipient currency matching, cents validation matrix, Pending semantics, send errors, unexpected-status rejection - payment.spec.ts updated for the new lock semantics: Completed-row short-circuit, incomplete-attempt re-run, lock contention, and unattributed re-delivery dedupe Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb
…k throw redlock reports a throw from inside the wrapped callback as a generic lock error; acking that as "already_processing" (200) would stop Fygaro's retries and strand the payment at Fiat Received with no alert. Only ResourceAttemptsLockServiceError (real contention) is acked now — any other lock error is rethrown so the catch-all returns 500 + critical alert and the provider retries. Test added for the throw path; the contention test now uses the real error class. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb
bobodread876
approved these changes
Aug 8, 2026
islandbitcoin
added a commit
to lnflash/frappe-flash-admin
that referenced
this pull request
Aug 8, 2026
…ests (#60) * feat(admin): accept + surface Fygaro provider on Bridge Transfer Requests Companion to lnflash/flash#472 (Fygaro card top-up webhook), which writes Bridge Transfer Request rows with provider=Fygaro. - provider Select options: Bridge -> Bridge\nFygaro. Without this, every Fygaro audit insert fails Frappe's server-side Select validation, the webhook 500s, and no top-up is ever recorded. - provider added to the doctype list view and shown on the Transfer Requests page: a Provider column on the bridge tab and a Provider row in the detail drawer (existing rows default-display as Bridge). The page's query already returns the field; no API change needed. - doctype `modified` stamp bumped so bench migrate actually syncs the new option. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb * style: prettier formatting for bridgeHeaders array (pre-commit) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb * feat(admin): dedicated Card Top-Ups tab — Fygaro records off the Bridge tab Putting card top-ups under "Bridge" was counter-intuitive. The Transfer Requests page now has three tabs: Cashouts, Bridge, and Card Top-Ups. - Card Top-Ups tab lists Bridge Transfer Request rows with provider=Fygaro; the Bridge tab is now provider=Bridge only (get_bridge_transfer_requests gains a provider filter) - per-tab pulse tiles: Fygaro shows Fiat Received (awaiting credit), Completed, Failed — counts provider-scoped in get_transfer_pulse so the Bridge tiles no longer mix in card top-ups - per-tab titles, placeholders, and empty states; the transaction-type filter stays Bridge-only (card top-ups are Topup by definition) - the redundant Provider table column is dropped (the tab implies it); the detail drawer keeps the Provider row and titles itself "Card Top-Up Details" for Fygaro rows Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb * style: ruff formatting for pulse.py (pre-commit) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb --------- Co-authored-by: Dread <dread@example.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
islandbitcoin
pushed a commit
to lnflash/charts
that referenced
this pull request
Aug 8, 2026
…chart 3.2.53) Companion to lnflash/flash#472 (Fygaro card top-up payment webhook). Mirrors the bridge-webhook workload 1:1: same env surface, config secret mount, probes, and enabled-gating. - templates: fygaro-webhook-deployment/-service/fygaro-ingress, rendered only when galoy.fygaro.webhook.enabled (default off) - values: galoy.fygaro.webhook workload block (port 4010) and the `fygaro` app-config block under galoy.config (default-off; HMAC secrets injected per environment via deployments overlays) - helper: galoy.fygaro.webhook.fullname Render-verified: enabled=true produces all three objects with the fygaro-webhook-server entrypoint; default renders no workload while the fygaro config still reaches custom.yaml. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb
islandbitcoin
added a commit
to lnflash/charts
that referenced
this pull request
Aug 8, 2026
…chart 3.2.54) (#253) * feat(flash): fygaro-webhook workload — deployment, service, ingress (chart 3.2.53) Companion to lnflash/flash#472 (Fygaro card top-up payment webhook). Mirrors the bridge-webhook workload 1:1: same env surface, config secret mount, probes, and enabled-gating. - templates: fygaro-webhook-deployment/-service/fygaro-ingress, rendered only when galoy.fygaro.webhook.enabled (default off) - values: galoy.fygaro.webhook workload block (port 4010) and the `fygaro` app-config block under galoy.config (default-off; HMAC secrets injected per environment via deployments overlays) - helper: galoy.fygaro.webhook.fullname Render-verified: enabled=true produces all three objects with the fygaro-webhook-server entrypoint; default renders no workload while the fygaro config still reaches custom.yaml. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb * fix(flash): keep fygaro out of default galoy.config — old images crash on unknown keys The app config schema is additionalProperties:false at the root, so the currently-pinned image rejects the fygaro key in custom.yaml and every config-loading pod crash-loops (api/trigger/websocket/exporter/ ibex-webhook) — which is exactly what the dev-setup smoketest caught. The fygaro config block moves to deployments overlays, added only after the flash image includes the fygaro schema (which defaults the feature off when the key is absent). The chart keeps only the workload block, which renders nothing while disabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb --------- Co-authored-by: Dread <dread@example.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Fygaro card top-ups leave no server-side record: the app opens Fygaro's hosted payment button and nothing ever comes back to Flash. Attribution requires reading a PayPal receipt email, and crediting is a manual treasury transfer. This implements the missing recording path from the TopUp V1 design, mirroring the Bridge webhook server structure throughout. Companion app fix: lnflash/flash-mobile#685 (sends the username in
custom_reference— the parameter Fygaro actually records).What
New
fygaro-webhookserver (default port 4010,yarn fygaro-webhook), mirroringbridge-webhook-server:/health, enabled guard (503 whenfygaro.enabledis off), per-sender rate limitingFygaro-Signature: t=<ts>,v1=<hex>[,v1=…]over${timestamp}.${rawBody},Fygaro-Key-IDselects the shared secret, multiple secrets supported for rotation, timestamp-skew rejection, timing-safe comparePOST /paymenthandler:customReference→ username → accountBridge Transfer Request,provider=Fygaro,fygaro:<transactionId>,Fiat Received) for every payment — unattributed ones (blank/unknown reference, i.e. every pre-fix app build) are recorded and alert ops with the payer email instead of being silently droppeddepositflow)Flag-gated auto-credit (
fygaro.credit.enabled, default OFF):bankownertreasury to the user's cash wallet (USDT-preferred, USD fallback — same wallet resolution as the referral-reward payout) viaintraledgerPaymentSendWalletIdForUsdWalletfygaro:<transactionId>(withPaymentIdempotency, ENG-530 pattern);Pendingsends are treated as credited, never retriedCompletedwith the credited wallet; on failure it stays atFiat Receivedwith a critical alert for manual follow-upConfig:
fygaroblock in the YAML schema (enabled,webhook.port/secrets/timestampSkewMs,credit.enabled) with a default-off baseline;AlertSourcegainsfygaro-webhook; new dedup keys.Rollout
fygaro.enabled: false(baseline) — no behavior changefygaro.webhook.secretsvia config overrides, enablefygaro.enabled— payments now recorded + ops-notified, crediting stays manualfygaro.credit.enabled— full automationTests
test/flash/unit/services/fygaro/webhook-server/:verify-signature.spec.ts— valid sig (key id / no key id / rotation with multiple v1 / ms timestamps), wrong sig, tampered body, missing/malformed header, stale timestamp, no secrets configured, missing raw bodyenabled-guard.spec.ts— 503 when disabled,/healthexempt, pass-through when enabledpayment.spec.ts— full handler matrix: 400 validation, attributed record with credit off, blank/unknown reference → recorded + unattributed alert, ERPNext failure → 500 for provider retry, duplicate delivery short-circuit, credit success (cents conversion + promotion), credit failure (critical alert, no promotion), non-USD guard, promotion-failure-after-credit still reports successyarn tsc-checkclean,yarn eslint-checkclean, full unit suite green (168 suites / 1420 tests).🤖 Generated with Claude Code
https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb