@chipmobilesdk/rn-payment 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -112,7 +112,7 @@ Verified against this repository's toolchain, not assumed:
112
112
  | Node | >= 22.11.0 |
113
113
  | Android `minSdk` / `compileSdk` / `targetSdk` | 24 / 36 / 36 |
114
114
  | Kotlin | 2.1.20 |
115
- | `react-native-iap` | >= 16.6.1 < 17 |
115
+ | `react-native-iap` | >= 16.7.3 < 17 (verified 16.7.3) |
116
116
  | `react-native-mmkv` | >= 4.0.0 |
117
117
  | `react-native-nitro-modules` | >= 0.36.5 |
118
118
 
@@ -120,16 +120,18 @@ Verified against this repository's toolchain, not assumed:
120
120
  requirement and the bundled Play Billing version move on majors, so a major bump
121
121
  needs re-verification rather than silent adoption.
122
122
 
123
- > ⚠️ **The Android adapter is written but not yet verified on a device.** Its call
124
- > shapes follow `react-native-iap` 16.x as documented, and the surface it depends on
125
- > is declared explicitly in a local `IapModule` interface so a mismatch fails at
126
- > typecheck rather than at runtime on a user's device. Verify it on a device before
127
- > shipping.
123
+ > ⚠️ **Use `0.3.1` or later.** Up to `0.3.0` the Android adapter followed
124
+ > `react-native-iap` 16.x as documented, not as installed, and on a device **no
125
+ > purchase could complete**: the request used the pre-16.7 `android` key, which the
126
+ > binding rejects before Play opens. `0.3.1` was checked call by call against the
127
+ > installed 16.7.3 source, types its request with the binding's own
128
+ > `RequestPurchaseProps` (a renamed key now fails to typecheck), and is tested
129
+ > against a fake binding that behaves as 16.7.3 does. Still confirm one licence-tester
130
+ > purchase on a device before shipping.
128
131
  >
129
- > One part *has* been checked against an installed binding (16.7.3): how a purchase's
130
- > state is read. That check found a defect in `0.1.0` — the binding sends the state as
131
- > a string, the adapter expected a number, and a pending purchase was granted. Fixed
132
- > in `0.2.0`; see the changelog.
132
+ > Earlier, the same check of how a purchase's state is read found a defect in `0.1.0`
133
+ > — the binding sends the state as a string, the adapter expected a number, and a
134
+ > pending purchase was granted. Fixed in `0.2.0`; see the changelog.
133
135
 
134
136
  ---
135
137
 
@@ -572,7 +574,14 @@ outage. The queued transaction is re-validated on a later sync, and a rejection
572
574
  then revokes — which bounds the exposure this default accepts. Choose `'withhold'`
573
575
  if your threat model requires it.
574
576
 
575
- A hook that throws is treated as unavailable, never as an implicit approval.
577
+ A hook that throws is treated as unavailable, never as an implicit approval. So is
578
+ one that does not answer within `timeouts.validateMs` (default 15 s): the purchase
579
+ result and the purchase lock never wait on your backend indefinitely.
580
+
581
+ **Re-validation carries `transactionId`, not a payload.** Verification material is
582
+ never persisted, so a queued transaction is re-checked with `verificationPayload: ''`.
583
+ Every candidate carries `transactionId` (Play's order id) and `platform`, which is
584
+ what your backend looks the purchase up by on that second call.
576
585
 
577
586
  This package calls **no endpoint** other than the platform's billing service. All
578
587
  other traffic is your own code, through this hook.
package/package.json CHANGED
@@ -1,54 +1,54 @@
1
- {
2
- "name": "@chipmobilesdk/rn-payment",
3
- "version": "0.3.0",
4
- "private": false,
5
- "license": "UNLICENSED",
6
- "description": "Reusable in-app payment and entitlement capability for ChipMobileSdk React Native apps",
7
- "main": "src/index.ts",
8
- "types": "src/index.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./src/index.ts",
12
- "react-native": "./src/index.ts",
13
- "default": "./src/index.ts"
14
- },
15
- "./testing": {
16
- "types": "./src/testing/index.ts",
17
- "react-native": "./src/testing/index.ts",
18
- "default": "./src/testing/index.ts"
19
- }
20
- },
21
- "files": [
22
- "src/index.ts",
23
- "src/adapter",
24
- "src/config",
25
- "src/core",
26
- "src/diagnostics",
27
- "src/engine",
28
- "src/entitlements",
29
- "src/persistence",
30
- "src/react",
31
- "src/testing",
32
- "skills",
33
- "README.md",
34
- "CHANGELOG.md"
35
- ],
36
- "sideEffects": false,
37
- "scripts": {
38
- "typecheck": "tsc -p tsconfig.json --noEmit",
39
- "pack:dry-run": "npm pack --dry-run"
40
- },
41
- "peerDependencies": {
42
- "react": ">=19",
43
- "react-native": ">=0.85",
44
- "react-native-iap": ">=16.6.1 <17",
45
- "react-native-mmkv": ">=4.0.0",
46
- "react-native-nitro-modules": ">=0.36.5"
47
- },
48
- "keywords": [
49
- "react-native",
50
- "in-app-purchase",
51
- "subscriptions",
52
- "entitlements"
53
- ]
54
- }
1
+ {
2
+ "name": "@chipmobilesdk/rn-payment",
3
+ "version": "0.4.0",
4
+ "private": false,
5
+ "license": "UNLICENSED",
6
+ "description": "Reusable in-app payment and entitlement capability for ChipMobileSdk React Native apps",
7
+ "main": "src/index.ts",
8
+ "types": "src/index.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./src/index.ts",
12
+ "react-native": "./src/index.ts",
13
+ "default": "./src/index.ts"
14
+ },
15
+ "./testing": {
16
+ "types": "./src/testing/index.ts",
17
+ "react-native": "./src/testing/index.ts",
18
+ "default": "./src/testing/index.ts"
19
+ }
20
+ },
21
+ "files": [
22
+ "src/index.ts",
23
+ "src/adapter",
24
+ "src/config",
25
+ "src/core",
26
+ "src/diagnostics",
27
+ "src/engine",
28
+ "src/entitlements",
29
+ "src/persistence",
30
+ "src/react",
31
+ "src/testing",
32
+ "skills",
33
+ "README.md",
34
+ "CHANGELOG.md"
35
+ ],
36
+ "sideEffects": false,
37
+ "scripts": {
38
+ "typecheck": "tsc -p tsconfig.json --noEmit",
39
+ "pack:dry-run": "npm pack --dry-run"
40
+ },
41
+ "peerDependencies": {
42
+ "react": ">=19",
43
+ "react-native": ">=0.85",
44
+ "react-native-iap": ">=16.7.3 <17",
45
+ "react-native-mmkv": ">=4.0.0",
46
+ "react-native-nitro-modules": ">=0.36.5"
47
+ },
48
+ "keywords": [
49
+ "react-native",
50
+ "in-app-purchase",
51
+ "subscriptions",
52
+ "entitlements"
53
+ ]
54
+ }
@@ -1,168 +1,174 @@
1
- ---
2
- name: sdk-rn-payment
3
- description: "In-app payments via @chipmobilesdk/rn-payment: Play Billing one-time/consumable/subscription behind a store-neutral contract, entitlement keys with a required unknown state, pending purchases visible across sessions, guaranteed transaction finalization, per-environment production-transaction blocking. Use when selling a feature, gating on an entitlement, showing a payment still pending after a restart, debugging a purchase that returns environment_blocked or an entitlement stuck at unknown, mapping Play products to entitlement keys, or answering a Data safety question about purchases."
4
- metadata:
5
- package: "@chipmobilesdk/rn-payment"
6
- packageVersion: "0.3.0"
7
- user-invocable: true
8
- disable-model-invocation: false
9
- ---
10
-
11
- # @chipmobilesdk/rn-payment
12
-
13
- One-time, consumable and subscription purchases on Android via Google Play Billing,
14
- behind a contract that names no store. The app asks about **entitlement keys** it chose;
15
- the package owns the transaction. Consuming code never imports the billing library.
16
-
17
- ## Use this when
18
-
19
- - Selling a feature, or gating one on an entitlement
20
- - Showing "payment pending" — in this session or after a restart — and withholding Buy
21
- - Mapping Play product ids to entitlement keys
22
- - Debugging a purchase that returns `environment_blocked`, or an entitlement stuck at
23
- `unknown`
24
- - Wiring `no_ads` (or any entitlement) to another system
25
- - Answering a Data safety / Play policy question about purchases
26
-
27
- ## Do not use this for
28
-
29
- - **Paywall UI, pricing strategy, or when to offer** — the package ships no paywall, no
30
- price table and no purchase button, and chooses no moment.
31
- - **What an entitlement unlocks** — it reports `entitled`; the meaning is the app's.
32
- - **Cancellation UI** — platforms forbid a self-built one. Use
33
- `openSubscriptionManagement()`, which opens the store's.
34
- - **Receipt verification** — `validateTransaction` is a hook for *your* backend; the
35
- package calls no endpoint but the billing service.
36
- - iOS — contract only. `createStoreProvider()` returns an `unavailable` stub.
37
-
38
- ## `unknown` is a real state, and collapsing it is the defect this package exists to prevent
39
-
40
- `getEntitlement()` returns `unknown | entitled | not_entitled`. On a cold start the answer
41
- is genuinely not known yet, and the package says so rather than guessing. Treat `unknown`
42
- as "wait", never as "unpaid":
43
-
44
- ```tsx
45
- if (state.status === 'unknown') return <Loading />; // not the unpaid branch
46
- ```
47
-
48
- Collapse it and a paying user watches ads flash before they vanish, or sees a purchased
49
- feature locked for a second. It is also **re-entrant** — a cached answer returns to
50
- `unknown` past its staleness ceiling (7 days by default), so it is not merely a startup
51
- flicker. See `## The entitlement model`.
52
-
53
- ## Pending is observable across sessions — never keep your own marker
54
-
55
- New in 0.2.0. `usePendingPurchases(productKeys?)` (or `getPendingPurchases()` /
56
- `subscribePendingPurchases()`) lists `{ productKey, since }` for payments still in flight,
57
- read from the persisted snapshot, so it survives an app kill and an offline restart. Same
58
- rule as entitlements: check `status` first — `unknown` is not "nothing pending". Show the
59
- existing `payment.state.pending` message key and withhold Buy while listed.
60
- `purchase_pending_resolved` (`settled` → then `entitlement_granted`, or `cancelled` /
61
- `expired`) fires once when it ends. On Android an unpaid end is always `cancelled`: Play
62
- does not say why. See `### A payment that stays pending` under `## Purchase flow`.
63
-
64
- ## A debug build cannot buy anything, and that is the guard working
65
-
66
- `purchase()` returning `environment_blocked` in development is correct, not a bug. The
67
- rule: a chargeable purchase is permitted only when `environment: 'production'` **and**
68
- `isProductionBuild: true`. A non-production build declaring `production` throws
69
- `PAYMENT_PRODUCTION_CONFIG_IN_NON_PRODUCTION_BUILD` at construction and never connects.
70
-
71
- Consequence worth knowing before you plan device testing: **licence-tester purchases are
72
- real Play transactions**, so verifying them needs a release-configuration build
73
- (`isProductionBuild: !__DEV__`, `environment: 'production'`) installed from a Play track.
74
- Debug builds are structurally unable to reach the store. Drive every behaviour that is
75
- not "does Play accept this" through the fake store instead. See
76
- `## Environments and the production guard`.
77
-
78
- Unlike `@chipmobilesdk/rn-ads`, there is **no test-id substitution**: Play has no test
79
- *product* id, so blocking is the only honest option.
80
-
81
- ## Two more things not to "fix"
82
-
83
- - **Finalization is not yours to call** for non-consumables. Google auto-refunds an
84
- unacknowledged purchase after three days, so the package owns a persisted queue drained
85
- at startup and on foreground. A **consumable** is the exception: call `consume()` after
86
- *your* data records the item, because only you know it was banked.
87
- - **`renewalState: 'indeterminate'` on Android is honest.** Play returns a grace-period
88
- subscription as active and stops returning a held one, so the two are not separable
89
- client-side. The default policy (`grant` in grace, `withhold` on hold) is nonetheless
90
- correct with no backend, because Play's own behaviour matches it. A *non-default* policy
91
- needs server data through the validation hook.
92
-
93
- Call `handleForeground()` yourself on app resume — the package will not guess what
94
- "foreground" means for a host backgrounded by its own modal or the store's purchase sheet.
95
-
96
- ## Public API (v0.3.0)
97
-
98
- Runtime: `createEngine` (throws `PaymentConfigError` on a bad catalog or an unsafe
99
- environment; everything else returns a typed result) · `createStoreProvider` ·
100
- `PaymentProvider`, `usePaymentEngine` · `useEntitlement`, `useEntitlements`,
101
- `usePendingPurchases`, `usePaymentStatus`, `usePurchase`, `useProducts`, `useSync` ·
102
- `PaymentError`,
103
- `PaymentConfigError`, `PAYMENT_ERROR_CODES` · `DEFAULT_MESSAGES`,
104
- `DEFAULT_USER_MESSAGES`, `DEFAULT_DEVELOPER_MESSAGES`, `USER_MESSAGE_KEYS`,
105
- `DEVELOPER_MESSAGE_KEYS`, `isUserMessageKey` · timeout/retry/cache defaults and bounds
106
- (`DEFAULT_STALE_AFTER_MS`, `DEFAULT_PURCHASE_LAUNCH_TIMEOUT_MS`, `STALE_AFTER_BOUNDS`, …)
107
- · `createFakeStore`, `makePurchase` from `@chipmobilesdk/rn-payment/testing` (test
108
- doubles only).
109
-
110
- Engine: `initialize`, `cleanup`, `getStatus`, `subscribeStatus`, `getEntitlement`,
111
- `getAllEntitlements`, `subscribeEntitlements`, `getPendingPurchases`,
112
- `subscribePendingPurchases`, `purchase`, `consume`, `getUnfinalized`,
113
- `getProducts`, `refreshProducts`, `changePlan`, `openSubscriptionManagement`,
114
- `confirmPriceChange`, `sync`, `restore`, `getLastSyncedAt`, `clearLocalData`,
115
- `handleForeground`, `subscribeEvents`.
116
-
117
- Types: `PaymentConfig`, `CatalogEntry`, `ProductType`, `StoreProductIds`,
118
- `EntitlementKey`, `ProductKey`, `EntitlementState`, `EntitlementStatus`,
119
- `EntitlementMap`, `SubscriptionDetail`, `RenewalState`, `PendingPurchase`,
120
- `PendingPurchasesState`, `PendingResolution`, `PurchaseResult`,
121
- `PurchaseOptions`, `TransactionRef`, `FinalizeResult`, `UnfinalizedSummary`,
122
- `ProductInfo`, `Offer`, `ProductQueryResult`, `MissingProduct`, `ProrationMode`,
123
- `PriceChangeResult`, `PaymentStatus`, `PaymentStatusKind`, `UnavailableReason`,
124
- `Environment`, `LapsePolicy`, `ValidationHook`, `ValidationCandidate`,
125
- `ValidationDecision`, `ValidationUnavailablePolicy`, `SyncOutcome`, `RestoreOutcome`,
126
- `InitOutcome`, `PaymentEvent`, `PaymentErrorCode`, `LoggerAdapter`, `Unsubscribe`.
127
-
128
- `cleanup()` is idempotent, and so is every `Unsubscribe`. After cleanup no event fires.
129
-
130
- ## Upgrading 0.1.0 → 0.2.0
131
-
132
- One compile-time change: `PaymentEventType` gained `'purchase_pending_resolved'`, so an
133
- exhaustive `switch` needs the case or a `default`. A hand-written `PaymentEngine` mock
134
- needs the two pending methods. No storage migration (schema unchanged). Replace any
135
- app-side "pending since" marker with `usePendingPurchases`.
136
-
137
- **Require `0.2.1` or later.** Earlier versions opened MMKV with the v3 constructor, which
138
- react-native-mmkv v4 does not have, so on a device nothing persisted: owners read
139
- `unknown` after every cold start and pending payments vanished after a kill.
140
-
141
- **Upgrade before shipping on Android.** In `0.1.0` the adapter misread `react-native-iap`
142
- 16.x's string purchase state, so on a device a pending purchase was granted. `0.2.0`
143
- reads it correctly, and an unrecognized (`unspecified`) state never grants or finalizes.
144
-
145
- ## Where the detail lives
146
-
147
- Read one section of `node_modules/@chipmobilesdk/rn-payment/README.md`:
148
-
149
- | Need | Section |
150
- |---|---|
151
- | First integration | `## Install`, `## Quick start` |
152
- | Catalog and Play Console product setup | `## Catalog and entitlement mapping` |
153
- | Entitlement states and staleness | `## The entitlement model` |
154
- | Every purchase outcome and how to handle it | `## Purchase flow` |
155
- | A payment pending across sessions, and how it ends | `### A payment that stays pending` |
156
- | Why acknowledgement is not yours | `## Finalization: the invariant you cannot opt out of` |
157
- | Consumables | `## Consumables` |
158
- | Grace/hold, plan change, cancellation | `## Subscriptions` |
159
- | Prices, offers, trial eligibility | `## Prices and offers` |
160
- | Restore, offline, logout, account switch | `## Sync, restore, and offline` |
161
- | `environment_blocked`, release config | `## Environments and the production guard` |
162
- | Backend verification | `## Server-side validation` |
163
- | Events, ordering, error codes | `## Events and errors` |
164
- | Wiring an entitlement to ads | `## Wiring an entitlement to another system` |
165
- | Tests and licence testers | `## Testing` |
166
- | Data safety, permissions | `## What is stored on the device`, `## Play policy checklist` |
167
- | Before release | `## Production release checklist` |
168
- | What is deferred | `## Known limitations`, `## Adding iOS later` |
1
+ ---
2
+ name: sdk-rn-payment
3
+ description: "In-app payments via @chipmobilesdk/rn-payment: Play Billing one-time/consumable/subscription behind a store-neutral contract, entitlement keys with a required unknown state, pending purchases visible across sessions, guaranteed transaction finalization, per-environment production-transaction blocking. Use when selling a feature, gating on an entitlement, showing a payment still pending after a restart, debugging a purchase that returns environment_blocked or an entitlement stuck at unknown, mapping Play products to entitlement keys, or answering a Data safety question about purchases."
4
+ metadata:
5
+ package: "@chipmobilesdk/rn-payment"
6
+ packageVersion: "0.3.1"
7
+ user-invocable: true
8
+ disable-model-invocation: false
9
+ ---
10
+
11
+ # @chipmobilesdk/rn-payment
12
+
13
+ One-time, consumable and subscription purchases on Android via Google Play Billing,
14
+ behind a contract that names no store. The app asks about **entitlement keys** it chose;
15
+ the package owns the transaction. Consuming code never imports the billing library.
16
+
17
+ ## Use this when
18
+
19
+ - Selling a feature, or gating one on an entitlement
20
+ - Showing "payment pending" — in this session or after a restart — and withholding Buy
21
+ - Mapping Play product ids to entitlement keys
22
+ - Debugging a purchase that returns `environment_blocked`, or an entitlement stuck at
23
+ `unknown`
24
+ - Wiring `no_ads` (or any entitlement) to another system
25
+ - Answering a Data safety / Play policy question about purchases
26
+
27
+ ## Do not use this for
28
+
29
+ - **Paywall UI, pricing strategy, or when to offer** — the package ships no paywall, no
30
+ price table and no purchase button, and chooses no moment.
31
+ - **What an entitlement unlocks** — it reports `entitled`; the meaning is the app's.
32
+ - **Cancellation UI** — platforms forbid a self-built one. Use
33
+ `openSubscriptionManagement()`, which opens the store's.
34
+ - **Receipt verification** — `validateTransaction` is a hook for *your* backend; the
35
+ package calls no endpoint but the billing service.
36
+ - iOS — contract only. `createStoreProvider()` returns an `unavailable` stub.
37
+
38
+ ## `unknown` is a real state, and collapsing it is the defect this package exists to prevent
39
+
40
+ `getEntitlement()` returns `unknown | entitled | not_entitled`. On a cold start the answer
41
+ is genuinely not known yet, and the package says so rather than guessing. Treat `unknown`
42
+ as "wait", never as "unpaid":
43
+
44
+ ```tsx
45
+ if (state.status === 'unknown') return <Loading />; // not the unpaid branch
46
+ ```
47
+
48
+ Collapse it and a paying user watches ads flash before they vanish, or sees a purchased
49
+ feature locked for a second. It is also **re-entrant** — a cached answer returns to
50
+ `unknown` past its staleness ceiling (7 days by default), so it is not merely a startup
51
+ flicker. See `## The entitlement model`.
52
+
53
+ ## Pending is observable across sessions — never keep your own marker
54
+
55
+ New in 0.2.0. `usePendingPurchases(productKeys?)` (or `getPendingPurchases()` /
56
+ `subscribePendingPurchases()`) lists `{ productKey, since }` for payments still in flight,
57
+ read from the persisted snapshot, so it survives an app kill and an offline restart. Same
58
+ rule as entitlements: check `status` first — `unknown` is not "nothing pending". Show the
59
+ existing `payment.state.pending` message key and withhold Buy while listed.
60
+ `purchase_pending_resolved` (`settled` → then `entitlement_granted`, or `cancelled` /
61
+ `expired`) fires once when it ends. On Android an unpaid end is always `cancelled`: Play
62
+ does not say why. See `### A payment that stays pending` under `## Purchase flow`.
63
+
64
+ ## A debug build cannot buy anything, and that is the guard working
65
+
66
+ `purchase()` returning `environment_blocked` in development is correct, not a bug. The
67
+ rule: a chargeable purchase is permitted only when `environment: 'production'` **and**
68
+ `isProductionBuild: true`. A non-production build declaring `production` throws
69
+ `PAYMENT_PRODUCTION_CONFIG_IN_NON_PRODUCTION_BUILD` at construction and never connects.
70
+
71
+ Consequence worth knowing before you plan device testing: **licence-tester purchases are
72
+ real Play transactions**, so verifying them needs a release-configuration build
73
+ (`isProductionBuild: !__DEV__`, `environment: 'production'`) installed from a Play track.
74
+ Debug builds are structurally unable to reach the store. Drive every behaviour that is
75
+ not "does Play accept this" through the fake store instead. See
76
+ `## Environments and the production guard`.
77
+
78
+ Unlike `@chipmobilesdk/rn-ads`, there is **no test-id substitution**: Play has no test
79
+ *product* id, so blocking is the only honest option.
80
+
81
+ ## Two more things not to "fix"
82
+
83
+ - **Finalization is not yours to call** for non-consumables. Google auto-refunds an
84
+ unacknowledged purchase after three days, so the package owns a persisted queue drained
85
+ at startup and on foreground. A **consumable** is the exception: call `consume()` after
86
+ *your* data records the item, because only you know it was banked.
87
+ - **`renewalState: 'indeterminate'` on Android is honest.** Play returns a grace-period
88
+ subscription as active and stops returning a held one, so the two are not separable
89
+ client-side. The default policy (`grant` in grace, `withhold` on hold) is nonetheless
90
+ correct with no backend, because Play's own behaviour matches it. A *non-default* policy
91
+ needs server data through the validation hook.
92
+
93
+ Call `handleForeground()` yourself on app resume — the package will not guess what
94
+ "foreground" means for a host backgrounded by its own modal or the store's purchase sheet.
95
+
96
+ ## Public API (v0.4.0)
97
+
98
+ Runtime: `createEngine` (throws `PaymentConfigError` on a bad catalog or an unsafe
99
+ environment; everything else returns a typed result) · `createStoreProvider` ·
100
+ `PaymentProvider`, `usePaymentEngine` · `useEntitlement`, `useEntitlements`,
101
+ `usePendingPurchases`, `usePaymentStatus`, `usePurchase`, `useProducts`, `useSync` ·
102
+ `PaymentError`,
103
+ `PaymentConfigError`, `PAYMENT_ERROR_CODES` · `DEFAULT_MESSAGES`,
104
+ `DEFAULT_USER_MESSAGES`, `DEFAULT_DEVELOPER_MESSAGES`, `USER_MESSAGE_KEYS`,
105
+ `DEVELOPER_MESSAGE_KEYS`, `isUserMessageKey` · timeout/retry/cache defaults and bounds
106
+ (`DEFAULT_STALE_AFTER_MS`, `DEFAULT_PURCHASE_LAUNCH_TIMEOUT_MS`, `STALE_AFTER_BOUNDS`, …)
107
+ · `createFakeStore`, `makePurchase` from `@chipmobilesdk/rn-payment/testing` (test
108
+ doubles only).
109
+
110
+ Engine: `initialize`, `cleanup`, `getStatus`, `subscribeStatus`, `getEntitlement`,
111
+ `getAllEntitlements`, `subscribeEntitlements`, `getPendingPurchases`,
112
+ `subscribePendingPurchases`, `purchase`, `consume`, `getUnfinalized`,
113
+ `getProducts`, `refreshProducts`, `changePlan`, `openSubscriptionManagement`,
114
+ `confirmPriceChange`, `sync`, `restore`, `getLastSyncedAt`, `clearLocalData`,
115
+ `handleForeground`, `subscribeEvents`.
116
+
117
+ Types: `PaymentConfig`, `CatalogEntry`, `ProductType`, `StoreProductIds`,
118
+ `EntitlementKey`, `ProductKey`, `EntitlementState`, `EntitlementStatus`,
119
+ `EntitlementMap`, `SubscriptionDetail`, `RenewalState`, `PendingPurchase`,
120
+ `PendingPurchasesState`, `PendingResolution`, `PurchaseResult`,
121
+ `PurchaseOptions`, `TransactionRef`, `FinalizeResult`, `UnfinalizedSummary`,
122
+ `ProductInfo`, `Offer`, `ProductQueryResult`, `MissingProduct`, `ProrationMode`,
123
+ `PriceChangeResult`, `PaymentStatus`, `PaymentStatusKind`, `UnavailableReason`,
124
+ `Environment`, `LapsePolicy`, `ValidationHook`, `ValidationCandidate`,
125
+ `ValidationDecision`, `ValidationUnavailablePolicy`, `SyncOutcome`, `RestoreOutcome`,
126
+ `InitOutcome`, `PaymentEvent`, `PaymentErrorCode`, `LoggerAdapter`, `Unsubscribe`.
127
+
128
+ `cleanup()` is idempotent, and so is every `Unsubscribe`. After cleanup no event fires.
129
+
130
+ ## Upgrading 0.1.0 → 0.2.0
131
+
132
+ One compile-time change: `PaymentEventType` gained `'purchase_pending_resolved'`, so an
133
+ exhaustive `switch` needs the case or a `default`. A hand-written `PaymentEngine` mock
134
+ needs the two pending methods. No storage migration (schema unchanged). Replace any
135
+ app-side "pending since" marker with `usePendingPurchases`.
136
+
137
+ **Require `0.3.1` or later on Android.** Up to `0.3.0` no purchase could complete on a
138
+ device: the adapter sent `react-native-iap`'s pre-16.7 request key (`android`), rejected
139
+ before Play opens, and read the binding's always-empty resolved value instead of its
140
+ outcome events. `0.3.1` needs `react-native-iap` >= 16.7.3. If a purchase returns
141
+ `store_error` at once, check the installed binding against that range first.
142
+
143
+ **Require `0.2.1` or later.** Earlier versions opened MMKV with the v3 constructor, which
144
+ react-native-mmkv v4 does not have, so on a device nothing persisted: owners read
145
+ `unknown` after every cold start and pending payments vanished after a kill.
146
+
147
+ **Upgrade before shipping on Android.** In `0.1.0` the adapter misread `react-native-iap`
148
+ 16.x's string purchase state, so on a device a pending purchase was granted. `0.2.0`
149
+ reads it correctly, and an unrecognized (`unspecified`) state never grants or finalizes.
150
+
151
+ ## Where the detail lives
152
+
153
+ Read one section of `node_modules/@chipmobilesdk/rn-payment/README.md`:
154
+
155
+ | Need | Section |
156
+ |---|---|
157
+ | First integration | `## Install`, `## Quick start` |
158
+ | Catalog and Play Console product setup | `## Catalog and entitlement mapping` |
159
+ | Entitlement states and staleness | `## The entitlement model` |
160
+ | Every purchase outcome and how to handle it | `## Purchase flow` |
161
+ | A payment pending across sessions, and how it ends | `### A payment that stays pending` |
162
+ | Why acknowledgement is not yours | `## Finalization: the invariant you cannot opt out of` |
163
+ | Consumables | `## Consumables` |
164
+ | Grace/hold, plan change, cancellation | `## Subscriptions` |
165
+ | Prices, offers, trial eligibility | `## Prices and offers` |
166
+ | Restore, offline, logout, account switch | `## Sync, restore, and offline` |
167
+ | `environment_blocked`, release config | `## Environments and the production guard` |
168
+ | Backend verification | `## Server-side validation` |
169
+ | Events, ordering, error codes | `## Events and errors` |
170
+ | Wiring an entitlement to ads | `## Wiring an entitlement to another system` |
171
+ | Tests and licence testers | `## Testing` |
172
+ | Data safety, permissions | `## What is stored on the device`, `## Play policy checklist` |
173
+ | Before release | `## Production release checklist` |
174
+ | What is deferred | `## Known limitations`, `## Adding iOS later` |
@@ -33,6 +33,7 @@ const UNSUPPORTED_CAPABILITIES: ProviderCapabilities = {
33
33
  supportsExternalTransactions: false,
34
34
  /** Nothing here can charge anyone, so the environment guard has nothing to block. */
35
35
  createsRealTransactions: false,
36
+ purchaseOutcomeIncludesStoreTime: false,
36
37
  };
37
38
 
38
39
  /**