capacitor-plugin-cdv-purchase 13.17.2 → 13.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/www/store.js CHANGED
@@ -521,6 +521,10 @@ var CdvPurchase;
521
521
  this.receiptsToValidate = new ReceiptsToValidate();
522
522
  /** List of verified receipts */
523
523
  this.verifiedReceipts = [];
524
+ /** Maximum number of validation attempts for a receipt when failures are temporary */
525
+ this.maxAttempts = 4;
526
+ /** Number of failed attempts in a row, per receipt */
527
+ this.failedAttempts = new Map();
524
528
  this.numRequests = 0;
525
529
  this.numResponses = 0;
526
530
  /**
@@ -534,6 +538,36 @@ var CdvPurchase;
534
538
  this.controller = controller;
535
539
  this.log = log.child('Validator');
536
540
  }
541
+ /**
542
+ * Schedule a new validation if the failure is temporary and attempts remain.
543
+ *
544
+ * @return true if a retry was scheduled
545
+ */
546
+ scheduleRetry(receipt, payload) {
547
+ var _a;
548
+ if (payload.ok || !isRetriableError(payload)) {
549
+ this.failedAttempts.delete(receipt);
550
+ if (payload.ok && this.retry)
551
+ this.retry.retryTimeout = this.retry.minTimeout;
552
+ return false;
553
+ }
554
+ const attempts = ((_a = this.failedAttempts.get(receipt)) !== null && _a !== void 0 ? _a : 0) + 1;
555
+ if (attempts >= this.maxAttempts) {
556
+ this.log.warn(`Validation failed ${attempts} times, giving up: ${payload.message}`);
557
+ this.failedAttempts.delete(receipt);
558
+ return false;
559
+ }
560
+ this.failedAttempts.set(receipt, attempts);
561
+ if (!this.retry)
562
+ this.retry = new Internal.Retry();
563
+ this.log.info(`Validation failed (${payload.message}), retry in ${this.retry.retryTimeout}ms (attempt ${attempts + 1}/${this.maxAttempts})`);
564
+ this.retry.retry(() => {
565
+ // the request is still counted as pending, so don't count it again
566
+ this.receiptsToValidate.add(receipt);
567
+ this.run();
568
+ });
569
+ return true;
570
+ }
537
571
  incrRequestsCounter() {
538
572
  this.numRequests = (this.numRequests + 1) | 0;
539
573
  this.log.debug(`Validation requests=${this.numRequests} responses=${this.numResponses}`);
@@ -574,6 +608,9 @@ var CdvPurchase;
574
608
  const onResponse = (r) => __awaiter(this, void 0, void 0, function* () {
575
609
  var _a;
576
610
  const { receipt, payload } = r;
611
+ // a retried request is still pending: only count final responses
612
+ if (this.scheduleRetry(receipt, payload))
613
+ return;
577
614
  this.incrResponsesCounter();
578
615
  try {
579
616
  const adapter = this.controller.adapters.find(receipt.platform);
@@ -595,11 +632,11 @@ var CdvPurchase;
595
632
  this.controller.verifiedCallbacks.trigger(vr, 'payload_expired');
596
633
  }
597
634
  else {
598
- this.controller.unverifiedCallbacks.trigger({ receipt, payload }, 'no_verified_receipt');
635
+ this.controller.unverifiedCallbacks.trigger({ receipt, payload, transient: false }, 'no_verified_receipt');
599
636
  }
600
637
  }
601
638
  else {
602
- this.controller.unverifiedCallbacks.trigger({ receipt, payload }, 'validator_error');
639
+ this.controller.unverifiedCallbacks.trigger({ receipt, payload, transient: isTransientError(payload) }, 'validator_error');
603
640
  }
604
641
  }
605
642
  catch (err) {
@@ -608,7 +645,7 @@ var CdvPurchase;
608
645
  ok: false,
609
646
  code: CdvPurchase.ErrorCode.VERIFICATION_FAILED,
610
647
  message: err.message,
611
- } }, 'validator_exception');
648
+ }, transient: false }, 'validator_exception');
612
649
  }
613
650
  });
614
651
  receipts.forEach(receipt => this.runOnReceipt(receipt, onResponse));
@@ -768,10 +805,13 @@ var CdvPurchase;
768
805
  data: { latest_receipt: (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.latest_receipt },
769
806
  }
770
807
  });
771
- this.cache[bodyTransactionHash] = {
772
- payload: response,
773
- expires: (+new Date()) + 120000, // expires in 2 minutes
774
- };
808
+ // don't cache temporary errors, so a retry makes a new request
809
+ if (response.ok || !isTransientError(response)) {
810
+ this.cache[bodyTransactionHash] = {
811
+ payload: response,
812
+ expires: (+new Date()) + 120000, // expires in 2 minutes
813
+ };
814
+ }
775
815
  callback({ receipt, payload: response });
776
816
  },
777
817
  error: (status, message, data) => {
@@ -793,6 +833,44 @@ var CdvPurchase;
793
833
  }
794
834
  }
795
835
  Internal.Validator = Validator;
836
+ /** Validator error code: the validator couldn't reach the store (Apple, Google, ...). */
837
+ const VALIDATOR_CONNECTION_FAILED = 6778002;
838
+ /**
839
+ * True if a validation error is temporary (validator or store unreachable,
840
+ * server error, timeout, no network) rather than a definitive answer about
841
+ * the receipt.
842
+ *
843
+ * Error responses from the validator come in two shapes:
844
+ * - HTTP 200 with `{ ok: false, status, code }` in the body (iaptic does this,
845
+ * so a store outage is `status: 502, code: 6778002` in the body);
846
+ * - a failed HTTP request, reported by the plugin as `code: COMMUNICATION`
847
+ * with the transport status: 5xx, 408 (timeout), 417 (unreadable response,
848
+ * e.g. a captive portal page), 0 or negative (no network, as reported by
849
+ * XMLHttpRequest or cordova-plugin-advanced-http).
850
+ */
851
+ function isTransientError(payload) {
852
+ const status = payload.status;
853
+ if (typeof status === 'number' && status >= 500)
854
+ return true;
855
+ if (payload.code === VALIDATOR_CONNECTION_FAILED)
856
+ return true;
857
+ if (payload.code === CdvPurchase.ErrorCode.COMMUNICATION) {
858
+ if (typeof status !== 'number' || status <= 0)
859
+ return true;
860
+ if (status === CdvPurchase.Utils.Ajax.HTTP_REQUEST_TIMEOUT || status === 417)
861
+ return true;
862
+ }
863
+ return false;
864
+ }
865
+ Internal.isTransientError = isTransientError;
866
+ /**
867
+ * True if a validation failure is worth retrying: a temporary error
868
+ * (see isTransientError) or an invalid response from the validator.
869
+ * Validator verdicts (invalid receipt, expired, ...) are not retried.
870
+ */
871
+ function isRetriableError(payload) {
872
+ return isTransientError(payload) || payload.code === CdvPurchase.ErrorCode.BAD_RESPONSE;
873
+ }
796
874
  /**
797
875
  * Check if a payload looks like a valid validator response.
798
876
  *
@@ -1674,6 +1752,276 @@ var CdvPurchase;
1674
1752
  Utils.md5toUUID = md5toUUID;
1675
1753
  })(Utils = CdvPurchase.Utils || (CdvPurchase.Utils = {}));
1676
1754
  })(CdvPurchase || (CdvPurchase = {}));
1755
+ var CdvPurchase;
1756
+ (function (CdvPurchase) {
1757
+ /**
1758
+ * Persist a subset of {@link VerifiedPurchase} to device storage so that
1759
+ * `offline.isOwned()` can answer "is this product owned?" when the device
1760
+ * is offline or has just restarted without connectivity.
1761
+ *
1762
+ * Phase 1 — unsigned cache. No JWT, no crypto, no server changes.
1763
+ *
1764
+ * @example
1765
+ * ```typescript
1766
+ * const offline = new CdvPurchase.OfflineEntitlements(store, {
1767
+ * gracePeriodMs: 30 * 24 * 60 * 60 * 1000, // 30 days
1768
+ * onExpiredOffline: 'readonly',
1769
+ * detectClockRollback: true,
1770
+ * });
1771
+ *
1772
+ * await offline.ready();
1773
+ *
1774
+ * function isUserPremium(): boolean {
1775
+ * return store.owned('premium') || offline.isOwned('premium');
1776
+ * }
1777
+ * ```
1778
+ *
1779
+ * **Durability warning:** for long-offline deployments (weeks without
1780
+ * connectivity), pass a file-based or secure-storage adapter. `localStorage`
1781
+ * (the default) can be evicted by the WebView under storage pressure, which
1782
+ * would silently lose the cached entitlements.
1783
+ */
1784
+ class OfflineEntitlements {
1785
+ constructor(store, options = {}) {
1786
+ var _a, _b, _c, _d;
1787
+ /** In-memory cache of persisted entitlements, keyed by `platform:productId`. */
1788
+ this.cache = {};
1789
+ /** Last seen timestamp persisted with the data, used for clock rollback detection. */
1790
+ this.lastSeenTimestamp = 0;
1791
+ /** True once `ready()` has resolved. */
1792
+ this.isReady = false;
1793
+ /** Last event fired per productId, to deduplicate events on repeated isOwned() calls. */
1794
+ this.lastEventPerProduct = {};
1795
+ this.store = store;
1796
+ this.storage = (_a = options.storage) !== null && _a !== void 0 ? _a : OfflineEntitlements.createLocalStorageAdapter();
1797
+ this.gracePeriodMs = (_b = options.gracePeriodMs) !== null && _b !== void 0 ? _b : OfflineEntitlements.DEFAULT_GRACE_PERIOD_MS;
1798
+ this.onExpiredOffline = (_c = options.onExpiredOffline) !== null && _c !== void 0 ? _c : 'readonly';
1799
+ this.detectClockRollback = (_d = options.detectClockRollback) !== null && _d !== void 0 ? _d : false;
1800
+ this.eventCallbacks = new CdvPurchase.Internal.Callbacks(store.log, 'OfflineEntitlements');
1801
+ this.store.when().verified((receipt) => { void this.onVerified(receipt); });
1802
+ }
1803
+ /** Wrap the global `localStorage` as an async `OfflineStorageAdapter`. */
1804
+ static createLocalStorageAdapter() {
1805
+ return {
1806
+ getItem: (key) => Promise.resolve(localStorage.getItem(key)),
1807
+ setItem: (key, value) => { localStorage.setItem(key, value); return Promise.resolve(); },
1808
+ removeItem: (key) => { localStorage.removeItem(key); return Promise.resolve(); },
1809
+ };
1810
+ }
1811
+ /** Load persisted entitlements from storage into the in-memory cache. Idempotent. */
1812
+ ready() {
1813
+ return __awaiter(this, void 0, void 0, function* () {
1814
+ if (this.isReady)
1815
+ return;
1816
+ yield this.loadFromStorage();
1817
+ this.isReady = true;
1818
+ });
1819
+ }
1820
+ /** Reload from storage and re-evaluate. Resolves when the reload is complete. Call after reconnecting or manually. */
1821
+ refresh() {
1822
+ return this.loadFromStorage();
1823
+ }
1824
+ /** Register a callback for {@link OfflineEntitlementEvent}s. */
1825
+ onEvent(callback) {
1826
+ this.eventCallbacks.push(callback);
1827
+ }
1828
+ /** Remove all persisted entitlements from storage and clear the in-memory cache. For user logout. */
1829
+ clear() {
1830
+ return __awaiter(this, void 0, void 0, function* () {
1831
+ this.cache = {};
1832
+ this.lastSeenTimestamp = 0;
1833
+ this.isReady = false;
1834
+ this.lastEventPerProduct = {};
1835
+ yield this.storage.removeItem(OfflineEntitlements.STORAGE_KEY);
1836
+ });
1837
+ }
1838
+ /**
1839
+ * Return `true` if the product is owned, using the persisted cache when
1840
+ * the in-memory `verifiedReceipts` don't grant ownership.
1841
+ *
1842
+ * Returns `false` for all products until `ready()` has resolved.
1843
+ */
1844
+ isOwned(productId) {
1845
+ if (!this.isReady) {
1846
+ this.store.log.warn('OfflineEntitlements.isOwned("' + productId + '") called before ready() — returning false. Call await offline.ready() at startup.');
1847
+ return false;
1848
+ }
1849
+ // 1. If store.verifiedReceipts has a valid (non-expired) entry, return true.
1850
+ const online = CdvPurchase.Internal.VerifiedReceipts.isOwned(this.store.verifiedReceipts, { id: productId });
1851
+ if (online)
1852
+ return true;
1853
+ const now = +new Date();
1854
+ // 2. Find persisted purchase for this productId across all platforms.
1855
+ const persisted = this.findPersisted(productId);
1856
+ if (!persisted) {
1857
+ // No persisted entitlement.
1858
+ this.fireEvent('entitlement_missing', productId, 'No persisted entitlement for this product.');
1859
+ return false;
1860
+ }
1861
+ // 3. Branch by product type.
1862
+ // Subscriptions have an expiryDate; non-consumables don't.
1863
+ if (persisted.expiryDate !== undefined && persisted.expiryDate !== null) {
1864
+ // Subscription
1865
+ // Clock rollback detection — scoped to subscriptions (non-consumables have no time component).
1866
+ if (this.detectClockRollback && this.lastSeenTimestamp > now) {
1867
+ this.fireEvent('clock_rollback', persisted.id, 'Clock appears to have rolled back; denying offline entitlement.');
1868
+ return false;
1869
+ }
1870
+ if (persisted.isExpired) {
1871
+ this.fireEvent('expired', persisted.id, 'Subscription is marked as expired.');
1872
+ return false;
1873
+ }
1874
+ if (now < persisted.expiryDate) {
1875
+ return true;
1876
+ }
1877
+ const renewalIntent = persisted.renewalIntent;
1878
+ if (renewalIntent === CdvPurchase.RenewalIntent.RENEW && now < persisted.expiryDate + this.gracePeriodMs) {
1879
+ this.fireEvent('grace', persisted.id, 'Subscription expired but within grace period.');
1880
+ return true;
1881
+ }
1882
+ if (renewalIntent === CdvPurchase.RenewalIntent.LAPSE) {
1883
+ // Lapsed subscriptions deny at expiryDate.
1884
+ return false;
1885
+ }
1886
+ // Unknown/undefined renewalIntent: fall through to onExpiredOffline.
1887
+ if (this.onExpiredOffline === 'readonly') {
1888
+ this.fireEvent('readonly', persisted.id, 'Subscription expired and grace period elapsed; granting readonly access.');
1889
+ return true;
1890
+ }
1891
+ return false;
1892
+ }
1893
+ else {
1894
+ // Non-consumable: never hard-expires, no clock rollback check.
1895
+ return true;
1896
+ }
1897
+ }
1898
+ /**
1899
+ * Retrieve the persisted entitlement for a product, analogous to
1900
+ * `store.findInVerifiedReceipts()`.
1901
+ *
1902
+ * Returns the {@link PersistedPurchase} (with `expiryDate`,
1903
+ * `renewalIntent`, `lastRenewalDate`, etc.) if one has been cached,
1904
+ * or `undefined` if no offline entitlement exists for this product.
1905
+ *
1906
+ * Returns `undefined` for all products until `ready()` has resolved.
1907
+ *
1908
+ * @example
1909
+ * ```typescript
1910
+ * const entitlement = offline.find('premium');
1911
+ * if (entitlement?.expiryDate) {
1912
+ * const daysLeft = Math.ceil((entitlement.expiryDate - Date.now()) / 86400000);
1913
+ * showRenewalBanner(daysLeft);
1914
+ * }
1915
+ * ```
1916
+ */
1917
+ find(productId) {
1918
+ if (!this.isReady)
1919
+ return undefined;
1920
+ return this.findPersisted(productId);
1921
+ }
1922
+ /** Persist all `VerifiedPurchase`s in a verified receipt to storage. */
1923
+ onVerified(receipt) {
1924
+ var _a;
1925
+ return __awaiter(this, void 0, void 0, function* () {
1926
+ for (const purchase of receipt.collection) {
1927
+ // Skip consumables — they don't need offline entitlement.
1928
+ if (purchase.isConsumed)
1929
+ continue;
1930
+ const platform = (_a = purchase.platform) !== null && _a !== void 0 ? _a : receipt.platform;
1931
+ const product = this.store.get(purchase.id, platform);
1932
+ if (product && product.type === CdvPurchase.ProductType.CONSUMABLE)
1933
+ continue;
1934
+ const key = platform + ':' + purchase.id;
1935
+ this.cache[key] = {
1936
+ id: purchase.id,
1937
+ platform,
1938
+ expiryDate: purchase.expiryDate,
1939
+ isExpired: purchase.isExpired,
1940
+ renewalIntent: purchase.renewalIntent,
1941
+ lastRenewalDate: purchase.lastRenewalDate,
1942
+ purchaseDate: purchase.purchaseDate,
1943
+ cancelationReason: purchase.cancelationReason,
1944
+ isBillingRetryPeriod: purchase.isBillingRetryPeriod,
1945
+ };
1946
+ }
1947
+ this.lastSeenTimestamp = +new Date();
1948
+ yield this.saveToStorage();
1949
+ });
1950
+ }
1951
+ /** Find the persisted purchase for a productId across all platforms. */
1952
+ findPersisted(productId) {
1953
+ var _a, _b, _c, _d;
1954
+ let found;
1955
+ for (const key of Object.keys(this.cache)) {
1956
+ const entry = this.cache[key];
1957
+ if (entry.id === productId) {
1958
+ if (!found || ((_b = (_a = found.lastRenewalDate) !== null && _a !== void 0 ? _a : found.purchaseDate) !== null && _b !== void 0 ? _b : 0) < ((_d = (_c = entry.lastRenewalDate) !== null && _c !== void 0 ? _c : entry.purchaseDate) !== null && _d !== void 0 ? _d : 0)) {
1959
+ found = entry;
1960
+ }
1961
+ }
1962
+ }
1963
+ return found;
1964
+ }
1965
+ /** Load the persisted payload from storage into the in-memory cache. */
1966
+ loadFromStorage() {
1967
+ var _a, _b;
1968
+ return __awaiter(this, void 0, void 0, function* () {
1969
+ try {
1970
+ const raw = yield this.storage.getItem(OfflineEntitlements.STORAGE_KEY);
1971
+ if (!raw) {
1972
+ this.cache = {};
1973
+ this.lastSeenTimestamp = 0;
1974
+ return;
1975
+ }
1976
+ const parsed = JSON.parse(raw);
1977
+ if (parsed.schemaVersion !== OfflineEntitlements.SCHEMA_VERSION) {
1978
+ this.store.log.warn('OfflineEntitlements: schema version mismatch, ignoring cached data.');
1979
+ this.cache = {};
1980
+ this.lastSeenTimestamp = 0;
1981
+ return;
1982
+ }
1983
+ this.cache = (_a = parsed.receipts) !== null && _a !== void 0 ? _a : {};
1984
+ this.lastSeenTimestamp = (_b = parsed.lastSeenTimestamp) !== null && _b !== void 0 ? _b : 0;
1985
+ }
1986
+ catch (err) {
1987
+ this.store.log.warn('OfflineEntitlements: failed to load from storage: ' + err.message);
1988
+ this.cache = {};
1989
+ this.lastSeenTimestamp = 0;
1990
+ }
1991
+ });
1992
+ }
1993
+ /** Serialize the in-memory cache to storage. */
1994
+ saveToStorage() {
1995
+ return __awaiter(this, void 0, void 0, function* () {
1996
+ const payload = {
1997
+ receipts: this.cache,
1998
+ lastSeenTimestamp: this.lastSeenTimestamp,
1999
+ schemaVersion: OfflineEntitlements.SCHEMA_VERSION,
2000
+ };
2001
+ try {
2002
+ yield this.storage.setItem(OfflineEntitlements.STORAGE_KEY, JSON.stringify(payload));
2003
+ }
2004
+ catch (err) {
2005
+ this.store.log.warn('OfflineEntitlements: failed to save to storage: ' + err.message);
2006
+ }
2007
+ });
2008
+ }
2009
+ /** Fire an event to all registered callbacks, deduplicating per productId. */
2010
+ fireEvent(type, productId, message) {
2011
+ if (this.lastEventPerProduct[productId] === type)
2012
+ return;
2013
+ this.lastEventPerProduct[productId] = type;
2014
+ this.eventCallbacks.trigger({ type, productId, message }, 'offline_entitlements');
2015
+ }
2016
+ }
2017
+ /** Storage key used in the storage adapter. */
2018
+ OfflineEntitlements.STORAGE_KEY = 'cdvpurchase.offline_entitlements';
2019
+ /** Schema version for the persisted payload. */
2020
+ OfflineEntitlements.SCHEMA_VERSION = 1;
2021
+ /** Default grace period: 30 days. */
2022
+ OfflineEntitlements.DEFAULT_GRACE_PERIOD_MS = 30 * 24 * 60 * 60 * 1000;
2023
+ CdvPurchase.OfflineEntitlements = OfflineEntitlements;
2024
+ })(CdvPurchase || (CdvPurchase = {}));
1677
2025
  /// <reference path="types.ts" />
1678
2026
  /// <reference path="utils/compatibility.ts" />
1679
2027
  /// <reference path="utils/platform-name.ts" />
@@ -1689,6 +2037,7 @@ var CdvPurchase;
1689
2037
  /// <reference path="internal/receipts-monitor.ts" />
1690
2038
  /// <reference path="internal/expiry-monitor.ts" />
1691
2039
  /// <reference path="utils/to-uuid.ts" />
2040
+ /// <reference path="offline-entitlements.ts" />
1692
2041
  /**
1693
2042
  * Namespace for the cordova-plugin-purchase plugin.
1694
2043
  *
@@ -1722,7 +2071,7 @@ var CdvPurchase;
1722
2071
  /**
1723
2072
  * Current release number of the plugin.
1724
2073
  */
1725
- CdvPurchase.PLUGIN_VERSION = '13.17.2';
2074
+ CdvPurchase.PLUGIN_VERSION = '13.19.1';
1726
2075
  /**
1727
2076
  * Entry class of the plugin.
1728
2077
  */
@@ -3443,13 +3792,19 @@ var CdvPurchase;
3443
3792
  yield this.receiptsUpdated.wait();
3444
3793
  this.log.info('finish: ' + transactionIdentifier + ' - ' + productId);
3445
3794
  this.removeTransactionInProgress(productId);
3446
- yield this.upsertTransaction(productId, transactionIdentifier, CdvPurchase.TransactionState.FINISHED);
3795
+ const finishedTransaction = yield this.upsertTransaction(productId, transactionIdentifier, CdvPurchase.TransactionState.FINISHED);
3796
+ finishedTransaction.isAcknowledged = true;
3447
3797
  this.receiptsUpdated.call();
3448
3798
  }),
3449
- restored: (transactionIdentifier, productId, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity) => __awaiter(this, void 0, void 0, function* () {
3799
+ restored: (transactionIdentifier, productId, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity, isFinished) => __awaiter(this, void 0, void 0, function* () {
3450
3800
  this.log.info('restore: ' + transactionIdentifier + ' - ' + productId);
3451
3801
  const transaction = yield this.upsertTransaction(productId, transactionIdentifier, CdvPurchase.TransactionState.APPROVED);
3452
3802
  transaction.refresh(productId, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity);
3803
+ // StoreKit 2 re-emits finished entitlements (non-consumables,
3804
+ // non-renewing subscriptions) at every launch. Expose the finished
3805
+ // state the same way Google Play does, so apps can skip them in approved().
3806
+ if (isFinished !== undefined)
3807
+ transaction.isAcknowledged = isFinished;
3453
3808
  this.receiptsUpdated.call();
3454
3809
  }),
3455
3810
  receiptsRefreshed: (receipt) => {
@@ -4083,7 +4438,7 @@ var CdvPurchase;
4083
4438
  }
4084
4439
  // Listen for transaction updates from native
4085
4440
  plugin.addListener('transactionUpdated', (data) => {
4086
- this.transactionUpdated(data.state, data.errorCode, data.errorText, data.transactionIdentifier, data.productId, data.transactionReceipt, data.originalTransactionIdentifier, data.transactionDate, data.discountId, data.expirationDate, data.jwsRepresentation, data.quantity);
4441
+ this.transactionUpdated(data.state, data.errorCode, data.errorText, data.transactionIdentifier, data.productId, data.transactionReceipt, data.originalTransactionIdentifier, data.transactionDate, data.discountId, data.expirationDate, data.jwsRepresentation, data.quantity, data.isFinished);
4087
4442
  });
4088
4443
  plugin.addListener('restoreCompleted', () => {
4089
4444
  this.restoreCompletedTransactionsFinished();
@@ -4104,7 +4459,7 @@ var CdvPurchase;
4104
4459
  const pending = this.pendingTransactionUpdates;
4105
4460
  this.pendingTransactionUpdates = [];
4106
4461
  for (const args of pending) {
4107
- this.transactionUpdated(args.state, args.errorCode, args.errorText, args.transactionIdentifier, args.productId, args.transactionReceipt, args.originalTransactionIdentifier, args.transactionDate, args.discountId, args.expirationDate, args.jwsRepresentation, args.quantity);
4462
+ this.transactionUpdated(args.state, args.errorCode, args.errorText, args.transactionIdentifier, args.productId, args.transactionReceipt, args.originalTransactionIdentifier, args.transactionDate, args.discountId, args.expirationDate, args.jwsRepresentation, args.quantity, args.isFinished);
4108
4463
  }
4109
4464
  if (this.options.ready)
4110
4465
  this.options.ready();
@@ -4188,13 +4543,13 @@ var CdvPurchase;
4188
4543
  .catch((err) => errorCb(CdvPurchase.ErrorCode.LOAD, (err === null || err === void 0 ? void 0 : err.message) || 'loadReceipts failed'));
4189
4544
  }
4190
4545
  // Called when the native side sends a transaction update
4191
- transactionUpdated(state, errorCode, errorText, transactionIdentifier, productId, transactionReceipt, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity) {
4546
+ transactionUpdated(state, errorCode, errorText, transactionIdentifier, productId, transactionReceipt, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity, isFinished) {
4192
4547
  if (!this.initialized) {
4193
4548
  this.pendingTransactionUpdates.push({
4194
4549
  state, errorCode, errorText, transactionIdentifier,
4195
4550
  productId, transactionReceipt, originalTransactionIdentifier,
4196
4551
  transactionDate, discountId, expirationDate, jwsRepresentation,
4197
- quantity,
4552
+ quantity, isFinished,
4198
4553
  });
4199
4554
  return;
4200
4555
  }
@@ -4230,7 +4585,7 @@ var CdvPurchase;
4230
4585
  // purchased, but is meaningless here: consumables cannot be
4231
4586
  // restored, so restored transactions are always quantity 1.
4232
4587
  if (this.options.restored) {
4233
- this.options.restored(transactionIdentifier, productId, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity);
4588
+ this.options.restored(transactionIdentifier, productId, originalTransactionIdentifier, transactionDate, discountId, expirationDate, jwsRepresentation, quantity, isFinished);
4234
4589
  }
4235
4590
  break;
4236
4591
  case 'PaymentTransactionStateDeferred':