Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
- run: npm ci --ignore-scripts
- run: npm test
- run: npm run test:tooling
- name: Reproduce service composition from the source archive
run: bun composition/export.mjs "$RUNNER_TEMP/commerce-composition"
- name: Verify documentation matches the recorded source
run: bun export-docs.mjs "$RUNNER_TEMP/commerce-docs"
- run: npm run verify:checkpoints
22 changes: 22 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Example acceptance

Read `README.md` for the runnable scope, `INTEGRATE.md` for product boundaries,
and `BUILD.md` for the implementation milestones and completion checks.

Before finishing a change:

- Follow the running example as a first-time reader on desktop and mobile.
Explain the result and the reader's next decision before implementation
detail. Keep a short record of the tasks attempted and points of confusion.
- Compare the affected responsibility with the corresponding IAPKit handler
and test linked from the [purchase guide](https://openiap.dev/commerce-protocol/getting-started).
Report the actual differences; the installed protocol remains authoritative.
- Replay changed implementation instructions in a clean project, using only
the documented inputs. Run the startup command, tests, and affected demos.
Retain failed attempts and verify the repaired behavior independently.
- Identify the tested source revision, commands, results, and fixture scope.
Do not infer real-store support, full profile conformance, or interoperability
between providers from a local fixture run. Label an AI reader simulation as
a simulation, not a human user study.

Do not manufacture successful output or weaken acceptance to hide a failure.
45 changes: 41 additions & 4 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ Implement the backend in my project. Do not require an OpenIAP or IAPKit checkou
and do not invent request fields, response shapes, role rules, or enum values.
Follow my repository's instructions. Keep work uncommitted for review.

Read this example alongside [IAPKit's service source](https://github.com/hyodotdev/openiap/tree/main/packages/kit).
The [purchase walkthrough](https://openiap.dev/commerce-protocol/getting-started)
connects each step to both implementations and their checks. Use this example
to understand the small SQLite flow; use IAPKit to study real store adapters,
project authorization, account erasure, and the GraphQL adapter. Compare the
relevant code at each milestone without making either repository a runtime
dependency of the new project.

## Start with a reviewable local result

Use the stack already in my repository. If this is an empty project, choose a
Expand Down Expand Up @@ -66,26 +74,55 @@ Build these milestones in order:
events profile requires the public HTTPS destination protections in the spec.
6. **Recovery:** reopen the databases with pending deliveries, resume processing,
and prove that neither ownership nor receiver deduplication disappears.
7. **Account deletion:** remove provider identity and recipient copies, retry the
same erasure after restart, and reject stale account requests and late events.

After each milestone, run it. Show the command, actual API result, storage
change, and passing assertions. Capture the working screen. Do not manufacture
logs, screenshots, conformance counts, or claims about capabilities not tested.

## Complete the local implementation

Before calling the result complete, implement erasure for the account lifecycle:
remove the user identity from provider records and event history, preserve other
users, and prevent a late retry from restoring erased recipient data. Keep
provider erasure separate from the recipient's responsibility for delivered
copies. Exercise erasure during delivery, on repetition, and after restart.

Run the portable conformance runner for every selected profile and binding.
Do not finish with tests that expect known conformance failures. Keep every
previously exercised case in the completed run; changing declarations must not
hide a failure. Describe fixture-only capabilities explicitly, without implying
that a real store API or notification channel was connected.

Copy only source and package metadata into an empty directory. Install, test,
and start it there, without the development database or output directories.
Check the visible app after purchase, cancellation, expiry, reload, and deletion.

## Deliver

- A runnable local backend and small inspection UI.
- One command that verifies the demonstrated flow and exits nonzero on failure.
- A short visual walkthrough, with real captured results for each milestone.
- The exact scope and remaining work, including real store validation,
authentication, erasure, multi-tenant isolation, public HTTPS delivery,
operations, and full profile conformance. Keep the main explanation short;
real authentication, multi-tenant isolation, public HTTPS delivery, and
operations. Include the passing local conformance report and its fixture scope. Keep the main explanation short;
link the specification for details.

## Match the selected store

Read the store table in `INTEGRATE.md` before replacing the fixture. Exercise
all evidence shapes the provider advertises. Keep the app account distinct
from the Amazon/Meta store user, and reject a claim for someone else's store
account. Recheck ownership for Amazon/Horizon access; never invent subscription
or notification support to make their flow look like Apple/Google. Prove
negative rechecks, outages, conflicting bindings, erasure, and restart.

## Then extend toward a production provider

Ask me which real store, backend identity system, and deployment environment to
integrate before using credentials or external services. Implement the remaining
operations of each chosen profile, including erasure for `accountLifecycle`.
integrate before using credentials or external services. Replace fictional store and session adapters with the chosen integrations.
Keep the completed profile behavior and erasure checks passing.
Run the portable conformance runner for every advertised binding and profile;
also run real store sandbox, recovery, isolation, and load tests. Treat IAPKit as
an implementation example, never as a replacement for the protocol's contract.
Expand Down
57 changes: 52 additions & 5 deletions INTEGRATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Use your favorite package manager: `npm install`, `pnpm install`, `yarn install`
or `bun install`. This example's runtime is Bun. The contract is
`openiap-commerce-protocol` package 0.1.0, protocol 1.0; it does not require Bun.

- `npm run demo:bridge`: maps Apple/Google OpenIAP purchase fields into the
- `npm run demo:bridge`: maps Apple, Google, Amazon, and Horizon OpenIAP purchase fields into the
installed verification schema; rejects missing or unsupported evidence.
- `npm run demo:consumer`: sends signed lifecycle events to a SQLite inbox over
HTTP, repeats deliveries, rejects tampering, and reopens persisted storage.
Expand All @@ -36,6 +36,13 @@ gives the endpoint, configuration, and limits.

## Task for the AI

Use the [purchase walkthrough](https://openiap.dev/commerce-protocol/getting-started)
to compare this example with [IAPKit's service implementation](https://github.com/hyodotdev/openiap/tree/main/packages/kit)
at each step. This repository shows the small local implementation; IAPKit shows
store adapters, project credentials, erasure, and both API bindings. Follow the
linked handlers and checks for the responsibility you own. The installed
specification defines the required behavior; neither implementation changes it.

Inspect this repository's purchase flow and choose the role from the table.
Install `openiap-commerce-protocol` with this repository's package manager.
Read its `SPEC.md`, generated bindings and schemas, and signature/lifecycle
Expand All @@ -55,7 +62,7 @@ for the app team. Follow these boundaries:
UI, targeting, or product catalog API; document this host adapter explicitly.
2. **App connection:** the app uses its OpenIAP library to fetch products and
request a store purchase. Its purchase callback sends evidence to its
authenticated backend. Use `client-bridge.mjs` there to map Apple/Google
authenticated backend. Use `client-bridge.mjs` there to map Apple, Google, Amazon, and Horizon
purchase fields into a verification input; this does not authenticate the
evidence. Keep server keys and user selection on that backend. Verify, bind
under the ownership policy, read current access, fulfill durably, then finish
Expand All @@ -76,9 +83,49 @@ for the app team. Follow these boundaries:

The current client `verifyPurchaseWithProvider` helper supports IAPKit's own
API. A different provider name or base URL does not turn it into this protocol.
Other providers connect through the app backend's REST or GraphQL calls. The
Apple/Google helper does not support Amazon or Horizon, whose protocol evidence
requires store-specific user identifiers distinct from the app's user ID.
Other providers connect through the app backend's REST or GraphQL calls. Amazon and Horizon require a store-specific user identifier distinct from the
app user ID. Pass it as `context.storeUserId` to the bridge after authenticating
the store account link. `startAppBackend` requires `resolveStoreUser` for these
stores and rejects evidence belonging to a different store account. Never
implement that callback by copying a user ID from the request body.

## Select the store before implementing

Follow the six-step purchase flow with your chosen store. Verification and
binding use these evidence shapes:

| Store | Purchase evidence | IAPKit access path |
| --- | --- | --- |
| Apple | `apple.jws` from the store purchase | Bind the verified subscription; read its current state and listen for lifecycle events |
| Google | `google.purchaseToken` | Bind the verified subscription; read its current state and listen for lifecycle events |
| Amazon | `amazon.userId`, `amazon.receiptId`, optional `amazon.sandbox` | Bind the verified receipt; each entitlement read rechecks RVS |
| Meta Horizon | `horizon.userId`, `horizon.sku` | Bind the verified store-user/SKU pair; each entitlement read rechecks Meta |

For Amazon and Horizon, use `entitlements.productIds` for access. IAPKit does
not invent a subscription record, expiry date, or lifecycle event for these
ownership checks. An empty `subscriptions` list can accompany owned products.
A negative store answer removes the product; a failed store call fails the
read. Decide caching and outage policy in the app backend. Reads currently
fail if an account has more than 20 linked Amazon/Horizon purchase rows.

For Quest, verify Meta's user proof on your authenticated backend before linking
that Meta user to the app account. Follow the official
[Meta user verification guide](https://developers.meta.com/horizon/documentation/android-apps/ps-ownership/).
For Amazon, establish the store account association through your application's
trusted sign-in and ownership policy. Receipt possession alone does not prove
which app account may claim it. The runnable comparison uses explicit fictional
session links; it does not implement your authentication provider.

Keep consumable fulfillment separate: record each granted unit durably and
idempotently before finishing/consuming. A verified SKU is not a new quantity
to credit on every read. The protocol walkthrough demonstrates Premium access;
it does not implement a wallet or sell a Nami paywall.

For IAPKit setup, configure Apple bundle/App ID and Server API signing key,
Google package and service account, Meta App ID/secret, or Amazon RVS shared
secret in the project. Keep secrets on the server. Enable Amazon sandbox only
for App Tester evidence. The local comparison requires none of these real
credentials; its external store responses are fixtures.

## Deliver and prove the connection

Expand Down
36 changes: 31 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# OpenIAP Commerce Protocol example

A runnable purchase-to-access backend, built and reviewed with AI in six
A runnable purchase-to-access backend, built and reviewed with AI in seven
milestones. Follow a purchase through verification, ownership, access, and
signed event delivery. Inspect the actual HTTP responses and database changes.
signed event delivery, and account deletion. Inspect the actual HTTP responses and database changes.

The backend uses the published **`openiap-commerce-protocol`** package. HTTP,
SQLite, and webhook signatures run locally; the store, users, and clock are
Expand All @@ -23,7 +23,7 @@ npm start
```

Open **http://127.0.0.1:5181**, then click **Run step 1 →** and continue through
step 6. Each step changes real local state. The dashboard shows purchases,
step 7. Each step changes real local state. The dashboard shows purchases,
current access, delivery attempts, and expandable request/response details.
No store account, API key, OpenIAP checkout, or IAPKit account is required.
Modern Yarn uses the included `node_modules` linker.
Expand All @@ -44,6 +44,7 @@ and the [complete build history](https://github.com/hyodotdev/openiap-commerce-p
| 4. Cancel | Turn off renewal; queue an event | Paid access remains until expiry |
| 5. Deliver | Sign events; retry a failed receiver | A repeated delivery has one inbox effect |
| 6. Expire | Advance the clock; reopen SQLite | Access closes; ownership and delivery records remain |
| 7. Erase | Remove provider identity and receiver copies | Repeated deletion and late deliveries cannot restore the user |

Restarting `npm start` creates a fresh temporary database, so you can replay the
walkthrough. Step 6 reopens the existing databases **inside the running process**;
Expand All @@ -59,8 +60,10 @@ If port 5181 is occupied, run `COMMERCE_LAB_PORT=5183 npm start`.
| Data / automation | [Event receiver guide](https://github.com/hyodotdev/openiap-commerce-protocol-example/blob/main/docs/receiver.md) | A ready signed-event receiver with a durable inbox |
| Integrated platform | [Integration brief](INTEGRATE.md) | How the roles compose without splitting account authority |

`client-bridge.mjs` maps Apple/Google OpenIAP purchase fields into the installed
`client-bridge.mjs` maps Apple, Google, Amazon, and Horizon OpenIAP purchase fields into the installed
verification schema **on the app backend**. Run `npm run demo:bridge` to check it.
`npm test` also exercises all four fixture shapes through verification, binding,
access and erasure. Amazon/Horizon cases include negative rechecks and outages.
It does not perform a mobile purchase or authenticate store evidence. The
current client `verifyPurchaseWithProvider` helper uses IAPKit's own API; other
providers connect through the app's authenticated backend.
Expand Down Expand Up @@ -109,9 +112,32 @@ and Google Chrome. The [recording guide](https://github.com/hyodotdev/openiap-co
explains how to preserve a checkpoint and export evidence. GitHub CI runs the
runtime, tooling, archive, and documentation-export checks.

## Account deletion

Step 7 runs `eraseUser` using server credentials. The provider removes identity
from purchases and removes identity-bearing event records in one transaction.
A repeated request returns the same completed job, including after restart.

The app owns already-delivered copies: it erases its receiver inbox and retains
a keyed deletion marker so late signed events are acknowledged without storing
the deleted identity. The example also refuses rebinding erased evidence.
This is a local ownership policy; the protocol does not cancel the store subscription.
Database backups and the app’s own account records remain the operator’s responsibility.

Run `bun verify-erasure.mjs` to exercise deletion while a delivery is in flight,
late lifecycle events, repeated requests, and storage reopening.

## Replace the example with IAPKit

The OpenIAP checkout includes `packages/kit/scripts/docs/run-commerce-interop.mjs`.
It starts IAPKit with an isolated local Convex deployment and keeps one app
backend and receiver running while switching the commerce provider. See the
[composition guide](https://openiap.dev/commerce-protocol/ecosystem#composition-proof)
for the executed report, source, and command. No IAPKit account or store keys are needed.

## What remains for production

Real store validation and sandbox purchases, login, user erasure, tenant
Real store validation and sandbox purchases, login, tenant
isolation, GraphQL, public HTTPS delivery protections, and operational recovery
are not implemented here. The backend advertises **no complete profiles**.
Schema checks and the local walkthrough do not establish profile conformance.
Expand Down
Loading