@nosslabs/iap 0.3.1 → 0.4.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/dist/index.d.cts CHANGED
@@ -224,6 +224,19 @@ declare const optionsConfigSchema: z.ZodObject<{
224
224
  * Excess entries stay in storage and are processed on subsequent launches.
225
225
  */
226
226
  recoveryMaxBatch: z.ZodDefault<z.ZodNumber>;
227
+ /**
228
+ * List of backend `valid:false` error codes that recovery should treat as
229
+ * permanent — entries with a matching error are removed from storage
230
+ * instead of retried on every launch. When omitted (the default), iap
231
+ * uses `DEFAULT_PERMANENT_ERROR_CODES` (`['TRANSACTION_NOT_FOUND',
232
+ * 'PRODUCT_MISMATCH']`).
233
+ *
234
+ * REPLACES the default when provided — pass `[...DEFAULT_PERMANENT_ERROR_CODES,
235
+ * 'YOUR_CODE']` to extend, or `[]` to disable the feature entirely
236
+ * (revert to retry-forever behavior). Export the constant from
237
+ * `@nosslabs/iap` for the spread form.
238
+ */
239
+ permanentErrorCodes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
227
240
  productPriceCacheTtlMs: z.ZodDefault<z.ZodNumber>;
228
241
  logLevel: z.ZodDefault<z.ZodEnum<["silent", "error", "warn", "info", "debug"]>>;
229
242
  logger: z.ZodOptional<z.ZodUnknown>;
@@ -234,12 +247,14 @@ declare const optionsConfigSchema: z.ZodObject<{
234
247
  recoveryMaxBatch: number;
235
248
  productPriceCacheTtlMs: number;
236
249
  logLevel: "silent" | "error" | "warn" | "info" | "debug";
250
+ permanentErrorCodes?: string[] | undefined;
237
251
  logger?: unknown;
238
252
  }, {
239
253
  refreshOnResume?: boolean | undefined;
240
254
  entitlementCacheTtlMs?: number | undefined;
241
255
  recoverUnfinishedTransactions?: boolean | undefined;
242
256
  recoveryMaxBatch?: number | undefined;
257
+ permanentErrorCodes?: string[] | undefined;
243
258
  productPriceCacheTtlMs?: number | undefined;
244
259
  logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
245
260
  logger?: unknown;
@@ -434,6 +449,19 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
434
449
  * Excess entries stay in storage and are processed on subsequent launches.
435
450
  */
436
451
  recoveryMaxBatch: z.ZodDefault<z.ZodNumber>;
452
+ /**
453
+ * List of backend `valid:false` error codes that recovery should treat as
454
+ * permanent — entries with a matching error are removed from storage
455
+ * instead of retried on every launch. When omitted (the default), iap
456
+ * uses `DEFAULT_PERMANENT_ERROR_CODES` (`['TRANSACTION_NOT_FOUND',
457
+ * 'PRODUCT_MISMATCH']`).
458
+ *
459
+ * REPLACES the default when provided — pass `[...DEFAULT_PERMANENT_ERROR_CODES,
460
+ * 'YOUR_CODE']` to extend, or `[]` to disable the feature entirely
461
+ * (revert to retry-forever behavior). Export the constant from
462
+ * `@nosslabs/iap` for the spread form.
463
+ */
464
+ permanentErrorCodes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
437
465
  productPriceCacheTtlMs: z.ZodDefault<z.ZodNumber>;
438
466
  logLevel: z.ZodDefault<z.ZodEnum<["silent", "error", "warn", "info", "debug"]>>;
439
467
  logger: z.ZodOptional<z.ZodUnknown>;
@@ -444,12 +472,14 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
444
472
  recoveryMaxBatch: number;
445
473
  productPriceCacheTtlMs: number;
446
474
  logLevel: "silent" | "error" | "warn" | "info" | "debug";
475
+ permanentErrorCodes?: string[] | undefined;
447
476
  logger?: unknown;
448
477
  }, {
449
478
  refreshOnResume?: boolean | undefined;
450
479
  entitlementCacheTtlMs?: number | undefined;
451
480
  recoverUnfinishedTransactions?: boolean | undefined;
452
481
  recoveryMaxBatch?: number | undefined;
482
+ permanentErrorCodes?: string[] | undefined;
453
483
  productPriceCacheTtlMs?: number | undefined;
454
484
  logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
455
485
  logger?: unknown;
@@ -462,6 +492,7 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
462
492
  recoveryMaxBatch: number;
463
493
  productPriceCacheTtlMs: number;
464
494
  logLevel: "silent" | "error" | "warn" | "info" | "debug";
495
+ permanentErrorCodes?: string[] | undefined;
465
496
  logger?: unknown;
466
497
  };
467
498
  backend: {
@@ -514,6 +545,7 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
514
545
  entitlementCacheTtlMs?: number | undefined;
515
546
  recoverUnfinishedTransactions?: boolean | undefined;
516
547
  recoveryMaxBatch?: number | undefined;
548
+ permanentErrorCodes?: string[] | undefined;
517
549
  productPriceCacheTtlMs?: number | undefined;
518
550
  logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
519
551
  logger?: unknown;
@@ -536,6 +568,7 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
536
568
  recoveryMaxBatch: number;
537
569
  productPriceCacheTtlMs: number;
538
570
  logLevel: "silent" | "error" | "warn" | "info" | "debug";
571
+ permanentErrorCodes?: string[] | undefined;
539
572
  logger?: unknown;
540
573
  };
541
574
  backend: {
@@ -588,6 +621,7 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
588
621
  entitlementCacheTtlMs?: number | undefined;
589
622
  recoverUnfinishedTransactions?: boolean | undefined;
590
623
  recoveryMaxBatch?: number | undefined;
624
+ permanentErrorCodes?: string[] | undefined;
591
625
  productPriceCacheTtlMs?: number | undefined;
592
626
  logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
593
627
  logger?: unknown;
@@ -814,6 +848,24 @@ interface EventMap<TEntitlement extends EntitlementBase = EntitlementBase> {
814
848
  productId: string;
815
849
  lastFetchedAt: number;
816
850
  };
851
+ /**
852
+ * Recovery classified an `unfinished_transactions` entry as permanently
853
+ * invalid (per `options.permanentErrorCodes`) and removed it from
854
+ * storage. Will not be retried on subsequent launches. Useful for ops
855
+ * logging / alerting on stuck-loop self-heal events.
856
+ *
857
+ * **Token is unmasked.** Receipt tokens (Apple `transactionId` /
858
+ * Google `purchaseToken`) are useful for correlation in debugging
859
+ * but are receipts you don't want to leak — treat as sensitive. Mask
860
+ * before forwarding to external analytics / logging services. iap's
861
+ * own internal logs use a masked form (see `lib/redact.ts`).
862
+ */
863
+ 'recovery-dropped-permanent': {
864
+ productId: string;
865
+ token: string;
866
+ error: string;
867
+ message?: string;
868
+ };
817
869
  error: {
818
870
  error: IAPError;
819
871
  };
@@ -995,12 +1047,6 @@ interface IAP<TEntitlement extends EntitlementBase = EntitlementBase> {
995
1047
  }
996
1048
  declare function createIAP<TEntitlement extends EntitlementBase = EntitlementBase>(input: IAPConfigInput): IAP<TEntitlement>;
997
1049
 
998
- /**
999
- * Library version. Updated by the publish workflow to match `package.json`.
1000
- * Read at runtime by the logger so error reports include the version.
1001
- */
1002
- declare const VERSION = "0.1.0";
1003
-
1004
1050
  interface VerifyAppleRequest {
1005
1051
  productId: string;
1006
1052
  /** Apple StoreKit transaction id (numeric string). */
@@ -1098,6 +1144,28 @@ interface BackendAdapter<TEntitlement extends EntitlementBase = EntitlementBase>
1098
1144
  listProducts?(): Promise<ConfiguredProduct[]>;
1099
1145
  }
1100
1146
 
1147
+ /**
1148
+ * Backend `valid:false` error codes that are treated as permanent by default
1149
+ * (entry removed from `unfinished_transactions` instead of retried forever).
1150
+ *
1151
+ * Conservative on purpose: only the two codes the documented recipe
1152
+ * contract identifies as unambiguous "domain-not-found" answers. Consumers
1153
+ * with custom backend error vocabularies extend via
1154
+ * `options.permanentErrorCodes`.
1155
+ *
1156
+ * Distinct from the `RECOVERABLE_CODES` set in `lib/errors.ts`: that one
1157
+ * classifies iap-internal `IAPErrorCode`s for transport/storage retry
1158
+ * semantics; this one classifies opaque error strings the consumer's
1159
+ * backend returns in `valid:false` responses.
1160
+ */
1161
+ declare const DEFAULT_PERMANENT_ERROR_CODES: readonly string[];
1162
+
1163
+ /**
1164
+ * Library version. Updated by the publish workflow to match `package.json`.
1165
+ * Read at runtime by the logger so error reports include the version.
1166
+ */
1167
+ declare const VERSION = "0.1.0";
1168
+
1101
1169
  interface HttpBackendAdapterOptions {
1102
1170
  baseUrl: string;
1103
1171
  endpoints: {
@@ -1154,4 +1222,4 @@ declare class HttpBackendAdapter<TEntitlement extends EntitlementBase = Entitlem
1154
1222
  listProducts(): Promise<ConfiguredProduct[]>;
1155
1223
  }
1156
1224
 
1157
- 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 };
1225
+ export { type AppUserId, 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 Unsubscribe, VERSION, type VerifiedTransaction, type VerifyAppleRequest, type VerifyGoogleRequest, type VerifyResponse, createIAP, errorHint, isIAPError };
package/dist/index.d.ts CHANGED
@@ -224,6 +224,19 @@ declare const optionsConfigSchema: z.ZodObject<{
224
224
  * Excess entries stay in storage and are processed on subsequent launches.
225
225
  */
226
226
  recoveryMaxBatch: z.ZodDefault<z.ZodNumber>;
227
+ /**
228
+ * List of backend `valid:false` error codes that recovery should treat as
229
+ * permanent — entries with a matching error are removed from storage
230
+ * instead of retried on every launch. When omitted (the default), iap
231
+ * uses `DEFAULT_PERMANENT_ERROR_CODES` (`['TRANSACTION_NOT_FOUND',
232
+ * 'PRODUCT_MISMATCH']`).
233
+ *
234
+ * REPLACES the default when provided — pass `[...DEFAULT_PERMANENT_ERROR_CODES,
235
+ * 'YOUR_CODE']` to extend, or `[]` to disable the feature entirely
236
+ * (revert to retry-forever behavior). Export the constant from
237
+ * `@nosslabs/iap` for the spread form.
238
+ */
239
+ permanentErrorCodes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
227
240
  productPriceCacheTtlMs: z.ZodDefault<z.ZodNumber>;
228
241
  logLevel: z.ZodDefault<z.ZodEnum<["silent", "error", "warn", "info", "debug"]>>;
229
242
  logger: z.ZodOptional<z.ZodUnknown>;
@@ -234,12 +247,14 @@ declare const optionsConfigSchema: z.ZodObject<{
234
247
  recoveryMaxBatch: number;
235
248
  productPriceCacheTtlMs: number;
236
249
  logLevel: "silent" | "error" | "warn" | "info" | "debug";
250
+ permanentErrorCodes?: string[] | undefined;
237
251
  logger?: unknown;
238
252
  }, {
239
253
  refreshOnResume?: boolean | undefined;
240
254
  entitlementCacheTtlMs?: number | undefined;
241
255
  recoverUnfinishedTransactions?: boolean | undefined;
242
256
  recoveryMaxBatch?: number | undefined;
257
+ permanentErrorCodes?: string[] | undefined;
243
258
  productPriceCacheTtlMs?: number | undefined;
244
259
  logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
245
260
  logger?: unknown;
@@ -434,6 +449,19 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
434
449
  * Excess entries stay in storage and are processed on subsequent launches.
435
450
  */
436
451
  recoveryMaxBatch: z.ZodDefault<z.ZodNumber>;
452
+ /**
453
+ * List of backend `valid:false` error codes that recovery should treat as
454
+ * permanent — entries with a matching error are removed from storage
455
+ * instead of retried on every launch. When omitted (the default), iap
456
+ * uses `DEFAULT_PERMANENT_ERROR_CODES` (`['TRANSACTION_NOT_FOUND',
457
+ * 'PRODUCT_MISMATCH']`).
458
+ *
459
+ * REPLACES the default when provided — pass `[...DEFAULT_PERMANENT_ERROR_CODES,
460
+ * 'YOUR_CODE']` to extend, or `[]` to disable the feature entirely
461
+ * (revert to retry-forever behavior). Export the constant from
462
+ * `@nosslabs/iap` for the spread form.
463
+ */
464
+ permanentErrorCodes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
437
465
  productPriceCacheTtlMs: z.ZodDefault<z.ZodNumber>;
438
466
  logLevel: z.ZodDefault<z.ZodEnum<["silent", "error", "warn", "info", "debug"]>>;
439
467
  logger: z.ZodOptional<z.ZodUnknown>;
@@ -444,12 +472,14 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
444
472
  recoveryMaxBatch: number;
445
473
  productPriceCacheTtlMs: number;
446
474
  logLevel: "silent" | "error" | "warn" | "info" | "debug";
475
+ permanentErrorCodes?: string[] | undefined;
447
476
  logger?: unknown;
448
477
  }, {
449
478
  refreshOnResume?: boolean | undefined;
450
479
  entitlementCacheTtlMs?: number | undefined;
451
480
  recoverUnfinishedTransactions?: boolean | undefined;
452
481
  recoveryMaxBatch?: number | undefined;
482
+ permanentErrorCodes?: string[] | undefined;
453
483
  productPriceCacheTtlMs?: number | undefined;
454
484
  logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
455
485
  logger?: unknown;
@@ -462,6 +492,7 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
462
492
  recoveryMaxBatch: number;
463
493
  productPriceCacheTtlMs: number;
464
494
  logLevel: "silent" | "error" | "warn" | "info" | "debug";
495
+ permanentErrorCodes?: string[] | undefined;
465
496
  logger?: unknown;
466
497
  };
467
498
  backend: {
@@ -514,6 +545,7 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
514
545
  entitlementCacheTtlMs?: number | undefined;
515
546
  recoverUnfinishedTransactions?: boolean | undefined;
516
547
  recoveryMaxBatch?: number | undefined;
548
+ permanentErrorCodes?: string[] | undefined;
517
549
  productPriceCacheTtlMs?: number | undefined;
518
550
  logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
519
551
  logger?: unknown;
@@ -536,6 +568,7 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
536
568
  recoveryMaxBatch: number;
537
569
  productPriceCacheTtlMs: number;
538
570
  logLevel: "silent" | "error" | "warn" | "info" | "debug";
571
+ permanentErrorCodes?: string[] | undefined;
539
572
  logger?: unknown;
540
573
  };
541
574
  backend: {
@@ -588,6 +621,7 @@ declare const iapConfigSchema: z.ZodEffects<z.ZodObject<{
588
621
  entitlementCacheTtlMs?: number | undefined;
589
622
  recoverUnfinishedTransactions?: boolean | undefined;
590
623
  recoveryMaxBatch?: number | undefined;
624
+ permanentErrorCodes?: string[] | undefined;
591
625
  productPriceCacheTtlMs?: number | undefined;
592
626
  logLevel?: "silent" | "error" | "warn" | "info" | "debug" | undefined;
593
627
  logger?: unknown;
@@ -814,6 +848,24 @@ interface EventMap<TEntitlement extends EntitlementBase = EntitlementBase> {
814
848
  productId: string;
815
849
  lastFetchedAt: number;
816
850
  };
851
+ /**
852
+ * Recovery classified an `unfinished_transactions` entry as permanently
853
+ * invalid (per `options.permanentErrorCodes`) and removed it from
854
+ * storage. Will not be retried on subsequent launches. Useful for ops
855
+ * logging / alerting on stuck-loop self-heal events.
856
+ *
857
+ * **Token is unmasked.** Receipt tokens (Apple `transactionId` /
858
+ * Google `purchaseToken`) are useful for correlation in debugging
859
+ * but are receipts you don't want to leak — treat as sensitive. Mask
860
+ * before forwarding to external analytics / logging services. iap's
861
+ * own internal logs use a masked form (see `lib/redact.ts`).
862
+ */
863
+ 'recovery-dropped-permanent': {
864
+ productId: string;
865
+ token: string;
866
+ error: string;
867
+ message?: string;
868
+ };
817
869
  error: {
818
870
  error: IAPError;
819
871
  };
@@ -995,12 +1047,6 @@ interface IAP<TEntitlement extends EntitlementBase = EntitlementBase> {
995
1047
  }
996
1048
  declare function createIAP<TEntitlement extends EntitlementBase = EntitlementBase>(input: IAPConfigInput): IAP<TEntitlement>;
997
1049
 
998
- /**
999
- * Library version. Updated by the publish workflow to match `package.json`.
1000
- * Read at runtime by the logger so error reports include the version.
1001
- */
1002
- declare const VERSION = "0.1.0";
1003
-
1004
1050
  interface VerifyAppleRequest {
1005
1051
  productId: string;
1006
1052
  /** Apple StoreKit transaction id (numeric string). */
@@ -1098,6 +1144,28 @@ interface BackendAdapter<TEntitlement extends EntitlementBase = EntitlementBase>
1098
1144
  listProducts?(): Promise<ConfiguredProduct[]>;
1099
1145
  }
1100
1146
 
1147
+ /**
1148
+ * Backend `valid:false` error codes that are treated as permanent by default
1149
+ * (entry removed from `unfinished_transactions` instead of retried forever).
1150
+ *
1151
+ * Conservative on purpose: only the two codes the documented recipe
1152
+ * contract identifies as unambiguous "domain-not-found" answers. Consumers
1153
+ * with custom backend error vocabularies extend via
1154
+ * `options.permanentErrorCodes`.
1155
+ *
1156
+ * Distinct from the `RECOVERABLE_CODES` set in `lib/errors.ts`: that one
1157
+ * classifies iap-internal `IAPErrorCode`s for transport/storage retry
1158
+ * semantics; this one classifies opaque error strings the consumer's
1159
+ * backend returns in `valid:false` responses.
1160
+ */
1161
+ declare const DEFAULT_PERMANENT_ERROR_CODES: readonly string[];
1162
+
1163
+ /**
1164
+ * Library version. Updated by the publish workflow to match `package.json`.
1165
+ * Read at runtime by the logger so error reports include the version.
1166
+ */
1167
+ declare const VERSION = "0.1.0";
1168
+
1101
1169
  interface HttpBackendAdapterOptions {
1102
1170
  baseUrl: string;
1103
1171
  endpoints: {
@@ -1154,4 +1222,4 @@ declare class HttpBackendAdapter<TEntitlement extends EntitlementBase = Entitlem
1154
1222
  listProducts(): Promise<ConfiguredProduct[]>;
1155
1223
  }
1156
1224
 
1157
- 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 };
1225
+ export { type AppUserId, 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 Unsubscribe, VERSION, type VerifiedTransaction, type VerifyAppleRequest, type VerifyGoogleRequest, type VerifyResponse, createIAP, errorHint, isIAPError };
package/dist/index.js CHANGED
@@ -728,6 +728,19 @@ var optionsConfigSchema = z.object({
728
728
  * Excess entries stay in storage and are processed on subsequent launches.
729
729
  */
730
730
  recoveryMaxBatch: z.number().int().positive().default(50),
731
+ /**
732
+ * List of backend `valid:false` error codes that recovery should treat as
733
+ * permanent — entries with a matching error are removed from storage
734
+ * instead of retried on every launch. When omitted (the default), iap
735
+ * uses `DEFAULT_PERMANENT_ERROR_CODES` (`['TRANSACTION_NOT_FOUND',
736
+ * 'PRODUCT_MISMATCH']`).
737
+ *
738
+ * REPLACES the default when provided — pass `[...DEFAULT_PERMANENT_ERROR_CODES,
739
+ * 'YOUR_CODE']` to extend, or `[]` to disable the feature entirely
740
+ * (revert to retry-forever behavior). Export the constant from
741
+ * `@nosslabs/iap` for the spread form.
742
+ */
743
+ permanentErrorCodes: z.array(z.string()).optional(),
731
744
  productPriceCacheTtlMs: z.number().int().positive().default(24 * 60 * 60 * 1e3),
732
745
  logLevel: z.enum(["silent", "error", "warn", "info", "debug"]).default("info"),
733
746
  logger: z.unknown().optional()
@@ -1376,6 +1389,10 @@ async function resolveAppUserId(supply, ctx) {
1376
1389
  }
1377
1390
 
1378
1391
  // src/core/recovery-flow.ts
1392
+ var DEFAULT_PERMANENT_ERROR_CODES = [
1393
+ "TRANSACTION_NOT_FOUND",
1394
+ "PRODUCT_MISMATCH"
1395
+ ];
1379
1396
  var RecoveryOrchestrator = class {
1380
1397
  constructor(deps) {
1381
1398
  this.deps = deps;
@@ -1385,7 +1402,7 @@ var RecoveryOrchestrator = class {
1385
1402
  const { unfinished, logger, maxBatch } = this.deps;
1386
1403
  const allEntries = await unfinished.list();
1387
1404
  if (allEntries.length === 0) {
1388
- return { recovered: 0, failures: 0, inspected: 0 };
1405
+ return { recovered: 0, failures: 0, droppedPermanent: 0, inspected: 0 };
1389
1406
  }
1390
1407
  const entries = allEntries.slice(0, maxBatch);
1391
1408
  if (allEntries.length > maxBatch) {
@@ -1398,6 +1415,7 @@ var RecoveryOrchestrator = class {
1398
1415
  const settled = await Promise.allSettled(entries.map((entry) => this.processEntry(entry)));
1399
1416
  let recovered = 0;
1400
1417
  let failures = 0;
1418
+ let droppedPermanent = 0;
1401
1419
  let latestEntitlements = null;
1402
1420
  for (const result of settled) {
1403
1421
  if (result.status === "rejected") {
@@ -1407,6 +1425,8 @@ var RecoveryOrchestrator = class {
1407
1425
  if (result.value.kind === "recovered") {
1408
1426
  recovered += 1;
1409
1427
  latestEntitlements = result.value.entitlements;
1428
+ } else if (result.value.kind === "dropped-permanent") {
1429
+ droppedPermanent += 1;
1410
1430
  } else {
1411
1431
  failures += 1;
1412
1432
  }
@@ -1415,19 +1435,47 @@ var RecoveryOrchestrator = class {
1415
1435
  await this.applyEntitlements(latestEntitlements);
1416
1436
  }
1417
1437
  logger.debug(
1418
- `Recovery: ${recovered} recovered, ${failures} left in list (will retry next launch).`
1438
+ `Recovery: ${recovered} recovered, ${droppedPermanent} dropped (permanent), ${failures} left in list (will retry next launch).`
1419
1439
  );
1420
- return { recovered, failures, inspected: entries.length };
1440
+ return { recovered, failures, droppedPermanent, inspected: entries.length };
1421
1441
  }
1422
1442
  async processEntry(entry) {
1423
- const { nativeAdapter, unfinished, logger } = this.deps;
1443
+ const { nativeAdapter, unfinished, logger, emitter, permanentErrorCodes } = this.deps;
1424
1444
  const tx = entryToNativeTransaction(entry);
1425
1445
  const tokenLabel = maskToken(entry.token);
1426
1446
  try {
1427
1447
  const response = await verifyNativeTransaction(this.deps.backend, tx);
1428
1448
  if (!response.valid) {
1449
+ if (permanentErrorCodes.has(response.error)) {
1450
+ logger.info(
1451
+ `Recovery: dropping permanently-invalid token=${tokenLabel} productId=${entry.productId} (${response.error}).`
1452
+ );
1453
+ try {
1454
+ await nativeAdapter.acknowledge(tx);
1455
+ } catch (error) {
1456
+ logger.warn(
1457
+ `Recovery: best-effort acknowledge() failed for productId=${entry.productId}; proceeding with removal anyway.`,
1458
+ error
1459
+ );
1460
+ }
1461
+ try {
1462
+ await unfinished.remove(entry.token);
1463
+ } catch (error) {
1464
+ logger.warn(
1465
+ `Recovery: unfinished.remove() failed for productId=${entry.productId} after permanent classification; will dedupe on next launch.`,
1466
+ error
1467
+ );
1468
+ }
1469
+ emitter.emit("recovery-dropped-permanent", {
1470
+ productId: entry.productId,
1471
+ token: entry.token,
1472
+ error: response.error,
1473
+ ...response.message !== void 0 ? { message: response.message } : {}
1474
+ });
1475
+ return { kind: "dropped-permanent" };
1476
+ }
1429
1477
  logger.debug(
1430
- `Recovery: backend rejected token=${tokenLabel} productId=${entry.productId} (${response.error}); leaving in list.`
1478
+ `Recovery: backend rejected token=${tokenLabel} productId=${entry.productId} (${response.error}); leaving in list for retry.`
1431
1479
  );
1432
1480
  return { kind: "failed" };
1433
1481
  }
@@ -1878,7 +1926,10 @@ function createIAP(input) {
1878
1926
  state.restorer = new RestoreOrchestrator(sharedDeps);
1879
1927
  state.recoverer = new RecoveryOrchestrator({
1880
1928
  ...sharedDeps,
1881
- maxBatch: state.config.options.recoveryMaxBatch
1929
+ maxBatch: state.config.options.recoveryMaxBatch,
1930
+ permanentErrorCodes: new Set(
1931
+ state.config.options.permanentErrorCodes ?? DEFAULT_PERMANENT_ERROR_CODES
1932
+ )
1882
1933
  });
1883
1934
  try {
1884
1935
  await state.adapter.isAvailable();
@@ -2069,6 +2120,6 @@ init_errors();
2069
2120
  // src/version.ts
2070
2121
  var VERSION = "0.1.0";
2071
2122
 
2072
- export { HttpBackendAdapter, HttpClient, IAPError, IAPErrorCode, VERSION, createIAP, errorHint, isIAPError };
2123
+ export { DEFAULT_PERMANENT_ERROR_CODES, HttpBackendAdapter, HttpClient, IAPError, IAPErrorCode, VERSION, createIAP, errorHint, isIAPError };
2073
2124
  //# sourceMappingURL=index.js.map
2074
2125
  //# sourceMappingURL=index.js.map