Checkout
A checkout charges one-time items and starts one or more subscriptions in a single payment. Your customer enters a card once, confirms once, and sees one charge. Use it when a plan comes with something else on the first invoice, such as a setup fee, a hardware kit, or a second plan.
When to use a checkout
| You want to… | Use |
|---|---|
| Charge for one-time items only | createPayment |
| Start one subscription with nothing else on the first charge | createSubscription |
| Charge one-time items and start one or more subscriptions together | createCheckout |
A checkout with no subscriptions behaves like a payment, and one with a single subscription and no one-time items behaves like createSubscription. Each subscription still renews on its own schedule afterward.
Prerequisites
- Your project owner has completed compliance registration (live projects must be
APPROVED). Sandbox projects bypass this.
Limits
- At most 10 subscriptions per checkout.
- At most 50 line items across the whole checkout, one-time and subscription lines together.
- At least one one-time line or one subscription.
- Card only. A checkout that includes a subscription can't be paid with ACH direct debit, for the same reasons given in Subscriptions.
- Every subscription starts now. A checkout has no
startTime. To schedule a future start, usecreateSubscription. - The first-charge subtotal and each subscription's subtotal must be at least the smallest charge Forte accepts, because each subscription renews as its own charge.
lineItemIdandsubscriptionIdon the lines you send are ignored. Forte sets them.
Creating a checkout
Call createCheckout with a currency, any one-time lineItems, and a list of subscriptions. Each subscription has its own interval, lineItems, and optional description, metadata, and endTime. Use the client-side surface when a signed-in user checks out. Use the server-side surface from your backend with an API key. See API Surfaces.
Client-side (signed-in user session):
POST /api/v1/{projectId}/users/me/checkoutsPOST /api/v1/{projectId}/users/me/checkouts/preview
Server-side (API key):
POST /api/v1/projects/{projectId}/users/{userId}/checkoutsPOST /api/v1/projects/{projectId}/users/{userId}/checkouts/preview
import { ForteClient } from "@forteplatforms/sdk";
const forte = new ForteClient();
const result = await forte.users.createCheckout({
projectId,
createCheckoutRequest: {
currency: "usd",
description: "Pro plan with onboarding",
lineItems: [
{ description: "Onboarding", unitAmountCents: 9900, quantity: 1, taxCode: "txcd_20030000" },
{ description: "Starter kit", unitAmountCents: 2500, quantity: 1, taxCode: "txcd_99999999" },
],
subscriptions: [
{
interval: "MONTH",
description: "Pro plan",
lineItems: [
{ description: "Pro plan", unitAmountCents: 4900, quantity: 1, taxCode: "txcd_10000000" },
],
},
],
customerAddress: { line1: "...", city: "...", state: "...", postalCode: "...", country: "US" },
shippingAddress: { line1: "...", city: "...", state: "...", postalCode: "...", country: "US" },
// paymentMethodId is optional: omit it to let the customer enter a card now (it's saved for renewals).
},
});
const { payment, subscriptions, stripeClientSecret, stripePublishableKey, stripeConnectedAccountId } = result;The response contains:
payment: the singlePaymentfor the first charge. Each line on it has alineItemId, which you use to refund an item.subscriptions: one subscription per entry in your request, in the same order. Each isINCOMPLETEuntil the payment is confirmed. The list is empty when you sent no subscriptions.stripeClientSecret,stripePublishableKey, andstripeConnectedAccountId, as withcreatePayment.
metadata on the checkout is stored on the Payment. Each subscription keeps its own metadata.
Previewing the first charge and each renewal
createCheckoutPreview takes the same body as createCheckout, minus paymentMethodId. The call is read-only: nothing is charged or saved.
It returns:
- The first charge:
subtotalCents,taxCents,amountCents, andlineItems(one-time lines first, then each subscription's lines in request order).taxCentsisnullwhen you send no address. - Each renewal: a
subscriptionslist, in request order, where each entry hasinterval,subtotalCents,taxCents,amountCents,lineItems, andnextRenewalAt.
Use it to show "Due today: $X. Then $49.00 per month starting …" before the customer commits. Renewal figures are estimates, because each real renewal recalculates tax.
Confirming with one client secret
Confirm stripeClientSecret with Stripe Elements exactly as you do for a one-off payment (Confirming the payment in the browser). That one confirmation pays for every line and saves the card for renewals. When it succeeds, every subscription on the checkout becomes ACTIVE.
If you omit paymentMethodId, the card the customer enters is saved. If you pass a saved card, Forte uses it for this charge and for renewals. The customer may be asked for an extra card authentication step (3D Secure) during confirmation. Elements handles the prompt.
If a saved card can't be reused for renewals, each subscription is cancelled, and the one-time items stay paid.
What renews
Each subscription renews on its own, on its own interval, and only its own lines are charged. One-time items never renew. Renewals, failed-payment retries, and cancellations work as described in Subscriptions.
A subscription's price is set from its own lines, not from the checkout total. For example, a checkout of a $99 setup fee and a $49 monthly plan starts a subscription that renews at $49 plus tax.
Tax addresses
- Subscription lines are always taxed at
customerAddress, on the first charge and on every renewal. - One-time lines are taxed at
shippingAddresswhen you send one, and atcustomerAddressotherwise. shippingAddressrequirescustomerAddress. Sending only ashippingAddressreturns a400.- With no
customerAddress, no tax is computed, as withcreatePayment.
Triggers
A checkout fires one PAYMENT_COMPLETED trigger, not one per item. The body includes subscriptionIds, an array with the ID of every subscription the payment started.
{
"event": "PAYMENT_COMPLETED",
"userId": "usr_...",
"paymentId": "pay_...",
"subscriptionIds": ["sub_...", "sub_..."],
"paymentTime": "2026-11-02T18:42:11.123Z",
"state": "COMPLETED"
}subscriptionId is also sent when the payment started exactly one subscription. For a checkout with no subscriptions, the trigger omits subscriptionIds. Read subscriptionIds in new handlers. Each subscription's later renewals fire their own PAYMENT_COMPLETED with that subscription's subscriptionId.
Refunding one item
A checkout has no refund route of its own. It creates a normal Payment, so you refund it with refundPayment. Pass lineItems in the request body to refund individual lines, using the lineItemId values on the payment.
For each line, amountCents is the pre-tax amount to refund. Omit it to refund the rest of that line. Forte works out the tax share and returns it with the refund. Refunding the last of a line returns exactly the tax that remains, so the totals always match what you charged.
// Refund the whole "Onboarding" line, tax included.
const payment = await forte.projects.refundPayment({
projectId,
userId,
paymentId,
createPaymentRefundRequest: {
lineItems: [{ lineItemId: "li_..." }],
},
});
// Refund $20.00 (before tax) of the "Starter kit" line.
const partial = await forte.projects.refundPayment({
projectId,
userId,
paymentId,
createPaymentRefundRequest: {
lineItems: [{ lineItemId: "li_...", amountCents: 2000 }],
},
});Rules for line refunds:
- Don't combine
lineItemswithrefundAmountCents. That returns a400. - A
lineItemIdthat isn't on the payment returns a400. Payments created before line refunds existed have no line IDs, so refund those by amount. - An
amountCentslarger than what's left on a line returnsINVALID_REFUND_AMOUNT.
Cancellation rules
A refund can cancel the subscriptions on a checkout. What happens depends on how you refund.
| Refund | Default effect | With keepSubscriptionActive=true |
|---|---|---|
| Lines through the API | Cancels only the subscriptions whose lines you refund | Cancels none |
| Amount only through the API | Cancels all subscriptions on the payment | Cancels none |
| Partial refund from the Stripe dashboard | Cancels none | Not applicable |
| Full refund from the Stripe dashboard, or a lost dispute | Cancels all | Not applicable |
keepSubscriptionActive applies to every refund you make through the API, including a full refund. Refunds made in the Stripe dashboard and disputes don't go through it, so the last two rows are fixed.
Refunding a one-time line leaves the subscriptions running. Refunding a subscription's line cancels that subscription and no others.
The Forte console refund dialog also lets you pick lines and amounts, and shows the tax and total before you confirm.