Skip to content

🐛 server: make card creation idempotent#1093

Merged
cruzdanilo merged 1 commit into
mainfrom
create-card-idempotency
Jun 12, 2026
Merged

🐛 server: make card creation idempotent#1093
cruzdanilo merged 1 commit into
mainfrom
create-card-idempotency

Conversation

@nfmelendez

@nfmelendez nfmelendez commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes
    • Card creation process is now idempotent, gracefully handling edge cases by reusing existing orphan cards instead of attempting to create duplicates, improving system stability
    • Added explicit error response with HTTP 409 status when card-creation limits are exceeded, providing clearer feedback for card management

@nfmelendez nfmelendez requested a review from cruzdanilo as a code owner June 11, 2026 19:38
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@nfmelendez, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 44 minutes and 42 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4a57ce02-f1a4-490a-b611-36cd4b011318

📥 Commits

Reviewing files that changed from the base of the PR and between 708e81f and 73b8aa4.

📒 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

Walkthrough

This PR makes card creation idempotent by fetching a user's existing cards from Panda and adopting active orphan cards instead of always creating new ones. It adds a getCards() utility, modifies the card-creation handler to check for and return existing active cards with Sentry logging, adds HTTP 409 response handling for card-limit errors, and includes comprehensive test coverage.

Changes

Card creation idempotency via orphan card adoption

Layer / File(s) Summary
Panda cards utility and schema
server/utils/panda.ts, server/test/utils/panda.test.ts
Valibot array import added; new CardsResponse schema defines card objects with id, status, last4, and expiration fields; exported getCards(userId) function fetches issued cards from Panda /issuing/cards endpoint with limit=100; unit test verifies correct endpoint, query params, and response parsing.
Card adoption flow in card-creation handler
server/api/card.ts
Imports getCards from panda utility; stores credential's Panda ID in local pandaId variable; fetches user's cards via getCards(pandaId) before creating new card; detects first active card and returns it with Sentry warning ("orphan card adopted"); persists adopted card reference to database; falls back to creating new card if no active card exists.
Card-limit error handling with 409 response
server/api/card.ts
OpenAPI route responses extended with HTTP 409 "Conflict" mapping for card limit reached error; error handler detects Panda ServiceError 400 with "maximum number of cards allowed" message; captures exception to Sentry with card-limit-reached fingerprint and extra metadata; returns HTTP 409 with { code: "card limit reached" } instead of 500.
Test coverage for all card-creation scenarios
server/test/api/card.test.ts, server/test/e2e.ts
Default test mock setup includes panda.getCards returning empty array; new tests assert card-limit error response (409), unrelated 400 errors treated as 500, orphan card adoption (skipping creation and persisting reference), multiple active orphans (adopting only first), non-active cards (forcing creation and skipping Sentry capture), and getCards failure (returning 500 without card creation); e2e panda mock updated with getCards stub.
Release metadata
.changeset/swift-pandas-reconcile.md
Changeset entry documents patch release for @exactly/server with idempotent card creation fix.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • exactly/exa#1082: Identical card idempotency changes (orphan card adoption via panda.getCards, 409 card-limit handling, comprehensive test coverage).
  • exactly/exa#791: Prior card-creation route and Panda error handling updates; this PR extends the pandaId usage and error capture paths introduced there.
  • exactly/exa#948: Earlier KYC gating changes around pandaId checks in card-creation handler; this PR adjusts pandaId variable usage while maintaining that flow.

Suggested reviewers

  • cruzdanilo
🚥 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: making card creation idempotent by adopting existing orphan cards instead of always creating new ones.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch create-card-idempotency
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch create-card-idempotency

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 ("orphan cards") and adopting them instead of creating duplicates. It also introduces a getCards utility to fetch a user's cards, handles card limit errors by returning a 409 Conflict status, and adds comprehensive unit and integration tests to verify these behaviors. There are no review comments, so I have no 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.

@changeset-bot

changeset-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 73b8aa4

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

@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: 137d157985

ℹ️ 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
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

✅ All tests passed.

@nfmelendez nfmelendez force-pushed the create-card-idempotency branch from 137d157 to 708e81f Compare June 12, 2026 18:43

@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

Caution

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

⚠️ Outside diff range comments (1)
server/api/card.ts (1)

537-539: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use upsert semantics when persisting an adopted orphan card.

This path always inserts card.id. If a row with the same id already exists locally in DELETED state, the PK collision returns 500 and breaks idempotent recovery. Persist adoption with conflict handling (reactivate/update) instead of unconditional insert.

proposed fix
-            await database
-              .insert(cards)
-              .values([{ id: card.id, credentialId, lastFour: card.last4, mode, productId: SIGNATURE_PRODUCT_ID }]);
+            await database
+              .insert(cards)
+              .values([{ id: card.id, credentialId, lastFour: card.last4, mode, productId: SIGNATURE_PRODUCT_ID }])
+              .onConflictDoUpdate({
+                target: cards.id,
+                set: {
+                  credentialId,
+                  lastFour: card.last4,
+                  mode,
+                  status: "ACTIVE",
+                  productId: SIGNATURE_PRODUCT_ID,
+                },
+              });

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 113bd687-f60c-4837-881c-a214921b77ac

📥 Commits

Reviewing files that changed from the base of the PR and between df0fd6c and 708e81f.

📒 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/api/card.ts
@nfmelendez nfmelendez force-pushed the create-card-idempotency branch from 708e81f to 73b8aa4 Compare June 12, 2026 18:58
@cruzdanilo cruzdanilo merged commit 73b8aa4 into main Jun 12, 2026
1 of 4 checks passed
@cruzdanilo cruzdanilo deleted the create-card-idempotency branch June 12, 2026 19:00
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.

2 participants