Simulators
Subscription changes and the customer balance
Changing a subscription’s items, ending it or pausing it changes what the clock bills next. Subscribe a customer, set the subscription to cancel at its period end, then move the clock past it:
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' | jq -c '{id, status}'
# -> {"id":"sub_Rystic0000000001","status":"active"}
curl -s "$RYSTIC_URL/v1/subscriptions/sub_Rystic0000000001" -u sk_test_123: -d cancel_at_period_end=true | jq -c '{status, cancel_at}'
# -> {"status":"active","cancel_at":1775001600}
curl -s -X POST "$RYSTIC_URL/_rystic/clock" -d '{"advance_seconds": 2764800}'
curl -s "$RYSTIC_URL/v1/subscriptions/sub_Rystic0000000001" -u sk_test_123: | jq -c '{status, ended_at}'
# -> {"status":"canceled","ended_at":1775001600}
This page covers what the clock and the simulator decide, not parameter validation. “Unconfirmed” marks behavior not yet checked against Stripe. Amounts are in the currency’s smallest unit, as on Stripe, so 1500 is $15.00. How the first invoice, trials and renewals bill is on Subscriptions and invoices.
Differs from Stripe: the simulator returns Stripe’s 400 for many invalid parameter values, but not for all.
Cancel at the period end
cancel_at_period_end=true sets cancel_at to the period end. When the clock reaches cancel_at, the subscription becomes canceled and customer.subscription.deleted goes out.
Setting cancel_at when creating a subscription records a cancellation request. A cancel_at before the first period’s end ends that period there: the first invoice bills it as a proration, and the clock ends the subscription at cancel_at with no further invoice. min_period_end, max_period_end or max_billed_until sets cancel_at to the first period’s end.
A renewal or trial end whose next period would run past cancel_at bills only up to it, as a proration; trial_end=now still bills the whole period.
However a subscription ends (a DELETE, its cancel_at, its last failed retry or its customer’s deletion), automatic collection stops on every open invoice of the customer and on the subscription’s renewal draft. Each gets auto_advance false and no retry, announced by an invoice.updated with request.id null. 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 credits each item’s unused time on a final invoice for 0, paid an hour later. That puts the credit on the customer’s balance and clears delinquent. A cancel_at inside the period leaves the same credit pending, billed on such a final invoice when the clock ends the subscription.
Items and prorations
An item added later, with POST /v1/subscription_items or an update’s items[], joins the subscription’s current period. Unless proration_behavior is none, adding an item to an active or past_due subscription leaves a pending proration for the rest of the period. The next renewal bills it first.
With proration_behavior=always_invoice, POST /v1/subscription_items bills the proration at once instead, on an invoice that latest_invoice then names.
Differs from Stripe: an item added through an update’s items[] waits for the renewal, even under always_invoice.
| Subscription | The proration invoice |
|---|---|
charge_automatically, with a card, proration above 0 | is paid from the card |
charge_automatically, no stored payment method, proration above 0 | isn’t written: the add returns 400 resource_missing, past_due or not. A default_source that names nothing counts as no payment method. |
charge_automatically, proration of 0 or one that rounds to 0 | is paid at once, with no payment |
send_invoice | is a draft that finalizes an hour later |
Differs from Stripe: changing an item’s price or quantity prorates nothing.
Deleting an item mid-period leaves a pending credit for its unused time, based on what was last billed for it. It writes no invoice.
Differs from Stripe: the credit waits for the renewal even under always_invoice, where Stripe invoices it at once.
billing_cycle_anchor=now on an update of an active or past_due subscription restarts the 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’s refused; in any other status it moves the anchor only.
Invoice items created with POST /v1/invoiceitems stay pending, even when the request names a subscription.
Pause and resume
How a resume works depends on billing_cycle_anchor.
billing_cycle_anchor=now (the default). The subscription stays paused, with a pending_update and an open subscription_cycle invoice for the period the resume starts. Nothing charges that invoice on its own, and each resume writes another.
- When the latest resume invoice is paid or marked uncollectible, the subscription becomes
activefrom the resume. It sendscustomer.subscription.resumed, carrying the subscription as it was before (trialing, as on Stripe), thencustomer.subscription.pending_update_appliedandcustomer.subscription.updated. - If the invoice is still unpaid when the clock passes
pending_update.expires_at, 23 hours after the resume, it’s voided, its PaymentIntent is canceled andpending_updateis cleared. The subscription stayspaused(customer.subscription.pending_update_expired). ADELETEbefore then does the same, then cancels.
billing_cycle_anchor=unchanged. The subscription is active at once, with no invoice, on the anchor’s schedule. Unless proration_behavior is none, it 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 isn’t already canceled or incomplete_expired, with cancellation_details.reason cancellation_requested, so none renews. Each draft invoice of those subscriptions becomes uncollectible, never finalized or charged, and its pending invoice items stay pending.
customer.deleted, carrying the customer as it was, goes out before each subscription’s customer.subscription.deleted. So do each draft’s invoice.updated and invoice.marked_uncollectible.
Connected accounts
A subscription created while acting as a connected account belongs to that account. The PaymentIntents and charges from its renewals, trial end and later payments appear in that account’s lists and balance. Every event its clock sends is the account’s: the account’s GET /v1/events lists it, and the platform’s connect=true endpoints receive it.
The customer balance
A customer’s balance is a credit when negative and an amount owed when positive. Creating or updating the customer sets it, a credit note’s credit_amount lowers it and an invoice that ends below zero leaves its credit there.
A draft invoice has:
starting_balance: the customer’s balance when the draft was createdending_balance: nullamount_due:totalplusstarting_balance, never below 0. After a credit of -500 ($5.00), a draft with a 1200 ($12.00) item hasamount_due700 andtotal1200.
A balance change while the invoice is a draft shows only once it’s finalized. Finalizing reads the balance again, so a credit another invoice spent first isn’t applied twice. The invoice’s ending_balance is 0, or what’s left of a larger credit, and its PaymentIntent asks for the reduced amount. The customer’s balance becomes the invoice’s ending_balance. A credit that covers the whole invoice leaves it paid at finalization, with no PaymentIntent.
A subscription’s first invoice and its renewals spend the balance the same way. That, a larger credit and a positive balance are unconfirmed.
Differs from Stripe: the simulator serves no /v1/customers/{customer}/balance_transactions and writes no applied_to_invoice transaction. Voiding an invoice doesn’t give the balance back.
Invoice payments
Each invoice payment names the PaymentIntent that pays its invoice. Paying an invoice paid_out_of_band adds a paid invoice payment of type payment_record and cancels the PaymentIntent’s. After that, neither is the default.