@nosslabs/iap 0.3.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 +114 -0
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/index.cjs +2074 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1134 -0
- package/dist/index.d.ts +1134 -0
- package/dist/index.js +2067 -0
- package/dist/index.js.map +1 -0
- package/package.json +90 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';
|
|
4
|
+
interface Logger {
|
|
5
|
+
error(message: string, ...args: unknown[]): void;
|
|
6
|
+
warn(message: string, ...args: unknown[]): void;
|
|
7
|
+
info(message: string, ...args: unknown[]): void;
|
|
8
|
+
debug(message: string, ...args: unknown[]): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface HttpRequest {
|
|
12
|
+
method: 'GET' | 'POST';
|
|
13
|
+
/**
|
|
14
|
+
* Path appended to baseUrl. Leading slash is optional — both `'/foo'` and
|
|
15
|
+
* `'foo'` are normalized at request time, and any trailing slash on `baseUrl`
|
|
16
|
+
* is stripped, so all four corner cases produce the same joined URL.
|
|
17
|
+
*/
|
|
18
|
+
path: string;
|
|
19
|
+
/** JSON-serializable body (for POST). */
|
|
20
|
+
body?: unknown;
|
|
21
|
+
/** Extra headers, merged on top of auth headers + Content-Type. */
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
interface HttpClientOptions {
|
|
25
|
+
baseUrl: string;
|
|
26
|
+
/** Called before every request. Returns headers to merge in (e.g. `{ Authorization: 'Bearer ...' }`). */
|
|
27
|
+
getAuthHeaders: () => Record<string, string> | Promise<Record<string, string>>;
|
|
28
|
+
/** Per-attempt timeout in ms. */
|
|
29
|
+
timeoutMs: number;
|
|
30
|
+
/** Number of retry attempts on transient errors (5xx, 408, 429, network). */
|
|
31
|
+
retries: number;
|
|
32
|
+
/** Optional pre-send transform. Lets consumer rewrite path/body/headers. */
|
|
33
|
+
requestTransform?: (req: HttpRequest) => HttpRequest | Promise<HttpRequest>;
|
|
34
|
+
/** Optional response transform. Runs on the parsed JSON before validation. */
|
|
35
|
+
responseTransform?: (raw: unknown) => unknown | Promise<unknown>;
|
|
36
|
+
/** Override `globalThis.fetch` for tests. */
|
|
37
|
+
fetch?: typeof fetch;
|
|
38
|
+
logger: Logger;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generic HTTP client for the consumer backend.
|
|
42
|
+
*
|
|
43
|
+
* - **Timeout**: per-attempt via `AbortController` (native fetch has no timeout option).
|
|
44
|
+
* - **Retry policy**: 5xx, 408, 429, and network errors retry with exponential backoff.
|
|
45
|
+
* Other 4xx fail immediately (caller's responsibility to fix).
|
|
46
|
+
* - **Auth + transforms**: `getAuthHeaders()` runs once per request; `requestTransform`
|
|
47
|
+
* and `responseTransform` are escape hatches for consumers whose backend uses a
|
|
48
|
+
* different shape than the library's default.
|
|
49
|
+
* - **Logging**: requests are logged at debug level with sensitive headers redacted
|
|
50
|
+
* via `redactHeaders()` (PLAN.md §13 — never log raw bearer tokens).
|
|
51
|
+
*
|
|
52
|
+
* Consumers don't construct this directly — `HttpBackendAdapter` wraps it.
|
|
53
|
+
*/
|
|
54
|
+
declare class HttpClient {
|
|
55
|
+
private readonly opts;
|
|
56
|
+
private readonly fetchImpl;
|
|
57
|
+
constructor(opts: HttpClientOptions);
|
|
58
|
+
/**
|
|
59
|
+
* Execute the request and parse the response with the given zod schema.
|
|
60
|
+
* Returns the validated, transformed result.
|
|
61
|
+
*/
|
|
62
|
+
request<T>(req: HttpRequest, schema: z.ZodType<T>): Promise<T>;
|
|
63
|
+
private singleAttempt;
|
|
64
|
+
private fetchWithTimeout;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
declare const backendConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
68
|
+
/**
|
|
69
|
+
* Custom backend transport. If provided, all HTTP-specific fields below
|
|
70
|
+
* are ignored and the library uses this object directly for backend
|
|
71
|
+
* operations. Must implement `BackendAdapter`.
|
|
72
|
+
*/
|
|
73
|
+
adapter: z.ZodOptional<z.ZodUnknown>;
|
|
74
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
75
|
+
endpoints: z.ZodOptional<z.ZodEffects<z.ZodObject<{
|
|
76
|
+
/**
|
|
77
|
+
* Optional. Set when the consumer supports iOS purchases. iOS-less
|
|
78
|
+
* (e.g. Android-only) configs may omit it; the HTTP adapter will throw
|
|
79
|
+
* `INVALID_CONFIG` at runtime if `verifyApple()` is invoked without this
|
|
80
|
+
* endpoint configured. At least one of `verifyApple` or `verifyGoogle`
|
|
81
|
+
* must be set.
|
|
82
|
+
*/
|
|
83
|
+
verifyApple: z.ZodOptional<z.ZodString>;
|
|
84
|
+
/**
|
|
85
|
+
* Optional. Set when the consumer supports Android purchases.
|
|
86
|
+
* Android-less (e.g. iOS-only) configs may omit it; the HTTP adapter will
|
|
87
|
+
* throw `INVALID_CONFIG` at runtime if `verifyGoogle()` is invoked
|
|
88
|
+
* without this endpoint configured. At least one of `verifyApple` or
|
|
89
|
+
* `verifyGoogle` must be set.
|
|
90
|
+
*/
|
|
91
|
+
verifyGoogle: z.ZodOptional<z.ZodString>;
|
|
92
|
+
entitlements: z.ZodString;
|
|
93
|
+
restore: z.ZodString;
|
|
94
|
+
/**
|
|
95
|
+
* Optional. When set, the library fetches the SKU manifest from this
|
|
96
|
+
* endpoint during `initialize()` if `products` is omitted from config.
|
|
97
|
+
* See `docs/guide/backend-contract.md` for the response shape.
|
|
98
|
+
*/
|
|
99
|
+
products: z.ZodOptional<z.ZodString>;
|
|
100
|
+
}, "strip", z.ZodTypeAny, {
|
|
101
|
+
entitlements: string;
|
|
102
|
+
restore: string;
|
|
103
|
+
verifyApple?: string | undefined;
|
|
104
|
+
verifyGoogle?: string | undefined;
|
|
105
|
+
products?: string | undefined;
|
|
106
|
+
}, {
|
|
107
|
+
entitlements: string;
|
|
108
|
+
restore: string;
|
|
109
|
+
verifyApple?: string | undefined;
|
|
110
|
+
verifyGoogle?: string | undefined;
|
|
111
|
+
products?: string | undefined;
|
|
112
|
+
}>, {
|
|
113
|
+
entitlements: string;
|
|
114
|
+
restore: string;
|
|
115
|
+
verifyApple?: string | undefined;
|
|
116
|
+
verifyGoogle?: string | undefined;
|
|
117
|
+
products?: string | undefined;
|
|
118
|
+
}, {
|
|
119
|
+
entitlements: string;
|
|
120
|
+
restore: string;
|
|
121
|
+
verifyApple?: string | undefined;
|
|
122
|
+
verifyGoogle?: string | undefined;
|
|
123
|
+
products?: string | undefined;
|
|
124
|
+
}>>;
|
|
125
|
+
/**
|
|
126
|
+
* Returns auth headers to merge into every backend request. Called fresh
|
|
127
|
+
* per request so token refresh works automatically. Type is checked at
|
|
128
|
+
* runtime via shape guard, not zod (zod can't validate function contracts).
|
|
129
|
+
*/
|
|
130
|
+
getAuthHeaders: z.ZodOptional<z.ZodUnknown>;
|
|
131
|
+
/** Pre-send request transform. See {@link BackendConfig} for the typed shape. */
|
|
132
|
+
requestTransform: z.ZodOptional<z.ZodUnknown>;
|
|
133
|
+
/** Post-receive response transform. See {@link BackendConfig} for the typed shape. */
|
|
134
|
+
responseTransform: z.ZodOptional<z.ZodUnknown>;
|
|
135
|
+
entitlementSchema: z.ZodOptional<z.ZodUnknown>;
|
|
136
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
137
|
+
retries: z.ZodDefault<z.ZodNumber>;
|
|
138
|
+
}, "strip", z.ZodTypeAny, {
|
|
139
|
+
timeoutMs: number;
|
|
140
|
+
retries: number;
|
|
141
|
+
adapter?: unknown;
|
|
142
|
+
baseUrl?: string | undefined;
|
|
143
|
+
endpoints?: {
|
|
144
|
+
entitlements: string;
|
|
145
|
+
restore: string;
|
|
146
|
+
verifyApple?: string | undefined;
|
|
147
|
+
verifyGoogle?: string | undefined;
|
|
148
|
+
products?: string | undefined;
|
|
149
|
+
} | undefined;
|
|
150
|
+
getAuthHeaders?: unknown;
|
|
151
|
+
requestTransform?: unknown;
|
|
152
|
+
responseTransform?: unknown;
|
|
153
|
+
entitlementSchema?: unknown;
|
|
154
|
+
}, {
|
|
155
|
+
adapter?: unknown;
|
|
156
|
+
baseUrl?: string | undefined;
|
|
157
|
+
endpoints?: {
|
|
158
|
+
entitlements: string;
|
|
159
|
+
restore: string;
|
|
160
|
+
verifyApple?: string | undefined;
|
|
161
|
+
verifyGoogle?: string | undefined;
|
|
162
|
+
products?: string | undefined;
|
|
163
|
+
} | undefined;
|
|
164
|
+
getAuthHeaders?: unknown;
|
|
165
|
+
requestTransform?: unknown;
|
|
166
|
+
responseTransform?: unknown;
|
|
167
|
+
entitlementSchema?: unknown;
|
|
168
|
+
timeoutMs?: number | undefined;
|
|
169
|
+
retries?: number | undefined;
|
|
170
|
+
}>, {
|
|
171
|
+
timeoutMs: number;
|
|
172
|
+
retries: number;
|
|
173
|
+
adapter?: unknown;
|
|
174
|
+
baseUrl?: string | undefined;
|
|
175
|
+
endpoints?: {
|
|
176
|
+
entitlements: string;
|
|
177
|
+
restore: string;
|
|
178
|
+
verifyApple?: string | undefined;
|
|
179
|
+
verifyGoogle?: string | undefined;
|
|
180
|
+
products?: string | undefined;
|
|
181
|
+
} | undefined;
|
|
182
|
+
getAuthHeaders?: unknown;
|
|
183
|
+
requestTransform?: unknown;
|
|
184
|
+
responseTransform?: unknown;
|
|
185
|
+
entitlementSchema?: unknown;
|
|
186
|
+
}, {
|
|
187
|
+
adapter?: unknown;
|
|
188
|
+
baseUrl?: string | undefined;
|
|
189
|
+
endpoints?: {
|
|
190
|
+
entitlements: string;
|
|
191
|
+
restore: string;
|
|
192
|
+
verifyApple?: string | undefined;
|
|
193
|
+
verifyGoogle?: string | undefined;
|
|
194
|
+
products?: string | undefined;
|
|
195
|
+
} | undefined;
|
|
196
|
+
getAuthHeaders?: unknown;
|
|
197
|
+
requestTransform?: unknown;
|
|
198
|
+
responseTransform?: unknown;
|
|
199
|
+
entitlementSchema?: unknown;
|
|
200
|
+
timeoutMs?: number | undefined;
|
|
201
|
+
retries?: number | undefined;
|
|
202
|
+
}>;
|
|
203
|
+
declare const storageConfigSchema: z.ZodObject<{
|
|
204
|
+
type: z.ZodDefault<z.ZodEnum<["preferences", "memory", "custom"]>>;
|
|
205
|
+
namespace: z.ZodDefault<z.ZodString>;
|
|
206
|
+
adapter: z.ZodOptional<z.ZodUnknown>;
|
|
207
|
+
}, "strip", z.ZodTypeAny, {
|
|
208
|
+
type: "preferences" | "memory" | "custom";
|
|
209
|
+
namespace: string;
|
|
210
|
+
adapter?: unknown;
|
|
211
|
+
}, {
|
|
212
|
+
type?: "preferences" | "memory" | "custom" | undefined;
|
|
213
|
+
adapter?: unknown;
|
|
214
|
+
namespace?: string | undefined;
|
|
215
|
+
}>;
|
|
216
|
+
declare const optionsConfigSchema: z.ZodObject<{
|
|
217
|
+
refreshOnResume: z.ZodDefault<z.ZodBoolean>;
|
|
218
|
+
entitlementCacheTtlMs: z.ZodDefault<z.ZodNumber>;
|
|
219
|
+
recoverUnfinishedTransactions: z.ZodDefault<z.ZodBoolean>;
|
|
220
|
+
/**
|
|
221
|
+
* Cap on how many unfinished transactions recovery inspects per launch.
|
|
222
|
+
* Defends against pathological growth if the consumer's backend has been
|
|
223
|
+
* down for an extended period and the unfinished list keeps growing.
|
|
224
|
+
* Excess entries stay in storage and are processed on subsequent launches.
|
|
225
|
+
*/
|
|
226
|
+
recoveryMaxBatch: z.ZodDefault<z.ZodNumber>;
|
|
227
|
+
productPriceCacheTtlMs: z.ZodDefault<z.ZodNumber>;
|
|
228
|
+
logLevel: z.ZodDefault<z.ZodEnum<["silent", "error", "warn", "info", "debug"]>>;
|
|
229
|
+
logger: z.ZodOptional<z.ZodUnknown>;
|
|
230
|
+
}, "strip", z.ZodTypeAny, {
|
|
231
|
+
refreshOnResume: boolean;
|
|
232
|
+
entitlementCacheTtlMs: number;
|
|
233
|
+
recoverUnfinishedTransactions: boolean;
|
|
234
|
+
recoveryMaxBatch: number;
|
|
235
|
+
productPriceCacheTtlMs: number;
|
|
236
|
+
logLevel: "silent" | "error" | "warn" | "info" | "debug";
|
|
237
|
+
logger?: unknown;
|
|
238
|
+
}, {
|
|
239
|
+
refreshOnResume?: boolean | undefined;
|
|
240
|
+
entitlementCacheTtlMs?: number | undefined;
|
|
241
|
+
recoverUnfinishedTransactions?: boolean | undefined;
|
|
242
|
+
recoveryMaxBatch?: number | undefined;
|
|
243
|
+
productPriceCacheTtlMs?: number | undefined;
|
|
244
|
+
logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
|
|
245
|
+
logger?: unknown;
|
|
246
|
+
}>;
|
|
247
|
+
declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
248
|
+
/**
|
|
249
|
+
* Static SKU manifest. Optional: when omitted, the library calls
|
|
250
|
+
* `backend.adapter.listProducts()` (custom adapter) or GETs
|
|
251
|
+
* `backend.endpoints.products` (HTTP) during `initialize()`. Configs
|
|
252
|
+
* without either path throw `INVALID_CONFIG` at parse time.
|
|
253
|
+
*/
|
|
254
|
+
products: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
255
|
+
id: z.ZodString;
|
|
256
|
+
type: z.ZodEnum<["subscription", "product", "consumable"]>;
|
|
257
|
+
/**
|
|
258
|
+
* Optional. Used only by the Android native adapter to disambiguate which
|
|
259
|
+
* base plan to purchase for multi-plan subscription products (Google Play
|
|
260
|
+
* Billing). iOS ignores it. When omitted on Android, the native adapter
|
|
261
|
+
* falls back to `native.getOffer()` (the default offer) — fine for
|
|
262
|
+
* single-plan subscriptions and for non-subscription products.
|
|
263
|
+
*
|
|
264
|
+
* Recommended to set explicitly when a single subscription product has
|
|
265
|
+
* multiple base plans (e.g. monthly + yearly under one product id).
|
|
266
|
+
*/
|
|
267
|
+
androidPlanId: z.ZodOptional<z.ZodString>;
|
|
268
|
+
}, "strip", z.ZodTypeAny, {
|
|
269
|
+
id: string;
|
|
270
|
+
type: "subscription" | "product" | "consumable";
|
|
271
|
+
androidPlanId?: string | undefined;
|
|
272
|
+
}, {
|
|
273
|
+
id: string;
|
|
274
|
+
type: "subscription" | "product" | "consumable";
|
|
275
|
+
androidPlanId?: string | undefined;
|
|
276
|
+
}>, "many">>;
|
|
277
|
+
backend: z.ZodEffects<z.ZodObject<{
|
|
278
|
+
/**
|
|
279
|
+
* Custom backend transport. If provided, all HTTP-specific fields below
|
|
280
|
+
* are ignored and the library uses this object directly for backend
|
|
281
|
+
* operations. Must implement `BackendAdapter`.
|
|
282
|
+
*/
|
|
283
|
+
adapter: z.ZodOptional<z.ZodUnknown>;
|
|
284
|
+
baseUrl: z.ZodOptional<z.ZodString>;
|
|
285
|
+
endpoints: z.ZodOptional<z.ZodEffects<z.ZodObject<{
|
|
286
|
+
/**
|
|
287
|
+
* Optional. Set when the consumer supports iOS purchases. iOS-less
|
|
288
|
+
* (e.g. Android-only) configs may omit it; the HTTP adapter will throw
|
|
289
|
+
* `INVALID_CONFIG` at runtime if `verifyApple()` is invoked without this
|
|
290
|
+
* endpoint configured. At least one of `verifyApple` or `verifyGoogle`
|
|
291
|
+
* must be set.
|
|
292
|
+
*/
|
|
293
|
+
verifyApple: z.ZodOptional<z.ZodString>;
|
|
294
|
+
/**
|
|
295
|
+
* Optional. Set when the consumer supports Android purchases.
|
|
296
|
+
* Android-less (e.g. iOS-only) configs may omit it; the HTTP adapter will
|
|
297
|
+
* throw `INVALID_CONFIG` at runtime if `verifyGoogle()` is invoked
|
|
298
|
+
* without this endpoint configured. At least one of `verifyApple` or
|
|
299
|
+
* `verifyGoogle` must be set.
|
|
300
|
+
*/
|
|
301
|
+
verifyGoogle: z.ZodOptional<z.ZodString>;
|
|
302
|
+
entitlements: z.ZodString;
|
|
303
|
+
restore: z.ZodString;
|
|
304
|
+
/**
|
|
305
|
+
* Optional. When set, the library fetches the SKU manifest from this
|
|
306
|
+
* endpoint during `initialize()` if `products` is omitted from config.
|
|
307
|
+
* See `docs/guide/backend-contract.md` for the response shape.
|
|
308
|
+
*/
|
|
309
|
+
products: z.ZodOptional<z.ZodString>;
|
|
310
|
+
}, "strip", z.ZodTypeAny, {
|
|
311
|
+
entitlements: string;
|
|
312
|
+
restore: string;
|
|
313
|
+
verifyApple?: string | undefined;
|
|
314
|
+
verifyGoogle?: string | undefined;
|
|
315
|
+
products?: string | undefined;
|
|
316
|
+
}, {
|
|
317
|
+
entitlements: string;
|
|
318
|
+
restore: string;
|
|
319
|
+
verifyApple?: string | undefined;
|
|
320
|
+
verifyGoogle?: string | undefined;
|
|
321
|
+
products?: string | undefined;
|
|
322
|
+
}>, {
|
|
323
|
+
entitlements: string;
|
|
324
|
+
restore: string;
|
|
325
|
+
verifyApple?: string | undefined;
|
|
326
|
+
verifyGoogle?: string | undefined;
|
|
327
|
+
products?: string | undefined;
|
|
328
|
+
}, {
|
|
329
|
+
entitlements: string;
|
|
330
|
+
restore: string;
|
|
331
|
+
verifyApple?: string | undefined;
|
|
332
|
+
verifyGoogle?: string | undefined;
|
|
333
|
+
products?: string | undefined;
|
|
334
|
+
}>>;
|
|
335
|
+
/**
|
|
336
|
+
* Returns auth headers to merge into every backend request. Called fresh
|
|
337
|
+
* per request so token refresh works automatically. Type is checked at
|
|
338
|
+
* runtime via shape guard, not zod (zod can't validate function contracts).
|
|
339
|
+
*/
|
|
340
|
+
getAuthHeaders: z.ZodOptional<z.ZodUnknown>;
|
|
341
|
+
/** Pre-send request transform. See {@link BackendConfig} for the typed shape. */
|
|
342
|
+
requestTransform: z.ZodOptional<z.ZodUnknown>;
|
|
343
|
+
/** Post-receive response transform. See {@link BackendConfig} for the typed shape. */
|
|
344
|
+
responseTransform: z.ZodOptional<z.ZodUnknown>;
|
|
345
|
+
entitlementSchema: z.ZodOptional<z.ZodUnknown>;
|
|
346
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
347
|
+
retries: z.ZodDefault<z.ZodNumber>;
|
|
348
|
+
}, "strip", z.ZodTypeAny, {
|
|
349
|
+
timeoutMs: number;
|
|
350
|
+
retries: number;
|
|
351
|
+
adapter?: unknown;
|
|
352
|
+
baseUrl?: string | undefined;
|
|
353
|
+
endpoints?: {
|
|
354
|
+
entitlements: string;
|
|
355
|
+
restore: string;
|
|
356
|
+
verifyApple?: string | undefined;
|
|
357
|
+
verifyGoogle?: string | undefined;
|
|
358
|
+
products?: string | undefined;
|
|
359
|
+
} | undefined;
|
|
360
|
+
getAuthHeaders?: unknown;
|
|
361
|
+
requestTransform?: unknown;
|
|
362
|
+
responseTransform?: unknown;
|
|
363
|
+
entitlementSchema?: unknown;
|
|
364
|
+
}, {
|
|
365
|
+
adapter?: unknown;
|
|
366
|
+
baseUrl?: string | undefined;
|
|
367
|
+
endpoints?: {
|
|
368
|
+
entitlements: string;
|
|
369
|
+
restore: string;
|
|
370
|
+
verifyApple?: string | undefined;
|
|
371
|
+
verifyGoogle?: string | undefined;
|
|
372
|
+
products?: string | undefined;
|
|
373
|
+
} | undefined;
|
|
374
|
+
getAuthHeaders?: unknown;
|
|
375
|
+
requestTransform?: unknown;
|
|
376
|
+
responseTransform?: unknown;
|
|
377
|
+
entitlementSchema?: unknown;
|
|
378
|
+
timeoutMs?: number | undefined;
|
|
379
|
+
retries?: number | undefined;
|
|
380
|
+
}>, {
|
|
381
|
+
timeoutMs: number;
|
|
382
|
+
retries: number;
|
|
383
|
+
adapter?: unknown;
|
|
384
|
+
baseUrl?: string | undefined;
|
|
385
|
+
endpoints?: {
|
|
386
|
+
entitlements: string;
|
|
387
|
+
restore: string;
|
|
388
|
+
verifyApple?: string | undefined;
|
|
389
|
+
verifyGoogle?: string | undefined;
|
|
390
|
+
products?: string | undefined;
|
|
391
|
+
} | undefined;
|
|
392
|
+
getAuthHeaders?: unknown;
|
|
393
|
+
requestTransform?: unknown;
|
|
394
|
+
responseTransform?: unknown;
|
|
395
|
+
entitlementSchema?: unknown;
|
|
396
|
+
}, {
|
|
397
|
+
adapter?: unknown;
|
|
398
|
+
baseUrl?: string | undefined;
|
|
399
|
+
endpoints?: {
|
|
400
|
+
entitlements: string;
|
|
401
|
+
restore: string;
|
|
402
|
+
verifyApple?: string | undefined;
|
|
403
|
+
verifyGoogle?: string | undefined;
|
|
404
|
+
products?: string | undefined;
|
|
405
|
+
} | undefined;
|
|
406
|
+
getAuthHeaders?: unknown;
|
|
407
|
+
requestTransform?: unknown;
|
|
408
|
+
responseTransform?: unknown;
|
|
409
|
+
entitlementSchema?: unknown;
|
|
410
|
+
timeoutMs?: number | undefined;
|
|
411
|
+
retries?: number | undefined;
|
|
412
|
+
}>;
|
|
413
|
+
storage: z.ZodDefault<z.ZodObject<{
|
|
414
|
+
type: z.ZodDefault<z.ZodEnum<["preferences", "memory", "custom"]>>;
|
|
415
|
+
namespace: z.ZodDefault<z.ZodString>;
|
|
416
|
+
adapter: z.ZodOptional<z.ZodUnknown>;
|
|
417
|
+
}, "strip", z.ZodTypeAny, {
|
|
418
|
+
type: "preferences" | "memory" | "custom";
|
|
419
|
+
namespace: string;
|
|
420
|
+
adapter?: unknown;
|
|
421
|
+
}, {
|
|
422
|
+
type?: "preferences" | "memory" | "custom" | undefined;
|
|
423
|
+
adapter?: unknown;
|
|
424
|
+
namespace?: string | undefined;
|
|
425
|
+
}>>;
|
|
426
|
+
options: z.ZodDefault<z.ZodObject<{
|
|
427
|
+
refreshOnResume: z.ZodDefault<z.ZodBoolean>;
|
|
428
|
+
entitlementCacheTtlMs: z.ZodDefault<z.ZodNumber>;
|
|
429
|
+
recoverUnfinishedTransactions: z.ZodDefault<z.ZodBoolean>;
|
|
430
|
+
/**
|
|
431
|
+
* Cap on how many unfinished transactions recovery inspects per launch.
|
|
432
|
+
* Defends against pathological growth if the consumer's backend has been
|
|
433
|
+
* down for an extended period and the unfinished list keeps growing.
|
|
434
|
+
* Excess entries stay in storage and are processed on subsequent launches.
|
|
435
|
+
*/
|
|
436
|
+
recoveryMaxBatch: z.ZodDefault<z.ZodNumber>;
|
|
437
|
+
productPriceCacheTtlMs: z.ZodDefault<z.ZodNumber>;
|
|
438
|
+
logLevel: z.ZodDefault<z.ZodEnum<["silent", "error", "warn", "info", "debug"]>>;
|
|
439
|
+
logger: z.ZodOptional<z.ZodUnknown>;
|
|
440
|
+
}, "strip", z.ZodTypeAny, {
|
|
441
|
+
refreshOnResume: boolean;
|
|
442
|
+
entitlementCacheTtlMs: number;
|
|
443
|
+
recoverUnfinishedTransactions: boolean;
|
|
444
|
+
recoveryMaxBatch: number;
|
|
445
|
+
productPriceCacheTtlMs: number;
|
|
446
|
+
logLevel: "silent" | "error" | "warn" | "info" | "debug";
|
|
447
|
+
logger?: unknown;
|
|
448
|
+
}, {
|
|
449
|
+
refreshOnResume?: boolean | undefined;
|
|
450
|
+
entitlementCacheTtlMs?: number | undefined;
|
|
451
|
+
recoverUnfinishedTransactions?: boolean | undefined;
|
|
452
|
+
recoveryMaxBatch?: number | undefined;
|
|
453
|
+
productPriceCacheTtlMs?: number | undefined;
|
|
454
|
+
logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
|
|
455
|
+
logger?: unknown;
|
|
456
|
+
}>>;
|
|
457
|
+
}, "strip", z.ZodTypeAny, {
|
|
458
|
+
options: {
|
|
459
|
+
refreshOnResume: boolean;
|
|
460
|
+
entitlementCacheTtlMs: number;
|
|
461
|
+
recoverUnfinishedTransactions: boolean;
|
|
462
|
+
recoveryMaxBatch: number;
|
|
463
|
+
productPriceCacheTtlMs: number;
|
|
464
|
+
logLevel: "silent" | "error" | "warn" | "info" | "debug";
|
|
465
|
+
logger?: unknown;
|
|
466
|
+
};
|
|
467
|
+
backend: {
|
|
468
|
+
timeoutMs: number;
|
|
469
|
+
retries: number;
|
|
470
|
+
adapter?: unknown;
|
|
471
|
+
baseUrl?: string | undefined;
|
|
472
|
+
endpoints?: {
|
|
473
|
+
entitlements: string;
|
|
474
|
+
restore: string;
|
|
475
|
+
verifyApple?: string | undefined;
|
|
476
|
+
verifyGoogle?: string | undefined;
|
|
477
|
+
products?: string | undefined;
|
|
478
|
+
} | undefined;
|
|
479
|
+
getAuthHeaders?: unknown;
|
|
480
|
+
requestTransform?: unknown;
|
|
481
|
+
responseTransform?: unknown;
|
|
482
|
+
entitlementSchema?: unknown;
|
|
483
|
+
};
|
|
484
|
+
storage: {
|
|
485
|
+
type: "preferences" | "memory" | "custom";
|
|
486
|
+
namespace: string;
|
|
487
|
+
adapter?: unknown;
|
|
488
|
+
};
|
|
489
|
+
products?: {
|
|
490
|
+
id: string;
|
|
491
|
+
type: "subscription" | "product" | "consumable";
|
|
492
|
+
androidPlanId?: string | undefined;
|
|
493
|
+
}[] | undefined;
|
|
494
|
+
}, {
|
|
495
|
+
backend: {
|
|
496
|
+
adapter?: unknown;
|
|
497
|
+
baseUrl?: string | undefined;
|
|
498
|
+
endpoints?: {
|
|
499
|
+
entitlements: string;
|
|
500
|
+
restore: string;
|
|
501
|
+
verifyApple?: string | undefined;
|
|
502
|
+
verifyGoogle?: string | undefined;
|
|
503
|
+
products?: string | undefined;
|
|
504
|
+
} | undefined;
|
|
505
|
+
getAuthHeaders?: unknown;
|
|
506
|
+
requestTransform?: unknown;
|
|
507
|
+
responseTransform?: unknown;
|
|
508
|
+
entitlementSchema?: unknown;
|
|
509
|
+
timeoutMs?: number | undefined;
|
|
510
|
+
retries?: number | undefined;
|
|
511
|
+
};
|
|
512
|
+
options?: {
|
|
513
|
+
refreshOnResume?: boolean | undefined;
|
|
514
|
+
entitlementCacheTtlMs?: number | undefined;
|
|
515
|
+
recoverUnfinishedTransactions?: boolean | undefined;
|
|
516
|
+
recoveryMaxBatch?: number | undefined;
|
|
517
|
+
productPriceCacheTtlMs?: number | undefined;
|
|
518
|
+
logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
|
|
519
|
+
logger?: unknown;
|
|
520
|
+
} | undefined;
|
|
521
|
+
products?: {
|
|
522
|
+
id: string;
|
|
523
|
+
type: "subscription" | "product" | "consumable";
|
|
524
|
+
androidPlanId?: string | undefined;
|
|
525
|
+
}[] | undefined;
|
|
526
|
+
storage?: {
|
|
527
|
+
type?: "preferences" | "memory" | "custom" | undefined;
|
|
528
|
+
adapter?: unknown;
|
|
529
|
+
namespace?: string | undefined;
|
|
530
|
+
} | undefined;
|
|
531
|
+
}>, {
|
|
532
|
+
options: {
|
|
533
|
+
refreshOnResume: boolean;
|
|
534
|
+
entitlementCacheTtlMs: number;
|
|
535
|
+
recoverUnfinishedTransactions: boolean;
|
|
536
|
+
recoveryMaxBatch: number;
|
|
537
|
+
productPriceCacheTtlMs: number;
|
|
538
|
+
logLevel: "silent" | "error" | "warn" | "info" | "debug";
|
|
539
|
+
logger?: unknown;
|
|
540
|
+
};
|
|
541
|
+
backend: {
|
|
542
|
+
timeoutMs: number;
|
|
543
|
+
retries: number;
|
|
544
|
+
adapter?: unknown;
|
|
545
|
+
baseUrl?: string | undefined;
|
|
546
|
+
endpoints?: {
|
|
547
|
+
entitlements: string;
|
|
548
|
+
restore: string;
|
|
549
|
+
verifyApple?: string | undefined;
|
|
550
|
+
verifyGoogle?: string | undefined;
|
|
551
|
+
products?: string | undefined;
|
|
552
|
+
} | undefined;
|
|
553
|
+
getAuthHeaders?: unknown;
|
|
554
|
+
requestTransform?: unknown;
|
|
555
|
+
responseTransform?: unknown;
|
|
556
|
+
entitlementSchema?: unknown;
|
|
557
|
+
};
|
|
558
|
+
storage: {
|
|
559
|
+
type: "preferences" | "memory" | "custom";
|
|
560
|
+
namespace: string;
|
|
561
|
+
adapter?: unknown;
|
|
562
|
+
};
|
|
563
|
+
products?: {
|
|
564
|
+
id: string;
|
|
565
|
+
type: "subscription" | "product" | "consumable";
|
|
566
|
+
androidPlanId?: string | undefined;
|
|
567
|
+
}[] | undefined;
|
|
568
|
+
}, {
|
|
569
|
+
backend: {
|
|
570
|
+
adapter?: unknown;
|
|
571
|
+
baseUrl?: string | undefined;
|
|
572
|
+
endpoints?: {
|
|
573
|
+
entitlements: string;
|
|
574
|
+
restore: string;
|
|
575
|
+
verifyApple?: string | undefined;
|
|
576
|
+
verifyGoogle?: string | undefined;
|
|
577
|
+
products?: string | undefined;
|
|
578
|
+
} | undefined;
|
|
579
|
+
getAuthHeaders?: unknown;
|
|
580
|
+
requestTransform?: unknown;
|
|
581
|
+
responseTransform?: unknown;
|
|
582
|
+
entitlementSchema?: unknown;
|
|
583
|
+
timeoutMs?: number | undefined;
|
|
584
|
+
retries?: number | undefined;
|
|
585
|
+
};
|
|
586
|
+
options?: {
|
|
587
|
+
refreshOnResume?: boolean | undefined;
|
|
588
|
+
entitlementCacheTtlMs?: number | undefined;
|
|
589
|
+
recoverUnfinishedTransactions?: boolean | undefined;
|
|
590
|
+
recoveryMaxBatch?: number | undefined;
|
|
591
|
+
productPriceCacheTtlMs?: number | undefined;
|
|
592
|
+
logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
|
|
593
|
+
logger?: unknown;
|
|
594
|
+
} | undefined;
|
|
595
|
+
products?: {
|
|
596
|
+
id: string;
|
|
597
|
+
type: "subscription" | "product" | "consumable";
|
|
598
|
+
androidPlanId?: string | undefined;
|
|
599
|
+
}[] | undefined;
|
|
600
|
+
storage?: {
|
|
601
|
+
type?: "preferences" | "memory" | "custom" | undefined;
|
|
602
|
+
adapter?: unknown;
|
|
603
|
+
namespace?: string | undefined;
|
|
604
|
+
} | undefined;
|
|
605
|
+
}>;
|
|
606
|
+
type RawBackendConfig = z.infer<typeof backendConfigSchema>;
|
|
607
|
+
type RawBackendConfigInput = z.input<typeof backendConfigSchema>;
|
|
608
|
+
/**
|
|
609
|
+
* Replace the schema's `unknown` typings for function-shaped fields with
|
|
610
|
+
* their precise TS contracts. The schema captures the *structural* shape
|
|
611
|
+
* and runtime validates "is a function" via `superRefine`; this overlay
|
|
612
|
+
* gives consumers proper IDE autocomplete and removes the need for
|
|
613
|
+
* `as never` casts inside the HTTP adapter.
|
|
614
|
+
*
|
|
615
|
+
* Zod's `z.function()` would type these as `(...args: unknown[]) => unknown`
|
|
616
|
+
* which is too wide, and zod can't validate a function *contract* at runtime
|
|
617
|
+
* anyway — only the existence of a function reference.
|
|
618
|
+
*/
|
|
619
|
+
type FunctionTypedBackendOverlay = {
|
|
620
|
+
getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
621
|
+
requestTransform?: (req: HttpRequest) => HttpRequest | Promise<HttpRequest>;
|
|
622
|
+
responseTransform?: (raw: unknown) => unknown | Promise<unknown>;
|
|
623
|
+
};
|
|
624
|
+
type BackendConfig = Omit<RawBackendConfig, 'getAuthHeaders' | 'requestTransform' | 'responseTransform'> & FunctionTypedBackendOverlay;
|
|
625
|
+
type BackendConfigInput = Omit<RawBackendConfigInput, 'getAuthHeaders' | 'requestTransform' | 'responseTransform'> & FunctionTypedBackendOverlay;
|
|
626
|
+
type RawIAPConfig = z.infer<typeof iapConfigSchema>;
|
|
627
|
+
type RawIAPConfigInput = z.input<typeof iapConfigSchema>;
|
|
628
|
+
type IAPConfig = Omit<RawIAPConfig, 'backend'> & {
|
|
629
|
+
backend: BackendConfig;
|
|
630
|
+
};
|
|
631
|
+
type IAPConfigInput = Omit<RawIAPConfigInput, 'backend'> & {
|
|
632
|
+
backend: BackendConfigInput;
|
|
633
|
+
};
|
|
634
|
+
type StorageConfig = z.infer<typeof storageConfigSchema>;
|
|
635
|
+
type OptionsConfig = z.infer<typeof optionsConfigSchema>;
|
|
636
|
+
|
|
637
|
+
declare const entitlementBaseSchema: z.ZodObject<{
|
|
638
|
+
key: z.ZodString;
|
|
639
|
+
productId: z.ZodString;
|
|
640
|
+
expiresAt: z.ZodNullable<z.ZodString>;
|
|
641
|
+
}, "strip", z.ZodTypeAny, {
|
|
642
|
+
key: string;
|
|
643
|
+
productId: string;
|
|
644
|
+
expiresAt: string | null;
|
|
645
|
+
}, {
|
|
646
|
+
key: string;
|
|
647
|
+
productId: string;
|
|
648
|
+
expiresAt: string | null;
|
|
649
|
+
}>;
|
|
650
|
+
/**
|
|
651
|
+
* Minimum shape every entitlement returned by the backend must satisfy.
|
|
652
|
+
* Consumer-defined fields pass through unvalidated unless the consumer
|
|
653
|
+
* supplies their own schema via `config.backend.entitlementSchema`.
|
|
654
|
+
*/
|
|
655
|
+
type EntitlementBase = z.infer<typeof entitlementBaseSchema>;
|
|
656
|
+
/** Default shape used when no `TEntitlement` type parameter is given. */
|
|
657
|
+
type DefaultEntitlement = EntitlementBase;
|
|
658
|
+
|
|
659
|
+
declare const IAPErrorCode: {
|
|
660
|
+
readonly INVALID_CONFIG: "INVALID_CONFIG";
|
|
661
|
+
readonly NOT_INITIALIZED: "NOT_INITIALIZED";
|
|
662
|
+
readonly PLATFORM_NOT_SUPPORTED: "PLATFORM_NOT_SUPPORTED";
|
|
663
|
+
readonly BILLING_NOT_AVAILABLE: "BILLING_NOT_AVAILABLE";
|
|
664
|
+
readonly PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND";
|
|
665
|
+
readonly USER_CANCELLED: "USER_CANCELLED";
|
|
666
|
+
readonly PURCHASE_PENDING: "PURCHASE_PENDING";
|
|
667
|
+
readonly ALREADY_PURCHASED: "ALREADY_PURCHASED";
|
|
668
|
+
readonly STORE_ERROR: "STORE_ERROR";
|
|
669
|
+
readonly UNACKNOWLEDGED_PENDING: "UNACKNOWLEDGED_PENDING";
|
|
670
|
+
readonly ALREADY_IN_PROGRESS: "ALREADY_IN_PROGRESS";
|
|
671
|
+
readonly BACKEND_UNAVAILABLE: "BACKEND_UNAVAILABLE";
|
|
672
|
+
readonly BACKEND_TIMEOUT: "BACKEND_TIMEOUT";
|
|
673
|
+
readonly BACKEND_AUTH_FAILED: "BACKEND_AUTH_FAILED";
|
|
674
|
+
/** Backend reachable but the response was rejected (non-transient 4xx other
|
|
675
|
+
* than auth, malformed JSON, schema violation, 204 No Content on a JSON
|
|
676
|
+
* endpoint). Fix the request shape or the backend; do not retry. */
|
|
677
|
+
readonly BACKEND_BAD_RESPONSE: "BACKEND_BAD_RESPONSE";
|
|
678
|
+
readonly VERIFICATION_REJECTED: "VERIFICATION_REJECTED";
|
|
679
|
+
readonly STORAGE_ERROR: "STORAGE_ERROR";
|
|
680
|
+
/**
|
|
681
|
+
* The supplied `appUserId` (literal or fetcher-returned) is not a valid
|
|
682
|
+
* UUID v4. Apple requires a UUID for `appAccountToken`; we enforce the
|
|
683
|
+
* same constraint cross-platform for a consistent contract.
|
|
684
|
+
*/
|
|
685
|
+
readonly INVALID_APP_USER_ID: "INVALID_APP_USER_ID";
|
|
686
|
+
/**
|
|
687
|
+
* The async `appUserId` fetcher threw or rejected. Original error is
|
|
688
|
+
* attached as `cause` so the caller can introspect (network failure,
|
|
689
|
+
* backend 5xx, etc.).
|
|
690
|
+
*/
|
|
691
|
+
readonly APP_USER_ID_FETCH_FAILED: "APP_USER_ID_FETCH_FAILED";
|
|
692
|
+
};
|
|
693
|
+
type IAPErrorCode = (typeof IAPErrorCode)[keyof typeof IAPErrorCode];
|
|
694
|
+
/** Public accessor for the hint text — exported so docs / consumer error UIs can render it. */
|
|
695
|
+
declare function errorHint(code: IAPErrorCode): string;
|
|
696
|
+
interface IAPErrorOptions {
|
|
697
|
+
code: IAPErrorCode;
|
|
698
|
+
message: string;
|
|
699
|
+
cause?: unknown;
|
|
700
|
+
recoverable?: boolean;
|
|
701
|
+
/**
|
|
702
|
+
* Whether to append the per-code remediation hint to the message.
|
|
703
|
+
* Defaults to `true`. Set `false` only if the caller already includes a
|
|
704
|
+
* hint in `message` (avoids double-tagging).
|
|
705
|
+
*/
|
|
706
|
+
includeHint?: boolean;
|
|
707
|
+
}
|
|
708
|
+
declare class IAPError extends Error {
|
|
709
|
+
readonly code: IAPErrorCode;
|
|
710
|
+
readonly recoverable: boolean;
|
|
711
|
+
readonly cause?: unknown;
|
|
712
|
+
constructor(options: IAPErrorOptions);
|
|
713
|
+
}
|
|
714
|
+
declare function isIAPError(error: unknown): error is IAPError;
|
|
715
|
+
|
|
716
|
+
type ProductType = 'subscription' | 'product' | 'consumable';
|
|
717
|
+
interface ConfiguredProduct {
|
|
718
|
+
id: string;
|
|
719
|
+
type: ProductType;
|
|
720
|
+
/**
|
|
721
|
+
* Required for Android subscriptions — the base plan identifier configured in
|
|
722
|
+
* Google Play Console. Ignored on iOS and for non-subscription products.
|
|
723
|
+
*/
|
|
724
|
+
androidPlanId?: string;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Native product info merged with configured metadata.
|
|
728
|
+
* Library-public shape — kept stable across Capacitor versions.
|
|
729
|
+
*/
|
|
730
|
+
interface Product {
|
|
731
|
+
id: string;
|
|
732
|
+
type: ProductType;
|
|
733
|
+
title: string;
|
|
734
|
+
description: string;
|
|
735
|
+
/** Localized price string, e.g. "$4.99". Always render this — never hardcode. */
|
|
736
|
+
priceString: string;
|
|
737
|
+
/** BigInt as string to avoid precision loss, e.g. "4990000". */
|
|
738
|
+
priceMicros: string;
|
|
739
|
+
/** ISO 4217 code, e.g. "USD". */
|
|
740
|
+
currency: string;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/** Platform a transaction originated from. */
|
|
744
|
+
type Platform = 'apple' | 'google';
|
|
745
|
+
/**
|
|
746
|
+
* Normalized native transaction handed to core flows.
|
|
747
|
+
* Plugin-version differences are translated by the adapter so this shape
|
|
748
|
+
* stays stable across Capacitor majors.
|
|
749
|
+
*/
|
|
750
|
+
interface NativeTransaction {
|
|
751
|
+
platform: Platform;
|
|
752
|
+
productId: string;
|
|
753
|
+
/**
|
|
754
|
+
* Apple: the StoreKit `transactionId` (numeric string).
|
|
755
|
+
* Google: the `purchaseToken` from Play Billing.
|
|
756
|
+
*/
|
|
757
|
+
token: string;
|
|
758
|
+
/** Google only — required by Attesto's `/v1/google/verify`. */
|
|
759
|
+
packageName?: string;
|
|
760
|
+
/** Product type the transaction was originally purchased as. */
|
|
761
|
+
productType: ProductType;
|
|
762
|
+
/** Optional pass-through of plugin-native fields for backend hints. */
|
|
763
|
+
raw?: unknown;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Verified transaction returned by the consumer backend after Attesto check.
|
|
767
|
+
* Generic over the backend-defined transaction shape.
|
|
768
|
+
*/
|
|
769
|
+
interface VerifiedTransaction<TExtra = Record<string, unknown>> {
|
|
770
|
+
id: string;
|
|
771
|
+
productId: string;
|
|
772
|
+
expiresAt: string | null;
|
|
773
|
+
verifiedAt: string;
|
|
774
|
+
raw?: TExtra;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Map of event name → payload. Used by the typed event emitter so consumer
|
|
779
|
+
* subscriptions are statically checked.
|
|
780
|
+
*/
|
|
781
|
+
interface EventMap<TEntitlement extends EntitlementBase = EntitlementBase> {
|
|
782
|
+
ready: undefined;
|
|
783
|
+
'purchase-started': {
|
|
784
|
+
productId: string;
|
|
785
|
+
};
|
|
786
|
+
'purchase-success': {
|
|
787
|
+
productId: string;
|
|
788
|
+
transaction: VerifiedTransaction;
|
|
789
|
+
};
|
|
790
|
+
'purchase-cancelled': {
|
|
791
|
+
productId: string;
|
|
792
|
+
};
|
|
793
|
+
'purchase-pending': {
|
|
794
|
+
productId: string;
|
|
795
|
+
};
|
|
796
|
+
'purchase-failed': {
|
|
797
|
+
productId: string;
|
|
798
|
+
error: IAPError;
|
|
799
|
+
};
|
|
800
|
+
'verification-failed': {
|
|
801
|
+
productId: string;
|
|
802
|
+
error: IAPError;
|
|
803
|
+
};
|
|
804
|
+
'restore-started': undefined;
|
|
805
|
+
'restore-completed': {
|
|
806
|
+
restored: number;
|
|
807
|
+
entitlements: TEntitlement[];
|
|
808
|
+
};
|
|
809
|
+
'entitlements-changed': {
|
|
810
|
+
entitlements: TEntitlement[];
|
|
811
|
+
previous: TEntitlement[];
|
|
812
|
+
};
|
|
813
|
+
'price-stale': {
|
|
814
|
+
productId: string;
|
|
815
|
+
lastFetchedAt: number;
|
|
816
|
+
};
|
|
817
|
+
error: {
|
|
818
|
+
error: IAPError;
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
type EventName<TEntitlement extends EntitlementBase = EntitlementBase> = keyof EventMap<TEntitlement>;
|
|
822
|
+
type EventPayload<K extends EventName<TEntitlement>, TEntitlement extends EntitlementBase = EntitlementBase> = EventMap<TEntitlement>[K];
|
|
823
|
+
type Unsubscribe = () => void;
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Value supplied to `iap.purchase({ appUserId })`. Either a UUID v4
|
|
827
|
+
* string the caller already has (e.g. from local cache / app state) or
|
|
828
|
+
* an async fetcher the library invokes once per purchase to retrieve
|
|
829
|
+
* the UUID from the caller's backend (typical pattern: backend mints +
|
|
830
|
+
* persists on first call, returns the existing UUID on later calls).
|
|
831
|
+
*
|
|
832
|
+
* The fetcher is invoked **fresh on every purchase** — iap caches
|
|
833
|
+
* nothing. The backend owns the mint-or-lookup idempotency. The
|
|
834
|
+
* fetcher closes over whatever auth state the caller needs (session
|
|
835
|
+
* token, JWT, cookie); iap passes no implicit context.
|
|
836
|
+
*
|
|
837
|
+
* Either form is validated as UUID v4 before being forwarded to
|
|
838
|
+
* StoreKit's `appAccountToken` (iOS) / Play Billing's
|
|
839
|
+
* `obfuscatedAccountId` (Android). Non-UUID values throw
|
|
840
|
+
* `IAPError(INVALID_APP_USER_ID)`. A throwing/rejecting fetcher
|
|
841
|
+
* surfaces as `IAPError(APP_USER_ID_FETCH_FAILED, cause: <original>)`.
|
|
842
|
+
*/
|
|
843
|
+
type AppUserId = string | (() => Promise<string>);
|
|
844
|
+
/**
|
|
845
|
+
* Options accepted by `iap.purchase(...)`. `productId` is required;
|
|
846
|
+
* `appUserId` is optional — when omitted, no `applicationUsername` is
|
|
847
|
+
* passed to the native plugin (identical to behavior before v0.2).
|
|
848
|
+
*/
|
|
849
|
+
interface PurchaseOptions {
|
|
850
|
+
productId: string;
|
|
851
|
+
/**
|
|
852
|
+
* Pre-attach a user identifier so it travels through the StoreKit /
|
|
853
|
+
* Play Billing purchase and reaches your backend on both the verify
|
|
854
|
+
* response and the eventual webhook. Eliminates the verify/webhook
|
|
855
|
+
* race for purchases where you have a user identity at purchase time.
|
|
856
|
+
* See `AppUserId` for supplied-value semantics.
|
|
857
|
+
*/
|
|
858
|
+
appUserId?: AppUserId;
|
|
859
|
+
}
|
|
860
|
+
type PurchaseResult<TEntitlement extends EntitlementBase = EntitlementBase> = {
|
|
861
|
+
status: 'success';
|
|
862
|
+
productId: string;
|
|
863
|
+
transaction: VerifiedTransaction;
|
|
864
|
+
entitlements: TEntitlement[];
|
|
865
|
+
} | {
|
|
866
|
+
status: 'cancelled';
|
|
867
|
+
productId: string;
|
|
868
|
+
} | {
|
|
869
|
+
status: 'pending';
|
|
870
|
+
productId: string;
|
|
871
|
+
} | {
|
|
872
|
+
status: 'verification_failed';
|
|
873
|
+
productId: string;
|
|
874
|
+
error: IAPError;
|
|
875
|
+
} | {
|
|
876
|
+
status: 'failed';
|
|
877
|
+
productId: string;
|
|
878
|
+
error: IAPError;
|
|
879
|
+
};
|
|
880
|
+
interface RestoreResult<TEntitlement extends EntitlementBase = EntitlementBase> {
|
|
881
|
+
restored: number;
|
|
882
|
+
entitlements: TEntitlement[];
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
interface IAP<TEntitlement extends EntitlementBase = EntitlementBase> {
|
|
886
|
+
initialize(): Promise<void>;
|
|
887
|
+
/**
|
|
888
|
+
* Refresh entitlements from the consumer backend.
|
|
889
|
+
*
|
|
890
|
+
* Fetches via the configured `BackendAdapter` (HTTP default or custom),
|
|
891
|
+
* freezes results, persists them via the storage adapter, and emits
|
|
892
|
+
* `entitlements-changed`.
|
|
893
|
+
*/
|
|
894
|
+
refresh(): Promise<void>;
|
|
895
|
+
/**
|
|
896
|
+
* Tear down. Removes event listeners and disposes the native adapter
|
|
897
|
+
* (which clears its `pendingFinish` map and removes the long-lived
|
|
898
|
+
* `.approved()` listener on cdv).
|
|
899
|
+
*
|
|
900
|
+
* NOTE 1: persisted entitlement cache is NOT cleared. If you're handling
|
|
901
|
+
* a logout for a multi-user app, also call your storage adapter's
|
|
902
|
+
* `clear()` (or the consumer-supplied equivalent) before the next user
|
|
903
|
+
* logs in, otherwise their first read will see the previous user's
|
|
904
|
+
* cached entitlements until `refresh()` returns.
|
|
905
|
+
*
|
|
906
|
+
* NOTE 2: calling `destroy()` while a `purchase()` is in flight may
|
|
907
|
+
* leave the result in an inconsistent state — the backend may have
|
|
908
|
+
* recorded the entitlement but the native `acknowledge()` call will
|
|
909
|
+
* be a no-op (because cdv's `pendingFinish` was cleared mid-flow).
|
|
910
|
+
* On Android this means Google auto-refunds in 3 days. Avoid by
|
|
911
|
+
* awaiting the in-flight `purchase()` before calling `destroy()`.
|
|
912
|
+
*/
|
|
913
|
+
destroy(): Promise<void>;
|
|
914
|
+
/**
|
|
915
|
+
* Start a purchase. Throws `IAPError` only on impossible states
|
|
916
|
+
* (NOT_INITIALIZED, ALREADY_IN_PROGRESS, PRODUCT_NOT_FOUND,
|
|
917
|
+
* INVALID_APP_USER_ID, APP_USER_ID_FETCH_FAILED); all other
|
|
918
|
+
* outcomes — user cancellation, backend rejection, native errors — are
|
|
919
|
+
* surfaced via the `PurchaseResult` discriminated union so the caller
|
|
920
|
+
* can render the right UI without try/catch gymnastics.
|
|
921
|
+
*
|
|
922
|
+
* `opts.appUserId` is optional. When provided (string or async fetcher
|
|
923
|
+
* returning a string), the resolved value is validated as a UUID v4
|
|
924
|
+
* and forwarded to StoreKit's `appAccountToken` (iOS) / Play
|
|
925
|
+
* Billing's `obfuscatedAccountId` (Android) — making it available on
|
|
926
|
+
* Attesto's verify response and outbound webhook payload as
|
|
927
|
+
* `appUserId` so backends can join on user identity directly. See
|
|
928
|
+
* `PurchaseOptions` and `AppUserId` for full semantics.
|
|
929
|
+
*
|
|
930
|
+
* Emits `purchase-started`, then exactly one of: `purchase-success`
|
|
931
|
+
* (+ `entitlements-changed`), `purchase-cancelled`, `purchase-pending`,
|
|
932
|
+
* `verification-failed`, or `purchase-failed`.
|
|
933
|
+
*/
|
|
934
|
+
purchase(opts: PurchaseOptions): Promise<PurchaseResult<TEntitlement>>;
|
|
935
|
+
/**
|
|
936
|
+
* Re-verify every owned transaction with the consumer backend and
|
|
937
|
+
* refresh entitlements from the consolidated response. Wire this to a
|
|
938
|
+
* "Restore Purchases" button.
|
|
939
|
+
*
|
|
940
|
+
* Returns `{ restored, entitlements }` where `restored` is the number
|
|
941
|
+
* of native transactions submitted (0 on a fresh install with no
|
|
942
|
+
* purchases). Throws `IAPError` on backend rejection or transport
|
|
943
|
+
* failure — wrap the call in try/catch in the consumer's button
|
|
944
|
+
* handler.
|
|
945
|
+
*
|
|
946
|
+
* Emits `restore-started`, then on success `restore-completed` +
|
|
947
|
+
* `entitlements-changed` (the latter only when the entitlements list
|
|
948
|
+
* actually changed). On failure no completion event fires; the thrown
|
|
949
|
+
* error is the only signal.
|
|
950
|
+
*
|
|
951
|
+
* NOTE on the empty-owned-list case: when the platform store reports
|
|
952
|
+
* no owned transactions (fresh install, signed-out Apple ID, etc.),
|
|
953
|
+
* the library short-circuits — it does NOT call the backend and
|
|
954
|
+
* preserves whatever entitlements were already cached in memory. If
|
|
955
|
+
* you suspect cache staleness (e.g. user just signed in to a new
|
|
956
|
+
* Apple ID), call `iap.refresh()` afterward to reconcile against the
|
|
957
|
+
* backend's view of the user's entitlements.
|
|
958
|
+
*/
|
|
959
|
+
restorePurchases(): Promise<RestoreResult<TEntitlement>>;
|
|
960
|
+
/**
|
|
961
|
+
* Get product info merged with native pricing. Returns one entry per
|
|
962
|
+
* product the platform store knows about; products configured but not
|
|
963
|
+
* yet ingested by the store are silently skipped (no error).
|
|
964
|
+
*/
|
|
965
|
+
getProducts(): Promise<Product[]>;
|
|
966
|
+
hasEntitlement(key: string): boolean;
|
|
967
|
+
/** Returns a defensive shallow copy. Each entitlement is frozen. */
|
|
968
|
+
getEntitlements(): TEntitlement[];
|
|
969
|
+
/** Returns a frozen entitlement reference, or null if missing. */
|
|
970
|
+
getEntitlement(key: string): TEntitlement | null;
|
|
971
|
+
on<K extends EventName<TEntitlement>>(event: K, handler: (payload: EventPayload<K, TEntitlement>) => void): Unsubscribe;
|
|
972
|
+
}
|
|
973
|
+
declare function createIAP<TEntitlement extends EntitlementBase = EntitlementBase>(input: IAPConfigInput): IAP<TEntitlement>;
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Library version. Updated by the publish workflow to match `package.json`.
|
|
977
|
+
* Read at runtime by the logger so error reports include the version.
|
|
978
|
+
*/
|
|
979
|
+
declare const VERSION = "0.1.0";
|
|
980
|
+
|
|
981
|
+
interface VerifyAppleRequest {
|
|
982
|
+
productId: string;
|
|
983
|
+
/** Apple StoreKit transaction id (numeric string). */
|
|
984
|
+
transactionId: string;
|
|
985
|
+
type: ProductType;
|
|
986
|
+
}
|
|
987
|
+
interface VerifyGoogleRequest {
|
|
988
|
+
productId: string;
|
|
989
|
+
/** Google Play `purchaseToken`. */
|
|
990
|
+
purchaseToken: string;
|
|
991
|
+
/** Application package name (e.g. `com.example.app`). */
|
|
992
|
+
packageName: string;
|
|
993
|
+
type: ProductType;
|
|
994
|
+
}
|
|
995
|
+
/** A single entry in a restore batch. Discriminated by `platform`. */
|
|
996
|
+
type RestoreRequestTransaction = {
|
|
997
|
+
platform: 'apple';
|
|
998
|
+
productId: string;
|
|
999
|
+
transactionId: string;
|
|
1000
|
+
} | {
|
|
1001
|
+
platform: 'google';
|
|
1002
|
+
productId: string;
|
|
1003
|
+
purchaseToken: string;
|
|
1004
|
+
packageName: string;
|
|
1005
|
+
};
|
|
1006
|
+
interface RestoreRequest {
|
|
1007
|
+
transactions: RestoreRequestTransaction[];
|
|
1008
|
+
}
|
|
1009
|
+
/** Public response type for `verifyApple` / `verifyGoogle`, generic over the consumer's entitlement shape. */
|
|
1010
|
+
type VerifyResponse<TEntitlement extends EntitlementBase = EntitlementBase> = {
|
|
1011
|
+
valid: true;
|
|
1012
|
+
entitlements: TEntitlement[];
|
|
1013
|
+
transaction: {
|
|
1014
|
+
id: string;
|
|
1015
|
+
productId: string;
|
|
1016
|
+
expiresAt?: string | null;
|
|
1017
|
+
};
|
|
1018
|
+
} | {
|
|
1019
|
+
valid: false;
|
|
1020
|
+
error: string;
|
|
1021
|
+
message?: string;
|
|
1022
|
+
};
|
|
1023
|
+
/**
|
|
1024
|
+
* Restore response. Distinct from {@link VerifyResponse} because the
|
|
1025
|
+
* orchestrator never reads `transaction` on the restore path — the schema
|
|
1026
|
+
* accordingly omits any required transaction echo. Backends that include
|
|
1027
|
+
* a `transaction` (or any other field) on the success branch ride through
|
|
1028
|
+
* unmodified via top-level passthrough; consumers cast to read them.
|
|
1029
|
+
*/
|
|
1030
|
+
type RestoreResponse<TEntitlement extends EntitlementBase = EntitlementBase> = {
|
|
1031
|
+
valid: true;
|
|
1032
|
+
entitlements: TEntitlement[];
|
|
1033
|
+
} | {
|
|
1034
|
+
valid: false;
|
|
1035
|
+
error: string;
|
|
1036
|
+
message?: string;
|
|
1037
|
+
};
|
|
1038
|
+
/**
|
|
1039
|
+
* Abstracts the consumer's backend so the orchestrator (Phase 4+) doesn't
|
|
1040
|
+
* depend on `fetch`. The default `HttpBackendAdapter` is HTTP/JSON via
|
|
1041
|
+
* `fetch`; consumers on GraphQL, gRPC-web, Supabase, Firebase, etc. supply
|
|
1042
|
+
* their own implementation via `config.backend.adapter`.
|
|
1043
|
+
*
|
|
1044
|
+
* All methods MUST validate response shape. Failures should throw IAPError
|
|
1045
|
+
* (HTTP impl uses BACKEND_UNAVAILABLE / BACKEND_TIMEOUT / BACKEND_AUTH_FAILED
|
|
1046
|
+
* / VERIFICATION_REJECTED depending on cause) — the orchestrator relies on
|
|
1047
|
+
* `IAPError.recoverable` to decide whether to surface a transient error or
|
|
1048
|
+
* abort the purchase flow.
|
|
1049
|
+
*/
|
|
1050
|
+
interface BackendAdapter<TEntitlement extends EntitlementBase = EntitlementBase> {
|
|
1051
|
+
verifyApple(req: VerifyAppleRequest): Promise<VerifyResponse<TEntitlement>>;
|
|
1052
|
+
verifyGoogle(req: VerifyGoogleRequest): Promise<VerifyResponse<TEntitlement>>;
|
|
1053
|
+
/** GET current entitlements; library uses this for refresh + warm cache. */
|
|
1054
|
+
getEntitlements(): Promise<TEntitlement[]>;
|
|
1055
|
+
/**
|
|
1056
|
+
* Batch re-verify. Backend returns consolidated entitlements.
|
|
1057
|
+
*
|
|
1058
|
+
* Returns {@link RestoreResponse} (not {@link VerifyResponse}) — the orchestrator
|
|
1059
|
+
* does not consume any per-transaction echo on restore, so the response
|
|
1060
|
+
* shape intentionally omits the required `transaction` field that verify
|
|
1061
|
+
* has. Backends may still attach a `transaction` (or any other extras)
|
|
1062
|
+
* — they ride through via the schema's top-level passthrough.
|
|
1063
|
+
*/
|
|
1064
|
+
restore(req: RestoreRequest): Promise<RestoreResponse<TEntitlement>>;
|
|
1065
|
+
/**
|
|
1066
|
+
* Optional: return the SKU manifest the app should register.
|
|
1067
|
+
*
|
|
1068
|
+
* When implemented, the library calls this during `initialize()` if the
|
|
1069
|
+
* consumer omitted `products` from `createIAP()` config — letting the
|
|
1070
|
+
* backend curate which SKUs are surfaced (feature flags, A/B mixes,
|
|
1071
|
+
* regional catalogs). Returned ids MUST still be pre-registered in App
|
|
1072
|
+
* Store Connect / Google Play Console; the manifest is a curated subset,
|
|
1073
|
+
* not a registration.
|
|
1074
|
+
*/
|
|
1075
|
+
listProducts?(): Promise<ConfiguredProduct[]>;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
interface HttpBackendAdapterOptions {
|
|
1079
|
+
baseUrl: string;
|
|
1080
|
+
endpoints: {
|
|
1081
|
+
/**
|
|
1082
|
+
* Optional. Required only when the consumer's app actually invokes
|
|
1083
|
+
* `verifyApple` (iOS purchases). Android-only configs may omit it; the
|
|
1084
|
+
* adapter throws `INVALID_CONFIG` if `verifyApple()` is called without
|
|
1085
|
+
* this set. At least one of `verifyApple` or `verifyGoogle` must be set
|
|
1086
|
+
* for any usable config.
|
|
1087
|
+
*/
|
|
1088
|
+
verifyApple?: string;
|
|
1089
|
+
/**
|
|
1090
|
+
* Optional. Required only when the consumer's app actually invokes
|
|
1091
|
+
* `verifyGoogle` (Android purchases). iOS-only configs may omit it; the
|
|
1092
|
+
* adapter throws `INVALID_CONFIG` if `verifyGoogle()` is called without
|
|
1093
|
+
* this set. At least one of `verifyApple` or `verifyGoogle` must be set
|
|
1094
|
+
* for any usable config.
|
|
1095
|
+
*/
|
|
1096
|
+
verifyGoogle?: string;
|
|
1097
|
+
entitlements: string;
|
|
1098
|
+
restore: string;
|
|
1099
|
+
products?: string;
|
|
1100
|
+
};
|
|
1101
|
+
getAuthHeaders: () => Record<string, string> | Promise<Record<string, string>>;
|
|
1102
|
+
requestTransform?: BackendConfig['requestTransform'];
|
|
1103
|
+
responseTransform?: BackendConfig['responseTransform'];
|
|
1104
|
+
timeoutMs: number;
|
|
1105
|
+
retries: number;
|
|
1106
|
+
fetch?: typeof fetch;
|
|
1107
|
+
logger: Logger;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Default `BackendAdapter` implementation: HTTP/JSON via `fetch`.
|
|
1111
|
+
*
|
|
1112
|
+
* The four methods translate to HTTP calls against the consumer's backend:
|
|
1113
|
+
* - `verifyApple` → POST `endpoints.verifyApple`
|
|
1114
|
+
* - `verifyGoogle` → POST `endpoints.verifyGoogle`
|
|
1115
|
+
* - `getEntitlements` → GET `endpoints.entitlements`
|
|
1116
|
+
* - `restore` → POST `endpoints.restore`
|
|
1117
|
+
*
|
|
1118
|
+
* The recommended request/response shape mirrors Attesto's normalized
|
|
1119
|
+
* transaction one hop downstream (see PLAN.md §5.8). Consumers whose
|
|
1120
|
+
* backend uses a different shape can supply `requestTransform` /
|
|
1121
|
+
* `responseTransform` to map between the library's defaults and theirs.
|
|
1122
|
+
*/
|
|
1123
|
+
declare class HttpBackendAdapter<TEntitlement extends EntitlementBase = EntitlementBase> implements BackendAdapter<TEntitlement> {
|
|
1124
|
+
private readonly http;
|
|
1125
|
+
private readonly endpoints;
|
|
1126
|
+
constructor(opts: HttpBackendAdapterOptions);
|
|
1127
|
+
verifyApple(req: VerifyAppleRequest): Promise<VerifyResponse<TEntitlement>>;
|
|
1128
|
+
verifyGoogle(req: VerifyGoogleRequest): Promise<VerifyResponse<TEntitlement>>;
|
|
1129
|
+
getEntitlements(): Promise<TEntitlement[]>;
|
|
1130
|
+
restore(req: RestoreRequest): Promise<RestoreResponse<TEntitlement>>;
|
|
1131
|
+
listProducts(): Promise<ConfiguredProduct[]>;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
export { type AppUserId, type BackendAdapter, type BackendConfig, type BackendConfigInput, type ConfiguredProduct, 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 Unsubscribe, VERSION, type VerifiedTransaction, type VerifyAppleRequest, type VerifyGoogleRequest, type VerifyResponse, createIAP, errorHint, isIAPError };
|