@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 +113 -1
- package/README.md +885 -761
- package/package.json +54 -54
- package/skills/sdk-rn-payment/SKILL.md +33 -6
- package/src/adapter/createStoreProvider.ts +117 -117
- package/src/adapter/iap-adapter.ts +547 -516
- package/src/adapter/port.ts +12 -0
- package/src/config/types.ts +40 -0
- package/src/diagnostics/events.ts +9 -0
- package/src/engine/engine.ts +951 -854
- package/src/engine/finalize.ts +298 -298
- package/src/engine/pending.ts +118 -0
- package/src/engine/products.ts +259 -259
- package/src/engine/purchase.ts +526 -495
- package/src/engine/subscriptions.ts +157 -157
- package/src/entitlements/aggregate.ts +21 -4
- package/src/index.ts +4 -0
- package/src/persistence/mmkv-store.ts +16 -8
- package/src/react/hooks.ts +274 -238
- package/src/testing/fakeStore.ts +33 -0
package/package.json
CHANGED
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@chipmobilesdk/rn-payment",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"private": false,
|
|
5
|
-
"license": "UNLICENSED",
|
|
6
|
-
"description": "Reusable in-app payment and entitlement capability for ChipMobileSdk React Native apps",
|
|
7
|
-
"main": "src/index.ts",
|
|
8
|
-
"types": "src/index.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"types": "./src/index.ts",
|
|
12
|
-
"react-native": "./src/index.ts",
|
|
13
|
-
"default": "./src/index.ts"
|
|
14
|
-
},
|
|
15
|
-
"./testing": {
|
|
16
|
-
"types": "./src/testing/index.ts",
|
|
17
|
-
"react-native": "./src/testing/index.ts",
|
|
18
|
-
"default": "./src/testing/index.ts"
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
"files": [
|
|
22
|
-
"src/index.ts",
|
|
23
|
-
"src/adapter",
|
|
24
|
-
"src/config",
|
|
25
|
-
"src/core",
|
|
26
|
-
"src/diagnostics",
|
|
27
|
-
"src/engine",
|
|
28
|
-
"src/entitlements",
|
|
29
|
-
"src/persistence",
|
|
30
|
-
"src/react",
|
|
31
|
-
"src/testing",
|
|
32
|
-
"skills",
|
|
33
|
-
"README.md",
|
|
34
|
-
"CHANGELOG.md"
|
|
35
|
-
],
|
|
36
|
-
"sideEffects": false,
|
|
37
|
-
"scripts": {
|
|
38
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
39
|
-
"pack:dry-run": "npm pack --dry-run"
|
|
40
|
-
},
|
|
41
|
-
"peerDependencies": {
|
|
42
|
-
"react": ">=19",
|
|
43
|
-
"react-native": ">=0.85",
|
|
44
|
-
"react-native-iap": ">=16.6.1 <17",
|
|
45
|
-
"react-native-mmkv": ">=4.0.0",
|
|
46
|
-
"react-native-nitro-modules": ">=0.36.5"
|
|
47
|
-
},
|
|
48
|
-
"keywords": [
|
|
49
|
-
"react-native",
|
|
50
|
-
"in-app-purchase",
|
|
51
|
-
"subscriptions",
|
|
52
|
-
"entitlements"
|
|
53
|
-
]
|
|
54
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@chipmobilesdk/rn-payment",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"description": "Reusable in-app payment and entitlement capability for ChipMobileSdk React Native apps",
|
|
7
|
+
"main": "src/index.ts",
|
|
8
|
+
"types": "src/index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./src/index.ts",
|
|
12
|
+
"react-native": "./src/index.ts",
|
|
13
|
+
"default": "./src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"./testing": {
|
|
16
|
+
"types": "./src/testing/index.ts",
|
|
17
|
+
"react-native": "./src/testing/index.ts",
|
|
18
|
+
"default": "./src/testing/index.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src/index.ts",
|
|
23
|
+
"src/adapter",
|
|
24
|
+
"src/config",
|
|
25
|
+
"src/core",
|
|
26
|
+
"src/diagnostics",
|
|
27
|
+
"src/engine",
|
|
28
|
+
"src/entitlements",
|
|
29
|
+
"src/persistence",
|
|
30
|
+
"src/react",
|
|
31
|
+
"src/testing",
|
|
32
|
+
"skills",
|
|
33
|
+
"README.md",
|
|
34
|
+
"CHANGELOG.md"
|
|
35
|
+
],
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"scripts": {
|
|
38
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
39
|
+
"pack:dry-run": "npm pack --dry-run"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"react": ">=19",
|
|
43
|
+
"react-native": ">=0.85",
|
|
44
|
+
"react-native-iap": ">=16.6.1 <17",
|
|
45
|
+
"react-native-mmkv": ">=4.0.0",
|
|
46
|
+
"react-native-nitro-modules": ">=0.36.5"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"react-native",
|
|
50
|
+
"in-app-purchase",
|
|
51
|
+
"subscriptions",
|
|
52
|
+
"entitlements"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sdk-rn-payment
|
|
3
|
-
description: "In-app payments via @chipmobilesdk/rn-payment: Play Billing one-time/consumable/subscription behind a store-neutral contract, entitlement keys with a required unknown state, guaranteed transaction finalization, per-environment production-transaction blocking. Use when selling a feature, gating on an entitlement, debugging a purchase that returns environment_blocked or an entitlement stuck at unknown, mapping Play products to entitlement keys, or answering a Data safety question about purchases."
|
|
3
|
+
description: "In-app payments via @chipmobilesdk/rn-payment: Play Billing one-time/consumable/subscription behind a store-neutral contract, entitlement keys with a required unknown state, pending purchases visible across sessions, guaranteed transaction finalization, per-environment production-transaction blocking. Use when selling a feature, gating on an entitlement, showing a payment still pending after a restart, debugging a purchase that returns environment_blocked or an entitlement stuck at unknown, mapping Play products to entitlement keys, or answering a Data safety question about purchases."
|
|
4
4
|
metadata:
|
|
5
5
|
package: "@chipmobilesdk/rn-payment"
|
|
6
|
-
packageVersion: "0.1
|
|
6
|
+
packageVersion: "0.2.1"
|
|
7
7
|
user-invocable: true
|
|
8
8
|
disable-model-invocation: false
|
|
9
9
|
---
|
|
@@ -17,6 +17,7 @@ the package owns the transaction. Consuming code never imports the billing libra
|
|
|
17
17
|
## Use this when
|
|
18
18
|
|
|
19
19
|
- Selling a feature, or gating one on an entitlement
|
|
20
|
+
- Showing "payment pending" — in this session or after a restart — and withholding Buy
|
|
20
21
|
- Mapping Play product ids to entitlement keys
|
|
21
22
|
- Debugging a purchase that returns `environment_blocked`, or an entitlement stuck at
|
|
22
23
|
`unknown`
|
|
@@ -49,6 +50,17 @@ feature locked for a second. It is also **re-entrant** — a cached answer retur
|
|
|
49
50
|
`unknown` past its staleness ceiling (7 days by default), so it is not merely a startup
|
|
50
51
|
flicker. See `## The entitlement model`.
|
|
51
52
|
|
|
53
|
+
## Pending is observable across sessions — never keep your own marker
|
|
54
|
+
|
|
55
|
+
New in 0.2.0. `usePendingPurchases(productKeys?)` (or `getPendingPurchases()` /
|
|
56
|
+
`subscribePendingPurchases()`) lists `{ productKey, since }` for payments still in flight,
|
|
57
|
+
read from the persisted snapshot, so it survives an app kill and an offline restart. Same
|
|
58
|
+
rule as entitlements: check `status` first — `unknown` is not "nothing pending". Show the
|
|
59
|
+
existing `payment.state.pending` message key and withhold Buy while listed.
|
|
60
|
+
`purchase_pending_resolved` (`settled` → then `entitlement_granted`, or `cancelled` /
|
|
61
|
+
`expired`) fires once when it ends. On Android an unpaid end is always `cancelled`: Play
|
|
62
|
+
does not say why. See `### A payment that stays pending` under `## Purchase flow`.
|
|
63
|
+
|
|
52
64
|
## A debug build cannot buy anything, and that is the guard working
|
|
53
65
|
|
|
54
66
|
`purchase()` returning `environment_blocked` in development is correct, not a bug. The
|
|
@@ -81,12 +93,13 @@ Unlike `@chipmobilesdk/rn-ads`, there is **no test-id substitution**: Play has n
|
|
|
81
93
|
Call `handleForeground()` yourself on app resume — the package will not guess what
|
|
82
94
|
"foreground" means for a host backgrounded by its own modal or the store's purchase sheet.
|
|
83
95
|
|
|
84
|
-
## Public API (v0.
|
|
96
|
+
## Public API (v0.2.0)
|
|
85
97
|
|
|
86
98
|
Runtime: `createEngine` (throws `PaymentConfigError` on a bad catalog or an unsafe
|
|
87
99
|
environment; everything else returns a typed result) · `createStoreProvider` ·
|
|
88
100
|
`PaymentProvider`, `usePaymentEngine` · `useEntitlement`, `useEntitlements`,
|
|
89
|
-
`usePaymentStatus`, `usePurchase`, `useProducts`, `useSync` ·
|
|
101
|
+
`usePendingPurchases`, `usePaymentStatus`, `usePurchase`, `useProducts`, `useSync` ·
|
|
102
|
+
`PaymentError`,
|
|
90
103
|
`PaymentConfigError`, `PAYMENT_ERROR_CODES` · `DEFAULT_MESSAGES`,
|
|
91
104
|
`DEFAULT_USER_MESSAGES`, `DEFAULT_DEVELOPER_MESSAGES`, `USER_MESSAGE_KEYS`,
|
|
92
105
|
`DEVELOPER_MESSAGE_KEYS`, `isUserMessageKey` · timeout/retry/cache defaults and bounds
|
|
@@ -95,14 +108,16 @@ environment; everything else returns a typed result) · `createStoreProvider` ·
|
|
|
95
108
|
doubles only).
|
|
96
109
|
|
|
97
110
|
Engine: `initialize`, `cleanup`, `getStatus`, `subscribeStatus`, `getEntitlement`,
|
|
98
|
-
`getAllEntitlements`, `subscribeEntitlements`, `
|
|
111
|
+
`getAllEntitlements`, `subscribeEntitlements`, `getPendingPurchases`,
|
|
112
|
+
`subscribePendingPurchases`, `purchase`, `consume`, `getUnfinalized`,
|
|
99
113
|
`getProducts`, `refreshProducts`, `changePlan`, `openSubscriptionManagement`,
|
|
100
114
|
`confirmPriceChange`, `sync`, `restore`, `getLastSyncedAt`, `clearLocalData`,
|
|
101
115
|
`handleForeground`, `subscribeEvents`.
|
|
102
116
|
|
|
103
117
|
Types: `PaymentConfig`, `CatalogEntry`, `ProductType`, `StoreProductIds`,
|
|
104
118
|
`EntitlementKey`, `ProductKey`, `EntitlementState`, `EntitlementStatus`,
|
|
105
|
-
`EntitlementMap`, `SubscriptionDetail`, `RenewalState`, `
|
|
119
|
+
`EntitlementMap`, `SubscriptionDetail`, `RenewalState`, `PendingPurchase`,
|
|
120
|
+
`PendingPurchasesState`, `PendingResolution`, `PurchaseResult`,
|
|
106
121
|
`PurchaseOptions`, `TransactionRef`, `FinalizeResult`, `UnfinalizedSummary`,
|
|
107
122
|
`ProductInfo`, `Offer`, `ProductQueryResult`, `MissingProduct`, `ProrationMode`,
|
|
108
123
|
`PriceChangeResult`, `PaymentStatus`, `PaymentStatusKind`, `UnavailableReason`,
|
|
@@ -112,6 +127,17 @@ Types: `PaymentConfig`, `CatalogEntry`, `ProductType`, `StoreProductIds`,
|
|
|
112
127
|
|
|
113
128
|
`cleanup()` is idempotent, and so is every `Unsubscribe`. After cleanup no event fires.
|
|
114
129
|
|
|
130
|
+
## Upgrading 0.1.0 → 0.2.0
|
|
131
|
+
|
|
132
|
+
One compile-time change: `PaymentEventType` gained `'purchase_pending_resolved'`, so an
|
|
133
|
+
exhaustive `switch` needs the case or a `default`. A hand-written `PaymentEngine` mock
|
|
134
|
+
needs the two pending methods. No storage migration — `0.1.0` already persisted pending
|
|
135
|
+
records. Replace any app-side "pending since" marker with `usePendingPurchases`.
|
|
136
|
+
|
|
137
|
+
**Upgrade before shipping on Android.** In `0.1.0` the adapter misread `react-native-iap`
|
|
138
|
+
16.x's string purchase state, so on a device a pending purchase was granted. `0.2.0`
|
|
139
|
+
reads it correctly, and an unrecognized (`unspecified`) state never grants or finalizes.
|
|
140
|
+
|
|
115
141
|
## Where the detail lives
|
|
116
142
|
|
|
117
143
|
Read one section of `node_modules/@chipmobilesdk/rn-payment/README.md`:
|
|
@@ -122,6 +148,7 @@ Read one section of `node_modules/@chipmobilesdk/rn-payment/README.md`:
|
|
|
122
148
|
| Catalog and Play Console product setup | `## Catalog and entitlement mapping` |
|
|
123
149
|
| Entitlement states and staleness | `## The entitlement model` |
|
|
124
150
|
| Every purchase outcome and how to handle it | `## Purchase flow` |
|
|
151
|
+
| A payment pending across sessions, and how it ends | `### A payment that stays pending` |
|
|
125
152
|
| Why acknowledgement is not yours | `## Finalization: the invariant you cannot opt out of` |
|
|
126
153
|
| Consumables | `## Consumables` |
|
|
127
154
|
| Grace/hold, plan change, cancellation | `## Subscriptions` |
|
|
@@ -1,117 +1,117 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Platform selection for the store adapter.
|
|
3
|
-
*
|
|
4
|
-
* This exists so the public surface never names a store. An app writes
|
|
5
|
-
* `createStoreProvider()`, not `createIapAdapter()` — the same call will pick the
|
|
6
|
-
* StoreKit adapter once iOS exists, and no consumer line changes (FR-002, FR-004,
|
|
7
|
-
* FR-007).
|
|
8
|
-
*
|
|
9
|
-
* On a platform with no adapter the result is a stub that reports
|
|
10
|
-
* `platform_unsupported` rather than throwing. That is what lets a host app start
|
|
11
|
-
* and run normally where selling is impossible, losing only the ability to sell
|
|
12
|
-
* (FR-021, FR-023).
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { Platform } from 'react-native';
|
|
16
|
-
import type { ProductType, Unsubscribe } from '../config/types';
|
|
17
|
-
import { createIapAdapter } from './iap-adapter';
|
|
18
|
-
import type {
|
|
19
|
-
ProviderCapabilities,
|
|
20
|
-
ProviderConnectOutcome,
|
|
21
|
-
ProviderPort,
|
|
22
|
-
ProviderPriceChangeOutcome,
|
|
23
|
-
ProviderProduct,
|
|
24
|
-
ProviderPurchase,
|
|
25
|
-
ProviderPurchaseOutcome,
|
|
26
|
-
} from './port';
|
|
27
|
-
|
|
28
|
-
const UNSUPPORTED_CAPABILITIES: ProviderCapabilities = {
|
|
29
|
-
supportsSubscriptions: false,
|
|
30
|
-
supportsPlanChange: false,
|
|
31
|
-
supportsPriceChangeConfirmation: false,
|
|
32
|
-
reportsGraceAndHoldDistinctly: false,
|
|
33
|
-
supportsExternalTransactions: false,
|
|
34
|
-
/** Nothing here can charge anyone, so the environment guard has nothing to block. */
|
|
35
|
-
createsRealTransactions: false,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* The no-adapter stub.
|
|
40
|
-
*
|
|
41
|
-
* Every operation fails safely and predictably. Notably `queryOwned` returns an
|
|
42
|
-
* empty list rather than throwing: the engine then computes `not_entitled` after a
|
|
43
|
-
* successful-but-empty sync, which is honest — on a platform with no store, the
|
|
44
|
-
* user genuinely holds nothing this package can verify.
|
|
45
|
-
*/
|
|
46
|
-
function createUnsupportedProvider(): ProviderPort {
|
|
47
|
-
return {
|
|
48
|
-
async connect(): Promise<ProviderConnectOutcome> {
|
|
49
|
-
return { ok: false, reason: 'platform_unsupported', retryable: false };
|
|
50
|
-
},
|
|
51
|
-
async disconnect(): Promise<void> {
|
|
52
|
-
// Nothing was ever connected.
|
|
53
|
-
},
|
|
54
|
-
observeConnection(): Unsubscribe {
|
|
55
|
-
return () => undefined;
|
|
56
|
-
},
|
|
57
|
-
async queryProducts(): Promise<ProviderProduct[]> {
|
|
58
|
-
return [];
|
|
59
|
-
},
|
|
60
|
-
async queryOwned(): Promise<ProviderPurchase[]> {
|
|
61
|
-
return [];
|
|
62
|
-
},
|
|
63
|
-
async launchPurchase(): Promise<ProviderPurchaseOutcome> {
|
|
64
|
-
return { ok: false, reason: 'not_ready', detail: 'platform_unsupported' };
|
|
65
|
-
},
|
|
66
|
-
observePurchases(): Unsubscribe {
|
|
67
|
-
return () => undefined;
|
|
68
|
-
},
|
|
69
|
-
async acknowledge(): Promise<void> {
|
|
70
|
-
// No transaction can exist to acknowledge.
|
|
71
|
-
},
|
|
72
|
-
async consume(): Promise<void> {
|
|
73
|
-
// As above.
|
|
74
|
-
},
|
|
75
|
-
async openSubscriptionManagement(): Promise<void> {
|
|
76
|
-
// No store screen to open.
|
|
77
|
-
},
|
|
78
|
-
async confirmPriceChange(): Promise<ProviderPriceChangeOutcome> {
|
|
79
|
-
return { ok: false, reason: 'not_ready' };
|
|
80
|
-
},
|
|
81
|
-
capabilities: () => UNSUPPORTED_CAPABILITIES,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface StoreProviderOptions {
|
|
86
|
-
/**
|
|
87
|
-
* Force the unsupported stub regardless of platform.
|
|
88
|
-
*
|
|
89
|
-
* Useful for an app that wants to ship one build with selling switched off at
|
|
90
|
-
* the provider level rather than through the `enabled` flag.
|
|
91
|
-
*/
|
|
92
|
-
forceUnsupported?: boolean;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* The platform's store adapter.
|
|
97
|
-
*
|
|
98
|
-
* iOS deliberately returns the stub today rather than throwing: the contract is
|
|
99
|
-
* prepared for StoreKit, the implementation is not, and an app running on iOS
|
|
100
|
-
* should degrade to "cannot sell here" instead of crashing.
|
|
101
|
-
*/
|
|
102
|
-
export function createStoreProvider(
|
|
103
|
-
options: StoreProviderOptions = {},
|
|
104
|
-
): ProviderPort {
|
|
105
|
-
if (options.forceUnsupported) {
|
|
106
|
-
return createUnsupportedProvider();
|
|
107
|
-
}
|
|
108
|
-
if (Platform.OS === 'android') {
|
|
109
|
-
return createIapAdapter();
|
|
110
|
-
}
|
|
111
|
-
return createUnsupportedProvider();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Exported for a test or a screen that needs the stub explicitly. */
|
|
115
|
-
export { createUnsupportedProvider };
|
|
116
|
-
|
|
117
|
-
export type { ProductType };
|
|
1
|
+
/**
|
|
2
|
+
* Platform selection for the store adapter.
|
|
3
|
+
*
|
|
4
|
+
* This exists so the public surface never names a store. An app writes
|
|
5
|
+
* `createStoreProvider()`, not `createIapAdapter()` — the same call will pick the
|
|
6
|
+
* StoreKit adapter once iOS exists, and no consumer line changes (FR-002, FR-004,
|
|
7
|
+
* FR-007).
|
|
8
|
+
*
|
|
9
|
+
* On a platform with no adapter the result is a stub that reports
|
|
10
|
+
* `platform_unsupported` rather than throwing. That is what lets a host app start
|
|
11
|
+
* and run normally where selling is impossible, losing only the ability to sell
|
|
12
|
+
* (FR-021, FR-023).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { Platform } from 'react-native';
|
|
16
|
+
import type { ProductType, Unsubscribe } from '../config/types';
|
|
17
|
+
import { createIapAdapter } from './iap-adapter';
|
|
18
|
+
import type {
|
|
19
|
+
ProviderCapabilities,
|
|
20
|
+
ProviderConnectOutcome,
|
|
21
|
+
ProviderPort,
|
|
22
|
+
ProviderPriceChangeOutcome,
|
|
23
|
+
ProviderProduct,
|
|
24
|
+
ProviderPurchase,
|
|
25
|
+
ProviderPurchaseOutcome,
|
|
26
|
+
} from './port';
|
|
27
|
+
|
|
28
|
+
const UNSUPPORTED_CAPABILITIES: ProviderCapabilities = {
|
|
29
|
+
supportsSubscriptions: false,
|
|
30
|
+
supportsPlanChange: false,
|
|
31
|
+
supportsPriceChangeConfirmation: false,
|
|
32
|
+
reportsGraceAndHoldDistinctly: false,
|
|
33
|
+
supportsExternalTransactions: false,
|
|
34
|
+
/** Nothing here can charge anyone, so the environment guard has nothing to block. */
|
|
35
|
+
createsRealTransactions: false,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The no-adapter stub.
|
|
40
|
+
*
|
|
41
|
+
* Every operation fails safely and predictably. Notably `queryOwned` returns an
|
|
42
|
+
* empty list rather than throwing: the engine then computes `not_entitled` after a
|
|
43
|
+
* successful-but-empty sync, which is honest — on a platform with no store, the
|
|
44
|
+
* user genuinely holds nothing this package can verify.
|
|
45
|
+
*/
|
|
46
|
+
function createUnsupportedProvider(): ProviderPort {
|
|
47
|
+
return {
|
|
48
|
+
async connect(): Promise<ProviderConnectOutcome> {
|
|
49
|
+
return { ok: false, reason: 'platform_unsupported', retryable: false };
|
|
50
|
+
},
|
|
51
|
+
async disconnect(): Promise<void> {
|
|
52
|
+
// Nothing was ever connected.
|
|
53
|
+
},
|
|
54
|
+
observeConnection(): Unsubscribe {
|
|
55
|
+
return () => undefined;
|
|
56
|
+
},
|
|
57
|
+
async queryProducts(): Promise<ProviderProduct[]> {
|
|
58
|
+
return [];
|
|
59
|
+
},
|
|
60
|
+
async queryOwned(): Promise<ProviderPurchase[]> {
|
|
61
|
+
return [];
|
|
62
|
+
},
|
|
63
|
+
async launchPurchase(): Promise<ProviderPurchaseOutcome> {
|
|
64
|
+
return { ok: false, reason: 'not_ready', detail: 'platform_unsupported' };
|
|
65
|
+
},
|
|
66
|
+
observePurchases(): Unsubscribe {
|
|
67
|
+
return () => undefined;
|
|
68
|
+
},
|
|
69
|
+
async acknowledge(): Promise<void> {
|
|
70
|
+
// No transaction can exist to acknowledge.
|
|
71
|
+
},
|
|
72
|
+
async consume(): Promise<void> {
|
|
73
|
+
// As above.
|
|
74
|
+
},
|
|
75
|
+
async openSubscriptionManagement(): Promise<void> {
|
|
76
|
+
// No store screen to open.
|
|
77
|
+
},
|
|
78
|
+
async confirmPriceChange(): Promise<ProviderPriceChangeOutcome> {
|
|
79
|
+
return { ok: false, reason: 'not_ready' };
|
|
80
|
+
},
|
|
81
|
+
capabilities: () => UNSUPPORTED_CAPABILITIES,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface StoreProviderOptions {
|
|
86
|
+
/**
|
|
87
|
+
* Force the unsupported stub regardless of platform.
|
|
88
|
+
*
|
|
89
|
+
* Useful for an app that wants to ship one build with selling switched off at
|
|
90
|
+
* the provider level rather than through the `enabled` flag.
|
|
91
|
+
*/
|
|
92
|
+
forceUnsupported?: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The platform's store adapter.
|
|
97
|
+
*
|
|
98
|
+
* iOS deliberately returns the stub today rather than throwing: the contract is
|
|
99
|
+
* prepared for StoreKit, the implementation is not, and an app running on iOS
|
|
100
|
+
* should degrade to "cannot sell here" instead of crashing.
|
|
101
|
+
*/
|
|
102
|
+
export function createStoreProvider(
|
|
103
|
+
options: StoreProviderOptions = {},
|
|
104
|
+
): ProviderPort {
|
|
105
|
+
if (options.forceUnsupported) {
|
|
106
|
+
return createUnsupportedProvider();
|
|
107
|
+
}
|
|
108
|
+
if (Platform.OS === 'android') {
|
|
109
|
+
return createIapAdapter();
|
|
110
|
+
}
|
|
111
|
+
return createUnsupportedProvider();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Exported for a test or a screen that needs the stub explicitly. */
|
|
115
|
+
export { createUnsupportedProvider };
|
|
116
|
+
|
|
117
|
+
export type { ProductType };
|