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/README.md +32 -0
- package/dist/index.js +370 -15
- package/dist/index.js.map +1 -1
- package/ios/Sources/PurchasePlugin/PurchasePlugin.swift +126 -36
- package/package.json +2 -1
- package/src/ts/platforms/iaptic-js/iaptic-js-types.d.ts +326 -0
- package/www/store.d.ts +213 -5
- package/www/store.js +370 -15
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.
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
3148
|
-
|
|
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 {
|