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

# Implement Stripe Checkout

> Create Checkout on your server, register exact Biqli attribution, and redirect safely.

The recommended implementation creates Stripe Checkout on your server, returns the public Checkout Session ID and URL to the browser, registers that ID with Biqli, and redirects only after registration succeeds.

## Prerequisites

Before writing Checkout code:

* connect the intended Stripe account and environment to the workspace;
* enable conversion tracking;
* configure the website hostname and a Biqli publishable key;
* install `@biqli/analytics` in the browser or use the global script;
* keep the Stripe secret key on your server;
* decide whether the flow is a one-time payment, paid subscription, or subscription with trial.

## Create Checkout on the server

This Express-style example accepts an authenticated customer and creates a one-time Checkout Session. Validate product and price choices on the server rather than trusting the browser.

```ts theme={null}
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

app.post('/api/checkout', requireUser, async (request, response) => {
  const session = await stripe.checkout.sessions.create({
    mode: 'payment',
    customer: request.user.stripeCustomerId,
    line_items: [{price: 'price_example', quantity: 1}],
    success_url: 'https://app.example.com/billing/success',
    cancel_url: 'https://app.example.com/billing/cancel',
  });

  response.json({
    id: session.id,
    url: session.url,
    customerEmail: request.user.email,
  });
});
```

The response may contain the `cs_...` ID and Checkout URL. It must not contain your Stripe secret key.

## Register and redirect in the browser

```ts theme={null}
import {BiqliAnalytics} from '@biqli/analytics';

const biqli = new BiqliAnalytics({
  publishableKey: 'biqli_pk_xxxxxxxxx',
});

async function beginCheckout() {
  const response = await fetch('/api/checkout', {
    method: 'POST',
    credentials: 'same-origin',
  });
  if (!response.ok) throw new Error('Could not create Checkout.');

  const checkout = await response.json();

  await biqli.storeStripeSession({
    stripe_account_id: 'acct_example123',
    stripe_environment: 'live',
    stripe_session_id: checkout.id,
    customer_email: checkout.customerEmail,
  });

  window.location.assign(checkout.url);
}
```

If `storeStripeSession()` fails, show a retryable error before redirecting. Do not continue and assume the success page can repair attribution later.

The browser SDK generates an idempotency key for this registration. A repeated exact request is safe. Reusing an identity for another click or reusing an idempotency key with different data is rejected.

## One-time payment behavior

For `mode: 'payment'`:

* `checkout.session.completed` creates a sale only when `payment_status` is `paid`;
* `amount_total` is recorded as integer minor units;
* the Checkout Session ID is the durable Biqli invoice identity for this path;
* PaymentIntent attribution is stored when Stripe includes it;
* an unpaid completion establishes mapping but creates no sale.

## Delayed payment methods

Some payment methods complete Checkout before payment settles.

* `checkout.session.completed` can leave the mapping in an awaiting-payment state.
* `checkout.session.async_payment_succeeded` records the one-time sale after settlement.
* `checkout.session.async_payment_failed` marks the mapped Checkout failed and records no sale.

Do not create a provisional sale in your return page. Wait for Stripe's final event.

## Declines, 3DS, cancellation, and abandonment

| Outcome                            | Expected Biqli result                                              |
| :--------------------------------- | :----------------------------------------------------------------- |
| Card declined                      | No sale. The customer can retry in Stripe or start a new Checkout. |
| 3DS completed successfully         | Sale after Stripe confirms payment.                                |
| 3DS failed or abandoned            | No sale.                                                           |
| Customer uses the cancel URL       | No sale.                                                           |
| Checkout abandoned                 | No sale. Pending mapping expires.                                  |
| Customer revisits your success URL | No additional sale.                                                |
| Stripe resends the event           | Same result; no duplicate revenue.                                 |

## Payment Links and Pricing Tables

Stripe Payment Links and Pricing Tables create the Checkout Session inside Stripe, often after the browser has left the part of your application that knows `bq_id`. That means the recommended exact pre-redirect registration is not automatically available.

Do not assume that installing the Stripe App makes arbitrary Payment Link sales attributable. Use one of these approaches:

1. Prefer a server-created Checkout Session and register its `cs_...` ID.
2. If your application already knows a verified customer email, create one pending mapping immediately before launch and rely on the unique-email fallback only when one eligible candidate exists.
3. Build a trusted server-side attribution flow and use Biqli's generic sale API after verifying Stripe's webhook yourself.

The email fallback is not suitable when multiple people, tabs, attempts, or workspaces can share the same email candidate. Biqli rejects ambiguity instead of guessing.

## Embedded Checkout and custom frontends

Embedded Checkout uses the same principle: create the session on your server and call `storeStripeSession()` as soon as the browser receives the session ID, before the customer can complete payment.

If you build directly with PaymentIntents rather than Checkout Sessions, preserve `bq_id` in your trusted order record and use the server tracking API after verified payment. The managed app's browser registration endpoint accepts Checkout Session IDs, not arbitrary PaymentIntent IDs.

## Keep product data authoritative

Biqli records the paid Stripe amount and currency. Subscription product labels come from invoice line data included in the allowed event payload. Biqli does not make a separate Product or Price lookup just to decorate conversion activity.

Use Stripe as the authority for Checkout price, tax, discounts, and final amount. Never calculate the recorded sale from a browser-submitted display price.

## Production checklist

* Use `stripe_environment: 'live'` with `cs_live_...` sessions.
* Use the live Stripe account ID connected to the intended workspace.
* Use an allowed production hostname and the production workspace publishable key.
* Keep all secret keys and Checkout creation on the server.
* Register before every redirect, including retry and upgrade flows.
* Test one real low-value payment and refund before sending production traffic.

For subscriptions and trials, continue with [Subscription lifecycle](/developers/integrations/stripe/subscriptions).
