Skip to content

feat: /checkout deal leg — variable-amount vin deal settlement priced from the deal door's OFFER - #1

Open
nathanclevenger wants to merge 1 commit into
mainfrom
feat/vin-deal-checkout
Open

feat: /checkout deal leg — variable-amount vin deal settlement priced from the deal door's OFFER#1
nathanclevenger wants to merge 1 commit into
mainfrom
feat/vin-deal-checkout

Conversation

@nathanclevenger

Copy link
Copy Markdown
Contributor

What

Adds the vin estate's variable-amount deal-checkout leg to the /checkout front, and the matching webhook settle forward. The fixed-price legs (sticker / vhr) are untouched.

  • GET /checkout?sku=deal&deal=…&link=…&vin=…&door=…&return_to=… → fetches the deal's posted OFFER server-side from the CLOSED deal-door table (apis.vin), verifies the asked payment link is the standing OPEN one and names the same VIN, prices the Stripe Checkout Session from settle.amount_total (integer cents, USD, card-ceiling-capped) → 303 to Stripe Checkout. The query string never carries a price.
  • POST /webhooks: PAID sessions with metadata estate=vin, kind=deal forward to the deal door's own settle leg (POST https://{door}/buy/deals/{deal}/settle, bearer VIN_SETTLE_TOKEN, body { order_id, settlement_ref, amount_total, currency }). Failed forward → 500 so Stripe redelivers; the estate dedupes by order_id and refuses any amount disagreeing with the desked cash-to-close.
  • vinSettlementFromSession now ignores kind=deal sessions — a deal never double-forwards to the fixed-price /_settle.

Money-code review notes

  • Amount integrity: price comes ONLY from the server-side OFFER fetch; the OFFER source URL is built from the closed door table + regex-validated ids (no traversal, no SSRF surface).
  • Open-redirect guard: return_to must be https on the offering door.
  • Stale links never collect: superseded / expired / settled statuses refuse before any session is minted.
  • Known bounded risk (same posture as the sticker rail): a buyer opening two Stripe tabs from the same open link before either settles can pay twice — the estate's ratified settle law accepts fresh order_ids post-settlement (post-delivery payments); the refund policy is the recovery path.

Verification

  • pnpm typecheck clean; pnpm test 199/199 (37 new in test/deal-checkout.test.ts).

🤖 Generated with Claude Code

…d from the deal door's OFFER

GET /checkout?sku=deal&deal=…&link=…&vin=…&door=…&return_to=… prices a
Stripe Checkout Session from the deal door's posted OFFER
(https://{door}/buy/deals/{deal}/checkout.json?link=…), fetched
server-side — the query string never carries a price. The door table is
CLOSED (apis.vin); ids are shape-validated; return_to must be https on
the offering door (no open redirect). A superseded / expired / settled
link refuses — a re-desk changes the numbers, so a stale link never
collects them.

PAID deal sessions (metadata estate=vin, kind=deal) forward from
POST /webhooks to the deal door's own settle leg —
POST https://{door}/buy/deals/{deal}/settle, bearer VIN_SETTLE_TOKEN,
body { order_id, settlement_ref, amount_total, currency } — where the
estate refuses any amount that disagrees with the desked cash-to-close
and dedupes by order_id. A failed forward answers Stripe 500 so the
event redelivers. The fixed-price extractor now ignores kind=deal
sessions, so a deal never double-forwards to /_settle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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: f303cc8731

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/index.ts
Comment on lines +494 to +496
const session = await getStripe().checkout.sessions.create(
vinDealCheckoutSessionParams(parsed.intent, fetched.offer),
)

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 Revalidate offers before late Checkout payments

When a deal is re-desked or its link is superseded after this Stripe Session is created but before the buyer pays, the hosted Session remains independently payable because there is no subsequent open-link check or invalidation. The buyer can therefore be charged the old amount, after which the current deal's settle endpoint refuses it and the webhook returns 500 indefinitely without delivering the settlement. Tie the Session to an immutable offer revision or ensure outstanding Sessions are invalidated when the offer changes.

Useful? React with 👍 / 👎.

Comment thread src/deal-checkout.ts
): VinDealSettlement | null {
const metadata = (session.metadata ?? null) as Record<string, string> | null
if (metadata?.estate !== 'vin' || metadata?.kind !== 'deal') return null
if (session.payment_status !== 'paid') return null

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 asynchronous Checkout payment success

When an enabled delayed payment method is used, checkout.session.completed can arrive with payment_status still unpaid, so this returns null and the webhook acknowledges the event without settling the deal. The eventual checkout.session.async_payment_succeeded event is also ignored because src/index.ts invokes this extractor only for checkout.session.completed, leaving a paid deal unforwarded. Either restrict these Sessions to synchronous card payments or process the asynchronous success event.

Useful? React with 👍 / 👎.

Comment thread src/deal-checkout.ts
Comment on lines +156 to +158
if (typeof page.vin === 'string' && page.vin.toUpperCase() !== intent.vin) {
return refuse('the OFFER names a different VIN than this checkout')
}

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 Require the offer to name the VIN

If the deal door returns an otherwise valid open OFFER with vin missing or non-string, this condition skips validation and accepts the client-controlled VIN into the Stripe description and metadata. A malformed or version-drifted OFFER can consequently charge the deal while presenting the wrong vehicle, despite this function's contract requiring the OFFER to name the same VIN; reject unless page.vin is a string equal to intent.vin.

Useful? React with 👍 / 👎.

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