@nosslabs/iap 5.0.0 → 7.0.0-next.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 +43 -13
- package/README.md +7 -7
- package/dist/index.cjs +108 -233
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -7
- package/dist/index.d.ts +6 -7
- package/dist/index.js +108 -233
- package/dist/index.js.map +1 -1
- package/package.json +12 -11
package/CHANGELOG.md
CHANGED
|
@@ -5,22 +5,52 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [7.0.0-next.0] — 2026-05-14
|
|
9
|
+
|
|
10
|
+
First release of the **Capacitor 7+** line, published on the `@next`
|
|
11
|
+
npm dist-tag. The Capacitor 5 line continues as `5.x` on `@latest`
|
|
12
|
+
(from the `5.x` branch) — see [Migration](https://iap.nossdev.com/migration/).
|
|
13
|
+
|
|
14
|
+
Numbering: the library's major version tracks the Capacitor major it
|
|
15
|
+
targets (the convention `@capgo/native-purchases` and Ionic plugins
|
|
16
|
+
use). What was framed as `1.0.0-next.0` during development is published
|
|
17
|
+
as `7.0.0-next.0` for the same reason `5.0.0` superseded `0.4.0` on
|
|
18
|
+
the maintenance line — same code, version aligned with the platform.
|
|
9
19
|
|
|
10
20
|
### Changed
|
|
11
21
|
|
|
12
|
-
- **
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
- **BREAKING: dropped Capacitor 5 support.** The `7.x` line targets
|
|
23
|
+
**Capacitor 7+** (also runs on Capacitor 8) via
|
|
24
|
+
[`@capgo/native-purchases`](https://github.com/Cap-go/native-purchases),
|
|
25
|
+
replacing `cordova-plugin-purchase`. The native adapter now lives at
|
|
26
|
+
`src/adapters/native/capgo/native-adapter.ts` (`CapgoNativeAdapter`),
|
|
27
|
+
selected behind the same `NativeAdapter` interface as before.
|
|
28
|
+
- **Peer dependencies** are now `@capacitor/core`, `@capacitor/preferences`,
|
|
29
|
+
and (optional) `@capacitor/app` at `^7.0.0 || ^8.0.0`, plus
|
|
30
|
+
`@capgo/native-purchases` at `7.16.x || ^8.0.0`. `cordova-plugin-purchase`
|
|
31
|
+
is no longer a peer dependency. Migration is a peer-dep swap + `npx cap sync`
|
|
32
|
+
— no changes to your `createIAP({ ... })` config or any consumer code.
|
|
33
|
+
- **Acknowledgement defers on both platforms.** `@capgo/native-purchases`
|
|
34
|
+
supports `autoAcknowledgePurchases: false` on iOS and Android, so the
|
|
35
|
+
"never grant entitlement before the backend confirms" guarantee holds
|
|
36
|
+
with no iOS-specific finish-before-verify race.
|
|
37
|
+
- **Android user-cancellation surfaces as `status: 'failed'`** (not
|
|
38
|
+
`'cancelled'`). Google Play Billing — at the level `@capgo/native-purchases`
|
|
39
|
+
exposes — doesn't distinguish a user-cancelled flow from other purchase
|
|
40
|
+
failures; iOS still reports `'cancelled'` reliably. Treat `failed` on
|
|
41
|
+
Android the same as `cancelled` for UX. (The Capacitor 5 line via
|
|
42
|
+
`cordova-plugin-purchase` could distinguish this.)
|
|
43
|
+
|
|
44
|
+
### Unchanged
|
|
45
|
+
|
|
46
|
+
- Public API surface: `createIAP`, the `IAP` interface, all events, all
|
|
47
|
+
`IAPErrorCode` values, and every public type are identical to `5.0.0`
|
|
48
|
+
(= `0.4.0` code).
|
|
49
|
+
- The full `0.2`–`0.4` feature set carries forward: the options-object
|
|
50
|
+
`purchase()` signature, optional `appUserId` pre-attachment, the
|
|
51
|
+
`INVALID_APP_USER_ID` / `APP_USER_ID_FETCH_FAILED` error codes, the
|
|
52
|
+
`permanentErrorCodes` config, the `recovery-dropped-permanent` event,
|
|
53
|
+
and `RecoveryResult.droppedPermanent`.
|
|
24
54
|
|
|
25
55
|
## [0.4.0] — 2026-05-08
|
|
26
56
|
|
package/README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
> Thin Capacitor IAP orchestrator. Server-side validation via [Attesto](https://attesto.nossdev.com).
|
|
4
4
|
|
|
5
|
-
**Status: `
|
|
5
|
+
**Status: `7.0.0-next.0` — prerelease on the `@next` dist-tag** (the Capacitor 7+ line, built on `@capgo/native-purchases`). The Capacitor 5 line (`cordova-plugin-purchase`) continues as `5.x` on `@latest` (from the `5.x` branch). API may have breaking changes through the 7.x prerelease line; watch the [CHANGELOG](./CHANGELOG.md).
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @nosslabs/iap
|
|
8
|
+
npm install @nosslabs/iap@next @capgo/native-purchases
|
|
9
9
|
npx cap sync
|
|
10
10
|
```
|
|
11
11
|
|
|
@@ -65,9 +65,9 @@ await iap.purchase({
|
|
|
65
65
|
|
|
66
66
|
`@nosslabs/iap` does **one thing**: orchestrate the purchase flow on the client. It
|
|
67
67
|
|
|
68
|
-
- wraps `
|
|
68
|
+
- wraps [`@capgo/native-purchases`](https://github.com/Cap-go/native-purchases) for native purchase + restore,
|
|
69
69
|
- POSTs to **your** backend (which calls Attesto) for receipt validation,
|
|
70
|
-
- acknowledges native transactions only **after** the backend confirms
|
|
70
|
+
- acknowledges native transactions only **after** the backend confirms — `autoAcknowledgePurchases: false` defers finishing on **both** iOS and Android, so there's no phantom grant and no iOS finish-before-verify race,
|
|
71
71
|
- caches entitlements locally for instant, reactive UI reads,
|
|
72
72
|
- recovers unfinished transactions across app launches.
|
|
73
73
|
|
|
@@ -77,8 +77,8 @@ It does **not**: talk to Attesto directly, define entitlement business logic, ma
|
|
|
77
77
|
|
|
78
78
|
| `@nosslabs/iap` | Capacitor | Native plugin | dist-tag | Status |
|
|
79
79
|
|---|---|---|---|---|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
80
|
+
| 7.x | 7.x (also runs on 8.x) | `@capgo/native-purchases 7.16.x` (or `^8` on Cap 8) | `@next` | **Current (prerelease)** |
|
|
81
|
+
| 5.x | 5.x | `cordova-plugin-purchase ^13.x` | `@latest` | Maintenance |
|
|
82
82
|
|
|
83
83
|
## Optional peer dependency
|
|
84
84
|
|
|
@@ -94,7 +94,7 @@ Or disable the listener with `options.refreshOnResume: false`. See [installation
|
|
|
94
94
|
## Development
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
|
-
mise install # Node 22 + npm
|
|
97
|
+
mise install # Node 22 + npm 11 (pinned in mise.toml)
|
|
98
98
|
npm install
|
|
99
99
|
npm run typecheck # tsc --noEmit
|
|
100
100
|
npm run lint # biome check
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var core = require('@capacitor/core');
|
|
4
|
-
require('
|
|
4
|
+
var nativePurchases = require('@capgo/native-purchases');
|
|
5
5
|
var zod = require('zod');
|
|
6
6
|
var preferences = require('@capacitor/preferences');
|
|
7
7
|
|
|
@@ -85,7 +85,7 @@ var init_errors = __esm({
|
|
|
85
85
|
NOT_INITIALIZED: "Call iap.initialize() before this method, or recreate the instance after destroy().",
|
|
86
86
|
// Native plugin
|
|
87
87
|
PLATFORM_NOT_SUPPORTED: "In-app purchases run on iOS/Android only. Web is no-op by design \u2014 guard your purchase UI behind Capacitor.isNativePlatform().",
|
|
88
|
-
BILLING_NOT_AVAILABLE: "
|
|
88
|
+
BILLING_NOT_AVAILABLE: "The store billing service is unavailable. Confirm @capgo/native-purchases is installed and `npx cap sync` has run; check the device sandbox/test account is signed in.",
|
|
89
89
|
PRODUCT_NOT_FOUND: "Ensure the productId is registered in App Store Connect / Play Console AND in your createIAP({ products }) config.",
|
|
90
90
|
USER_CANCELLED: "No action needed \u2014 the user dismissed the native purchase sheet.",
|
|
91
91
|
PURCHASE_PENDING: "Android only: payment is awaiting external clearance (e.g. cash payment, bank verification). The backend will receive a Google RTDN webhook when it clears; call iap.refresh() afterward.",
|
|
@@ -139,295 +139,170 @@ var init_platform = __esm({
|
|
|
139
139
|
}
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
-
// src/adapters/native/
|
|
142
|
+
// src/adapters/native/capgo/native-adapter.ts
|
|
143
143
|
var native_adapter_exports = {};
|
|
144
144
|
__export(native_adapter_exports, {
|
|
145
|
-
|
|
145
|
+
CapgoNativeAdapter: () => CapgoNativeAdapter
|
|
146
146
|
});
|
|
147
|
-
function getCdv() {
|
|
148
|
-
const candidate = globalThis.CdvPurchase;
|
|
149
|
-
if (!candidate || !candidate.store) {
|
|
150
|
-
throw new exports.IAPError({
|
|
151
|
-
code: exports.IAPErrorCode.BILLING_NOT_AVAILABLE,
|
|
152
|
-
message: "cordova-plugin-purchase is not available. Ensure the plugin is installed and `npx cap sync` has run."
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
return candidate;
|
|
156
|
-
}
|
|
157
|
-
function currentCdvPlatform() {
|
|
158
|
-
const cdv = getCdv();
|
|
159
|
-
const platform = getPlatform();
|
|
160
|
-
if (platform === "android") return cdv.Platform.GOOGLE_PLAY;
|
|
161
|
-
return cdv.Platform.APPLE_APPSTORE;
|
|
162
|
-
}
|
|
163
|
-
function mapProductType(type) {
|
|
164
|
-
const cdv = getCdv();
|
|
165
|
-
switch (type) {
|
|
166
|
-
case "subscription":
|
|
167
|
-
return cdv.ProductType.PAID_SUBSCRIPTION;
|
|
168
|
-
case "consumable":
|
|
169
|
-
return cdv.ProductType.CONSUMABLE;
|
|
170
|
-
default:
|
|
171
|
-
return cdv.ProductType.NON_CONSUMABLE;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
function inferProductType(tx, configured) {
|
|
175
|
-
const id = tx.products[0]?.id;
|
|
176
|
-
if (!id) return "product";
|
|
177
|
-
const match = configured.find((p) => p.id === id);
|
|
178
|
-
return match?.type ?? "product";
|
|
179
|
-
}
|
|
180
147
|
function normalizeProduct(p, type) {
|
|
181
|
-
const
|
|
182
|
-
const phase = offer?.pricingPhases?.[0];
|
|
183
|
-
const priceMicros = phase?.priceMicros?.toString() ?? "0";
|
|
184
|
-
const priceString = phase?.price ?? "";
|
|
185
|
-
const currency = phase?.currency ?? "";
|
|
148
|
+
const priceMicros = Math.round(p.price * 1e6).toString();
|
|
186
149
|
return {
|
|
187
|
-
id: p.
|
|
150
|
+
id: p.identifier,
|
|
188
151
|
type,
|
|
189
|
-
title: p.title
|
|
190
|
-
description: p.description
|
|
191
|
-
priceString,
|
|
152
|
+
title: p.title,
|
|
153
|
+
description: p.description,
|
|
154
|
+
priceString: p.priceString,
|
|
192
155
|
priceMicros,
|
|
193
|
-
currency
|
|
156
|
+
currency: p.currencyCode
|
|
194
157
|
};
|
|
195
158
|
}
|
|
196
|
-
function normalizeTransaction(tx, productType
|
|
197
|
-
const platform = tx
|
|
198
|
-
const
|
|
199
|
-
const
|
|
159
|
+
function normalizeTransaction(tx, productType) {
|
|
160
|
+
const platform = inferPlatform(tx);
|
|
161
|
+
const token = platform === "google" ? tx.purchaseToken ?? tx.transactionId : tx.transactionId;
|
|
162
|
+
const native = {
|
|
200
163
|
platform,
|
|
201
|
-
productId,
|
|
164
|
+
productId: tx.productIdentifier,
|
|
202
165
|
token,
|
|
203
166
|
productType,
|
|
204
167
|
raw: tx
|
|
205
168
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
169
|
+
return native;
|
|
170
|
+
}
|
|
171
|
+
function inferPlatform(tx) {
|
|
172
|
+
if (tx.purchaseToken !== void 0 || tx.purchaseState !== void 0 || tx.orderId !== void 0) {
|
|
173
|
+
return "google";
|
|
174
|
+
}
|
|
175
|
+
if (tx.receipt !== void 0 || tx.jwsRepresentation !== void 0) {
|
|
176
|
+
return "apple";
|
|
209
177
|
}
|
|
210
|
-
return
|
|
178
|
+
return getPlatform() === "android" ? "google" : "apple";
|
|
211
179
|
}
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
return
|
|
180
|
+
function inferProductType(tx) {
|
|
181
|
+
if (tx.productType === "subs") return "subscription";
|
|
182
|
+
return "product";
|
|
215
183
|
}
|
|
216
|
-
function
|
|
217
|
-
|
|
218
|
-
return tx.transactionId || null;
|
|
219
|
-
}
|
|
220
|
-
const googleTx = tx;
|
|
221
|
-
return googleTx.nativePurchase?.purchaseToken ?? googleTx.parentReceipt?.purchaseToken ?? tx.transactionId ?? null;
|
|
184
|
+
function mapToPluginPurchaseType(type) {
|
|
185
|
+
return type === "subscription" ? nativePurchases.PURCHASE_TYPE.SUBS : nativePurchases.PURCHASE_TYPE.INAPP;
|
|
222
186
|
}
|
|
223
|
-
function
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
187
|
+
function mapPurchaseError(error, productId) {
|
|
188
|
+
if (error instanceof exports.IAPError) return error;
|
|
189
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
190
|
+
const lower = message.toLowerCase();
|
|
191
|
+
if (lower.includes("cancel")) {
|
|
227
192
|
return new exports.IAPError({
|
|
228
193
|
code: exports.IAPErrorCode.USER_CANCELLED,
|
|
229
|
-
message:
|
|
230
|
-
cause:
|
|
194
|
+
message: `Purchase of "${productId}" was cancelled.`,
|
|
195
|
+
cause: error
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (lower.includes("pending")) {
|
|
199
|
+
return new exports.IAPError({
|
|
200
|
+
code: exports.IAPErrorCode.PURCHASE_PENDING,
|
|
201
|
+
message: `Purchase of "${productId}" is pending external clearance.`,
|
|
202
|
+
cause: error
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (lower.includes("product not found")) {
|
|
206
|
+
return new exports.IAPError({
|
|
207
|
+
code: exports.IAPErrorCode.PRODUCT_NOT_FOUND,
|
|
208
|
+
message: `Product "${productId}" was not found in the store catalog.`,
|
|
209
|
+
cause: error
|
|
231
210
|
});
|
|
232
211
|
}
|
|
233
212
|
return new exports.IAPError({
|
|
234
213
|
code: exports.IAPErrorCode.STORE_ERROR,
|
|
235
|
-
message:
|
|
236
|
-
cause:
|
|
214
|
+
message: `Native purchase of "${productId}" failed.`,
|
|
215
|
+
cause: error
|
|
237
216
|
});
|
|
238
217
|
}
|
|
239
|
-
var
|
|
218
|
+
var CapgoNativeAdapter;
|
|
240
219
|
var init_native_adapter = __esm({
|
|
241
|
-
"src/adapters/native/
|
|
220
|
+
"src/adapters/native/capgo/native-adapter.ts"() {
|
|
242
221
|
init_errors();
|
|
243
222
|
init_platform();
|
|
244
|
-
|
|
245
|
-
products;
|
|
246
|
-
bootstrapped = false;
|
|
247
|
-
bootstrapping = null;
|
|
248
|
-
pendingFinish = /* @__PURE__ */ new Map();
|
|
249
|
-
/** Long-lived bootstrap-time .approved() listener — kept for dispose(). */
|
|
250
|
-
bootstrapApprovedHandler = null;
|
|
251
|
-
constructor(opts) {
|
|
252
|
-
this.products = opts.products;
|
|
253
|
-
}
|
|
223
|
+
CapgoNativeAdapter = class {
|
|
254
224
|
async isAvailable() {
|
|
255
225
|
try {
|
|
256
|
-
await
|
|
257
|
-
return
|
|
226
|
+
const result = await nativePurchases.NativePurchases.isBillingSupported();
|
|
227
|
+
return result.isBillingSupported;
|
|
258
228
|
} catch {
|
|
259
229
|
return false;
|
|
260
230
|
}
|
|
261
231
|
}
|
|
262
232
|
async getProducts(requests) {
|
|
263
233
|
if (requests.length === 0) return [];
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
const
|
|
234
|
+
const inappIds = [];
|
|
235
|
+
const subsIds = [];
|
|
236
|
+
const requestById = /* @__PURE__ */ new Map();
|
|
267
237
|
for (const req of requests) {
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
|
|
238
|
+
requestById.set(req.id, req.type);
|
|
239
|
+
if (req.type === "subscription") {
|
|
240
|
+
subsIds.push(req.id);
|
|
241
|
+
} else {
|
|
242
|
+
inappIds.push(req.id);
|
|
243
|
+
}
|
|
271
244
|
}
|
|
272
|
-
|
|
245
|
+
const [inapp, subs] = await Promise.all([
|
|
246
|
+
inappIds.length > 0 ? nativePurchases.NativePurchases.getProducts({
|
|
247
|
+
productIdentifiers: inappIds,
|
|
248
|
+
productType: nativePurchases.PURCHASE_TYPE.INAPP
|
|
249
|
+
}) : Promise.resolve({ products: [] }),
|
|
250
|
+
subsIds.length > 0 ? nativePurchases.NativePurchases.getProducts({
|
|
251
|
+
productIdentifiers: subsIds,
|
|
252
|
+
productType: nativePurchases.PURCHASE_TYPE.SUBS
|
|
253
|
+
}) : Promise.resolve({ products: [] })
|
|
254
|
+
]);
|
|
255
|
+
const all = [...inapp.products, ...subs.products];
|
|
256
|
+
return all.map((p) => normalizeProduct(p, requestById.get(p.identifier) ?? "product"));
|
|
273
257
|
}
|
|
274
258
|
async purchaseProduct(opts) {
|
|
275
|
-
const
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
code: exports.IAPErrorCode.PRODUCT_NOT_FOUND,
|
|
287
|
-
message: `Product "${opts.productId}" has no purchasable offer${opts.androidPlanId ? ` (planId="${opts.androidPlanId}")` : ""}.`
|
|
259
|
+
const purchaseType = mapToPluginPurchaseType(opts.productType);
|
|
260
|
+
const isConsumable = opts.productType === "consumable";
|
|
261
|
+
let tx;
|
|
262
|
+
try {
|
|
263
|
+
tx = await nativePurchases.NativePurchases.purchaseProduct({
|
|
264
|
+
productIdentifier: opts.productId,
|
|
265
|
+
productType: purchaseType,
|
|
266
|
+
planIdentifier: opts.androidPlanId,
|
|
267
|
+
appAccountToken: opts.appAccountToken,
|
|
268
|
+
isConsumable,
|
|
269
|
+
autoAcknowledgePurchases: false
|
|
288
270
|
});
|
|
271
|
+
} catch (error) {
|
|
272
|
+
throw mapPurchaseError(error, opts.productId);
|
|
289
273
|
}
|
|
290
|
-
return
|
|
291
|
-
let settled = false;
|
|
292
|
-
const cleanup = () => {
|
|
293
|
-
store.off(handleApproved);
|
|
294
|
-
};
|
|
295
|
-
const handleApproved = (tx) => {
|
|
296
|
-
if (settled) return;
|
|
297
|
-
if (!tx.products.some((p) => p.id === opts.productId)) return;
|
|
298
|
-
const token = transactionToken(tx);
|
|
299
|
-
if (!token) {
|
|
300
|
-
settled = true;
|
|
301
|
-
cleanup();
|
|
302
|
-
reject(
|
|
303
|
-
new exports.IAPError({
|
|
304
|
-
code: exports.IAPErrorCode.STORE_ERROR,
|
|
305
|
-
message: `Approved transaction for "${opts.productId}" has no token; cannot verify.`
|
|
306
|
-
})
|
|
307
|
-
);
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
settled = true;
|
|
311
|
-
cleanup();
|
|
312
|
-
const normalized = normalizeTransaction(tx, opts.productType, token);
|
|
313
|
-
this.pendingFinish.set(token, tx);
|
|
314
|
-
resolve(normalized);
|
|
315
|
-
};
|
|
316
|
-
store.when().approved(handleApproved);
|
|
317
|
-
const additionalData = opts.appAccountToken ? { applicationUsername: opts.appAccountToken } : void 0;
|
|
318
|
-
void Promise.resolve(offer.order(additionalData)).then((err) => {
|
|
319
|
-
if (settled) return;
|
|
320
|
-
if (!err) return;
|
|
321
|
-
settled = true;
|
|
322
|
-
cleanup();
|
|
323
|
-
reject(mapOrderError(err, opts.productId));
|
|
324
|
-
}).catch((cause) => {
|
|
325
|
-
if (settled) return;
|
|
326
|
-
settled = true;
|
|
327
|
-
cleanup();
|
|
328
|
-
reject(
|
|
329
|
-
new exports.IAPError({
|
|
330
|
-
code: exports.IAPErrorCode.STORE_ERROR,
|
|
331
|
-
message: `order() rejected for ${opts.productId}.`,
|
|
332
|
-
cause
|
|
333
|
-
})
|
|
334
|
-
);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
274
|
+
return normalizeTransaction(tx, opts.productType);
|
|
337
275
|
}
|
|
338
276
|
async getOwnedTransactions() {
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
const out = [];
|
|
342
|
-
for (const tx of store.localTransactions) {
|
|
343
|
-
if (tx.state !== getCdv().TransactionState.APPROVED) continue;
|
|
344
|
-
const token = transactionToken(tx);
|
|
345
|
-
if (!token) continue;
|
|
346
|
-
const normalized = normalizeTransaction(tx, inferProductType(tx, this.products), token);
|
|
347
|
-
this.pendingFinish.set(token, tx);
|
|
348
|
-
out.push(normalized);
|
|
349
|
-
}
|
|
350
|
-
return out;
|
|
277
|
+
const result = await nativePurchases.NativePurchases.getPurchases();
|
|
278
|
+
return result.purchases.filter((tx) => tx.purchaseState === void 0 || tx.purchaseState === "1").map((tx) => normalizeTransaction(tx, inferProductType(tx)));
|
|
351
279
|
}
|
|
352
280
|
async acknowledge(transaction) {
|
|
353
|
-
const cdvTx = this.pendingFinish.get(transaction.token);
|
|
354
|
-
if (!cdvTx) {
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
281
|
try {
|
|
358
|
-
await
|
|
359
|
-
|
|
282
|
+
await nativePurchases.NativePurchases.acknowledgePurchase({
|
|
283
|
+
purchaseToken: transaction.token
|
|
284
|
+
});
|
|
285
|
+
} catch (error) {
|
|
360
286
|
throw new exports.IAPError({
|
|
361
287
|
code: exports.IAPErrorCode.STORE_ERROR,
|
|
362
|
-
message: `Failed to
|
|
363
|
-
cause,
|
|
288
|
+
message: `Failed to acknowledge transaction for ${transaction.productId}.`,
|
|
289
|
+
cause: error,
|
|
364
290
|
recoverable: true
|
|
365
291
|
});
|
|
366
292
|
}
|
|
367
|
-
this.pendingFinish.delete(transaction.token);
|
|
368
293
|
}
|
|
369
294
|
async manageSubscriptions() {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
295
|
+
try {
|
|
296
|
+
await nativePurchases.NativePurchases.manageSubscriptions();
|
|
297
|
+
} catch (error) {
|
|
373
298
|
throw new exports.IAPError({
|
|
374
299
|
code: exports.IAPErrorCode.STORE_ERROR,
|
|
375
|
-
message:
|
|
300
|
+
message: "Failed to open the native subscription management UI.",
|
|
301
|
+
cause: error
|
|
376
302
|
});
|
|
377
303
|
}
|
|
378
304
|
}
|
|
379
305
|
async dispose() {
|
|
380
|
-
if (this.bootstrapApprovedHandler) {
|
|
381
|
-
try {
|
|
382
|
-
const cdv = globalThis.CdvPurchase;
|
|
383
|
-
cdv?.store?.off(this.bootstrapApprovedHandler);
|
|
384
|
-
} catch {
|
|
385
|
-
}
|
|
386
|
-
this.bootstrapApprovedHandler = null;
|
|
387
|
-
}
|
|
388
|
-
this.pendingFinish.clear();
|
|
389
|
-
this.bootstrapped = false;
|
|
390
|
-
this.bootstrapping = null;
|
|
391
|
-
}
|
|
392
|
-
// ----- internals -----
|
|
393
|
-
async ensureStore() {
|
|
394
|
-
await this.bootstrap();
|
|
395
|
-
return getCdv().store;
|
|
396
|
-
}
|
|
397
|
-
bootstrap() {
|
|
398
|
-
if (this.bootstrapped) return Promise.resolve();
|
|
399
|
-
if (this.bootstrapping) return this.bootstrapping;
|
|
400
|
-
this.bootstrapping = (async () => {
|
|
401
|
-
const cdv = getCdv();
|
|
402
|
-
const platform = currentCdvPlatform();
|
|
403
|
-
cdv.store.register(
|
|
404
|
-
this.products.map((p) => ({
|
|
405
|
-
id: p.id,
|
|
406
|
-
type: mapProductType(p.type),
|
|
407
|
-
platform
|
|
408
|
-
}))
|
|
409
|
-
);
|
|
410
|
-
const errors = await cdv.store.initialize([platform]);
|
|
411
|
-
if (errors && errors.length > 0) {
|
|
412
|
-
const first = errors[0];
|
|
413
|
-
throw new exports.IAPError({
|
|
414
|
-
code: exports.IAPErrorCode.BILLING_NOT_AVAILABLE,
|
|
415
|
-
message: first?.message ?? "cordova-plugin-purchase initialize() reported errors."
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
const handler = (tx) => {
|
|
419
|
-
const token = transactionToken(tx);
|
|
420
|
-
if (!token) return;
|
|
421
|
-
if (!this.pendingFinish.has(token)) {
|
|
422
|
-
this.pendingFinish.set(token, tx);
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
this.bootstrapApprovedHandler = handler;
|
|
426
|
-
cdv.store.when().approved(handler);
|
|
427
|
-
await cdv.store.update();
|
|
428
|
-
this.bootstrapped = true;
|
|
429
|
-
})();
|
|
430
|
-
return this.bootstrapping;
|
|
431
306
|
}
|
|
432
307
|
};
|
|
433
308
|
}
|
|
@@ -960,11 +835,11 @@ var WebStubAdapter = class {
|
|
|
960
835
|
};
|
|
961
836
|
|
|
962
837
|
// src/adapters/native/index.ts
|
|
963
|
-
async function selectNativeAdapter(
|
|
838
|
+
async function selectNativeAdapter() {
|
|
964
839
|
const platform = getPlatform();
|
|
965
840
|
if (platform === "ios" || platform === "android") {
|
|
966
841
|
const mod = await Promise.resolve().then(() => (init_native_adapter(), native_adapter_exports));
|
|
967
|
-
return new mod.
|
|
842
|
+
return new mod.CapgoNativeAdapter();
|
|
968
843
|
}
|
|
969
844
|
return new WebStubAdapter();
|
|
970
845
|
}
|
|
@@ -1902,7 +1777,7 @@ function createIAP(input) {
|
|
|
1902
1777
|
state.products = Object.freeze([...validated.data]);
|
|
1903
1778
|
state.logger.debug(`Resolved ${validated.data.length} product(s) from backend manifest.`);
|
|
1904
1779
|
}
|
|
1905
|
-
state.adapter = await selectNativeAdapter(
|
|
1780
|
+
state.adapter = await selectNativeAdapter();
|
|
1906
1781
|
const configGetAuthHeaders = state.config.backend.getAuthHeaders;
|
|
1907
1782
|
const getAuthHeaders = configGetAuthHeaders ? async () => configGetAuthHeaders() : async () => ({});
|
|
1908
1783
|
const sharedDeps = {
|