Skip to Content
SimulatorsStripeSubscriptions and invoices

Simulators

Subscriptions and invoices

Subscriptions bill as Stripe Billing does, and their trials, renewals and payment retries happen when you move the clock. Subscribe a customer with a saved card, move the clock past the first period and list the invoices:

curl -s "$RYSTIC_URL/v1/customers" -u sk_test_123: -d email=jenny@example.com -d payment_method=pm_card_visa -d 'invoice_settings[default_payment_method]=pm_card_visa' | jq -c '{id}'
# -> {"id":"cus_lllllllllllllc"}
curl -s "$RYSTIC_URL/v1/prices" -u sk_test_123: -d unit_amount=1500 -d currency=usd -d 'recurring[interval]=month' -d 'product_data[name]=Hosting' | jq -c '{id}'
# -> {"id":"price_1"}
curl -s "$RYSTIC_URL/v1/subscriptions" -u sk_test_123: -d customer=cus_lllllllllllllc -d 'items[0][price]=price_1' -d 'expand[]=latest_invoice' | jq -c '{id, status, invoice: .latest_invoice.status, amount_paid: .latest_invoice.amount_paid}'
# -> {"id":"sub_Rystic0000000001","status":"active","invoice":"paid","amount_paid":1500}
curl -s -X POST "$RYSTIC_URL/_rystic/clock" -d '{"advance_seconds": 2764800}'
curl -s "$RYSTIC_URL/v1/invoices?customer=cus_lllllllllllllc" -u sk_test_123: | jq -c '[.data[] | {billing_reason, status, amount_paid}]'
# -> [{"billing_reason":"subscription_cycle","status":"paid","amount_paid":1500},{"billing_reason":"subscription_create","status":"paid","amount_paid":1500}]

The first invoice

POST /v1/subscriptions writes the first invoice (billing_reason subscription_create, one line per item), which latest_invoice names. With charge_automatically and a card, the subscription’s default_payment_method or else the customer’s invoice_settings.default_payment_method, the invoice is paid at once by a PaymentIntent and charge on that card, and the charge credits the balance. A customer with no currency takes the subscription’s. The create sends payment_intent.created, charge.succeeded, payment_intent.succeeded, customer.updated (only when the customer’s currency or balance moved), invoice.finalized, invoice.created, invoice_payment.paid, invoice.paid, invoice.payment_succeeded and customer.subscription.created, in that order, its payment_intent.created carrying the PaymentIntent as created, before the charge. The first invoice’s PaymentIntent carries setup_future_usage off_session, which an invoice pay that succeeds sets to null.

With no card and no trial, a create whose payment_behavior is absent, allow_incomplete or error_if_incomplete answers 400 resource_missing and creates nothing once the account holds any PaymentMethod; in an account that holds none it reads active with an open first invoice. default_incomplete answers incomplete, with an open invoice and a PaymentIntent awaiting confirmation, which an invoice pay or a confirm pays as below, attempt_count becoming 1. A send_invoice subscription is active, and its first invoice is a draft that finalizes an hour after the create. A billing_cycle_anchor ahead of a create with no trial runs the first period to the anchor, billed on the first invoice as a proration of the interval that ends there, with its own invoice item.

Before it looks anything up, a create answers Stripe’s 400 to these, in this order, each naming only that parameter even beside one it conflicts with: an empty collection_method (parameter_invalid_empty) or one other than charge_automatically or send_invoice; an empty trial_end or one that is neither now nor a Unix time; an empty cancel_at or one that is neither a Unix time nor min_period_end, max_period_end or max_billed_until.

A declined first payment

A card whose charge declines, such as pm_card_chargeCustomerFail, leaves the create incomplete, trial_end=now or not. The first invoice stays open with one attempt and next_payment_attempt null, and its PaymentIntent reads requires_payment_method. The customer is not made delinquent. The create sends payment_intent.created, charge.failed, payment_intent.payment_failed, customer.updated (as above), invoice.payment_failed, invoice.created, invoice.finalized and customer.subscription.created. Under payment_behavior=error_if_incomplete it answers the decline’s 402 instead, leaving no subscription or invoice and the PaymentIntent canceled (payment_intent.canceled).

Paying an incomplete subscription’s open invoice, by an invoice pay or by confirming its PaymentIntent with a card that charges, makes it active at once, reusing the invoice’s PaymentIntent and invoice payment: attempt_count stays 1 and auto_advance turns false. The payment sends charge.succeeded, payment_intent.succeeded, invoice_payment.paid, invoice.updated, invoice.paid, invoice.payment_succeeded and customer.subscription.updated. A confirm also sends payment_method.attached after the charge, as it attaches the card.

Trials

A trial, from trial_period_days or a trial_end after the create’s clock (its customer’s test clock’s frozen_time when it is on one), needs no card. Its first invoice is paid at zero with lines reading Free trial for <quantity> × <product>, and its items run from the create to trial_end. Its billing_cycle_anchor is trial_end unless the create sets one. With no card, a charge_automatically trial also makes a pending SetupIntent, requires_payment_method with usage off_session, named in pending_setup_intent and announced by setup_intent.created before the create’s other events; nothing later clears it. A send_invoice trial keeps pending_setup_intent null (inferred), and the succeeded SetupIntent Stripe makes for a trial with a card is not modeled. A create answers Stripe’s 400 to trial_end beside trial_period_days, and to a trial_end at or before its clock, with param trial_end.

A later trial_end on an update moves the anchor, the period end and a period-end cancel_at with it, and bills the added days on a paid zero invoice. One at or before the subscription’s clock answers Stripe’s 400 with param trial_end, and one that is neither now nor a Unix time answers 400 “Invalid trial_end: must be one of now”, as a hash or an array does. trial_end=now on an update ends the trial at once and bills the new period from the card; a declining card leaves that invoice open with one attempt and the subscription past_due, its customer not made delinquent, with invoice.payment_failed, naming no request, after the update’s own events.

Three days before trial_end (at once for a shorter trial, and on a test clock at its next advance) the clock sends customer.subscription.trial_will_end, after invoice.upcoming previewing the invoice the trial’s end will write, when it will write one and the trial runs three days or more. At trial_end, a subscription with a card, a send_invoice one or one whose trial_settings[end_behavior][missing_payment_method] is create_invoice (the default) reads active and renews as at a period end; a billing_cycle_anchor after trial_end is billed first, as a proration up to it with its own invoice item. With no card, pause pauses the subscription instead (status_details names trial_end_without_payment_method) and cancel cancels it, writing no invoice.

Renewals and failed payments

When the clock passes an active or past_due subscription’s period end, a draft renewal invoice is written (invoice.created, customer.subscription.updated) and finalizes an hour later, paid from the card under charge_automatically (charge.succeeded, payment_intent.succeeded, payment_intent.created, customer.updated when a balance moved, invoice.updated, invoice.finalized, invoice.paid, invoice.payment_succeeded and invoice_payment.paid, each naming no request). Seven days before the period end, invoice.upcoming goes out once, naming no request and previewing the renewal’s lines, amount and period. The lead is the account’s “upcoming renewal events” setting, which the simulator fixes at seven days; a subscription set to cancel at its period end gets none, nor does a period shorter than seven days.

Periods are counted from billing_cycle_anchor, each ending on its day of the month or the month’s last day, so a Jan 31 anchor renews on Feb 28, Mar 31 and Apr 30. Each item renews on its own price’s interval, so a yearly item beside a monthly one is billed once a year. An item whose current_period_end is not a time, as a seeded record’s 0, never renews.

With no card under charge_automatically, or with pm_card_chargeCustomerFail, a renewal’s or a trial end’s payment fails as the invoice finalizes: the invoice stays open with one attempt and next_payment_attempt 42.1 hours on, the subscription reads past_due and the customer delinquent. The declining card also leaves a failed charge carrying the decline (charge.failed) and the PaymentIntent requires_payment_method with the decline as its last_payment_error (payment_intent.payment_failed); paying the invoice with another card clears it. The clock retries at next_payment_attempt: with no card, or the declining one (inferred), each attempt fails again and dates the next 42.1 hours on, until the ninth fails for the last time, leaving the invoice open with auto_advance false and no retry, the subscription canceled for payment_failed and the customer delinquent. With a card that charges, the retry pays the invoice, its customer.updated last.

Voiding, marking uncollectible or paying the invoice reads the subscription active in that request, and the customer not delinquent even while another of its subscriptions is still past_due; the paid invoice reads auto_advance false and no retry, its attempt_count unchanged.

Items and prorations

An item added later, through POST /v1/subscription_items or an update’s items[], takes the subscription’s current period: that of an item of its interval, the trial’s in a trial or else its interval’s period around now, bounded by billing_cycle_anchor. Unless proration_behavior is none, an item added to an active or past_due subscription leaves a pending proration for the rest of the period, which the next renewal bills first.

Under always_invoice, POST /v1/subscription_items bills the proration at once instead, on an invoice latest_invoice then names: paid from the card under charge_automatically, paid with no payment when there is nothing to bill and under send_invoice a draft that finalizes an hour later. Under charge_automatically, when the proration is above 0 and no stored payment method resolves (a default_source naming none counts as none), the add is refused with Stripe’s 400 resource_missing, past_due or not, while a proration that rounds to 0 is paid at once; an update’s items[] add waits for the renewal.

A change of an item’s price or quantity prorates nothing. Deleting an item mid-period leaves a pending credit for its unused time, figured from what was last billed for it, and writes no invoice. An update or item delete that would leave a subscription with no item answers Stripe’s 400, as does an update whose items[] names another subscription’s item or an id no item has (“No subscription item with this ID (<id>) on the subscription.”), changing neither subscription. An update whose items[i][id] is empty answers 400 parameter_invalid_empty.

billing_cycle_anchor=now on an update of an active or past_due subscription restarts its period at once and writes no invoice; unless proration_behavior is none, it leaves one pending proration for the days the new period adds, which the next renewal bills first. On a paused subscription it is refused; in any other status it moves the anchor alone. Invoice items created with POST /v1/invoiceitems stay pending, even with a subscription in the request.

Cancellation

A create’s cancel_at records a cancellation request, canceled_at the create’s time. One before the first period’s end ends that period there, billed on the first invoice as a proration with its own invoice item (a trial keeps its trial_end and anchor, its zero line running to cancel_at), and the clock ends the subscription at cancel_at with no further invoice. A cancel_at of min_period_end, max_period_end or max_billed_until sets it to the first period’s end, billing that period whole. A cancel_at at or before the create’s clock, or beside cancel_at_period_end=true, answers Stripe’s 400.

cancel_at_period_end=true sets cancel_at to the period end; once the clock reaches cancel_at, the subscription reads canceled and customer.subscription.deleted goes out. A renewal or a trial end whose next period would run past cancel_at bills only up to it (trial_end=now still bills the whole period), and a renewal into a period shorter than the price’s interval, after a cleared cancel_at, bills that period, each as a proration. A subscription the clock ends keeps the request’s time in canceled_at, with ended_at the end and cancel_at and cancel_at_period_end kept; a DELETE while the request is pending keeps canceled_at too and clears both.

An update whose cancel_at is not after the subscription’s clock (a test clock’s frozen_time on one) answers Stripe’s 400 “The cancel_at param can only be set to a date in the future.” An update setting cancel_at_period_end, true or false, or billing_cycle_anchor=now on a paused subscription answers Stripe’s 400 asking for a resume first, writing nothing; with both, the anchor’s refusal answers. A cancel_at_period_end that is neither true nor false answers 400 “Invalid boolean: <value>” before either.

Ending a subscription, by DELETE, at its cancel_at, at its last failed retry or by its customer’s delete, stops automatic collection of every open invoice of the customer and of the subscription’s own renewal draft: each reads auto_advance false and no retry, with invoice.updated naming no request. The draft never finalizes, and the clock retries none of them. cancel_at_period_end=true stops nothing before the period ends. DELETE with invoice_now=true and prorate=true, in the body or the query string, credits each item’s unused time on a final 0.00 invoice paid an hour later, which puts the credit on the customer’s balance and clears delinquent. A cancel_at inside the period cuts the items’ period there and leaves the same credit pending, billed on such a final invoice when the clock ends the subscription.

Pause and resume

A resume under billing_cycle_anchor=now (the default) leaves the subscription paused, with a pending_update and an open subscription_cycle invoice for the period it starts, which nothing charges on its own; each resume writes another. Once the latest is paid or marked uncollectible, the subscription reads active from the resume and sends customer.subscription.resumed, carrying the subscription as it stood before (trialing, as Stripe’s does), then customer.subscription.pending_update_applied and customer.subscription.updated. Once the clock passes pending_update.expires_at, 23 hours after the resume, an unpaid resume invoice is voided, its PaymentIntent canceled and pending_update cleared, and the subscription stays paused (customer.subscription.pending_update_expired); a DELETE before then does the same before it cancels. A resume under billing_cycle_anchor=unchanged reads active at once with no invoice, on the anchor’s schedule, and unless proration_behavior is none leaves a pending proration for the rest of the period, billed with the next renewal.

Deleting a customer

Deleting a customer cancels each of its subscriptions that is not already canceled or incomplete_expired, at the delete’s time on the subscription’s clock (a pending cancellation request keeps its canceled_at), with cancellation_details.reason cancellation_requested, so none renews. It sends customer.deleted, carrying the customer as it stood, before each one’s customer.subscription.deleted. Each draft invoice of such a subscription reads uncollectible first, never finalized or charged (invoice.updated and invoice.marked_uncollectible go out before customer.subscription.deleted), and a void of it answers Stripe’s 400; its pending invoice items stay pending.

Connected accounts

A subscription created acting as a connected account settles as that account: the PaymentIntents and charges its renewals, trial end and later payments make are in that account’s lists and balance, and every event its clock sends is the account’s, listed by the account’s GET /v1/events and sent to the platform’s connect=true endpoints.

The customer balance

A customer’s balance is a credit when negative and an amount owed when positive: a customer create or update sets it, a credit note’s credit_amount lowers it and an invoice that ends below zero leaves its credit there. A draft invoice reads starting_balance as the customer’s balance when it is created, ending_balance null and amount_due as total plus starting_balance, never below 0: after a -500 credit, a draft with a 1200 item reads amount_due 700 with total 1200. A balance that changes while an invoice is a draft shows only when it is finalized.

Finalizing reads the balance again, so a credit another invoice spent first is not applied twice: the invoice reads ending_balance 0 (or the rest of a larger credit) and its PaymentIntent asks for the reduced amount. The customer’s balance becomes the invoice’s ending_balance, and customer.updated goes out before the finalize’s invoice.updated. A credit that covers the whole invoice leaves it paid at the finalize with no PaymentIntent. A subscription’s first invoice and its renewals spend the balance the same way (inferred, as are a larger credit and a positive balance). The simulator serves no /v1/customers/{customer}/balance_transactions and writes no applied_to_invoice transaction, and voiding an invoice does not give the balance back.

Invoice payments

Each invoice payment names the PaymentIntent that pays its invoice. A paid_out_of_band pay adds a paid one of type payment_record and cancels the PaymentIntent’s, and neither is then the default.

Last updated on