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.d.ts CHANGED
@@ -366,6 +366,18 @@ declare namespace CdvPurchase {
366
366
  /** List of verified receipts */
367
367
  verifiedReceipts: VerifiedReceipt[];
368
368
  constructor(controller: ValidatorController, log: Logger);
369
+ /** Maximum number of validation attempts for a receipt when failures are temporary */
370
+ maxAttempts: number;
371
+ /** Number of failed attempts in a row, per receipt */
372
+ private failedAttempts;
373
+ /** Backoff for retrying temporary failures (created on first use, it listens to the "online" event) */
374
+ private retry?;
375
+ /**
376
+ * Schedule a new validation if the failure is temporary and attempts remain.
377
+ *
378
+ * @return true if a retry was scheduled
379
+ */
380
+ private scheduleRetry;
369
381
  numRequests: number;
370
382
  numResponses: number;
371
383
  incrRequestsCounter(): void;
@@ -393,6 +405,20 @@ declare namespace CdvPurchase {
393
405
  private removeExpiredCache;
394
406
  private runValidatorRequest;
395
407
  }
408
+ /**
409
+ * True if a validation error is temporary (validator or store unreachable,
410
+ * server error, timeout, no network) rather than a definitive answer about
411
+ * the receipt.
412
+ *
413
+ * Error responses from the validator come in two shapes:
414
+ * - HTTP 200 with `{ ok: false, status, code }` in the body (iaptic does this,
415
+ * so a store outage is `status: 502, code: 6778002` in the body);
416
+ * - a failed HTTP request, reported by the plugin as `code: COMMUNICATION`
417
+ * with the transport status: 5xx, 408 (timeout), 417 (unreadable response,
418
+ * e.g. a captive portal page), 0 or negative (no network, as reported by
419
+ * XMLHttpRequest or cordova-plugin-advanced-http).
420
+ */
421
+ function isTransientError(payload: Validator.Response.ErrorPayload): boolean;
396
422
  }
397
423
  }
398
424
  declare namespace CdvPurchase {
@@ -879,6 +905,141 @@ declare namespace CdvPurchase {
879
905
  function md5toUUID(str: string): string;
880
906
  }
881
907
  }
908
+ declare namespace CdvPurchase {
909
+ /** Storage adapter for persisting offline entitlements. */
910
+ interface OfflineStorageAdapter {
911
+ getItem(key: string): Promise<string | null>;
912
+ setItem(key: string, value: string): Promise<void>;
913
+ removeItem(key: string): Promise<void>;
914
+ }
915
+ /** Options for {@link OfflineEntitlements}. */
916
+ interface OfflineEntitlementsOptions {
917
+ /** Storage adapter. Defaults to a `localStorage` wrapper. */
918
+ storage?: OfflineStorageAdapter;
919
+ /** Grace period in milliseconds after a subscription's expiryDate during which it's still considered owned. Defaults to 30 days. */
920
+ gracePeriodMs?: number;
921
+ /** Behavior when the grace period has elapsed and the device is still offline. `'readonly'` keeps granting access; `'deny'` revokes it. Defaults to `'readonly'`. */
922
+ onExpiredOffline?: 'deny' | 'readonly';
923
+ /** If true, detect clock rollback (persisted lastSeenTimestamp is in the future relative to now) and deny access. */
924
+ detectClockRollback?: boolean;
925
+ }
926
+ /** Event emitted by {@link OfflineEntitlements} when evaluating ownership offline. */
927
+ interface OfflineEntitlementEvent {
928
+ type: 'grace' | 'readonly' | 'clock_rollback' | 'entitlement_missing' | 'expired';
929
+ productId: string;
930
+ message: string;
931
+ }
932
+ /**
933
+ * Persist a subset of {@link VerifiedPurchase} to device storage so that
934
+ * `offline.isOwned()` can answer "is this product owned?" when the device
935
+ * is offline or has just restarted without connectivity.
936
+ *
937
+ * Phase 1 — unsigned cache. No JWT, no crypto, no server changes.
938
+ *
939
+ * @example
940
+ * ```typescript
941
+ * const offline = new CdvPurchase.OfflineEntitlements(store, {
942
+ * gracePeriodMs: 30 * 24 * 60 * 60 * 1000, // 30 days
943
+ * onExpiredOffline: 'readonly',
944
+ * detectClockRollback: true,
945
+ * });
946
+ *
947
+ * await offline.ready();
948
+ *
949
+ * function isUserPremium(): boolean {
950
+ * return store.owned('premium') || offline.isOwned('premium');
951
+ * }
952
+ * ```
953
+ *
954
+ * **Durability warning:** for long-offline deployments (weeks without
955
+ * connectivity), pass a file-based or secure-storage adapter. `localStorage`
956
+ * (the default) can be evicted by the WebView under storage pressure, which
957
+ * would silently lose the cached entitlements.
958
+ */
959
+ class OfflineEntitlements {
960
+ /** Storage key used in the storage adapter. */
961
+ private static STORAGE_KEY;
962
+ /** Schema version for the persisted payload. */
963
+ private static SCHEMA_VERSION;
964
+ /** Default grace period: 30 days. */
965
+ private static DEFAULT_GRACE_PERIOD_MS;
966
+ private store;
967
+ private storage;
968
+ private gracePeriodMs;
969
+ private onExpiredOffline;
970
+ private detectClockRollback;
971
+ /** In-memory cache of persisted entitlements, keyed by `platform:productId`. */
972
+ private cache;
973
+ /** Last seen timestamp persisted with the data, used for clock rollback detection. */
974
+ private lastSeenTimestamp;
975
+ /** True once `ready()` has resolved. */
976
+ private isReady;
977
+ /** Event callbacks. */
978
+ private eventCallbacks;
979
+ /** Last event fired per productId, to deduplicate events on repeated isOwned() calls. */
980
+ private lastEventPerProduct;
981
+ constructor(store: Store, options?: OfflineEntitlementsOptions);
982
+ /** Wrap the global `localStorage` as an async `OfflineStorageAdapter`. */
983
+ private static createLocalStorageAdapter;
984
+ /** Load persisted entitlements from storage into the in-memory cache. Idempotent. */
985
+ ready(): Promise<void>;
986
+ /** Reload from storage and re-evaluate. Resolves when the reload is complete. Call after reconnecting or manually. */
987
+ refresh(): Promise<void>;
988
+ /** Register a callback for {@link OfflineEntitlementEvent}s. */
989
+ onEvent(callback: Callback<OfflineEntitlementEvent>): void;
990
+ /** Remove all persisted entitlements from storage and clear the in-memory cache. For user logout. */
991
+ clear(): Promise<void>;
992
+ /**
993
+ * Return `true` if the product is owned, using the persisted cache when
994
+ * the in-memory `verifiedReceipts` don't grant ownership.
995
+ *
996
+ * Returns `false` for all products until `ready()` has resolved.
997
+ */
998
+ isOwned(productId: string): boolean;
999
+ /**
1000
+ * Retrieve the persisted entitlement for a product, analogous to
1001
+ * `store.findInVerifiedReceipts()`.
1002
+ *
1003
+ * Returns the {@link PersistedPurchase} (with `expiryDate`,
1004
+ * `renewalIntent`, `lastRenewalDate`, etc.) if one has been cached,
1005
+ * or `undefined` if no offline entitlement exists for this product.
1006
+ *
1007
+ * Returns `undefined` for all products until `ready()` has resolved.
1008
+ *
1009
+ * @example
1010
+ * ```typescript
1011
+ * const entitlement = offline.find('premium');
1012
+ * if (entitlement?.expiryDate) {
1013
+ * const daysLeft = Math.ceil((entitlement.expiryDate - Date.now()) / 86400000);
1014
+ * showRenewalBanner(daysLeft);
1015
+ * }
1016
+ * ```
1017
+ */
1018
+ find(productId: string): PersistedPurchase | undefined;
1019
+ /** Persist all `VerifiedPurchase`s in a verified receipt to storage. */
1020
+ private onVerified;
1021
+ /** Find the persisted purchase for a productId across all platforms. */
1022
+ private findPersisted;
1023
+ /** Load the persisted payload from storage into the in-memory cache. */
1024
+ private loadFromStorage;
1025
+ /** Serialize the in-memory cache to storage. */
1026
+ private saveToStorage;
1027
+ /** Fire an event to all registered callbacks, deduplicating per productId. */
1028
+ private fireEvent;
1029
+ }
1030
+ /** Persisted subset of {@link VerifiedPurchase}, stored by the offline entitlements layer. */
1031
+ interface PersistedPurchase {
1032
+ id: string;
1033
+ platform: Platform;
1034
+ expiryDate?: number;
1035
+ isExpired?: boolean;
1036
+ renewalIntent?: string;
1037
+ lastRenewalDate?: number;
1038
+ purchaseDate?: number;
1039
+ cancelationReason?: CancelationReason;
1040
+ isBillingRetryPeriod?: boolean;
1041
+ }
1042
+ }
882
1043
  /**
883
1044
  * Namespace for the cordova-plugin-purchase plugin.
884
1045
  *
@@ -911,7 +1072,7 @@ declare namespace CdvPurchase {
911
1072
  /**
912
1073
  * Current release number of the plugin.
913
1074
  */
914
- const PLUGIN_VERSION = "13.17.2";
1075
+ const PLUGIN_VERSION = "13.19.1";
915
1076
  /**
916
1077
  * Entry class of the plugin.
917
1078
  */
@@ -2046,7 +2207,23 @@ declare namespace CdvPurchase {
2046
2207
  lastRenewalDate?: Date;
2047
2208
  /** Time when the subscription is set to expire following this transaction */
2048
2209
  expirationDate?: Date;
2049
- /** True when the transaction has been acknowledged to the platform. */
2210
+ /**
2211
+ * True when the transaction has been acknowledged to the platform.
2212
+ *
2213
+ * - Google Play: the purchase was acknowledged (or consumed).
2214
+ * - Apple App Store (StoreKit 2, Capacitor build only): the transaction was finished. StoreKit 2 re-reports
2215
+ * finished non-consumables and non-renewing subscriptions as `approved` at every
2216
+ * app launch; check this flag in your `approved()` handler to skip delivering them again.
2217
+ *
2218
+ * Undefined when the platform doesn't report it (including StoreKit 1 and the Cordova
2219
+ * `cordova-plugin-purchase-storekit2` plugin).
2220
+ *
2221
+ * @example
2222
+ * store.when().approved(transaction => {
2223
+ * if (transaction.isAcknowledged) return transaction.finish(); // already delivered
2224
+ * deliver(transaction).then(() => transaction.finish());
2225
+ * });
2226
+ */
2050
2227
  isAcknowledged?: boolean;
2051
2228
  /** True when the transaction is still pending payment. */
2052
2229
  isPending?: boolean;
@@ -2880,7 +3057,7 @@ declare namespace CdvPurchase {
2880
3057
  /** Extended callbacks with SK2 fields (same as SK2BridgeCallbacks) */
2881
3058
  interface CapacitorBridgeCallbacks extends Bridge.BridgeCallbacks {
2882
3059
  purchased: (transactionIdentifier: string, productId: string, originalTransactionIdentifier?: string, transactionDate?: string, discountId?: string, expirationDate?: string, jwsRepresentation?: string, quantity?: number) => void;
2883
- restored: (transactionIdentifier: string, productId: string, originalTransactionIdentifier?: string, transactionDate?: string, discountId?: string, expirationDate?: string, jwsRepresentation?: string, quantity?: number) => void;
3060
+ restored: (transactionIdentifier: string, productId: string, originalTransactionIdentifier?: string, transactionDate?: string, discountId?: string, expirationDate?: string, jwsRepresentation?: string, quantity?: number, isFinished?: boolean) => void;
2884
3061
  }
2885
3062
  class CapacitorNativeBridge implements Bridge.BridgeInterface {
2886
3063
  appStoreReceipt: ApplicationReceipt | null;
@@ -3144,8 +3321,14 @@ declare namespace CdvPurchase {
3144
3321
  deferred: (productId: string) => void;
3145
3322
  /** Called when a transaction is in "finished" state */
3146
3323
  finished: (transactionIdentifier: string, productId: string) => void;
3147
- /** Called when a transaction is in "restored" state */
3148
- restored: (transactionIdentifier: string, productId: string, originalTransactionIdentifier?: string, transactionDate?: string, discountId?: string, expirationDate?: string, jwsRepresentation?: string, quantity?: number) => void;
3324
+ /**
3325
+ * Called when a transaction is in "restored" state.
3326
+ *
3327
+ * `isFinished` is set by StoreKit 2 bridges when known: true when the app
3328
+ * already finished the transaction (e.g. a finished entitlement re-emitted
3329
+ * at launch), false when it is still unfinished.
3330
+ */
3331
+ restored: (transactionIdentifier: string, productId: string, originalTransactionIdentifier?: string, transactionDate?: string, discountId?: string, expirationDate?: string, jwsRepresentation?: string, quantity?: number, isFinished?: boolean) => void;
3149
3332
  /** Called when the application receipt is refreshed */
3150
3333
  receiptsRefreshed: (receipt: ApplicationReceipt) => void;
3151
3334
  /** Called when a call to "restore" failed */
@@ -6579,6 +6762,31 @@ declare namespace CdvPurchase {
6579
6762
  interface UnverifiedReceipt {
6580
6763
  receipt: Receipt;
6581
6764
  payload: Validator.Response.ErrorPayload;
6765
+ /**
6766
+ * True when validation failed for a temporary reason: the validator
6767
+ * (or the store behind it) couldn't be reached, timed out, or answered
6768
+ * with a server error. The receipt may well be valid.
6769
+ *
6770
+ * Don't revoke access on a transient failure: keep what your app (or
6771
+ * your server) last knew, and validate again later.
6772
+ *
6773
+ * The plugin already retried a few times (with backoff) before firing
6774
+ * a transient failure.
6775
+ *
6776
+ * False when the validator gave a definitive answer (e.g. invalid
6777
+ * receipt, expired subscription, authentication error).
6778
+ *
6779
+ * Transient:
6780
+ * - HTTP 5xx, from the transport or as `payload.status` in the response body
6781
+ * - `CONNECTION_FAILED` (6778002) from the validator
6782
+ * - request timeout, no network, unreadable response
6783
+ *
6784
+ * Definitive: everything else (HTTP 400 `INVALID_PAYLOAD`, 401/403, 410,
6785
+ * 419 expired, ...).
6786
+ *
6787
+ * Set by the plugin on every `unverified` event since v13.19.0.
6788
+ */
6789
+ transient?: boolean;
6582
6790
  }
6583
6791
  /** Receipt data as validated by the receipt validation server */
6584
6792
  class VerifiedReceipt {