@chipmobilesdk/rn-payment 0.6.0 → 0.7.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 +111 -0
- package/README.md +1 -1
- package/lib/commonjs/adapter/createStoreProvider.js +110 -0
- package/lib/commonjs/adapter/createStoreProvider.js.map +1 -0
- package/lib/commonjs/adapter/iap-adapter.js +393 -0
- package/lib/commonjs/adapter/iap-adapter.js.map +1 -0
- package/lib/commonjs/adapter/iapShapes.js +169 -0
- package/lib/commonjs/adapter/iapShapes.js.map +1 -0
- package/lib/commonjs/adapter/port.js +6 -0
- package/lib/commonjs/adapter/port.js.map +1 -0
- package/lib/commonjs/config/types.js +2 -0
- package/lib/commonjs/config/types.js.map +1 -0
- package/lib/commonjs/config/validate.js +181 -0
- package/lib/commonjs/config/validate.js.map +1 -0
- package/lib/commonjs/core/mutex.js +31 -0
- package/lib/commonjs/core/mutex.js.map +1 -0
- package/lib/commonjs/core/retry.js +37 -0
- package/lib/commonjs/core/retry.js.map +1 -0
- package/lib/commonjs/core/timers.js +207 -0
- package/lib/commonjs/core/timers.js.map +1 -0
- package/lib/commonjs/diagnostics/errors.js +83 -0
- package/lib/commonjs/diagnostics/errors.js.map +1 -0
- package/lib/commonjs/diagnostics/events.js +126 -0
- package/lib/commonjs/diagnostics/events.js.map +1 -0
- package/lib/commonjs/diagnostics/messageKeys.js +70 -0
- package/lib/commonjs/diagnostics/messageKeys.js.map +1 -0
- package/lib/commonjs/engine/connection.js +189 -0
- package/lib/commonjs/engine/connection.js.map +1 -0
- package/lib/commonjs/engine/engine.js +279 -0
- package/lib/commonjs/engine/engine.js.map +1 -0
- package/lib/commonjs/engine/finalize.js +325 -0
- package/lib/commonjs/engine/finalize.js.map +1 -0
- package/lib/commonjs/engine/foreground.js +26 -0
- package/lib/commonjs/engine/foreground.js.map +1 -0
- package/lib/commonjs/engine/internals.js +31 -0
- package/lib/commonjs/engine/internals.js.map +1 -0
- package/lib/commonjs/engine/pending.js +102 -0
- package/lib/commonjs/engine/pending.js.map +1 -0
- package/lib/commonjs/engine/products.js +198 -0
- package/lib/commonjs/engine/products.js.map +1 -0
- package/lib/commonjs/engine/purchase.js +395 -0
- package/lib/commonjs/engine/purchase.js.map +1 -0
- package/lib/commonjs/engine/purchaseFailure.js +77 -0
- package/lib/commonjs/engine/purchaseFailure.js.map +1 -0
- package/lib/commonjs/engine/receiptValidation.js +90 -0
- package/lib/commonjs/engine/receiptValidation.js.map +1 -0
- package/lib/commonjs/engine/state.js +201 -0
- package/lib/commonjs/engine/state.js.map +1 -0
- package/lib/commonjs/engine/subscriptions.js +122 -0
- package/lib/commonjs/engine/subscriptions.js.map +1 -0
- package/lib/commonjs/engine/sync.js +163 -0
- package/lib/commonjs/engine/sync.js.map +1 -0
- package/lib/commonjs/entitlements/aggregate.js +212 -0
- package/lib/commonjs/entitlements/aggregate.js.map +1 -0
- package/lib/commonjs/entitlements/mapping.js +41 -0
- package/lib/commonjs/entitlements/mapping.js.map +1 -0
- package/lib/commonjs/entitlements/policy.js +110 -0
- package/lib/commonjs/entitlements/policy.js.map +1 -0
- package/lib/commonjs/index.js +266 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/persistence/mmkv-store.js +149 -0
- package/lib/commonjs/persistence/mmkv-store.js.map +1 -0
- package/lib/commonjs/persistence/snapshot.js +229 -0
- package/lib/commonjs/persistence/snapshot.js.map +1 -0
- package/lib/commonjs/react/PaymentProvider.js +96 -0
- package/lib/commonjs/react/PaymentProvider.js.map +1 -0
- package/lib/commonjs/react/hooks.js +202 -0
- package/lib/commonjs/react/hooks.js.map +1 -0
- package/lib/commonjs/testing/conformance.js +136 -0
- package/lib/commonjs/testing/conformance.js.map +1 -0
- package/lib/commonjs/testing/fakeStore.js +281 -0
- package/lib/commonjs/testing/fakeStore.js.map +1 -0
- package/lib/commonjs/testing/index.js +45 -0
- package/lib/commonjs/testing/index.js.map +1 -0
- package/lib/module/adapter/createStoreProvider.js +106 -0
- package/lib/module/adapter/createStoreProvider.js.map +1 -0
- package/lib/module/adapter/iap-adapter.js +383 -0
- package/lib/module/adapter/iap-adapter.js.map +1 -0
- package/lib/module/adapter/iapShapes.js +160 -0
- package/lib/module/adapter/iapShapes.js.map +1 -0
- package/lib/module/adapter/port.js +4 -0
- package/lib/module/adapter/port.js.map +1 -0
- package/lib/module/config/types.js +2 -0
- package/lib/module/config/types.js.map +1 -0
- package/lib/module/config/validate.js +175 -0
- package/lib/module/config/validate.js.map +1 -0
- package/lib/module/core/mutex.js +22 -0
- package/lib/module/core/mutex.js.map +1 -0
- package/lib/module/core/retry.js +33 -0
- package/lib/module/core/retry.js.map +1 -0
- package/lib/module/core/timers.js +198 -0
- package/lib/module/core/timers.js.map +1 -0
- package/lib/module/diagnostics/errors.js +76 -0
- package/lib/module/diagnostics/errors.js.map +1 -0
- package/lib/module/diagnostics/events.js +117 -0
- package/lib/module/diagnostics/events.js.map +1 -0
- package/lib/module/diagnostics/messageKeys.js +64 -0
- package/lib/module/diagnostics/messageKeys.js.map +1 -0
- package/lib/module/engine/connection.js +185 -0
- package/lib/module/engine/connection.js.map +1 -0
- package/lib/module/engine/engine.js +274 -0
- package/lib/module/engine/engine.js.map +1 -0
- package/lib/module/engine/finalize.js +322 -0
- package/lib/module/engine/finalize.js.map +1 -0
- package/lib/module/engine/foreground.js +21 -0
- package/lib/module/engine/foreground.js.map +1 -0
- package/lib/module/engine/internals.js +26 -0
- package/lib/module/engine/internals.js.map +1 -0
- package/lib/module/engine/pending.js +95 -0
- package/lib/module/engine/pending.js.map +1 -0
- package/lib/module/engine/products.js +195 -0
- package/lib/module/engine/products.js.map +1 -0
- package/lib/module/engine/purchase.js +390 -0
- package/lib/module/engine/purchase.js.map +1 -0
- package/lib/module/engine/purchaseFailure.js +72 -0
- package/lib/module/engine/purchaseFailure.js.map +1 -0
- package/lib/module/engine/receiptValidation.js +87 -0
- package/lib/module/engine/receiptValidation.js.map +1 -0
- package/lib/module/engine/state.js +196 -0
- package/lib/module/engine/state.js.map +1 -0
- package/lib/module/engine/subscriptions.js +119 -0
- package/lib/module/engine/subscriptions.js.map +1 -0
- package/lib/module/engine/sync.js +158 -0
- package/lib/module/engine/sync.js.map +1 -0
- package/lib/module/entitlements/aggregate.js +204 -0
- package/lib/module/entitlements/aggregate.js.map +1 -0
- package/lib/module/entitlements/mapping.js +35 -0
- package/lib/module/entitlements/mapping.js.map +1 -0
- package/lib/module/entitlements/policy.js +104 -0
- package/lib/module/entitlements/policy.js.map +1 -0
- package/lib/module/index.js +59 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/persistence/mmkv-store.js +144 -0
- package/lib/module/persistence/mmkv-store.js.map +1 -0
- package/lib/module/persistence/snapshot.js +221 -0
- package/lib/module/persistence/snapshot.js.map +1 -0
- package/lib/module/react/PaymentProvider.js +90 -0
- package/lib/module/react/PaymentProvider.js.map +1 -0
- package/lib/module/react/hooks.js +192 -0
- package/lib/module/react/hooks.js.map +1 -0
- package/lib/module/testing/conformance.js +130 -0
- package/lib/module/testing/conformance.js.map +1 -0
- package/lib/module/testing/fakeStore.js +276 -0
- package/lib/module/testing/fakeStore.js.map +1 -0
- package/lib/module/testing/index.js +13 -0
- package/lib/module/testing/index.js.map +1 -0
- package/lib/typescript/commonjs/adapter/createStoreProvider.d.ts +45 -0
- package/lib/typescript/commonjs/adapter/createStoreProvider.d.ts.map +1 -0
- package/lib/typescript/commonjs/adapter/iap-adapter.d.ts +80 -0
- package/lib/typescript/commonjs/adapter/iap-adapter.d.ts.map +1 -0
- package/lib/typescript/commonjs/adapter/iapShapes.d.ts +94 -0
- package/lib/typescript/commonjs/adapter/iapShapes.d.ts.map +1 -0
- package/lib/typescript/commonjs/adapter/port.d.ts +184 -0
- package/lib/typescript/commonjs/adapter/port.d.ts.map +1 -0
- package/lib/typescript/commonjs/config/types.d.ts +326 -0
- package/lib/typescript/commonjs/config/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/config/validate.d.ts +63 -0
- package/lib/typescript/commonjs/config/validate.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/mutex.d.ts +17 -0
- package/lib/typescript/commonjs/core/mutex.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/retry.d.ts +19 -0
- package/lib/typescript/commonjs/core/retry.d.ts.map +1 -0
- package/lib/typescript/commonjs/core/timers.d.ts +142 -0
- package/lib/typescript/commonjs/core/timers.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/errors.d.ts +55 -0
- package/lib/typescript/commonjs/diagnostics/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/events.d.ts +158 -0
- package/lib/typescript/commonjs/diagnostics/events.d.ts.map +1 -0
- package/lib/typescript/commonjs/diagnostics/messageKeys.d.ts +54 -0
- package/lib/typescript/commonjs/diagnostics/messageKeys.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/connection.d.ts +30 -0
- package/lib/typescript/commonjs/engine/connection.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/engine.d.ts +93 -0
- package/lib/typescript/commonjs/engine/engine.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/finalize.d.ts +53 -0
- package/lib/typescript/commonjs/engine/finalize.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/foreground.d.ts +15 -0
- package/lib/typescript/commonjs/engine/foreground.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/internals.d.ts +28 -0
- package/lib/typescript/commonjs/engine/internals.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/pending.d.ts +51 -0
- package/lib/typescript/commonjs/engine/pending.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/products.d.ts +82 -0
- package/lib/typescript/commonjs/engine/products.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/purchase.d.ts +61 -0
- package/lib/typescript/commonjs/engine/purchase.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/purchaseFailure.d.ts +15 -0
- package/lib/typescript/commonjs/engine/purchaseFailure.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/receiptValidation.d.ts +42 -0
- package/lib/typescript/commonjs/engine/receiptValidation.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/state.d.ts +46 -0
- package/lib/typescript/commonjs/engine/state.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/subscriptions.d.ts +61 -0
- package/lib/typescript/commonjs/engine/subscriptions.d.ts.map +1 -0
- package/lib/typescript/commonjs/engine/sync.d.ts +36 -0
- package/lib/typescript/commonjs/engine/sync.d.ts.map +1 -0
- package/lib/typescript/commonjs/entitlements/aggregate.d.ts +97 -0
- package/lib/typescript/commonjs/entitlements/aggregate.d.ts.map +1 -0
- package/lib/typescript/commonjs/entitlements/mapping.d.ts +27 -0
- package/lib/typescript/commonjs/entitlements/mapping.d.ts.map +1 -0
- package/lib/typescript/commonjs/entitlements/policy.d.ts +75 -0
- package/lib/typescript/commonjs/entitlements/policy.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +44 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/persistence/mmkv-store.d.ts +45 -0
- package/lib/typescript/commonjs/persistence/mmkv-store.d.ts.map +1 -0
- package/lib/typescript/commonjs/persistence/snapshot.d.ts +106 -0
- package/lib/typescript/commonjs/persistence/snapshot.d.ts.map +1 -0
- package/lib/typescript/commonjs/react/PaymentProvider.d.ts +57 -0
- package/lib/typescript/commonjs/react/PaymentProvider.d.ts.map +1 -0
- package/lib/typescript/commonjs/react/hooks.d.ts +80 -0
- package/lib/typescript/commonjs/react/hooks.d.ts.map +1 -0
- package/lib/typescript/commonjs/testing/conformance.d.ts +41 -0
- package/lib/typescript/commonjs/testing/conformance.d.ts.map +1 -0
- package/lib/typescript/commonjs/testing/fakeStore.d.ts +69 -0
- package/lib/typescript/commonjs/testing/fakeStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/testing/index.d.ts +12 -0
- package/lib/typescript/commonjs/testing/index.d.ts.map +1 -0
- package/lib/typescript/module/adapter/createStoreProvider.d.ts +45 -0
- package/lib/typescript/module/adapter/createStoreProvider.d.ts.map +1 -0
- package/lib/typescript/module/adapter/iap-adapter.d.ts +80 -0
- package/lib/typescript/module/adapter/iap-adapter.d.ts.map +1 -0
- package/lib/typescript/module/adapter/iapShapes.d.ts +94 -0
- package/lib/typescript/module/adapter/iapShapes.d.ts.map +1 -0
- package/lib/typescript/module/adapter/port.d.ts +184 -0
- package/lib/typescript/module/adapter/port.d.ts.map +1 -0
- package/lib/typescript/module/config/types.d.ts +326 -0
- package/lib/typescript/module/config/types.d.ts.map +1 -0
- package/lib/typescript/module/config/validate.d.ts +63 -0
- package/lib/typescript/module/config/validate.d.ts.map +1 -0
- package/lib/typescript/module/core/mutex.d.ts +17 -0
- package/lib/typescript/module/core/mutex.d.ts.map +1 -0
- package/lib/typescript/module/core/retry.d.ts +19 -0
- package/lib/typescript/module/core/retry.d.ts.map +1 -0
- package/lib/typescript/module/core/timers.d.ts +142 -0
- package/lib/typescript/module/core/timers.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/errors.d.ts +55 -0
- package/lib/typescript/module/diagnostics/errors.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/events.d.ts +158 -0
- package/lib/typescript/module/diagnostics/events.d.ts.map +1 -0
- package/lib/typescript/module/diagnostics/messageKeys.d.ts +54 -0
- package/lib/typescript/module/diagnostics/messageKeys.d.ts.map +1 -0
- package/lib/typescript/module/engine/connection.d.ts +30 -0
- package/lib/typescript/module/engine/connection.d.ts.map +1 -0
- package/lib/typescript/module/engine/engine.d.ts +93 -0
- package/lib/typescript/module/engine/engine.d.ts.map +1 -0
- package/lib/typescript/module/engine/finalize.d.ts +53 -0
- package/lib/typescript/module/engine/finalize.d.ts.map +1 -0
- package/lib/typescript/module/engine/foreground.d.ts +15 -0
- package/lib/typescript/module/engine/foreground.d.ts.map +1 -0
- package/lib/typescript/module/engine/internals.d.ts +28 -0
- package/lib/typescript/module/engine/internals.d.ts.map +1 -0
- package/lib/typescript/module/engine/pending.d.ts +51 -0
- package/lib/typescript/module/engine/pending.d.ts.map +1 -0
- package/lib/typescript/module/engine/products.d.ts +82 -0
- package/lib/typescript/module/engine/products.d.ts.map +1 -0
- package/lib/typescript/module/engine/purchase.d.ts +61 -0
- package/lib/typescript/module/engine/purchase.d.ts.map +1 -0
- package/lib/typescript/module/engine/purchaseFailure.d.ts +15 -0
- package/lib/typescript/module/engine/purchaseFailure.d.ts.map +1 -0
- package/lib/typescript/module/engine/receiptValidation.d.ts +42 -0
- package/lib/typescript/module/engine/receiptValidation.d.ts.map +1 -0
- package/lib/typescript/module/engine/state.d.ts +46 -0
- package/lib/typescript/module/engine/state.d.ts.map +1 -0
- package/lib/typescript/module/engine/subscriptions.d.ts +61 -0
- package/lib/typescript/module/engine/subscriptions.d.ts.map +1 -0
- package/lib/typescript/module/engine/sync.d.ts +36 -0
- package/lib/typescript/module/engine/sync.d.ts.map +1 -0
- package/lib/typescript/module/entitlements/aggregate.d.ts +97 -0
- package/lib/typescript/module/entitlements/aggregate.d.ts.map +1 -0
- package/lib/typescript/module/entitlements/mapping.d.ts +27 -0
- package/lib/typescript/module/entitlements/mapping.d.ts.map +1 -0
- package/lib/typescript/module/entitlements/policy.d.ts +75 -0
- package/lib/typescript/module/entitlements/policy.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +44 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/persistence/mmkv-store.d.ts +45 -0
- package/lib/typescript/module/persistence/mmkv-store.d.ts.map +1 -0
- package/lib/typescript/module/persistence/snapshot.d.ts +106 -0
- package/lib/typescript/module/persistence/snapshot.d.ts.map +1 -0
- package/lib/typescript/module/react/PaymentProvider.d.ts +57 -0
- package/lib/typescript/module/react/PaymentProvider.d.ts.map +1 -0
- package/lib/typescript/module/react/hooks.d.ts +80 -0
- package/lib/typescript/module/react/hooks.d.ts.map +1 -0
- package/lib/typescript/module/testing/conformance.d.ts +41 -0
- package/lib/typescript/module/testing/conformance.d.ts.map +1 -0
- package/lib/typescript/module/testing/fakeStore.d.ts +69 -0
- package/lib/typescript/module/testing/fakeStore.d.ts.map +1 -0
- package/lib/typescript/module/testing/index.d.ts +12 -0
- package/lib/typescript/module/testing/index.d.ts.map +1 -0
- package/package.json +49 -13
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What a refused purchase means to the app (PAY-19).
|
|
5
|
+
*
|
|
6
|
+
* The store answers with a reason of its own; this turns it into the result the
|
|
7
|
+
* app sees, under the code it can branch on, so no store vocabulary leaks out.
|
|
8
|
+
* Split from the flow next door, which is about the sequence of calls.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const FAILURE_CODES = {
|
|
12
|
+
network_error: 'PAYMENT_NETWORK_ERROR',
|
|
13
|
+
unknown_product: 'PAYMENT_UNKNOWN_PRODUCT',
|
|
14
|
+
ineligible: 'PAYMENT_INELIGIBLE',
|
|
15
|
+
product_unavailable_in_region: 'PAYMENT_PRODUCT_UNAVAILABLE_IN_REGION'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Maps a provider failure onto the public vocabulary, so no store code leaks out. */
|
|
19
|
+
export function mapFailure(reason, productKey, status) {
|
|
20
|
+
switch (reason) {
|
|
21
|
+
case 'user_cancelled':
|
|
22
|
+
return {
|
|
23
|
+
kind: 'user_cancelled',
|
|
24
|
+
productKey
|
|
25
|
+
};
|
|
26
|
+
case 'already_owned':
|
|
27
|
+
return {
|
|
28
|
+
kind: 'already_owned',
|
|
29
|
+
productKey
|
|
30
|
+
};
|
|
31
|
+
case 'ineligible':
|
|
32
|
+
return {
|
|
33
|
+
kind: 'ineligible',
|
|
34
|
+
productKey
|
|
35
|
+
};
|
|
36
|
+
case 'unknown_product':
|
|
37
|
+
return {
|
|
38
|
+
kind: 'unknown_product',
|
|
39
|
+
productKey
|
|
40
|
+
};
|
|
41
|
+
case 'product_unavailable_in_region':
|
|
42
|
+
return {
|
|
43
|
+
kind: 'product_unavailable_in_region',
|
|
44
|
+
productKey
|
|
45
|
+
};
|
|
46
|
+
case 'not_ready':
|
|
47
|
+
// The engine's real status, not a guess (PAY-19).
|
|
48
|
+
return {
|
|
49
|
+
kind: 'not_ready',
|
|
50
|
+
status
|
|
51
|
+
};
|
|
52
|
+
case 'network_error':
|
|
53
|
+
return {
|
|
54
|
+
kind: 'network_error',
|
|
55
|
+
productKey
|
|
56
|
+
};
|
|
57
|
+
case 'timeout':
|
|
58
|
+
return {
|
|
59
|
+
kind: 'timeout',
|
|
60
|
+
productKey
|
|
61
|
+
};
|
|
62
|
+
case 'store_error':
|
|
63
|
+
return {
|
|
64
|
+
kind: 'store_error',
|
|
65
|
+
productKey,
|
|
66
|
+
code: 'PAYMENT_STORE_ERROR'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Plan-change parameters, which only `subscriptions.ts` supplies. */
|
|
72
|
+
//# sourceMappingURL=purchaseFailure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["FAILURE_CODES","network_error","unknown_product","ineligible","product_unavailable_in_region","mapFailure","reason","productKey","status","kind","code"],"sourceRoot":"../../../src","sources":["engine/purchaseFailure.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;AAKA,OAAO,MAAMA,aAAwE,GAAG;EACvFC,aAAa,EAAE,uBAAuB;EACtCC,eAAe,EAAE,yBAAyB;EAC1CC,UAAU,EAAE,oBAAoB;EAChCC,6BAA6B,EAAE;AAChC,CAAC;;AAED;AACA,OAAO,SAASC,UAAUA,CACzBC,MAA6B,EAC7BC,UAAsB,EACtBC,MAAyB,EACR;EACjB,QAAQF,MAAM;IACb,KAAK,gBAAgB;MACpB,OAAO;QAAEG,IAAI,EAAE,gBAAgB;QAAEF;MAAW,CAAC;IAC9C,KAAK,eAAe;MACnB,OAAO;QAAEE,IAAI,EAAE,eAAe;QAAEF;MAAW,CAAC;IAC7C,KAAK,YAAY;MAChB,OAAO;QAAEE,IAAI,EAAE,YAAY;QAAEF;MAAW,CAAC;IAC1C,KAAK,iBAAiB;MACrB,OAAO;QAAEE,IAAI,EAAE,iBAAiB;QAAEF;MAAW,CAAC;IAC/C,KAAK,+BAA+B;MACnC,OAAO;QAAEE,IAAI,EAAE,+BAA+B;QAAEF;MAAW,CAAC;IAC7D,KAAK,WAAW;MACf;MACA,OAAO;QAAEE,IAAI,EAAE,WAAW;QAAED;MAAO,CAAC;IACrC,KAAK,eAAe;MACnB,OAAO;QAAEC,IAAI,EAAE,eAAe;QAAEF;MAAW,CAAC;IAC7C,KAAK,SAAS;MACb,OAAO;QAAEE,IAAI,EAAE,SAAS;QAAEF;MAAW,CAAC;IACvC,KAAK,aAAa;MACjB,OAAO;QAAEE,IAAI,EAAE,aAAa;QAAEF,UAAU;QAAEG,IAAI,EAAE;MAAsB,CAAC;EACzE;AACD;;AAEA","ignoreList":[]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Asking the app's backend whether a purchase is real (FR-069a, FR-072b).
|
|
5
|
+
*
|
|
6
|
+
* The hook is optional and bounded by a timeout, and its answer decides whether
|
|
7
|
+
* an entitlement is granted — so what happens when it throws, times out, or is
|
|
8
|
+
* absent is the whole point of this module. Split from the flow next door,
|
|
9
|
+
* which is about the sequence of store calls.
|
|
10
|
+
*
|
|
11
|
+
* A hook that could not answer is never an implicit approval: the configured
|
|
12
|
+
* `onValidationUnavailable` decides, and the default refuses.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { withTimeout } from "../core/mutex.js";
|
|
16
|
+
import { PaymentError } from "../diagnostics/errors.js";
|
|
17
|
+
|
|
18
|
+
/** What the backend decided about one purchase. */
|
|
19
|
+
|
|
20
|
+
export function createReceiptValidation(deps) {
|
|
21
|
+
const {
|
|
22
|
+
resolved
|
|
23
|
+
} = deps;
|
|
24
|
+
const askHook = (hook, candidate) => withTimeout(hook(candidate), resolved.timeouts.validateMs, () => new PaymentError({
|
|
25
|
+
code: 'PAYMENT_VALIDATION_UNAVAILABLE',
|
|
26
|
+
message: 'The validation hook did not answer in time',
|
|
27
|
+
operation: 'validate',
|
|
28
|
+
retryable: true
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Runs the app's backend check, if configured.
|
|
33
|
+
*
|
|
34
|
+
* Called for newly completed purchases only (FR-069a) — sync and restore trust
|
|
35
|
+
* a decision already made, which is what keeps restore working while the
|
|
36
|
+
* backend is unreachable.
|
|
37
|
+
*/
|
|
38
|
+
const validate = async (purchase, productKey, correlationId) => {
|
|
39
|
+
const hook = deps.config.validateTransaction;
|
|
40
|
+
if (!hook) {
|
|
41
|
+
return {
|
|
42
|
+
outcome: 'approved'
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const entry = resolved.entries.get(productKey);
|
|
46
|
+
try {
|
|
47
|
+
const decision = await askHook(hook, {
|
|
48
|
+
productKey,
|
|
49
|
+
productType: entry?.productType ?? 'one_time_permanent',
|
|
50
|
+
verificationPayload: purchase.verificationPayload ?? '',
|
|
51
|
+
correlationId,
|
|
52
|
+
transactionId: purchase.transactionId,
|
|
53
|
+
platform: deps.platform
|
|
54
|
+
});
|
|
55
|
+
deps.emit({
|
|
56
|
+
type: 'validation_decided',
|
|
57
|
+
productKey,
|
|
58
|
+
decision: decision.approved ? 'approved' : 'rejected'
|
|
59
|
+
});
|
|
60
|
+
return decision.approved ? {
|
|
61
|
+
outcome: 'approved'
|
|
62
|
+
} : {
|
|
63
|
+
outcome: 'rejected',
|
|
64
|
+
reason: decision.reason
|
|
65
|
+
};
|
|
66
|
+
} catch {
|
|
67
|
+
// A hook that throws is unavailable, never an implicit approval (FR-072b).
|
|
68
|
+
const fallback = resolved.onValidationUnavailable;
|
|
69
|
+
deps.emit({
|
|
70
|
+
type: 'validation_unavailable',
|
|
71
|
+
productKey,
|
|
72
|
+
fallback
|
|
73
|
+
});
|
|
74
|
+
return fallback === 'grant_and_queue' ? {
|
|
75
|
+
outcome: 'granted_pending_revalidation'
|
|
76
|
+
} : {
|
|
77
|
+
outcome: 'rejected',
|
|
78
|
+
reason: 'validation_unavailable'
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
askHook,
|
|
84
|
+
validate
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=receiptValidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["withTimeout","PaymentError","createReceiptValidation","deps","resolved","askHook","hook","candidate","timeouts","validateMs","code","message","operation","retryable","validate","purchase","productKey","correlationId","config","validateTransaction","outcome","entry","entries","get","decision","productType","verificationPayload","transactionId","platform","emit","type","approved","reason","fallback","onValidationUnavailable"],"sourceRoot":"../../../src","sources":["engine/receiptValidation.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAKA,SAASA,WAAW,QAAQ,kBAAe;AAC3C,SAASC,YAAY,QAAQ,0BAAuB;;AASpD;;AAMA,OAAO,SAASC,uBAAuBA,CAACC,IAA2B,EAAE;EACpE,MAAM;IAAEC;EAAS,CAAC,GAAGD,IAAI;EAEzB,MAAME,OAAO,GAAGA,CACfC,IAAuD,EACvDC,SAA2E,KAE3EP,WAAW,CACVM,IAAI,CAACC,SAAS,CAAC,EACfH,QAAQ,CAACI,QAAQ,CAACC,UAAU,EAC5B,MACC,IAAIR,YAAY,CAAC;IAChBS,IAAI,EAAE,gCAAgC;IACtCC,OAAO,EAAE,4CAA4C;IACrDC,SAAS,EAAE,UAAU;IACrBC,SAAS,EAAE;EACZ,CAAC,CACH,CAAC;;EAEF;AACD;AACA;AACA;AACA;AACA;AACA;EACC,MAAMC,QAAQ,GAAG,MAAAA,CAChBC,QAA0B,EAC1BC,UAAsB,EACtBC,aAAqB,KAKjB;IACJ,MAAMX,IAAI,GAAGH,IAAI,CAACe,MAAM,CAACC,mBAAmB;IAC5C,IAAI,CAACb,IAAI,EAAE;MACV,OAAO;QAAEc,OAAO,EAAE;MAAW,CAAC;IAC/B;IAEA,MAAMC,KAAK,GAAGjB,QAAQ,CAACkB,OAAO,CAACC,GAAG,CAACP,UAAU,CAAC;IAC9C,IAAI;MACH,MAAMQ,QAAQ,GAAG,MAAMnB,OAAO,CAACC,IAAI,EAAE;QACpCU,UAAU;QACVS,WAAW,EAAEJ,KAAK,EAAEI,WAAW,IAAI,oBAAoB;QACvDC,mBAAmB,EAAEX,QAAQ,CAACW,mBAAmB,IAAI,EAAE;QACvDT,aAAa;QACbU,aAAa,EAAEZ,QAAQ,CAACY,aAAa;QACrCC,QAAQ,EAAEzB,IAAI,CAACyB;MAChB,CAAC,CAAC;MACFzB,IAAI,CAAC0B,IAAI,CAAC;QACTC,IAAI,EAAE,oBAAoB;QAC1Bd,UAAU;QACVQ,QAAQ,EAAEA,QAAQ,CAACO,QAAQ,GAAG,UAAU,GAAG;MAC5C,CAAC,CAAC;MACF,OAAOP,QAAQ,CAACO,QAAQ,GACrB;QAAEX,OAAO,EAAE;MAAW,CAAC,GACvB;QAAEA,OAAO,EAAE,UAAU;QAAEY,MAAM,EAAER,QAAQ,CAACQ;MAAO,CAAC;IACpD,CAAC,CAAC,MAAM;MACP;MACA,MAAMC,QAAQ,GAAG7B,QAAQ,CAAC8B,uBAAuB;MACjD/B,IAAI,CAAC0B,IAAI,CAAC;QAAEC,IAAI,EAAE,wBAAwB;QAAEd,UAAU;QAAEiB;MAAS,CAAC,CAAC;MACnE,OAAOA,QAAQ,KAAK,iBAAiB,GAClC;QAAEb,OAAO,EAAE;MAA+B,CAAC,GAC3C;QAAEA,OAAO,EAAE,UAAU;QAAEY,MAAM,EAAE;MAAyB,CAAC;IAC7D;EACD,CAAC;EAED,OAAO;IAAE3B,OAAO;IAAES;EAAS,CAAC;AAC7B","ignoreList":[]}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The engine's state and the one way it changes (PAY-10).
|
|
5
|
+
*
|
|
6
|
+
* Status, the persisted snapshot, entitlements and pending purchases live here
|
|
7
|
+
* with the listeners that hear about them. Every change goes through `commit`
|
|
8
|
+
* (the snapshot) or `setStatus`, so diffing and emission are written once:
|
|
9
|
+
* an entitlement is granted or revoked exactly once however many sources it has
|
|
10
|
+
* (FR-079), a renewal that changes nothing emits nothing (FR-068), and an
|
|
11
|
+
* unchanged map is the same object (PERF-03).
|
|
12
|
+
*/
|
|
13
|
+
import { createListenerSet } from '@chipmobilesdk/rn-core';
|
|
14
|
+
import { redact } from "../diagnostics/errors.js";
|
|
15
|
+
import { createEmitter } from "../diagnostics/events.js";
|
|
16
|
+
import { aggregate, diffEntitlements, initialEntitlements, shareUnchanged } from "../entitlements/aggregate.js";
|
|
17
|
+
import { emptySnapshot } from "../persistence/snapshot.js";
|
|
18
|
+
import { derivePending, pendingTransitions, samePending, UNKNOWN_PENDING } from "./pending.js";
|
|
19
|
+
export function createEngineState(config, resolved, provider, store, now) {
|
|
20
|
+
// A listener that throws is a consumer bug, so it goes to the app's own logger
|
|
21
|
+
// rather than back into the event stream (FR-080, FR-081).
|
|
22
|
+
const emitter = createEmitter((error, event) => {
|
|
23
|
+
config.logger?.error?.('payment event listener threw', {
|
|
24
|
+
eventType: event.type,
|
|
25
|
+
reason: redact(error)
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
const listenerOptions = {
|
|
29
|
+
onListenerError: error => config.logger?.error?.('payment listener threw', {
|
|
30
|
+
reason: redact(error)
|
|
31
|
+
})
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Why an entitlement that was held is held no more, from what its sources left
|
|
36
|
+
* behind (PAY-12): a backend rejection, a consumed item, an expired
|
|
37
|
+
* subscription — or, when the store simply stopped reporting it, a refund.
|
|
38
|
+
*/
|
|
39
|
+
function revokeReason(sources) {
|
|
40
|
+
const records = state.snapshot.purchases.filter(record => sources.includes(record.productKey));
|
|
41
|
+
if (records.some(record => record.validationState === 'rejected')) {
|
|
42
|
+
return 'validation_rejected';
|
|
43
|
+
}
|
|
44
|
+
if (records.some(record => record.state === 'consumed')) {
|
|
45
|
+
return 'consumed';
|
|
46
|
+
}
|
|
47
|
+
const at = now();
|
|
48
|
+
if (records.some(record => record.expiresAt !== undefined && record.expiresAt <= at)) {
|
|
49
|
+
return 'expired';
|
|
50
|
+
}
|
|
51
|
+
return 'revoked_by_store';
|
|
52
|
+
}
|
|
53
|
+
function publishPending(next) {
|
|
54
|
+
// Diffed: a sync that changes nothing is the common case, and an app
|
|
55
|
+
// rendering "payment pending" has no use for a re-render that says the same.
|
|
56
|
+
if (samePending(state.pending, next)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
state.pending = next;
|
|
60
|
+
state.pendingListeners.notify(next);
|
|
61
|
+
}
|
|
62
|
+
function recompute() {
|
|
63
|
+
if (state.disposed) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const previous = state.entitlements;
|
|
67
|
+
state.entitlements = shareUnchanged(previous, aggregate({
|
|
68
|
+
config: resolved,
|
|
69
|
+
snapshot: state.snapshot,
|
|
70
|
+
hasSynced: state.hasSynced,
|
|
71
|
+
reportsGraceAndHoldDistinctly: provider.capabilities().reportsGraceAndHoldDistinctly,
|
|
72
|
+
now: now()
|
|
73
|
+
}));
|
|
74
|
+
for (const change of diffEntitlements(previous, state.entitlements)) {
|
|
75
|
+
if (change.next === 'entitled') {
|
|
76
|
+
state.emit({
|
|
77
|
+
type: 'entitlement_granted',
|
|
78
|
+
entitlementKey: change.key,
|
|
79
|
+
sources: change.sources
|
|
80
|
+
});
|
|
81
|
+
} else if (change.previous === 'entitled') {
|
|
82
|
+
state.emit({
|
|
83
|
+
type: 'entitlement_revoked',
|
|
84
|
+
entitlementKey: change.key,
|
|
85
|
+
reason: revokeReason(previous[change.key]?.sources ?? [])
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (state.entitlements !== previous) {
|
|
90
|
+
state.entitlementListeners.notify(state.entitlements);
|
|
91
|
+
}
|
|
92
|
+
publishPending(derivePending({
|
|
93
|
+
config: resolved,
|
|
94
|
+
snapshot: state.snapshot,
|
|
95
|
+
hasSynced: state.hasSynced,
|
|
96
|
+
now: now()
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
const state = {
|
|
100
|
+
config,
|
|
101
|
+
resolved,
|
|
102
|
+
provider,
|
|
103
|
+
store,
|
|
104
|
+
now,
|
|
105
|
+
emitter,
|
|
106
|
+
statusListeners: createListenerSet(listenerOptions),
|
|
107
|
+
entitlementListeners: createListenerSet(listenerOptions),
|
|
108
|
+
pendingListeners: createListenerSet(listenerOptions),
|
|
109
|
+
disposed: false,
|
|
110
|
+
hasSynced: false,
|
|
111
|
+
snapshot: emptySnapshot(config.scopeToken),
|
|
112
|
+
entitlements: initialEntitlements(resolved),
|
|
113
|
+
status: {
|
|
114
|
+
kind: resolved.enabled ? 'not_started' : 'disabled',
|
|
115
|
+
blockedOperations: []
|
|
116
|
+
},
|
|
117
|
+
pending: UNKNOWN_PENDING,
|
|
118
|
+
emit(event) {
|
|
119
|
+
emitter.emit({
|
|
120
|
+
...event,
|
|
121
|
+
timestamp: event.timestamp ?? now()
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
setStatus(next) {
|
|
125
|
+
if (state.disposed) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
state.status = {
|
|
129
|
+
...state.status,
|
|
130
|
+
...next
|
|
131
|
+
};
|
|
132
|
+
state.emit({
|
|
133
|
+
type: 'initialization',
|
|
134
|
+
status: state.status.kind,
|
|
135
|
+
unavailableReason: state.status.unavailableReason,
|
|
136
|
+
retryable: state.status.retryable
|
|
137
|
+
});
|
|
138
|
+
state.statusListeners.notify(state.status);
|
|
139
|
+
},
|
|
140
|
+
commit(next) {
|
|
141
|
+
// Read before the swap: a transition is "pending before, not pending now",
|
|
142
|
+
// and only the commit sees both sides (FR-104).
|
|
143
|
+
const ended = pendingTransitions(state.snapshot, next, resolved, transactionId => provider.pendingEndReason?.(transactionId));
|
|
144
|
+
state.snapshot = next;
|
|
145
|
+
if (store.save(next) === false) {
|
|
146
|
+
// The write-intent-first guarantee (FR-042) did not hold for this write.
|
|
147
|
+
// Said out loud: the app learns its finalize queue may not survive a kill.
|
|
148
|
+
state.emit({
|
|
149
|
+
type: 'internal_error',
|
|
150
|
+
code: 'PAYMENT_STORAGE_UNAVAILABLE',
|
|
151
|
+
operation: 'persist'
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
// Before `recompute`, so `settled` precedes the `entitlement_granted` it
|
|
155
|
+
// causes (FR-102).
|
|
156
|
+
for (const transition of ended) {
|
|
157
|
+
state.emit({
|
|
158
|
+
type: 'purchase_pending_resolved',
|
|
159
|
+
productKey: transition.productKey,
|
|
160
|
+
outcome: transition.outcome
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
recompute();
|
|
164
|
+
},
|
|
165
|
+
hydrate() {
|
|
166
|
+
state.snapshot = store.load(config.scopeToken);
|
|
167
|
+
// A record with `lastSyncedAt` was written by a sync that succeeded, so it
|
|
168
|
+
// is a basis for asserting entitlement — and the only one a build with
|
|
169
|
+
// selling disabled will ever have (FR-012, FR-053).
|
|
170
|
+
if (state.snapshot.lastSyncedAt !== undefined) {
|
|
171
|
+
state.hasSynced = true;
|
|
172
|
+
}
|
|
173
|
+
if (!store.isDurable()) {
|
|
174
|
+
state.emit({
|
|
175
|
+
type: 'config_warning',
|
|
176
|
+
detail: 'Local storage unavailable; entitlements will not survive restart'
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
recompute();
|
|
180
|
+
},
|
|
181
|
+
forget() {
|
|
182
|
+
store.clearAll();
|
|
183
|
+
state.snapshot = emptySnapshot(config.scopeToken);
|
|
184
|
+
state.hasSynced = false;
|
|
185
|
+
// Back to `unknown`, not `not_entitled`: after a logout there is no basis
|
|
186
|
+
// for either claim until the next sync.
|
|
187
|
+
state.entitlements = initialEntitlements(resolved);
|
|
188
|
+
state.entitlementListeners.notify(state.entitlements);
|
|
189
|
+
// No `purchase_pending_resolved`: the records were discarded locally, not
|
|
190
|
+
// ended at the store.
|
|
191
|
+
publishPending(UNKNOWN_PENDING);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
return state;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["createListenerSet","redact","createEmitter","aggregate","diffEntitlements","initialEntitlements","shareUnchanged","emptySnapshot","derivePending","pendingTransitions","samePending","UNKNOWN_PENDING","createEngineState","config","resolved","provider","store","now","emitter","error","event","logger","eventType","type","reason","listenerOptions","onListenerError","revokeReason","sources","records","state","snapshot","purchases","filter","record","includes","productKey","some","validationState","at","expiresAt","undefined","publishPending","next","pending","pendingListeners","notify","recompute","disposed","previous","entitlements","hasSynced","reportsGraceAndHoldDistinctly","capabilities","change","emit","entitlementKey","key","entitlementListeners","statusListeners","scopeToken","status","kind","enabled","blockedOperations","timestamp","setStatus","unavailableReason","retryable","commit","ended","transactionId","pendingEndReason","save","code","operation","transition","outcome","hydrate","load","lastSyncedAt","isDurable","detail","forget","clearAll"],"sourceRoot":"../../../src","sources":["engine/state.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Subscription plan changes and the route to the store's management screen (US12).
|
|
5
|
+
*
|
|
6
|
+
* Two platform rules drive this module:
|
|
7
|
+
*
|
|
8
|
+
* - **Proration is the app's decision, inside what the store permits** (FR-065).
|
|
9
|
+
* Whether a user who upgrades gets charged immediately, keeps their remaining
|
|
10
|
+
* time, or starts a fresh period is a product and pricing choice, so the
|
|
11
|
+
* package forwards it rather than picking.
|
|
12
|
+
* - **Cancellation belongs to the store.** Platforms forbid an app presenting its
|
|
13
|
+
* own cancellation screen, so this module offers only a route to the store's
|
|
14
|
+
* (FR-066) — there is deliberately no self-built alternative anywhere in the
|
|
15
|
+
* package.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { storeIdForProductKey } from "../entitlements/mapping.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* How already-paid time is handled when a plan changes.
|
|
22
|
+
*
|
|
23
|
+
* Named for the user-visible effect rather than mirroring a store's enum, so the
|
|
24
|
+
* same vocabulary will map onto StoreKit later (FR-004).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Store-specific replacement codes stay here, behind the neutral vocabulary above. */
|
|
28
|
+
const PRORATION_CODES = {
|
|
29
|
+
immediate_with_time_proration: 'WITH_TIME_PRORATION',
|
|
30
|
+
immediate_and_charge_full_price: 'CHARGE_FULL_PRICE',
|
|
31
|
+
immediate_and_charge_prorated_price: 'CHARGE_PRORATED_PRICE',
|
|
32
|
+
deferred: 'DEFERRED'
|
|
33
|
+
};
|
|
34
|
+
export function createSubscriptions(deps) {
|
|
35
|
+
const {
|
|
36
|
+
resolved
|
|
37
|
+
} = deps;
|
|
38
|
+
return {
|
|
39
|
+
async changePlan(fromProductKey, toProductKey, mode, options) {
|
|
40
|
+
if (!deps.provider.capabilities().supportsPlanChange) {
|
|
41
|
+
return {
|
|
42
|
+
kind: 'unsupported'
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const target = resolved.entries.get(toProductKey);
|
|
46
|
+
if (!target) {
|
|
47
|
+
return {
|
|
48
|
+
kind: 'unknown_product',
|
|
49
|
+
productKey: toProductKey
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (!resolved.entries.has(fromProductKey)) {
|
|
53
|
+
return {
|
|
54
|
+
kind: 'unknown_product',
|
|
55
|
+
productKey: fromProductKey
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The store needs the *token* of the subscription being replaced, not its
|
|
60
|
+
// product id — that is how it knows which active purchase to migrate, and
|
|
61
|
+
// it is why a plan change needs a live local record rather than just two
|
|
62
|
+
// product keys.
|
|
63
|
+
const active = deps.snapshot().purchases.find(p => p.productKey === fromProductKey && p.productType === 'subscription' && p.state === 'purchased');
|
|
64
|
+
if (!active) {
|
|
65
|
+
// No subscription to replace. Treated as a plain purchase would be
|
|
66
|
+
// surprising — the caller asked to *change* something that is not there,
|
|
67
|
+
// which almost always means its own state is stale.
|
|
68
|
+
return {
|
|
69
|
+
kind: 'already_owned',
|
|
70
|
+
productKey: fromProductKey
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Routed through the normal purchase flow on purpose: a plan change is a
|
|
75
|
+
// purchase, so it must inherit the single-flight lock, the environment
|
|
76
|
+
// guard, validation, and the finalization queue rather than reimplementing
|
|
77
|
+
// any of them.
|
|
78
|
+
return deps.purchase(toProductKey, {
|
|
79
|
+
...options,
|
|
80
|
+
replaceProductKey: fromProductKey,
|
|
81
|
+
prorationMode: PRORATION_CODES[mode]
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
async openManagement(productKey) {
|
|
85
|
+
const storeProductId = productKey ? storeIdForProductKey(resolved, productKey) : undefined;
|
|
86
|
+
// No-ops rather than throwing where the platform has no such screen: this
|
|
87
|
+
// is wired to a button, and a button that crashes is worse than one that
|
|
88
|
+
// does nothing on a platform that cannot honour it.
|
|
89
|
+
await deps.provider.openSubscriptionManagement(storeProductId);
|
|
90
|
+
},
|
|
91
|
+
async confirmPriceChange(productKey) {
|
|
92
|
+
if (!deps.provider.capabilities().supportsPriceChangeConfirmation) {
|
|
93
|
+
return {
|
|
94
|
+
kind: 'unsupported'
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const storeProductId = storeIdForProductKey(resolved, productKey);
|
|
98
|
+
if (!storeProductId) {
|
|
99
|
+
return {
|
|
100
|
+
kind: 'failed',
|
|
101
|
+
code: 'PAYMENT_UNKNOWN_PRODUCT'
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const outcome = await deps.provider.confirmPriceChange(storeProductId);
|
|
105
|
+
if (!outcome.ok) {
|
|
106
|
+
return {
|
|
107
|
+
kind: 'failed',
|
|
108
|
+
code: 'PAYMENT_STORE_ERROR'
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return outcome.confirmed ? {
|
|
112
|
+
kind: 'confirmed'
|
|
113
|
+
} : {
|
|
114
|
+
kind: 'dismissed'
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=subscriptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["storeIdForProductKey","PRORATION_CODES","immediate_with_time_proration","immediate_and_charge_full_price","immediate_and_charge_prorated_price","deferred","createSubscriptions","deps","resolved","changePlan","fromProductKey","toProductKey","mode","options","provider","capabilities","supportsPlanChange","kind","target","entries","get","productKey","has","active","snapshot","purchases","find","p","productType","state","purchase","replaceProductKey","prorationMode","openManagement","storeProductId","undefined","openSubscriptionManagement","confirmPriceChange","supportsPriceChangeConfirmation","code","outcome","ok","confirmed"],"sourceRoot":"../../../src","sources":["engine/subscriptions.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Syncing with the store, and the one translation from a store purchase to a
|
|
5
|
+
* persisted record (PAY-10).
|
|
6
|
+
*
|
|
7
|
+
* The purchase flow and sync both build records here, so they cannot disagree
|
|
8
|
+
* about what a transaction still owes: a disagreement is how a paid purchase
|
|
9
|
+
* goes unacknowledged and is refunded three days later.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { PaymentError } from "../diagnostics/errors.js";
|
|
13
|
+
import { productKeyForStoreId } from "../entitlements/mapping.js";
|
|
14
|
+
import { createRateLimiter, createSingleFlight, withTimeout } from "../core/mutex.js";
|
|
15
|
+
import { resolveFinalizeState } from "../persistence/snapshot.js";
|
|
16
|
+
/** The persisted record for one store purchase, keeping what we already knew about it. */
|
|
17
|
+
export function toPersistedRecord(resolved, purchase, productKey, existing) {
|
|
18
|
+
return {
|
|
19
|
+
transactionId: purchase.transactionId,
|
|
20
|
+
productKey,
|
|
21
|
+
productType: resolved.entries.get(productKey)?.productType ?? 'one_time_permanent',
|
|
22
|
+
token: purchase.token,
|
|
23
|
+
state: purchase.state === 'pending' ? 'pending' : 'purchased',
|
|
24
|
+
purchasedAt: purchase.purchasedAt,
|
|
25
|
+
// Finalize progress survives: a sync must never reset a queued
|
|
26
|
+
// acknowledgement and lose the paid transaction.
|
|
27
|
+
finalizeState: resolveFinalizeState(purchase, existing),
|
|
28
|
+
attempts: existing?.attempts ?? 0,
|
|
29
|
+
validationState: existing?.validationState ?? 'not_required',
|
|
30
|
+
expiresAt: purchase.expiresAt,
|
|
31
|
+
willAutoRenew: purchase.willAutoRenew
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Store purchases as records, dropping products this catalog does not know. */
|
|
36
|
+
function toPersisted(resolved, purchases, known) {
|
|
37
|
+
const records = [];
|
|
38
|
+
for (const purchase of purchases) {
|
|
39
|
+
// Legitimately possible: a user owns something from a build whose catalog
|
|
40
|
+
// has since changed. Ignored rather than treated as impossible.
|
|
41
|
+
const productKey = productKeyForStoreId(resolved, purchase.storeProductId);
|
|
42
|
+
if (!productKey || !resolved.entries.has(productKey)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const existing = known.find(p => p.transactionId === purchase.transactionId);
|
|
46
|
+
if (purchase.state === 'unspecified') {
|
|
47
|
+
// No basis for granting, finalizing or ending anything: a new transaction
|
|
48
|
+
// waits until the store says what it is, a known one keeps what we knew.
|
|
49
|
+
if (existing) {
|
|
50
|
+
records.push(existing);
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
records.push(toPersistedRecord(resolved, purchase, productKey, existing));
|
|
55
|
+
}
|
|
56
|
+
return records;
|
|
57
|
+
}
|
|
58
|
+
function purchasedIds(records) {
|
|
59
|
+
return new Set(records.filter(p => p.state === 'purchased').map(p => p.transactionId));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `settle` runs after every successful sync, outside the sync flight: the finalize
|
|
63
|
+
* drain and backend revalidation (FR-039, FR-042, FR-072a). Inside the flight, one
|
|
64
|
+
* store call or hook that never answered held it, and every later sync joined
|
|
65
|
+
* that stuck promise for the rest of the session.
|
|
66
|
+
*/
|
|
67
|
+
export function createSync(state, settle) {
|
|
68
|
+
const {
|
|
69
|
+
resolved,
|
|
70
|
+
provider,
|
|
71
|
+
now
|
|
72
|
+
} = state;
|
|
73
|
+
const syncFlight = createSingleFlight();
|
|
74
|
+
const settleFlight = createSingleFlight();
|
|
75
|
+
const foregroundLimiter = createRateLimiter(resolved.foregroundSyncMinIntervalMs, now);
|
|
76
|
+
async function querySnapshot(trigger) {
|
|
77
|
+
try {
|
|
78
|
+
const owned = await withTimeout(provider.queryOwned(), resolved.timeouts.syncMs, () => new PaymentError({
|
|
79
|
+
code: 'PAYMENT_TIMEOUT',
|
|
80
|
+
message: 'Syncing entitlements timed out',
|
|
81
|
+
operation: 'sync',
|
|
82
|
+
retryable: true
|
|
83
|
+
}));
|
|
84
|
+
const before = purchasedIds(state.snapshot.purchases);
|
|
85
|
+
const records = toPersisted(resolved, owned, state.snapshot.purchases);
|
|
86
|
+
const after = purchasedIds(records);
|
|
87
|
+
// A transaction the store no longer reports is revoked — a refund. The
|
|
88
|
+
// cache must not resurrect it (FR-064, FR-055).
|
|
89
|
+
const revokedCount = [...before].filter(id => !after.has(id)).length;
|
|
90
|
+
const restoredCount = [...after].filter(id => !before.has(id)).length;
|
|
91
|
+
state.hasSynced = true;
|
|
92
|
+
state.commit({
|
|
93
|
+
...state.snapshot,
|
|
94
|
+
purchases: records,
|
|
95
|
+
lastSyncedAt: now()
|
|
96
|
+
});
|
|
97
|
+
state.emit({
|
|
98
|
+
type: 'sync_completed',
|
|
99
|
+
restoredCount,
|
|
100
|
+
revokedCount,
|
|
101
|
+
trigger
|
|
102
|
+
});
|
|
103
|
+
return {
|
|
104
|
+
ok: true,
|
|
105
|
+
restoredCount,
|
|
106
|
+
revokedCount
|
|
107
|
+
};
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const paymentError = error instanceof PaymentError ? error : new PaymentError({
|
|
110
|
+
code: 'PAYMENT_STORE_ERROR',
|
|
111
|
+
message: 'Sync failed',
|
|
112
|
+
operation: 'sync',
|
|
113
|
+
retryable: true
|
|
114
|
+
});
|
|
115
|
+
// A failed sync leaves the previous snapshot in place, so offline
|
|
116
|
+
// entitlements survive rather than being wiped (FR-053).
|
|
117
|
+
state.emit({
|
|
118
|
+
type: 'internal_error',
|
|
119
|
+
code: paymentError.code,
|
|
120
|
+
operation: 'sync'
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
error: paymentError
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
async sync(trigger = 'manual') {
|
|
130
|
+
if (state.disposed || !resolved.enabled) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
error: new PaymentError({
|
|
134
|
+
code: 'PAYMENT_DISABLED',
|
|
135
|
+
message: 'Payments are disabled',
|
|
136
|
+
operation: 'sync'
|
|
137
|
+
})
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Rejected rather than deferred: the next foreground event tries again,
|
|
141
|
+
// whereas queueing would fire a burst once the interval elapsed (PERF-002).
|
|
142
|
+
if (trigger === 'foreground' && !foregroundLimiter.tryAcquire()) {
|
|
143
|
+
return {
|
|
144
|
+
ok: true,
|
|
145
|
+
restoredCount: 0,
|
|
146
|
+
revokedCount: 0
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// Concurrent callers join one flight: one store request (FR-063).
|
|
150
|
+
const outcome = await syncFlight.run(() => querySnapshot(trigger));
|
|
151
|
+
if (outcome.ok) {
|
|
152
|
+
await settleFlight.run(settle);
|
|
153
|
+
}
|
|
154
|
+
return outcome;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=sync.js.map
|