> ## Documentation Index
> Fetch the complete documentation index at: https://docs.morphllm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dedicated checkout operations

# 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:

```text theme={null}
https://www.morphllm.com/api/webhooks/stripe
```

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:

```text theme={null}
planVersionId: b200-hourly-4-v1
requestedModelId: deepseek-v4-flash
modelTemplate: DeepSeek V4 Flash
capacityPool: b200-dsv4flash
gpuEquivalents: 4
nodeGpuCount: 8
```

## 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:

```sql theme={null}
BEGIN;

UPDATE dedicated_capacity_pools
SET total_gpu_equivalents = 4,
    accepting_purchases = true,
    updated_at = now()
WHERE id = 'b200-dsv4flash';

UPDATE dedicated_plan_versions
SET stripe_price_id = 'price_REPLACE_WITH_LIVE_B200_HOURLY_PRICE',
    checkout_enabled = true,
    provisional_pricing = false,
    margin_approved_at = now(),
    launch_approved_at = now(),
    available_from = now(),
    retired_at = NULL
WHERE id = 'b200-hourly-4-v1'
  AND billing_model = 'reserved_gpu_hour'
  AND gpu_hour_rate_microusd = 9827100;

UPDATE dedicated_plan_versions
SET checkout_enabled = false,
    retired_at = COALESCE(retired_at, now())
WHERE id IN ('b200-priority-2-v1', 'b200-priority-4-v1', 'b200-priority-8-v1');

COMMIT;
```

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`.
