feat: /checkout deal leg — variable-amount vin deal settlement priced from the deal door's OFFER - #1
feat: /checkout deal leg — variable-amount vin deal settlement priced from the deal door's OFFER#1nathanclevenger wants to merge 1 commit into
Conversation
…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>
There was a problem hiding this comment.
💡 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".
| const session = await getStripe().checkout.sessions.create( | ||
| vinDealCheckoutSessionParams(parsed.intent, fetched.offer), | ||
| ) |
There was a problem hiding this comment.
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 👍 / 👎.
| ): 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 |
There was a problem hiding this comment.
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 👍 / 👎.
| if (typeof page.vin === 'string' && page.vin.toUpperCase() !== intent.vin) { | ||
| return refuse('the OFFER names a different VIN than this checkout') | ||
| } |
There was a problem hiding this comment.
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 👍 / 👎.
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 fromsettle.amount_total(integer cents, USD, card-ceiling-capped) → 303 to Stripe Checkout. The query string never carries a price.POST /webhooks: PAID sessions with metadataestate=vin, kind=dealforward to the deal door's own settle leg (POST https://{door}/buy/deals/{deal}/settle, bearerVIN_SETTLE_TOKEN, body{ order_id, settlement_ref, amount_total, currency }). Failed forward → 500 so Stripe redelivers; the estate dedupes byorder_idand refuses any amount disagreeing with the desked cash-to-close.vinSettlementFromSessionnow ignoreskind=dealsessions — a deal never double-forwards to the fixed-price/_settle.Money-code review notes
return_tomust be https on the offering door.Verification
pnpm typecheckclean;pnpm test199/199 (37 new intest/deal-checkout.test.ts).🤖 Generated with Claude Code