Skip to content

Card contract: Idempotency-Key + 409s, DECLINED status, CARD_TRANSACTION.DECLINED, threeDSecurePassword#686

Draft
kphurley7 wants to merge 7 commits into
mainfrom
kph/cards-idempotency-txn-webhook
Draft

Card contract: Idempotency-Key + 409s, DECLINED status, CARD_TRANSACTION.DECLINED, threeDSecurePassword#686
kphurley7 wants to merge 7 commits into
mainfrom
kph/cards-idempotency-txn-webhook

Conversation

@kphurley7

@kphurley7 kphurley7 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Contract prerequisite (C1) for the Striga-under-Grid card work (design: lightsparkdev/webdev#30261; server-side stack lightsparkdev/webdev#30314 → #30403).

Changes

  • POST /cards: declares the Idempotency-Key header, mirroring the money-movement operations' shape. The server now requires it for card programs on EUR-funded (EU) platforms, where card creation is a non-idempotent fee-bearing provider call.
  • POST /cards + PATCH /cards/{id}: declare the 409 responses the server actually emits (key replayed with a different body / same key still processing; provider-reported state conflicts), reusing the existing Error409 component. No new error codes invented.
  • DECLINED terminal status on CardTransactionStatus: declined authorizations are first-class transactions (no funds moved, terminal), needed for read parity with the EU provider's decline stream.
  • CARD_TRANSACTION.DECLINED webhook type: extends the CARD_TRANSACTION.* family merged in feat(webhooks): add CARD_TRANSACTION webhook contract #690 (this branch originally proposed a single CARD.TRANSACTION_UPDATE type; rebased onto the merged family and dropped the competing type). Includes a declined delivery example and doc-snippet rework to the per-status family semantics.
  • threeDSecurePassword (write-only, optional, 8-36 chars) on card create: static 3-D Secure challenge password pass-through for EU card programs; Grid generates when omitted; rejected on programs without a static-password factor.

Bundles regenerated with make build (Redocly).

Known conformance note (not changed here)

The existing PATCH 409 prose says state: CLOSED on an already-CLOSED card returns CARD_ALREADY_CLOSED — the server implementation (both providers) actually treats CLOSED→CLOSED as an idempotent 200. Reconciling contract-vs-implementation is part of the card contract-conformance workstream (design §10 Phase 0a).

@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
grid-flow-builder Ignored Ignored Preview Jul 17, 2026 7:26pm
grid-wallet-demo Ignored Ignored Preview Jul 17, 2026 7:26pm

Request Review

@mintlify

mintlify Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Grid 🟢 Ready View Preview Jul 14, 2026, 4:04 PM

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

✱ Stainless preview builds for grid

This PR will update the grid SDKs with the following commit messages.

cli

feat(api): add idempotency-key parameter to cards issue method

go

feat(api): add card_transaction_update webhook, idempotency_key param to cards.issue

kotlin

feat(api): add idempotency_key to cards.issue, CardTransactionUpdate webhook

openapi

feat(api): add idempotency to card creation, card transaction update webhook

php

feat(api): add idempotencyKey to cards.issue, CardTransactionUpdate webhook

python

feat(api): add idempotency_key to cards.issue, CardTransactionUpdateWebhookEvent

ruby

feat(api): add CardTransactionUpdateWebhookEvent, idempotency_key to cards.issue

typescript

feat(api): add CardTransactionUpdate webhook, Idempotency-Key to cards issue

Edit this comment to update them. They will appear in their respective SDK's changelogs.

grid-typescript studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ❗test ✅

npm install https://pkg.stainless.com/s/grid-typescript/919c175ce15adcdca1221a5d68c6a4627ecf29fd/dist.tar.gz
grid-openapi studio · code · diff

Your SDK build had at least one "warning" diagnostic, but this did not represent a regression.
generate ⚠️

grid-ruby studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ✅test ✅

grid-go studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ❗test ❗

go get github.com/stainless-sdks/grid-go@8ad1f5f6c1a33dd58499955dde84411946d3646d
grid-kotlin studio · code · diff

Your SDK build had at least one "warning" diagnostic, but this did not represent a regression.
generate ⚠️build ✅lint ✅test ❗

grid-python studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ❗test ❗

pip install https://pkg.stainless.com/s/grid-python/1c280bca81add49c6119f55065a77cc511617407/grid-0.0.1-py3-none-any.whl
grid-php studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅lint ✅test ✅

grid-cli studio · code · diff

Your SDK build had at least one "warning" diagnostic, but this did not represent a regression.
generate ⚠️build ❗lint ❗test ❗


This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-07-17 19:30:01 UTC

@mintlify

mintlify Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Grid 🟡 Building Jul 14, 2026, 4:02 PM

@kphurley7
kphurley7 force-pushed the kph/cards-idempotency-txn-webhook branch from 4e82188 to 2edd585 Compare July 16, 2026 15:03
@kphurley7 kphurley7 changed the title Add card Idempotency-Key, 409 responses, and CARD.TRANSACTION_UPDATE webhook Card contract: Idempotency-Key + 409s, DECLINED status, CARD_TRANSACTION.DECLINED, threeDSecurePassword Jul 16, 2026
@kphurley7

Copy link
Copy Markdown
Contributor Author

@greptileai review this

@kphurley7

Copy link
Copy Markdown
Contributor Author

@faraday review this

@ls-bolt

ls-bolt Bot commented Jul 17, 2026

Copy link
Copy Markdown

📌 Bolt Status

2026-07-17 14:58:46 UTC — ⚡ Agent cryptic-nexus(#1) started
2026-07-17 15:15:42 UTC — ⚡ Agent cryptic-nexus-2(#2) started
2026-07-17 19:21:44 UTC — ⚡ Agent cryptic-nexus-3(#3) started


Feedback

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends the card contract with five additions for Striga-under-Grid (EU card program) support: an Idempotency-Key header on POST /cards, 409 responses on both card endpoints, a DECLINED terminal status and matching CARD_TRANSACTION.DECLINED webhook type, and the threeDSecurePassword write-only field on card create. Documentation across the mintlify/snippets/cards/ family is updated in parallel to drop references to the now-retired generic transaction webhook stream and point everything to the CARD_TRANSACTION.* family.

  • POST /cards idempotency: Header declared required: false but described as required for EUR-funded platforms; server enforces, 409 documented, <uuid> example matches the established spec convention.
  • DECLINED status: Added to CardTransactionStatus enum and WebhookType/CardTransactionWebhook schemas; a concrete declined webhook example is included; Error409 enum already covers the reused CONFLICT code for PATCH provider conflicts.
  • threeDSecurePassword: writeOnly: true, 8–36 chars, optional; lint suppressions added to both Redocly and Spectral configs for the non-standard threeDSecure casing.

Confidence Score: 5/5

Safe to merge — all schema additions are additive, lint suppressions are correctly mirrored in both linters, and the bundled openapi.yaml matches the source changes.

Every new schema element (DECLINED status, CARD_TRANSACTION.DECLINED webhook type, threeDSecurePassword, Idempotency-Key header, 409 responses) is correctly wired: enum values appear in all the right places (CardTransactionStatus, WebhookType, CardTransactionWebhook), the sibling description on refundedAmount is valid in the spec's OpenAPI 3.1 dialect, and the Error409 enum already contains CONFLICT which covers the new provider-conflict case. Two cosmetic observations from prior reviews (enum ordering, ASCII diagram) remain open but do not affect correctness.

mintlify/snippets/cards/reconciliation.mdx — the ASCII state-machine diagram still lacks the AUTHORIZED → DECLINED branch (carry-over from a prior review comment).

Important Files Changed

Filename Overview
openapi/paths/cards/cards.yaml Adds Idempotency-Key header (required: false, consistent with other endpoints) and 409 response for idempotency conflicts; aligns with existing spec conventions and PR description.
openapi/paths/cards/cards_{id}.yaml Adds CONFLICT error code to PATCH 409 description for provider-reported state conflicts; CONFLICT is already in Error409.yaml enum.
openapi/components/schemas/cards/CardTransactionStatus.yaml Adds DECLINED to enum and description table; enum position (between REFUNDED and EXCEPTION) doesn't match description-table position (second, after AUTHORIZED) — noted in a prior review.
openapi/components/schemas/cards/CardCreateRequest.yaml Adds threeDSecurePassword (writeOnly, optional, 8-36 chars); lint suppressions for the non-standard casing are correctly mirrored in both .redocly.lint-ignore.yaml and .spectral.yaml.
openapi/webhooks/card-transaction.yaml Adds DECLINED to the webhook description and a complete declined example payload; all required CardTransaction fields are present and zeros on all summaries are correct for a terminal-no-funds event.
openapi/components/schemas/cards/CardTransaction.yaml Adds a sibling description to refundedAmount alongside its $ref; valid in OpenAPI 3.1 (spec is openapi: 3.1.0) and correctly documents the EU/US refund-linkage difference.
mintlify/snippets/cards/reconciliation.mdx Updates status table to include DECLINED and rewrites webhook section, but the ASCII state-machine diagram still lacks the AUTHORIZED → DECLINED terminal branch (raised in a prior review).
mintlify/snippets/cards/webhooks.mdx Fully rewrites the card-transaction lifecycle section to document all CARD_TRANSACTION.* webhook types including DECLINED; adds a complete settled payload example.
.spectral.yaml Adds an overrides section to suppress field-names-camelCase for threeDSecurePassword, correctly mirroring the .redocly.lint-ignore.yaml exception.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Authorization Received] --> B{Issuer Decision}
    B -- Approved --> C[AUTHORIZED]
    B -- Rejected --> D[DECLINED - terminal, no funds moved]
    C --> E[PARTIALLY_SETTLED - one or more clearings landed]
    E --> F[SETTLED - all clearings posted]
    F --> G[REFUNDED - RETURN received]
    C --> H[EXCEPTION - pull failed after settlement]
    E --> H
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Authorization Received] --> B{Issuer Decision}
    B -- Approved --> C[AUTHORIZED]
    B -- Rejected --> D[DECLINED - terminal, no funds moved]
    C --> E[PARTIALLY_SETTLED - one or more clearings landed]
    E --> F[SETTLED - all clearings posted]
    F --> G[REFUNDED - RETURN received]
    C --> H[EXCEPTION - pull failed after settlement]
    E --> H
Loading

Reviews (4): Last reviewed commit: "LINT: spectral override for threeDSecure..." | Re-trigger Greptile

kphurley7 and others added 5 commits July 17, 2026 10:03
…webhook

- POST /cards declares the Idempotency-Key header (same shape as the
  money-movement operations); required for EU card programs.
- POST /cards and PATCH /cards/{id} declare 409 conflict responses via
  the existing Error409 component (idempotency replay conflicts;
  provider state conflicts).
- New CARD.TRANSACTION_UPDATE webhook type + CardTransactionUpdateWebhook
  payload (mirrors CardStateChangeWebhook; data = CardTransaction),
  registered in the webhook operations and card docs snippets.

Bundles regenerated with make build; lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kzvhCS9mZrkHaH7E5EQ94
Upstream #690 merged a canonical per-status card-transaction webhook
contract (CARD_TRANSACTION.AUTHORIZED/PARTIALLY_SETTLED/SETTLED/
REFUNDED/EXCEPTION) that supersedes this branch's single
CARD.TRANSACTION_UPDATE type. Drop CardTransactionUpdateWebhook and its
operation/registration/enum value, rewrite the card doc snippets to the
family semantics, and extend the family with CARD_TRANSACTION.DECLINED
(webhook enum, operation description, and a declined example) to match
the DECLINED terminal status this branch adds to CardTransactionStatus.
Bundles regenerated with make build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kzvhCS9mZrkHaH7E5EQ94
The custom camelCase checker rejects the consecutive-capital hump in
threeDSecurePassword, but threeDSecure is the industry-standard casing
for 3DS and the field matches the EU provider's wire name. Recorded as
a deliberate exception in the lint-ignore file (same mechanism as the
existing no-ambiguous-paths entry) rather than renaming across the SDK
and server.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kzvhCS9mZrkHaH7E5EQ94
Comment on lines +27 to +35
description: >-
Optional static 3-D Secure password to set on the card, for card
programs whose issuer challenges online purchases with a static
password factor (EU/Striga cards). 8-36 characters; Latin letters,
digits, and the issuer's allowed special characters. Grid forwards it
to the provider and never stores or returns it. When omitted, Grid
generates a strong password; the cardholder-facing view/change flow
is delivered through the secure card UI. Rejected for card programs
without a static-password challenge factor (US/Lithic cards).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 The description mentions EU/Striga cards and US/Lithic cards by provider name. The API contract is provider-agnostic in the rest of the spec (card programs are described by geography or feature set, not by the underlying vendor name), and leaking Striga / Lithic into a public description couples the documented interface to implementation details that may change. Describing the distinction by platform configuration keeps the contract stable.

Suggested change
description: >-
Optional static 3-D Secure password to set on the card, for card
programs whose issuer challenges online purchases with a static
password factor (EU/Striga cards). 8-36 characters; Latin letters,
digits, and the issuer's allowed special characters. Grid forwards it
to the provider and never stores or returns it. When omitted, Grid
generates a strong password; the cardholder-facing view/change flow
is delivered through the secure card UI. Rejected for card programs
without a static-password challenge factor (US/Lithic cards).
description: >-
Optional static 3-D Secure password to set on the card, for card
programs whose issuer challenges online purchases with a static
password factor (EUR-funded EU card programs). 8-36 characters; Latin
letters, digits, and the issuer's allowed special characters. Grid
forwards it to the provider and never stores or returns it. When
omitted, Grid generates a strong password; the cardholder-facing
view/change flow is delivered through the secure card UI. Rejected for
card programs without a static-password challenge factor (USD-funded
card programs).
Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/components/schemas/cards/CardCreateRequest.yaml
Line: 27-35

Comment:
The description mentions `EU/Striga cards` and `US/Lithic cards` by provider name. The API contract is provider-agnostic in the rest of the spec (card programs are described by geography or feature set, not by the underlying vendor name), and leaking `Striga` / `Lithic` into a public description couples the documented interface to implementation details that may change. Describing the distinction by platform configuration keeps the contract stable.

```suggestion
    description: >-
      Optional static 3-D Secure password to set on the card, for card
      programs whose issuer challenges online purchases with a static
      password factor (EUR-funded EU card programs). 8-36 characters; Latin
      letters, digits, and the issuer's allowed special characters. Grid
      forwards it to the provider and never stores or returns it. When
      omitted, Grid generates a strong password; the cardholder-facing
      view/change flow is delivered through the secure card UI. Rejected for
      card programs without a static-password challenge factor (USD-funded
      card programs).
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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

Request changes

Faraday score: 3/5 (changes requested) - 5 clean | 4 non-blocking notes | 3 one major or human review | 2 two majors | 1 a blocker or 3+ majors

when a pull or confirmation fails (`CARD_TRANSACTION.EXCEPTION`).
The payload carries the full `CardTransaction` resource.
(`CARD_TRANSACTION.AUTHORIZED`), when an authorization is declined by
the issuer or program (`CARD_TRANSACTION.DECLINED`), as clearings

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This introduces the DECLINED terminal status and CARD_TRANSACTION.DECLINED webhook, but sandbox-testing.mdx's decline-outcome table (descriptor suffixes 002/003) wasn't updated beyond the family rename — it never says these now resolve to status DECLINED / fire CARD_TRANSACTION.DECLINED, unlike EXCEPTION's explicit suffix-006 mapping. Without that, there's no documented way for integrators to trigger or verify the new status in sandbox.

- PARTIALLY_SETTLED
- SETTLED
- REFUNDED
- DECLINED

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit (optional): DECLINED is enum position 5 — I'd move it to position 2 to match the description table order and reflect that it's an immediate alternative to AUTHORIZED rather than a state preceding EXCEPTION.

## Card-transaction lifecycle
## The CARD_TRANSACTION.* family

Fires on every state transition of a `CardTransaction` — the webhook

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Now that this event only fires on state transitions and the type itself names the new status, how do we deliver reconciliation updates that don't change CardTransaction.status — e.g. a partial refund that stays SETTLED, or another partial clearing? Is there still a channel for those, or is that intentionally out of scope here?

@kphurley7

Copy link
Copy Markdown
Contributor Author

@greptileai review this

@kphurley7

Copy link
Copy Markdown
Contributor Author

@faraday review this

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

Faraday score: 3/5 (flagged for human review) - 5 clean | 4 non-blocking notes | 3 one major or human review | 2 two majors | 1 a blocker or 3+ majors

settlementSummary:
count: 0
totalAmount: 0
authorizedAt: '2026-05-09T11:20:00Z'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In the declined example, authorizedAt is set to a real timestamp even though this authorization was never approved. The field description says "When the auth was approved," which is confusing for anyone generating fixtures off this example. Could we broaden the description (e.g. "when the authorization was processed") or note what it means on DECLINED rows specifically?

minLength: 8
maxLength: 36
description: >-
Optional static 3-D Secure password to set on the card, for card

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit (optional): the threeDSecurePassword description calls out providers by brand name (EU/Striga vs US/Lithic) in public contract docs. Let's describe this by platform funding config (EUR-funded vs USD-funded) instead, matching how the Idempotency-Key description phrases the same distinction — keeps the public docs from coupling to vendor names.

## Card-transaction lifecycle
## The CARD_TRANSACTION.* family

Fires on every state transition of a `CardTransaction` — the webhook

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Question: since this family fires only on state transitions, does a same-status mutation get its own event — e.g. another partial clearing while still PARTIALLY_SETTLED, or a second partial return while REFUNDED? If not, could pullSummary/refundSummary/settlementSummary go stale between transitions for consumers relying on these webhooks for reconciliation?

| Status | Description |
|--------|-------------|
| `AUTHORIZED` | The auth has been approved and a hold placed on the funding source; no clearing has arrived yet. |
| `DECLINED` | The authorization was declined by the issuer or program; no funds moved and the transaction is terminal. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Question: should CardTransaction expose a decline code/reason alongside DECLINED? The existing card flow already distinguishes cases like INSUFFICIENT_FUNDS vs CARD_PAUSED vs issuer/program declines and says that code is visible on the resulting transaction — is leaving it out here intentional, or should it be surfaced too?

US programs link merchant refunds to the original purchase (REFUNDED
status, refundedAmount/refundSummary update). EU programs receive
refunds from the network without a purchase reference, so they surface
as standalone CREDIT-direction card transactions and never update the
purchase's refund fields. Stated on the CardTransaction schema and the
reconciliation guide so platforms reconcile EU refunds by
merchant/amount/time instead of parent linkage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kzvhCS9mZrkHaH7E5EQ94
@kphurley7

Copy link
Copy Markdown
Contributor Author

@greptileai review this

@kphurley7

Copy link
Copy Markdown
Contributor Author

@faraday review this

The repo runs the same conventions through BOTH redocly and spectral;
the redocly lint-ignore entry silenced one linter while spectral (which
has no ignore-file mechanism) kept erroring - the source of the
pass/fail flapping on this check. Adds the mirrored per-pointer
override with the same rationale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kzvhCS9mZrkHaH7E5EQ94

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

Faraday score: 4/5 (accepted with minor notes) - 5 clean | 4 non-blocking notes | 3 one major or human review | 2 two majors | 1 a blocker or 3+ majors

the card program's provider links refunds to the original purchase
(US programs). EU card programs receive merchant refunds WITHOUT a
purchase reference, so those refunds surface as standalone
CREDIT-direction card transactions and never update the purchase's

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This description says EU merchant refunds surface as standalone CREDIT-direction card transactions, but the direction field elsewhere in this schema documents that card transactions debit the customer's account (and only gives a DEBIT example). That's a direct contradiction for a valid EU refund response — generated clients (and anyone reading this spec) get two mutually exclusive answers for what direction should be. Can we reconcile these, e.g. by calling out CREDIT as a valid direction value in its own description?

settlementSummary:
count: 0
totalAmount: 0
authorizedAt: '2026-05-09T11:20:00Z'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This declined example populates authorizedAt, but CardTransaction.yaml documents authorizedAt as "when the auth was approved" and marks it required — a DECLINED auth was never approved, so this is our only DECLINED fixture and it's encoding the wrong invariant. Since authorizedAt is required we can't just drop it from the example; this needs a schema-side fix, either loosen the requirement for DECLINED or adjust the description so it doesn't imply approval.

@kphurley7

Copy link
Copy Markdown
Contributor Author

@greptileai review this

@kphurley7

Copy link
Copy Markdown
Contributor Author

@faraday review this

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

Faraday score: 4/5 (accepted with minor notes) - 5 clean | 4 non-blocking notes | 3 one major or human review | 2 two majors | 1 a blocker or 3+ majors

the card program's provider links refunds to the original purchase
(US programs). EU card programs receive merchant refunds WITHOUT a
purchase reference, so those refunds surface as standalone
CREDIT-direction card transactions and never update the purchase's

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

refundedAmount's description says EU merchant refunds surface as standalone CREDIT-direction transactions, but that contradicts direction's description just above it ("Card transactions debit the customer's account", example DEBIT) in this same schema. Generated clients end up with mutually exclusive semantics for a valid EU refund response — let's reconcile these two descriptions.

settlementSummary:
count: 0
totalAmount: 0
authorizedAt: '2026-05-09T11:20:00Z'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This declined example sets authorizedAt ('2026-05-09T11:20:00Z'), but CardTransaction docs authorizedAt as "when the auth was approved" and marks it required — a declined auth was never approved, so this fixture encodes a contradiction. This needs a schema-side fix (redescribe authorizedAt or relax required for declines), not just a tweak to the example.

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