capacitor-plugin-cdv-purchase 13.15.0 → 13.15.2
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/{ios/CapacitorPluginPurchase.podspec → CapacitorPluginCdvPurchase.podspec} +2 -2
- package/Package.swift +27 -0
- package/dist/index.js +117 -15
- package/dist/index.js.map +1 -1
- package/ios/Sources/PurchasePlugin/PurchasePlugin.swift +86 -8
- package/package.json +5 -3
- package/www/store.d.ts +23 -3
- package/www/store.js +117 -15
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'json'
|
|
2
2
|
|
|
3
|
-
package = JSON.parse(File.read(File.join(__dir__, '
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
4
|
|
|
5
5
|
Pod::Spec.new do |s|
|
|
6
6
|
s.name = 'CapacitorPluginCdvPurchase'
|
|
@@ -10,7 +10,7 @@ Pod::Spec.new do |s|
|
|
|
10
10
|
s.homepage = package['repository']['url']
|
|
11
11
|
s.author = 'Jean-Christophe Hoelt'
|
|
12
12
|
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
|
-
s.source_files = 'Sources/PurchasePlugin/**/*.{swift,h,m}'
|
|
13
|
+
s.source_files = 'ios/Sources/PurchasePlugin/**/*.{swift,h,m}'
|
|
14
14
|
s.ios.deployment_target = '13.0'
|
|
15
15
|
s.swift_version = '5.9'
|
|
16
16
|
s.dependency 'Capacitor'
|
package/Package.swift
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "CapacitorPluginCdvPurchase",
|
|
6
|
+
platforms: [.iOS(.v15)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapacitorPluginCdvPurchase",
|
|
10
|
+
targets: ["PurchasePlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", "6.0.0"..<"10.0.0")
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "PurchasePlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Sources/PurchasePlugin",
|
|
23
|
+
swiftSettings: [
|
|
24
|
+
.enableExperimentalFeature("NonescapableTypes")
|
|
25
|
+
])
|
|
26
|
+
]
|
|
27
|
+
)
|
package/dist/index.js
CHANGED
|
@@ -683,9 +683,39 @@ var CdvPurchase;
|
|
|
683
683
|
body.introPriceMicros = intro.priceMicros;
|
|
684
684
|
}
|
|
685
685
|
}
|
|
686
|
+
// Only include the products array once per day to reduce request size.
|
|
687
|
+
// The products array is used by the validator for analytics (price history,
|
|
688
|
+
// transaction amounts) but not for the validation flow itself.
|
|
689
|
+
// The top-level price/currency fields serve as fallback when products is omitted.
|
|
690
|
+
if (!this.shouldSendProducts()) {
|
|
691
|
+
delete body.products;
|
|
692
|
+
}
|
|
686
693
|
return body;
|
|
687
694
|
});
|
|
688
695
|
}
|
|
696
|
+
/** Check if the products array should be included in the validation request.
|
|
697
|
+
* Returns true at most once per day, tracked via localStorage. */
|
|
698
|
+
shouldSendProducts() {
|
|
699
|
+
var _a, _b;
|
|
700
|
+
const STORAGE_KEY = 'cdvpurchase_has_sent_products_in_validation';
|
|
701
|
+
const ONE_DAY_MS = 86400000;
|
|
702
|
+
try {
|
|
703
|
+
const stored = (_a = window.localStorage) === null || _a === void 0 ? void 0 : _a.getItem(STORAGE_KEY);
|
|
704
|
+
if (stored) {
|
|
705
|
+
const lastSent = parseInt(stored, 10);
|
|
706
|
+
if (!isNaN(lastSent) && (Date.now() - lastSent) < ONE_DAY_MS) {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
// Send products this time and record the date
|
|
711
|
+
(_b = window.localStorage) === null || _b === void 0 ? void 0 : _b.setItem(STORAGE_KEY, String(Date.now()));
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
catch (_e) {
|
|
715
|
+
// localStorage not available (e.g. private browsing)
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
689
719
|
removeExpiredCache() {
|
|
690
720
|
const now = +new Date();
|
|
691
721
|
const deleteList = [];
|
|
@@ -1038,6 +1068,8 @@ var CdvPurchase;
|
|
|
1038
1068
|
/** Those platforms have reported that their receipts are ready */
|
|
1039
1069
|
this.platformWithReceiptsReady = [];
|
|
1040
1070
|
this.lastTransactionState = {};
|
|
1071
|
+
/** Remember the first transactionId for each subscription dedup key */
|
|
1072
|
+
this.subscriptionFirstTransactionId = {};
|
|
1041
1073
|
/** Store the listener's latest calling time (in ms) for a given transaction at a given state */
|
|
1042
1074
|
this.lastCallTimeForState = {};
|
|
1043
1075
|
this.updatedReceiptsToProcess = [];
|
|
@@ -1047,6 +1079,26 @@ var CdvPurchase;
|
|
|
1047
1079
|
static makeTransactionToken(transaction) {
|
|
1048
1080
|
return transaction.platform + '|' + transaction.transactionId;
|
|
1049
1081
|
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Create a subscription dedup key from a transaction.
|
|
1084
|
+
*
|
|
1085
|
+
* StoreKit 2 can deliver the same subscription purchase event twice with
|
|
1086
|
+
* different `transactionId` but identical `originalTransactionId` and
|
|
1087
|
+
* `purchaseDate`. This key groups those duplicates together so only one
|
|
1088
|
+
* `approved`/`finished` event is surfaced per billing period.
|
|
1089
|
+
*
|
|
1090
|
+
* Returns `undefined` for non-subscription transactions (no `originalTransactionId`).
|
|
1091
|
+
*/
|
|
1092
|
+
static makeSubscriptionKey(transaction) {
|
|
1093
|
+
if (transaction.platform !== CdvPurchase.Platform.APPLE_APPSTORE)
|
|
1094
|
+
return undefined;
|
|
1095
|
+
const skTransaction = transaction;
|
|
1096
|
+
if (!skTransaction.originalTransactionId)
|
|
1097
|
+
return undefined;
|
|
1098
|
+
if (!transaction.purchaseDate)
|
|
1099
|
+
return undefined;
|
|
1100
|
+
return transaction.platform + '|' + skTransaction.originalTransactionId + '|' + transaction.purchaseDate.getTime();
|
|
1101
|
+
}
|
|
1050
1102
|
/**
|
|
1051
1103
|
* Set the list of supported platforms.
|
|
1052
1104
|
*
|
|
@@ -1126,16 +1178,32 @@ var CdvPurchase;
|
|
|
1126
1178
|
const transactionToken = StoreAdapterListener.makeTransactionToken(transaction);
|
|
1127
1179
|
const tokenWithState = transactionToken + '@' + transaction.state;
|
|
1128
1180
|
const lastState = this.lastTransactionState[transactionToken];
|
|
1181
|
+
const subscriptionKey = StoreAdapterListener.makeSubscriptionKey(transaction);
|
|
1182
|
+
const isSubscriptionDuplicate = !!subscriptionKey
|
|
1183
|
+
&& !!this.subscriptionFirstTransactionId[subscriptionKey]
|
|
1184
|
+
&& this.subscriptionFirstTransactionId[subscriptionKey] !== transaction.transactionId;
|
|
1185
|
+
// Remember the first transactionId for this subscription key
|
|
1186
|
+
if (subscriptionKey && !this.subscriptionFirstTransactionId[subscriptionKey]) {
|
|
1187
|
+
this.subscriptionFirstTransactionId[subscriptionKey] = transaction.transactionId;
|
|
1188
|
+
}
|
|
1129
1189
|
// Retrigger "approved", so validation is rerun on potential update.
|
|
1130
1190
|
if (transaction.state === CdvPurchase.TransactionState.APPROVED) {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
this.
|
|
1135
|
-
this.delegate.approvedCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_approved');
|
|
1191
|
+
if (isSubscriptionDuplicate) {
|
|
1192
|
+
// Finish the duplicate at the native level so StoreKit won't re-deliver it
|
|
1193
|
+
this.log.debug(`Auto-finishing subscription duplicate ${transactionToken}, already processed as ${this.subscriptionFirstTransactionId[subscriptionKey]}`);
|
|
1194
|
+
this.delegate.finishDuplicate(transaction);
|
|
1136
1195
|
}
|
|
1137
1196
|
else {
|
|
1138
|
-
|
|
1197
|
+
// Use subscription key for dedup when available, otherwise fall back to transactionToken
|
|
1198
|
+
const dedupKey = subscriptionKey ? subscriptionKey + '@' + transaction.state : tokenWithState;
|
|
1199
|
+
const lastCalled = (_a = this.lastCallTimeForState[dedupKey]) !== null && _a !== void 0 ? _a : 0;
|
|
1200
|
+
if (now - lastCalled > 60000) {
|
|
1201
|
+
this.lastCallTimeForState[dedupKey] = now;
|
|
1202
|
+
this.delegate.approvedCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_approved');
|
|
1203
|
+
}
|
|
1204
|
+
else {
|
|
1205
|
+
this.log.debug(`Skipping ${tokenWithState}, because it has been last called ${lastCalled > 0 ? Math.round(now - lastCalled) + 'ms ago (' + now + '-' + lastCalled + ')' : 'never'}`);
|
|
1206
|
+
}
|
|
1139
1207
|
}
|
|
1140
1208
|
}
|
|
1141
1209
|
else if (lastState !== transaction.state) {
|
|
@@ -1144,15 +1212,31 @@ var CdvPurchase;
|
|
|
1144
1212
|
this.delegate.initiatedCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_initiated');
|
|
1145
1213
|
}
|
|
1146
1214
|
else if (transaction.state === CdvPurchase.TransactionState.FINISHED) {
|
|
1147
|
-
|
|
1148
|
-
|
|
1215
|
+
if (isSubscriptionDuplicate) {
|
|
1216
|
+
// Finish the duplicate at the native level so StoreKit won't re-deliver it
|
|
1217
|
+
this.log.debug(`Auto-finishing subscription duplicate ${transactionToken}, already processed as ${this.subscriptionFirstTransactionId[subscriptionKey]}`);
|
|
1218
|
+
this.delegate.finishDuplicate(transaction);
|
|
1219
|
+
}
|
|
1220
|
+
else {
|
|
1221
|
+
this.lastCallTimeForState[tokenWithState] = now;
|
|
1222
|
+
this.delegate.finishedCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_finished');
|
|
1223
|
+
}
|
|
1149
1224
|
}
|
|
1150
1225
|
else if (transaction.state === CdvPurchase.TransactionState.PENDING) {
|
|
1151
1226
|
this.lastCallTimeForState[tokenWithState] = now;
|
|
1152
1227
|
this.delegate.pendingCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_pending');
|
|
1153
1228
|
}
|
|
1154
1229
|
}
|
|
1155
|
-
|
|
1230
|
+
// Mark subscription duplicates as FINISHED so the re-processing triggered
|
|
1231
|
+
// by finishDuplicate()'s receiptsUpdated.call() won't enter the FINISHED
|
|
1232
|
+
// handler and call finishDuplicate() a second time on an already-finished
|
|
1233
|
+
// native transaction.
|
|
1234
|
+
if (isSubscriptionDuplicate) {
|
|
1235
|
+
this.lastTransactionState[transactionToken] = CdvPurchase.TransactionState.FINISHED;
|
|
1236
|
+
}
|
|
1237
|
+
else {
|
|
1238
|
+
this.lastTransactionState[transactionToken] = transaction.state;
|
|
1239
|
+
}
|
|
1156
1240
|
});
|
|
1157
1241
|
});
|
|
1158
1242
|
}
|
|
@@ -1561,7 +1645,7 @@ var CdvPurchase;
|
|
|
1561
1645
|
/**
|
|
1562
1646
|
* Current release number of the plugin.
|
|
1563
1647
|
*/
|
|
1564
|
-
CdvPurchase.PLUGIN_VERSION = '13.15.
|
|
1648
|
+
CdvPurchase.PLUGIN_VERSION = '13.15.2';
|
|
1565
1649
|
/**
|
|
1566
1650
|
* Entry class of the plugin.
|
|
1567
1651
|
*/
|
|
@@ -1641,6 +1725,12 @@ var CdvPurchase;
|
|
|
1641
1725
|
finishedCallbacks: this.finishedCallbacks,
|
|
1642
1726
|
pendingCallbacks: this.pendingCallbacks,
|
|
1643
1727
|
receiptsReadyCallbacks: this.receiptsReadyCallbacks,
|
|
1728
|
+
finishDuplicate: (transaction) => {
|
|
1729
|
+
const adapter = this.adapters.findReady(transaction.platform);
|
|
1730
|
+
if (adapter) {
|
|
1731
|
+
adapter.finish(transaction);
|
|
1732
|
+
}
|
|
1733
|
+
},
|
|
1644
1734
|
}, this.log);
|
|
1645
1735
|
this.transactionStateMonitors = new CdvPurchase.Internal.TransactionStateMonitors(this.when());
|
|
1646
1736
|
this._validator = new CdvPurchase.Internal.Validator({
|
|
@@ -3475,16 +3565,20 @@ var CdvPurchase;
|
|
|
3475
3565
|
this.receiptsUpdated.call();
|
|
3476
3566
|
return resolve(undefined);
|
|
3477
3567
|
}
|
|
3568
|
+
const wasAlreadyFinished = transaction.state === CdvPurchase.TransactionState.FINISHED;
|
|
3478
3569
|
const success = () => {
|
|
3479
3570
|
transaction.state = CdvPurchase.TransactionState.FINISHED;
|
|
3480
|
-
|
|
3571
|
+
if (!wasAlreadyFinished) {
|
|
3572
|
+
this.receiptsUpdated.call();
|
|
3573
|
+
}
|
|
3481
3574
|
resolve(undefined);
|
|
3482
3575
|
};
|
|
3483
3576
|
const error = (msg) => {
|
|
3484
3577
|
var _a, _b;
|
|
3485
3578
|
if (msg === null || msg === void 0 ? void 0 : msg.includes('[#CdvPurchase:100]')) {
|
|
3486
|
-
// already finished
|
|
3487
|
-
|
|
3579
|
+
// already finished at the native level
|
|
3580
|
+
transaction.state = CdvPurchase.TransactionState.FINISHED;
|
|
3581
|
+
resolve(undefined);
|
|
3488
3582
|
}
|
|
3489
3583
|
else {
|
|
3490
3584
|
resolve(appStoreError(CdvPurchase.ErrorCode.FINISH, 'Failed to finish transaction', (_b = (_a = transaction.products[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null));
|
|
@@ -4222,13 +4316,14 @@ var CdvPurchase;
|
|
|
4222
4316
|
/** Retrieve the storefront country code from StoreKit */
|
|
4223
4317
|
getStorefront() {
|
|
4224
4318
|
return new Promise((resolve) => {
|
|
4225
|
-
//
|
|
4319
|
+
// Use StoreKit 2's Storefront.current via the SK2 plugin,
|
|
4320
|
+
// which works on Mac Catalyst where SK1's storefront is nil.
|
|
4226
4321
|
window.cordova.exec((countryCode) => {
|
|
4227
4322
|
resolve(countryCode || undefined);
|
|
4228
4323
|
}, (err) => {
|
|
4229
4324
|
log('getStorefront failed: ' + err);
|
|
4230
4325
|
resolve(undefined);
|
|
4231
|
-
}, "
|
|
4326
|
+
}, "StoreKit2Plugin", "getStorefront", []);
|
|
4232
4327
|
});
|
|
4233
4328
|
}
|
|
4234
4329
|
loadReceipts(callback, errorCb) {
|
|
@@ -4756,6 +4851,13 @@ var CdvPurchase;
|
|
|
4756
4851
|
offerType: 'Default',
|
|
4757
4852
|
}, decorator));
|
|
4758
4853
|
}
|
|
4854
|
+
// Ensure the default offer is always first so that getOffer()
|
|
4855
|
+
// without arguments returns it instead of a discount offer.
|
|
4856
|
+
const defaultIndex = this.offers.findIndex(o => o.id === AppleAppStore.DEFAULT_OFFER_ID);
|
|
4857
|
+
if (defaultIndex > 0) {
|
|
4858
|
+
const [defaultOffer] = this.offers.splice(defaultIndex, 1);
|
|
4859
|
+
this.offers.unshift(defaultOffer);
|
|
4860
|
+
}
|
|
4759
4861
|
function hasIntroductoryOffer(product) {
|
|
4760
4862
|
return product.offers.filter(offer => {
|
|
4761
4863
|
const skOffer = offer;
|
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;;;;"}
|
|
@@ -7,6 +7,10 @@ private class SK2State {
|
|
|
7
7
|
var products: [String: Product] = [:]
|
|
8
8
|
var unfinishedTransactions: [String: Transaction] = [:]
|
|
9
9
|
var transactionObserverTask: Task<Void, Never>?
|
|
10
|
+
/// Transaction IDs already emitted to JS. Prevents duplicate delivery when
|
|
11
|
+
/// both init() (via currentEntitlements) and Transaction.updates deliver
|
|
12
|
+
/// the same transaction. Access must be serialized on the main thread.
|
|
13
|
+
var processedTransactionIds: Set<UInt64> = []
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
@objc(PurchasePlugin)
|
|
@@ -69,6 +73,18 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
69
73
|
let jwsRepresentation = result.jwsRepresentation
|
|
70
74
|
switch result {
|
|
71
75
|
case .verified(let transaction):
|
|
76
|
+
// Serialize access to processedTransactionIds on the main thread
|
|
77
|
+
// since this method runs from a detached Task while init()/purchase()
|
|
78
|
+
// also mutate the set.
|
|
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
|
|
86
|
+
}
|
|
87
|
+
guard shouldEmit else { return }
|
|
72
88
|
await emitTransactionUpdate(transaction, state: "PaymentTransactionStatePurchased",
|
|
73
89
|
jwsRepresentation: jwsRepresentation)
|
|
74
90
|
case .unverified(let transaction, _):
|
|
@@ -88,11 +104,27 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
88
104
|
debugEnabled = call.getBool("debug", false)
|
|
89
105
|
debugLog("init")
|
|
90
106
|
|
|
91
|
-
//
|
|
107
|
+
// Load current entitlements and emit to JS so existing subscriptions
|
|
108
|
+
// are visible immediately on app launch (without requiring a manual restore).
|
|
92
109
|
Task {
|
|
93
110
|
for await result in Transaction.currentEntitlements {
|
|
94
|
-
|
|
111
|
+
switch result {
|
|
112
|
+
case .verified(let transaction):
|
|
113
|
+
if transaction.isUpgraded {
|
|
114
|
+
debugLog("init: skipping upgraded entitlement id=\(transaction.id) product=\(transaction.productID)")
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
if self.sk2.processedTransactionIds.contains(transaction.id) {
|
|
118
|
+
debugLog("init: skipping already-processed entitlement id=\(transaction.id)")
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
self.sk2.processedTransactionIds.insert(transaction.id)
|
|
95
122
|
self.sk2.unfinishedTransactions[String(transaction.id)] = transaction
|
|
123
|
+
await self.emitTransactionUpdate(transaction,
|
|
124
|
+
state: "PaymentTransactionStateRestored",
|
|
125
|
+
jwsRepresentation: result.jwsRepresentation)
|
|
126
|
+
case .unverified(let transaction, let error):
|
|
127
|
+
debugLog("init: unverified entitlement id=\(transaction.id) product=\(transaction.productID) error=\(error)")
|
|
96
128
|
}
|
|
97
129
|
}
|
|
98
130
|
call.resolve()
|
|
@@ -162,12 +194,16 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
162
194
|
options.insert(.quantity(quantity))
|
|
163
195
|
}
|
|
164
196
|
|
|
197
|
+
// Clear expired unfinished transactions that could block the purchase
|
|
198
|
+
await clearExpiredUnfinishedTransactions()
|
|
199
|
+
|
|
165
200
|
let result = try await product.purchase(options: options)
|
|
166
201
|
switch result {
|
|
167
202
|
case .success(let verification):
|
|
168
203
|
let jwsRepresentation = verification.jwsRepresentation
|
|
169
204
|
switch verification {
|
|
170
205
|
case .verified(let transaction):
|
|
206
|
+
sk2.processedTransactionIds.insert(transaction.id)
|
|
171
207
|
await emitTransactionUpdate(transaction,
|
|
172
208
|
state: "PaymentTransactionStatePurchased",
|
|
173
209
|
jwsRepresentation: jwsRepresentation)
|
|
@@ -218,6 +254,10 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
218
254
|
if let transaction = sk2.unfinishedTransactions[transactionId] {
|
|
219
255
|
await transaction.finish()
|
|
220
256
|
sk2.unfinishedTransactions.removeValue(forKey: transactionId)
|
|
257
|
+
// Note: do NOT remove from processedTransactionIds here.
|
|
258
|
+
// Once a transaction ID has been emitted to JS, it must stay in the set
|
|
259
|
+
// for the lifetime of the session to prevent Transaction.updates from
|
|
260
|
+
// re-delivering it.
|
|
221
261
|
notifyListeners("transactionUpdated", data: [
|
|
222
262
|
"state": "PaymentTransactionStateFinished",
|
|
223
263
|
"transactionIdentifier": transactionId,
|
|
@@ -247,10 +287,18 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
247
287
|
do {
|
|
248
288
|
try await AppStore.sync()
|
|
249
289
|
for await result in Transaction.currentEntitlements {
|
|
250
|
-
|
|
290
|
+
switch result {
|
|
291
|
+
case .verified(let transaction):
|
|
292
|
+
if transaction.isUpgraded {
|
|
293
|
+
debugLog("restore: skipping upgraded entitlement id=\(transaction.id) product=\(transaction.productID)")
|
|
294
|
+
continue
|
|
295
|
+
}
|
|
296
|
+
sk2.processedTransactionIds.insert(transaction.id)
|
|
251
297
|
await emitTransactionUpdate(transaction,
|
|
252
298
|
state: "PaymentTransactionStateRestored",
|
|
253
299
|
jwsRepresentation: result.jwsRepresentation)
|
|
300
|
+
case .unverified(let transaction, let error):
|
|
301
|
+
debugLog("restore: unverified entitlement id=\(transaction.id) product=\(transaction.productID) error=\(error)")
|
|
254
302
|
}
|
|
255
303
|
}
|
|
256
304
|
notifyListeners("restoreCompleted", data: [:])
|
|
@@ -320,19 +368,49 @@ public class PurchasePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
320
368
|
}
|
|
321
369
|
|
|
322
370
|
@objc func getStorefront(_ call: CAPPluginCall) {
|
|
323
|
-
//
|
|
324
|
-
// which matches the plugin's minimum deployment target.
|
|
371
|
+
// Try StoreKit 1 first (available from iOS 13).
|
|
325
372
|
if let storefront = SKPaymentQueue.default().storefront {
|
|
326
373
|
debugLog("getStorefront: \(storefront.countryCode)")
|
|
327
374
|
call.resolve(["countryCode": storefront.countryCode])
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
// Fallback to StoreKit 2's Storefront.current (available iOS 15+).
|
|
378
|
+
// This works on Mac Catalyst where SK1's storefront is nil.
|
|
379
|
+
if #available(iOS 15.0, macOS 12.0, *) {
|
|
380
|
+
Task {
|
|
381
|
+
if let storefront = await Storefront.current {
|
|
382
|
+
debugLog("getStorefront (SK2 fallback): \(storefront.countryCode)")
|
|
383
|
+
call.resolve(["countryCode": storefront.countryCode])
|
|
384
|
+
} else {
|
|
385
|
+
debugLog("getStorefront: storefront not available")
|
|
386
|
+
call.reject("Storefront not available")
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return
|
|
331
390
|
}
|
|
391
|
+
debugLog("getStorefront: storefront not available")
|
|
392
|
+
call.reject("Storefront not available")
|
|
332
393
|
}
|
|
333
394
|
|
|
334
395
|
// MARK: - Helpers
|
|
335
396
|
|
|
397
|
+
/// Finish any unfinished transactions whose subscription has already expired.
|
|
398
|
+
/// Stale unfinished transactions can block product.purchase() from initiating
|
|
399
|
+
/// a new purchase flow (confirmed on Apple Developer Forums).
|
|
400
|
+
@available(iOS 15.0, *)
|
|
401
|
+
private func clearExpiredUnfinishedTransactions() async {
|
|
402
|
+
for await result in Transaction.unfinished {
|
|
403
|
+
guard case .verified(let transaction) = result else { continue }
|
|
404
|
+
if let expirationDate = transaction.expirationDate, expirationDate < Date() {
|
|
405
|
+
debugLog("clearExpired: finishing expired transaction id=\(transaction.id) product=\(transaction.productID) expired=\(expirationDate)")
|
|
406
|
+
await transaction.finish()
|
|
407
|
+
sk2.unfinishedTransactions.removeValue(forKey: String(transaction.id))
|
|
408
|
+
// Note: do NOT remove from processedTransactionIds.
|
|
409
|
+
// The set must grow monotonically to prevent re-delivery via Transaction.updates.
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
336
414
|
@available(iOS 15.0, *)
|
|
337
415
|
private func emitTransactionUpdate(_ transaction: Transaction, state: String,
|
|
338
416
|
errorCode: Int? = nil, errorText: String? = nil,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capacitor-plugin-cdv-purchase",
|
|
3
|
-
"version": "13.15.
|
|
3
|
+
"version": "13.15.2",
|
|
4
4
|
"description": "In-App Purchases for Capacitor — iOS (StoreKit 2) and Android (Google Play Billing)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"android": { "src": "android" }
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@capacitor/core": "^6.0.0"
|
|
20
|
+
"@capacitor/core": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@capacitor/core": "^6.0.0",
|
|
@@ -35,7 +35,9 @@
|
|
|
35
35
|
"www/",
|
|
36
36
|
"android/",
|
|
37
37
|
"!android/build",
|
|
38
|
-
"ios/"
|
|
38
|
+
"ios/",
|
|
39
|
+
"Package.swift",
|
|
40
|
+
"CapacitorPluginCdvPurchase.podspec"
|
|
39
41
|
],
|
|
40
42
|
"license": "MIT",
|
|
41
43
|
"repository": {
|
package/www/store.d.ts
CHANGED
|
@@ -376,6 +376,9 @@ declare namespace CdvPurchase {
|
|
|
376
376
|
private runOnReceipt;
|
|
377
377
|
private runValidatorFunction;
|
|
378
378
|
private buildRequestBody;
|
|
379
|
+
/** Check if the products array should be included in the validation request.
|
|
380
|
+
* Returns true at most once per day, tracked via localStorage. */
|
|
381
|
+
private shouldSendProducts;
|
|
379
382
|
/**
|
|
380
383
|
* For each md5-hashed values of the validator request's ".transaction" field,
|
|
381
384
|
* store the response from the server.
|
|
@@ -564,6 +567,8 @@ declare namespace CdvPurchase {
|
|
|
564
567
|
updatedCallbacks: Callbacks<Product>;
|
|
565
568
|
updatedReceiptCallbacks: Callbacks<Receipt>;
|
|
566
569
|
receiptsReadyCallbacks: Callbacks<void>;
|
|
570
|
+
/** Finish a duplicate subscription transaction at the native level so StoreKit won't re-deliver it */
|
|
571
|
+
finishDuplicate(transaction: Transaction): void;
|
|
567
572
|
}
|
|
568
573
|
/**
|
|
569
574
|
* Monitor the updates for products and receipt.
|
|
@@ -582,6 +587,21 @@ declare namespace CdvPurchase {
|
|
|
582
587
|
[transactionToken: string]: TransactionState;
|
|
583
588
|
};
|
|
584
589
|
static makeTransactionToken(transaction: Transaction): string;
|
|
590
|
+
/**
|
|
591
|
+
* Create a subscription dedup key from a transaction.
|
|
592
|
+
*
|
|
593
|
+
* StoreKit 2 can deliver the same subscription purchase event twice with
|
|
594
|
+
* different `transactionId` but identical `originalTransactionId` and
|
|
595
|
+
* `purchaseDate`. This key groups those duplicates together so only one
|
|
596
|
+
* `approved`/`finished` event is surfaced per billing period.
|
|
597
|
+
*
|
|
598
|
+
* Returns `undefined` for non-subscription transactions (no `originalTransactionId`).
|
|
599
|
+
*/
|
|
600
|
+
static makeSubscriptionKey(transaction: Transaction): string | undefined;
|
|
601
|
+
/** Remember the first transactionId for each subscription dedup key */
|
|
602
|
+
subscriptionFirstTransactionId: {
|
|
603
|
+
[subscriptionKey: string]: string;
|
|
604
|
+
};
|
|
585
605
|
/** Store the listener's latest calling time (in ms) for a given transaction at a given state */
|
|
586
606
|
lastCallTimeForState: {
|
|
587
607
|
[transactionTokenWithState: string]: number;
|
|
@@ -864,7 +884,7 @@ declare namespace CdvPurchase {
|
|
|
864
884
|
/**
|
|
865
885
|
* Current release number of the plugin.
|
|
866
886
|
*/
|
|
867
|
-
const PLUGIN_VERSION = "13.15.
|
|
887
|
+
const PLUGIN_VERSION = "13.15.2";
|
|
868
888
|
/**
|
|
869
889
|
* Entry class of the plugin.
|
|
870
890
|
*/
|
|
@@ -6168,8 +6188,8 @@ declare namespace CdvPurchase {
|
|
|
6168
6188
|
trialPeriodUnit?: SubscriptionPeriodUnit;
|
|
6169
6189
|
/** Metadata about the user's device */
|
|
6170
6190
|
device?: CdvPurchase.Validator.DeviceInfo;
|
|
6171
|
-
/** List of products available in the store */
|
|
6172
|
-
products
|
|
6191
|
+
/** List of products available in the store. Included at most once per day. */
|
|
6192
|
+
products?: {
|
|
6173
6193
|
/** Type of product (subscription, consumable, etc.) */
|
|
6174
6194
|
type: ProductType;
|
|
6175
6195
|
/** Product identifier on the store (unique per platform) */
|
package/www/store.js
CHANGED
|
@@ -688,9 +688,39 @@ var CdvPurchase;
|
|
|
688
688
|
body.introPriceMicros = intro.priceMicros;
|
|
689
689
|
}
|
|
690
690
|
}
|
|
691
|
+
// Only include the products array once per day to reduce request size.
|
|
692
|
+
// The products array is used by the validator for analytics (price history,
|
|
693
|
+
// transaction amounts) but not for the validation flow itself.
|
|
694
|
+
// The top-level price/currency fields serve as fallback when products is omitted.
|
|
695
|
+
if (!this.shouldSendProducts()) {
|
|
696
|
+
delete body.products;
|
|
697
|
+
}
|
|
691
698
|
return body;
|
|
692
699
|
});
|
|
693
700
|
}
|
|
701
|
+
/** Check if the products array should be included in the validation request.
|
|
702
|
+
* Returns true at most once per day, tracked via localStorage. */
|
|
703
|
+
shouldSendProducts() {
|
|
704
|
+
var _a, _b;
|
|
705
|
+
const STORAGE_KEY = 'cdvpurchase_has_sent_products_in_validation';
|
|
706
|
+
const ONE_DAY_MS = 86400000;
|
|
707
|
+
try {
|
|
708
|
+
const stored = (_a = window.localStorage) === null || _a === void 0 ? void 0 : _a.getItem(STORAGE_KEY);
|
|
709
|
+
if (stored) {
|
|
710
|
+
const lastSent = parseInt(stored, 10);
|
|
711
|
+
if (!isNaN(lastSent) && (Date.now() - lastSent) < ONE_DAY_MS) {
|
|
712
|
+
return false;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
// Send products this time and record the date
|
|
716
|
+
(_b = window.localStorage) === null || _b === void 0 ? void 0 : _b.setItem(STORAGE_KEY, String(Date.now()));
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
catch (_e) {
|
|
720
|
+
// localStorage not available (e.g. private browsing)
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
694
724
|
removeExpiredCache() {
|
|
695
725
|
const now = +new Date();
|
|
696
726
|
const deleteList = [];
|
|
@@ -1047,6 +1077,8 @@ var CdvPurchase;
|
|
|
1047
1077
|
/** Those platforms have reported that their receipts are ready */
|
|
1048
1078
|
this.platformWithReceiptsReady = [];
|
|
1049
1079
|
this.lastTransactionState = {};
|
|
1080
|
+
/** Remember the first transactionId for each subscription dedup key */
|
|
1081
|
+
this.subscriptionFirstTransactionId = {};
|
|
1050
1082
|
/** Store the listener's latest calling time (in ms) for a given transaction at a given state */
|
|
1051
1083
|
this.lastCallTimeForState = {};
|
|
1052
1084
|
this.updatedReceiptsToProcess = [];
|
|
@@ -1056,6 +1088,26 @@ var CdvPurchase;
|
|
|
1056
1088
|
static makeTransactionToken(transaction) {
|
|
1057
1089
|
return transaction.platform + '|' + transaction.transactionId;
|
|
1058
1090
|
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Create a subscription dedup key from a transaction.
|
|
1093
|
+
*
|
|
1094
|
+
* StoreKit 2 can deliver the same subscription purchase event twice with
|
|
1095
|
+
* different `transactionId` but identical `originalTransactionId` and
|
|
1096
|
+
* `purchaseDate`. This key groups those duplicates together so only one
|
|
1097
|
+
* `approved`/`finished` event is surfaced per billing period.
|
|
1098
|
+
*
|
|
1099
|
+
* Returns `undefined` for non-subscription transactions (no `originalTransactionId`).
|
|
1100
|
+
*/
|
|
1101
|
+
static makeSubscriptionKey(transaction) {
|
|
1102
|
+
if (transaction.platform !== CdvPurchase.Platform.APPLE_APPSTORE)
|
|
1103
|
+
return undefined;
|
|
1104
|
+
const skTransaction = transaction;
|
|
1105
|
+
if (!skTransaction.originalTransactionId)
|
|
1106
|
+
return undefined;
|
|
1107
|
+
if (!transaction.purchaseDate)
|
|
1108
|
+
return undefined;
|
|
1109
|
+
return transaction.platform + '|' + skTransaction.originalTransactionId + '|' + transaction.purchaseDate.getTime();
|
|
1110
|
+
}
|
|
1059
1111
|
/**
|
|
1060
1112
|
* Set the list of supported platforms.
|
|
1061
1113
|
*
|
|
@@ -1135,16 +1187,32 @@ var CdvPurchase;
|
|
|
1135
1187
|
const transactionToken = StoreAdapterListener.makeTransactionToken(transaction);
|
|
1136
1188
|
const tokenWithState = transactionToken + '@' + transaction.state;
|
|
1137
1189
|
const lastState = this.lastTransactionState[transactionToken];
|
|
1190
|
+
const subscriptionKey = StoreAdapterListener.makeSubscriptionKey(transaction);
|
|
1191
|
+
const isSubscriptionDuplicate = !!subscriptionKey
|
|
1192
|
+
&& !!this.subscriptionFirstTransactionId[subscriptionKey]
|
|
1193
|
+
&& this.subscriptionFirstTransactionId[subscriptionKey] !== transaction.transactionId;
|
|
1194
|
+
// Remember the first transactionId for this subscription key
|
|
1195
|
+
if (subscriptionKey && !this.subscriptionFirstTransactionId[subscriptionKey]) {
|
|
1196
|
+
this.subscriptionFirstTransactionId[subscriptionKey] = transaction.transactionId;
|
|
1197
|
+
}
|
|
1138
1198
|
// Retrigger "approved", so validation is rerun on potential update.
|
|
1139
1199
|
if (transaction.state === CdvPurchase.TransactionState.APPROVED) {
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
this.
|
|
1144
|
-
this.delegate.approvedCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_approved');
|
|
1200
|
+
if (isSubscriptionDuplicate) {
|
|
1201
|
+
// Finish the duplicate at the native level so StoreKit won't re-deliver it
|
|
1202
|
+
this.log.debug(`Auto-finishing subscription duplicate ${transactionToken}, already processed as ${this.subscriptionFirstTransactionId[subscriptionKey]}`);
|
|
1203
|
+
this.delegate.finishDuplicate(transaction);
|
|
1145
1204
|
}
|
|
1146
1205
|
else {
|
|
1147
|
-
|
|
1206
|
+
// Use subscription key for dedup when available, otherwise fall back to transactionToken
|
|
1207
|
+
const dedupKey = subscriptionKey ? subscriptionKey + '@' + transaction.state : tokenWithState;
|
|
1208
|
+
const lastCalled = (_a = this.lastCallTimeForState[dedupKey]) !== null && _a !== void 0 ? _a : 0;
|
|
1209
|
+
if (now - lastCalled > 60000) {
|
|
1210
|
+
this.lastCallTimeForState[dedupKey] = now;
|
|
1211
|
+
this.delegate.approvedCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_approved');
|
|
1212
|
+
}
|
|
1213
|
+
else {
|
|
1214
|
+
this.log.debug(`Skipping ${tokenWithState}, because it has been last called ${lastCalled > 0 ? Math.round(now - lastCalled) + 'ms ago (' + now + '-' + lastCalled + ')' : 'never'}`);
|
|
1215
|
+
}
|
|
1148
1216
|
}
|
|
1149
1217
|
}
|
|
1150
1218
|
else if (lastState !== transaction.state) {
|
|
@@ -1153,15 +1221,31 @@ var CdvPurchase;
|
|
|
1153
1221
|
this.delegate.initiatedCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_initiated');
|
|
1154
1222
|
}
|
|
1155
1223
|
else if (transaction.state === CdvPurchase.TransactionState.FINISHED) {
|
|
1156
|
-
|
|
1157
|
-
|
|
1224
|
+
if (isSubscriptionDuplicate) {
|
|
1225
|
+
// Finish the duplicate at the native level so StoreKit won't re-deliver it
|
|
1226
|
+
this.log.debug(`Auto-finishing subscription duplicate ${transactionToken}, already processed as ${this.subscriptionFirstTransactionId[subscriptionKey]}`);
|
|
1227
|
+
this.delegate.finishDuplicate(transaction);
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
this.lastCallTimeForState[tokenWithState] = now;
|
|
1231
|
+
this.delegate.finishedCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_finished');
|
|
1232
|
+
}
|
|
1158
1233
|
}
|
|
1159
1234
|
else if (transaction.state === CdvPurchase.TransactionState.PENDING) {
|
|
1160
1235
|
this.lastCallTimeForState[tokenWithState] = now;
|
|
1161
1236
|
this.delegate.pendingCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_pending');
|
|
1162
1237
|
}
|
|
1163
1238
|
}
|
|
1164
|
-
|
|
1239
|
+
// Mark subscription duplicates as FINISHED so the re-processing triggered
|
|
1240
|
+
// by finishDuplicate()'s receiptsUpdated.call() won't enter the FINISHED
|
|
1241
|
+
// handler and call finishDuplicate() a second time on an already-finished
|
|
1242
|
+
// native transaction.
|
|
1243
|
+
if (isSubscriptionDuplicate) {
|
|
1244
|
+
this.lastTransactionState[transactionToken] = CdvPurchase.TransactionState.FINISHED;
|
|
1245
|
+
}
|
|
1246
|
+
else {
|
|
1247
|
+
this.lastTransactionState[transactionToken] = transaction.state;
|
|
1248
|
+
}
|
|
1165
1249
|
});
|
|
1166
1250
|
});
|
|
1167
1251
|
}
|
|
@@ -1578,7 +1662,7 @@ var CdvPurchase;
|
|
|
1578
1662
|
/**
|
|
1579
1663
|
* Current release number of the plugin.
|
|
1580
1664
|
*/
|
|
1581
|
-
CdvPurchase.PLUGIN_VERSION = '13.15.
|
|
1665
|
+
CdvPurchase.PLUGIN_VERSION = '13.15.2';
|
|
1582
1666
|
/**
|
|
1583
1667
|
* Entry class of the plugin.
|
|
1584
1668
|
*/
|
|
@@ -1658,6 +1742,12 @@ var CdvPurchase;
|
|
|
1658
1742
|
finishedCallbacks: this.finishedCallbacks,
|
|
1659
1743
|
pendingCallbacks: this.pendingCallbacks,
|
|
1660
1744
|
receiptsReadyCallbacks: this.receiptsReadyCallbacks,
|
|
1745
|
+
finishDuplicate: (transaction) => {
|
|
1746
|
+
const adapter = this.adapters.findReady(transaction.platform);
|
|
1747
|
+
if (adapter) {
|
|
1748
|
+
adapter.finish(transaction);
|
|
1749
|
+
}
|
|
1750
|
+
},
|
|
1661
1751
|
}, this.log);
|
|
1662
1752
|
this.transactionStateMonitors = new CdvPurchase.Internal.TransactionStateMonitors(this.when());
|
|
1663
1753
|
this._validator = new CdvPurchase.Internal.Validator({
|
|
@@ -3548,16 +3638,20 @@ var CdvPurchase;
|
|
|
3548
3638
|
this.receiptsUpdated.call();
|
|
3549
3639
|
return resolve(undefined);
|
|
3550
3640
|
}
|
|
3641
|
+
const wasAlreadyFinished = transaction.state === CdvPurchase.TransactionState.FINISHED;
|
|
3551
3642
|
const success = () => {
|
|
3552
3643
|
transaction.state = CdvPurchase.TransactionState.FINISHED;
|
|
3553
|
-
|
|
3644
|
+
if (!wasAlreadyFinished) {
|
|
3645
|
+
this.receiptsUpdated.call();
|
|
3646
|
+
}
|
|
3554
3647
|
resolve(undefined);
|
|
3555
3648
|
};
|
|
3556
3649
|
const error = (msg) => {
|
|
3557
3650
|
var _a, _b;
|
|
3558
3651
|
if (msg === null || msg === void 0 ? void 0 : msg.includes('[#CdvPurchase:100]')) {
|
|
3559
|
-
// already finished
|
|
3560
|
-
|
|
3652
|
+
// already finished at the native level
|
|
3653
|
+
transaction.state = CdvPurchase.TransactionState.FINISHED;
|
|
3654
|
+
resolve(undefined);
|
|
3561
3655
|
}
|
|
3562
3656
|
else {
|
|
3563
3657
|
resolve(appStoreError(CdvPurchase.ErrorCode.FINISH, 'Failed to finish transaction', (_b = (_a = transaction.products[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null));
|
|
@@ -4299,13 +4393,14 @@ var CdvPurchase;
|
|
|
4299
4393
|
/** Retrieve the storefront country code from StoreKit */
|
|
4300
4394
|
getStorefront() {
|
|
4301
4395
|
return new Promise((resolve) => {
|
|
4302
|
-
//
|
|
4396
|
+
// Use StoreKit 2's Storefront.current via the SK2 plugin,
|
|
4397
|
+
// which works on Mac Catalyst where SK1's storefront is nil.
|
|
4303
4398
|
window.cordova.exec((countryCode) => {
|
|
4304
4399
|
resolve(countryCode || undefined);
|
|
4305
4400
|
}, (err) => {
|
|
4306
4401
|
log('getStorefront failed: ' + err);
|
|
4307
4402
|
resolve(undefined);
|
|
4308
|
-
}, "
|
|
4403
|
+
}, "StoreKit2Plugin", "getStorefront", []);
|
|
4309
4404
|
});
|
|
4310
4405
|
}
|
|
4311
4406
|
loadReceipts(callback, errorCb) {
|
|
@@ -4847,6 +4942,13 @@ var CdvPurchase;
|
|
|
4847
4942
|
offerType: 'Default',
|
|
4848
4943
|
}, decorator));
|
|
4849
4944
|
}
|
|
4945
|
+
// Ensure the default offer is always first so that getOffer()
|
|
4946
|
+
// without arguments returns it instead of a discount offer.
|
|
4947
|
+
const defaultIndex = this.offers.findIndex(o => o.id === AppleAppStore.DEFAULT_OFFER_ID);
|
|
4948
|
+
if (defaultIndex > 0) {
|
|
4949
|
+
const [defaultOffer] = this.offers.splice(defaultIndex, 1);
|
|
4950
|
+
this.offers.unshift(defaultOffer);
|
|
4951
|
+
}
|
|
4850
4952
|
function hasIntroductoryOffer(product) {
|
|
4851
4953
|
return product.offers.filter(offer => {
|
|
4852
4954
|
const skOffer = offer;
|