Skip to content

🐛 server: make card creation idempotent - #1082

Merged
nfmelendez merged 1 commit into
basefrom
nicolas
Jun 9, 2026
Merged

🐛 server: make card creation idempotent#1082
nfmelendez merged 1 commit into
basefrom
nicolas

Conversation

@nfmelendez

@nfmelendez nfmelendez commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Card creation is idempotent: existing active cards are detected and adopted rather than duplicated; a warning is logged when an orphan card is adopted.
    • Failures fetching card history now surface appropriate errors instead of creating inconsistent records.
    • Card-limit failures now return a 409 conflict and prevent card creation.
  • Tests

    • Added tests for orphan-card adoption, multiple/missing/non-active card scenarios, card-limit error handling, and card-list fetching.
  • Chores

    • Prepared a patch release entry for this fix.

@nfmelendez
nfmelendez requested a review from cruzdanilo as a code owner June 9, 2026 13:22
@changeset-bot

changeset-bot Bot commented Jun 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 529376a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@exactly/server Patch

Not sure what this means? Click here to learn what changesets are.

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

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds getCards and a CardsResponse schema; POST /api/card now adopts the first active Panda orphan card after KYC approval or falls back to creating a new card, adds 409 card-limit handling, updates Sentry context, extends tests, and adds a changeset.

Changes

Orphan Card Adoption Feature

Layer / File(s) Summary
Panda cards schema and fetch API
server/utils/panda.ts, server/test/utils/panda.test.ts, server/test/e2e.ts
Adds CardsResponse schema and exported getCards(userId) calling Panda /issuing/cards?limit=100; includes unit test for parsing and adds an e2e mock that resolves to an empty array.
Card creation orphan adoption logic
server/api/card.ts
Imports getCards, derives a local pandaId, and after KYC approval fetches existing cards to adopt the first status === "active" orphan (captures a Sentry warning). If none, falls back to panda.createCard, deriving optional card_limit_usd from getAccount; adds 409 card-limit response and updates Sentry error context.
Card creation integration tests for adoption
server/test/api/card.test.ts
Test setup stubs panda.getCards empty by default. Adds tests for: max-cards error (409 & Sentry fingerprint), unrelated 400 (500), single active orphan adoption, multiple active orphan adoption (first only), non-active-only -> create new card, and getCards failure (500).
Release notes
.changeset/swift-pandas-reconcile.md
Adds changeset entry for @exactly/server patch titled “make card creation idempotent”.

Sequence Diagram

sequenceDiagram
  participant Client
  participant CardAPI as POST /api/card
  participant GetCards as getCards(pandaId)
  participant PandaAPI as Panda /issuing/cards
  participant CreateCard as panda.createCard
  participant Database as cards table
  
  Client->>CardAPI: POST /api/card (create signature card)
  CardAPI->>GetCards: fetch existing cards for pandaId
  GetCards->>PandaAPI: GET /issuing/cards?userId&limit=100
  PandaAPI-->>GetCards: []{id,status,last4,...}
  
  alt Active card exists
    GetCards-->>CardAPI: [{status:"active",...}]
    CardAPI->>Database: adopt orphan card by ID
    CardAPI-->>Client: 200 with adopted card
    Note over CardAPI: emit orphan-card-adopted warning
  else No active card
    GetCards-->>CardAPI: [] or [{status:"canceled",...}]
    CardAPI->>CreateCard: create new card (with optional card_limit_usd)
    CreateCard-->>CardAPI: {id,last4,...}
    CardAPI->>Database: persist new card
    CardAPI-->>Client: 200 with new card
  else getCards fails
    GetCards-->>CardAPI: error
    CardAPI-->>Client: 500 error
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • exactly/exa#791: Modifies POST /api/card flows and related tests; overlaps on card-creation logic.
  • exactly/exa#948: Introduced the KYC approval gate used by the card creation flow this PR extends.

Suggested reviewers

  • cruzdanilo
  • dieguezguille
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the changeset: making card creation idempotent by allowing adoption of existing orphan cards after KYC approval.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nicolas
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch nicolas

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 gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request makes card creation idempotent by checking for existing active cards before creating a new one. Specifically, it introduces a getCards utility to fetch a user's cards from the Panda API. If an active card (referred to as an "orphan") is found, it is adopted and returned instead of creating a duplicate, with a warning logged to Sentry. Otherwise, a new card is created as before. Comprehensive unit tests have been added to verify this behavior under various scenarios. No review comments were provided, so there is no feedback to address.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request makes card creation idempotent by checking for existing active cards before attempting to create a new one. It introduces a new getCards utility to fetch a user's cards, adopts an existing active card (logging a warning) if found, and only proceeds with createCard if no active cards are present. Comprehensive unit tests have been added to cover these adoption and creation scenarios. There are no review comments, and we have no additional feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

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

Copy link
Copy Markdown

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: e185c1d7f1

ℹ️ 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 thread server/api/card.ts
Comment thread server/api/card.ts
Comment thread server/api/card.ts
Comment thread server/api/card.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f41b44a6-f390-4df2-b515-81deda267399

📥 Commits

Reviewing files that changed from the base of the PR and between 6e258c2 and e185c1d.

📒 Files selected for processing (5)
  • .changeset/swift-pandas-reconcile.md
  • server/api/card.ts
  • server/test/api/card.test.ts
  • server/test/utils/panda.test.ts
  • server/utils/panda.ts

Comment thread server/api/card.ts
@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.36364% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.99%. Comparing base (6e258c2) to head (529376a).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
server/api/card.ts 86.36% 2 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             base    #1082      +/-   ##
==========================================
+ Coverage   74.00%   74.99%   +0.98%     
==========================================
  Files         244      245       +1     
  Lines       11712    11951     +239     
  Branches     4057     4171     +114     
==========================================
+ Hits         8668     8963     +295     
+ Misses       2704     2650      -54     
+ Partials      340      338       -2     
Flag Coverage Δ
e2e 74.69% <77.27%> (+3.03%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

@nfmelendez

Copy link
Copy Markdown
Contributor Author

@codex review

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

Copy link
Copy Markdown

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: 6ea72e2637

ℹ️ 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 thread server/api/card.ts
Comment thread server/api/card.ts
Comment thread server/utils/panda.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
server/api/card.ts (1)

497-499: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Filter adopted Panda cards to signature-compatible cards before persisting.

Line 498 adopts the first active card only, while Lines 539 and 577 persist/return SIGNATURE_PRODUCT_ID. If Panda returns an active non-signature card first, local state and API response become inconsistent with the provider card that was adopted.

fix sketch
-const card = await getCards(pandaId)
-  .then((pandaCards) => pandaCards.find(({ status }) => status === "active"))
+const card = await getCards(pandaId)
+  .then((pandaCards) =>
+    pandaCards.find(
+      ({ status, productId, type }) =>
+        status === "active" && productId === SIGNATURE_PRODUCT_ID && type === "virtual",
+    ),
+  )

Also extend CardsResponse in server/utils/panda.ts to include productId and type so this filter is enforceable by schema.

Also applies to: 537-540, 572-578


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: db30cc18-31b1-4d3d-a958-ab7703a4b76b

📥 Commits

Reviewing files that changed from the base of the PR and between 6ea72e2 and 529376a.

📒 Files selected for processing (6)
  • .changeset/swift-pandas-reconcile.md
  • server/api/card.ts
  • server/test/api/card.test.ts
  • server/test/e2e.ts
  • server/test/utils/panda.test.ts
  • server/utils/panda.ts

Comment thread server/utils/panda.ts
@nfmelendez

Copy link
Copy Markdown
Contributor Author

@codex review

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

Copy link
Copy Markdown

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: 529376ad8c

ℹ️ 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 thread server/api/card.ts
@nfmelendez
nfmelendez merged commit f4493fa into base Jun 9, 2026
13 of 14 checks passed
@nfmelendez
nfmelendez deleted the nicolas branch June 9, 2026 18:40
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