capacitor-plugin-cdv-purchase 13.18.0 → 13.20.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-plugin-cdv-purchase",
3
- "version": "13.18.0",
3
+ "version": "13.20.0",
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",
@@ -38,6 +38,7 @@
38
38
  "dist/",
39
39
  "types/",
40
40
  "www/",
41
+ "src/ts/platforms/iaptic-js/iaptic-js-types.d.ts",
41
42
  "android/",
42
43
  "!android/build",
43
44
  "ios/",
@@ -0,0 +1,451 @@
1
+ declare namespace ModuleIapticJS {
2
+ /**
3
+ * Type of product that can be purchased:
4
+ *
5
+ * - subscription: A recurring subscription product
6
+ * - consumable: A product that can be purchased multiple times
7
+ * - non consumable: A product that can only be purchased once
8
+ * - paid subscription: An auto-renewing subscription
9
+ * - non renewing subscription: A subscription that does not auto-renew
10
+ * - application: The application itself
11
+ * - product: A generic product
12
+ *
13
+ * `'non_consumable'` is deprecated: the server never sends it. It is kept so existing
14
+ * code compiles, and will be removed in the next major version. Use `'non consumable'`.
15
+ */
16
+ export type ProductType = 'subscription' | 'consumable' | 'non consumable' | 'paid subscription' | 'non renewing subscription' | 'application' | 'product' | 'non_consumable';
17
+ /**
18
+ * Represents a pricing phase for an offer
19
+ * @remarks
20
+ * A pricing phase defines how much and when a customer will be charged.
21
+ * For example, a subscription might have an initial trial phase followed by a recurring payment phase.
22
+ */
23
+ export interface PricingPhase {
24
+ /** Price in micros (1/1,000,000 of the currency unit). For example, $1.99 = 1990000 micros */
25
+ priceMicros: number;
26
+ /** ISO 4217 currency code (e.g., 'USD', 'EUR', 'JPY') */
27
+ currency: string;
28
+ /**
29
+ * ISO 8601 duration string representing the billing period
30
+ * @example
31
+ * - 'P1M' = 1 month
32
+ * - 'P1Y' = 1 year
33
+ * - 'P7D' = 7 days
34
+ *
35
+ * Absent for one-time purchases.
36
+ */
37
+ billingPeriod: string;
38
+ /**
39
+ * Defines how the subscription recurs
40
+ * @remarks
41
+ * - INFINITE_RECURRING: Subscription continues until explicitly cancelled
42
+ * - NON_RECURRING: One-time purchase that does not auto-renew
43
+ * - FINITE_RECURRING: Subscription that renews a fixed number of times
44
+ */
45
+ recurrenceMode: 'INFINITE_RECURRING' | 'NON_RECURRING' | 'FINITE_RECURRING';
46
+ /**
47
+ * Defines when payment is collected
48
+ * @remarks
49
+ * - PayAsYouGo: Payment collected at the start of each billing period
50
+ * - UpFront: Full payment collected at time of purchase
51
+ * - FreeTrial: No payment during this phase
52
+ *
53
+ * `'PayUpFront'` is deprecated: the server never sends it. It is kept so existing
54
+ * code compiles, and will be removed in the next major version. Use `'UpFront'`.
55
+ */
56
+ paymentMode: 'PayAsYouGo' | 'UpFront' | 'FreeTrial' | 'PayUpFront';
57
+ }
58
+ /**
59
+ * Represents a purchase offer for a product
60
+ * @remarks
61
+ * An offer defines how a product can be purchased, including pricing and billing details
62
+ */
63
+ export interface Offer {
64
+ /** Unique identifier for the offer in the format 'platform:id' */
65
+ id: string;
66
+ /** Payment platform identifier (e.g., 'stripe', 'google', 'apple') */
67
+ platform: string;
68
+ /** Type of the offer */
69
+ offerType: 'Subscription' | 'Default' | 'Introductory';
70
+ /** Array of pricing phases that make up this offer's payment schedule */
71
+ pricingPhases: PricingPhase[];
72
+ }
73
+ /**
74
+ * Represents a product that can be purchased
75
+ */
76
+ export interface Product {
77
+ /** Type of the product (subscription, consumable, etc.) */
78
+ type: ProductType;
79
+ /** Unique identifier for the product */
80
+ id: string;
81
+ /** Display name of the product shown to customers */
82
+ title: string;
83
+ /** Optional detailed description of the product */
84
+ description?: string;
85
+ /** Available purchase offers for this product */
86
+ offers: Offer[];
87
+ /**
88
+ * Optional additional data associated with the product
89
+ * @remarks
90
+ * This can be used to store custom data needed by your application
91
+ */
92
+ metadata?: Record<string, any>;
93
+ /** Optional platform identifier (e.g., 'stripe') */
94
+ platform?: string;
95
+ }
96
+ /**
97
+ * Represents a purchased product
98
+ */
99
+ export interface Purchase {
100
+ /** Unique identifier for the purchase */
101
+ purchaseId: string;
102
+ /** Unique identifier for the transaction */
103
+ transactionId: string;
104
+ /** Unique identifier for the product */
105
+ productId: string;
106
+ /** Platform identifier (e.g., 'stripe') */
107
+ platform: 'stripe';
108
+ /** Date of the purchase (ISO 8601). Can be absent, check before use. */
109
+ purchaseDate: string;
110
+ /** Date of the last renewal (ISO 8601). Can be absent, check before use. */
111
+ lastRenewalDate: string;
112
+ /** Date of the expiration (ISO 8601). Absent for one-time purchases. */
113
+ expirationDate: string;
114
+ /**
115
+ * Intent of the renewal: 'Lapse' to let the subscription expire, 'Renew' to keep it
116
+ *
117
+ * `'Cancel'` is deprecated: the server never sends it. It is kept so existing
118
+ * code compiles, and will be removed in the next major version. Use `'Lapse'`.
119
+ */
120
+ renewalIntent: 'Lapse' | 'Renew' | 'Cancel';
121
+ /** Whether the purchase is a trial period */
122
+ isTrialPeriod: boolean;
123
+ /** Price in micros (1/1,000,000 of the currency unit) */
124
+ amountMicros: number;
125
+ /** Currency code (e.g., 'USD', 'EUR') */
126
+ currency: string;
127
+ }
128
+ /**
129
+ * Represents an order for a product
130
+ */
131
+ export interface Order {
132
+ /** Unique identifier for the offer */
133
+ offerId: string;
134
+ /** Username in the application */
135
+ applicationUsername: string;
136
+ /** URL to redirect to on success */
137
+ successUrl: string;
138
+ /** URL to redirect to on cancel */
139
+ cancelUrl: string;
140
+ /** Optional access token for the user */
141
+ accessToken?: string;
142
+ }
143
+ /** Plan change request */
144
+ export interface PlanChange {
145
+ /** Unique identifier for the purchase to replace */
146
+ purchaseId: string;
147
+ /** New offer to subscribe to */
148
+ offerId: string;
149
+ /** Optional access token for the user */
150
+ accessToken?: string;
151
+ }
152
+ /**
153
+ * Configuration options for creating an Iaptic adapter
154
+ * @remarks
155
+ * Currently only supports Stripe integration, but may support other platforms in the future
156
+ */
157
+ export interface Config {
158
+ /**
159
+ * Adapter type (currently only 'stripe' is supported)
160
+ * @remarks
161
+ * This field determines which payment platform will be used
162
+ */
163
+ type: 'stripe';
164
+ /**
165
+ * Your application name as configured on iaptic.com
166
+ * @remarks
167
+ * This must match exactly what you set up in the Iaptic dashboard
168
+ */
169
+ appName: string;
170
+ /**
171
+ * Your Iaptic public API key
172
+ * @remarks
173
+ * Can be found in your Iaptic dashboard under API settings
174
+ */
175
+ apiKey: string;
176
+ /**
177
+ * Stripe publishable key
178
+ */
179
+ stripePublicKey: string;
180
+ /**
181
+ * Optional custom Iaptic URL for private deployments
182
+ * @remarks
183
+ * Only needed if you're using a self-hosted version of Iaptic
184
+ */
185
+ customIapticUrl?: string;
186
+ }
187
+
188
+
189
+ /**
190
+ * Response from Iaptic's /stripe/products API
191
+ * @internal
192
+ */
193
+ export interface GetProductsResponse {
194
+ /** Whether the request was successful */
195
+ ok: boolean;
196
+ /** Array of available products */
197
+ products: Product[];
198
+ }
199
+ /**
200
+ * Response from Iaptic's /stripe/checkout API
201
+ * @internal
202
+ */
203
+ export interface PostCheckoutSessionResponse {
204
+ /** Whether the request was successful */
205
+ ok: boolean;
206
+ /** Stripe Checkout URL where the customer will be redirected */
207
+ url: string;
208
+ /** Access token for the customer */
209
+ accessToken: string;
210
+ }
211
+ /**
212
+ * Response from Iaptic's /stripe/purchases API
213
+ * @internal
214
+ */
215
+ export interface GetPurchasesResponse {
216
+ /** Whether the request was successful */
217
+ ok: boolean;
218
+ /** Array of customer's purchases */
219
+ purchases: Purchase[];
220
+ /** Optional new access token */
221
+ newAccessToken?: string;
222
+ }
223
+ /**
224
+ * Response from Iaptic's /stripe/change-plan API
225
+ * @internal
226
+ */
227
+ export interface ChangePlanResponse {
228
+ /** Whether the request was successful */
229
+ ok: boolean;
230
+ /** Updated purchase details */
231
+ purchase: Purchase;
232
+ /** Optional new access token */
233
+ newAccessToken?: string;
234
+ }
235
+ /**
236
+ * Main class for interacting with Iaptic's Stripe integration
237
+ * @remarks
238
+ * This class handles all Stripe-related operations including product listing,
239
+ * checkout sessions, and subscription management.
240
+ */
241
+ export class IapticStripe {
242
+ /**
243
+ * Creates a new IapticStripe instance
244
+ * @param config - Configuration options for the Stripe integration
245
+ * @throws Error if required configuration is missing
246
+ */
247
+ constructor(config: Config);
248
+ /**
249
+ * Gets a list of available products and their pricing
250
+ * @returns Promise resolving to an array of products
251
+ * @remarks
252
+ * Results are cached locally. Use refreshProducts() to force a fresh fetch.
253
+ */
254
+ getProducts(): Promise<Product[]>;
255
+ /**
256
+ * Forces a refresh of the products list from the server
257
+ * @returns Promise resolving to an array of products
258
+ */
259
+ refreshProducts(): Promise<Product[]>;
260
+ /**
261
+ * Gets the current access token if one exists
262
+ * @returns The current access token or undefined if none exists
263
+ */
264
+ getAccessToken(): string | undefined;
265
+ /**
266
+ * Creates a new order and redirects to Stripe Checkout
267
+ * @param params - Order parameters including product and URLs
268
+ * @throws Error if the checkout session creation fails
269
+ * @remarks
270
+ * This method will redirect the user to Stripe's checkout page on success
271
+ */
272
+ order(params: Order): Promise<void>;
273
+ /**
274
+ * Gets the customer's purchase history
275
+ * @param accessToken - Optional access token (uses stored token if not provided)
276
+ * @returns Promise resolving to an array of purchases
277
+ * @remarks
278
+ * This method will automatically schedule refresh operations for active subscriptions
279
+ */
280
+ getPurchases(accessToken?: string): Promise<Purchase[]>;
281
+ /**
282
+ * Redirects to Stripe Customer Portal for subscription management
283
+ * @param params - Portal parameters including return URL
284
+ * @throws Error if portal session creation fails
285
+ * @remarks
286
+ * This method will redirect the user to Stripe's customer portal on success
287
+ */
288
+ redirectToCustomerPortal(params: {
289
+ returnUrl?: string;
290
+ accessToken?: string;
291
+ }): Promise<void>;
292
+ /**
293
+ * Changes a subscription plan
294
+ * @param planChange - Plan change parameters
295
+ * @returns Promise resolving to the updated purchase details
296
+ * @remarks
297
+ * This method handles immediate plan changes without requiring customer portal access
298
+ */
299
+ changePlan(planChange: PlanChange): Promise<Purchase>;
300
+ /**
301
+ * Clears all stored data including access tokens and cached products
302
+ */
303
+ clearStoredData(): void;
304
+ onAccessTokenChange(callback: (accessToken: string) => void): void;
305
+ }
306
+
307
+
308
+
309
+ /**
310
+ * Represents a scheduled refresh for a subscription
311
+ */
312
+ export interface ScheduledRefresh {
313
+ /** Unique identifier for the scheduled refresh */
314
+ id: string;
315
+ /** Unique identifier for the subscription */
316
+ subscriptionId: string;
317
+ /** Date and time of the scheduled refresh */
318
+ scheduledAt: number;
319
+ /** Whether the refresh has been completed */
320
+ completed: boolean;
321
+ /** Whether the refresh is currently in progress */
322
+ inProgress: boolean;
323
+ /** Reason for the scheduled refresh */
324
+ reason: string;
325
+ }
326
+ export class RefreshScheduler {
327
+ schedules: ScheduledRefresh[];
328
+ constructor(iapticStripe: IapticStripe);
329
+ scheduleRefresh(subscriptionId: string, date: Date, reason: string): void;
330
+ schedulePurchaseRefreshes(purchase: Purchase): void;
331
+ clearSchedules(): void;
332
+ }
333
+
334
+ /**
335
+ * Utility functions for the Iaptic library
336
+ */
337
+ export class Utils {
338
+ /**
339
+ * Base64 encode a string with fallbacks for different environments
340
+ * @param str - String to encode
341
+ * @returns Base64 encoded string
342
+ * @remarks
343
+ * Handles non-ASCII characters and provides fallbacks for older browsers
344
+ * and Node.js environments
345
+ */
346
+ static base64Encode(str: string): string;
347
+ /**
348
+ * Get and parse a JSON item from localStorage
349
+ * @param key - Storage key
350
+ * @returns Parsed value or null if not found/invalid
351
+ * @internal
352
+ */
353
+ static storageGetJson<T>(key: string): T | null;
354
+ /**
355
+ * Get a string item from localStorage
356
+ * @param key - Storage key
357
+ * @returns String value or null if not found
358
+ * @internal
359
+ */
360
+ static storageGetString(key: string): string | null;
361
+ /**
362
+ * Store a JSON-serializable value in localStorage
363
+ * @param key - Storage key
364
+ * @param value - Value to store
365
+ * @returns true if successful, false if storage failed
366
+ * @internal
367
+ */
368
+ static storageSetJson(key: string, value: any): boolean;
369
+ /**
370
+ * Store a string value in localStorage
371
+ * @param key - Storage key
372
+ * @param value - String to store
373
+ * @returns true if successful, false if storage failed
374
+ * @internal
375
+ */
376
+ static storageSetString(key: string, value: string): boolean;
377
+ /**
378
+ * Remove an item from localStorage
379
+ * @param key - Storage key
380
+ * @returns true if successful, false if removal failed
381
+ * @internal
382
+ */
383
+ static storageRemove(key: string): boolean;
384
+ /**
385
+ * Build a URL with query parameters
386
+ * @param baseUrl - Base URL without query parameters
387
+ * @param params - Object containing query parameters
388
+ * @returns Complete URL with encoded query parameters
389
+ * @remarks
390
+ * Handles URL encoding and removes undefined/null parameters
391
+ * @internal
392
+ */
393
+ static buildUrl(baseUrl: string, params: Record<string, string>): string;
394
+ /**
395
+ * Format a currency amount from micros with proper localization
396
+ * @param amountMicros - Amount in micros (1/1,000,000 of currency unit)
397
+ * @param currency - ISO 4217 currency code (e.g., 'USD', 'EUR')
398
+ * @returns Formatted currency string
399
+ * @example
400
+ * ```ts
401
+ * Utils.formatCurrency(1990000, 'USD') // Returns "$1.99"
402
+ * Utils.formatCurrency(1000000, 'EUR') // Returns "€1"
403
+ * ```
404
+ */
405
+ static formatCurrency(amountMicros: number, currency: string): string;
406
+ /**
407
+ * Format an ISO 8601 period string into human-readable English
408
+ * @param period - ISO 8601 duration string
409
+ * @returns Human-readable period string
410
+ * @example
411
+ * ```ts
412
+ * Utils.formatBillingPeriodEN('P1M') // Returns "Monthly"
413
+ * Utils.formatBillingPeriodEN('P3M') // Returns "Every 3 months"
414
+ * Utils.formatBillingPeriodEN('P1Y') // Returns "Yearly"
415
+ * ```
416
+ */
417
+ static formatBillingPeriodEN(period: string): string;
418
+ }
419
+
420
+ /**
421
+ * Creates an Iaptic adapter for the specified payment platform
422
+ * @param config - Configuration options for the adapter
423
+ * @returns An initialized adapter instance
424
+ * @throws Error if the specified adapter type is not supported
425
+ * @example
426
+ * ```ts
427
+ * const iaptic = createAdapter({
428
+ * type: 'stripe',
429
+ * appName: 'my-app',
430
+ * apiKey: '1234567890'
431
+ * });
432
+ * ```
433
+ */
434
+ export function createAdapter(config: Config): IapticStripe;
435
+ }
436
+
437
+ /**
438
+ * Global IapticJS object available in browser environments
439
+ */
440
+ interface Window {
441
+ IapticJS: {
442
+ /** Current version of the IapticJS library */
443
+ version: string;
444
+ /** Function to create a new Iaptic adapter */
445
+ createAdapter: typeof ModuleIapticJS.createAdapter;
446
+ /** Stripe adapter class */
447
+ IapticStripe: typeof ModuleIapticJS.IapticStripe;
448
+ /** Utility functions */
449
+ Utils: typeof ModuleIapticJS.Utils;
450
+ };
451
+ }
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 {
@@ -905,8 +931,8 @@ declare namespace CdvPurchase {
905
931
  }
906
932
  /**
907
933
  * Persist a subset of {@link VerifiedPurchase} to device storage so that
908
- * `store.owned()` works when the device is offline or has just restarted
909
- * without connectivity.
934
+ * `offline.isOwned()` can answer "is this product owned?" when the device
935
+ * is offline or has just restarted without connectivity.
910
936
  *
911
937
  * Phase 1 — unsigned cache. No JWT, no crypto, no server changes.
912
938
  *
@@ -1046,7 +1072,7 @@ declare namespace CdvPurchase {
1046
1072
  /**
1047
1073
  * Current release number of the plugin.
1048
1074
  */
1049
- const PLUGIN_VERSION = "13.18.0";
1075
+ const PLUGIN_VERSION = "13.20.0";
1050
1076
  /**
1051
1077
  * Entry class of the plugin.
1052
1078
  */
@@ -2181,7 +2207,23 @@ declare namespace CdvPurchase {
2181
2207
  lastRenewalDate?: Date;
2182
2208
  /** Time when the subscription is set to expire following this transaction */
2183
2209
  expirationDate?: Date;
2184
- /** 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
+ */
2185
2227
  isAcknowledged?: boolean;
2186
2228
  /** True when the transaction is still pending payment. */
2187
2229
  isPending?: boolean;
@@ -3015,7 +3057,7 @@ declare namespace CdvPurchase {
3015
3057
  /** Extended callbacks with SK2 fields (same as SK2BridgeCallbacks) */
3016
3058
  interface CapacitorBridgeCallbacks extends Bridge.BridgeCallbacks {
3017
3059
  purchased: (transactionIdentifier: string, productId: string, originalTransactionIdentifier?: string, transactionDate?: string, discountId?: string, expirationDate?: string, jwsRepresentation?: string, quantity?: number) => void;
3018
- 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;
3019
3061
  }
3020
3062
  class CapacitorNativeBridge implements Bridge.BridgeInterface {
3021
3063
  appStoreReceipt: ApplicationReceipt | null;
@@ -3279,8 +3321,14 @@ declare namespace CdvPurchase {
3279
3321
  deferred: (productId: string) => void;
3280
3322
  /** Called when a transaction is in "finished" state */
3281
3323
  finished: (transactionIdentifier: string, productId: string) => void;
3282
- /** Called when a transaction is in "restored" state */
3283
- 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;
3284
3332
  /** Called when the application receipt is refreshed */
3285
3333
  receiptsRefreshed: (receipt: ApplicationReceipt) => void;
3286
3334
  /** Called when a call to "restore" failed */
@@ -6635,6 +6683,9 @@ declare namespace CdvPurchase {
6635
6683
  *
6636
6684
  * It might be present when the server had to fallback to a backup validation solution. */
6637
6685
  warning?: string;
6686
+ /** Present when the server answered from its last successful validation of this
6687
+ * receipt because the store couldn't be reached, instead of asking the store. */
6688
+ cache?: ValidatorCacheInfo;
6638
6689
  /** Date and time the receipt was validated.
6639
6690
  *
6640
6691
  * It will provide the client with a more reliable clock time
@@ -6642,6 +6693,15 @@ declare namespace CdvPurchase {
6642
6693
  date?: ISODate;
6643
6694
  };
6644
6695
  }
6696
+ /** Marker added by the validator to an answer that wasn't confirmed by the store just now. */
6697
+ interface ValidatorCacheInfo {
6698
+ /** Where the answer comes from. `last-verified`: the last answer the store confirmed for this exact receipt. */
6699
+ source: 'last-verified' | (string & {});
6700
+ /** Why the cache was used. `store_unreachable`: the store (Apple, Google, ...) couldn't be reached. */
6701
+ reason: 'store_unreachable' | (string & {});
6702
+ /** When the store last confirmed this receipt (ISO 8601). */
6703
+ lastConfirmedAt: ISODate;
6704
+ }
6645
6705
  type NativeTransaction = ({
6646
6706
  type: 'braintree';
6647
6707
  data: Braintree.TransactionObject;
@@ -6714,6 +6774,39 @@ declare namespace CdvPurchase {
6714
6774
  interface UnverifiedReceipt {
6715
6775
  receipt: Receipt;
6716
6776
  payload: Validator.Response.ErrorPayload;
6777
+ /**
6778
+ * True when validation failed without a definitive answer about the
6779
+ * receipt: the validator (or the store behind it) couldn't be
6780
+ * reached, timed out, answered with a server error, or responded with
6781
+ * data the plugin couldn't process. The receipt may well be valid.
6782
+ *
6783
+ * Don't revoke access on a transient failure: keep what your app (or
6784
+ * your server) last knew, and validate again later.
6785
+ *
6786
+ * The plugin already retried a few times (with backoff) before firing
6787
+ * a transient failure. This matches the plugin's own retry decision:
6788
+ * since v13.20.0, `BAD_RESPONSE` (the validator's answer was
6789
+ * malformed) and `VERIFICATION_FAILED` (the plugin failed while
6790
+ * processing the validator's answer) are reported as transient (they
6791
+ * used to be `transient: false`).
6792
+ *
6793
+ * False when the validator gave a definitive answer (e.g. invalid
6794
+ * receipt, expired subscription, authentication error).
6795
+ *
6796
+ * Transient:
6797
+ * - HTTP 5xx, from the transport or as `payload.status` in the response body
6798
+ * - `CONNECTION_FAILED` (6778002) from the validator
6799
+ * - request timeout, no network, unreadable response
6800
+ * - `BAD_RESPONSE`: the validator's answer was malformed
6801
+ * - `VERIFICATION_FAILED`: the plugin failed while processing the
6802
+ * validator's answer
6803
+ *
6804
+ * Definitive: everything else (HTTP 400 `INVALID_PAYLOAD`, 401/403, 410,
6805
+ * 419 expired, ...).
6806
+ *
6807
+ * Set by the plugin on every `unverified` event since v13.19.0.
6808
+ */
6809
+ transient?: boolean;
6717
6810
  }
6718
6811
  /** Receipt data as validated by the receipt validation server */
6719
6812
  class VerifiedReceipt {
@@ -6746,6 +6839,17 @@ declare namespace CdvPurchase {
6746
6839
  * When a warning is present, you should threat the content of this receipt accordingly.
6747
6840
  */
6748
6841
  warning?: string;
6842
+ /**
6843
+ * Set when the validator answered from its last successful validation of this receipt,
6844
+ * because the store (Apple, Google, ...) couldn't be reached.
6845
+ *
6846
+ * `undefined` means the store confirmed this receipt during the validation.
6847
+ * The content is the last one confirmed by the store, `cache.lastConfirmedAt` tells when.
6848
+ *
6849
+ * Receipts with a `cache` are as valid as the others (no purchase is owned past its `expiryDate`),
6850
+ * but you might prefer to validate again later, or tell your users.
6851
+ */
6852
+ cache?: Validator.Response.ValidatorCacheInfo;
6749
6853
  /**
6750
6854
  * Id of the product that have been validated. Used internally.
6751
6855
  */