@chipmobilesdk/rn-payment 0.1.0 → 0.2.1

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 CHANGED
@@ -7,7 +7,119 @@ product keys, error codes, event names, state values, message keys, and the
7
7
  public API shape are this package's contract: renaming any of them is a breaking
8
8
  change.
9
9
 
10
- ## [0.1.0] — unreleased
10
+ ## [0.2.1] — 2026-10-12
11
+
12
+ Patch release. **Upgrade before shipping**: on a device, `0.2.0` and earlier persisted
13
+ nothing.
14
+
15
+ ### Fixed
16
+
17
+ - **The purchase snapshot never reached the device's storage.** The store opened MMKV
18
+ with `new MMKV()`, the react-native-mmkv **v3** constructor. In v4 — the only major the
19
+ peer range (`>=4.0.0`) allows — `MMKV` is a type, not a value, so the call threw and the
20
+ store fell back to memory on every device. Consequences: entitlements read `unknown`
21
+ after every cold start until a sync, an owner with no connection stayed `unknown`, and a
22
+ pending purchase was forgotten after an app kill. The store now uses v4's `createMMKV`
23
+ and `remove`. Found on an emulator: the app's `files/mmkv` held no
24
+ `chipmobilesdk.payment` instance.
25
+ - The engine did report the fallback, as a `config_warning` ("Local storage
26
+ unavailable"), but nothing in the tests could trigger it: the repository's test double
27
+ exported a v3 `MMKV` class. The double now mirrors v4 exactly, and the persistence suite
28
+ fails against the old code.
29
+
30
+ No API, storage format or behaviour change otherwise. Records written by a working
31
+ install are read as before; there were none from `0.2.0` on a device to migrate.
32
+
33
+ ## [0.2.0] — 2026-10-12
34
+
35
+ Additive minor release closing one gap that surfaced when `0.1.0` met an app that
36
+ must show a pending payment after a restart. It is a **capability** gap: `0.1.0`'s
37
+ engine refused to grant a pending purchase, but an app could only *see* one in the
38
+ session that started it. Reconstructing the pending set meant importing the billing
39
+ library, which this package exists to prevent.
40
+
41
+ It also fixes a **serious defect on Android devices**: a pending purchase was
42
+ granted. See [Fixed](#fixed). **Upgrade from `0.1.0` before shipping.**
43
+
44
+ **Upgrading requires one change, compile-time only:** `PaymentEventType` gained a
45
+ member. See [Migration](#migration).
46
+
47
+ ### Added
48
+
49
+ **Pending purchases, visible across sessions**
50
+
51
+ - `getPendingPurchases()` and `subscribePendingPurchases(listener)` on the engine,
52
+ and `usePendingPurchases(productKeys?)` in React. They return a
53
+ `PendingPurchasesState`: a `status` (`known` | `unknown`), an `isStale` flag, and
54
+ `purchases`, one `{ productKey, since }` per product. No transaction id, token or
55
+ store object.
56
+ - Read from the persisted snapshot, so the set survives an app kill and an offline
57
+ restart. It is judged by the **same staleness rule as entitlements**, now shared
58
+ code rather than a parallel copy: never synced, or older than `staleAfterMs`,
59
+ reports `unknown`, never an empty "nothing pending".
60
+ - Pushed only when the set, its status, or its staleness changes. A sync that finds
61
+ nothing new re-renders nothing.
62
+
63
+ **How a pending purchase ended**
64
+
65
+ - `purchase_pending_resolved` event, `{ productKey, outcome }`, with `outcome`
66
+ `settled` | `cancelled` | `expired`, emitted once per pending transaction.
67
+ `settled` precedes the `entitlement_granted` it leads to.
68
+ - Detected from the snapshot commit itself, so it is exactly-once by construction:
69
+ a store push and a sync racing on one settlement emit one event, a sync that
70
+ changed nothing emits none, and `clearLocalData()` emits none, because discarding
71
+ a record locally is not the store ending it.
72
+ - **On Android every unpaid end is `cancelled`.** Play does not say whether a pending
73
+ purchase was declined or expired; it stops reporting it. The package does not
74
+ guess from elapsed time. `expired` is reported only by a store that says so,
75
+ through a new optional `pendingEndReason` on the internal provider port.
76
+
77
+ **Testing**
78
+
79
+ - Fake store: `settlePending`, `cancelPending` and `expirePending` change store-side
80
+ state the way the real store does, so the engine sees the change at its next sync,
81
+ in the same session or after a simulated app kill.
82
+
83
+ ### Fixed
84
+
85
+ - **On a device, a pending purchase was granted.** The Android adapter read only
86
+ Play's numeric purchase state and treated anything else as purchased. The
87
+ `react-native-iap` 16.x binding hands JavaScript the state as a **string**
88
+ (`'pending' | 'purchased' | 'unknown'`) and does not carry the numeric one across,
89
+ so every pending purchase fell through to "purchased": entitlement granted, and an
90
+ acknowledgement attempted, for money that had not arrived. The engine's own
91
+ "pending never grants" rule was intact; the adapter fed it the wrong state. No
92
+ test could see it, because tests drive the fake store, which bypasses the adapter.
93
+ The adapter now reads either form, and anything it does not recognize is
94
+ `unspecified`, never purchased. Checked against the installed 16.7.3 declarations
95
+ and its JavaScript bridge.
96
+ - **An `unspecified` purchase state is never treated as paid.** `0.1.0` persisted
97
+ Play's unknown state as `purchased`, granting it and queuing an acknowledgement.
98
+ Now a new transaction in that state is left out until the store says what it is,
99
+ a known one keeps what the package already knew (uncertainty is neither a grant nor
100
+ a refund), a store push in that state is ignored, and `purchase()` returning one
101
+ answers `store_error`, retryable: if the money did move, a retry gets
102
+ `already_owned`, which re-syncs and grants.
103
+
104
+ ### Unchanged, deliberately
105
+
106
+ - **Pending never grants**, under any configuration. The new surface is read-only.
107
+ - **No storage migration.** The schema stays at version 1. `0.1.0` already persisted
108
+ pending records; `0.2.0` only reads them.
109
+ - `purchase()` still returns `pending`, and `purchase_pending` still fires, exactly
110
+ as before.
111
+
112
+ ### Migration
113
+
114
+ | Union | New member |
115
+ |---|---|
116
+ | `PaymentEventType` / `PaymentEvent` | `'purchase_pending_resolved'` |
117
+
118
+ An exhaustive `switch` over either needs the new case or a `default`. A
119
+ hand-written `PaymentEngine`, typically a test mock, needs `getPendingPurchases` and
120
+ `subscribePendingPurchases`. Nothing else changes.
121
+
122
+ ## [0.1.0] — 2026-10-06
11
123
 
12
124
  First version. Android only, via Google Play Billing.
13
125