Skip to content

✨ server: add wallet provisioning endpoint#949

Merged
cruzdanilo merged 1 commit intomainfrom
server-provisioning
May 5, 2026
Merged

✨ server: add wallet provisioning endpoint#949
cruzdanilo merged 1 commit intomainfrom
server-provisioning

Conversation

@aguxez
Copy link
Copy Markdown
Contributor

@aguxez aguxez commented Apr 10, 2026

Closes #440


Open with Devin

Summary by CodeRabbit

  • New Features

    • Added a wallet provisioning endpoint allowing authenticated users to retrieve provisioning details (card ID and time-based secret) for an eligible card.
  • Tests

    • Added comprehensive tests for successful provisioning, processor error mappings, deleted/missing card, missing external association, and missing credential scenarios.
  • Chores

    • Added a changeset declaring a patch release documenting the new wallet provisioning endpoint.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 10, 2026

🦋 Changeset detected

Latest commit: 8e35a6d

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

@aguxez aguxez force-pushed the server-provisioning branch from 7de9bc2 to 1d0aa0f Compare April 10, 2026 09:27
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 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

Adds a new authenticated GET /wallet endpoint that loads a credential's ACTIVE|FROZEN card, calls Panda for processor details, returns processorCardId/timeBasedSecret, adds a Panda utility, tests covering success and error mappings, and adds a changeset for a patch release.

Changes

Cohort / File(s) Summary
Wallet Endpoint
server/api/card.ts
New authenticated GET /wallet route: reads credentialId from cookie, loads credential (pandaId + first `ACTIVE
Panda Utility
server/utils/panda.ts
Added exported getProcessorDetails(cardId: string) which GETs /issuing/cards/${cardId}/processorDetails and validates/returns { processorCardId, timeBasedSecret }.
Tests
server/test/api/card.test.ts
Added wallet test suite seeding credentials/cards and asserting: success for active/frozen cards (200 with processor details and stub verification), Panda error mappings (404→404 { code: "no card" }, 5xx→500), and credential/panda/card absence responses (500/403/404 as appropriate).
Release Metadata
.changeset/chilly-suns-dress.md
New changeset declaring a patch release for @exactly/server referencing the wallet provisioning endpoint.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant API as GET /wallet
    participant DB as Database
    participant Panda as Panda API

    Client->>API: GET /wallet (credentialId cookie)
    API->>DB: Load credential (pandaId, first ACTIVE|FROZEN card)
    alt no credential
        DB-->>API: not found
        API-->>Client: 500 { code: "no credential" }
    else credential missing pandaId
        DB-->>API: credential (no pandaId)
        API-->>Client: 403 { code: "no panda" }
    else no eligible card
        DB-->>API: credential (no eligible card)
        API-->>Client: 404 { code: "no card" }
    else credential + card found
        DB-->>API: credential + cardId
        API->>Panda: GET /issuing/cards/{cardId}/processorDetails
        alt processor details found
            Panda-->>API: { processorCardId, timeBasedSecret }
            API-->>Client: 200 { cardId, cardSecret }
        else processor 404
            Panda-->>API: 404
            API-->>Client: 404 { code: "no card" }
        else other error
            Panda-->>API: error
            API-->>Client: 500 (error)
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • cruzdanilo
  • nfmelendez
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'add wallet provisioning endpoint' accurately and directly describes the main change: a new authenticated GET /wallet endpoint for wallet provisioning. It is specific, clear, and concise without unnecessary details.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 server-provisioning

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@aguxez aguxez marked this pull request as draft April 10, 2026 09:27
devin-ai-integration[bot]

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as resolved.

@sentry
Copy link
Copy Markdown

sentry Bot commented Apr 10, 2026

✅ All tests passed.

@aguxez aguxez force-pushed the server-provisioning branch 3 times, most recently from af7c987 to 9495bf1 Compare April 10, 2026 13:10
coderabbitai[bot]

This comment was marked as resolved.

@aguxez aguxez force-pushed the server-provisioning branch from 9495bf1 to 0975d4d Compare April 10, 2026 13:46
coderabbitai[bot]

This comment was marked as resolved.

@aguxez aguxez marked this pull request as ready for review April 10, 2026 16:12
@aguxez aguxez force-pushed the server-provisioning branch from 0975d4d to 0ed88f7 Compare April 13, 2026 08:27
coderabbitai[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

@aguxez aguxez force-pushed the server-provisioning branch from 0ed88f7 to ba4376c Compare April 13, 2026 09:21
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)
server/test/api/card.test.ts (1)

867-869: ⚠️ Potential issue | 🟡 Minor

Use Panda in the mocked ServiceError for consistency.

"Rain" works, but using the real provider label keeps test fixtures closer to production behavior.

Suggested fix
-      vi.spyOn(panda, "getProcessorDetails").mockRejectedValueOnce(new ServiceError("Rain", 500, "internal error"));
+      vi.spyOn(panda, "getProcessorDetails").mockRejectedValueOnce(new ServiceError("Panda", 500, "internal error"));

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7e528c82-bb28-42c9-8cd9-b0f075ed5b87

📥 Commits

Reviewing files that changed from the base of the PR and between 0ed88f7 and ba4376c.

📒 Files selected for processing (4)
  • .changeset/chilly-suns-dress.md
  • server/api/card.ts
  • server/test/api/card.test.ts
  • server/utils/panda.ts

sentry[bot]

This comment was marked as resolved.

@aguxez aguxez force-pushed the server-provisioning branch from ba4376c to 2f6644b Compare April 15, 2026 08:39
coderabbitai[bot]

This comment was marked as resolved.

@aguxez aguxez force-pushed the server-provisioning branch 2 times, most recently from 9662eac to b49e535 Compare April 21, 2026 14:10
chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@aguxez aguxez force-pushed the server-provisioning branch from b49e535 to 1cf381c Compare April 21, 2026 14:40
@aguxez aguxez force-pushed the server-provisioning branch from e5b5193 to 2d5b10a Compare May 5, 2026 11:29
chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@aguxez aguxez force-pushed the server-provisioning branch from 2d5b10a to 27851ed Compare May 5, 2026 12:24
chatgpt-codex-connector[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 0 new potential issues.

View 11 additional findings in Devin Review.

Open in Devin Review

@aguxez aguxez force-pushed the server-provisioning branch 2 times, most recently from e304a18 to 231dc19 Compare May 5, 2026 13:08
chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@aguxez aguxez force-pushed the server-provisioning branch from f093130 to d5d7aa9 Compare May 5, 2026 15:36
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@cruzdanilo cruzdanilo force-pushed the server-provisioning branch from 7a89e5d to d7fcd10 Compare May 5, 2026 18:22
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 9 additional findings in Devin Review.

Open in Devin Review

Comment thread server/test/api/card.test.ts Outdated
Comment on lines +1552 to +1573
it("propagates stale card provisioning errors when user lookup fails", async () => {
const stale = new ServiceError("Panda", 404, "not found");
const forbidden = new ServiceError(
"Panda",
403,
'{"message":"User exists but is not approved yet","error":"ForbiddenError","statusCode":403}',
"ForbiddenError",
"User exists but is not approved yet",
);
vi.spyOn(panda, "getSecrets").mockResolvedValueOnce(panTemplate);
vi.spyOn(panda, "getPIN").mockResolvedValueOnce(pinTemplate);
vi.spyOn(panda, "getCard").mockResolvedValueOnce(cardTemplate);
vi.spyOn(panda, "getUser").mockRejectedValueOnce(forbidden);
vi.spyOn(panda, "getProcessorDetails").mockRejectedValueOnce(stale);

const response = await appClient.index.$get(
{ header: { sessionid: "fakeSession" }, query: { scope: "provisioning" } },
{ headers: { "test-credential-id": "default" } },
);

expect(response.status).toBe(500);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 Test for concurrent getUser + getProcessorDetails failures has non-deterministic captureException behavior

In the test "propagates stale card provisioning errors when user lookup fails" (server/test/api/card.test.ts:1552-1573), both getUser and getProcessorDetails are mocked to reject. The getUser catch handler at server/api/card.ts:312-337 catches its ForbiddenError and calls captureException (because shouldCapture evaluates to true since card status is "ACTIVE"), then returns null. Meanwhile getProcessorDetails rejects, causing Promise.all to reject. The test only asserts response.status === 500 but does not assert on captureException. Per the server rules' "assert aggressively" mandate, this is a gap — the captureException call from the getUser handler is observable side-effect behavior that should be verified.

Open in Devin Review

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

@cruzdanilo cruzdanilo force-pushed the server-provisioning branch from d7fcd10 to 8e35a6d Compare May 5, 2026 18:51
Base automatically changed from signature to main May 5, 2026 18:51
@cruzdanilo cruzdanilo merged commit 8e35a6d into main May 5, 2026
6 of 9 checks passed
@cruzdanilo cruzdanilo deleted the server-provisioning branch May 5, 2026 18:51
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.

server: push provisioning

3 participants