capacitor-plugin-cdv-purchase 13.18.0 → 13.20.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/README.md +32 -0
- package/dist/index.js +105 -19
- package/dist/index.js.map +1 -1
- package/ios/Sources/PurchasePlugin/PurchasePlugin.swift +127 -37
- package/package.json +2 -1
- package/src/ts/platforms/iaptic-js/iaptic-js-types.d.ts +451 -0
- package/www/store.d.ts +111 -7
- package/www/store.js +105 -19
package/README.md
CHANGED
|
@@ -42,6 +42,38 @@ If you're using `cordova-plugin-purchase` in a Capacitor app:
|
|
|
42
42
|
3. `npx cap sync`
|
|
43
43
|
4. Update your import to `import { store, ProductType, Platform } from 'capacitor-plugin-cdv-purchase'` — the API is identical
|
|
44
44
|
|
|
45
|
+
### Behavior change: purchase events at app launch (iOS)
|
|
46
|
+
|
|
47
|
+
The Cordova build used StoreKit 1, which only re-delivers *unfinished* transactions at launch. The Capacitor build uses StoreKit 2, where `store.initialize()` surfaces the user's full set of **current entitlements** by firing `approved` for:
|
|
48
|
+
|
|
49
|
+
- every non-consumable purchase,
|
|
50
|
+
- the latest transaction of each auto-renewable subscription,
|
|
51
|
+
- each non-renewing subscription — **including transactions you already finished**.
|
|
52
|
+
|
|
53
|
+
`restorePurchases()` fires `approved` for the same set.
|
|
54
|
+
|
|
55
|
+
Consumables are **not** re-delivered this way: they never appear in StoreKit 2's current entitlements. Unfinished consumables are still re-delivered once per launch, exactly as under StoreKit 1.
|
|
56
|
+
|
|
57
|
+
**What this means for your app:** with the common pattern
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
store.when().approved((transaction) => {
|
|
61
|
+
deliverToServer(transaction); // your fulfillment endpoint
|
|
62
|
+
transaction.finish();
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`approved` fires for already-fulfilled non-consumables and non-renewing subscriptions on every app launch. Those transactions have `isAcknowledged === true` (you already finished them), so you can skip them without keeping your own list of processed ids:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
store.when().approved((transaction) => {
|
|
70
|
+
if (transaction.isAcknowledged) return transaction.finish(); // already delivered
|
|
71
|
+
deliverToServer(transaction).then(() => transaction.finish());
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`isAcknowledged` is `false` for transactions you haven't finished yet, and becomes `true` once you call `finish()`. It's the same flag Google Play uses for acknowledged purchases. On iOS it is only set by this Capacitor plugin: Cordova apps (StoreKit 1, or the `cordova-plugin-purchase-storekit2` plugin) leave it `undefined`. Keeping server-side fulfillment idempotent on the transaction id is still good practice: an app killed between delivery and `finish()` gets the transaction again, unfinished.
|
|
76
|
+
|
|
45
77
|
## Device info for receipt validation
|
|
46
78
|
|
|
47
79
|
When validating receipts, the plugin sends device information (OS, model, manufacturer) to the validator to support fraud detection and support requests. In Capacitor apps, this information is collected automatically via `@capacitor/device` through the Capacitor plugin proxy — no additional plugin installation is required.
|
package/dist/index.js
CHANGED
|
@@ -516,6 +516,10 @@ var CdvPurchase;
|
|
|
516
516
|
this.receiptsToValidate = new ReceiptsToValidate();
|
|
517
517
|
/** List of verified receipts */
|
|
518
518
|
this.verifiedReceipts = [];
|
|
519
|
+
/** Maximum number of validation attempts for a receipt when failures are temporary */
|
|
520
|
+
this.maxAttempts = 4;
|
|
521
|
+
/** Number of failed attempts in a row, per receipt */
|
|
522
|
+
this.failedAttempts = new Map();
|
|
519
523
|
this.numRequests = 0;
|
|
520
524
|
this.numResponses = 0;
|
|
521
525
|
/**
|
|
@@ -529,6 +533,36 @@ var CdvPurchase;
|
|
|
529
533
|
this.controller = controller;
|
|
530
534
|
this.log = log.child('Validator');
|
|
531
535
|
}
|
|
536
|
+
/**
|
|
537
|
+
* Schedule a new validation if the failure is temporary and attempts remain.
|
|
538
|
+
*
|
|
539
|
+
* @return true if a retry was scheduled
|
|
540
|
+
*/
|
|
541
|
+
scheduleRetry(receipt, payload) {
|
|
542
|
+
var _a;
|
|
543
|
+
if (payload.ok || !isRetriableError(payload)) {
|
|
544
|
+
this.failedAttempts.delete(receipt);
|
|
545
|
+
if (payload.ok && this.retry)
|
|
546
|
+
this.retry.retryTimeout = this.retry.minTimeout;
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
const attempts = ((_a = this.failedAttempts.get(receipt)) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
550
|
+
if (attempts >= this.maxAttempts) {
|
|
551
|
+
this.log.warn(`Validation failed ${attempts} times, giving up: ${payload.message}`);
|
|
552
|
+
this.failedAttempts.delete(receipt);
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
this.failedAttempts.set(receipt, attempts);
|
|
556
|
+
if (!this.retry)
|
|
557
|
+
this.retry = new Internal.Retry();
|
|
558
|
+
this.log.info(`Validation failed (${payload.message}), retry in ${this.retry.retryTimeout}ms (attempt ${attempts + 1}/${this.maxAttempts})`);
|
|
559
|
+
this.retry.retry(() => {
|
|
560
|
+
// the request is still counted as pending, so don't count it again
|
|
561
|
+
this.receiptsToValidate.add(receipt);
|
|
562
|
+
this.run();
|
|
563
|
+
});
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
532
566
|
incrRequestsCounter() {
|
|
533
567
|
this.numRequests = (this.numRequests + 1) | 0;
|
|
534
568
|
this.log.debug(`Validation requests=${this.numRequests} responses=${this.numResponses}`);
|
|
@@ -569,6 +603,9 @@ var CdvPurchase;
|
|
|
569
603
|
const onResponse = (r) => __awaiter(this, void 0, void 0, function* () {
|
|
570
604
|
var _a;
|
|
571
605
|
const { receipt, payload } = r;
|
|
606
|
+
// a retried request is still pending: only count final responses
|
|
607
|
+
if (this.scheduleRetry(receipt, payload))
|
|
608
|
+
return;
|
|
572
609
|
this.incrResponsesCounter();
|
|
573
610
|
try {
|
|
574
611
|
const adapter = this.controller.adapters.find(receipt.platform);
|
|
@@ -590,11 +627,11 @@ var CdvPurchase;
|
|
|
590
627
|
this.controller.verifiedCallbacks.trigger(vr, 'payload_expired');
|
|
591
628
|
}
|
|
592
629
|
else {
|
|
593
|
-
this.controller.unverifiedCallbacks.trigger({ receipt, payload }, 'no_verified_receipt');
|
|
630
|
+
this.controller.unverifiedCallbacks.trigger({ receipt, payload, transient: false }, 'no_verified_receipt');
|
|
594
631
|
}
|
|
595
632
|
}
|
|
596
633
|
else {
|
|
597
|
-
this.controller.unverifiedCallbacks.trigger({ receipt, payload }, 'validator_error');
|
|
634
|
+
this.controller.unverifiedCallbacks.trigger({ receipt, payload, transient: isRetriableError(payload) }, 'validator_error');
|
|
598
635
|
}
|
|
599
636
|
}
|
|
600
637
|
catch (err) {
|
|
@@ -603,7 +640,7 @@ var CdvPurchase;
|
|
|
603
640
|
ok: false,
|
|
604
641
|
code: CdvPurchase.ErrorCode.VERIFICATION_FAILED,
|
|
605
642
|
message: err.message,
|
|
606
|
-
} }, 'validator_exception');
|
|
643
|
+
}, transient: true }, 'validator_exception');
|
|
607
644
|
}
|
|
608
645
|
});
|
|
609
646
|
receipts.forEach(receipt => this.runOnReceipt(receipt, onResponse));
|
|
@@ -763,10 +800,13 @@ var CdvPurchase;
|
|
|
763
800
|
data: { latest_receipt: (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.latest_receipt },
|
|
764
801
|
}
|
|
765
802
|
});
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
803
|
+
// don't cache temporary errors, so a retry makes a new request
|
|
804
|
+
if (response.ok || !isTransientError(response)) {
|
|
805
|
+
this.cache[bodyTransactionHash] = {
|
|
806
|
+
payload: response,
|
|
807
|
+
expires: (+new Date()) + 120000, // expires in 2 minutes
|
|
808
|
+
};
|
|
809
|
+
}
|
|
770
810
|
callback({ receipt, payload: response });
|
|
771
811
|
},
|
|
772
812
|
error: (status, message, data) => {
|
|
@@ -788,6 +828,44 @@ var CdvPurchase;
|
|
|
788
828
|
}
|
|
789
829
|
}
|
|
790
830
|
Internal.Validator = Validator;
|
|
831
|
+
/** Validator error code: the validator couldn't reach the store (Apple, Google, ...). */
|
|
832
|
+
const VALIDATOR_CONNECTION_FAILED = 6778002;
|
|
833
|
+
/**
|
|
834
|
+
* True if a validation error is temporary (validator or store unreachable,
|
|
835
|
+
* server error, timeout, no network) rather than a definitive answer about
|
|
836
|
+
* the receipt.
|
|
837
|
+
*
|
|
838
|
+
* Error responses from the validator come in two shapes:
|
|
839
|
+
* - HTTP 200 with `{ ok: false, status, code }` in the body (iaptic does this,
|
|
840
|
+
* so a store outage is `status: 502, code: 6778002` in the body);
|
|
841
|
+
* - a failed HTTP request, reported by the plugin as `code: COMMUNICATION`
|
|
842
|
+
* with the transport status: 5xx, 408 (timeout), 417 (unreadable response,
|
|
843
|
+
* e.g. a captive portal page), 0 or negative (no network, as reported by
|
|
844
|
+
* XMLHttpRequest or cordova-plugin-advanced-http).
|
|
845
|
+
*/
|
|
846
|
+
function isTransientError(payload) {
|
|
847
|
+
const status = payload.status;
|
|
848
|
+
if (typeof status === 'number' && status >= 500)
|
|
849
|
+
return true;
|
|
850
|
+
if (payload.code === VALIDATOR_CONNECTION_FAILED)
|
|
851
|
+
return true;
|
|
852
|
+
if (payload.code === CdvPurchase.ErrorCode.COMMUNICATION) {
|
|
853
|
+
if (typeof status !== 'number' || status <= 0)
|
|
854
|
+
return true;
|
|
855
|
+
if (status === CdvPurchase.Utils.Ajax.HTTP_REQUEST_TIMEOUT || status === 417)
|
|
856
|
+
return true;
|
|
857
|
+
}
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
Internal.isTransientError = isTransientError;
|
|
861
|
+
/**
|
|
862
|
+
* True if a validation failure is worth retrying: a temporary error
|
|
863
|
+
* (see isTransientError) or an invalid response from the validator.
|
|
864
|
+
* Validator verdicts (invalid receipt, expired, ...) are not retried.
|
|
865
|
+
*/
|
|
866
|
+
function isRetriableError(payload) {
|
|
867
|
+
return isTransientError(payload) || payload.code === CdvPurchase.ErrorCode.BAD_RESPONSE;
|
|
868
|
+
}
|
|
791
869
|
/**
|
|
792
870
|
* Check if a payload looks like a valid validator response.
|
|
793
871
|
*
|
|
@@ -1660,8 +1738,8 @@ var CdvPurchase;
|
|
|
1660
1738
|
(function (CdvPurchase) {
|
|
1661
1739
|
/**
|
|
1662
1740
|
* Persist a subset of {@link VerifiedPurchase} to device storage so that
|
|
1663
|
-
* `
|
|
1664
|
-
* without connectivity.
|
|
1741
|
+
* `offline.isOwned()` can answer "is this product owned?" when the device
|
|
1742
|
+
* is offline or has just restarted without connectivity.
|
|
1665
1743
|
*
|
|
1666
1744
|
* Phase 1 — unsigned cache. No JWT, no crypto, no server changes.
|
|
1667
1745
|
*
|
|
@@ -1975,7 +2053,7 @@ var CdvPurchase;
|
|
|
1975
2053
|
/**
|
|
1976
2054
|
* Current release number of the plugin.
|
|
1977
2055
|
*/
|
|
1978
|
-
CdvPurchase.PLUGIN_VERSION = '13.
|
|
2056
|
+
CdvPurchase.PLUGIN_VERSION = '13.20.0';
|
|
1979
2057
|
/**
|
|
1980
2058
|
* Entry class of the plugin.
|
|
1981
2059
|
*/
|
|
@@ -3640,13 +3718,19 @@ var CdvPurchase;
|
|
|
3640
3718
|
yield this.receiptsUpdated.wait();
|
|
3641
3719
|
this.log.info('finish: ' + transactionIdentifier + ' - ' + productId);
|
|
3642
3720
|
this.removeTransactionInProgress(productId);
|
|
3643
|
-
yield this.upsertTransaction(productId, transactionIdentifier, CdvPurchase.TransactionState.FINISHED);
|
|
3721
|
+
const finishedTransaction = yield this.upsertTransaction(productId, transactionIdentifier, CdvPurchase.TransactionState.FINISHED);
|
|
3722
|
+
finishedTransaction.isAcknowledged = true;
|
|
3644
3723
|
this.receiptsUpdated.call();
|
|
3645
3724
|
}),
|
|
3646
|
-
restored: (transactionIdentifier, productId, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity) => __awaiter(this, void 0, void 0, function* () {
|
|
3725
|
+
restored: (transactionIdentifier, productId, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity, isFinished) => __awaiter(this, void 0, void 0, function* () {
|
|
3647
3726
|
this.log.info('restore: ' + transactionIdentifier + ' - ' + productId);
|
|
3648
3727
|
const transaction = yield this.upsertTransaction(productId, transactionIdentifier, CdvPurchase.TransactionState.APPROVED);
|
|
3649
3728
|
transaction.refresh(productId, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity);
|
|
3729
|
+
// StoreKit 2 re-emits finished entitlements (non-consumables,
|
|
3730
|
+
// non-renewing subscriptions) at every launch. Expose the finished
|
|
3731
|
+
// state the same way Google Play does, so apps can skip them in approved().
|
|
3732
|
+
if (isFinished !== undefined)
|
|
3733
|
+
transaction.isAcknowledged = isFinished;
|
|
3650
3734
|
this.receiptsUpdated.call();
|
|
3651
3735
|
}),
|
|
3652
3736
|
receiptsRefreshed: (receipt) => {
|
|
@@ -4278,7 +4362,7 @@ var CdvPurchase;
|
|
|
4278
4362
|
}
|
|
4279
4363
|
// Listen for transaction updates from native
|
|
4280
4364
|
plugin.addListener('transactionUpdated', (data) => {
|
|
4281
|
-
this.transactionUpdated(data.state, data.errorCode, data.errorText, data.transactionIdentifier, data.productId, data.transactionReceipt, data.originalTransactionIdentifier, data.transactionDate, data.discountId, data.expirationDate, data.jwsRepresentation, data.quantity);
|
|
4365
|
+
this.transactionUpdated(data.state, data.errorCode, data.errorText, data.transactionIdentifier, data.productId, data.transactionReceipt, data.originalTransactionIdentifier, data.transactionDate, data.discountId, data.expirationDate, data.jwsRepresentation, data.quantity, data.isFinished);
|
|
4282
4366
|
});
|
|
4283
4367
|
plugin.addListener('restoreCompleted', () => {
|
|
4284
4368
|
this.restoreCompletedTransactionsFinished();
|
|
@@ -4299,7 +4383,7 @@ var CdvPurchase;
|
|
|
4299
4383
|
const pending = this.pendingTransactionUpdates;
|
|
4300
4384
|
this.pendingTransactionUpdates = [];
|
|
4301
4385
|
for (const args of pending) {
|
|
4302
|
-
this.transactionUpdated(args.state, args.errorCode, args.errorText, args.transactionIdentifier, args.productId, args.transactionReceipt, args.originalTransactionIdentifier, args.transactionDate, args.discountId, args.expirationDate, args.jwsRepresentation, args.quantity);
|
|
4386
|
+
this.transactionUpdated(args.state, args.errorCode, args.errorText, args.transactionIdentifier, args.productId, args.transactionReceipt, args.originalTransactionIdentifier, args.transactionDate, args.discountId, args.expirationDate, args.jwsRepresentation, args.quantity, args.isFinished);
|
|
4303
4387
|
}
|
|
4304
4388
|
if (this.options.ready)
|
|
4305
4389
|
this.options.ready();
|
|
@@ -4383,13 +4467,13 @@ var CdvPurchase;
|
|
|
4383
4467
|
.catch((err) => errorCb(CdvPurchase.ErrorCode.LOAD, (err === null || err === void 0 ? void 0 : err.message) || 'loadReceipts failed'));
|
|
4384
4468
|
}
|
|
4385
4469
|
// Called when the native side sends a transaction update
|
|
4386
|
-
transactionUpdated(state, errorCode, errorText, transactionIdentifier, productId, transactionReceipt, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity) {
|
|
4470
|
+
transactionUpdated(state, errorCode, errorText, transactionIdentifier, productId, transactionReceipt, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity, isFinished) {
|
|
4387
4471
|
if (!this.initialized) {
|
|
4388
4472
|
this.pendingTransactionUpdates.push({
|
|
4389
4473
|
state, errorCode, errorText, transactionIdentifier,
|
|
4390
4474
|
productId, transactionReceipt, originalTransactionIdentifier,
|
|
4391
4475
|
transactionDate, discountId, expirationDate, jwsRepresentation,
|
|
4392
|
-
quantity,
|
|
4476
|
+
quantity, isFinished,
|
|
4393
4477
|
});
|
|
4394
4478
|
return;
|
|
4395
4479
|
}
|
|
@@ -4425,7 +4509,7 @@ var CdvPurchase;
|
|
|
4425
4509
|
// purchased, but is meaningless here: consumables cannot be
|
|
4426
4510
|
// restored, so restored transactions are always quantity 1.
|
|
4427
4511
|
if (this.options.restored) {
|
|
4428
|
-
this.options.restored(transactionIdentifier, productId, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity);
|
|
4512
|
+
this.options.restored(transactionIdentifier, productId, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity, isFinished);
|
|
4429
4513
|
}
|
|
4430
4514
|
break;
|
|
4431
4515
|
case 'PaymentTransactionStateDeferred':
|
|
@@ -7767,7 +7851,7 @@ var CdvPurchase;
|
|
|
7767
7851
|
this.products = [{ id: productId }];
|
|
7768
7852
|
this.transactionId = purchase.transactionId;
|
|
7769
7853
|
this.purchaseId = purchase.purchaseId;
|
|
7770
|
-
this.purchaseDate = new Date(purchase.purchaseDate);
|
|
7854
|
+
this.purchaseDate = purchase.purchaseDate ? new Date(purchase.purchaseDate) : undefined;
|
|
7771
7855
|
this.expirationDate = purchase.expirationDate ? new Date(purchase.expirationDate) : undefined;
|
|
7772
7856
|
this.lastRenewalDate = purchase.lastRenewalDate ? new Date(purchase.lastRenewalDate) : undefined;
|
|
7773
7857
|
this.renewalIntent = purchase.renewalIntent === 'Renew' ? CdvPurchase.RenewalIntent.RENEW : CdvPurchase.RenewalIntent.LAPSE;
|
|
@@ -7886,7 +7970,7 @@ var CdvPurchase;
|
|
|
7886
7970
|
billingPeriod: pp.billingPeriod,
|
|
7887
7971
|
paymentMode: pp.paymentMode,
|
|
7888
7972
|
recurrenceMode: pp.recurrenceMode,
|
|
7889
|
-
price: window.IapticJS.Utils.formatCurrency(pp.priceMicros, pp.currency) // Use Utils for formatting
|
|
7973
|
+
price: pp.currency ? window.IapticJS.Utils.formatCurrency(pp.priceMicros, pp.currency) : '' // Use Utils for formatting
|
|
7890
7974
|
})),
|
|
7891
7975
|
}, this.context.apiDecorators);
|
|
7892
7976
|
product.addOffer(offer);
|
|
@@ -9358,6 +9442,7 @@ var CdvPurchase;
|
|
|
9358
9442
|
this.latestReceipt = response.latest_receipt;
|
|
9359
9443
|
this.nativeTransactions = [response.transaction];
|
|
9360
9444
|
this.warning = response.warning;
|
|
9445
|
+
this.cache = response.cache;
|
|
9361
9446
|
this.validationDate = response.date ? new Date(response.date) : new Date();
|
|
9362
9447
|
Object.defineProperty(this, 'raw', { 'enumerable': false, get() { return response; } });
|
|
9363
9448
|
Object.defineProperty(this, 'finish', { 'enumerable': false, get() { return () => decorator.finish(this); } });
|
|
@@ -9379,6 +9464,7 @@ var CdvPurchase;
|
|
|
9379
9464
|
this.latestReceipt = response.latest_receipt;
|
|
9380
9465
|
this.nativeTransactions = [response.transaction];
|
|
9381
9466
|
this.warning = response.warning;
|
|
9467
|
+
this.cache = response.cache;
|
|
9382
9468
|
}
|
|
9383
9469
|
/** Finish all transactions in the receipt */
|
|
9384
9470
|
finish() {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["esm/index.js","../build/entry.mjs"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\n// Set the marker so the shared store.js knows Capacitor native bridges are available\nif (typeof window !== 'undefined') {\n window.CdvPurchaseCapacitor = { installed: true };\n}\nconst PurchasePlugin = registerPlugin('PurchasePlugin');\nexport { PurchasePlugin };\n","// Capacitor bridge — sets CdvPurchaseCapacitor marker and registers native plugin\nexport { PurchasePlugin } from '../dist/esm/index.js';\n\n// Store runtime — executes the IIFE, creates window.CdvPurchase\nimport { CdvPurchase } from 'virtual:store-js';\n\n// Re-export the full namespace\nexport { CdvPurchase };\n\n// Convenience named exports\nexport const store = CdvPurchase.store;\nexport const Store = CdvPurchase.Store;\nexport const ProductType = CdvPurchase.ProductType;\nexport const Platform = CdvPurchase.Platform;\nexport const LogLevel = CdvPurchase.LogLevel;\nexport const ErrorCode = CdvPurchase.ErrorCode;\nexport const Logger = CdvPurchase.Logger;\nexport const Iaptic = CdvPurchase.Iaptic;\n"],"names":[],"mappings":";;AACA;AACA,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;AACnC,IAAI,MAAM,CAAC,oBAAoB,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE;AACrD;AACK,MAAC,cAAc,GAAG,cAAc,CAAC,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACLtD;;AASA;AACY,MAAC,KAAK,GAAG,WAAW,CAAC;AACrB,MAAC,KAAK,GAAG,WAAW,CAAC;AACrB,MAAC,WAAW,GAAG,WAAW,CAAC;AAC3B,MAAC,QAAQ,GAAG,WAAW,CAAC;AACxB,MAAC,QAAQ,GAAG,WAAW,CAAC;AACxB,MAAC,SAAS,GAAG,WAAW,CAAC;AACzB,MAAC,MAAM,GAAG,WAAW,CAAC;AACtB,MAAC,MAAM,GAAG,WAAW,CAAC;;;;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["esm/index.js","../build/entry.mjs"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\n// Set the marker so the shared store.js knows Capacitor native bridges are available\nif (typeof window !== 'undefined') {\n window.CdvPurchaseCapacitor = { installed: true };\n}\nconst PurchasePlugin = registerPlugin('PurchasePlugin');\nexport { PurchasePlugin };\n","// Capacitor bridge — sets CdvPurchaseCapacitor marker and registers native plugin\nexport { PurchasePlugin } from '../dist/esm/index.js';\n\n// Store runtime — executes the IIFE, creates window.CdvPurchase\nimport { CdvPurchase } from 'virtual:store-js';\n\n// Re-export the full namespace\nexport { CdvPurchase };\n\n// Convenience named exports\nexport const store = CdvPurchase.store;\nexport const Store = CdvPurchase.Store;\nexport const ProductType = CdvPurchase.ProductType;\nexport const Platform = CdvPurchase.Platform;\nexport const LogLevel = CdvPurchase.LogLevel;\nexport const ErrorCode = CdvPurchase.ErrorCode;\nexport const Logger = CdvPurchase.Logger;\nexport const Iaptic = CdvPurchase.Iaptic;\n"],"names":[],"mappings":";;AACA;AACA,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;AACnC,IAAI,MAAM,CAAC,oBAAoB,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE;AACrD;AACK,MAAC,cAAc,GAAG,cAAc,CAAC,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACLtD;;AASA;AACY,MAAC,KAAK,GAAG,WAAW,CAAC;AACrB,MAAC,KAAK,GAAG,WAAW,CAAC;AACrB,MAAC,WAAW,GAAG,WAAW,CAAC;AAC3B,MAAC,QAAQ,GAAG,WAAW,CAAC;AACxB,MAAC,QAAQ,GAAG,WAAW,CAAC;AACxB,MAAC,SAAS,GAAG,WAAW,CAAC;AACzB,MAAC,MAAM,GAAG,WAAW,CAAC;AACtB,MAAC,MAAM,GAAG,WAAW,CAAC;;;;"}
|
|
@@ -5,6 +5,11 @@ import StoreKit
|
|
|
5
5
|
@available(iOS 15.0, *)
|
|
6
6
|
private class SK2State {
|
|
7
7
|
var products: [String: Product] = [:]
|
|
8
|
+
/// Transactions emitted to JS and not yet finished. Access must be
|
|
9
|
+
/// serialized on the main thread (same as processedTransactionIds below):
|
|
10
|
+
/// the detached Transaction.updates observer, emitTransactionUpdate(),
|
|
11
|
+
/// finish(), and clearExpiredUnfinishedTransactions() all read or
|
|
12
|
+
/// mutate it from concurrent async contexts.
|
|
8
13
|
var unfinishedTransactions: [String: Transaction] = [:]
|
|
9
14
|
var transactionObserverTask: Task<Void, Never>?
|
|
10
15
|
/// Transaction IDs already emitted to JS. Prevents duplicate delivery when
|
|
@@ -46,20 +51,21 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
46
51
|
|
|
47
52
|
// MARK: - Lifecycle
|
|
48
53
|
|
|
49
|
-
override public func load() {
|
|
50
|
-
if #available(iOS 15.0, *) {
|
|
51
|
-
startTransactionObserver()
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
54
|
deinit {
|
|
56
55
|
if #available(iOS 15.0, *) {
|
|
57
56
|
sk2.transactionObserverTask?.cancel()
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
|
|
60
|
+
/// Start listening to Transaction.updates. Called at the end of init(),
|
|
61
|
+
/// NOT at plugin load: Transaction.updates delivers unfinished
|
|
62
|
+
/// transactions as soon as iteration begins, and before JS called init()
|
|
63
|
+
/// no listener is attached — the events would be lost while their ids
|
|
64
|
+
/// still land in processedTransactionIds, hiding unfinished purchases
|
|
65
|
+
/// for the rest of the session.
|
|
61
66
|
@available(iOS 15.0, *)
|
|
62
67
|
private func startTransactionObserver() {
|
|
68
|
+
guard sk2.transactionObserverTask == nil else { return }
|
|
63
69
|
sk2.transactionObserverTask = Task.detached { [weak self] in
|
|
64
70
|
for await result in Transaction.updates {
|
|
65
71
|
guard let self = self else { return }
|
|
@@ -73,22 +79,18 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
73
79
|
let jwsRepresentation = result.jwsRepresentation
|
|
74
80
|
switch result {
|
|
75
81
|
case .verified(let transaction):
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
let shouldEmit: Bool = await MainActor.run {
|
|
80
|
-
if sk2.processedTransactionIds.contains(transaction.id) {
|
|
81
|
-
debugLog("Transaction.updates: skipping duplicate id=\(transaction.id)")
|
|
82
|
-
return false
|
|
83
|
-
}
|
|
84
|
-
sk2.processedTransactionIds.insert(transaction.id)
|
|
85
|
-
return true
|
|
82
|
+
guard await markEmittedToJs(transaction.id) else {
|
|
83
|
+
debugLog("Transaction.updates: skipping duplicate id=\(transaction.id)")
|
|
84
|
+
return
|
|
86
85
|
}
|
|
87
|
-
guard shouldEmit else { return }
|
|
88
86
|
await emitTransactionUpdate(transaction, state: "PaymentTransactionStatePurchased",
|
|
89
87
|
jwsRepresentation: jwsRepresentation)
|
|
90
88
|
case .unverified(let transaction, _):
|
|
91
|
-
// Emit as Purchased — let JS layer handle verification
|
|
89
|
+
// Emit as Purchased — let JS layer handle verification.
|
|
90
|
+
// Deliberately NOT deduped via markEmittedToJs: marking an
|
|
91
|
+
// unverified id would suppress the transaction for the rest of
|
|
92
|
+
// the session even if a later delivery verifies. A double-emit of
|
|
93
|
+
// the same JWS is harmless — JS re-validates it.
|
|
92
94
|
await emitTransactionUpdate(transaction, state: "PaymentTransactionStatePurchased",
|
|
93
95
|
jwsRepresentation: jwsRepresentation)
|
|
94
96
|
}
|
|
@@ -104,29 +106,55 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
104
106
|
debugEnabled = call.getBool("debug", false)
|
|
105
107
|
debugLog("init")
|
|
106
108
|
|
|
107
|
-
// Load current entitlements and
|
|
108
|
-
// are visible immediately on app launch
|
|
109
|
+
// Load current entitlements and unfinished transactions, and emit them
|
|
110
|
+
// to JS so existing purchases are visible immediately on app launch
|
|
111
|
+
// (without requiring a manual restore).
|
|
109
112
|
Task {
|
|
110
|
-
|
|
113
|
+
let (entitlements, unfinishedIds) = await self.entitlementsWithUnfinishedIds()
|
|
114
|
+
for result in entitlements {
|
|
111
115
|
switch result {
|
|
112
116
|
case .verified(let transaction):
|
|
113
117
|
if transaction.isUpgraded {
|
|
114
118
|
debugLog("init: skipping upgraded entitlement id=\(transaction.id) product=\(transaction.productID)")
|
|
115
119
|
continue
|
|
116
120
|
}
|
|
117
|
-
|
|
118
|
-
debugLog("init: skipping already-
|
|
121
|
+
guard await self.markEmittedToJs(transaction.id) else {
|
|
122
|
+
debugLog("init: skipping already-emitted entitlement id=\(transaction.id)")
|
|
119
123
|
continue
|
|
120
124
|
}
|
|
121
|
-
self.sk2.processedTransactionIds.insert(transaction.id)
|
|
122
|
-
self.sk2.unfinishedTransactions[String(transaction.id)] = transaction
|
|
123
125
|
await self.emitTransactionUpdate(transaction,
|
|
124
126
|
state: "PaymentTransactionStateRestored",
|
|
125
|
-
jwsRepresentation: result.jwsRepresentation
|
|
127
|
+
jwsRepresentation: result.jwsRepresentation,
|
|
128
|
+
isFinished: !unfinishedIds.contains(transaction.id))
|
|
126
129
|
case .unverified(let transaction, let error):
|
|
127
130
|
debugLog("init: unverified entitlement id=\(transaction.id) product=\(transaction.productID) error=\(error)")
|
|
128
131
|
}
|
|
129
132
|
}
|
|
133
|
+
// Emit every transaction the app hasn't finished yet. This is the
|
|
134
|
+
// only reliable source for unfinished consumables: they never
|
|
135
|
+
// appear in currentEntitlements, so without this pass an
|
|
136
|
+
// unfinished consumable would hang in limbo for the session.
|
|
137
|
+
for await result in Transaction.unfinished {
|
|
138
|
+
switch result {
|
|
139
|
+
case .verified(let transaction):
|
|
140
|
+
if transaction.isUpgraded {
|
|
141
|
+
debugLog("init: skipping upgraded unfinished transaction id=\(transaction.id) product=\(transaction.productID)")
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
guard await self.markEmittedToJs(transaction.id) else { continue }
|
|
145
|
+
debugLog("init: emitting unfinished transaction id=\(transaction.id) product=\(transaction.productID)")
|
|
146
|
+
await self.emitTransactionUpdate(transaction,
|
|
147
|
+
state: "PaymentTransactionStateRestored",
|
|
148
|
+
jwsRepresentation: result.jwsRepresentation,
|
|
149
|
+
isFinished: false)
|
|
150
|
+
case .unverified(let transaction, let error):
|
|
151
|
+
debugLog("init: unverified unfinished transaction id=\(transaction.id) product=\(transaction.productID) error=\(error)")
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Only now start observing Transaction.updates — see the comment
|
|
155
|
+
// on startTransactionObserver() for why starting earlier loses
|
|
156
|
+
// unfinished transactions.
|
|
157
|
+
self.startTransactionObserver()
|
|
130
158
|
call.resolve()
|
|
131
159
|
}
|
|
132
160
|
}
|
|
@@ -206,12 +234,13 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
206
234
|
let jwsRepresentation = verification.jwsRepresentation
|
|
207
235
|
switch verification {
|
|
208
236
|
case .verified(let transaction):
|
|
209
|
-
|
|
237
|
+
_ = await markEmittedToJs(transaction.id)
|
|
210
238
|
await emitTransactionUpdate(transaction,
|
|
211
239
|
state: "PaymentTransactionStatePurchased",
|
|
212
240
|
jwsRepresentation: jwsRepresentation)
|
|
213
241
|
case .unverified(let transaction, _):
|
|
214
|
-
// Emit as Purchased — let JS handle verification
|
|
242
|
+
// Emit as Purchased — let JS handle verification.
|
|
243
|
+
// Deliberately not deduped — see handleTransactionUpdate.
|
|
215
244
|
await emitTransactionUpdate(transaction,
|
|
216
245
|
state: "PaymentTransactionStatePurchased",
|
|
217
246
|
jwsRepresentation: jwsRepresentation)
|
|
@@ -254,9 +283,17 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
254
283
|
debugLog("finish: \(transactionId)")
|
|
255
284
|
|
|
256
285
|
Task {
|
|
257
|
-
|
|
286
|
+
// Atomically claim the transaction on the main thread: read and
|
|
287
|
+
// removal must be one step, or two concurrent finish() calls for
|
|
288
|
+
// the same id could both proceed. Serialized because the detached
|
|
289
|
+
// Transaction.updates observer also mutates the dictionary.
|
|
290
|
+
let transaction: Transaction? = await MainActor.run {
|
|
291
|
+
guard let t = sk2.unfinishedTransactions[transactionId] else { return nil }
|
|
292
|
+
_ = sk2.unfinishedTransactions.removeValue(forKey: transactionId)
|
|
293
|
+
return t
|
|
294
|
+
}
|
|
295
|
+
if let transaction = transaction {
|
|
258
296
|
await transaction.finish()
|
|
259
|
-
sk2.unfinishedTransactions.removeValue(forKey: transactionId)
|
|
260
297
|
// Note: do NOT remove from processedTransactionIds here.
|
|
261
298
|
// Once a transaction ID has been emitted to JS, it must stay in the set
|
|
262
299
|
// for the lifetime of the session to prevent Transaction.updates from
|
|
@@ -289,17 +326,21 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
289
326
|
Task {
|
|
290
327
|
do {
|
|
291
328
|
try await AppStore.sync()
|
|
292
|
-
|
|
329
|
+
let (entitlements, unfinishedIds) = await entitlementsWithUnfinishedIds()
|
|
330
|
+
for result in entitlements {
|
|
293
331
|
switch result {
|
|
294
332
|
case .verified(let transaction):
|
|
295
333
|
if transaction.isUpgraded {
|
|
296
334
|
debugLog("restore: skipping upgraded entitlement id=\(transaction.id) product=\(transaction.productID)")
|
|
297
335
|
continue
|
|
298
336
|
}
|
|
299
|
-
|
|
337
|
+
// Restore deliberately re-emits transactions already
|
|
338
|
+
// delivered this session — mark, but ignore the result.
|
|
339
|
+
_ = await markEmittedToJs(transaction.id)
|
|
300
340
|
await emitTransactionUpdate(transaction,
|
|
301
341
|
state: "PaymentTransactionStateRestored",
|
|
302
|
-
jwsRepresentation: result.jwsRepresentation
|
|
342
|
+
jwsRepresentation: result.jwsRepresentation,
|
|
343
|
+
isFinished: !unfinishedIds.contains(transaction.id))
|
|
303
344
|
case .unverified(let transaction, let error):
|
|
304
345
|
debugLog("restore: unverified entitlement id=\(transaction.id) product=\(transaction.productID) error=\(error)")
|
|
305
346
|
}
|
|
@@ -407,19 +448,63 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
407
448
|
if let expirationDate = transaction.expirationDate, expirationDate < Date() {
|
|
408
449
|
debugLog("clearExpired: finishing expired transaction id=\(transaction.id) product=\(transaction.productID) expired=\(expirationDate)")
|
|
409
450
|
await transaction.finish()
|
|
410
|
-
|
|
451
|
+
await MainActor.run {
|
|
452
|
+
_ = sk2.unfinishedTransactions.removeValue(forKey: String(transaction.id))
|
|
453
|
+
}
|
|
411
454
|
// Note: do NOT remove from processedTransactionIds.
|
|
412
455
|
// The set must grow monotonically to prevent re-delivery via Transaction.updates.
|
|
413
456
|
}
|
|
414
457
|
}
|
|
415
458
|
}
|
|
416
459
|
|
|
460
|
+
/// Current entitlements, plus the ids of every transaction the app has
|
|
461
|
+
/// not finished yet. Used to tell finished entitlements apart from
|
|
462
|
+
/// unfinished ones: finished non-consumables and non-renewing
|
|
463
|
+
/// subscriptions stay in Transaction.currentEntitlements and are
|
|
464
|
+
/// re-emitted at every launch.
|
|
465
|
+
///
|
|
466
|
+
/// The unfinished snapshot is taken AFTER listing entitlements, so an
|
|
467
|
+
/// entitlement is never wrongly reported as finished: anything listed
|
|
468
|
+
/// that is still unfinished is in the snapshot. The only possible error
|
|
469
|
+
/// is the safe one (finished in between, reported as unfinished).
|
|
470
|
+
@available(iOS 15.0, *)
|
|
471
|
+
private func entitlementsWithUnfinishedIds() async -> ([VerificationResult<Transaction>], Set<UInt64>) {
|
|
472
|
+
var entitlements: [VerificationResult<Transaction>] = []
|
|
473
|
+
for await result in Transaction.currentEntitlements {
|
|
474
|
+
entitlements.append(result)
|
|
475
|
+
}
|
|
476
|
+
var ids = Set<UInt64>()
|
|
477
|
+
for await result in Transaction.unfinished {
|
|
478
|
+
ids.insert(result.unsafePayloadValue.id)
|
|
479
|
+
}
|
|
480
|
+
return (entitlements, ids)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/// Atomically check-and-mark a transaction id as emitted to the JS layer.
|
|
484
|
+
/// Returns false when it was already emitted this session. "Emitted" is
|
|
485
|
+
/// unrelated to Apple's finished state — it only prevents duplicate
|
|
486
|
+
/// delivery of the same transaction within one app session. Serialized on
|
|
487
|
+
/// the main thread because the Transaction.updates observer runs in a
|
|
488
|
+
/// detached Task while init()/restore()/purchase() run in their own Tasks.
|
|
489
|
+
@available(iOS 15.0, *)
|
|
490
|
+
private func markEmittedToJs(_ id: UInt64) async -> Bool {
|
|
491
|
+
return await MainActor.run {
|
|
492
|
+
if sk2.processedTransactionIds.contains(id) { return false }
|
|
493
|
+
sk2.processedTransactionIds.insert(id)
|
|
494
|
+
return true
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
417
498
|
@available(iOS 15.0, *)
|
|
418
499
|
private func emitTransactionUpdate(_ transaction: Transaction, state: String,
|
|
419
500
|
errorCode: Int? = nil, errorText: String? = nil,
|
|
420
|
-
jwsRepresentation: String? = nil
|
|
501
|
+
jwsRepresentation: String? = nil,
|
|
502
|
+
isFinished: Bool? = nil) async {
|
|
421
503
|
let transactionId = String(transaction.id)
|
|
422
|
-
|
|
504
|
+
// Serialized on the main thread — see SK2State.unfinishedTransactions.
|
|
505
|
+
await MainActor.run {
|
|
506
|
+
sk2.unfinishedTransactions[transactionId] = transaction
|
|
507
|
+
}
|
|
423
508
|
|
|
424
509
|
var data: [String: Any] = [
|
|
425
510
|
"state": state,
|
|
@@ -443,6 +528,11 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
443
528
|
if let jws = jwsRepresentation {
|
|
444
529
|
data["jwsRepresentation"] = jws
|
|
445
530
|
}
|
|
531
|
+
// Whether the app already called finish() on this transaction. Only
|
|
532
|
+
// set when known (Restored emissions), so JS can skip re-delivery.
|
|
533
|
+
if let isFinished = isFinished {
|
|
534
|
+
data["isFinished"] = isFinished
|
|
535
|
+
}
|
|
446
536
|
|
|
447
537
|
notifyListeners("transactionUpdated", data: data)
|
|
448
538
|
}
|
|
@@ -524,7 +614,7 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
524
614
|
private func paymentModeToString(_ mode: Product.SubscriptionOffer.PaymentMode) -> String {
|
|
525
615
|
switch mode {
|
|
526
616
|
case .payAsYouGo: return "PayAsYouGo"
|
|
527
|
-
case .payUpFront: return "
|
|
617
|
+
case .payUpFront: return "UpFront"
|
|
528
618
|
case .freeTrial: return "FreeTrial"
|
|
529
619
|
default: return "PayAsYouGo"
|
|
530
620
|
}
|