Skip to content

Test store

The test store is a channel that plays the role of a store. Its purchases go through the same recording, events, signed webhooks, GET /v1/customers and GET /v1/events as real stores. Channel id: test_store, platform: test.

Everything it produces has environment: "test", platform: "test" and data.store: "test_store". Your backend should never treat it as revenue.

The test store is off by default. While it is off, every test store call returns 403 test_store_disabled and automatic renewals pause.

Terminal window
npx @kasskeep/cli test-store enable --id app_xxx
npx @kasskeep/cli test-store config --id app_xxx --period-seconds 60 --renewal-success-rate 0.8 --products "demo.*"
Setting Default Meaning
subscription_period_seconds 300 Length of one test subscription period (10 seconds to 366 days)
renewal_success_rate 1 Probability that an automatic renewal succeeds (0 to 1)
grace_period_seconds 120 Grace period after a failed renewal; 0 goes straight to billing retry
billing_retry_seconds 300 Billing retry after the grace period; then the subscription expires
products any Allowed product ids; a trailing * matches a prefix. Others fail with 400 product_not_allowed
prices 0.99 USD {"id or prefix*": {price_micros, currency, title?}}; exact match first, then longest prefix

Turning it on for an app that already has production transactions or store credentials returns a test_store_on_live_app warning. It is allowed: one app can serve real and test traffic, separated by environment.

From the client with the public key, or through the SDK with testStore: true:

POST /v1/test-store/purchases
X-Kasskeep-Key: pk_...
{"app_user_id": "6f1c...", "product_id": "demo.gems_100", "type": "inapp", "quantity": 1, "outcome": "success"}
  • type: inapp or subs. quantity 1 to 100, subscriptions only 1.
  • outcome: success (default), cancelled, pending or failed. Only success records a purchase. pending keeps a pending purchase that you can approve or decline later.
  • Optional price_micros and currency override the price.
  • Transaction ids look like test_<24 hex>. A second active subscription to the same product returns 409 already_subscribed.

GET /v1/test-store/products?ids=a,b&type=inapp returns allowed products with prices, which the SDK’s getProducts uses.

Terminal window
npx @kasskeep/cli test-store simulate --id app_xxx --scenario renew_fail --user <app_user_id>
npx @kasskeep/cli test-store simulate --id app_xxx --scenario upgrade --user <app_user_id> --to-product-id pro_yearly
Scenario Applies to Event
renew Active subscription with auto-renew on subscription.renewed
renew_fail Active subscription subscription.billing_issue
grace_expire In grace period subscription.grace_period_expired
recover In grace period or billing retry subscription.renewed with data.recovered
cancel / uncancel Auto-renew on / off subscription.canceled / subscription.uncanceled
expire Not expired subscription.expired
upgrade Active, needs to_product_id subscription.product_changed, immediate
downgrade Active, needs to_product_id subscription.product_changed, at next renewal
refund Purchase or subscription refund
refund_reverse Refunded refund.reversed
revoke Purchase or subscription revoked (family sharing)
pending_approve Pending purchase or subscription.started
pending_decline Pending None

A scenario that does not fit the current state returns 409 scenario_not_applicable.

Terminal window
npx @kasskeep/cli test-store advance --id app_xxx --user <app_user_id> [--outcome failure]

This moves the end of the current period, grace period or retry period to now and immediately runs what is due: renew, fail, end grace, or expire. The system clock is not touched.

Without any action, a job runs every 10 seconds and renews or expires due test subscriptions according to the settings.