# Test clocks

Stripe's test clocks: customers and subscriptions on a clock of their own, moved only by its advance, which completes by itself as Stripe's does.

A test clock gives a group of customers and their subscriptions a time of their own, so you can move one billing scenario through months without moving the rest of the account. Put a subscribed customer on a clock, advance it past the first period and watch the advance complete:

```bash
curl -s "$RYSTIC_URL/v1/test_helpers/test_clocks" -u sk_test_123: -d frozen_time=1772323200 | jq -c '{id, status}'
# -> {"id":"clock_1Rystic00000000000000001","status":"ready"}
curl -s "$RYSTIC_URL/v1/customers" -u sk_test_123: -d test_clock=clock_1Rystic00000000000000001 -d payment_method=pm_card_visa -d 'invoice_settings[default_payment_method]=pm_card_visa' | jq -c '{id, test_clock}'
# -> {"id":"cus_lllllllllllllc","test_clock":"clock_1Rystic00000000000000001"}
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, test_clock}'
# -> {"id":"sub_Rystic0000000001","status":"active","test_clock":"clock_1Rystic00000000000000001"}
curl -s "$RYSTIC_URL/v1/test_helpers/test_clocks/clock_1Rystic00000000000000001/advance" -u sk_test_123: -d frozen_time=1775088000 | jq -c '{status, frozen_time}'
# -> {"status":"advancing","frozen_time":1772323200}
curl -s "$RYSTIC_URL/v1/test_helpers/test_clocks/clock_1Rystic00000000000000001" -u sk_test_123: | jq -c '{status, frozen_time}'
# -> {"status":"advancing","frozen_time":1772323200}
curl -s "$RYSTIC_URL/v1/test_helpers/test_clocks/clock_1Rystic00000000000000001" -u sk_test_123: | jq -c '{status, frozen_time}'
# -> {"status":"ready","frozen_time":1775088000}
curl -s "$RYSTIC_URL/v1/invoices?customer=cus_lllllllllllllc" -u sk_test_123: | jq -c '[.data[].billing_reason]'
# -> ["subscription_cycle","subscription_create"]
```

### What lives on a clock

A customer created with `test_clock` lives on that clock's `frozen_time`: its `created` and its subscriptions' `created`, `start_date`, periods, trial dates, invoices and invoice payments read it. A subscription and its invoices carry the clock's `test_clock`, and an update, cancel or `trial_end=now` on one stamps the clock's time. The events they send keep the simulator's own clock, and the PaymentIntents and charges paying their invoices carry no `test_clock` and the simulator's time, as Stripe's do. A Checkout Session for a customer on a test clock still expires on the simulator's clock (inferred).

`POST /_rystic/clock` moves only the subscriptions on no test clock, and one clock's advance moves only its own customers and subscriptions.

### The advance

An advance completes by itself. The next API request still reads the clock `advancing` with its old `frozen_time`, and an advance sent then answers 429 "Test clock advancement underway", as Stripe's does. The request after it reads the clock `ready` at the target, with `status_details` `{}`, having moved the clock's customers and subscriptions to it: every renewal, trial warning and end, draft finalize and `cancel_at` due between the old and new `frozen_time` happens at its own instant on the clock, while its events carry the time of the request that completes the advance.

A request counts toward completing an advance once the simulator hands it to an operation, a hosted page or `/_dispute`, even when that handler refuses it (a 404 for an unknown id, say), and so does a scenario timeline call to an API route when it fires. These leave the clock advancing: a request the simulator answers before that (an unserved path or method, a request with no key, an admin-token refusal, an oversized body, a network-profile reject), a `/_rystic/` control call, a clock jump or paced tick that fires no timeline call, a timeline call to `/_dispute` and a read of the Dashboard, its streams, the explorers or a file URL.

`frozen_time` is an integer as sent: `+<t>` reads as `<t>`, and a space-prefixed, empty, array or hash value answers 400 `parameter_invalid_integer`, echoing the value as Stripe renders it (`{:a=>"<t>"}` for `frozen_time[a]`). It runs from 2000-01-01 to 2100-01-01, not before the clock's time (its own time is accepted and completes there) and at most two of the shortest recurring interval of the clock's live subscriptions ahead: two calendar months for a monthly one, six years on a clock holding none. A clock still advancing answers its 429 before `frozen_time` is read. A clock create takes the same integers and range, and answers a negative time 400 "Invalid timestamp: must be an integer".

### Events

A clock's create sends `test_helpers.test_clock.created`, carrying the clock. Its advance sends `test_helpers.test_clock.advancing` at once and, when it completes, every event the completion settles, then `test_helpers.test_clock.ready`, naming no request. `test_helpers.test_clock.ready` is the platform's even for a clock made acting as a connected account, and a clock act for the platform stays the platform's when a connected account's request completes the advance.

### Deleting a clock

Deleting a test clock deletes its customers as a customer delete does and cancels every live subscription on it at the clock's time, those of a customer already deleted included ([Subscriptions and invoices](https://www.rystic.ai/docs/simulators/stripe/features/subscriptions.md)). It sends each customer's `customer.deleted`, oldest first, then `test_helpers.test_clock.deleted`, then each subscription's `customer.subscription.deleted`, newest first. Each subscription's renewal draft reads `uncollectible`, with `invoice.updated` and `invoice.marked_uncollectible`. What a delete does to the clock's invoice items, credit grants and credit balance transactions is not modeled.

A customer create whose `test_clock` names no clock, or a deleted one, answers Stripe's 400 `resource_missing` on `test_clock`; an empty `test_clock` answers 400 `parameter_invalid_empty`, and a hash 400 "Invalid string: …".

---

Source: https://www.rystic.ai/docs/simulators/stripe/features/test-clocks · Markdown: https://www.rystic.ai/docs/simulators/stripe/features/test-clocks.md
