fix(topup): send Fygaro custom_reference so card top-ups are attributable - #685
Merged
Conversation
…able Fygaro's checkout ignores the client_reference query parameter (verified against its production JS bundles) — the parameter it records is custom_reference. Every card top-up made through the app has therefore arrived with a blank reference, making manual crediting a guessing game. - rename client_reference -> custom_reference and URL-encode the username - build the payment URL only after the username resolves; drop the `|| "user"` fallback that could produce an unattributable charge, and show the error screen if no username exists - record the target wallet in client_note for the future webhook - restrict card top-ups to the USD wallet (BTC option no longer offered for card payments; notice shown instead) - add unit tests for URL construction, username gating, and the USD-only wallet restriction Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb
…curacy - handleNavigationStateChange now ignores the payment-button URL itself: it embeds the user-controlled username (custom_reference), so a username containing "success"/"failed"/"error"/"cancelled" could fake a payment outcome the moment the page loads (false success screen or an instant failure alert). The button path is a shared constant so the URL and the guard can't drift apart. Predates this PR but this flow owns the line that feeds it. - Retry on the no-username error screen now refetches the account query (cache-first never re-asks the server, and there is no WebView to reload) instead of dead-ending on the same error forever. - client_note comment corrected: it is recorded on the Fygaro payment for ops/audit; the backend credits the USD cash wallet regardless. - tests: trigger-word username never fires a navigation callback off our own URL; Retry invokes refetch when the username never resolved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb
…ring guard The previous fix guarded out any URL containing the payment-button path — but Fygaro's checkout returns to the payment-button URL itself with ?success=0|1 after external-processor flows (PayPal), so the guard swallowed the real success/failure redirect: users would pay and never see the success screen. Outcome detection now parses the URL: the ?success=1|0 query param is the explicit signal (read even on the /pb/ path), and keyword matching is restricted to host+path so the user-controlled username in the query string (custom_reference) still can't fake an outcome. Tests now use the real return shape (/pb/<id>/?success=1 and ?success=0) alongside the trigger-word-username no-op case, closing the gap that let the regression through. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb
check:translation-drift requires every en.json key in all 23 locales; English placeholder text per the existing convention for new keys. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb
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's checkout ignores the
client_referencequery parameter — verified against Fygaro's production JS bundles, which read onlycustom_reference,external_reference, andamountfrom the URL (their helpdesk doc advertisingclient_referenceis stale). Every card top-up made through the app has therefore arrived with a blank reference in Fygaro's dashboard, forcing manual PayPal-email archaeology to figure out who to credit (most recently a $10 top-up on Aug 7 that had to be traced through Kratos/Mongo by buyer email).What
client_reference→custom_referencein the Fygaro payment URL and URL-encode the username (CardPayment.tsx)|| "user"fallback that could produce an unattributable charge. If the account query settles with no username, show the error screen instead of loading the WebViewclient_note(wallet:USD) — recorded on the Fygaro payment for ops/audit (informational; the backend, feat(fygaro): payment webhook — record, notify, and flag-gated auto-credit flash#472, credits the USD cash wallet regardless)TopupDetailspinswallet: "USD"on the CardPayment navigationTopupDetails.usdOnlyNotice(translations regenerated)Tests
New unit specs (this flow previously had zero coverage):
CardPayment.test.tsx— URL usescustom_reference(and neverclient_reference), username URL-encoding,client_notewallet, WebView withheld while the username loads, error screen when no username exists, success-URL navigation carries amount + walletTopupDetails.test.tsx— USD-only for card even in advance mode (BTC still offered for bridge), continue always navigates withwallet: "USD"yarn tsc:checkclean,yarn testfull suite green, eslint clean on touched files (remaining repo lint errors are pre-existing in files this PR doesn't touch).🤖 Generated with Claude Code
https://claude.ai/code/session_01NcEjF6SS3Ci5D4CzZqdPjb