Skip to content

Conversation

@Jon-edge
Copy link
Collaborator

@Jon-edge Jon-edge commented Jan 1, 2026

image image image

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Requirements

If you have made any visual changes to the GUI. Make sure you have:

  • Tested on iOS device
  • Tested on Android device
  • Tested on small-screen device (iPod Touch)
  • Tested on large-screen device (tablet)

Note

Modernizes the Infinite ramps integration and related UI flows.

  • Switches to headless Infinite API: new challenge/verify paths, KYC link retrieval, improved error parsing, updated types/statuses; adds OTP flow (otpSent + verifyOtp); removes legacy TOS workflow
  • Adds OtpVerificationModal and updates KYC workflow to open provider webview via returned KYC link; pending-status handling revised for new statuses
  • New/updated scenes: RampBankFormScene (country-aware numeric validation + inline errors), RampBankRoutingDetailsScene (copy actions via EdgeRow), RampConfirmationScene layout polish; DevTest passes countryCode
  • Error handling: ErrorCard shows raw error in dev; normalized error display
  • UI refactor: centralized icons in ThemedIcons and replaced row icon usage in EdgeRow
  • Locales: add OTP/KYC/bank validation strings; remove old TOS strings; changelog notes Infinite buy support and UI/UX fixes
  • Test snapshots updated for text/icon prop changes

Written by Cursor Bugbot for commit 38a2f86. This will update automatically on new commits. Configure here.


Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the original commit message:

fix(infinite): redirect to KYC webview for PENDING customers

When a customer with PENDING KYC status already exists, skip the KYC
form and redirect directly to the KYC webview. The form is now only
shown for new customers without a customerId.

Extract openKycWebView helper to reuse webview logic in both the
PENDING status handling and new customer form submission flows.

It explained "why" this change.

@Jon-edge Jon-edge force-pushed the jon/infinite-v2 branch 2 times, most recently from 9641ee1 to 8aa54bb Compare January 7, 2026 01:10
Comment on lines 157 to 167
// Try to parse as HTTP error response
const httpErrorResponse = asMaybe(asInfiniteHttpErrorResponse)(data)
if (httpErrorResponse != null) {
// OTP verification errors should be localized for the modal:
if (
httpErrorResponse.statusCode === 400 &&
urlStr.includes('/v1/headless/customers/verify-otp')
) {
throw new InfiniteApiError(
400,
lstrings.ramp_kyc_error_title,
lstrings.ramp_otp_invalid_code
)
}

const detail = Array.isArray(httpErrorResponse.message)
? httpErrorResponse.message.join('; ')
: httpErrorResponse.message
throw new InfiniteApiError(
httpErrorResponse.statusCode,
httpErrorResponse.error,
detail
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize during this review turn that no localized errors should be implemented in the infiniteApi.

How we've handle localization and plan to handle localization is a the ramp plugin level, not the API library level. For example we translate duplicate_record errors in kycWorkflow.ts:

          if ('error' in customerResponse) {
            if (
              customerResponse.error instanceof InfiniteApiError &&
              customerResponse.error.detail.includes('duplicate_record')
            ) {
              throw new I18nError(
                lstrings.ramp_signup_failed_title,
                lstrings.ramp_signup_failed_account_existsmessage
              )
            }

            throw customerResponse.error
          }

This means we should always throw an InfiniteApiError from the HTTP response with no translations. Then we handle and translate errors in the workflows using I18nError.

This design is intentional. Localization strings are a GUI concern and ramp plugins are GUI plugins.

})
)

export const asInfiniteHttpErrorResponse = asJSON(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have asInfiniteErrorResponse why is this needed? It only seems needed cause we have a special handling for errors for localization? Well if we don't do localization here, then we can perhaps omit this additional cleaner?

Copy link
Collaborator Author

@Jon-edge Jon-edge Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the type differs from asInfiniteErrorResponse.

These HTTP errors were failing the cleaner previously in fetchInfinite so I was unable to catch it later as a recognized InfiniteApiError error to display the appropriate message. Previously, if fetchInfinite did not recognize the error, it only returns a generic Error with response.status, obfuscating the other required data.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I see.

So what we want is to extend asInfiniteErrorResponse with the new type information. It'd be a union of of the two shapes. Then we'd treat each shape the same (return a infinite error object).

We still want to move the special handling of the verify-otp error at the workflow level.

@Jon-edge Jon-edge force-pushed the jon/infinite-v2 branch 2 times, most recently from 01763af to f83f2bc Compare January 9, 2026 19:50
}
if (personalInfoUuid != null) {
await vault.updatePersonalInfo(personalInfoUuid, personalInfo)
} else {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExitError shows unfriendly message when user cancels OTP

Medium Severity

When the user cancels the OTP verification modal, an ExitError with the message "User cancelled OTP verification" is thrown from inside the onSubmit callback. This error is caught by RampKycFormScene's try/catch and displayed to the user via ErrorCard. The result is a non-localized, developer-facing message shown for a deliberate user action. ExitError is intended for graceful workflow exits (handled by handleExitErrorsGracefully), but here it's being caught by the form's error handler instead, causing poor UX.

Fix in Cursor Fix in Web

}
if (personalInfoUuid != null) {
await vault.updatePersonalInfo(personalInfoUuid, personalInfo)
} else {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User cancellation shows unexpected error in form

Medium Severity

When the user cancels OTP verification, ExitError is thrown inside the form's onSubmit callback. The RampKycFormScene catches all errors and displays them via ErrorCard. Since ExitError is not an I18nError, it triggers the generic "Unexpected error" message with a "Report Error" button. User cancellation is a normal flow and shouldn't show an error. The ExitError pattern expects errors to bubble up to handleExitErrorsGracefully in the plugin, but form scene error handling intercepts it first.

Fix in Cursor Fix in Web

@Jon-edge Jon-edge force-pushed the jon/infinite-v2 branch 3 times, most recently from f354054 to d56b9b8 Compare January 9, 2026 22:23
city: contactInfo.city,
state: contactInfo.state,
postalCode: contactInfo.postalCode,
country: 'US'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded address country inconsistent with countryCode parameter

Low Severity

The address.country field is hardcoded to 'US' while the top-level countryCode parameter uses the dynamically passed value from request.regionCode.countryCode. This creates an inconsistency in the createCustomer API request where the top-level countryCode could differ from the nested address.country. If the ramp is extended to support other countries, this would cause the API request to have mismatched country values. The address.country should use the countryCode parameter instead of the hardcoded 'US' string.

Fix in Cursor Fix in Web

Update challenge and verify endpoints to use the new headless API paths:
- /v1/auth/wallet/challenge → /v1/headless/auth/wallet/challenge
- /v1/auth/wallet/verify → /v1/headless/auth/wallet/verify
- Flatten customer request schema: remove nested 'data' wrapper
- Remove residentialAddress from request (no longer supported)
- Remove kycLinkUrl and usedPersonaKyc from response
- New customers now start with PENDING status instead of ACTIVE
- Add getKycLink API method for obtaining KYC verification links
- Update kycWorkflow to use new getKycLink endpoint
- Change status values: PENDING, IN_REVIEW, ACTIVE, NEED_ACTIONS, REJECTED
- Add sessionStatus field to KYC status response
- Update kycWorkflow status checks for new format
- ACTIVE replaces 'approved' as the completed status
- PENDING replaces 'not_started'/'incomplete'
@Jon-edge Jon-edge force-pushed the jon/infinite-v2 branch 3 times, most recently from c644d14 to 32cc49b Compare January 14, 2026 18:50
Change bridgeAccountId to externalAccountId in customer accounts response
to match new API format that abstracts away the underlying provider.
The TOS (Terms of Service) workflow is no longer part of the new Infinite API:
- Delete tosWorkflow.ts
- Remove tosWorkflow import and call from infiniteRampPlugin.ts
- Remove asInfiniteTosStatus, InfiniteTosStatus, asInfiniteTosResponse types
- Remove getTos method from InfiniteApi interface and implementation
- Remove ramp_tos_* locale strings from en_US.ts and all JSON files
For already-KYC'ed emails
Field focus doesn't properly move the focused field above the floating next button, so just changing it into `SceneButtons`
We should consider adding more information to this mostly blank scene...
Account and routing number length check failures will disable the next button, and length errors in these fields immediately show in the field on blur.
Fix spacing throughout, remove styled(), reuse existing components, add copy functionality.
The `ErrorCard` solved the situation of user-unfriendly error messages, but hid useful information for quickly debugging from QA and customers without having to deal with logs.

Expose a way to show the error if dev mode is enabled as a compromise.
@Jon-edge Jon-edge merged commit d975965 into develop Jan 15, 2026
4 checks passed
@Jon-edge Jon-edge deleted the jon/infinite-v2 branch January 15, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants