@chipmobilesdk/rn-payment 0.0.0-stage → 0.2.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 ADDED
@@ -0,0 +1,201 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@chipmobilesdk/rn-payment` are recorded here.
4
+
5
+ This package follows semantic versioning. **Entitlement keys are yours**, but
6
+ product keys, error codes, event names, state values, message keys, and the
7
+ public API shape are this package's contract: renaming any of them is a breaking
8
+ change.
9
+
10
+ ## [0.2.0] — 2026-10-12
11
+
12
+ Additive minor release closing one gap that surfaced when `0.1.0` met an app that
13
+ must show a pending payment after a restart. It is a **capability** gap: `0.1.0`'s
14
+ engine refused to grant a pending purchase, but an app could only *see* one in the
15
+ session that started it. Reconstructing the pending set meant importing the billing
16
+ library, which this package exists to prevent.
17
+
18
+ It also fixes a **serious defect on Android devices**: a pending purchase was
19
+ granted. See [Fixed](#fixed). **Upgrade from `0.1.0` before shipping.**
20
+
21
+ **Upgrading requires one change, compile-time only:** `PaymentEventType` gained a
22
+ member. See [Migration](#migration).
23
+
24
+ ### Added
25
+
26
+ **Pending purchases, visible across sessions**
27
+
28
+ - `getPendingPurchases()` and `subscribePendingPurchases(listener)` on the engine,
29
+ and `usePendingPurchases(productKeys?)` in React. They return a
30
+ `PendingPurchasesState`: a `status` (`known` | `unknown`), an `isStale` flag, and
31
+ `purchases`, one `{ productKey, since }` per product. No transaction id, token or
32
+ store object.
33
+ - Read from the persisted snapshot, so the set survives an app kill and an offline
34
+ restart. It is judged by the **same staleness rule as entitlements**, now shared
35
+ code rather than a parallel copy: never synced, or older than `staleAfterMs`,
36
+ reports `unknown`, never an empty "nothing pending".
37
+ - Pushed only when the set, its status, or its staleness changes. A sync that finds
38
+ nothing new re-renders nothing.
39
+
40
+ **How a pending purchase ended**
41
+
42
+ - `purchase_pending_resolved` event, `{ productKey, outcome }`, with `outcome`
43
+ `settled` | `cancelled` | `expired`, emitted once per pending transaction.
44
+ `settled` precedes the `entitlement_granted` it leads to.
45
+ - Detected from the snapshot commit itself, so it is exactly-once by construction:
46
+ a store push and a sync racing on one settlement emit one event, a sync that
47
+ changed nothing emits none, and `clearLocalData()` emits none, because discarding
48
+ a record locally is not the store ending it.
49
+ - **On Android every unpaid end is `cancelled`.** Play does not say whether a pending
50
+ purchase was declined or expired; it stops reporting it. The package does not
51
+ guess from elapsed time. `expired` is reported only by a store that says so,
52
+ through a new optional `pendingEndReason` on the internal provider port.
53
+
54
+ **Testing**
55
+
56
+ - Fake store: `settlePending`, `cancelPending` and `expirePending` change store-side
57
+ state the way the real store does, so the engine sees the change at its next sync,
58
+ in the same session or after a simulated app kill.
59
+
60
+ ### Fixed
61
+
62
+ - **On a device, a pending purchase was granted.** The Android adapter read only
63
+ Play's numeric purchase state and treated anything else as purchased. The
64
+ `react-native-iap` 16.x binding hands JavaScript the state as a **string**
65
+ (`'pending' | 'purchased' | 'unknown'`) and does not carry the numeric one across,
66
+ so every pending purchase fell through to "purchased": entitlement granted, and an
67
+ acknowledgement attempted, for money that had not arrived. The engine's own
68
+ "pending never grants" rule was intact; the adapter fed it the wrong state. No
69
+ test could see it, because tests drive the fake store, which bypasses the adapter.
70
+ The adapter now reads either form, and anything it does not recognize is
71
+ `unspecified`, never purchased. Checked against the installed 16.7.3 declarations
72
+ and its JavaScript bridge.
73
+ - **An `unspecified` purchase state is never treated as paid.** `0.1.0` persisted
74
+ Play's unknown state as `purchased`, granting it and queuing an acknowledgement.
75
+ Now a new transaction in that state is left out until the store says what it is,
76
+ a known one keeps what the package already knew (uncertainty is neither a grant nor
77
+ a refund), a store push in that state is ignored, and `purchase()` returning one
78
+ answers `store_error`, retryable: if the money did move, a retry gets
79
+ `already_owned`, which re-syncs and grants.
80
+
81
+ ### Unchanged, deliberately
82
+
83
+ - **Pending never grants**, under any configuration. The new surface is read-only.
84
+ - **No storage migration.** The schema stays at version 1. `0.1.0` already persisted
85
+ pending records; `0.2.0` only reads them.
86
+ - `purchase()` still returns `pending`, and `purchase_pending` still fires, exactly
87
+ as before.
88
+
89
+ ### Migration
90
+
91
+ | Union | New member |
92
+ |---|---|
93
+ | `PaymentEventType` / `PaymentEvent` | `'purchase_pending_resolved'` |
94
+
95
+ An exhaustive `switch` over either needs the new case or a `default`. A
96
+ hand-written `PaymentEngine`, typically a test mock, needs `getPendingPurchases` and
97
+ `subscribePendingPurchases`. Nothing else changes.
98
+
99
+ ## [0.1.0] — 2026-10-06
100
+
101
+ First version. Android only, via Google Play Billing.
102
+
103
+ ### Added
104
+
105
+ **Entitlements**
106
+
107
+ - `getEntitlement`, `getAllEntitlements`, `subscribeEntitlements` — synchronous,
108
+ in-memory reads safe to call on a render path.
109
+ - Three states: `unknown`, `entitled`, `not_entitled`. `unknown` is both the
110
+ initial state and re-entrant, so a long-offline paying user is never reported
111
+ as unpaid.
112
+ - Many-to-many product↔entitlement mapping. An entitlement stays valid while any
113
+ granting product remains.
114
+ - Source attribution: which of *your* product keys currently grants an
115
+ entitlement.
116
+
117
+ **Selling**
118
+
119
+ - `purchase` with a typed outcome covering success, pending, user cancellation,
120
+ already-owned, ineligible, unknown product, region unavailability, disabled,
121
+ not-ready, unsupported, timeout, network error, store error, environment block,
122
+ and validation rejection.
123
+ - One purchase flow at a time. A second request is refused, never queued.
124
+ - `already_owned` triggers a re-sync, which is how a reinstall repairs itself.
125
+ - Consumables with app-controlled consumption via `consume`.
126
+ - Subscriptions with expiry, auto-renew, and cancelled-but-valid reporting.
127
+ - `changePlan` with four proration modes; `openSubscriptionManagement`;
128
+ `confirmPriceChange`.
129
+
130
+ **Finalization**
131
+
132
+ - A persisted queue drained at startup, on foreground, and after each purchase.
133
+ A purchase survives the app being killed between payment and acknowledgement.
134
+ - Idempotent finalization and consumption.
135
+ - Bounded per-session retry that resumes in the next session rather than
136
+ abandoning a paid transaction.
137
+ - `getUnfinalized()` so "paid but not settled" is observable.
138
+
139
+ **Products and pricing**
140
+
141
+ - `getProducts` / `refreshProducts` returning store-formatted prices, currency
142
+ codes, numeric prices for comparison, billing periods, trials, and
143
+ introductory pricing.
144
+ - Offer eligibility as the store determined it.
145
+ - In-memory cache with a configurable TTL, defaulting to 15 minutes.
146
+
147
+ **Sync, restore, and offline**
148
+
149
+ - Automatic sync at initialization and on foreground, rate-limited.
150
+ - `restore` distinguishing "restored" from "nothing found".
151
+ - Revocation on the next sync when the store stops reporting a transaction.
152
+ - Offline reads from the last successful sync, expiring to `unknown` after a
153
+ configurable ceiling (7 days by default).
154
+ - `clearLocalData` and optional cache-scope partitioning.
155
+
156
+ **Safety**
157
+
158
+ - Fail-closed initialization when a non-production build declares `production`.
159
+ - Runtime block on any chargeable purchase outside production, recorded in
160
+ observable state.
161
+ - No exported way to grant an arbitrary entitlement.
162
+
163
+ **Optional server validation**
164
+
165
+ - A hook that decides entitlement for newly completed purchases.
166
+ - Configurable fallback when the backend is unreachable, defaulting to grant and
167
+ queue for re-validation.
168
+
169
+ **React**
170
+
171
+ - `PaymentProvider`, `useEntitlement`, `useEntitlements`, `usePaymentStatus`,
172
+ `usePurchase`, `useProducts`, `useSync`.
173
+
174
+ **Diagnostics**
175
+
176
+ - 19 typed event types with correlation ids, sufficient to reconstruct a
177
+ transaction's lifecycle from logs alone.
178
+ - 22 stable error codes.
179
+ - A localizable message-key map with English defaults and no i18n dependency.
180
+
181
+ **Testing**
182
+
183
+ - A fake store behind `@chipmobilesdk/rn-payment/testing` driving every outcome,
184
+ both callback orderings, connection loss, refunds, and crash-then-recover
185
+ finalization.
186
+
187
+ ### Known limitations
188
+
189
+ - **Grace period and account hold are not separable on Android.** Play returns a
190
+ grace-period subscription as active and stops returning a held one, so
191
+ `renewalState` reports `indeterminate` rather than inventing a distinction. The
192
+ default policy still behaves correctly with no backend; a non-default policy
193
+ needs server data through the validation hook.
194
+ - **iOS is contract-only.** `createStoreProvider()` returns a stub that reports
195
+ `unavailable` on iOS. The host app still starts and runs.
196
+ - **The Android adapter is unverified against an installed binding.** See the
197
+ README's compatibility section.
198
+
199
+ ### Migration
200
+
201
+ None — this is a new package.