# Purchases, renewals, and restores

The receipt API accepts store evidence for an authenticated app and customer. It is the point where a storefront purchase becomes a verified CherrySub observation. Notification delivery and reconciliation complement this client path; neither allows an unverified client payload to grant access.

## Receipt request contract

Send `POST /v1/receipts` with an app secret on a trusted backend, or a publishable key and customer token on a public client.

```json
{
  "platform": "apple",
  "app_user_id": "user-123",
  "store_payload": "STOREKIT_TRANSACTION_JWS"
}
```

```json
{
  "platform": "google",
  "app_user_id": "user-123",
  "product_type": "subscription",
  "store_payload": "GOOGLE_PURCHASE_TOKEN"
}
```

Apple uses the StoreKit transaction's JWS, not an old receipt blob or a locally generated test string. Google requires `product_type` to be `subscription` or `one_time_product`; its token is authoritatively read through Android Publisher. Unknown fields are rejected. The request-body ceiling is 256 KiB.

Success returns `{ "event_id": "123", "deduplicated": false }`. The event ID is a decimal string. A duplicate can return `deduplicated: true`; that is still acceptance, not a second sale.

## Complete the store purchase after acceptance

Use the SDK's submit-and-finish/complete helper, or reproduce its ordering explicitly: check the store purchase state, submit evidence, await backend acceptance, then finish, acknowledge, consume, or complete as appropriate.

Pending, cancelled, unverified, or unknown purchases must not be sent as successful purchases. Your app owns the choice between acknowledging and consuming a Google product and its offer/purchase UI. A server failure must leave the store transaction available for a safe retry.

## Retry without granting twice

A timeout can happen after a server commit. Retry the same store evidence and allow server idempotency to decide whether it was already accepted. Do not create a new artificial purchase ID to force success.

SDKs automatically retry only once after a 401 token refresh. They do not universally retry 429, validation errors, or store outages. Implement bounded application retry where appropriate, respect `Retry-After`, and leave durable store purchase completion pending until the backend acknowledges acceptance.

A 404 subscriber response before first purchase means no customer alias exists yet. A 200 receipt response does not prove a product was mapped to the access level your application expects; read the subscriber and inspect its mapping.

## Renewals and cancellation

Store notifications provide signed/authenticated triggers for subsequent changes. Google notification integers are a signal to read current store state, not the final purchase truth. Apple nested signed transaction/renewal data is independently verified.

Turning off auto-renewal can leave the subscription active through its paid expiry. Grace and billing retry are separate store states; inspect verified access and expiry rather than treating every cancellation as immediate loss of service. Free trials do not count as funded paid renewals.

## Refunds and revocations

Verified refund/revocation observations update transaction/access projections. Full refunds and chargebacks can remove the affected purchase's grant. Google partial refunds preserve remaining quantity according to the authoritative observation and refund history. Other active grants can still keep an access level active.

CherrySub reports store refunds and refund requests; the current dashboard does not issue a refund in the store. Use the store's authorized refund process, then verify the resulting state and event in CherrySub.

## Restore and transaction listeners

Listen for unfinished/restored purchases using your storefront library, submit valid evidence under the intended authenticated App User ID, and complete only after success. Implement both the interactive purchase response and the storefront's update/recovery stream; an app can be interrupted between purchase and completion.

A verified restore can transfer a purchase binding without merging customer identities. Test this explicitly with two application accounts. See [customer identity](/docs/customer-identity) and the [sandbox test matrix](/docs/testing).
