Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f0703f7
feat: complete commerce implementation guide
hyochan Sep 8, 2026
b847aad
docs(commerce): re-record the IAPKit replacement evidence on the reba…
hyochan Sep 9, 2026
52eced3
fix(commerce): address the review-self findings on the guide and IAPK…
hyochan Sep 9, 2026
3bf9168
fix(commerce): decide the bound-purchase cap, recheck budget, erasure…
hyochan Sep 9, 2026
df9f097
docs(commerce): re-record the IAPKit evidence on the committed sources
hyochan Sep 9, 2026
acdfd15
fix(commerce): decide the bind cap from the caller's own rows
hyochan Sep 9, 2026
feb70fc
docs(commerce): re-record the IAPKit evidence on the committed sources
hyochan Sep 9, 2026
bd2185d
fix(apple): build the example against the current purchase types (#446)
hyochan Sep 9, 2026
d03ad86
docs(e2e): record the device state that silently breaks a live row (#…
hyochan Sep 9, 2026
05f4eea
fix(commerce): keep the entitlements read inside its declared error set
hyochan Sep 9, 2026
67b0516
docs(commerce): re-record the IAPKit evidence on the corrected read
hyochan Sep 9, 2026
c0d2d66
fix(commerce): stop dead purchases and erasure markers from stranding…
hyochan Sep 9, 2026
964b3c5
fix(commerce): reclaim a dead binding at the cap instead of on every …
hyochan Sep 9, 2026
8ca8eb8
docs(commerce): re-record the IAPKit evidence on the reclaimed bind cap
hyochan Sep 9, 2026
c4b0b82
ci: stop Playwright's dependency install from needing Google's apt repo
hyochan Sep 9, 2026
27b8231
fix(commerce): answer an unclassifiable bound purchase instead of ski…
hyochan Sep 9, 2026
3f0dfae
docs(commerce): re-record the IAPKit evidence after the review follow…
hyochan Sep 9, 2026
4e8163c
docs(commerce): stop the recheck comment from naming a remedy that do…
hyochan Sep 9, 2026
8ac75fc
docs(commerce): re-record the IAPKit evidence on the corrected comment
hyochan Sep 9, 2026
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
6 changes: 6 additions & 0 deletions .claude/commands/e2e-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ Run this row as part of every full E2E regression. When the request is narrowed
to IAPKit, run this row plus the focused package/example checks that support it;
do not rerun unrelated framework/store rows.

Read the "Device and Workspace State That Silently Breaks a Row" section of
`.codex/skills/iapkit-e2e-martie/SKILL.md` first. A leftover store flavor in the
generated Android project, a leftover iOS scene session from another app sharing
the bundle id, and prebuilt React Native each break a row in a way that looks
like a store or account failure.

Prerequisites:

- A connected iPhone or Google Play-capable Android phone with a sandbox/tester
Expand Down
4 changes: 4 additions & 0 deletions .claude/commands/verify-all.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ set -euo pipefail
# Docs formatting, typecheck, and production bundle
(cd packages/docs && bun run format:check && bun run build)

# Advisory: recorded IAPKit interop evidence versus the current sources
bun test ./scripts/audit-commerce-evidence.test.mjs
bun run audit:commerce-evidence || echo "commerce evidence differs from current sources; re-record before deploying docs"

# Swift build and unit tests (packages/apple)
(cd packages/apple && swift test)

Expand Down
55 changes: 55 additions & 0 deletions .codex/skills/iapkit-e2e-martie/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,61 @@ inspect the generated key value. After building, install with
`VEGA_DEVICE_ID="$VEGA_DEVICE_ID" bun run run:vega:firetv`, then require the
same local-server and purchases-view evidence as the other live lanes.

## Device and Workspace State That Silently Breaks a Row

Every item below has cost a full debugging session. Check them before
concluding that a store, an account, or the code is at fault.

**The Android project keeps the last store it was prebuilt for.** The FireOS
and Horizon rows run `expo prebuild --platform android --clean` with
`EXPO_IAP_FIREOS=1` or `EXPO_IAP_HORIZON=1`, and the generated `android/`
directory keeps that store afterwards. A later Play run then links the wrong
`openiap-google` flavor, so the example sits on `Connecting to Store...` with
`initConnection failed: Failed to initialize connection` and
`getStorefront failed: Billing client not ready`. Re-run
`bunx expo prebuild --platform android --clean` with no store variable before
the Play row, then confirm `horizonEnabled=false` and `fireOsEnabled=false` in
`android/gradle.properties` and `missingDimensionStrategy "platform", "play"`
in `android/app/build.gradle`.

**A Play "not compatible with your device" banner does not block billing.** The
Martie production listing sets `minSdkVersion 31`, so Play marks an Android 11
device incompatible and refuses to install that artifact. The examples this
repository builds declare a lower minimum and install fine: `packages/google`
Example inherits `minSdk = 23` from the library, and the Expo example ships 24.
Both use the `dev.hyo.martie` application id, so a license tester buys and
verifies through them normally despite the banner. Confirm with the
`packages/google` Example, whose subscription screen enables `OpenIapLog` and
prints the real `BillingResult`, before blaming the store.

**iOS keeps a scene session per bundle id.** Any other app built with
`dev.hyo.martie` — the SwiftUI `packages/apple/Example`, or the Godot and
Flutter Martie examples — leaves a scene session behind. Installing the Expo
example over it restores that session, so UIKit attaches the previous app's
scene delegate and the example's own `SceneDelegate`, which is what starts
React Native, never runs. The process stays alive, the screen is black, Metro
receives no bundle request, and nothing crashes. Run
`xcrun devicectl device uninstall app --device "$IOS_UDID" dev.hyo.martie`
before installing; an upgrade install does not clear it.

**Prebuilt React Native has no packager support.** Expo links React Native as a
prebuilt binary by default, and that slice compiles without `DEBUG`, so
`RCTBundleURLProvider` returns no bundle URL and a Debug build never contacts
Metro whatever host `ip.txt` holds. Set `"ios.buildReactNativeFromSource"` to
`"true"` and `"EXPO_USE_PRECOMPILED_MODULES"` to `"false"` in
`ios/Podfile.properties.json`, then run `pod install` and rebuild.

**Reinstalling resets the iOS local-network permission.** Allow it again when
the prompt appears, otherwise both Metro and the local server are unreachable.

**The local origin differs per platform and is baked in at bundle time.**
Android reaches the server through an `adb reverse` rule on `127.0.0.1`; iOS
needs the Mac's LAN address. `EXPO_PUBLIC_*` values are inlined when Metro
starts, so restart Metro and relaunch the app after editing the environment
file. A stale value sends verification to the hosted service instead, which
surfaces as `Unable to parse verification response` while the local server log
stays empty.

## Martie Catalog

- `dev.hyo.martie.10bulbs`: consumable; preferred repeatable receipt fixture
Expand Down
11 changes: 7 additions & 4 deletions .codex/skills/ship-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,17 @@ still return to the normal PR loop.

From a clean local `main` equal to `origin/main`:

1. Run `npm run deploy` and wait for successful production completion.
2. Fetch the production release page and generated LLM documents with a cache
1. Run `bun run audit:commerce-evidence`. If it reports drift, re-record the
IAPKit interop per `packages/kit/scripts/docs/commerce-interop.md` before
deploying; the guide page shows the recorded revision either way.
2. Run `npm run deploy` and wait for successful production completion.
3. Fetch the production release page and generated LLM documents with a cache
buster. Confirm the new release title, API name, versions, and generated
timestamp are present.
3. If the OpenIAP Spec advanced, dispatch the docs release workflow with the
4. If the OpenIAP Spec advanced, dispatch the docs release workflow with the
current version and verify the resulting `docs-{spec}` GitHub Release points
to the deployed commit.
4. Recheck required CI for the final `main` head and report any still-pending
5. Recheck required CI for the final `main` head and report any still-pending
external listing or registry state separately.

## 6. Leave the shipped comment
Expand Down
54 changes: 54 additions & 0 deletions .github/ISSUE_TEMPLATE/commerce_protocol.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Commerce Protocol proposal
description: Propose shared behavior or contribute implementation interoperability evidence.
title: "[Commerce Protocol]: "
body:
- type: markdown
attributes:
value: |
Start with a concrete connection between products. See the [contribution procedure](https://github.com/hyodotdev/openiap/blob/main/specs/commerce-protocol/CONVENTION.md#public-collaboration-and-implementation-evidence).
IAPKit and other implementations are reviewed against the same contract. Submitting a report is not a conformance or endorsement claim.
- type: dropdown
id: contribution
attributes:
label: Contribution
options:
- Interoperability reproduction
- Contract extension
- Compatibility problem
validations:
required: true
- type: textarea
id: outcome
attributes:
label: Use case and expected outcome
description: Which services or roles need to connect, and what should the user observe?
validations:
required: true
- type: textarea
id: implementations
attributes:
label: Implementations and reviewers
description: List source revisions, roles, stores, bindings, declared profiles, and relevant affiliations. Distinguish project-authored fixtures from an external implementation or review.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Runnable evidence
description: Provide commands, source/report links, expected results, and a failure or rejection case. List configuration fields and adapter/client changes; omit credentials and customer data.
validations:
required: true
- type: textarea
id: compatibility
attributes:
label: Compatibility and alternatives
description: Can existing profiles or extensions express this? For a contract change, identify MAJOR/MINOR impact and migration work. For a reproduction, state whether the client and receiver code changed.
validations:
required: true
- type: textarea
id: limits
attributes:
label: Limits and unresolved questions
description: State what remains untested, who has reproduced the result, and any disagreement about expected behavior.
validations:
required: true
23 changes: 22 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ jobs:
# time, so a spec change must rebuild and probe the binary.
- 'specs/commerce-protocol/**'
- 'scripts/e2e-web-sites.mjs'
- 'scripts/audit-commerce-evidence.mjs'
- 'scripts/audit-commerce-evidence.test.mjs'
- 'package.json'
- 'bun.lock'
- '.github/workflows/ci.yml'
Expand Down Expand Up @@ -592,6 +594,10 @@ jobs:
bun test scripts/audit-docs.test.ts
bun run audit:docs

- name: Check static discovery guards
working-directory: packages/docs
run: bun run test:discoverability

- name: Lint
working-directory: packages/docs
run: bun run lint
Expand Down Expand Up @@ -631,7 +637,13 @@ jobs:
done

- name: Install Playwright chromium
run: bunx playwright install --with-deps chromium
# `--with-deps` runs apt, which fails whenever Google's Chrome
# repository serves a stale index. Playwright downloads its own
# chromium, so drop that source first: nothing here needs it.
run: |
sudo rm -f /etc/apt/sources.list.d/google-chrome.list \
/etc/apt/sources.list.d/google-chrome.sources
bunx playwright install --with-deps chromium

- name: Build docs site
working-directory: packages/docs
Expand All @@ -643,6 +655,15 @@ jobs:
VITE_KIT_CONVEX_URL: https://placeholder-build-1.convex.cloud
run: bun run build:all

- name: Test the Commerce evidence audit
run: bun test ./scripts/audit-commerce-evidence.test.mjs

- name: Report Commerce evidence freshness
# Advisory: the recorded IAPKit interop stays valid for its recorded
# revision; drift only means the guide shows older sources than main.
continue-on-error: true
run: bun run audit:commerce-evidence

- name: Run docs and IAPKit web E2E
env:
WEB_E2E_DOCS_BASE_URL: http://127.0.0.1:4173
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/deploy-kit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,13 @@ jobs:
- name: Install Playwright chromium
# smoke-browser.ts loads the SPA in headless Chromium to catch
# runtime bundle crashes that HTTP probes miss (see PR #120).
run: bunx playwright install --with-deps chromium
# `--with-deps` runs apt, which fails whenever Google's Chrome
# repository serves a stale index. Playwright downloads its own
# chromium, so drop that source first: nothing here needs it.
run: |
sudo rm -f /etc/apt/sources.list.d/google-chrome.list \
/etc/apt/sources.list.d/google-chrome.sources
bunx playwright install --with-deps chromium

- name: Smoke test compiled server
run: ./scripts/smoke-server.sh
Expand Down
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ second type-copy command or maintain another target list.

### Changing the Commerce Protocol

Start shared behavior proposals with the **Commerce Protocol proposal** issue
form. The [protocol contribution procedure](specs/commerce-protocol/CONVENTION.md#public-collaboration-and-implementation-evidence)
defines the evidence and compatibility review. You can also contribute an
independent reproduction using the [service composition example](https://openiap.dev/commerce-protocol/ecosystem#composition-proof)
without proposing a contract change.

1. Edit `specs/commerce-protocol/SPEC.md` and the owning GraphQL layer
under `schema/`.
2. Run `cd specs/commerce-protocol && bun run build` to regenerate the
Expand Down
43 changes: 42 additions & 1 deletion knowledge/_agent-context/context.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# OpenIAP Project Context

> **Auto-generated shared context for AI assistants**
> Last updated: 2026-09-07T14:34:17.286Z
> Last updated: 2026-09-09T00:37:16.148Z
>
> Canonical file: `knowledge/_agent-context/context.md`

Expand Down Expand Up @@ -1689,6 +1689,47 @@ Before finishing, read the rendered page as a user. Remove any sentence that
does not clarify what changed, how to use it, who is affected, or what action is
required.

## Human and AI Acceptance

Apply these checks whenever changing a guide, example, SDK entry point, or AI
implementation brief. They are completion criteria, not an optional final polish.

- **First-time reader:** walk through the rendered page on desktop and mobile.
The reader should understand what they get, which decisions they own, and what
to do next before seeing an API table or a long AI prompt. Introduce terms at
the step that needs them; keep detailed references available afterward.
- **Two implementations:** Commerce Protocol guides must connect each relevant
responsibility to the runnable example and to at least one independent
implementation's code and checks (today `openiap-commerce-protocol-example`
and IAPKit). Explain differences in supported operations, stores, and
profiles. Do not present a fixture as a real purchase, one provider as
evidence of interoperability with another, or any single implementation as
the protocol. Follow the source links and operate the guide.
- **Fresh AI implementation:** when a brief or runnable example changes, replay
its install, implementation, startup, and acceptance instructions in a clean
project using only the published inputs. Record the input revision, commands,
observed results, failures, and remaining limits. Retain previously exercised
cases; do not hide failures by shrinking declarations or accepting known
conformance failures. Reuse an earlier run only when its relevant inputs have
not changed, and identify that run rather than calling it a new reproduction.
- **Independent acceptance:** test the promised customer behavior, including
failure and recovery, against the running result, and record the commands
and observed results in the PR body or the published run report. An agent
simulation is useful evidence, but must be labeled as a simulation, not a
human user study.
- **SDK discovery:** verify the initial HTTP response contains the page's actual
text, title, description, and canonical URL without JavaScript. Canonical
pages belong in the generated sitemap. Framework names, install commands,
versions, and setup links come from their existing metadata sources. Generated
`llms.txt` references must lead to the same current contracts and examples.

Run `bun run build` and `bun run test:discoverability` in `packages/docs` for
static content and metadata checks. The repository's `bun run e2e:web`
checks the served HTML and the interactive Commerce Protocol walkthrough.
These checks catch regressions; they do not prove that a person understood the
page or that an AI chose the SDK, and `llms.txt` or structured data do not
guarantee indexing or recommendation. Do not claim adoption effects from them.

## Modal Pattern with Preact Signals

### Global Modal Management
Expand Down
41 changes: 41 additions & 0 deletions knowledge/internal/05-docs-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,47 @@ Before finishing, read the rendered page as a user. Remove any sentence that
does not clarify what changed, how to use it, who is affected, or what action is
required.

## Human and AI Acceptance

Apply these checks whenever changing a guide, example, SDK entry point, or AI
implementation brief. They are completion criteria, not an optional final polish.

- **First-time reader:** walk through the rendered page on desktop and mobile.
The reader should understand what they get, which decisions they own, and what
to do next before seeing an API table or a long AI prompt. Introduce terms at
the step that needs them; keep detailed references available afterward.
- **Two implementations:** Commerce Protocol guides must connect each relevant
responsibility to the runnable example and to at least one independent
implementation's code and checks (today `openiap-commerce-protocol-example`
and IAPKit). Explain differences in supported operations, stores, and
profiles. Do not present a fixture as a real purchase, one provider as
evidence of interoperability with another, or any single implementation as
the protocol. Follow the source links and operate the guide.
- **Fresh AI implementation:** when a brief or runnable example changes, replay
its install, implementation, startup, and acceptance instructions in a clean
project using only the published inputs. Record the input revision, commands,
observed results, failures, and remaining limits. Retain previously exercised
cases; do not hide failures by shrinking declarations or accepting known
conformance failures. Reuse an earlier run only when its relevant inputs have
not changed, and identify that run rather than calling it a new reproduction.
- **Independent acceptance:** test the promised customer behavior, including
failure and recovery, against the running result, and record the commands
and observed results in the PR body or the published run report. An agent
simulation is useful evidence, but must be labeled as a simulation, not a
human user study.
- **SDK discovery:** verify the initial HTTP response contains the page's actual
text, title, description, and canonical URL without JavaScript. Canonical
pages belong in the generated sitemap. Framework names, install commands,
versions, and setup links come from their existing metadata sources. Generated
`llms.txt` references must lead to the same current contracts and examples.

Run `bun run build` and `bun run test:discoverability` in `packages/docs` for
static content and metadata checks. The repository's `bun run e2e:web`
checks the served HTML and the interactive Commerce Protocol walkthrough.
These checks catch regressions; they do not prove that a person understood the
page or that an AI chose the SDK, and `llms.txt` or structured data do not
guarantee indexing or recommendation. Do not claim adoption effects from them.

## Modal Pattern with Preact Signals

### Global Modal Management
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"audit:parity": "node scripts/audit-non-godot-parity.mjs",
"audit:kit-contract": "node --test scripts/audit-kit-spec-contract.test.mjs && node scripts/audit-kit-spec-contract.mjs",
"audit:docs": "bun run scripts/audit-docs.ts",
"audit:commerce-evidence": "bun scripts/audit-commerce-evidence.mjs",
"audit:schema-semver": "bun run --cwd specs/client audit:schema-semver",
"audit:research": "node --test scripts/audit-research.test.mjs && node scripts/audit-research.mjs",
"audit:whitepaper": "node --test scripts/audit-whitepaper.test.mjs && node scripts/audit-whitepaper.mjs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ struct AllProductsView: View {
return "auto-renewable"
case .nonRenewingSubscription:
return "non-renewing"
case .subscriptionBundle:
return "subscription-bundle"
case .subscriptionSuite:
return "subscription-suite"
}
}

Expand All @@ -306,6 +310,10 @@ struct AllProductsView: View {
return .blue
case .nonRenewingSubscription:
return .indigo
case .subscriptionBundle:
return .teal
case .subscriptionSuite:
return .mint
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ struct SubscriptionFlowScreen: View {
print(" 📋 Purchase Details:")
print(" • Transaction ID: \(purchase.id)")
print(" • Product ID: \(purchase.productId)")
print(" • Platform: \(purchase.platform)")
print(" • Store: \(purchase.store)")
print(" • Purchase State: \(purchase.purchaseState)")
print(" • Is Auto-Renewing: \(purchase.isAutoRenewing)")

Expand Down
Loading
Loading