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

# The checkout door

> How a patient enters the hosted checkout, what the entry link carries, and how your servers learn the outcome.

The platform hosts the page where a patient pays. Your storefront never renders a card field,
never holds a payment credential, and never relays a payment token — it hands the patient to the
hosted checkout with a link, and your servers learn the outcome afterwards. This page describes
that door: the entry URL, what the link carries, and the reads that tell you what happened.

<Tip>
  **Fastest start — the site kit.** You do not have to build this door from scratch. Purple ships a
  **fork-and-own storefront starter**: a complete Next.js site that already stands up the storefront
  leading to this door, including the hand-off link that brings a patient here. Fork it, brand it,
  and deploy it as your own.

  Fork a **tagged release** — never the tip of the main branch — so your site is pinned to a known,
  supported version. Take the latest tag from the starter's releases page. While the repository is
  private, **access is provided at onboarding**: your onboarding contact points you at the repository
  and the release to fork.

  The in-repo `site-kit-quickstart` guide is your operator's manual — it takes a fork from first run
  to a live, branded site.

  **What the starter gives you** is the site itself: the storefront and the brand slots, plus a
  working reference for the calls this page describes. **What the platform issues you** is what makes
  that site yours — your credentials at onboarding, your brand, and your assigned offerings. The
  starter ships identical to everyone; your configuration is what makes it your brand's door.
</Tip>

## The entry link

The hosted checkout lives at `/checkout` on the platform's patient portal — the same origin where
your patients sign in and manage their plan. The link carries the entry context as query
parameters:

| Parameter      | When              | What it is                                                                                                                          |
| -------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `offering_ref` | Always required   | The offering the patient is buying. A link without it is refused with a plain explanation — nothing is charged, nothing is created. |
| `journey_id`   | The primary entry | The patient's enrollment reference (`jny_...`), carried when the patient arrives with the questionnaire already complete.           |
| `sku_id`       | Direct buy only   | The product the buy link is for (`sku_...`).                                                                                        |
| `therapy`      | Direct buy only   | The therapy the buy link is for.                                                                                                    |

Entry context selects **what to present — never what anything costs**. Malformed values are
dropped at the door, and every money figure on the page is resolved by the server from your
configured catalog and pricing. A link cannot name a price, a discount, or a fee; a request that
tries is rejected.

## Two ways in

* **After the questionnaire — the primary entry.** The patient completes the health questions
  first and arrives carrying `journey_id`: identity, eligibility context, and enrollment are
  already attached, and payment is the only step left.
* **Direct buy — configured per deployment.** Where your program is set up to take payment
  before the health questions, a buy link carries `sku_id` and `therapy` instead, and the
  enrollment is started at the door: the hosted checkout's own server calls `POST /v1/prospects`
  to open it. That operation is not yet part of the published API reference — your storefront
  never calls it; the hosted surface makes the call on its own server. Which way your deployment
  works is configuration agreed when your program is set up, never inferred from the link — on a
  questions-first deployment, a link without an enrollment shows the patient a plain
  "health questions come first" stop instead of a payment form.

## A reference implementation

The site kit ships a reference implementation of this entry contract. The hand-off composer at
`lib/purple/checkout-links.mjs` builds the entry link for both ways in above — carrying
`journey_id` for the questionnaire-first entry, and taking `sku_id` and `therapy` for direct buy.
Read it as the worked example of the link this page specifies: the table above is the source of
truth for the wire, and the composer is a ready way to build it.

## Sign-in at the door

Checkout requires sign-in. A patient who arrives anonymous starts with a single email field
and signs in through the platform's login — no password form is ever embedded in the page —
while a patient who is already authenticated sees their identity collapsed to one bar with a
sign-out. The
patient's account materializes at checkout; your storefront does not create it.

## Payment credentials never touch your code

The standing rule, stated plainly: **no payment credential is ever handled client-side** — not
in your storefront, and not in the checkout page's own frontend either. Card collection happens
on the hosted page using Stripe's browser components: the card number travels from the patient's
browser directly to the payment provider and never passes through the platform, or through you.
There is no card field for you to build, no token for you to relay, and nothing payment-shaped
for you to store. The browser side runs on the **payment provider's** publishable key — not one of
yours — and today that key is test-scoped only; a live-scoped key is refused outright. Your own
publishable brand key is a separate credential with a different purpose, described in
[authentication](/pages/authentication).

Behind the page, the hosted server creates the session with
`POST /v1/payments/checkout-sessions` under an idempotency key, so a retried submit continues
the same session instead of creating a second charge — and the required consent documents
configured for your deployment must be agreed to before that call is ever made. If no consent
documents are configured yet, payment stays locked: a patient is never charged without agreeing
to the configured set.

## How your servers learn the outcome

After paying, the patient continues inside the portal — there is no browser redirect back to
your storefront, and the outcome never rides the patient's browser to reach you. Your servers
learn what happened two ways:

* **Deliveries you subscribe to.** Register an endpoint with `POST /v1/webhooks/registrations`;
  the response returns your signing secret once, and every delivery carries a signature computed
  with it so your receiver can verify the sender. Deliveries are reference-only — an event type
  plus identifiers such as the order reference — so a delivery prompts a read but never leaks
  detail on its own. You can prove the pipe end to end at any time with
  `POST /v1/webhooks/registrations/{registration_id}/test`.
* **Reads you make.** `GET /v1/payments/{order_ref}/status` returns the payment state of the
  order — awaiting payment, authorized, paid, failed, refunded — fed by confirmed events from
  the payment provider, never by guesswork. `GET /v1/journeys/{journey_id}/status` returns the
  patient's public enrollment status, and a patient never shows as approved before their order
  is authorized — so the enrollment read is safe to drive your storefront's own status copy.
  Read the payment status for the order's own state: the enrollment read may return
  [no status yet](/pages/integrate/the-patient-flow#when-a-status-is-not-yet-reportable)
  instead of a status value, and it would never carry payment detail in any case.

Two facts worth designing around: a failed payment still leaves a readable order — the attempt
is never invisible — and a promo code the platform cannot honor is refused with the exact
reason, so a patient is never quietly charged full price against a code they believed in.
