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.
@@ -1,6 +1,6 @@
1
1
  require 'json'
2
2
 
3
- package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
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
- // prevent calling approved twice in a very short period (60 seconds).
1132
- const lastCalled = (_a = this.lastCallTimeForState[tokenWithState]) !== null && _a !== void 0 ? _a : 0;
1133
- if (now - lastCalled > 60000) {
1134
- this.lastCallTimeForState[tokenWithState] = now;
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
- this.log.debug(`Skipping ${tokenWithState}, because it has been last called ${lastCalled > 0 ? Math.round(now - lastCalled) + 'ms ago (' + now + '-' + lastCalled + ')' : 'never'}`);
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
- this.lastCallTimeForState[tokenWithState] = now;
1148
- this.delegate.finishedCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_finished');
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
- this.lastTransactionState[transactionToken] = transaction.state;
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.0';
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
- this.receiptsUpdated.call();
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
- success();
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
- // SK2 uses the same native getStorefront action via InAppPurchase plugin
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
- }, "InAppPurchase", "getStorefront", []);
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
- // Process any pending transactions
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
- if case .verified(let transaction) = result {
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
- if case .verified(let transaction) = result {
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
- // SKPaymentQueue.storefront is a StoreKit 1 API available from iOS 13,
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
- } else {
329
- debugLog("getStorefront: storefront not available")
330
- call.reject("Storefront not available")
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.0",
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.0";
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
- // prevent calling approved twice in a very short period (60 seconds).
1141
- const lastCalled = (_a = this.lastCallTimeForState[tokenWithState]) !== null && _a !== void 0 ? _a : 0;
1142
- if (now - lastCalled > 60000) {
1143
- this.lastCallTimeForState[tokenWithState] = now;
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
- this.log.debug(`Skipping ${tokenWithState}, because it has been last called ${lastCalled > 0 ? Math.round(now - lastCalled) + 'ms ago (' + now + '-' + lastCalled + ')' : 'never'}`);
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
- this.lastCallTimeForState[tokenWithState] = now;
1157
- this.delegate.finishedCallbacks.trigger(transaction, 'adapterListener_receiptsUpdated_finished');
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
- this.lastTransactionState[transactionToken] = transaction.state;
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.0';
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
- this.receiptsUpdated.call();
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
- success();
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
- // SK2 uses the same native getStorefront action via InAppPurchase plugin
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
- }, "InAppPurchase", "getStorefront", []);
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;