@nosslabs/iap 8.0.0 → 8.2.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/CHANGELOG.md +533 -1
- package/README.md +34 -6
- package/dist/index.cjs +2084 -507
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +910 -217
- package/dist/index.d.ts +910 -217
- package/dist/index.js +2083 -508
- package/dist/index.js.map +1 -1
- package/package.json +8 -4
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,403 @@ declare class HttpClient {
|
|
|
64
64
|
private fetchWithTimeout;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
type ProductType = 'subscription' | 'product' | 'consumable';
|
|
68
|
+
interface ConfiguredProduct {
|
|
69
|
+
id: string;
|
|
70
|
+
type: ProductType;
|
|
71
|
+
/**
|
|
72
|
+
* Required for Android subscriptions — the base plan identifier configured in
|
|
73
|
+
* Google Play Console. Ignored on iOS and for non-subscription products.
|
|
74
|
+
*
|
|
75
|
+
* Keep it in step with Play Console. When it matches no offer, the native
|
|
76
|
+
* plugin does **not** fail — it falls back to the first available base
|
|
77
|
+
* plan, so a stale value silently purchases a different plan. See
|
|
78
|
+
* `docs/guide/native-layer.md`.
|
|
79
|
+
*/
|
|
80
|
+
androidPlanId?: string;
|
|
81
|
+
}
|
|
82
|
+
/** What kind of offer this is, as the store classifies it. */
|
|
83
|
+
type SubscriptionOfferType = 'introductory' | 'promotional' | 'win-back' | 'unknown';
|
|
84
|
+
/**
|
|
85
|
+
* How the offer is paid for.
|
|
86
|
+
*
|
|
87
|
+
* - `free-trial` — no charge for the offer period.
|
|
88
|
+
* - `pay-up-front` — one discounted charge covering the whole offer period.
|
|
89
|
+
* - `pay-as-you-go` — a discounted charge each period, for `periodCount`
|
|
90
|
+
* periods.
|
|
91
|
+
*/
|
|
92
|
+
type SubscriptionOfferPayment = 'free-trial' | 'pay-up-front' | 'pay-as-you-go' | 'unknown';
|
|
93
|
+
/**
|
|
94
|
+
* An introductory or promotional offer on a subscription, normalized across
|
|
95
|
+
* platforms.
|
|
96
|
+
*
|
|
97
|
+
* Render `priceString` as-is; it is the store's own localized string, in the
|
|
98
|
+
* store's currency and format. Never reconstruct a price from `priceMicros`
|
|
99
|
+
* for display — that is for arithmetic and comparison only.
|
|
100
|
+
*/
|
|
101
|
+
interface SubscriptionOffer {
|
|
102
|
+
/**
|
|
103
|
+
* The store's identifier for the offer. `null` for an offer the store does
|
|
104
|
+
* not name — typically an iOS introductory offer, or an Android base-plan
|
|
105
|
+
* intro phase (which has no offer id of its own).
|
|
106
|
+
*/
|
|
107
|
+
id: string | null;
|
|
108
|
+
type: SubscriptionOfferType;
|
|
109
|
+
payment: SubscriptionOfferPayment;
|
|
110
|
+
/** Localized price string for the offer, e.g. "$0.00" or "₱49.00". */
|
|
111
|
+
priceString: string;
|
|
112
|
+
/** BigInt-safe string of micro-units, e.g. "0" or "49000000". */
|
|
113
|
+
priceMicros: string;
|
|
114
|
+
/** ISO 4217 code, e.g. "PHP". */
|
|
115
|
+
currency: string;
|
|
116
|
+
/**
|
|
117
|
+
* Offer period as an ISO 8601 duration, e.g. `"P1W"`. `null` if unknown.
|
|
118
|
+
*
|
|
119
|
+
* Single-component on Android: the native plugin's period parser keeps
|
|
120
|
+
* only the largest non-zero unit, so a Play period of `P1M15D` arrives
|
|
121
|
+
* as `P1M`.
|
|
122
|
+
*/
|
|
123
|
+
period: string | null;
|
|
124
|
+
/**
|
|
125
|
+
* How many periods the offer applies for.
|
|
126
|
+
*
|
|
127
|
+
* On iOS: `1` for a free trial or a pay-up-front offer, higher for
|
|
128
|
+
* pay-as-you-go.
|
|
129
|
+
*
|
|
130
|
+
* **On Android this is usually `0`**, and that does not mean zero
|
|
131
|
+
* periods. It is Play's `getBillingCycleCount()`, which reports `0` for
|
|
132
|
+
* both a non-recurring phase and an infinitely recurring one — so a
|
|
133
|
+
* pay-up-front Android offer reports `0` too. Treat `0` as "the store
|
|
134
|
+
* did not say" and do not render it as a count.
|
|
135
|
+
*/
|
|
136
|
+
periodCount: number;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Native product info merged with configured metadata.
|
|
140
|
+
* Library-public shape — kept stable across Capacitor versions.
|
|
141
|
+
*/
|
|
142
|
+
interface Product {
|
|
143
|
+
id: string;
|
|
144
|
+
type: ProductType;
|
|
145
|
+
title: string;
|
|
146
|
+
description: string;
|
|
147
|
+
/** Localized price string, e.g. "$4.99". Always render this — never hardcode. */
|
|
148
|
+
priceString: string;
|
|
149
|
+
/** BigInt as string to avoid precision loss, e.g. "4990000". */
|
|
150
|
+
priceMicros: string;
|
|
151
|
+
/** ISO 4217 code, e.g. "USD". */
|
|
152
|
+
currency: string;
|
|
153
|
+
/**
|
|
154
|
+
* The recurring billing period as an ISO 8601 duration, e.g. `"P1M"`.
|
|
155
|
+
*
|
|
156
|
+
* **iOS only.** `null` on Android: the native plugin collapses Play's
|
|
157
|
+
* pricing phases before exposing them and never surfaces a base plan's
|
|
158
|
+
* billing period, so it is not reachable. Where you need the cadence on
|
|
159
|
+
* both platforms, take it from your own product configuration — you chose
|
|
160
|
+
* the base plan's period in Play Console.
|
|
161
|
+
*/
|
|
162
|
+
subscriptionPeriod?: string | null;
|
|
163
|
+
/**
|
|
164
|
+
* The subscription group this product belongs to. Products in one group are
|
|
165
|
+
* mutually exclusive, and on iOS introductory-offer eligibility is assessed
|
|
166
|
+
* per group rather than per product.
|
|
167
|
+
*
|
|
168
|
+
* **iOS only.** `null` on Android, which has no equivalent concept exposed.
|
|
169
|
+
*/
|
|
170
|
+
subscriptionGroupId?: string | null;
|
|
171
|
+
/**
|
|
172
|
+
* The introductory offer on this subscription, or `null` when it has none.
|
|
173
|
+
*
|
|
174
|
+
* **Whether *this user* is eligible for it cannot be known on the device.**
|
|
175
|
+
* The native plugin exposes no eligibility query on either platform, so an
|
|
176
|
+
* offer being present here means "this product has one", not "this user
|
|
177
|
+
* will get it". The store's own purchase sheet applies eligibility at
|
|
178
|
+
* purchase time and is the authority. Write paywall copy that reads
|
|
179
|
+
* correctly either way, or confirm eligibility server-side.
|
|
180
|
+
*/
|
|
181
|
+
introductoryOffer?: SubscriptionOffer | null;
|
|
182
|
+
/**
|
|
183
|
+
* Promotional and win-back offers on this subscription. Empty when there
|
|
184
|
+
* are none. Same eligibility caveat as {@link Product.introductoryOffer}.
|
|
185
|
+
*
|
|
186
|
+
* **On Android, do not render these as the offer's own terms.** The
|
|
187
|
+
* native plugin builds each promotional entry from the base plan's
|
|
188
|
+
* *standard* recurring pricing phase, not from the promotional phase —
|
|
189
|
+
* so a free-trial promo arrives as `payment: 'pay-as-you-go'` at the
|
|
190
|
+
* full price. Use them to know an offer exists and to identify it; take
|
|
191
|
+
* the terms from your backend or from Play Console. iOS reports the
|
|
192
|
+
* offer's real terms, and {@link Product.introductoryOffer} is
|
|
193
|
+
* unaffected on both platforms.
|
|
194
|
+
*/
|
|
195
|
+
promotionalOffers?: SubscriptionOffer[];
|
|
196
|
+
/**
|
|
197
|
+
* Android: Play's token for the specific offer this entry describes, and
|
|
198
|
+
* the offer's id (`null` for a base plan with no offer).
|
|
199
|
+
*
|
|
200
|
+
* Exposed for identification and server-side reasoning only. A purchase
|
|
201
|
+
* **cannot** select an offer by token: the native plugin's purchase call
|
|
202
|
+
* takes a base plan id, and picks the offer itself. See
|
|
203
|
+
* `docs/guide/native-layer.md`.
|
|
204
|
+
*/
|
|
205
|
+
androidOfferToken?: string | null;
|
|
206
|
+
androidOfferId?: string | null;
|
|
207
|
+
/**
|
|
208
|
+
* Android: the base plan this entry describes, as configured in Play
|
|
209
|
+
* Console. `null` on iOS, which has no base-plan concept — there a
|
|
210
|
+
* product id identifies the subscription directly.
|
|
211
|
+
*
|
|
212
|
+
* `id` is always the product id you configured, on both platforms, so it
|
|
213
|
+
* round-trips to `purchase({ productId })`. This is the extra level Play
|
|
214
|
+
* adds underneath it.
|
|
215
|
+
*/
|
|
216
|
+
androidBasePlanId?: string | null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Platform a transaction originated from. */
|
|
220
|
+
type Platform = 'apple' | 'google';
|
|
221
|
+
/**
|
|
222
|
+
* Where a subscription stands with the store, as the store reports it.
|
|
223
|
+
*
|
|
224
|
+
* iap passes these through; it does not interpret them. Deciding what counts
|
|
225
|
+
* as "entitled" — whether a grace period still grants access, say — is the
|
|
226
|
+
* consumer's business rule, and the authoritative answer comes from the
|
|
227
|
+
* backend (PLAN.md §14).
|
|
228
|
+
*/
|
|
229
|
+
type SubscriptionState = 'subscribed' | 'expired' | 'revoked' | 'in-grace-period' | 'in-billing-retry' | 'unknown';
|
|
230
|
+
/**
|
|
231
|
+
* Renewal detail for an auto-renewing subscription, normalized from the
|
|
232
|
+
* store's own fields.
|
|
233
|
+
*
|
|
234
|
+
* **iOS only, in practice.** Every field is `null` on Android: the native
|
|
235
|
+
* plugin exposes neither `isAutoRenewing` nor a renewal date, and reports
|
|
236
|
+
* cancellation as always-null. On Android this information lives server-side
|
|
237
|
+
* (Google Play Developer API), so take it from your backend.
|
|
238
|
+
*/
|
|
239
|
+
interface SubscriptionRenewal {
|
|
240
|
+
state: SubscriptionState | null;
|
|
241
|
+
/**
|
|
242
|
+
* Whether the subscription renews at the end of the current period.
|
|
243
|
+
* `false` means the user cancelled but still has access until it expires.
|
|
244
|
+
*/
|
|
245
|
+
autoRenewing: boolean | null;
|
|
246
|
+
/** When the next period begins, ISO 8601. */
|
|
247
|
+
renewsAt: string | null;
|
|
248
|
+
/**
|
|
249
|
+
* The product the subscription renews into — different from the current
|
|
250
|
+
* one after a plan change takes effect at the period boundary.
|
|
251
|
+
*/
|
|
252
|
+
renewalProductId: string | null;
|
|
253
|
+
/** When the current period ends, ISO 8601. */
|
|
254
|
+
expiresAt: string | null;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Normalized native transaction handed to core flows.
|
|
258
|
+
* Plugin-version differences are translated by the adapter so this shape
|
|
259
|
+
* stays stable across Capacitor majors.
|
|
260
|
+
*/
|
|
261
|
+
interface NativeTransaction {
|
|
262
|
+
platform: Platform;
|
|
263
|
+
productId: string;
|
|
264
|
+
/**
|
|
265
|
+
* Apple: the StoreKit `transactionId` (numeric string) — **changes with
|
|
266
|
+
* every renewal**. Google: the `purchaseToken` from Play Billing.
|
|
267
|
+
*
|
|
268
|
+
* This is the handle for acknowledging *this* transaction. To store a
|
|
269
|
+
* reference to the purchase itself, use `purchaseReference` — which is
|
|
270
|
+
* what a consumer sees, as `StorePurchase.purchaseReference`.
|
|
271
|
+
*/
|
|
272
|
+
token: string;
|
|
273
|
+
/**
|
|
274
|
+
* The stable reference to the purchase, which survives renewals — store
|
|
275
|
+
* this when you need to identify the subscription later.
|
|
276
|
+
*
|
|
277
|
+
* Apple: `originalTransactionId`, read from the signed `jwsRepresentation`
|
|
278
|
+
* payload (the plugin does not expose it as a field). Falls back to
|
|
279
|
+
* `token` when the JWS is absent or unreadable, so it is always set.
|
|
280
|
+
* Google: the `purchaseToken`, identical to `token`.
|
|
281
|
+
*/
|
|
282
|
+
purchaseReference: string;
|
|
283
|
+
/** Google only — required by Attesto's `/v1/google/verify`. */
|
|
284
|
+
packageName?: string;
|
|
285
|
+
/** Product type the transaction was originally purchased as. */
|
|
286
|
+
productType: ProductType;
|
|
287
|
+
/**
|
|
288
|
+
* The binding this purchase was made with, read back off the transaction:
|
|
289
|
+
* `appAccountToken` on iOS, `obfuscatedAccountId` on Android. `null` when
|
|
290
|
+
* the purchase carried none.
|
|
291
|
+
*
|
|
292
|
+
* Use it to check what a purchase is bound to. iap never generates one —
|
|
293
|
+
* it is whatever was passed as `iap.purchase({ appUserId })`.
|
|
294
|
+
*/
|
|
295
|
+
appUserId?: string | null;
|
|
296
|
+
/**
|
|
297
|
+
* Renewal detail for a subscription, or `null` for a non-subscription and
|
|
298
|
+
* on platforms that do not report it. See {@link SubscriptionRenewal}.
|
|
299
|
+
*/
|
|
300
|
+
subscription?: SubscriptionRenewal | null;
|
|
301
|
+
/** Optional pass-through of plugin-native fields for backend hints. */
|
|
302
|
+
raw?: unknown;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Verified transaction returned by the consumer backend after Attesto check.
|
|
306
|
+
* Generic over the backend-defined transaction shape.
|
|
307
|
+
*/
|
|
308
|
+
interface VerifiedTransaction<TExtra = Record<string, unknown>> {
|
|
309
|
+
id: string;
|
|
310
|
+
productId: string;
|
|
311
|
+
expiresAt: string | null;
|
|
312
|
+
verifiedAt: string;
|
|
313
|
+
raw?: TExtra;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* How strongly a purchase was checked. Exposed rather than hidden, because
|
|
318
|
+
* the two platforms genuinely differ and a consumer deciding what to unlock
|
|
319
|
+
* offline deserves to know which one it got.
|
|
320
|
+
*
|
|
321
|
+
* - `store-signed` — iOS. StoreKit 2 verified Apple's signature over the
|
|
322
|
+
* transaction in native code before it ever reached the WebView; the
|
|
323
|
+
* native layer resolves a purchase only for a `VerificationResult.verified`
|
|
324
|
+
* and rejects anything else. So this is a cryptographic guarantee, made by
|
|
325
|
+
* the platform rather than by iap.
|
|
326
|
+
* - `store-reported` — Android. The native layer exposes neither Play's
|
|
327
|
+
* purchase signature nor the original JSON, so **no cryptographic check is
|
|
328
|
+
* possible on the device**. This means "Play told this device it owns
|
|
329
|
+
* this", which is a real answer and the one Play's own client APIs give —
|
|
330
|
+
* but it is the store's say-so, not proof.
|
|
331
|
+
*
|
|
332
|
+
* Neither is a substitute for server-side validation against the store's
|
|
333
|
+
* API. A rooted device can lie to the app about both.
|
|
334
|
+
*/
|
|
335
|
+
type PurchaseAssurance = 'store-signed' | 'store-reported';
|
|
336
|
+
/** Why a local check refused a purchase. Stable strings, safe to switch on. */
|
|
337
|
+
type LocalVerificationError = 'PRODUCT_NOT_CONFIGURED' | 'BINDING_MISMATCH'
|
|
338
|
+
/**
|
|
339
|
+
* The app binds purchases to an account, and there is no account to bind
|
|
340
|
+
* to right now — a signed-out session, or a `verification.appUserId`
|
|
341
|
+
* getter that threw. Raised by `LocalVerificationAdapter`, never by
|
|
342
|
+
* {@link verifyPurchaseOnDevice}, which is told the binding rather than
|
|
343
|
+
* resolving it.
|
|
344
|
+
*/
|
|
345
|
+
| 'BINDING_UNAVAILABLE' | 'NOT_PURCHASED' | 'REVOKED' | 'SUBSCRIPTION_EXPIRED';
|
|
346
|
+
/** A purchase that passed the on-device checks. */
|
|
347
|
+
interface LocallyVerifiedPurchase {
|
|
348
|
+
productId: string;
|
|
349
|
+
platform: Platform;
|
|
350
|
+
/**
|
|
351
|
+
* What the purchase was bought as.
|
|
352
|
+
*
|
|
353
|
+
* Exposed because it changes what an entitlement *means*, and a mapper
|
|
354
|
+
* needs to know. In particular iOS's owned set retains **finished
|
|
355
|
+
* consumables**, so a mapper that grants on every verified purchase
|
|
356
|
+
* re-grants a consumable on every refresh, forever. A consumable's effect
|
|
357
|
+
* belongs in your own records, not in a standing entitlement.
|
|
358
|
+
*/
|
|
359
|
+
productType: ProductType;
|
|
360
|
+
/** Stable across renewals. See `NativeTransaction.purchaseReference`. */
|
|
361
|
+
purchaseReference: string;
|
|
362
|
+
/** The binding the purchase carried, or `null`. */
|
|
363
|
+
appUserId: string | null;
|
|
364
|
+
/** ISO 8601 for a subscription; `null` for a non-expiring purchase. */
|
|
365
|
+
expiresAt: string | null;
|
|
366
|
+
subscription: SubscriptionRenewal | null;
|
|
367
|
+
/** How strongly this was checked. See {@link PurchaseAssurance}. */
|
|
368
|
+
assurance: PurchaseAssurance;
|
|
369
|
+
}
|
|
370
|
+
type LocalVerification = {
|
|
371
|
+
valid: true;
|
|
372
|
+
purchase: LocallyVerifiedPurchase;
|
|
373
|
+
} | {
|
|
374
|
+
valid: false;
|
|
375
|
+
error: LocalVerificationError;
|
|
376
|
+
message: string;
|
|
377
|
+
};
|
|
378
|
+
interface LocalVerificationOptions {
|
|
379
|
+
/**
|
|
380
|
+
* Product ids the app sells. A transaction for anything else is refused —
|
|
381
|
+
* the store can report purchases from a previous version of the app, or
|
|
382
|
+
* from a product that has since been retired.
|
|
383
|
+
*/
|
|
384
|
+
productIds: readonly string[];
|
|
385
|
+
/**
|
|
386
|
+
* When set, the purchase must carry this binding. Compared
|
|
387
|
+
* case-insensitively, because StoreKit returns an uppercase UUID string
|
|
388
|
+
* while callers deal in lowercase.
|
|
389
|
+
*
|
|
390
|
+
* `null` or omitted skips the check: a purchase made before the app
|
|
391
|
+
* started binding, or a flow with no user identity, carries none.
|
|
392
|
+
*/
|
|
393
|
+
appUserId?: string | null;
|
|
394
|
+
/** Extra time past `expiresAt` during which a subscription still counts. */
|
|
395
|
+
graceMs?: number;
|
|
396
|
+
/** Injectable clock. Defaults to `Date.now`. */
|
|
397
|
+
now?: () => number;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Whether an ISO 8601 expiry has passed, by the **device** clock.
|
|
401
|
+
*
|
|
402
|
+
* The device clock is the only clock available with no backend, and it can
|
|
403
|
+
* be wrong — a user can set it back and extend a subscription. That is the
|
|
404
|
+
* accepted cost of working offline, and the reason this is not a substitute
|
|
405
|
+
* for server-side validation.
|
|
406
|
+
*
|
|
407
|
+
* An unparseable or absent expiry is treated as "does not expire" rather
|
|
408
|
+
* than "expired": refusing a purchase because its expiry string was
|
|
409
|
+
* malformed would revoke access over a formatting problem.
|
|
410
|
+
*
|
|
411
|
+
* `expiresAt` must be ISO 8601. `Date.parse` is lenient and
|
|
412
|
+
* implementation-defined for anything else — `'10/26/2026'` parses as
|
|
413
|
+
* *local* time in V8 — so a non-ISO string may compare against the wrong
|
|
414
|
+
* instant. Everything iap produces is ISO 8601; this matters only for a
|
|
415
|
+
* value you supply. An exact boundary (`now === expiry`) reads as active.
|
|
416
|
+
*/
|
|
417
|
+
declare function isExpired(expiresAt: string | null | undefined, now: number, graceMs?: number): boolean;
|
|
418
|
+
/**
|
|
419
|
+
* Check a purchase on the device, with no backend.
|
|
420
|
+
*
|
|
421
|
+
* This is the whole of what can be checked without a server, and it is
|
|
422
|
+
* deliberately a short list:
|
|
423
|
+
*
|
|
424
|
+
* 1. The product is one the app actually sells.
|
|
425
|
+
* 2. The binding matches, when one is expected.
|
|
426
|
+
* 3. On Android, the store calls the purchase purchased rather than pending.
|
|
427
|
+
* 4. The purchase has not been revoked or refunded, where the platform
|
|
428
|
+
* says so locally (iOS does; Android does not).
|
|
429
|
+
* 5. A subscription has not expired by the device clock.
|
|
430
|
+
*
|
|
431
|
+
* What it does **not** do is verify a signature. On iOS it does not need to —
|
|
432
|
+
* the platform already did, natively, and {@link PurchaseAssurance}
|
|
433
|
+
* records that. On Android it cannot: the native layer exposes neither
|
|
434
|
+
* Play's purchase signature nor the original JSON it is computed over.
|
|
435
|
+
*
|
|
436
|
+
* So a passing result means "this device's store says it owns this, and the
|
|
437
|
+
* claim is internally consistent" — strong on iOS, the store's word on
|
|
438
|
+
* Android. For anything where being wrong is expensive, verify server-side.
|
|
439
|
+
*/
|
|
440
|
+
declare function verifyPurchaseOnDevice(transaction: NativeTransaction, options: LocalVerificationOptions): LocalVerification;
|
|
441
|
+
|
|
442
|
+
declare const entitlementBaseSchema: z.ZodObject<{
|
|
443
|
+
key: z.ZodString;
|
|
444
|
+
productId: z.ZodString;
|
|
445
|
+
expiresAt: z.ZodNullable<z.ZodString>;
|
|
446
|
+
}, "strip", z.ZodTypeAny, {
|
|
447
|
+
key: string;
|
|
448
|
+
productId: string;
|
|
449
|
+
expiresAt: string | null;
|
|
450
|
+
}, {
|
|
451
|
+
key: string;
|
|
452
|
+
productId: string;
|
|
453
|
+
expiresAt: string | null;
|
|
454
|
+
}>;
|
|
455
|
+
/**
|
|
456
|
+
* Minimum shape every entitlement returned by the backend must satisfy.
|
|
457
|
+
* Consumer-defined fields pass through unvalidated unless the consumer
|
|
458
|
+
* supplies their own schema via `config.backend.entitlementSchema`.
|
|
459
|
+
*/
|
|
460
|
+
type EntitlementBase = z.infer<typeof entitlementBaseSchema>;
|
|
461
|
+
/** Default shape used when no `TEntitlement` type parameter is given. */
|
|
462
|
+
type DefaultEntitlement = EntitlementBase;
|
|
463
|
+
|
|
67
464
|
declare const backendConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
68
465
|
/**
|
|
69
466
|
* Custom backend transport. If provided, all HTTP-specific fields below
|
|
@@ -200,22 +597,87 @@ declare const backendConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
200
597
|
timeoutMs?: number | undefined;
|
|
201
598
|
retries?: number | undefined;
|
|
202
599
|
}>;
|
|
600
|
+
/**
|
|
601
|
+
* Verify purchases on the device instead of through a backend.
|
|
602
|
+
*
|
|
603
|
+
* For an app with no server at all. Every safety guarantee still applies —
|
|
604
|
+
* the purchase flow, the acknowledge-after-verify ordering and the
|
|
605
|
+
* unfinished-transaction recovery are all above the adapter boundary and do
|
|
606
|
+
* not know which mode they are in. What changes is who answers "is this
|
|
607
|
+
* purchase real", and how strong that answer is.
|
|
608
|
+
*
|
|
609
|
+
* Read `docs/guide/offline-and-no-backend.md` before choosing this: on
|
|
610
|
+
* Android no cryptographic check is possible on the device, so a passing
|
|
611
|
+
* verification there means "Play told this device it owns this".
|
|
612
|
+
*/
|
|
613
|
+
declare const verificationConfigSchema: z.ZodObject<{
|
|
614
|
+
mode: z.ZodLiteral<"local">;
|
|
615
|
+
/**
|
|
616
|
+
* Maps a verified purchase to the entitlements it grants. Required: iap
|
|
617
|
+
* does not know what a product entitles anyone to, and in local mode
|
|
618
|
+
* there is no backend to ask. Type is checked at runtime via a shape
|
|
619
|
+
* guard, not zod — see {@link VerificationConfig}.
|
|
620
|
+
*/
|
|
621
|
+
entitlements: z.ZodUnknown;
|
|
622
|
+
/**
|
|
623
|
+
* The binding every purchase must carry, as passed to
|
|
624
|
+
* `purchase({ appUserId })`. A purchase bound to something else is
|
|
625
|
+
* refused. Omit it if the app does not bind purchases.
|
|
626
|
+
*
|
|
627
|
+
* Either the id itself, or a function returning it — in which case it is
|
|
628
|
+
* read **at the time of each check**, so the binding follows the session.
|
|
629
|
+
* A fixed string is captured at `createIAP()` time and cannot track a
|
|
630
|
+
* login change, which in a multi-user app means every purchase after a
|
|
631
|
+
* switch is refused as the previous user's.
|
|
632
|
+
*
|
|
633
|
+
* The function may return `null` for "nobody is signed in right now".
|
|
634
|
+
* That is **not** the same as using no binding: iap refuses the purchase
|
|
635
|
+
* rather than dropping the check. See `LocalVerificationAdapter`.
|
|
636
|
+
*
|
|
637
|
+
* Typed via the overlay on {@link VerificationConfig}; validated in the
|
|
638
|
+
* schema's `superRefine`, the same way the backend's functions are.
|
|
639
|
+
*/
|
|
640
|
+
appUserId: z.ZodOptional<z.ZodUnknown>;
|
|
641
|
+
}, "strip", z.ZodTypeAny, {
|
|
642
|
+
mode: "local";
|
|
643
|
+
entitlements?: unknown;
|
|
644
|
+
appUserId?: unknown;
|
|
645
|
+
}, {
|
|
646
|
+
mode: "local";
|
|
647
|
+
entitlements?: unknown;
|
|
648
|
+
appUserId?: unknown;
|
|
649
|
+
}>;
|
|
203
650
|
declare const storageConfigSchema: z.ZodObject<{
|
|
204
651
|
type: z.ZodDefault<z.ZodEnum<["preferences", "memory", "custom"]>>;
|
|
205
652
|
namespace: z.ZodDefault<z.ZodString>;
|
|
206
653
|
adapter: z.ZodOptional<z.ZodUnknown>;
|
|
207
654
|
}, "strip", z.ZodTypeAny, {
|
|
208
|
-
type: "
|
|
655
|
+
type: "custom" | "preferences" | "memory";
|
|
209
656
|
namespace: string;
|
|
210
657
|
adapter?: unknown;
|
|
211
658
|
}, {
|
|
212
|
-
type?: "
|
|
659
|
+
type?: "custom" | "preferences" | "memory" | undefined;
|
|
213
660
|
adapter?: unknown;
|
|
214
661
|
namespace?: string | undefined;
|
|
215
662
|
}>;
|
|
216
663
|
declare const optionsConfigSchema: z.ZodObject<{
|
|
217
664
|
refreshOnResume: z.ZodDefault<z.ZodBoolean>;
|
|
218
665
|
entitlementCacheTtlMs: z.ZodDefault<z.ZodNumber>;
|
|
666
|
+
/**
|
|
667
|
+
* Extra time past an entitlement's `expiresAt` during which it still
|
|
668
|
+
* counts as active, by the **device** clock.
|
|
669
|
+
*
|
|
670
|
+
* Exists because a renewal is not instantaneous: a subscription that
|
|
671
|
+
* renews at midnight may not have a fresh receipt on the device for
|
|
672
|
+
* minutes or hours, and locking the user out in that window is a worse
|
|
673
|
+
* error than letting a lapsed subscription run slightly long. Store
|
|
674
|
+
* grace periods and billing retry can stretch much further — on Android
|
|
675
|
+
* those are server-side only, so a backendless app cannot see them at all
|
|
676
|
+
* and this window is the only cushion it has.
|
|
677
|
+
*
|
|
678
|
+
* Defaults to 0: no grace, which is the strict reading of `expiresAt`.
|
|
679
|
+
*/
|
|
680
|
+
entitlementGraceMs: z.ZodDefault<z.ZodNumber>;
|
|
219
681
|
recoverUnfinishedTransactions: z.ZodDefault<z.ZodBoolean>;
|
|
220
682
|
/**
|
|
221
683
|
* Cap on how many unfinished transactions recovery inspects per launch.
|
|
@@ -243,20 +705,22 @@ declare const optionsConfigSchema: z.ZodObject<{
|
|
|
243
705
|
}, "strip", z.ZodTypeAny, {
|
|
244
706
|
refreshOnResume: boolean;
|
|
245
707
|
entitlementCacheTtlMs: number;
|
|
708
|
+
entitlementGraceMs: number;
|
|
246
709
|
recoverUnfinishedTransactions: boolean;
|
|
247
710
|
recoveryMaxBatch: number;
|
|
248
711
|
productPriceCacheTtlMs: number;
|
|
249
|
-
logLevel: "
|
|
712
|
+
logLevel: "error" | "silent" | "warn" | "info" | "debug";
|
|
250
713
|
permanentErrorCodes?: string[] | undefined;
|
|
251
714
|
logger?: unknown;
|
|
252
715
|
}, {
|
|
253
716
|
refreshOnResume?: boolean | undefined;
|
|
254
717
|
entitlementCacheTtlMs?: number | undefined;
|
|
718
|
+
entitlementGraceMs?: number | undefined;
|
|
255
719
|
recoverUnfinishedTransactions?: boolean | undefined;
|
|
256
720
|
recoveryMaxBatch?: number | undefined;
|
|
257
721
|
permanentErrorCodes?: string[] | undefined;
|
|
258
722
|
productPriceCacheTtlMs?: number | undefined;
|
|
259
|
-
logLevel?: "
|
|
723
|
+
logLevel?: "error" | "silent" | "warn" | "info" | "debug" | undefined;
|
|
260
724
|
logger?: unknown;
|
|
261
725
|
}>;
|
|
262
726
|
declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
@@ -281,15 +745,20 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
281
745
|
*/
|
|
282
746
|
androidPlanId: z.ZodOptional<z.ZodString>;
|
|
283
747
|
}, "strip", z.ZodTypeAny, {
|
|
284
|
-
id: string;
|
|
285
748
|
type: "subscription" | "product" | "consumable";
|
|
749
|
+
id: string;
|
|
286
750
|
androidPlanId?: string | undefined;
|
|
287
751
|
}, {
|
|
288
|
-
id: string;
|
|
289
752
|
type: "subscription" | "product" | "consumable";
|
|
753
|
+
id: string;
|
|
290
754
|
androidPlanId?: string | undefined;
|
|
291
755
|
}>, "many">>;
|
|
292
|
-
|
|
756
|
+
/**
|
|
757
|
+
* The consumer's backend, which calls Attesto. Optional **only** when
|
|
758
|
+
* `verification` is set instead — one of the two has to answer
|
|
759
|
+
* whether a purchase is real.
|
|
760
|
+
*/
|
|
761
|
+
backend: z.ZodOptional<z.ZodEffects<z.ZodObject<{
|
|
293
762
|
/**
|
|
294
763
|
* Custom backend transport. If provided, all HTTP-specific fields below
|
|
295
764
|
* are ignored and the library uses this object directly for backend
|
|
@@ -424,23 +893,84 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
424
893
|
entitlementSchema?: unknown;
|
|
425
894
|
timeoutMs?: number | undefined;
|
|
426
895
|
retries?: number | undefined;
|
|
427
|
-
}
|
|
896
|
+
}>>;
|
|
897
|
+
/** On-device verification, for an app with no backend. */
|
|
898
|
+
verification: z.ZodOptional<z.ZodObject<{
|
|
899
|
+
mode: z.ZodLiteral<"local">;
|
|
900
|
+
/**
|
|
901
|
+
* Maps a verified purchase to the entitlements it grants. Required: iap
|
|
902
|
+
* does not know what a product entitles anyone to, and in local mode
|
|
903
|
+
* there is no backend to ask. Type is checked at runtime via a shape
|
|
904
|
+
* guard, not zod — see {@link VerificationConfig}.
|
|
905
|
+
*/
|
|
906
|
+
entitlements: z.ZodUnknown;
|
|
907
|
+
/**
|
|
908
|
+
* The binding every purchase must carry, as passed to
|
|
909
|
+
* `purchase({ appUserId })`. A purchase bound to something else is
|
|
910
|
+
* refused. Omit it if the app does not bind purchases.
|
|
911
|
+
*
|
|
912
|
+
* Either the id itself, or a function returning it — in which case it is
|
|
913
|
+
* read **at the time of each check**, so the binding follows the session.
|
|
914
|
+
* A fixed string is captured at `createIAP()` time and cannot track a
|
|
915
|
+
* login change, which in a multi-user app means every purchase after a
|
|
916
|
+
* switch is refused as the previous user's.
|
|
917
|
+
*
|
|
918
|
+
* The function may return `null` for "nobody is signed in right now".
|
|
919
|
+
* That is **not** the same as using no binding: iap refuses the purchase
|
|
920
|
+
* rather than dropping the check. See `LocalVerificationAdapter`.
|
|
921
|
+
*
|
|
922
|
+
* Typed via the overlay on {@link VerificationConfig}; validated in the
|
|
923
|
+
* schema's `superRefine`, the same way the backend's functions are.
|
|
924
|
+
*/
|
|
925
|
+
appUserId: z.ZodOptional<z.ZodUnknown>;
|
|
926
|
+
}, "strip", z.ZodTypeAny, {
|
|
927
|
+
mode: "local";
|
|
928
|
+
entitlements?: unknown;
|
|
929
|
+
appUserId?: unknown;
|
|
930
|
+
}, {
|
|
931
|
+
mode: "local";
|
|
932
|
+
entitlements?: unknown;
|
|
933
|
+
appUserId?: unknown;
|
|
934
|
+
}>>;
|
|
935
|
+
/** Android-specific settings. See {@link androidConfigSchema}. */
|
|
936
|
+
android: z.ZodDefault<z.ZodObject<{
|
|
937
|
+
packageName: z.ZodOptional<z.ZodString>;
|
|
938
|
+
}, "strip", z.ZodTypeAny, {
|
|
939
|
+
packageName?: string | undefined;
|
|
940
|
+
}, {
|
|
941
|
+
packageName?: string | undefined;
|
|
942
|
+
}>>;
|
|
428
943
|
storage: z.ZodDefault<z.ZodObject<{
|
|
429
944
|
type: z.ZodDefault<z.ZodEnum<["preferences", "memory", "custom"]>>;
|
|
430
945
|
namespace: z.ZodDefault<z.ZodString>;
|
|
431
946
|
adapter: z.ZodOptional<z.ZodUnknown>;
|
|
432
947
|
}, "strip", z.ZodTypeAny, {
|
|
433
|
-
type: "
|
|
948
|
+
type: "custom" | "preferences" | "memory";
|
|
434
949
|
namespace: string;
|
|
435
950
|
adapter?: unknown;
|
|
436
951
|
}, {
|
|
437
|
-
type?: "
|
|
952
|
+
type?: "custom" | "preferences" | "memory" | undefined;
|
|
438
953
|
adapter?: unknown;
|
|
439
954
|
namespace?: string | undefined;
|
|
440
955
|
}>>;
|
|
441
956
|
options: z.ZodDefault<z.ZodObject<{
|
|
442
957
|
refreshOnResume: z.ZodDefault<z.ZodBoolean>;
|
|
443
958
|
entitlementCacheTtlMs: z.ZodDefault<z.ZodNumber>;
|
|
959
|
+
/**
|
|
960
|
+
* Extra time past an entitlement's `expiresAt` during which it still
|
|
961
|
+
* counts as active, by the **device** clock.
|
|
962
|
+
*
|
|
963
|
+
* Exists because a renewal is not instantaneous: a subscription that
|
|
964
|
+
* renews at midnight may not have a fresh receipt on the device for
|
|
965
|
+
* minutes or hours, and locking the user out in that window is a worse
|
|
966
|
+
* error than letting a lapsed subscription run slightly long. Store
|
|
967
|
+
* grace periods and billing retry can stretch much further — on Android
|
|
968
|
+
* those are server-side only, so a backendless app cannot see them at all
|
|
969
|
+
* and this window is the only cushion it has.
|
|
970
|
+
*
|
|
971
|
+
* Defaults to 0: no grace, which is the strict reading of `expiresAt`.
|
|
972
|
+
*/
|
|
973
|
+
entitlementGraceMs: z.ZodDefault<z.ZodNumber>;
|
|
444
974
|
recoverUnfinishedTransactions: z.ZodDefault<z.ZodBoolean>;
|
|
445
975
|
/**
|
|
446
976
|
* Cap on how many unfinished transactions recovery inspects per launch.
|
|
@@ -468,62 +998,52 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
468
998
|
}, "strip", z.ZodTypeAny, {
|
|
469
999
|
refreshOnResume: boolean;
|
|
470
1000
|
entitlementCacheTtlMs: number;
|
|
1001
|
+
entitlementGraceMs: number;
|
|
471
1002
|
recoverUnfinishedTransactions: boolean;
|
|
472
1003
|
recoveryMaxBatch: number;
|
|
473
1004
|
productPriceCacheTtlMs: number;
|
|
474
|
-
logLevel: "
|
|
1005
|
+
logLevel: "error" | "silent" | "warn" | "info" | "debug";
|
|
475
1006
|
permanentErrorCodes?: string[] | undefined;
|
|
476
1007
|
logger?: unknown;
|
|
477
1008
|
}, {
|
|
478
1009
|
refreshOnResume?: boolean | undefined;
|
|
479
1010
|
entitlementCacheTtlMs?: number | undefined;
|
|
1011
|
+
entitlementGraceMs?: number | undefined;
|
|
480
1012
|
recoverUnfinishedTransactions?: boolean | undefined;
|
|
481
1013
|
recoveryMaxBatch?: number | undefined;
|
|
482
1014
|
permanentErrorCodes?: string[] | undefined;
|
|
483
1015
|
productPriceCacheTtlMs?: number | undefined;
|
|
484
|
-
logLevel?: "
|
|
1016
|
+
logLevel?: "error" | "silent" | "warn" | "info" | "debug" | undefined;
|
|
485
1017
|
logger?: unknown;
|
|
486
1018
|
}>>;
|
|
487
1019
|
}, "strip", z.ZodTypeAny, {
|
|
488
1020
|
options: {
|
|
489
1021
|
refreshOnResume: boolean;
|
|
490
1022
|
entitlementCacheTtlMs: number;
|
|
1023
|
+
entitlementGraceMs: number;
|
|
491
1024
|
recoverUnfinishedTransactions: boolean;
|
|
492
1025
|
recoveryMaxBatch: number;
|
|
493
1026
|
productPriceCacheTtlMs: number;
|
|
494
|
-
logLevel: "
|
|
1027
|
+
logLevel: "error" | "silent" | "warn" | "info" | "debug";
|
|
495
1028
|
permanentErrorCodes?: string[] | undefined;
|
|
496
1029
|
logger?: unknown;
|
|
497
1030
|
};
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
retries: number;
|
|
501
|
-
adapter?: unknown;
|
|
502
|
-
baseUrl?: string | undefined;
|
|
503
|
-
endpoints?: {
|
|
504
|
-
entitlements: string;
|
|
505
|
-
restore: string;
|
|
506
|
-
verifyApple?: string | undefined;
|
|
507
|
-
verifyGoogle?: string | undefined;
|
|
508
|
-
products?: string | undefined;
|
|
509
|
-
} | undefined;
|
|
510
|
-
getAuthHeaders?: unknown;
|
|
511
|
-
requestTransform?: unknown;
|
|
512
|
-
responseTransform?: unknown;
|
|
513
|
-
entitlementSchema?: unknown;
|
|
1031
|
+
android: {
|
|
1032
|
+
packageName?: string | undefined;
|
|
514
1033
|
};
|
|
515
1034
|
storage: {
|
|
516
|
-
type: "
|
|
1035
|
+
type: "custom" | "preferences" | "memory";
|
|
517
1036
|
namespace: string;
|
|
518
1037
|
adapter?: unknown;
|
|
519
1038
|
};
|
|
520
1039
|
products?: {
|
|
521
|
-
id: string;
|
|
522
1040
|
type: "subscription" | "product" | "consumable";
|
|
1041
|
+
id: string;
|
|
523
1042
|
androidPlanId?: string | undefined;
|
|
524
1043
|
}[] | undefined;
|
|
525
|
-
|
|
526
|
-
|
|
1044
|
+
backend?: {
|
|
1045
|
+
timeoutMs: number;
|
|
1046
|
+
retries: number;
|
|
527
1047
|
adapter?: unknown;
|
|
528
1048
|
baseUrl?: string | undefined;
|
|
529
1049
|
endpoints?: {
|
|
@@ -537,43 +1057,30 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
537
1057
|
requestTransform?: unknown;
|
|
538
1058
|
responseTransform?: unknown;
|
|
539
1059
|
entitlementSchema?: unknown;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
1060
|
+
} | undefined;
|
|
1061
|
+
verification?: {
|
|
1062
|
+
mode: "local";
|
|
1063
|
+
entitlements?: unknown;
|
|
1064
|
+
appUserId?: unknown;
|
|
1065
|
+
} | undefined;
|
|
1066
|
+
}, {
|
|
543
1067
|
options?: {
|
|
544
1068
|
refreshOnResume?: boolean | undefined;
|
|
545
1069
|
entitlementCacheTtlMs?: number | undefined;
|
|
1070
|
+
entitlementGraceMs?: number | undefined;
|
|
546
1071
|
recoverUnfinishedTransactions?: boolean | undefined;
|
|
547
1072
|
recoveryMaxBatch?: number | undefined;
|
|
548
1073
|
permanentErrorCodes?: string[] | undefined;
|
|
549
1074
|
productPriceCacheTtlMs?: number | undefined;
|
|
550
|
-
logLevel?: "
|
|
1075
|
+
logLevel?: "error" | "silent" | "warn" | "info" | "debug" | undefined;
|
|
551
1076
|
logger?: unknown;
|
|
552
1077
|
} | undefined;
|
|
553
1078
|
products?: {
|
|
554
|
-
id: string;
|
|
555
1079
|
type: "subscription" | "product" | "consumable";
|
|
1080
|
+
id: string;
|
|
556
1081
|
androidPlanId?: string | undefined;
|
|
557
1082
|
}[] | undefined;
|
|
558
|
-
|
|
559
|
-
type?: "preferences" | "memory" | "custom" | undefined;
|
|
560
|
-
adapter?: unknown;
|
|
561
|
-
namespace?: string | undefined;
|
|
562
|
-
} | undefined;
|
|
563
|
-
}>, {
|
|
564
|
-
options: {
|
|
565
|
-
refreshOnResume: boolean;
|
|
566
|
-
entitlementCacheTtlMs: number;
|
|
567
|
-
recoverUnfinishedTransactions: boolean;
|
|
568
|
-
recoveryMaxBatch: number;
|
|
569
|
-
productPriceCacheTtlMs: number;
|
|
570
|
-
logLevel: "silent" | "error" | "warn" | "info" | "debug";
|
|
571
|
-
permanentErrorCodes?: string[] | undefined;
|
|
572
|
-
logger?: unknown;
|
|
573
|
-
};
|
|
574
|
-
backend: {
|
|
575
|
-
timeoutMs: number;
|
|
576
|
-
retries: number;
|
|
1083
|
+
backend?: {
|
|
577
1084
|
adapter?: unknown;
|
|
578
1085
|
baseUrl?: string | undefined;
|
|
579
1086
|
endpoints?: {
|
|
@@ -587,19 +1094,50 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
587
1094
|
requestTransform?: unknown;
|
|
588
1095
|
responseTransform?: unknown;
|
|
589
1096
|
entitlementSchema?: unknown;
|
|
1097
|
+
timeoutMs?: number | undefined;
|
|
1098
|
+
retries?: number | undefined;
|
|
1099
|
+
} | undefined;
|
|
1100
|
+
verification?: {
|
|
1101
|
+
mode: "local";
|
|
1102
|
+
entitlements?: unknown;
|
|
1103
|
+
appUserId?: unknown;
|
|
1104
|
+
} | undefined;
|
|
1105
|
+
android?: {
|
|
1106
|
+
packageName?: string | undefined;
|
|
1107
|
+
} | undefined;
|
|
1108
|
+
storage?: {
|
|
1109
|
+
type?: "custom" | "preferences" | "memory" | undefined;
|
|
1110
|
+
adapter?: unknown;
|
|
1111
|
+
namespace?: string | undefined;
|
|
1112
|
+
} | undefined;
|
|
1113
|
+
}>, {
|
|
1114
|
+
options: {
|
|
1115
|
+
refreshOnResume: boolean;
|
|
1116
|
+
entitlementCacheTtlMs: number;
|
|
1117
|
+
entitlementGraceMs: number;
|
|
1118
|
+
recoverUnfinishedTransactions: boolean;
|
|
1119
|
+
recoveryMaxBatch: number;
|
|
1120
|
+
productPriceCacheTtlMs: number;
|
|
1121
|
+
logLevel: "error" | "silent" | "warn" | "info" | "debug";
|
|
1122
|
+
permanentErrorCodes?: string[] | undefined;
|
|
1123
|
+
logger?: unknown;
|
|
1124
|
+
};
|
|
1125
|
+
android: {
|
|
1126
|
+
packageName?: string | undefined;
|
|
590
1127
|
};
|
|
591
1128
|
storage: {
|
|
592
|
-
type: "
|
|
1129
|
+
type: "custom" | "preferences" | "memory";
|
|
593
1130
|
namespace: string;
|
|
594
1131
|
adapter?: unknown;
|
|
595
1132
|
};
|
|
596
1133
|
products?: {
|
|
597
|
-
id: string;
|
|
598
1134
|
type: "subscription" | "product" | "consumable";
|
|
1135
|
+
id: string;
|
|
599
1136
|
androidPlanId?: string | undefined;
|
|
600
1137
|
}[] | undefined;
|
|
601
|
-
|
|
602
|
-
|
|
1138
|
+
backend?: {
|
|
1139
|
+
timeoutMs: number;
|
|
1140
|
+
retries: number;
|
|
603
1141
|
adapter?: unknown;
|
|
604
1142
|
baseUrl?: string | undefined;
|
|
605
1143
|
endpoints?: {
|
|
@@ -613,26 +1151,56 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
|
613
1151
|
requestTransform?: unknown;
|
|
614
1152
|
responseTransform?: unknown;
|
|
615
1153
|
entitlementSchema?: unknown;
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
1154
|
+
} | undefined;
|
|
1155
|
+
verification?: {
|
|
1156
|
+
mode: "local";
|
|
1157
|
+
entitlements?: unknown;
|
|
1158
|
+
appUserId?: unknown;
|
|
1159
|
+
} | undefined;
|
|
1160
|
+
}, {
|
|
619
1161
|
options?: {
|
|
620
1162
|
refreshOnResume?: boolean | undefined;
|
|
621
1163
|
entitlementCacheTtlMs?: number | undefined;
|
|
1164
|
+
entitlementGraceMs?: number | undefined;
|
|
622
1165
|
recoverUnfinishedTransactions?: boolean | undefined;
|
|
623
1166
|
recoveryMaxBatch?: number | undefined;
|
|
624
1167
|
permanentErrorCodes?: string[] | undefined;
|
|
625
1168
|
productPriceCacheTtlMs?: number | undefined;
|
|
626
|
-
logLevel?: "
|
|
1169
|
+
logLevel?: "error" | "silent" | "warn" | "info" | "debug" | undefined;
|
|
627
1170
|
logger?: unknown;
|
|
628
1171
|
} | undefined;
|
|
629
1172
|
products?: {
|
|
630
|
-
id: string;
|
|
631
1173
|
type: "subscription" | "product" | "consumable";
|
|
1174
|
+
id: string;
|
|
632
1175
|
androidPlanId?: string | undefined;
|
|
633
1176
|
}[] | undefined;
|
|
1177
|
+
backend?: {
|
|
1178
|
+
adapter?: unknown;
|
|
1179
|
+
baseUrl?: string | undefined;
|
|
1180
|
+
endpoints?: {
|
|
1181
|
+
entitlements: string;
|
|
1182
|
+
restore: string;
|
|
1183
|
+
verifyApple?: string | undefined;
|
|
1184
|
+
verifyGoogle?: string | undefined;
|
|
1185
|
+
products?: string | undefined;
|
|
1186
|
+
} | undefined;
|
|
1187
|
+
getAuthHeaders?: unknown;
|
|
1188
|
+
requestTransform?: unknown;
|
|
1189
|
+
responseTransform?: unknown;
|
|
1190
|
+
entitlementSchema?: unknown;
|
|
1191
|
+
timeoutMs?: number | undefined;
|
|
1192
|
+
retries?: number | undefined;
|
|
1193
|
+
} | undefined;
|
|
1194
|
+
verification?: {
|
|
1195
|
+
mode: "local";
|
|
1196
|
+
entitlements?: unknown;
|
|
1197
|
+
appUserId?: unknown;
|
|
1198
|
+
} | undefined;
|
|
1199
|
+
android?: {
|
|
1200
|
+
packageName?: string | undefined;
|
|
1201
|
+
} | undefined;
|
|
634
1202
|
storage?: {
|
|
635
|
-
type?: "
|
|
1203
|
+
type?: "custom" | "preferences" | "memory" | undefined;
|
|
636
1204
|
adapter?: unknown;
|
|
637
1205
|
namespace?: string | undefined;
|
|
638
1206
|
} | undefined;
|
|
@@ -659,37 +1227,34 @@ type BackendConfig = Omit<RawBackendConfig, 'getAuthHeaders' | 'requestTransform
|
|
|
659
1227
|
type BackendConfigInput = Omit<RawBackendConfigInput, 'getAuthHeaders' | 'requestTransform' | 'responseTransform'> & FunctionTypedBackendOverlay;
|
|
660
1228
|
type RawIAPConfig = z.infer<typeof iapConfigSchema>;
|
|
661
1229
|
type RawIAPConfigInput = z.input<typeof iapConfigSchema>;
|
|
662
|
-
|
|
663
|
-
|
|
1230
|
+
/**
|
|
1231
|
+
* Replace the schema's `unknown` typing for the entitlement mapper with its
|
|
1232
|
+
* real contract, the same way {@link BackendConfig} does for its functions.
|
|
1233
|
+
*
|
|
1234
|
+
* Generic over the consumer's entitlement shape. `LocallyVerifiedPurchase`
|
|
1235
|
+
* carries what a mapper needs to decide: the product, the expiry, the
|
|
1236
|
+
* binding, and how strongly the purchase was checked.
|
|
1237
|
+
*/
|
|
1238
|
+
type VerificationConfig<TEntitlement extends EntitlementBase = EntitlementBase> = Omit<z.infer<typeof verificationConfigSchema>, 'entitlements' | 'appUserId'> & {
|
|
1239
|
+
entitlements: (purchase: LocallyVerifiedPurchase) => TEntitlement[];
|
|
1240
|
+
/**
|
|
1241
|
+
* The id, or a getter so the binding can follow the session. A getter
|
|
1242
|
+
* returning `null` means "nobody signed in", and refuses rather than
|
|
1243
|
+
* skipping the check.
|
|
1244
|
+
*/
|
|
1245
|
+
appUserId?: string | (() => string | null);
|
|
664
1246
|
};
|
|
665
|
-
type
|
|
666
|
-
backend
|
|
1247
|
+
type IAPConfig<TEntitlement extends EntitlementBase = EntitlementBase> = Omit<RawIAPConfig, 'backend' | 'verification'> & {
|
|
1248
|
+
backend?: BackendConfig;
|
|
1249
|
+
verification?: VerificationConfig<TEntitlement>;
|
|
1250
|
+
};
|
|
1251
|
+
type IAPConfigInput<TEntitlement extends EntitlementBase = EntitlementBase> = Omit<RawIAPConfigInput, 'backend' | 'verification'> & {
|
|
1252
|
+
backend?: BackendConfigInput;
|
|
1253
|
+
verification?: VerificationConfig<TEntitlement>;
|
|
667
1254
|
};
|
|
668
1255
|
type StorageConfig = z.infer<typeof storageConfigSchema>;
|
|
669
1256
|
type OptionsConfig = z.infer<typeof optionsConfigSchema>;
|
|
670
1257
|
|
|
671
|
-
declare const entitlementBaseSchema: z.ZodObject<{
|
|
672
|
-
key: z.ZodString;
|
|
673
|
-
productId: z.ZodString;
|
|
674
|
-
expiresAt: z.ZodNullable<z.ZodString>;
|
|
675
|
-
}, "strip", z.ZodTypeAny, {
|
|
676
|
-
key: string;
|
|
677
|
-
productId: string;
|
|
678
|
-
expiresAt: string | null;
|
|
679
|
-
}, {
|
|
680
|
-
key: string;
|
|
681
|
-
productId: string;
|
|
682
|
-
expiresAt: string | null;
|
|
683
|
-
}>;
|
|
684
|
-
/**
|
|
685
|
-
* Minimum shape every entitlement returned by the backend must satisfy.
|
|
686
|
-
* Consumer-defined fields pass through unvalidated unless the consumer
|
|
687
|
-
* supplies their own schema via `config.backend.entitlementSchema`.
|
|
688
|
-
*/
|
|
689
|
-
type EntitlementBase = z.infer<typeof entitlementBaseSchema>;
|
|
690
|
-
/** Default shape used when no `TEntitlement` type parameter is given. */
|
|
691
|
-
type DefaultEntitlement = EntitlementBase;
|
|
692
|
-
|
|
693
1258
|
declare const IAPErrorCode: {
|
|
694
1259
|
readonly INVALID_CONFIG: "INVALID_CONFIG";
|
|
695
1260
|
readonly NOT_INITIALIZED: "NOT_INITIALIZED";
|
|
@@ -713,8 +1278,9 @@ declare const IAPErrorCode: {
|
|
|
713
1278
|
readonly STORAGE_ERROR: "STORAGE_ERROR";
|
|
714
1279
|
/**
|
|
715
1280
|
* The supplied `appUserId` (literal or fetcher-returned) is not a valid
|
|
716
|
-
* UUID
|
|
717
|
-
* same constraint cross-platform for a consistent contract.
|
|
1281
|
+
* RFC 9562 UUID. Apple requires a UUID for `appAccountToken`; we enforce
|
|
1282
|
+
* the same constraint cross-platform for a consistent contract. Any
|
|
1283
|
+
* version is accepted — see `isValidUuid` for what is still refused.
|
|
718
1284
|
*/
|
|
719
1285
|
readonly INVALID_APP_USER_ID: "INVALID_APP_USER_ID";
|
|
720
1286
|
/**
|
|
@@ -747,126 +1313,45 @@ declare class IAPError extends Error {
|
|
|
747
1313
|
}
|
|
748
1314
|
declare function isIAPError(error: unknown): error is IAPError;
|
|
749
1315
|
|
|
750
|
-
type ProductType = 'subscription' | 'product' | 'consumable';
|
|
751
|
-
interface ConfiguredProduct {
|
|
752
|
-
id: string;
|
|
753
|
-
type: ProductType;
|
|
754
|
-
/**
|
|
755
|
-
* Required for Android subscriptions — the base plan identifier configured in
|
|
756
|
-
* Google Play Console. Ignored on iOS and for non-subscription products.
|
|
757
|
-
*/
|
|
758
|
-
androidPlanId?: string;
|
|
759
|
-
}
|
|
760
|
-
/**
|
|
761
|
-
* Native product info merged with configured metadata.
|
|
762
|
-
* Library-public shape — kept stable across Capacitor versions.
|
|
763
|
-
*/
|
|
764
|
-
interface Product {
|
|
765
|
-
id: string;
|
|
766
|
-
type: ProductType;
|
|
767
|
-
title: string;
|
|
768
|
-
description: string;
|
|
769
|
-
/** Localized price string, e.g. "$4.99". Always render this — never hardcode. */
|
|
770
|
-
priceString: string;
|
|
771
|
-
/** BigInt as string to avoid precision loss, e.g. "4990000". */
|
|
772
|
-
priceMicros: string;
|
|
773
|
-
/** ISO 4217 code, e.g. "USD". */
|
|
774
|
-
currency: string;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
/** Platform a transaction originated from. */
|
|
778
|
-
type Platform = 'apple' | 'google';
|
|
779
1316
|
/**
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
1317
|
+
* The **store's** view of a purchase, as reported by StoreKit or Play
|
|
1318
|
+
* Billing — distinct from {@link VerifiedTransaction}, which is your
|
|
1319
|
+
* backend's view after Attesto validated the receipt.
|
|
1320
|
+
*
|
|
1321
|
+
* Both appear on a successful purchase because they answer different
|
|
1322
|
+
* questions. The backend's `transaction.id` identifies the receipt *it*
|
|
1323
|
+
* recorded; `purchaseReference` identifies the purchase as the store sees
|
|
1324
|
+
* it, which is what survives renewals and reinstalls.
|
|
783
1325
|
*/
|
|
784
|
-
interface
|
|
1326
|
+
interface StorePurchase {
|
|
785
1327
|
platform: Platform;
|
|
786
1328
|
productId: string;
|
|
787
1329
|
/**
|
|
788
|
-
*
|
|
789
|
-
* Google
|
|
1330
|
+
* The store's stable reference to this purchase: Apple's
|
|
1331
|
+
* `originalTransactionId`, Google's `purchaseToken`. Unchanged across
|
|
1332
|
+
* renewals, so this is what to persist if you need to recognise the
|
|
1333
|
+
* subscription later — re-deriving a server-side token from it months
|
|
1334
|
+
* after the purchase, for instance.
|
|
1335
|
+
*
|
|
1336
|
+
* Recoverable after a reinstall via `restorePurchases()`, which is the
|
|
1337
|
+
* only route once the install that made the purchase is gone.
|
|
1338
|
+
*
|
|
1339
|
+
* Treat it as a receipt: useful for correlation, not something to leak.
|
|
790
1340
|
*/
|
|
791
|
-
|
|
792
|
-
/** Google only — required by Attesto's `/v1/google/verify`. */
|
|
793
|
-
packageName?: string;
|
|
794
|
-
/** Product type the transaction was originally purchased as. */
|
|
795
|
-
productType: ProductType;
|
|
796
|
-
/** Optional pass-through of plugin-native fields for backend hints. */
|
|
797
|
-
raw?: unknown;
|
|
798
|
-
}
|
|
799
|
-
/**
|
|
800
|
-
* Verified transaction returned by the consumer backend after Attesto check.
|
|
801
|
-
* Generic over the backend-defined transaction shape.
|
|
802
|
-
*/
|
|
803
|
-
interface VerifiedTransaction<TExtra = Record<string, unknown>> {
|
|
804
|
-
id: string;
|
|
805
|
-
productId: string;
|
|
806
|
-
expiresAt: string | null;
|
|
807
|
-
verifiedAt: string;
|
|
808
|
-
raw?: TExtra;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
/**
|
|
812
|
-
* Map of event name → payload. Used by the typed event emitter so consumer
|
|
813
|
-
* subscriptions are statically checked.
|
|
814
|
-
*/
|
|
815
|
-
interface EventMap<TEntitlement extends EntitlementBase = EntitlementBase> {
|
|
816
|
-
ready: undefined;
|
|
817
|
-
'purchase-started': {
|
|
818
|
-
productId: string;
|
|
819
|
-
};
|
|
820
|
-
'purchase-success': {
|
|
821
|
-
productId: string;
|
|
822
|
-
transaction: VerifiedTransaction;
|
|
823
|
-
};
|
|
824
|
-
'purchase-cancelled': {
|
|
825
|
-
productId: string;
|
|
826
|
-
};
|
|
827
|
-
'purchase-pending': {
|
|
828
|
-
productId: string;
|
|
829
|
-
};
|
|
830
|
-
'purchase-failed': {
|
|
831
|
-
productId: string;
|
|
832
|
-
error: IAPError;
|
|
833
|
-
};
|
|
834
|
-
'verification-failed': {
|
|
835
|
-
productId: string;
|
|
836
|
-
error: IAPError;
|
|
837
|
-
};
|
|
838
|
-
'restore-started': undefined;
|
|
839
|
-
'restore-completed': {
|
|
840
|
-
restored: number;
|
|
841
|
-
entitlements: TEntitlement[];
|
|
842
|
-
};
|
|
843
|
-
'entitlements-changed': {
|
|
844
|
-
entitlements: TEntitlement[];
|
|
845
|
-
previous: TEntitlement[];
|
|
846
|
-
};
|
|
1341
|
+
purchaseReference: string;
|
|
847
1342
|
/**
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
* logging / alerting on stuck-loop self-heal events.
|
|
852
|
-
*
|
|
853
|
-
* **Token is unmasked.** Receipt tokens (Apple `transactionId` /
|
|
854
|
-
* Google `purchaseToken`) are useful for correlation in debugging
|
|
855
|
-
* but are receipts you don't want to leak — treat as sensitive. Mask
|
|
856
|
-
* before forwarding to external analytics / logging services. iap's
|
|
857
|
-
* own internal logs use a masked form (see `lib/redact.ts`).
|
|
1343
|
+
* The binding this purchase was made with — the value passed as
|
|
1344
|
+
* `purchase({ appUserId })`, read back off the transaction. `null` when
|
|
1345
|
+
* the purchase carried none. iap never generates one.
|
|
858
1346
|
*/
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1347
|
+
appUserId: string | null;
|
|
1348
|
+
/**
|
|
1349
|
+
* Renewal detail for a subscription, or `null` when the store reported
|
|
1350
|
+
* none — which is always the case on Android. See
|
|
1351
|
+
* {@link SubscriptionRenewal}.
|
|
1352
|
+
*/
|
|
1353
|
+
subscription: SubscriptionRenewal | null;
|
|
865
1354
|
}
|
|
866
|
-
type EventName<TEntitlement extends EntitlementBase = EntitlementBase> = keyof EventMap<TEntitlement>;
|
|
867
|
-
type EventPayload<K extends EventName<TEntitlement>, TEntitlement extends EntitlementBase = EntitlementBase> = EventMap<TEntitlement>[K];
|
|
868
|
-
type Unsubscribe = () => void;
|
|
869
|
-
|
|
870
1355
|
/**
|
|
871
1356
|
* Convenience context the library passes to a function-form
|
|
872
1357
|
* {@link AppUserId} fetcher. `authHeaders` is the result of awaiting
|
|
@@ -886,7 +1371,7 @@ interface AppUserIdFetcherContext {
|
|
|
886
1371
|
authHeaders: Record<string, string>;
|
|
887
1372
|
}
|
|
888
1373
|
/**
|
|
889
|
-
* Value supplied to `iap.purchase({ appUserId })`. Either a UUID
|
|
1374
|
+
* Value supplied to `iap.purchase({ appUserId })`. Either a UUID
|
|
890
1375
|
* string the caller already has (e.g. from local cache / app state) or
|
|
891
1376
|
* an async fetcher the library invokes once per purchase to retrieve
|
|
892
1377
|
* the UUID from the caller's backend (typical pattern: backend mints +
|
|
@@ -902,10 +1387,12 @@ interface AppUserIdFetcherContext {
|
|
|
902
1387
|
* requests can be reused without redefining a helper. See
|
|
903
1388
|
* {@link AppUserIdFetcherContext}.
|
|
904
1389
|
*
|
|
905
|
-
* Either form is validated as UUID
|
|
906
|
-
*
|
|
907
|
-
* `
|
|
908
|
-
* `
|
|
1390
|
+
* Either form is validated as an RFC 9562 UUID — **any version**, so a
|
|
1391
|
+
* deterministic UUIDv5 over a stable name works as well as a random v4 —
|
|
1392
|
+
* before being forwarded to StoreKit's `appAccountToken` (iOS) / Play
|
|
1393
|
+
* Billing's `obfuscatedAccountId` (Android). Non-UUID values throw
|
|
1394
|
+
* `IAPError(INVALID_APP_USER_ID)`, as do the nil and max UUIDs, which
|
|
1395
|
+
* carry no identity. A throwing/rejecting fetcher
|
|
909
1396
|
* surfaces as `IAPError(APP_USER_ID_FETCH_FAILED, cause: <original>)`.
|
|
910
1397
|
*/
|
|
911
1398
|
type AppUserId = string | (() => Promise<string>) | ((ctx: AppUserIdFetcherContext) => Promise<string>);
|
|
@@ -928,7 +1415,10 @@ interface PurchaseOptions {
|
|
|
928
1415
|
type PurchaseResult<TEntitlement extends EntitlementBase = EntitlementBase> = {
|
|
929
1416
|
status: 'success';
|
|
930
1417
|
productId: string;
|
|
1418
|
+
/** Your backend's view of the receipt, after Attesto validated it. */
|
|
931
1419
|
transaction: VerifiedTransaction;
|
|
1420
|
+
/** The store's own view of the purchase. See {@link StorePurchase}. */
|
|
1421
|
+
purchase: StorePurchase;
|
|
932
1422
|
entitlements: TEntitlement[];
|
|
933
1423
|
} | {
|
|
934
1424
|
status: 'cancelled';
|
|
@@ -948,8 +1438,115 @@ type PurchaseResult<TEntitlement extends EntitlementBase = EntitlementBase> = {
|
|
|
948
1438
|
interface RestoreResult<TEntitlement extends EntitlementBase = EntitlementBase> {
|
|
949
1439
|
restored: number;
|
|
950
1440
|
entitlements: TEntitlement[];
|
|
1441
|
+
/**
|
|
1442
|
+
* The store's view of every transaction the account currently owns — one
|
|
1443
|
+
* entry per transaction submitted, in the order the store reported them.
|
|
1444
|
+
* Empty when the store reports nothing owned.
|
|
1445
|
+
*
|
|
1446
|
+
* This is the route to a {@link StorePurchase.purchaseReference} after a
|
|
1447
|
+
* reinstall or on a new device, where the install that made the purchase
|
|
1448
|
+
* no longer exists to have kept it.
|
|
1449
|
+
*/
|
|
1450
|
+
purchases: StorePurchase[];
|
|
951
1451
|
}
|
|
952
1452
|
|
|
1453
|
+
/**
|
|
1454
|
+
* Map of event name → payload. Used by the typed event emitter so consumer
|
|
1455
|
+
* subscriptions are statically checked.
|
|
1456
|
+
*/
|
|
1457
|
+
interface EventMap<TEntitlement extends EntitlementBase = EntitlementBase> {
|
|
1458
|
+
ready: undefined;
|
|
1459
|
+
'purchase-started': {
|
|
1460
|
+
productId: string;
|
|
1461
|
+
};
|
|
1462
|
+
'purchase-success': {
|
|
1463
|
+
productId: string;
|
|
1464
|
+
transaction: VerifiedTransaction;
|
|
1465
|
+
};
|
|
1466
|
+
'purchase-cancelled': {
|
|
1467
|
+
productId: string;
|
|
1468
|
+
};
|
|
1469
|
+
'purchase-pending': {
|
|
1470
|
+
productId: string;
|
|
1471
|
+
};
|
|
1472
|
+
/**
|
|
1473
|
+
* A purchase previously reported as `purchase-pending` has resolved.
|
|
1474
|
+
*
|
|
1475
|
+
* `purchased` means it cleared and the entitlement is in place (the
|
|
1476
|
+
* transaction has been verified and acknowledged by the time this fires).
|
|
1477
|
+
* `cancelled` means the store no longer has it, either waiting or owned —
|
|
1478
|
+
* the payment failed, expired, or the user abandoned it.
|
|
1479
|
+
*
|
|
1480
|
+
* **`cancelled` only fires where the platform can distinguish "gone" from
|
|
1481
|
+
* "still waiting"** — Android, via Play's purchase state. On iOS a
|
|
1482
|
+
* deferred purchase that is never approved produces no signal at all, so
|
|
1483
|
+
* iap says nothing rather than guessing; the pending mark simply stays
|
|
1484
|
+
* until a transaction arrives.
|
|
1485
|
+
*
|
|
1486
|
+
* Fires at most once per pending purchase. Use it to clear
|
|
1487
|
+
* pending-state copy a paywall put up on `purchase-pending`.
|
|
1488
|
+
*/
|
|
1489
|
+
'purchase-pending-resolved': {
|
|
1490
|
+
productId: string;
|
|
1491
|
+
status: 'purchased' | 'cancelled';
|
|
1492
|
+
};
|
|
1493
|
+
/**
|
|
1494
|
+
* A transaction arrived outside a `purchase()` call — a renewal, a refund
|
|
1495
|
+
* or revocation, an Ask-to-Buy approval, a pending purchase clearing.
|
|
1496
|
+
*
|
|
1497
|
+
* Fires before iap has verified it, so treat the payload as "the store
|
|
1498
|
+
* says something happened", not as an entitlement change. The
|
|
1499
|
+
* authoritative signal is `entitlements-changed`, which follows if the
|
|
1500
|
+
* backend confirms it.
|
|
1501
|
+
*
|
|
1502
|
+
* iOS only in practice: the native layer feeds this from Apple's
|
|
1503
|
+
* `Transaction.updates` and has no Android equivalent, where the same
|
|
1504
|
+
* ground is covered by reconciliation at launch and on resume.
|
|
1505
|
+
*/
|
|
1506
|
+
'transaction-updated': {
|
|
1507
|
+
productId: string;
|
|
1508
|
+
purchase: StorePurchase;
|
|
1509
|
+
};
|
|
1510
|
+
'purchase-failed': {
|
|
1511
|
+
productId: string;
|
|
1512
|
+
error: IAPError;
|
|
1513
|
+
};
|
|
1514
|
+
'verification-failed': {
|
|
1515
|
+
productId: string;
|
|
1516
|
+
error: IAPError;
|
|
1517
|
+
};
|
|
1518
|
+
'restore-started': undefined;
|
|
1519
|
+
'restore-completed': {
|
|
1520
|
+
restored: number;
|
|
1521
|
+
entitlements: TEntitlement[];
|
|
1522
|
+
};
|
|
1523
|
+
'entitlements-changed': {
|
|
1524
|
+
entitlements: TEntitlement[];
|
|
1525
|
+
previous: TEntitlement[];
|
|
1526
|
+
};
|
|
1527
|
+
/**
|
|
1528
|
+
* Recovery classified an `unfinished_transactions` entry as permanently
|
|
1529
|
+
* invalid (per `options.permanentErrorCodes`) and removed it from
|
|
1530
|
+
* storage. Will not be retried on subsequent launches. Useful for ops
|
|
1531
|
+
* logging / alerting on stuck-loop self-heal events.
|
|
1532
|
+
*
|
|
1533
|
+
* **Token is unmasked.** Receipt tokens (Apple `transactionId` /
|
|
1534
|
+
* Google `purchaseToken`) are useful for correlation in debugging
|
|
1535
|
+
* but are receipts you don't want to leak — treat as sensitive. Mask
|
|
1536
|
+
* before forwarding to external analytics / logging services. iap's
|
|
1537
|
+
* own internal logs use a masked form (see `lib/redact.ts`).
|
|
1538
|
+
*/
|
|
1539
|
+
'recovery-dropped-permanent': {
|
|
1540
|
+
productId: string;
|
|
1541
|
+
token: string;
|
|
1542
|
+
error: string;
|
|
1543
|
+
message?: string;
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
type EventName<TEntitlement extends EntitlementBase = EntitlementBase> = keyof EventMap<TEntitlement>;
|
|
1547
|
+
type EventPayload<K extends EventName<TEntitlement>, TEntitlement extends EntitlementBase = EntitlementBase> = EventMap<TEntitlement>[K];
|
|
1548
|
+
type Unsubscribe = () => void;
|
|
1549
|
+
|
|
953
1550
|
/**
|
|
954
1551
|
* The user's App Store / Google Play storefront — i.e. the country their
|
|
955
1552
|
* store account is registered to, which is the platform-blessed signal for
|
|
@@ -1020,8 +1617,9 @@ interface IAP<TEntitlement extends EntitlementBase = EntitlementBase> {
|
|
|
1020
1617
|
* can render the right UI without try/catch gymnastics.
|
|
1021
1618
|
*
|
|
1022
1619
|
* `opts.appUserId` is optional. When provided (string or async fetcher
|
|
1023
|
-
* returning a string), the resolved value is validated as
|
|
1024
|
-
* and forwarded to StoreKit's `appAccountToken`
|
|
1620
|
+
* returning a string), the resolved value is validated as an RFC 9562
|
|
1621
|
+
* UUID of any version and forwarded to StoreKit's `appAccountToken`
|
|
1622
|
+
* (iOS) / Play
|
|
1025
1623
|
* Billing's `obfuscatedAccountId` (Android) — making it available on
|
|
1026
1624
|
* Attesto's verify response and outbound webhook payload as
|
|
1027
1625
|
* `appUserId` so backends can join on user identity directly. See
|
|
@@ -1082,14 +1680,69 @@ interface IAP<TEntitlement extends EntitlementBase = EntitlementBase> {
|
|
|
1082
1680
|
* signed storefront from your backend instead.
|
|
1083
1681
|
*/
|
|
1084
1682
|
getStorefront(): Promise<Storefront | null>;
|
|
1683
|
+
/**
|
|
1684
|
+
* Whether an **active** entitlement with this key is held.
|
|
1685
|
+
*
|
|
1686
|
+
* Active means its `expiresAt` has not passed by the device clock, plus
|
|
1687
|
+
* any `options.entitlementGraceMs`. An entitlement is never dropped
|
|
1688
|
+
* because a refresh failed or the cache is old — only the store's own
|
|
1689
|
+
* deadline, or an authoritative negative, takes one away.
|
|
1690
|
+
*/
|
|
1085
1691
|
hasEntitlement(key: string): boolean;
|
|
1086
|
-
/**
|
|
1692
|
+
/**
|
|
1693
|
+
* The active entitlements, as a defensive shallow copy. Each is frozen.
|
|
1694
|
+
* Expired ones are omitted — see {@link IAP.hasEntitlement}.
|
|
1695
|
+
*/
|
|
1087
1696
|
getEntitlements(): TEntitlement[];
|
|
1088
|
-
/**
|
|
1697
|
+
/** A frozen active entitlement, or null if missing or expired. */
|
|
1089
1698
|
getEntitlement(key: string): TEntitlement | null;
|
|
1699
|
+
/**
|
|
1700
|
+
* When the entitlements were last confirmed by the verification source —
|
|
1701
|
+
* your backend, or the device in local mode. ISO 8601, or `null` if they
|
|
1702
|
+
* have never been confirmed on this install.
|
|
1703
|
+
*
|
|
1704
|
+
* Exposed so an app can decide for itself how much staleness it will
|
|
1705
|
+
* tolerate. iap deliberately does not revoke on staleness: a cached
|
|
1706
|
+
* entitlement stands until it expires, because a network error must never
|
|
1707
|
+
* cost a paying user their access.
|
|
1708
|
+
*/
|
|
1709
|
+
getEntitlementsVerifiedAt(): string | null;
|
|
1710
|
+
/**
|
|
1711
|
+
* Erase everything iap has stored on this device: the entitlement cache,
|
|
1712
|
+
* the unfinished-transaction queue, and any pending-purchase marks.
|
|
1713
|
+
*
|
|
1714
|
+
* For a "delete everything" control. `destroy()` deliberately leaves the
|
|
1715
|
+
* cache in place (a new instance should see the user's entitlements
|
|
1716
|
+
* immediately); this is the opposite, and does not tear the instance down —
|
|
1717
|
+
* the instance stays usable and a `refresh()` will repopulate from the
|
|
1718
|
+
* verification source.
|
|
1719
|
+
*
|
|
1720
|
+
* Best-effort on storage: a failure to clear one key is logged and the
|
|
1721
|
+
* rest is still cleared. Emits `entitlements-changed` with an empty list,
|
|
1722
|
+
* so a reactive UI follows.
|
|
1723
|
+
*
|
|
1724
|
+
* Throws `ALREADY_IN_PROGRESS` if a `purchase()` is in flight. That
|
|
1725
|
+
* purchase would re-populate the state and re-persist the cache moments
|
|
1726
|
+
* after the wipe, and leave its unfinished entry behind, so the erase
|
|
1727
|
+
* would look like it had silently failed. Await the purchase, then reset.
|
|
1728
|
+
*
|
|
1729
|
+
* A no-op after `destroy()`, rather than an error: a teardown racing a
|
|
1730
|
+
* "delete my data" tap is a real sequence, not a programming mistake.
|
|
1731
|
+
*
|
|
1732
|
+
* It also throws if the storage backend cannot be reached at all — an
|
|
1733
|
+
* uninstalled `@capacitor/preferences`, say. Individual key failures are
|
|
1734
|
+
* logged and the rest still cleared, but reporting success having erased
|
|
1735
|
+
* nothing is the one outcome a delete-everything control must not have.
|
|
1736
|
+
*
|
|
1737
|
+
* It does **not** revoke the purchase. The store still owns it, and a
|
|
1738
|
+
* restore will bring the entitlement back — which is correct: the user
|
|
1739
|
+
* paid.
|
|
1740
|
+
*/
|
|
1741
|
+
reset(): Promise<void>;
|
|
1090
1742
|
on<K extends EventName<TEntitlement>>(event: K, handler: (payload: EventPayload<K, TEntitlement>) => void): Unsubscribe;
|
|
1091
1743
|
}
|
|
1092
|
-
|
|
1744
|
+
|
|
1745
|
+
declare function createIAP<TEntitlement extends EntitlementBase = EntitlementBase>(input: IAPConfigInput<TEntitlement>): IAP<TEntitlement>;
|
|
1093
1746
|
|
|
1094
1747
|
interface VerifyAppleRequest {
|
|
1095
1748
|
productId: string;
|
|
@@ -1143,6 +1796,20 @@ type VerifyResponse<TEntitlement extends EntitlementBase = EntitlementBase> = {
|
|
|
1143
1796
|
type RestoreResponse<TEntitlement extends EntitlementBase = EntitlementBase> = {
|
|
1144
1797
|
valid: true;
|
|
1145
1798
|
entitlements: TEntitlement[];
|
|
1799
|
+
/**
|
|
1800
|
+
* Optional: the tokens that were actually verified, when the
|
|
1801
|
+
* adapter judged them individually.
|
|
1802
|
+
*
|
|
1803
|
+
* When present, only these are acknowledged. It exists because a
|
|
1804
|
+
* single consolidated `valid: true` cannot say *which* of the
|
|
1805
|
+
* submitted transactions passed — and acknowledging one that did
|
|
1806
|
+
* not finalises a purchase the adapter just refused, waiving
|
|
1807
|
+
* Play's refund window for it.
|
|
1808
|
+
*
|
|
1809
|
+
* A backend that validates the batch as a whole omits it, and
|
|
1810
|
+
* every submitted transaction is acknowledged as before.
|
|
1811
|
+
*/
|
|
1812
|
+
verifiedTokens?: string[];
|
|
1146
1813
|
} | {
|
|
1147
1814
|
valid: false;
|
|
1148
1815
|
error: string;
|
|
@@ -1186,6 +1853,32 @@ interface BackendAdapter<TEntitlement extends EntitlementBase = EntitlementBase>
|
|
|
1186
1853
|
* not a registration.
|
|
1187
1854
|
*/
|
|
1188
1855
|
listProducts?(): Promise<ConfiguredProduct[]>;
|
|
1856
|
+
/**
|
|
1857
|
+
* Optional: verify a transaction **without** extracting it into the HTTP
|
|
1858
|
+
* request shapes.
|
|
1859
|
+
*
|
|
1860
|
+
* `VerifyAppleRequest` and `VerifyGoogleRequest` carry what a server needs
|
|
1861
|
+
* to look a receipt up — a product id and a token. An on-device check
|
|
1862
|
+
* needs more than that: the binding, the subscription state, the expiry.
|
|
1863
|
+
* Rather than widen the HTTP contract (and so change what gets sent over
|
|
1864
|
+
* the wire), an adapter that can answer from the transaction itself
|
|
1865
|
+
* implements this, and the router prefers it.
|
|
1866
|
+
*
|
|
1867
|
+
* Implemented by the built-in local-verification adapter. The HTTP path
|
|
1868
|
+
* does not implement it and is unaffected.
|
|
1869
|
+
*/
|
|
1870
|
+
verifyNative?(transaction: NativeTransaction): Promise<VerifyResponse<TEntitlement>>;
|
|
1871
|
+
/**
|
|
1872
|
+
* Optional counterpart to {@link verifyNative} for restore: the owned
|
|
1873
|
+
* transactions, unextracted.
|
|
1874
|
+
*
|
|
1875
|
+
* Avoids two problems for an adapter that answers from the transaction.
|
|
1876
|
+
* It needs the fields `RestoreRequestTransaction` drops; and building
|
|
1877
|
+
* that request requires a `packageName` on every Google transaction,
|
|
1878
|
+
* which such an adapter never reads — so a request it would discard
|
|
1879
|
+
* could fail the restore outright.
|
|
1880
|
+
*/
|
|
1881
|
+
restoreNative?(owned: NativeTransaction[]): Promise<RestoreResponse<TEntitlement>>;
|
|
1189
1882
|
}
|
|
1190
1883
|
|
|
1191
1884
|
/**
|
|
@@ -1270,4 +1963,4 @@ declare class HttpBackendAdapter<TEntitlement extends EntitlementBase = Entitlem
|
|
|
1270
1963
|
listProducts(): Promise<ConfiguredProduct[]>;
|
|
1271
1964
|
}
|
|
1272
1965
|
|
|
1273
|
-
export { type AppUserId, type AppUserIdFetcherContext, type BackendAdapter, type BackendConfig, type BackendConfigInput, type ConfiguredProduct, DEFAULT_PERMANENT_ERROR_CODES, type DefaultEntitlement, type EntitlementBase, type EventMap, type EventName, type EventPayload, HttpBackendAdapter, HttpClient, type HttpRequest, type IAP, type IAPConfig, type IAPConfigInput, IAPError, IAPErrorCode, type LogLevel, type Logger, type NativeTransaction, type OptionsConfig, type Platform, type Product, type ProductType, type PurchaseOptions, type PurchaseResult, type RestoreRequest, type RestoreRequestTransaction, type RestoreResponse, type RestoreResult, type StorageConfig, type Storefront, type Unsubscribe, VERSION, type VerifiedTransaction, type VerifyAppleRequest, type VerifyGoogleRequest, type VerifyResponse, createIAP, errorHint, isIAPError };
|
|
1966
|
+
export { type AppUserId, type AppUserIdFetcherContext, type BackendAdapter, type BackendConfig, type BackendConfigInput, type ConfiguredProduct, DEFAULT_PERMANENT_ERROR_CODES, type DefaultEntitlement, type EntitlementBase, type EventMap, type EventName, type EventPayload, HttpBackendAdapter, HttpClient, type HttpRequest, type IAP, type IAPConfig, type IAPConfigInput, IAPError, IAPErrorCode, type LocalVerification, type LocalVerificationError, type LocalVerificationOptions, type LocallyVerifiedPurchase, type LogLevel, type Logger, type NativeTransaction, type OptionsConfig, type Platform, type Product, type ProductType, type PurchaseAssurance, type PurchaseOptions, type PurchaseResult, type RestoreRequest, type RestoreRequestTransaction, type RestoreResponse, type RestoreResult, type StorageConfig, type StorePurchase, type Storefront, type SubscriptionOffer, type SubscriptionOfferPayment, type SubscriptionOfferType, type SubscriptionRenewal, type SubscriptionState, type Unsubscribe, VERSION, type VerificationConfig, type VerifiedTransaction, type VerifyAppleRequest, type VerifyGoogleRequest, type VerifyResponse, createIAP, errorHint, isExpired, isIAPError, verifyPurchaseOnDevice };
|