---
name: applicaster-payments-storefront
description: "Payments & Storefront API integration: use when building or hardening a customer-hosted backend that implements the Applicaster Payments & Storefront provider endpoints (products feed, transactions/receipt validation, entitlements, user subscriptions) the in-app purchase plugin drives across the app's stores (Apple, Google Play, Amazon, Roku). Works in any stack; asks which stores/platforms and payment provider are in scope and whether receipt validation is synchronous (201) or asynchronous (202 + Location), adds request AND response validation to every endpoint through the project's own validator, and ships a skill-local payments-e2e tool that validates a running provider against the contract."
---

# applicaster-payments-storefront

Implement or harden the **Payments & Storefront API** — the customer-hosted backend the
Applicaster in-app-purchase plugin calls to list products, validate store receipts, and
report what a user owns. The deliverable is provider endpoints that satisfy the documented
contract, with validation on **both** the request boundary and the response boundary, plus
an executable proof they do via the skill-local
[Payments E2E tool](tools/payments-e2e/README.md).

The app performs the actual purchase through the **native store** (Apple, Google Play,
Amazon, Roku); this backend never touches money. Its job is to map the storefront to
per-store SKUs, **validate the receipt** the store returns, and answer "does this user own
this?" so the app can unlock content. Which stores and platforms are in scope, and whether
receipt validation returns synchronously or via a polled `Location`, shapes every endpoint —
so those are the first things to resolve. See
[provider-decision-guidance.md](references/provider-decision-guidance.md).

## Flows

These are the flows from the Payments & Storefront guide's "The Flow" diagram. Walk the
developer through them, and present this diagram, so the endpoints they build map onto what
the app actually does. The endpoints are the labelled arrows into the backend.

**Purchase flow** — open storefront → buy → validate receipt → unlock:

```mermaid
sequenceDiagram
    actor User
    participant App
    participant Store as Native Store (IAP library)
    participant Products as Products endpoint
    participant Tx as Transactions endpoint
    participant Ent as Entitlements endpoint

    User->>App: Opens storefront (settings, or taps a non-free item)
    App->>Products: GET products (access_token / id_token)
    App->>Store: Fetch store products, match on external_sku
    App-->>User: Present storefront with products
    User->>App: Taps a product
    App->>Store: Purchase request (IAP native library)
    Store-->>App: Receipt
    App->>Tx: POST receipt (Notify Payment CloudEvent)
    alt Asynchronous validation
        Tx-->>App: 202 Accepted + Location (poll URL)
        App->>Ent: Poll Location (usually GET entitlement/:productId)
        Ent-->>App: 200 entitled
    else Synchronous validation
        Tx-->>App: 201 Created
    end
    App-->>User: Success → unlock item / next screen
```

**Restore flow** — the app re-reads the storefront (the optional Restorable Products feed
when it differs from current offers, else the Products feed) and re-runs the same validate →
entitle path to recover what the user already paid for.

**Roku silent restore** — at startup a Roku app restores subscriptions with **no UI**, which
is why the User Subscriptions endpoint is mandatory for Roku:

```mermaid
sequenceDiagram
    participant App as Roku app (startup)
    participant Subs as User Subscriptions endpoint
    participant Ent as Entitlements endpoint

    App->>Subs: GET user-purchases (access_token / id_token)
    Subs-->>App: active-purchases feed
    loop each active purchase
        App->>Ent: Verify against the payment provider
        Ent-->>App: 200 entitled / 401
    end
```

## Scope

- Use for a new or existing backend that must expose the Payments & Storefront provider
  endpoints — Products feed, Transactions (receipt validation), Entitlements, and User
  Subscriptions — whether it fronts a billing provider (Cleeng, RevenueCat, Stripe-backed,
  a store-receipt validator) or the customer's own store.
- **Adapt to the target project's stack** — language, framework, package manager,
  validation library, HTTP client, test runner, config/secrets, and deployment
  conventions. Do not assume a JavaScript implementation, and do not add a validation or
  HTTP dependency the project lacks a use for; use its existing tools.
- The contract is fixed and documented. Confirm live docs; do not invent feed fields,
  CloudEvent keys, or status codes.

## Non-Negotiable Gates

1. Every endpoint validates its **request** before doing any work, and validates its
   **response** payload before returning it, using the target project's own validator. A
   request-only integration is incomplete — the response check is what stops a broken feed
   or a missing SKU reaching the device. See [validation-patterns.md](references/validation-patterns.md).
2. Every purchasable entry in the Products feed carries `extensions.external_sku` with the
   store SKU for **every store in scope** (`apple_store`, `google_play`, `amazon`, `roku`,
   `vizio`).
   A missing store SKU silently breaks purchase mapping on that platform and only fails
   on-device — validate SKU presence on the way out.
3. The Transactions endpoint receives a `com.applicaster.payment.validate` CloudEvent,
   **validates the store receipt**, and returns `201` (synchronous) or `202` with a
   `Location` header to poll (asynchronous). A failed receipt validation must not entitle
   the user; never fabricate a success.
4. Do not report the work complete without a `## Zapp Configuration` section listing the
   exact URLs for the plugin (Products, Restorable Products, Transactions, Entitlements,
   User Subscriptions) and, per store in scope, the SKU / subscription-terms setup. Note
   that the **User Subscriptions** endpoint is **mandatory for Roku** silent restore.
5. Do not claim end-to-end validation without running the `payments-e2e` tool against a
   running server, or, if a live server is unavailable, without naming that as the blocker.

## 1. Establish facts

1. Fetch the current Payments & Storefront, Feed (Pipes2), and CloudEvents documentation,
   plus the store-specific setup guides for the stores in scope. Read
   [payments-contract.md](references/payments-contract.md) alongside them and reconcile any
   drift toward the live docs.
2. Inspect the target project and determine, without asking, what the code reveals: its
   **stack** (language, web framework, validation library already in use, HTTP client,
   test/build commands), whether a **billing/receipt provider is already integrated** (and
   which, with its base URL/upstream endpoints and credential convention if already in
   config), the **authentication/identity system** the app uses and how it presents tokens to
   existing endpoints (Bearer / query / `ctx`), the config/secrets convention, the deployment
   URL shape, and any existing products/purchase/entitlement/receipt or content→product-id
   mapping code. Only ask for details the project cannot answer yet (e.g. a greenfield
   service with no framework chosen).
3. Read [provider-decision-guidance.md](references/provider-decision-guidance.md) and
   [validation-patterns.md](references/validation-patterns.md) for the portable
   implementation and validation shapes; realize them through the project's own tools.

Completion: the target stack, the receipt/billing provider, the stores and platforms in
scope, the endpoints to add or change, and the project's validation and test conventions
are evidenced by the code and live docs.

## 2. Resolve developer decisions

Present a concise, sourced fact summary, then ask only what facts cannot settle. Ask one
question at a time with the discovered constraint, a recommended answer, and the
consequence of choosing otherwise. If a question exposes a missing fact, investigate and
return with a recommendation rather than asking the developer to decide blind. Use
[provider-decision-guidance.md](references/provider-decision-guidance.md) for any provider,
store, SKU-mapping, or validation branch.

Ask first, because they decide how every endpoint is built:

1. **Which business model — single-tier or multi-tier?** This is the starting point: it
   decides how entitlements are checked and whether content is mapped to product ids.
   - **Single-tier** — one subscription (or one all-access pass). The app only needs to know
     *does this user have an entitlement at all?* Gate content with the "any entitlement"
     check (`GET .../entitlements`); no per-product mapping is required.
   - **Multi-tier** — several products/tiers unlocking different content (e.g. basic vs
     premium, per-league passes). Entitlement must be checked **per product id**
     (`GET .../entitlement/:productId`), and each piece of content must carry the product
     id(s) that unlock it. Recommend confirming how content→product-id mapping is expressed.
   Recommend starting from single-tier unless the catalogue already has distinct tiers.
2. **Which stores/platforms are in scope?** Ask explicitly and get the full set — each one
   in scope needs its SKU in every product's `extensions.external_sku` and its own store-side
   setup:
   - `apple_store` — Apple (iOS + tvOS)
   - `google_play` — Google (Android)
   - `amazon` — Amazon Fire TV
   - `roku` — Roku
   - `vizio` — Vizio

   If **Roku** is in scope, the **User Subscriptions** endpoint is mandatory for silent
   restore — confirm it is implemented.
3. **What authentication system issues the token, and how is it passed to each endpoint?**
   The protected endpoints (Products where gated, Transactions, Entitlements, User
   Subscriptions) are called with the user's `access_token`/`id_token` from the app's login.
   Establish which identity system issues it (the same OIDC/OAuth2 provider or custom auth
   the app already uses), then resolve **per endpoint** how it arrives — a `Authorization:
   Bearer` header, a query parameter, or a base64url `ctx` param (as in the Login Flow) — and
   which endpoints are public vs protected. This decides how each endpoint reads and verifies
   the caller, so ask it before implementing them.
4. **Is receipt validation on the Transactions endpoint synchronous or asynchronous?** Ask
   this explicitly — it changes the endpoint's response contract and how the app confirms a
   purchase.
   - **Synchronous** — the endpoint validates the receipt against the store/provider
     *within the request* and returns `201 Created` once it knows the result. The app has
     its answer on the response; simplest, recommend it when the provider can validate
     inline.
   - **Asynchronous** — the endpoint accepts the receipt and returns `202 Accepted` with a
     `Location` header **before** validation finishes; the app then polls that URL until the
     purchase is granted or denied (in deployed integrations the poll URL is usually the
     Entitlements endpoint for the product). Use only when validation is genuinely slow, and
     define the polling resource's states (pending/granted/denied).
   Recommend synchronous unless the provider forces async.

Then resolve, only where facts leave real ambiguity:

5. **Is the Products feed filtered per current customer?** Ask whether the storefront is the
   same for everyone or personalized — filtered by the caller's region, segment, existing
   entitlements, or A/B tab. If filtered, the Products endpoint reads the customer from the
   token and returns a customer-specific catalogue (and is therefore protected); recommend a
   single shared catalogue unless the customer needs per-user filtering.
6. **Is this backend fronting a third-party billing/receipt system, or implementing
   validation itself?** Ask explicitly — it decides whether you build a thin adapter or the
   full logic.
   - **Third-party system** (Cleeng, RevenueCat, a store-receipt validator, the customer's
     own billing service): get its **base URL and the specific upstream endpoints** it
     exposes — catalogue/products, receipt validation, entitlements, active
     purchases/subscriptions — plus the **credentials** (client id/secret, API key, store
     shared secrets) and confirm where they live in the project's config/secrets. Then map
     each upstream response into the contract shape (Products feed, `active-purchases` feed,
     `{ type: "entitlement", id }`). If those endpoints aren't known yet, that is a blocker —
     surface it rather than guessing.
   - **Direct** (validating store receipts against Apple/Google/Amazon/Roku yourself): confirm
     the per-store verification API and shared secrets instead.
   Either way, capture how a store receipt is validated for each store in scope.
7. Whether **restorable products** differ from current offerings (legacy products, tabbed
   storefronts). If they do, implement the separate Restorable Products feed; otherwise it
   falls back to the Products feed.
8. The **validation approach in this stack** — confirm the existing validator to use for
   both boundaries (feed shape and SKU presence out, CloudEvent envelope in).
9. Which endpoints are in scope (Products always; plus Transactions, Entitlements, User
   Subscriptions as needed). Enable the matching `payments-e2e` flags for whatever is in
   scope.

Completion: every non-discoverable choice affecting stores, auth, data, or behavior has a
recorded developer answer.

## 3. Confirm before edits

Present a short implementation contract:

- The business model (single- vs multi-tier) and, for multi-tier, the content→product-id
  mapping and per-product entitlement checks.
- The stores/platforms in scope and their SKU source, and sync vs async validation.
- The authentication system and, **per endpoint**, how the token is passed (Bearer / query /
  `ctx`) and which endpoints are public vs protected.
- Whether the Products feed is customer-filtered, and on what.
- Each endpoint in scope, its request validation, its response/error mapping, its
  validator, and the file it lives in.
- Whether a third-party billing/receipt system is fronted and, if so, its upstream endpoints
  and credentials; the receipt-validation path per store and where provider secrets/config
  come from.
- The exact `payments-e2e` command (URLs, token and how it is passed, which optional flags,
  and whether a real sample/sandbox receipt is available for the transactions happy-path), or
  the specific missing prerequisite (e.g. no sandbox receipt, provider credentials not yet
  issued).

Wait for explicit approval. If a material decision changes later, ask one recommended
question and refresh the contract.

## 4. Implement the contract

1. **Products** (`GET`): return a Pipes2 feed; validate on the way out that it is a valid
   feed and that every purchasable entry (`subscription`/`consumable`) carries
   `extensions.external_sku` with a SKU for every store in scope (Gate 2). Read the token the
   way the auth decision fixed (Bearer / query / `ctx`); if the feed is customer-filtered,
   resolve the customer from the token and apply the agreed filter.
2. **Transactions** (`POST`): validate the `com.applicaster.payment.validate` CloudEvent
   envelope and `data.transaction` **before** any work; return a sanitized `4xx` on a
   malformed envelope. Validate the store receipt via the provider/store API, then return
   `201` (sync) or `202` + a `Location` to poll (async). A failed validation is an error,
   not a silent success (Gate 3). Branch on the `data.transaction.type` flag
   (`"payment" | "restore"`, absent → `"payment"`): a `"restore"` restores/transfers an
   **existing** purchase to the authenticated user (e.g. Apple StoreKit2 transfer endpoint) —
   re-validate and re-entitle **idempotently**, never creating a duplicate transaction, and
   return the same `201`/`202`. Restore may be gated behind a provider config flag; with it
   off, a restore falls through to the normal purchase flow.
3. **Entitlements**: return `200` when the authenticated user is entitled, `401` when the
   request is unauthenticated or the token is invalid. For **multi-tier**, implement the
   per-product `GET .../entitlement/:productId` returning `{ type: "entitlement", id }`; for
   **single-tier**, the "any entitlement" `GET .../entitlements` check is enough. Read the
   token the way the auth decision fixed.
4. **User Subscriptions** (`GET .../user-purchases`): return a Pipes2 `active-purchases`
   feed of the user's current subscriptions with `started_at`/`expires_at`. Mandatory for
   Roku silent restore; validate the feed shape on the way out.
5. **Restorable Products** (`GET`, only if in scope): same shape and validation as Products.
6. Read provider credentials and endpoints from the project's existing config/secret
   convention. Add sanitized logging that never emits the receipt, token, or username. Make
   only the changes a compliant integration needs; report unrelated findings separately.

Completion: every in-scope endpoint validates both boundaries and returns the contract shape.

## 5. Verify and hand off

1. Run the project's formatter/typecheck and its own tests for the changed endpoints, in
   the project's stack.
2. Install and run the skill-local `payments-e2e` tool against a running server
   ([tool README](tools/payments-e2e/README.md)). It is black-box and stack-agnostic — it
   asserts the Products feed shape and per-store SKU presence, the Transactions
   malformed-envelope rejection (and, given a sandbox receipt, the `201`/`202`+`Location`
   happy path), the Entitlements `401`/`200` boundary, and the User Subscriptions feed
   shape. Attach the `--json-report`. If no server can be run, report the tool command as
   blocked rather than claiming end-to-end validation.
3. Include a `## Zapp Configuration` section: the URL for each endpoint to paste into the
   in-app-purchase / storefront plugin, and per store in scope the SKU / subscription-terms
   setup (App Store Connect, Google Play Console, Amazon, Roku Channel). Call out that the
   User Subscriptions endpoint is required for Roku silent restore, and point to the live
   Payments & Storefront guide
   (<https://docs.applicaster.com/integrations/payments/payments-and-storefront>).
4. Report files changed, verification commands and results, the `payments-e2e` result or
   blocker, required secrets/config, and any remaining provider or store-setup prerequisite.

Completion: both boundaries are validated in code, `payments-e2e` passed (or its blocker is
named), and the developer has the exact Zapp values to configure.
