Skip to content

✨ app: add card limit kyc flow#903

Open
aguxez wants to merge 3 commits intokyc-limitfrom
card-limit
Open

✨ app: add card limit kyc flow#903
aguxez wants to merge 3 commits intokyc-limitfrom
card-limit

Conversation

@aguxez
Copy link
Copy Markdown
Contributor

@aguxez aguxez commented Mar 20, 2026


This is part 2 of 2 in a stack made with GitButler:

Summary by CodeRabbit

  • New Features

    • Persona-based verification flow to request card spending limit increases; action now closes the dialog immediately and starts the verification flow.
  • Improvements

    • Unified, cross-platform verification flow with consistent success/cancel/error signals.
    • User-facing notifications added: distinct messages for pending review, verification errors, and other failures.
    • Server accepts a card-limit verification scope so the flow can be initiated from the app.

Open with Devin

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 20, 2026

⚠️ No Changeset found

Latest commit: 13207cb

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replaces the Intercom message flow with an immediate modal close and a card-limit scoped Persona KYC initiation; generalizes Persona inquiry state to use a scope discriminator and adds a new cardLimit KYC entrypoint; expands server token API to accept "cardLimit" scope.

Changes

Cohort / File(s) Summary
Spending limits UI
src/components/card/SpendingLimits.tsx
Button handler now calls onClose() then starts startCardLimitKYC() instead of sending an Intercom message; KYC results show conditional toasts for status === "error" and for rejected APIError with "already approved"/"pending"; rejections still call reportError(error).
Persona utilities
src/utils/persona.ts
Replaced type with scope discriminator and introduced InquiryResult; added startScopedInquiry(scope, ...) helper; refactored startMantecaKYC to use scoped flow; added exported startCardLimitKYC(): Promise<InquiryResult>; unified web/native branches to consistently return InquiryResult; resume logic now checks current.scope.
Server KYC token endpoint
src/utils/server.ts
Expanded getKYCTokens scope union to include \"cardLimit\" (`"basic"

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as SpendingLimits UI
    participant Server as KYC Token Server
    participant Persona as Persona Service

    User->>UI: Click "Increase spending limit"
    UI->>UI: onClose()
    UI->>Server: request getKYCTokens("cardLimit")
    Server-->>UI: return { inquiryId, sessionToken }
    UI->>Persona: startCardLimitKYC(inquiryId, sessionToken)
    Persona->>Persona: initialize cardLimit inquiry
    Persona-->>UI: InquiryResult (complete / cancel / error)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • dieguezguille
  • cruzdanilo
  • franm91
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'add card limit kyc flow' accurately describes the main purpose of the pull request, which introduces a new card limit KYC (Know Your Customer) flow as evidenced by the changes across SpendingLimits.tsx, persona.ts, and server.ts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch card-limit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request implements a new user flow for increasing card spending limits by integrating with the Persona identity verification service. It generalizes the existing Persona integration to support multiple types of KYC inquiries, making the system more flexible and scalable for future identity verification needs. The change provides a direct, automated path for users to request higher limits.

Highlights

  • New Card Limit KYC Flow: Introduced a new Know Your Customer (KYC) flow specifically for increasing card spending limits, integrating with the Persona identity verification platform.
  • Persona Integration Refactoring: Refactored the Persona KYC initiation logic to be more generic, allowing different types of inquiries (e.g., 'manteca' and 'cardLimit') to share common code.
  • UI Integration: The 'Increase spending limit' button in the SpendingLimits component now triggers the new cardLimit Persona KYC flow instead of sending an Intercom message.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

gemini-code-assist[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@sentry
Copy link
Copy Markdown

sentry bot commented Mar 20, 2026

Codecov Report

❌ Patch coverage is 3.22581% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.02%. Comparing base (6fa754f) to head (13207cb).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/utils/persona.ts 0.00% 17 Missing ⚠️
src/components/card/SpendingLimits.tsx 7.69% 12 Missing ⚠️
src/utils/server.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           kyc-limit     #903      +/-   ##
=============================================
- Coverage      72.13%   72.02%   -0.12%     
=============================================
  Files            226      226              
  Lines           8401     8417      +16     
  Branches        2732     2737       +5     
=============================================
+ Hits            6060     6062       +2     
- Misses          2105     2120      +15     
+ Partials         236      235       -1     
Flag Coverage Δ
e2e 72.02% <3.22%> (-0.12%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/components/card/SpendingLimits.tsx (1)

47-50: ⚠️ Potential issue | 🟠 Major

Handle non-success outcomes from startCardLimitKYC().

The handler only logs errors via .catch(reportError) but doesn't surface resolved non-success statuses ("error", "cancel") or backend rejection codes ("already approved", "pending", "failed") to the user. When the backend rejects because the inquiry is already approved or pending, the button appears broken with no feedback.

Consider branching on the result status and displaying appropriate toasts or modals:

🛠️ Suggested approach
 <Button
   onPress={() => {
-    startCardLimitKYC().catch(reportError);
+    startCardLimitKYC()
+      .then((result) => {
+        if (result.status === "complete") {
+          // optionally show success feedback or close modal
+          onClose();
+        }
+        // "cancel" can be silent - user intentionally dismissed
+      })
+      .catch((error) => {
+        // surface API errors like "already approved", "pending"
+        toast.show(t("Unable to increase spending limit"), { native: true });
+        reportError(error);
+      });
   }}
   primary
 >

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e9a0dfa3-5166-45d6-89ec-250e0cecdb5a

📥 Commits

Reviewing files that changed from the base of the PR and between f935605 and 4d9d901.

📒 Files selected for processing (3)
  • src/components/card/SpendingLimits.tsx
  • src/utils/persona.ts
  • src/utils/server.ts

@aguxez aguxez marked this pull request as ready for review March 21, 2026 10:16
chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils/persona.ts (1)

170-175: ⚠️ Potential issue | 🟠 Major

Defer or poll the cache refresh until the webhook confirms the card limit update.

The cardLimit write happens asynchronously in the Persona webhook (server/hooks/persona.ts:271), not in the request path. When onComplete invalidates ["card", "details"] immediately (lines 170-175 web, 210-213 native), a refetch can race the webhook write and cache the old limit. Because the promise resolves right after scheduling the invalidate, there is no second refresh to pick up the new limit once the webhook completes.

Either wait for a webhook acknowledgment before invalidating, or implement polling to ensure the new limit is fetched after the backend update lands.

♻️ Duplicate comments (1)
src/components/card/SpendingLimits.tsx (1)

47-50: ⚠️ Potential issue | 🟠 Major

Still unresolved: handle startCardLimitKYC() outcomes in the button handler.

startCardLimitKYC() now fulfills with { status: "cancel" | "complete" | "error" } for SDK outcomes and only rejects preflight/API failures. This handler still only logs rejections, so pending/already approved/Persona error paths can leave the CTA looking inert and skip any explicit UX or refetch handling. Await the promise here and branch on both the resolved status and the caught API codes.

example
-                <Button
-                  onPress={() => {
-                    startCardLimitKYC().catch(reportError);
-                  }}
+                <Button
+                  onPress={async () => {
+                    try {
+                      const result = await startCardLimitKYC();
+                      if (result.status === "error") {
+                        // show toast / inline state
+                      }
+                    } catch (error) {
+                      reportError(error);
+                      // map pending/already approved to ux + refresh path
+                    }
+                  }}
                   primary
                 >

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ebe6e209-809b-438e-833c-d8347e0478e5

📥 Commits

Reviewing files that changed from the base of the PR and between 4d9d901 and bdcdb4a.

📒 Files selected for processing (3)
  • src/components/card/SpendingLimits.tsx
  • src/utils/persona.ts
  • src/utils/server.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/components/card/SpendingLimits.tsx (1)

48-50: ⚠️ Potential issue | 🟠 Major

Handle the resolved startCardLimitKYC() statuses here.

src/utils/persona.ts:startCardLimitKYC() now resolves { status: "cancel" | "complete" | "error" } for Persona-side outcomes, so this fire-and-forget call silently drops the "error" path after onClose() has already dismissed the sheet. Please await the result and branch on status instead of relying only on .catch(reportError).


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 123d3047-b3d3-489f-bf9d-592d77170f68

📥 Commits

Reviewing files that changed from the base of the PR and between bdcdb4a and 74535e3.

📒 Files selected for processing (3)
  • src/components/card/SpendingLimits.tsx
  • src/utils/persona.ts
  • src/utils/server.ts

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

sentry[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 7 additional findings in Devin Review.

Open in Devin Review

error instanceof APIError &&
(error.text === "already approved" || error.text === "pending")
)
toast.show(t("Your request is being reviewed"), {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 Missing Spanish translation for "Your request is being reviewed"

The i18n key "Your request is being reviewed" used at src/components/card/SpendingLimits.tsx:66 has no corresponding entry in src/i18n/es.json. The existing key "Error verifying identity" does have a translation (src/i18n/es.json:579). i18next will fall back to the English key string, so Spanish-language users will see English text for this toast. Not a code bug, but a content gap worth addressing.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@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.

Reviewed commit: 13207cb420

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +63 to +65
error instanceof APIError &&
(error.text === "already approved" || error.text === "pending")
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle no panda in the card-limit KYC error branch

This branch only treats APIError texts "already approved" and "pending" as expected outcomes, but the same /api/kyc card-limit flow also returns "no panda" for existing card users without a linked Panda profile. In that case the modal is already closed and the user only gets a generic failure toast, with no fallback path to request a limit increase, which is a regression from the previous Intercom-based flow for that cohort.

Useful? React with 👍 / 👎.

Comment on lines 154 to 157
const [{ Client }, { inquiryId, sessionToken }] = await Promise.all([
import("persona"),
tokens ?? getKYCTokens("manteca", await getRedirectURI()),
tokens ?? getKYCTokens(scope, await getRedirectURI()),
]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove web pagehide listener when token bootstrap rejects

On web, the pagehide listener is registered before fetching Persona tokens, but if getKYCTokens(scope, ...) rejects (for common card-limit states like pending/already approved), execution exits before any cleanup callback runs. Repeated retries can accumulate orphaned global listeners until a later pagehide event, causing avoidable leaks and stale controller aborts.

Useful? React with 👍 / 👎.

@aguxez aguxez force-pushed the kyc-limit branch 2 times, most recently from 0ff1b50 to 53b9786 Compare March 28, 2026 09:53
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.

1 participant