# Stripe Checkout

> Make a Stripe Checkout subscription sign-up compliant — the exact Checkout Session params, the consent checkbox, the disclosure before the redirect, and how to verify it.

Stripe-hosted Checkout shows the price and Stripe's own authorization line, but by default it does not state that
the subscription renews until cancelled, how to cancel, or ask for consent to those terms. Checkbox gives you the
exact `stripe.checkout.sessions.create` parameters that fix that, verified by the rules engine.
[How to make Stripe Checkout auto-renewal compliant](https://checkbox.site/docs/learn/stripe-checkout-auto-renewal-compliance) explains
the law behind each change.

## 1. Preflight the params your code sends {#preflight}

Send the params object exactly as your code builds it. When `line_items` reference a Price id, also pass the price and
interval in `merchant` (Checkbox does not call Stripe during a preflight):

```curl
curl https://app.checkbox.site/v1/checks/stripe_checkout_session \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
    "params": {
      "mode": "subscription",
      "line_items": [{ "price": "price_1Q2w3E", "quantity": 1 }],
      "success_url": "https://joybox.example/welcome"
    },
    "merchant": {
      "merchant_name": "Joybox",
      "price": 19.99,
      "interval": "month",
      "cancel_url": "https://joybox.example/account/cancel"
    }
  }'
```
```node
import Checkbox from '@checkbox-site/cli';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const params = {
  mode: 'subscription',
  line_items: [{ price: 'price_1Q2w3E', quantity: 1 }],
  success_url: 'https://joybox.example/welcome',
};
const result = await checkbox.checks.stripeCheckoutSession({
  params,
  merchant: { merchant_name: 'Joybox', price: 19.99, interval: 'month', cancel_url: 'https://joybox.example/account/cancel' },
});
for (const check of result.checks) console.log(check.status.padEnd(12), check.check_id);
console.log(JSON.stringify(result.correction.patch, null, 2));
```
```python
import json
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

params = {
    "mode": "subscription",
    "line_items": [{"price": "price_1Q2w3E", "quantity": 1}],
    "success_url": "https://joybox.example/welcome",
}
result = checkbox.checks.stripe_checkout_session(
    params=params,
    merchant={"merchant_name": "Joybox", "price": 19.99, "interval": "month",
              "cancel_url": "https://joybox.example/account/cancel"},
)
for check in result["checks"]:
    print(check["status"].ljust(12), check["check_id"])
print(json.dumps(result["correction"]["patch"], indent=2))
```

Or from the terminal, with your params in a file:

```bash
cat > session.json <<'EOF'
{ "mode": "subscription", "line_items": [{ "price": "price_1Q2w3E", "quantity": 1 }], "success_url": "https://joybox.example/welcome" }
EOF
checkbox check stripe-session --file session.json --merchant-name Joybox --price 19.99 --interval month \
  --cancel-url https://joybox.example/account/cancel --offline
```

`--offline` runs the same rules engine locally, without a key. The command exits with code 1 while a check fails, so it
works as a CI gate.

## 2. Apply the correction {#apply}

The correction adds two things to your params:

- `consent_collection.terms_of_service: "required"` — Stripe shows an **unchecked** checkbox the customer must tick.
  It needs a Terms of Service URL in Stripe (Dashboard → Settings → Public details).
- `custom_text.terms_of_service_acceptance.message` — the checkbox label, stating the renewal terms; and
  `custom_text.submit.message` — the full disclosure next to the Subscribe button: price, interval, renews until
  cancelled, how to cancel online, when cancellation takes effect, the trial and the charge after it.

The wording is generated from your terms and verified by the engine; use it as is. Then check again: the patch comes back
empty when nothing is left to change.

## 3. Show the terms before the redirect {#before-redirect}

One check stays `needs_review` on any Stripe-hosted page: **clear and conspicuous**. Stripe renders custom text in its own
small grey style, and you cannot make it larger or set it off. Close the gap on your own page, before you redirect to
Stripe: show the same disclosure next to the "Subscribe" button that creates the Checkout Session, set off in a box at
body size or larger. [Checkbox Elements](https://checkbox.site/docs/elements) renders it for you, or ask for the HTML with
[`checkDisclosure`](https://checkbox.site/docs/api/checks#checkDisclosure) (`replacement.html`).

## 4. Monitor the live flow {#monitor}

Register the site with `platform: "stripe"` ([quickstart](https://checkbox.site/docs/quickstart#register-your-site)) and connect Stripe
with a read-only restricted key in the dashboard (**Site → Integrations → Stripe**: Prices and Products read are
required; Subscriptions, Customer portal and Checkout Sessions read add coverage). Checkbox then reads your recent
Checkout Sessions' custom text and consent settings, your prices and trials, and your customer-portal settings, and the
synthetic shopper scans the real hosted page from each state.

To see the whole lifecycle — acknowledgment email, trial reminder, renewal and online cancellation — run a
[test subscription](https://checkbox.site/docs/guides/test-runs) with a Stripe test-mode key. You can register the exact params your code
uses so the test run mirrors them:

```curl
curl https://app.checkbox.site/v1/sites/site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b/stripe_checkout_configs \
  -u "$CHECKBOX_SECRET_KEY:" \
  -H "Content-Type: application/json" \
  -d '{
    "params": {
      "mode": "subscription",
      "line_items": [{ "price": "price_1Q2w3E", "quantity": 1 }],
      "consent_collection": { "terms_of_service": "required" }
    }
  }'
```
```node
import Checkbox from '@checkbox-site/cli';

const checkbox = new Checkbox(process.env.CHECKBOX_SECRET_KEY);

const config = await checkbox.stripeCheckoutConfigs.create('site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b', {
  params: {
    mode: 'subscription',
    line_items: [{ price: 'price_1Q2w3E', quantity: 1 }],
    consent_collection: { terms_of_service: 'required' },
  },
});
console.log(config.id);
```
```python
import os
from checkbox import Checkbox

checkbox = Checkbox(os.environ["CHECKBOX_SECRET_KEY"])

config = checkbox.stripe_checkout_configs.create(
    "site_3f9a1c2e7b4d4e0f9a8b7c6d5e4f3a2b",
    params={
        "mode": "subscription",
        "line_items": [{"price": "price_1Q2w3E", "quantity": 1}],
        "consent_collection": {"terms_of_service": "required"},
    },
)
print(config["id"])
```

## Checklist {#checklist}

- [ ] `consent_collection.terms_of_service: "required"` with a renewal-terms label
- [ ] `custom_text.submit.message` with price, interval, renewal until cancelled, how to cancel, trial terms
- [ ] The same disclosure, set off, on your page before the redirect
- [ ] Acknowledgment email after `checkout.session.completed` ([emails](https://checkbox.site/docs/guides/emails-and-notices))
- [ ] Online cancellation: the [customer portal](https://checkbox.site/docs/guides/stripe-customer-portal) or a [hosted cancellation page](https://checkbox.site/docs/guides/cancellation)
