payinOPERATIONS MANUAL

Latest articles

Zero-total Stripe Checkout: fulfill the order without a PaymentIntent

Design a Session-based fulfillment branch for free Checkout orders, with payment-state checks, optional payment references, concurrency controls, and a troubleshooting checklist.

Published; sources checked:

One-time hosted Checkout, zero-priced items and full-discount orders; excludes subscription lifecycle, tax advice, expiration and inventory reservation. Regional availability not established by the retrieved sources.

The order exists even when the payment object does not

A zero-total Checkout order is not a failed card payment waiting for a missing PaymentIntent. Stripe explicitly says that completed no-cost Checkout Sessions have no associated PaymentIntent and that fulfillment must handle checkout.session.completed instead of PaymentIntent events.[1] If a paid-order pipeline starts by requiring a pi_ identifier, a legitimate free order can fall out before an entitlement, download, or shipping task is created. The correction is to make the Checkout Session the entry point for order fulfillment, with payment references optional.

This article covers one-time, hosted Checkout in mode=payment, including genuinely free line items and discounts that reduce the final total to zero. It is not an inventory reservation or session-expiration guide. Subscription trials, setup-mode card collection, tax treatment, and account-specific eligibility require separate decisions. The documentation reviewed here does not provide a country-by-country availability list; do not interpret its examples as universal regional eligibility.[1]

What the official contract does—and does not—say

Stripe requires API version 2023-08-16 or later for no-cost orders through the Checkout Sessions API. You can use a Price with unit_amount=0 or supply zero-priced price_data; a 100% coupon is another documented route. When the total is zero, Checkout does not collect a payment method. If customer is omitted, this flow creates a Customer automatically, so guest customers are not supported.[1] A free acquisition therefore still needs a deliberate customer-to-user mapping, even though it has no payment record.

Do not replace one brittle assumption with another: “every free order always has payment_status=no_payment_required” is not the complete contract established by these sources. The Session reference lists paid, unpaid, and no_payment_required, while the fulfillment example accepts a status other than unpaid.[2][3] The retrieved reference describes no_payment_required using setup mode and a billing-cycle-anchor scenario, and describes paid as also covering a successfully processed zero-dollar subscription trial invoice.[3] Community descriptions of a particular zero-total payload are useful demand evidence, not a reason to hard-code a universal mapping. Record the actual fields in your own sandbox and pin the relevant API version before deployment.

A practical fulfillment decision

  1. Receive checkout.session.completed and verify its webhook signature. Pass its Session ID to a shared server-side fulfillment routine. Stripe’s guide also routes checkout.session.async_payment_succeeded to that routine for delayed-payment success.[2]
  2. Retrieve the Session with line_items expanded; retrieve additional line-item pages where necessary. Stripe recommends fulfilling the finalized items rather than relying on an earlier cart snapshot.[1][2] As an application control, match the Session to the expected local order, account context, customer, mode, and environment before granting anything.
  3. For this one-time-order routine, require a completed Session and inspect payment_status. The object reference warns that status=complete alone can coexist with payment processing still in progress.[3] Keep unpaid out of immediate delivery. Treat paid or no_payment_required as eligible payment-state inputs, not as substitutes for product, identity, and completion checks.
  4. If amount_total is zero and the completed order is otherwise eligible, allow fulfillment without payment_intent. If a nonzero order has an unexpected missing reference, investigate the actual state rather than relabeling it as free. A null reference by itself is not proof of a completed no-cost order.
  5. Record fulfillment against the Session and local order with a concurrency-safe uniqueness rule. Stripe says the routine can be called more than once, even simultaneously.[2] A database transaction and an outbox for downstream work are implementation recommendations, not guarantees provided by merely receiving a webhook.

Three scenarios that expose hidden assumptions

First, a paid course becomes free after a full discount. The order still grants a course entitlement, but a mandatory PaymentIntent foreign key prevents the entitlement transaction from committing. Prefer an optional payment reference and a separately recorded settlement classification such as no-cost. Preserve the real amount, currency, applied discount information, and final items; do not manufacture a successful payment record to satisfy a schema.

Second, a free physical item still needs a fulfillment address and an operations task. Decide which information your checkout and application must collect. Evaluate the actual final amount rather than the advertised discount percentage: a zero-priced item is not sufficient evidence that the entire order has a zero total. The no-payment-method behavior documented by Stripe is conditioned on the total being zero.[1]

Third, the webhook and the success-page request both call fulfillment. Stripe recommends webhooks and permits the landing page to call the same routine for responsiveness, while warning that customers might never reach that page.[2] Both callers must converge on the same idempotency boundary. A success URL is neither a payment receipt nor authorization to grant any product named in browser parameters.

Troubleshooting and a deployment checklist

  1. If no entitlement appears, first confirm that the completed Session exists and that the endpoint receives checkout.session.completed. A pipeline subscribed only to payment_intent.succeeded cannot cover the documented no-cost case.[1]
  2. If the event arrives but nothing happens, inspect early returns that require payment_intent or only accept one payment_status. Log the Session ID, mode, amount_total, payment_status, and fulfillment decision; avoid dumping unnecessary customer information.
  3. If persistence fails, check non-null payment columns, joins to payment tables, receipt templates, and downstream jobs that dereference a missing payment object. Keep monetary refunds distinct from canceling or revoking a free entitlement; a missing PaymentIntent is not an instruction to create a synthetic charge.
  4. In a sandbox, exercise a zero-priced order, a full-discount order, an ordinary paid order, and a still-unpaid delayed-payment completion. Replay delivery and invoke the routine concurrently, then confirm that each eligible order creates only one business fulfillment. These are proposed acceptance tests, not tests executed for this article.

Product boundaries and source notes

Payment Links and pricing tables have additional account-age and enablement rules in the no-cost guide. It says accounts created after August 17, 2023 support these orders by default; older accounts can enable them in Checkout settings, with a three-day period to disable the feature again.[1] This is an enablement caveat, not a discussion of Payment Link completion limits. Check the guide before changing a live account, and do not treat a subscription trial as interchangeable with a one-time free order.

The sources below were retrieved on September 22, 2026. No publication or last-updated date was stated in the retrieved documents. The API-version and account-creation dates above are product conditions, not publication dates. This research used public documentation and search results only; no Stripe account, credentials, or live checkout was accessed.

Sources and dates

Official documentation; the site may return localized text. Publication and update dates are not stated; retrieved and checked on 2026-09-22. Retrieval is not publication. This is documentation research, not a live-account test, PayIn feature claim, or legal, tax or financial advice.

More guides

All guides