@chipmobilesdk/rn-payment 0.4.1 → 0.6.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/CHANGELOG.md +202 -0
- package/README.md +70 -16
- package/package.json +5 -2
- package/skills/sdk-rn-payment/SKILL.md +8 -6
- package/src/adapter/iap-adapter.ts +44 -251
- package/src/adapter/iapShapes.ts +232 -0
- package/src/config/types.ts +8 -0
- package/src/config/validate.ts +19 -4
- package/src/core/mutex.ts +9 -110
- package/src/core/retry.ts +9 -104
- package/src/core/timers.ts +40 -18
- package/src/diagnostics/errors.ts +20 -13
- package/src/diagnostics/events.ts +51 -32
- package/src/diagnostics/messageKeys.ts +15 -9
- package/src/engine/connection.ts +194 -0
- package/src/engine/engine.ts +142 -733
- package/src/engine/foreground.ts +29 -0
- package/src/engine/internals.ts +40 -0
- package/src/engine/products.ts +39 -17
- package/src/engine/purchase.ts +22 -130
- package/src/engine/purchaseFailure.ts +48 -0
- package/src/engine/receiptValidation.ts +101 -0
- package/src/engine/state.ts +253 -0
- package/src/engine/sync.ts +161 -0
- package/src/entitlements/aggregate.ts +37 -5
- package/src/entitlements/mapping.ts +0 -22
- package/src/index.ts +3 -0
- package/src/persistence/mmkv-store.ts +45 -35
- package/src/persistence/snapshot.ts +102 -27
- package/src/react/PaymentProvider.tsx +85 -23
- package/src/react/hooks.ts +23 -34
- package/src/testing/conformance.ts +169 -0
- package/src/testing/fakeStore.ts +11 -29
- package/src/testing/index.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,207 @@
|
|
|
1
1
|
# @chipmobilesdk/rn-payment
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- The engine is split by responsibility, and its internals are off the public type. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
|
|
8
|
+
|
|
9
|
+
- `PaymentEngine` no longer has `resolvedConfig`, `nextCorrelationId`, `emitter`,
|
|
10
|
+
`snapshot`, `commitSnapshot`, `mayCreateRealTransaction` or `recordBlockedOperation`.
|
|
11
|
+
They were documented as internal, but any consumer could call `commitSnapshot` and
|
|
12
|
+
grant itself an entitlement the store never sold, and a hand-written engine double had
|
|
13
|
+
to implement all of them.
|
|
14
|
+
- `entitlement_revoked` now carries the real reason: `validation_rejected` when your
|
|
15
|
+
backend said no, `consumed` for a consumed item, `expired` for a lapsed subscription,
|
|
16
|
+
and `revoked_by_store` only when the store stopped reporting the purchase. Before, every
|
|
17
|
+
revocation said `revoked_by_store`.
|
|
18
|
+
- `clearLocalData()` also drops cached product prices.
|
|
19
|
+
- A `not_ready` purchase result reports the engine's actual status instead of always
|
|
20
|
+
`connecting`. `usePurchase().purchase` takes a `ProductKey`, as it always accepted.
|
|
21
|
+
- Local storage moves to schema version 2: the never-written `entitlements` map and
|
|
22
|
+
`finalized` purchase state are gone. A version 1 record is migrated in place on first
|
|
23
|
+
load, so nothing needs to be done.
|
|
24
|
+
- Internally, `engine.ts` is 346 lines (was 921): state, connection and sync are their
|
|
25
|
+
own modules, and sync and the purchase flow build purchase records with one function.
|
|
26
|
+
The unused `runWithRetry`, `backoffSchedule`, `sourcesFor` and `entitlementsCoveredBy`
|
|
27
|
+
helpers are gone.
|
|
28
|
+
|
|
29
|
+
- The provider keeps one engine, stored records are checked one by one, and product
|
|
30
|
+
queries run in parallel. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
|
|
31
|
+
|
|
32
|
+
- `PaymentProvider` builds its engine once and keeps it across renders. Inline
|
|
33
|
+
`config` and `deps` objects no longer reconnect to the store on every render of your
|
|
34
|
+
root. A new engine is built only when the new `engineKey` prop changes (default:
|
|
35
|
+
`config.scopeToken`). It now works under React StrictMode, where it used to be left
|
|
36
|
+
holding a cleaned-up engine that never became ready. Pass `engine` to share an engine
|
|
37
|
+
you created and own; the provider then neither initializes nor cleans it up.
|
|
38
|
+
- `initialize()` on a cleaned-up engine reports `PAYMENT_NOT_CONFIGURED` even if the
|
|
39
|
+
engine had been ready, instead of answering `ok`.
|
|
40
|
+
- Every stored purchase record is checked field by field when read. A damaged one is
|
|
41
|
+
dropped and comes back with the next sync, rather than being trusted or taking the
|
|
42
|
+
rest down with it. Only a `purchased` record grants an entitlement; a state this
|
|
43
|
+
version does not know never does.
|
|
44
|
+
- A failed write to local storage is reported as an `internal_error` with
|
|
45
|
+
`PAYMENT_STORAGE_UNAVAILABLE` and operation `persist`, instead of being swallowed.
|
|
46
|
+
A custom `SnapshotStore.save` may return `false` to report one.
|
|
47
|
+
- One-time and subscription products are queried in parallel, identical requests in
|
|
48
|
+
flight share one store call, and `useProducts` never lets an older answer overwrite a
|
|
49
|
+
newer one. `status.blockedOperations` keeps the 20 most recent.
|
|
50
|
+
|
|
51
|
+
- `allowStoreTestPurchases` lets a debug build buy through the real store for Play
|
|
52
|
+
license testers. It is accepted only with `environment: 'test'`; any other environment
|
|
53
|
+
rejects it at startup with `PAYMENT_INVALID_CONFIG`. Without it, non-production
|
|
54
|
+
purchases are still refused as `environment_blocked`, as before. The README no longer
|
|
55
|
+
says the billing library is uninstalled or the adapter unverified, and describes what
|
|
56
|
+
adding iOS actually involves. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
|
|
57
|
+
|
|
58
|
+
### Patch Changes
|
|
59
|
+
|
|
60
|
+
- Internal: no module in these packages is over 500 lines any more. The camera controller's session I/O moved to `sessionRuntime` and its contract to `contract.ts`; the Play adapter's shape translation to `iapShapes`; the purchase flow's failure mapping and its receipt-validation hook to modules of their own; the AI client's quota request to `quotaRequest`; the in-memory adapter's attachments and the fake ads engine's clock likewise. Behaviour is unchanged — the splits are by responsibility, and every package's suite passes as it stood. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
|
|
61
|
+
|
|
62
|
+
The repository also measures what it promised: `npm run perf` prints the median of nine runs of the deterministic hot paths against written budgets (recurrence expansion, theme construction, translation lookup, chart geometry, filter compilation), the demo app has a Performance screen reporting startup phases, time to interactive and the same benchmarks on Hermes, and `npm run check:file-size` keeps the limit from drifting. The budgets and the measured figures are recorded in `docs/standards.md` §VI.
|
|
63
|
+
|
|
64
|
+
## 0.5.0
|
|
65
|
+
|
|
66
|
+
### Minor Changes
|
|
67
|
+
|
|
68
|
+
- One diagnostic stream: install a sink with `installSdkSink` from
|
|
69
|
+
`@chipmobilesdk/rn-core` and it receives what every package observes (events,
|
|
70
|
+
failures, warnings) as `SdkDiagnostic`s, redacted. Listeners are notified through
|
|
71
|
+
rn-core, so a listener that throws no longer stops the others nor fails the call that
|
|
72
|
+
notified, and its error is reported rather than swallowed. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
73
|
+
|
|
74
|
+
- rn-ads: a banner's auto-refresh is a new cycle, so its `loaded` and `impression`
|
|
75
|
+
events are no longer deduplicated away; the dedupe memory is bounded. A throwing
|
|
76
|
+
status, consent or placement listener is reported as an `internal_error` event.
|
|
77
|
+
- rn-i18n: new `onDiagnostic` config receives every diagnostic (`I18nDiagnostic`,
|
|
78
|
+
with a stable `code`); without it, warnings go to `console.warn` in development
|
|
79
|
+
builds only. The warn-once memory is bounded.
|
|
80
|
+
- rn-chart: now depends on rn-core for its diagnostics.
|
|
81
|
+
- rn-payment: status, entitlement and pending listeners report a throw to the logger
|
|
82
|
+
instead of swallowing it.
|
|
83
|
+
|
|
84
|
+
- The package's errors extend `SdkError` from the new `@chipmobilesdk/rn-core`
|
|
85
|
+
dependency: `isSdkError` recognises them alongside every other package's, and each
|
|
86
|
+
carries `pkg`, `code` and `retryable`. Names and codes are unchanged. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
87
|
+
|
|
88
|
+
- rn-ai and rn-camera: `retryable` is the new name of `recoverable`, which stays as a
|
|
89
|
+
deprecated alias.
|
|
90
|
+
- rn-auth: `retryable` is true for `network-unavailable`. `redact` also removes URLs,
|
|
91
|
+
e-mail addresses and UUIDs.
|
|
92
|
+
- rn-ads: `redact` also removes JWTs and e-mail addresses, and reduces an `AdsError`
|
|
93
|
+
to its code.
|
|
94
|
+
- rn-ai: an error message quoted from the gateway is pattern-redacted.
|
|
95
|
+
- rn-camera: `redact` also removes JWTs, e-mail addresses and UUIDs.
|
|
96
|
+
- rn-theme: `ThemeConfigError` has `code: 'invalid_config'` and `ThemeContrastError`
|
|
97
|
+
`code: 'contrast'` (new `ThemeErrorCode` type).
|
|
98
|
+
- rn-i18n: `I18nConfigError` has `code: 'invalid_config'` and `UnitConversionError`
|
|
99
|
+
`code: 'unit_conversion'` (new `I18nErrorCode` type).
|
|
100
|
+
|
|
101
|
+
- Timers, retries and bounds now come from `@chipmobilesdk/rn-core`, and the drift
|
|
102
|
+
between the copies is fixed. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
103
|
+
|
|
104
|
+
- rn-ads: a retry policy out of bounds throws `retry_out_of_bounds`, as documented,
|
|
105
|
+
instead of `timeout_out_of_bounds`; a placement's `minRequestIntervalMs` out of
|
|
106
|
+
bounds throws `invalid_config`.
|
|
107
|
+
- rn-payment: backoff validates `initialDelayMs` (100 ms to 60 s, not above
|
|
108
|
+
`maxDelayMs`) and `factor` (1 to 10); `factor: 0` used to retry at once, forever.
|
|
109
|
+
Every timeout, backoff or interval out of bounds now throws the new
|
|
110
|
+
`PAYMENT_INVALID_CONFIG` instead of `PAYMENT_INVALID_CATALOG`, which is about
|
|
111
|
+
products.
|
|
112
|
+
- rn-ai: concurrent calls share one behavior-config request, so a cold start makes
|
|
113
|
+
one instead of one per call. A silent retry after a config reload waits
|
|
114
|
+
`recovery.retryBackoffMs`, which was parsed but never applied.
|
|
115
|
+
|
|
116
|
+
- Native peers load through `lazyPeer` from `@chipmobilesdk/rn-core`, on first use,
|
|
117
|
+
and a missing one is each package's own typed error. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
118
|
+
|
|
119
|
+
- rn-auth: creating `auth` no longer loads react-native-app-auth or
|
|
120
|
+
react-native-keychain; each loads on first use. New entrypoints
|
|
121
|
+
`@chipmobilesdk/rn-auth/app-auth` and `/keychain`. The default platform comes from
|
|
122
|
+
rn-core.
|
|
123
|
+
- rn-camera: a preview whose VisionCamera feed cannot load fails the session with
|
|
124
|
+
`cameraUnavailable` (new `controller.reportSurfaceUnavailable()`), instead of
|
|
125
|
+
showing black with no error. A missing VisionCamera module is `cameraUnavailable`.
|
|
126
|
+
- rn-notification: `createMmkvStore` reports a fallback to memory as
|
|
127
|
+
`STATE_STORE_DEGRADED` (to the SDK sinks and a new `onDegraded` option) and
|
|
128
|
+
exposes `durable`.
|
|
129
|
+
- rn-payment: an iOS app validates its catalog against the iOS product ids; the
|
|
130
|
+
default platform used to be Android everywhere. A missing react-native-iap is
|
|
131
|
+
`PAYMENT_UNAVAILABLE`.
|
|
132
|
+
- rn-theme: `buildThemes` runs without React Native (new `hairlineWidth` option) and
|
|
133
|
+
importing the package no longer loads Unistyles; `applyThemeMode` loads it on
|
|
134
|
+
first use.
|
|
135
|
+
- rn-ads, rn-local-db: the same loading, with the same error codes as before.
|
|
136
|
+
|
|
137
|
+
- Hooks read through `useSyncExternalStore` (rn-core's `useExternalStore`) and
|
|
138
|
+
re-render only when what they return changes. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
139
|
+
|
|
140
|
+
- rn-auth: `useProfile` and `useAccessTier` no longer re-render for a status change,
|
|
141
|
+
and the provider's context no longer changes on every transition. New
|
|
142
|
+
`useAuthSelector`. `auth.getState()` returns the same frozen object until the state
|
|
143
|
+
changes, instead of a copy per call.
|
|
144
|
+
- rn-payment: `useEntitlement` re-renders only when its own entitlement changes; the
|
|
145
|
+
engine keeps unchanged entitlement objects across syncs and does not notify for a
|
|
146
|
+
sync that changed nothing.
|
|
147
|
+
- rn-ads: `usePlacementState` and `useAdsStatus` read tear-free; `useAdsEvents` keeps
|
|
148
|
+
one subscription when given an inline listener.
|
|
149
|
+
- rn-i18n: `useTranslation` returns the same `t` until the locale changes, and
|
|
150
|
+
`useLocale`'s `setLocale` is stable.
|
|
151
|
+
- rn-ai: new `useAiBehaviorState`, with `client.subscribe` and a stable
|
|
152
|
+
`client.getBehaviorState()`.
|
|
153
|
+
|
|
154
|
+
- Every package's own copy is an rn-core message catalog that an app can translate. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
155
|
+
|
|
156
|
+
- rn-i18n: new `runtimeEntriesFromCatalog(catalog)` registers any SDK catalog in one
|
|
157
|
+
line, converting `{name}` variables to `{{name}}`.
|
|
158
|
+
- rn-ai: new `messageKeyForError(error)` gives the copy for every error code, and
|
|
159
|
+
`aiMessageSet`. The input placeholder default is neutral ("Type a message…"), and
|
|
160
|
+
two keys were added for errors that had none (`ai.error.signIn`, `ai.error.generic`).
|
|
161
|
+
- rn-auth: new `authMessages`, the catalog split into user and developer copy.
|
|
162
|
+
- rn-payment: new `paymentMessages`.
|
|
163
|
+
- rn-chart: summaries, announcements, adjustable-action labels, built-in state text
|
|
164
|
+
and legend hints come from `chartStrings`; pass `strings` to translate them. The
|
|
165
|
+
`accessibility.summaryFormatter` prop, declared before, now replaces the summary.
|
|
166
|
+
|
|
167
|
+
- Port conformance suites: one list of checks that a package's fake and its real adapter
|
|
168
|
+
both pass, so a test written against the fake proves something about the device. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
169
|
+
|
|
170
|
+
- rn-core: `@chipmobilesdk/rn-core/testing` adds `runChecks`, `expectConformance`,
|
|
171
|
+
`ensure` and `createFaultRegistry` (deterministic fault injection by named point).
|
|
172
|
+
- rn-camera: a `./testing` entrypoint with `runPermissionPortConformance`,
|
|
173
|
+
`runCameraEngineConformance` and the test adapter, which stays on the main
|
|
174
|
+
entrypoint too until the next major. `createTestCamera(...).prompts()` counts system
|
|
175
|
+
prompts.
|
|
176
|
+
- rn-payment: `runProviderPortConformance` in `./testing`, passed by the fake store and
|
|
177
|
+
the Play adapter.
|
|
178
|
+
- rn-ads: `runAdsEngineConformance` in `./testing`, passed by the fake engine and the
|
|
179
|
+
AdMob adapter.
|
|
180
|
+
- rn-auth: a `./testing` entrypoint with `createMemorySecureStore`,
|
|
181
|
+
`runSecureTokenStoreConformance` and `runOidcAuthClientConformance`, passed by the
|
|
182
|
+
in-memory doubles and the keychain and app-auth adapters.
|
|
183
|
+
|
|
184
|
+
- One app-lifecycle source, and a startup that needs no extra frame. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
185
|
+
|
|
186
|
+
- rn-core: `appLifecycle()`, a shared, reference-counted `AppState` subscription that
|
|
187
|
+
reports changes only (`active`, `inactive`, `background`), with `onForeground` for a
|
|
188
|
+
return from the background and `createManualLifecycle` in `./testing`.
|
|
189
|
+
- rn-camera, rn-ads and rn-notification hear the lifecycle through it instead of
|
|
190
|
+
subscribing to `AppState` each, so an app holds one native listener. The camera
|
|
191
|
+
controller's core layer no longer imports `react-native`.
|
|
192
|
+
- rn-payment: `syncOnForeground(engine)` and `<PaymentProvider syncOnForeground>` call
|
|
193
|
+
`handleForeground()` on each return from the background. Off by default; calling
|
|
194
|
+
`handleForeground()` yourself works as before.
|
|
195
|
+
- rn-i18n: `i18n.initSync()` resolves the locale before the first render when the
|
|
196
|
+
storage adapter has the new optional `getItemSync`, and returns false otherwise.
|
|
197
|
+
`localeStorageFromKeyValue(port)` builds such an adapter from rn-core's
|
|
198
|
+
`KeyValuePort` (MMKV).
|
|
199
|
+
|
|
200
|
+
### Patch Changes
|
|
201
|
+
|
|
202
|
+
- Updated dependencies ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17), [#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17), [#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17)):
|
|
203
|
+
- @chipmobilesdk/rn-core@0.1.0
|
|
204
|
+
|
|
3
205
|
## 0.4.1
|
|
4
206
|
|
|
5
207
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -91,12 +91,9 @@ npm install react-native-iap react-native-mmkv react-native-nitro-modules
|
|
|
91
91
|
> do **not** add it yourself. No runtime permission is requested and no sensitive
|
|
92
92
|
> API is touched, so there is no permission prompt and no denial path to handle.
|
|
93
93
|
>
|
|
94
|
-
> **
|
|
95
|
-
> `android
|
|
96
|
-
>
|
|
97
|
-
> billing library is not installed yet. Once it is, confirm the merged manifest
|
|
98
|
-
> contains exactly one `com.android.vending.BILLING` entry and no new runtime
|
|
99
|
-
> permission:
|
|
94
|
+
> **Check your merged manifest** once `react-native-iap` is installed: it should
|
|
95
|
+
> contain exactly one `com.android.vending.BILLING` entry, added by the billing
|
|
96
|
+
> library, and no new runtime permission:
|
|
100
97
|
>
|
|
101
98
|
> ```bash
|
|
102
99
|
> # After a debug build, inspect the merge report:
|
|
@@ -164,9 +161,13 @@ const config = {
|
|
|
164
161
|
isProductionBuild: true,
|
|
165
162
|
};
|
|
166
163
|
|
|
164
|
+
// Created once, outside the component: the store adapter holds the billing
|
|
165
|
+
// connection.
|
|
166
|
+
const deps = { provider: createStoreProvider() };
|
|
167
|
+
|
|
167
168
|
export function App() {
|
|
168
169
|
return (
|
|
169
|
-
<PaymentProvider config={config} deps={
|
|
170
|
+
<PaymentProvider config={config} deps={deps}>
|
|
170
171
|
<YourApp />
|
|
171
172
|
</PaymentProvider>
|
|
172
173
|
);
|
|
@@ -182,6 +183,13 @@ function AdSlot() {
|
|
|
182
183
|
}
|
|
183
184
|
```
|
|
184
185
|
|
|
186
|
+
The provider builds its engine once and keeps it across renders; `config` and `deps`
|
|
187
|
+
are read when it is built. It builds a new one when `engineKey` changes (by default
|
|
188
|
+
`config.scopeToken`, so switching accounts gets a fresh engine), and it survives React
|
|
189
|
+
StrictMode in development. To own the engine yourself — create it at startup, share it
|
|
190
|
+
with code outside React — pass it instead: `<PaymentProvider engine={engine}>`. The
|
|
191
|
+
provider then neither initializes nor cleans it up.
|
|
192
|
+
|
|
185
193
|
`createStoreProvider()` picks the platform adapter. On a platform with no adapter
|
|
186
194
|
it returns a stub that reports `unavailable` instead of throwing, so your app
|
|
187
195
|
still starts and runs — it only loses the ability to sell.
|
|
@@ -192,6 +200,13 @@ what "foreground" means for an app that may be backgrounded by its own modal, a
|
|
|
192
200
|
biometric prompt, or the store's own purchase sheet. It is rate-limited
|
|
193
201
|
regardless, so calling it liberally is safe.
|
|
194
202
|
|
|
203
|
+
If "back from the background" is what you mean, opt in instead of wiring `AppState`:
|
|
204
|
+
`<PaymentProvider syncOnForeground>` or `syncOnForeground(engine)` (returns an
|
|
205
|
+
unsubscribe). Both listen through rn-core's shared lifecycle port, so they add no
|
|
206
|
+
native subscription of their own, and a trip through `inactive` alone does not count.
|
|
207
|
+
On Android the Play purchase sheet does background the app, so a purchase is followed
|
|
208
|
+
by one rate-limited sync.
|
|
209
|
+
|
|
195
210
|
---
|
|
196
211
|
|
|
197
212
|
## Catalog and entitlement mapping
|
|
@@ -544,6 +559,20 @@ Two layers:
|
|
|
544
559
|
recorded in `getStatus().blockedOperations`, and emitted as
|
|
545
560
|
`environment_blocked` — observable in state, so a test can assert it rather
|
|
546
561
|
than scraping logs.
|
|
562
|
+
3. **License testing, opted in.** `environment: 'test'` with
|
|
563
|
+
`allowStoreTestPurchases: true` lets a debug build buy through the real store,
|
|
564
|
+
so a Play license tester can check a purchase without a release build from a
|
|
565
|
+
track. Any other environment rejects the option at construction. License
|
|
566
|
+
testers are not charged; anyone else signed in on the device would be.
|
|
567
|
+
|
|
568
|
+
```ts
|
|
569
|
+
const config = {
|
|
570
|
+
// ...
|
|
571
|
+
environment: 'test',
|
|
572
|
+
isProductionBuild: false,
|
|
573
|
+
allowStoreTestPurchases: true, // license testers only
|
|
574
|
+
};
|
|
575
|
+
```
|
|
547
576
|
|
|
548
577
|
> **Why this blocks where `@chipmobilesdk/rn-ads` substitutes.** The ads package
|
|
549
578
|
> swaps in Google's official *test ad units* when a non-production build supplies
|
|
@@ -604,7 +633,9 @@ import { DEFAULT_USER_MESSAGES, USER_MESSAGE_KEYS, isUserMessageKey } from '@chi
|
|
|
604
633
|
|
|
605
634
|
User-actionable and developer copy are **separate maps**, so you cannot
|
|
606
635
|
accidentally show "Payment catalog is invalid" to a customer. `isUserMessageKey()`
|
|
607
|
-
lets you assert that at your own boundary.
|
|
636
|
+
lets you assert that at your own boundary. `paymentMessages` is the same catalog as
|
|
637
|
+
an rn-core message set; register the user copy with rn-i18n in one line:
|
|
638
|
+
`runtimeEntriesFromCatalog(paymentMessages.user)`.
|
|
608
639
|
|
|
609
640
|
Message keys are public API; renaming one is a breaking change.
|
|
610
641
|
|
|
@@ -612,6 +643,9 @@ Message keys are public API; renaming one is a breaking change.
|
|
|
612
643
|
|
|
613
644
|
## Events and errors
|
|
614
645
|
|
|
646
|
+
Every error is a `PaymentError` (or `PaymentConfigError`) with a stable `code`, the
|
|
647
|
+
`operation` and a `retryable` flag. `PaymentError` extends `SdkError` from `@chipmobilesdk/rn-core`, so `isSdkError(error)` recognises it alongside every other package's errors, `error.pkg` names this package, and `error.redactedCause` (when present) is the underlying failure reduced to log-safe text.
|
|
648
|
+
|
|
615
649
|
20 event types, each carrying a timestamp and — within a purchase cycle — one
|
|
616
650
|
`correlationId`. The bar is that the event stream **alone** is enough to
|
|
617
651
|
reconstruct a transaction's lifecycle, so "the user says they paid but has no
|
|
@@ -647,6 +681,11 @@ The failure goes to your `logger`, not back into the event stream.
|
|
|
647
681
|
Errors carry a stable `code`, a log-safe `message`, the `operation`, a `retryable`
|
|
648
682
|
flag, and a redacted `cause`. Error codes are public API.
|
|
649
683
|
|
|
684
|
+
A timeout, backoff or interval outside its bounds throws `PaymentConfigError` with
|
|
685
|
+
`PAYMENT_INVALID_CONFIG`, naming the field in `offenders`. Backoff checks every
|
|
686
|
+
field: `initialDelayMs` 100 ms to 60 s and not above `maxDelayMs`, `factor` 1 to
|
|
687
|
+
10, `maxDelayMs` 5 s to 10 min, `maxAttempts` 1 to 10.
|
|
688
|
+
|
|
650
689
|
---
|
|
651
690
|
|
|
652
691
|
## Wiring an entitlement to another system
|
|
@@ -710,6 +749,12 @@ store.pushPurchaseUpdate(...); // deliver a settlement with no sync
|
|
|
710
749
|
`./testing` is a separate entrypoint so test doubles cannot reach a production
|
|
711
750
|
bundle.
|
|
712
751
|
|
|
752
|
+
`runProviderPortConformance(harness)` holds the checks every store adapter must pass:
|
|
753
|
+
connection and a dropped service, products with the store's own price, pending
|
|
754
|
+
purchases flagged rather than filtered, a bought and a cancelled flow, and
|
|
755
|
+
finalization that is safe twice. The package runs it against the fake store and the
|
|
756
|
+
Play adapter; an iOS adapter, or a device run from a demo screen, uses the same suite.
|
|
757
|
+
|
|
713
758
|
### Licence testers, for device verification
|
|
714
759
|
|
|
715
760
|
1. Play Console → **Setup → Licence testing**, add tester accounts.
|
|
@@ -728,10 +773,8 @@ Exhaustively, for your Data safety declaration:
|
|
|
728
773
|
|
|
729
774
|
| Stored | Why |
|
|
730
775
|
|---|---|
|
|
731
|
-
|
|
|
732
|
-
|
|
|
733
|
-
| Subscription expiry and renewal status | So lifecycle is readable offline |
|
|
734
|
-
| Unfinished-transaction records (transaction id, product key, token, attempt count) | So a paid purchase is never lost to an app kill |
|
|
776
|
+
| Purchase records (transaction id, product key, product type, token, purchase time, store state, finalize state and attempt count, backend validation state) | So entitlements are readable offline and a paid purchase is never lost to an app kill. Entitlements themselves are derived from these, never stored |
|
|
777
|
+
| Subscription expiry and renewal status, on the purchase record | So lifecycle is readable offline |
|
|
735
778
|
| Pending-purchase records (the same fields, plus the time the store reports the purchase was made) | So a payment still in flight is visible after an app kill. Unchanged since `0.1.0`; `0.2.0` only reads them |
|
|
736
779
|
| Last successful sync time | So data age is observable |
|
|
737
780
|
| Schema version | So the record can be migrated |
|
|
@@ -822,9 +865,11 @@ Your obligations as the app owner — this package cannot discharge them for you
|
|
|
822
865
|
non-default policy needs server data.
|
|
823
866
|
2. **iOS is contract-only.** `createStoreProvider()` returns an `unavailable` stub
|
|
824
867
|
on iOS; the host app still starts and runs.
|
|
825
|
-
3. **
|
|
826
|
-
|
|
827
|
-
|
|
868
|
+
3. **Debug builds do not reach the store unless you opt in.** Outside production a
|
|
869
|
+
purchase reports `environment_blocked`. For a license tester on a debug build,
|
|
870
|
+
set `environment: 'test'` and `allowStoreTestPurchases: true`; only license
|
|
871
|
+
testers are not charged. See [Compatibility](#compatibility) for the devices and
|
|
872
|
+
binding the Android adapter was verified with.
|
|
828
873
|
4. **Price-change confirmation is unsupported on Android.** Play notifies the user
|
|
829
874
|
itself and offers no client-side flow, so `confirmPriceChange` reports
|
|
830
875
|
`unsupported` rather than faking a confirmation.
|
|
@@ -840,7 +885,12 @@ Your obligations as the app owner — this package cannot discharge them for you
|
|
|
840
885
|
|
|
841
886
|
## Adding iOS later
|
|
842
887
|
|
|
843
|
-
Implement `ProviderPort` for StoreKit
|
|
888
|
+
Implement `ProviderPort` for StoreKit and return it from `createStoreProvider()` on
|
|
889
|
+
iOS. Your entitlement checks, hooks and purchase calls do not change. What an iOS
|
|
890
|
+
release adds: `storeProductIds.ios` in each catalog entry, the adapter's own
|
|
891
|
+
`capabilities()` (StoreKit can tell grace period from account hold, and supports
|
|
892
|
+
externally initiated purchases, so some states Android reports as `indeterminate`
|
|
893
|
+
become distinct), and its conformance run (`runProviderPortConformance`).
|
|
844
894
|
|
|
845
895
|
| Play concept | Port concept | StoreKit |
|
|
846
896
|
|---|---|---|
|
|
@@ -867,6 +917,10 @@ Local storage carries a schema version and a forward migration chain. A record
|
|
|
867
917
|
that cannot be migrated is discarded and rebuilt from the store — safe, because
|
|
868
918
|
discarding yields `unknown`, never a false `not_entitled`.
|
|
869
919
|
|
|
920
|
+
Schema version 2 (0.6.0) drops two things version 1 stored and never used: an
|
|
921
|
+
empty `entitlements` map and a `finalized` purchase state nothing set. A version 1
|
|
922
|
+
record is migrated in place on first load.
|
|
923
|
+
|
|
870
924
|
### Migrating `0.1.0` to `0.2.0`
|
|
871
925
|
|
|
872
926
|
**One union gained a member**, so an exhaustive `switch` over it needs the new
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chipmobilesdk/rn-payment",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"description": "Reusable in-app payment and entitlement capability for ChipMobileSdk React Native apps",
|
|
@@ -27,9 +27,12 @@
|
|
|
27
27
|
],
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"scripts": {
|
|
30
|
-
"typecheck": "tsc -p tsconfig.json
|
|
30
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.test.json",
|
|
31
31
|
"pack:check": "npm pack --dry-run"
|
|
32
32
|
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@chipmobilesdk/rn-core": "^0.1.0"
|
|
35
|
+
},
|
|
33
36
|
"peerDependencies": {
|
|
34
37
|
"react": ">=19",
|
|
35
38
|
"react-native": ">=0.85",
|
|
@@ -68,10 +68,10 @@ rule: a chargeable purchase is permitted only when `environment: 'production'` *
|
|
|
68
68
|
`PAYMENT_PRODUCTION_CONFIG_IN_NON_PRODUCTION_BUILD` at construction and never connects.
|
|
69
69
|
|
|
70
70
|
Consequence worth knowing before you plan device testing: **licence-tester purchases are
|
|
71
|
-
real Play transactions
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
real Play transactions**. A debug build reaches the store only with `environment: 'test'`
|
|
72
|
+
and `allowStoreTestPurchases: true` (licence testers are not charged; anyone else would
|
|
73
|
+
be); otherwise it gets `environment_blocked`. Drive every behaviour that is not "does
|
|
74
|
+
Play accept this" through the fake store instead. See
|
|
75
75
|
`## Environments and the production guard`.
|
|
76
76
|
|
|
77
77
|
Unlike `@chipmobilesdk/rn-ads`, there is **no test-id substitution**: Play has no test
|
|
@@ -91,6 +91,8 @@ Unlike `@chipmobilesdk/rn-ads`, there is **no test-id substitution**: Play has n
|
|
|
91
91
|
|
|
92
92
|
Call `handleForeground()` yourself on app resume — the package will not guess what
|
|
93
93
|
"foreground" means for a host backgrounded by its own modal or the store's purchase sheet.
|
|
94
|
+
When a return from the background is enough, opt in with `<PaymentProvider
|
|
95
|
+
syncOnForeground>` or `syncOnForeground(engine)` (rn-core's shared lifecycle port).
|
|
94
96
|
|
|
95
97
|
## Public API
|
|
96
98
|
|
|
@@ -103,8 +105,8 @@ environment; everything else returns a typed result) · `createStoreProvider` ·
|
|
|
103
105
|
`DEFAULT_USER_MESSAGES`, `DEFAULT_DEVELOPER_MESSAGES`, `USER_MESSAGE_KEYS`,
|
|
104
106
|
`DEVELOPER_MESSAGE_KEYS`, `isUserMessageKey` · timeout/retry/cache defaults and bounds
|
|
105
107
|
(`DEFAULT_STALE_AFTER_MS`, `DEFAULT_PURCHASE_LAUNCH_TIMEOUT_MS`, `STALE_AFTER_BOUNDS`, …)
|
|
106
|
-
· `createFakeStore`, `makePurchase` from
|
|
107
|
-
doubles only).
|
|
108
|
+
· `createFakeStore`, `makePurchase`, `runProviderPortConformance` from
|
|
109
|
+
`@chipmobilesdk/rn-payment/testing` (test doubles and the store-adapter suite only).
|
|
108
110
|
|
|
109
111
|
Engine: `initialize`, `cleanup`, `getStatus`, `subscribeStatus`, `getEntitlement`,
|
|
110
112
|
`getAllEntitlements`, `subscribeEntitlements`, `getPendingPurchases`,
|