The first successful checkout is the easy part. Billing becomes a system when the webhook arrives twice, the trial expires without a card, or someone pays and still sees the free plan.
I went through several iterations of Codelit's billing before giving those paths the same attention as the buy button.
connects Stripe's billing records to an application's subscription state and server-enforced access policy.
What should happen after Checkout?
After Checkout, the application should confirm the relevant payment and subscription state before granting access. The browser redirect is a navigation event, not payment evidence, and a completed session does not mean every supported payment method has settled.
Create sessions on an authenticated server. Resolve the customer and eligible price from trusted application state; do not accept an arbitrary user ID, customer ID, or price as authority.
I prefer a success page that can honestly say "Confirming your subscription" until the entitlement record is ready. A fast path is welcome. A false success is not.
Keep API credentials server-side in protected configuration, and prefer a restricted key with the permissions the integration needs. The webhook signing secret is a separate secret.
Why can webhook processing happen twice?
Webhook processing can repeat because deliveries are retried and duplicates can arrive even after earlier success. Stripe also does not guarantee event ordering. The receiver must handle both properties rather than assuming a tidy sequence of subscription events.
Verify the signature against the raw request body. Durably accept the event before acknowledging it. Subscribe only to the event types the integration actually uses.
A duplicate check followed by processing and a final "processed" write has two failure windows. Concurrent requests can both pass the check. A crash after the side effect but before the final write can also repeat the work.
A safer design needs an atomic claim, explicit processing state, retryable work, and reconciliation for uncertain outcomes. An event ledger prevents repeat application in your system; it does not independently prevent a duplicate charge created by a separate API call.
What does a durable billing pipeline look like?
A durable pipeline separates event receipt from processing and makes interrupted work recoverable. Record what was received, what is being applied, and what completed. Use transactional updates for local state and idempotent operation identifiers for external effects.
| Stage | Responsibility | Failure behavior |
|---|---|---|
| Receive | Verify signature and durably store or enqueue | Do not acknowledge unaccepted work |
| Claim | Atomically acquire processing ownership | Duplicate workers do not both apply |
| Resolve | Load the correct account and current billing state | Missing mappings remain visible |
| Apply | Update entitlement state under concurrency control | Retry without repeating local effects |
| Complete | Record the outcome | Recover abandoned claims |
| Reconcile | Compare local state with Stripe | Repair missed or stale updates |
A durable database inbox can be the queue. If storage and a separate queue cannot be committed together, use an outbox or another recoverable dispatch design rather than leaving a gap between them.
For external effects such as emails, record an outgoing operation separately. A database transaction cannot atomically commit a remote send.
How do you keep subscription state consistent?
Keep a server-owned projection of billing state for application reads, and reconcile it with Stripe when events are delayed or disagree. Client listeners improve responsiveness, but paid access must be enforced on the server where the protected work happens.
Map Stripe customer and subscription identifiers to your own user or organization records. Session metadata is not automatically copied to every invoice object, so a handler should not depend on finding the same field everywhere.
Serialize updates per subscription or use a version-aware update policy. Fetching current Stripe state helps with out-of-order events, but concurrent workers can still overwrite newer local state unless the application protects that write.
This is a distributed-state problem, closely related to the provider boundary: receiving a successful response is not the same as completing the full product operation.
Which trial policy should you choose?
Choose trial behavior before offering the trial, and describe it clearly to the customer. A no-card trial can reasonably cancel or pause at expiry. Invoicing is also supported, but should be an intentional billing decision rather than an overlooked default.
Stripe documents the cancel, pause, and create-invoice options. Test the option you select with and without a payment method, including the customer's route back after expiry.
For Codelit's no-card trial design, cancellation was easier to explain than leaving an unpaid subscription behind. That is a product judgment, not a rule every SaaS must follow.
How should failed payments affect access?
Failed-payment access should follow a documented grace policy with clear boundaries. A first failure does not have to mean immediate lockout, but continuing costly usage indefinitely is not a neutral choice either.
Stripe Smart Retries selects retry timing within configured recovery settings. An attempt count is not a clock. Do not interpret the third attempt as "three days have passed" or assume it means retries are exhausted.
| State | A possible policy | What needs verification |
|---|---|---|
| First failure | Notify the customer and begin a bounded grace period | Recovery link works |
| Grace period active | Keep appropriate access or cap expensive usage | Server checks match the policy |
| Grace period expired | Restrict paid features | Preserve the user's data and explain recovery |
| Payment recovered | Restore eligible access | Old events cannot undo recovery |
| Subscription ended | Apply the configured end-of-access policy | No stale paid entitlement remains |
This is an example policy, not a statement of current Codelit plan terms. Measure recovery and customer impact before claiming a particular grace period improves revenue.
What changes between testing and live billing?
Testing and live billing have separate data and configuration, so a working sandbox proves only that environment. Before release, verify the live endpoint, signing secret, products, prices, customer mappings, portal configuration, and supported payment paths.
Use Stripe's test tooling to exercise trial expiry and failure cases without creating real charges. A live-mode configuration check does not require buying your own subscription unless that specific test is authorized.
Also review tax obligations. Stripe's recurring-payment tax guide covers the setup; enabling automatic calculation alone does not establish where you are registered or required to collect tax.
When should you use the Customer Portal?
Use the Customer Portal when its supported workflows match the customer's billing needs. It can remove a substantial amount of account-management UI from your product, but the allowed actions and account mapping still need deliberate configuration and testing.
The portal integration guide covers hosted management. Create the session from the authenticated user's stored customer association. Allowing a caller to choose any customer ID would turn a convenience feature into an access-control bug.
What did billing teach me about pricing?
Billing made the gap between a technically valid plan and a useful offer hard to ignore. Infrastructure cost matters, but customers decide based on the value they can recognize and the work they can complete.
Historical prices need context, and conversion figures need a measurement window. The current pricing page is the place for current terms; the growth story is the place for the product lessons.
For a pricing experiment, record the cohort, dates, trial changes, and denominator. A price cut and a new trial launched together do not isolate the effect of either one.
How should plan changes handle proration?
Plan changes should show the customer what will be charged or credited and when. Preview the billing impact, apply the chosen policy, and reconcile access from the resulting subscription state instead of treating every invoice as a normal renewal.
Stripe distinguishes creating prorations from invoicing them immediately. A prorated line item is not evidence that payment has completed.
If I rebuilt the integration, I would write the failure-state table before the checkout handler. The polished billing experience is not the one with the fewest screens. It is the one that stays understandable when money, messages, and access arrive at different times.
Questions people actually ask
- What makes a Stripe integration production ready?
- Production billing needs more than Checkout. It needs verified webhooks, durable processing, correct handling of duplicate and out-of-order events, server-enforced entitlements, and tested trial and payment-failure behavior. The release checklist should include recovery when Stripe and the application's stored state disagree.
- Why do Stripe webhooks need idempotent processing?
- Webhook deliveries can repeat, and two deliveries can arrive concurrently. A simple read-before-write check is not enough. Use an atomic event claim or transactional update, plus a recovery path for interrupted work. Any external side effect needs its own idempotency design.
- What should happen when a trial ends without a card?
- Choose an explicit policy that matches what the customer was told. Stripe supports canceling, pausing, or creating an invoice when the trial ends without a payment method. Cancellation can be a straightforward choice for a no-card trial, but it is not the only valid product policy.
- Should access stop after a failed subscription payment?
- That depends on the product's risk and published policy. A defined grace period can give customers time to fix a payment method, while expensive usage may need a tighter limit. Base access on authoritative subscription state and elapsed time, not a guessed retry schedule or invoice attempt count.
- Should I build a billing management interface?
- Start by checking whether Stripe's Customer Portal supports the operations your customers need. It can cover payment methods, invoices, cancellation, and configured plan changes. Create portal sessions on an authenticated server using the Stripe customer linked to that user, never a customer ID supplied on trust.