Skip to main content

Dedicated Checkout operations

A dedicated purchase takes one of two paths, both ending at the same order state machine.

Saved payment method (accounts with billing on file)

POST /api/dedicated/checkout looks up the account’s Stripe customer before creating anything hosted. When a saved payment method is found it creates the subscription directly with off_session: true and activates the order in the same request, so the buyer never leaves the dashboard and never sees a Stripe-hosted page. The response is { url: <dashboard success URL>, checkout: 'saved_payment_method' }. A bank account wins over a card whenever the customer has both — ACH carries no percentage fee on a five-figure invoice. Within a type, the customer’s default payment method wins. Two conditions send an account with billing on file to hosted Checkout anyway:
  • An upfront commitment charge on a bank account. Only metered plans (reserved_gpu_hour) owe nothing at subscription creation. A plan with a licensed commitment price settles synchronously on a card, but an ACH debit reports processing for days, and GPU capacity must not be held against money that has not moved.
  • The subscription came back short of active. A declined card leaves an incomplete subscription; it is canceled before falling back so one order can never carry two subscriptions.
Automatic tax is enabled only when Stripe already recognizes the customer’s location (customer.tax.automatic_tax === 'supported'). Off-session there is no address-collection step to make an unrecognized location calculable. Stripe’s hosted promotion-code field does not exist on this path. A discounted purchase for an existing account is applied as a customer or subscription discount in Stripe, not typed at checkout.

Hosted Checkout (accounts with nothing on file)

Cold accounts use Stripe-hosted Checkout in subscription mode, restricted to us_bank_account; cards are not an allowed fallback there. Stripe’s hosted promotion-code field is enabled, so promotion eligibility, redemption limits, and expiration remain authoritative in Stripe. The response is { url: <Stripe URL>, checkout: 'hosted' }.

Production webhook

Configure the Stripe account webhook destination as:
Subscribe it to at least these events:
  • checkout.session.completed
  • invoice.paid
  • invoice.payment_failed
Set the destination’s signing secret as STRIPE_WEBHOOK_SECRET in the Vercel Production environment. The shared handler detects metadata.type=dedicated_endpoint and routes those events to the dedicated commerce ledger before ordinary account billing. Event IDs are claimed in the same database transaction as their effects, so duplicate delivery is safe. A dedicated checkout.session.completed activates an order only when payment_status is paid or no_payment_required. The latter is expected for hourly metered subscriptions with no initial usage, including a fully discounted Checkout. Before holding capacity, the handler verifies the Checkout session ID, plan version, and requested model against the stored order. Both purchase paths then call the same activateDedicatedOrder (src/lib/dedicated-commerce-db.ts): it sets the subscription ID and two-hour activation deadline, transitions checkout_pending → paid, and holds capacity or drops the order to refunding. It no-ops once the order has left checkout_pending, so duplicate webhooks and retried requests are safe. invoice.paid grants the commitment from the subscription’s dedicatedOrderId metadata on both paths, and the reconciler keys refunds off stripe_subscription_id, so neither depends on a Checkout session existing.

Scale from zero

Purchasing never procures infrastructure. After payment is committed, the activation transaction holds GPU-equivalents against the configured pool limit. The hold may be pending_capacity with no node or slots when the first compatible node does not yet exist. The GitOps pull request records that demand but cannot merge until inventory is registered and the hold is atomically assigned a node and contiguous slots. For the 4× B200 DeepSeek offer, the immutable values are:

Annual display

Production currently has monthly hourly Stripe prices only. Selecting Yearly displays the annual reference rate but changes the CTA to contact sales. It must not silently start a monthly Checkout at the displayed annual rate. Add a versioned annual plan and Stripe price before enabling direct annual Checkout.

Initial 4× B200 production enablement

Run migrations 0040_add_dedicated_discount_catalog.sql and 0041_add_dedicated_order_model.sql first. Create the Stripe product and metered hourly Price, then substitute its real price_... ID below. This opens exactly one 4-GPU-equivalent logical sale while leaving physical inventory empty:
Before committing, verify that each UPDATE matched the intended row and that no physical node was inserted into dedicated_capacity_nodes. The first paid purchase creates a four-GPU logical hold and an unmergeable pending_capacity GitOps PR. Register the procured node in both control-plane inventory and dedicated_capacity_nodes; the reconciler will atomically assign slots and update the same PR to allocated.