expo-iap 3.0.2 → 3.0.4

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/CLAUDE.md +2 -0
  3. package/build/helpers/subscription.d.ts +1 -12
  4. package/build/helpers/subscription.d.ts.map +1 -1
  5. package/build/helpers/subscription.js +12 -7
  6. package/build/helpers/subscription.js.map +1 -1
  7. package/build/index.d.ts +9 -7
  8. package/build/index.d.ts.map +1 -1
  9. package/build/index.js +6 -4
  10. package/build/index.js.map +1 -1
  11. package/build/modules/android.d.ts +7 -6
  12. package/build/modules/android.d.ts.map +1 -1
  13. package/build/modules/android.js +19 -4
  14. package/build/modules/android.js.map +1 -1
  15. package/build/modules/ios.d.ts +7 -10
  16. package/build/modules/ios.d.ts.map +1 -1
  17. package/build/modules/ios.js +3 -1
  18. package/build/modules/ios.js.map +1 -1
  19. package/build/purchase-error.d.ts +69 -0
  20. package/build/purchase-error.d.ts.map +1 -0
  21. package/build/purchase-error.js +164 -0
  22. package/build/purchase-error.js.map +1 -0
  23. package/build/types.d.ts +649 -0
  24. package/build/types.d.ts.map +1 -0
  25. package/build/types.js +100 -0
  26. package/build/types.js.map +1 -0
  27. package/build/useIAP.d.ts +6 -5
  28. package/build/useIAP.d.ts.map +1 -1
  29. package/build/useIAP.js +2 -3
  30. package/build/useIAP.js.map +1 -1
  31. package/build/utils/errorMapping.d.ts +10 -4
  32. package/build/utils/errorMapping.d.ts.map +1 -1
  33. package/build/utils/errorMapping.js +71 -47
  34. package/build/utils/errorMapping.js.map +1 -1
  35. package/jest.config.js +1 -1
  36. package/package.json +1 -1
  37. package/src/helpers/subscription.ts +12 -20
  38. package/src/index.ts +20 -20
  39. package/src/modules/android.ts +28 -10
  40. package/src/modules/ios.ts +11 -13
  41. package/src/purchase-error.ts +268 -0
  42. package/src/types.ts +738 -0
  43. package/src/useIAP.ts +15 -18
  44. package/src/utils/errorMapping.ts +89 -55
  45. package/build/ExpoIap.types.d.ts +0 -294
  46. package/build/ExpoIap.types.d.ts.map +0 -1
  47. package/build/ExpoIap.types.js +0 -226
  48. package/build/ExpoIap.types.js.map +0 -1
  49. package/build/types/ExpoIapAndroid.types.d.ts +0 -115
  50. package/build/types/ExpoIapAndroid.types.d.ts.map +0 -1
  51. package/build/types/ExpoIapAndroid.types.js +0 -29
  52. package/build/types/ExpoIapAndroid.types.js.map +0 -1
  53. package/build/types/ExpoIapIOS.types.d.ts +0 -143
  54. package/build/types/ExpoIapIOS.types.d.ts.map +0 -1
  55. package/build/types/ExpoIapIOS.types.js +0 -2
  56. package/build/types/ExpoIapIOS.types.js.map +0 -1
  57. package/src/ExpoIap.types.ts +0 -429
  58. package/src/types/ExpoIapAndroid.types.ts +0 -136
  59. package/src/types/ExpoIapIOS.types.ts +0 -167
package/jest.config.js CHANGED
@@ -35,7 +35,7 @@ module.exports = {
35
35
  ],
36
36
  coveragePathIgnorePatterns: [
37
37
  '<rootDir>/src/useIAP.ts',
38
- '<rootDir>/src/ExpoIap.types.ts',
38
+ '<rootDir>/src/types.ts',
39
39
  '<rootDir>/src/utils/constants.ts',
40
40
  ],
41
41
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-iap",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "In App Purchase module in Expo",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -1,18 +1,6 @@
1
1
  import {Platform} from 'react-native';
2
2
  import {getAvailablePurchases} from '../index';
3
-
4
- export interface ActiveSubscription {
5
- productId: string;
6
- isActive: boolean;
7
- transactionId: string; // Transaction identifier for backend validation
8
- purchaseToken?: string; // JWT token (iOS) or purchase token (Android) for backend validation
9
- transactionDate: number; // Transaction timestamp
10
- expirationDateIOS?: Date;
11
- autoRenewingAndroid?: boolean;
12
- environmentIOS?: string;
13
- willExpireSoon?: boolean;
14
- daysUntilExpirationIOS?: number;
15
- }
3
+ import type {ActiveSubscription} from '../types';
16
4
 
17
5
  /**
18
6
  * Get all active subscriptions with detailed information
@@ -91,8 +79,7 @@ export const getActiveSubscriptions = async (
91
79
  const subscription: ActiveSubscription = {
92
80
  productId: purchase.productId,
93
81
  isActive: true,
94
- // Use unified id as transaction identifier in v3
95
- transactionId: purchase.id,
82
+ transactionId: String(purchase.id),
96
83
  purchaseToken: purchase.purchaseToken,
97
84
  transactionDate: purchase.transactionDate,
98
85
  };
@@ -100,8 +87,7 @@ export const getActiveSubscriptions = async (
100
87
  // Add platform-specific details
101
88
  if (Platform.OS === 'ios') {
102
89
  if ('expirationDateIOS' in purchase && purchase.expirationDateIOS) {
103
- const expirationDate = new Date(purchase.expirationDateIOS);
104
- subscription.expirationDateIOS = expirationDate;
90
+ subscription.expirationDateIOS = purchase.expirationDateIOS;
105
91
 
106
92
  // Calculate days until expiration (round to nearest day)
107
93
  const daysUntilExpiration = Math.round(
@@ -112,13 +98,19 @@ export const getActiveSubscriptions = async (
112
98
  }
113
99
 
114
100
  if ('environmentIOS' in purchase) {
115
- subscription.environmentIOS = purchase.environmentIOS;
101
+ subscription.environmentIOS = purchase.environmentIOS ?? undefined;
116
102
  }
117
103
  } else if (Platform.OS === 'android') {
118
104
  if ('autoRenewingAndroid' in purchase) {
119
- subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;
105
+ if (typeof purchase.autoRenewingAndroid !== 'undefined') {
106
+ subscription.autoRenewingAndroid = purchase.autoRenewingAndroid;
107
+ }
120
108
  // If auto-renewing is false, subscription will expire soon
121
- subscription.willExpireSoon = !purchase.autoRenewingAndroid;
109
+ if (purchase.autoRenewingAndroid === false) {
110
+ subscription.willExpireSoon = true;
111
+ } else if (purchase.autoRenewingAndroid === true) {
112
+ subscription.willExpireSoon = false;
113
+ }
122
114
  }
123
115
  }
124
116
 
package/src/index.ts CHANGED
@@ -20,19 +20,20 @@ import {
20
20
  import {
21
21
  Product,
22
22
  Purchase,
23
- PurchaseError,
24
23
  ErrorCode,
25
- PurchaseResult,
26
- RequestSubscriptionProps,
27
24
  RequestPurchaseProps,
28
- SubscriptionProduct,
29
- // Bring platform types from the barrel to avoid deep imports
25
+ RequestSubscriptionPropsByPlatforms,
26
+ ProductSubscription,
30
27
  PurchaseAndroid,
31
- PaymentDiscount,
32
- } from './ExpoIap.types';
28
+ DiscountOfferInputIOS,
29
+ VoidResult,
30
+ ReceiptValidationResult,
31
+ } from './types';
32
+ import {PurchaseError} from './purchase-error';
33
33
 
34
34
  // Export all types
35
- export * from './ExpoIap.types';
35
+ export * from './types';
36
+ export {ErrorCodeUtils, ErrorCodeMapping} from './purchase-error';
36
37
  export * from './modules/android';
37
38
  export * from './modules/ios';
38
39
 
@@ -40,7 +41,6 @@ export * from './modules/ios';
40
41
  export {
41
42
  getActiveSubscriptions,
42
43
  hasActiveSubscriptions,
43
- type ActiveSubscription,
44
44
  } from './helpers/subscription';
45
45
 
46
46
  // Get the native constant value
@@ -169,11 +169,11 @@ export const fetchProducts = async ({
169
169
  }: {
170
170
  skus: string[];
171
171
  type?: 'inapp' | 'subs';
172
- }): Promise<Product[] | SubscriptionProduct[]> => {
172
+ }): Promise<Product[] | ProductSubscription[]> => {
173
173
  if (!skus?.length) {
174
174
  throw new PurchaseError({
175
175
  message: 'No SKUs provided',
176
- code: ErrorCode.E_EMPTY_SKU_LIST,
176
+ code: ErrorCode.EmptySkuList,
177
177
  });
178
178
  }
179
179
 
@@ -195,7 +195,7 @@ export const fetchProducts = async ({
195
195
 
196
196
  return type === 'inapp'
197
197
  ? (filteredItems as Product[])
198
- : (filteredItems as SubscriptionProduct[]);
198
+ : (filteredItems as ProductSubscription[]);
199
199
  }
200
200
 
201
201
  if (Platform.OS === 'android') {
@@ -213,7 +213,7 @@ export const fetchProducts = async ({
213
213
 
214
214
  return type === 'inapp'
215
215
  ? (filteredItems as Product[])
216
- : (filteredItems as SubscriptionProduct[]);
216
+ : (filteredItems as ProductSubscription[]);
217
217
  }
218
218
 
219
219
  throw new Error('Unsupported platform');
@@ -272,8 +272,8 @@ export const restorePurchases = async (
272
272
  };
273
273
 
274
274
  const offerToRecordIOS = (
275
- offer: PaymentDiscount | undefined,
276
- ): Record<keyof PaymentDiscount, string> | undefined => {
275
+ offer: DiscountOfferInputIOS | undefined,
276
+ ): Record<keyof DiscountOfferInputIOS, string> | undefined => {
277
277
  if (!offer) return undefined;
278
278
  return {
279
279
  identifier: offer.identifier,
@@ -291,7 +291,7 @@ type PurchaseRequest =
291
291
  type?: 'inapp';
292
292
  }
293
293
  | {
294
- request: RequestSubscriptionProps;
294
+ request: RequestSubscriptionPropsByPlatforms;
295
295
  type: 'subs';
296
296
  };
297
297
 
@@ -299,7 +299,7 @@ type PurchaseRequest =
299
299
  * Helper to normalize request props to platform-specific format
300
300
  */
301
301
  const normalizeRequestProps = (
302
- request: RequestPurchaseProps | RequestSubscriptionProps,
302
+ request: RequestPurchaseProps | RequestSubscriptionPropsByPlatforms,
303
303
  platform: 'ios' | 'android',
304
304
  ): any => {
305
305
  // Platform-specific format - directly return the appropriate platform data
@@ -443,7 +443,7 @@ export const finishTransaction = ({
443
443
  }: {
444
444
  purchase: Purchase;
445
445
  isConsumable?: boolean;
446
- }): Promise<PurchaseResult | boolean> => {
446
+ }): Promise<VoidResult | boolean> => {
447
447
  return (
448
448
  Platform.select({
449
449
  ios: async () => {
@@ -466,7 +466,7 @@ export const finishTransaction = ({
466
466
  return Promise.reject(
467
467
  new PurchaseError({
468
468
  message: 'Purchase token is required to finish transaction',
469
- code: 'E_DEVELOPER_ERROR' as ErrorCode,
469
+ code: ErrorCode.DeveloperError,
470
470
  productId: androidPurchase.productId,
471
471
  platform: 'android',
472
472
  }),
@@ -539,7 +539,7 @@ export const validateReceipt = async (
539
539
  accessToken: string;
540
540
  isSub?: boolean;
541
541
  },
542
- ): Promise<any> => {
542
+ ): Promise<ReceiptValidationResult> => {
543
543
  if (Platform.OS === 'ios') {
544
544
  return await validateReceiptIOS(sku);
545
545
  } else if (Platform.OS === 'android') {
@@ -5,17 +5,19 @@ import {Linking} from 'react-native';
5
5
  import ExpoIapModule from '../ExpoIapModule';
6
6
 
7
7
  // Types
8
- import type {PurchaseResult, ReceiptAndroid} from '../ExpoIap.types';
8
+ import type {ReceiptValidationResultAndroid, VoidResult} from '../types';
9
+ import {Platform as PurchasePlatform} from '../types';
9
10
 
10
11
  // Type guards
11
- export function isProductAndroid<T extends {platform?: string}>(
12
- item: unknown,
13
- ): item is T & {platform: 'android'} {
12
+ export function isProductAndroid<
13
+ T extends {platform?: string | PurchasePlatform},
14
+ >(item: unknown): item is T & {platform: PurchasePlatform.Android | 'android'} {
14
15
  return (
15
16
  item != null &&
16
17
  typeof item === 'object' &&
17
18
  'platform' in item &&
18
- item.platform === 'android'
19
+ ((item as any).platform === 'android' ||
20
+ (item as any).platform === PurchasePlatform.Android)
19
21
  );
20
22
  }
21
23
 
@@ -86,7 +88,7 @@ export const validateReceiptAndroid = async ({
86
88
  productToken: string;
87
89
  accessToken: string;
88
90
  isSub?: boolean;
89
- }): Promise<ReceiptAndroid> => {
91
+ }): Promise<ReceiptValidationResultAndroid> => {
90
92
  const type = isSub ? 'subscriptions' : 'products';
91
93
 
92
94
  const url =
@@ -115,14 +117,30 @@ export const validateReceiptAndroid = async ({
115
117
  * Acknowledge a product (on Android.) No-op on iOS.
116
118
  * @param {Object} params - The parameters object
117
119
  * @param {string} params.token - The product's token (on Android)
118
- * @returns {Promise<PurchaseResult | void>}
120
+ * @returns {Promise<VoidResult | void>}
119
121
  */
120
- export const acknowledgePurchaseAndroid = ({
122
+ export const acknowledgePurchaseAndroid = async ({
121
123
  token,
122
124
  }: {
123
125
  token: string;
124
- }): Promise<PurchaseResult | boolean | void> => {
125
- return ExpoIapModule.acknowledgePurchaseAndroid(token);
126
+ }): Promise<VoidResult | boolean | void> => {
127
+ const result = await ExpoIapModule.acknowledgePurchaseAndroid(token);
128
+
129
+ if (typeof result === 'boolean' || typeof result === 'undefined') {
130
+ return result;
131
+ }
132
+
133
+ if (result && typeof result === 'object') {
134
+ const record = result as Record<string, unknown>;
135
+ if (typeof record.success === 'boolean') {
136
+ return {success: record.success};
137
+ }
138
+ if (typeof record.responseCode === 'number') {
139
+ return {success: record.responseCode === 0};
140
+ }
141
+ }
142
+
143
+ return {success: true};
126
144
  };
127
145
 
128
146
  /**
@@ -8,10 +8,12 @@ import ExpoIapModule from '../ExpoIapModule';
8
8
  import type {
9
9
  Product,
10
10
  Purchase,
11
- PurchaseError,
12
11
  SubscriptionStatusIOS,
13
- AppTransactionIOS,
14
- } from '../ExpoIap.types';
12
+ AppTransaction,
13
+ ReceiptValidationResultIOS,
14
+ } from '../types';
15
+ import type {PurchaseError} from '../purchase-error';
16
+ import {Platform as PurchasePlatform} from '../types';
15
17
  import {Linking} from 'react-native';
16
18
 
17
19
  export type TransactionEvent = {
@@ -22,14 +24,15 @@ export type TransactionEvent = {
22
24
  // Listeners
23
25
 
24
26
  // Type guards
25
- export function isProductIOS<T extends {platform?: string}>(
27
+ export function isProductIOS<T extends {platform?: string | PurchasePlatform}>(
26
28
  item: unknown,
27
- ): item is T & {platform: 'ios'} {
29
+ ): item is T & {platform: PurchasePlatform.Ios | 'ios'} {
28
30
  return (
29
31
  item != null &&
30
32
  typeof item === 'object' &&
31
33
  'platform' in item &&
32
- item.platform === 'ios'
34
+ ((item as any).platform === 'ios' ||
35
+ (item as any).platform === PurchasePlatform.Ios)
33
36
  );
34
37
  }
35
38
 
@@ -191,12 +194,7 @@ export const getTransactionJwsIOS = (sku: string): Promise<string> => {
191
194
  */
192
195
  export const validateReceiptIOS = async (
193
196
  sku: string,
194
- ): Promise<{
195
- isValid: boolean;
196
- receiptData: string;
197
- jwsRepresentation: string;
198
- latestTransaction?: Purchase;
199
- }> => {
197
+ ): Promise<ReceiptValidationResultIOS> => {
200
198
  const result = await ExpoIapModule.validateReceiptIOS(sku);
201
199
  return result;
202
200
  };
@@ -230,7 +228,7 @@ export const presentCodeRedemptionSheetIOS = (): Promise<boolean> => {
230
228
  * @platform iOS
231
229
  * @since iOS 16.0
232
230
  */
233
- export const getAppTransactionIOS = (): Promise<AppTransactionIOS | null> => {
231
+ export const getAppTransactionIOS = (): Promise<AppTransaction | null> => {
234
232
  return ExpoIapModule.getAppTransactionIOS();
235
233
  };
236
234
 
@@ -0,0 +1,268 @@
1
+ import {NATIVE_ERROR_CODES} from './ExpoIapModule';
2
+ import {ErrorCode, Platform} from './types';
3
+
4
+ /** Platform identifiers supported by {@link PurchaseError}. */
5
+ export type PurchaseErrorPlatform = Platform | 'ios' | 'android';
6
+
7
+ /** Properties used to construct a {@link PurchaseError}. */
8
+ export interface PurchaseErrorProps {
9
+ message: string;
10
+ responseCode?: number;
11
+ debugMessage?: string;
12
+ code?: ErrorCode;
13
+ productId?: string;
14
+ platform?: PurchaseErrorPlatform;
15
+ }
16
+
17
+ /** Shape of raw platform error objects coming from native modules. */
18
+ type PlatformErrorData = {
19
+ code?: string | number;
20
+ message?: string;
21
+ responseCode?: number;
22
+ debugMessage?: string;
23
+ productId?: string;
24
+ };
25
+
26
+ const toStandardizedCode = (errorCode: ErrorCode): string =>
27
+ errorCode.startsWith('E_') ? errorCode : `E_${errorCode}`;
28
+
29
+ const normalizePlatform = (
30
+ platform: PurchaseErrorPlatform,
31
+ ): 'ios' | 'android' =>
32
+ platform === Platform.Ios || platform === 'ios' ? 'ios' : 'android';
33
+
34
+ const OPENIAP_ERROR_CODE_SET: Set<string> = new Set(
35
+ Object.values(ErrorCode).map((code) => toStandardizedCode(code)),
36
+ );
37
+
38
+ const COMMON_ERROR_CODE_MAP: Record<ErrorCode, string> = {
39
+ [ErrorCode.Unknown]: toStandardizedCode(ErrorCode.Unknown),
40
+ [ErrorCode.UserCancelled]: toStandardizedCode(ErrorCode.UserCancelled),
41
+ [ErrorCode.UserError]: toStandardizedCode(ErrorCode.UserError),
42
+ [ErrorCode.ItemUnavailable]: toStandardizedCode(ErrorCode.ItemUnavailable),
43
+ [ErrorCode.RemoteError]: toStandardizedCode(ErrorCode.RemoteError),
44
+ [ErrorCode.NetworkError]: toStandardizedCode(ErrorCode.NetworkError),
45
+ [ErrorCode.ServiceError]: toStandardizedCode(ErrorCode.ServiceError),
46
+ [ErrorCode.ReceiptFailed]: toStandardizedCode(ErrorCode.ReceiptFailed),
47
+ [ErrorCode.ReceiptFinished]: toStandardizedCode(ErrorCode.ReceiptFinished),
48
+ [ErrorCode.ReceiptFinishedFailed]: toStandardizedCode(
49
+ ErrorCode.ReceiptFinishedFailed,
50
+ ),
51
+ [ErrorCode.NotPrepared]: toStandardizedCode(ErrorCode.NotPrepared),
52
+ [ErrorCode.NotEnded]: toStandardizedCode(ErrorCode.NotEnded),
53
+ [ErrorCode.AlreadyOwned]: toStandardizedCode(ErrorCode.AlreadyOwned),
54
+ [ErrorCode.DeveloperError]: toStandardizedCode(ErrorCode.DeveloperError),
55
+ [ErrorCode.BillingResponseJsonParseError]: toStandardizedCode(
56
+ ErrorCode.BillingResponseJsonParseError,
57
+ ),
58
+ [ErrorCode.DeferredPayment]: toStandardizedCode(ErrorCode.DeferredPayment),
59
+ [ErrorCode.Interrupted]: toStandardizedCode(ErrorCode.Interrupted),
60
+ [ErrorCode.IapNotAvailable]: toStandardizedCode(ErrorCode.IapNotAvailable),
61
+ [ErrorCode.PurchaseError]: toStandardizedCode(ErrorCode.PurchaseError),
62
+ [ErrorCode.SyncError]: toStandardizedCode(ErrorCode.SyncError),
63
+ [ErrorCode.TransactionValidationFailed]: toStandardizedCode(
64
+ ErrorCode.TransactionValidationFailed,
65
+ ),
66
+ [ErrorCode.ActivityUnavailable]: toStandardizedCode(
67
+ ErrorCode.ActivityUnavailable,
68
+ ),
69
+ [ErrorCode.AlreadyPrepared]: toStandardizedCode(ErrorCode.AlreadyPrepared),
70
+ [ErrorCode.Pending]: toStandardizedCode(ErrorCode.Pending),
71
+ [ErrorCode.ConnectionClosed]: toStandardizedCode(ErrorCode.ConnectionClosed),
72
+ [ErrorCode.InitConnection]: toStandardizedCode(ErrorCode.InitConnection),
73
+ [ErrorCode.ServiceDisconnected]: toStandardizedCode(
74
+ ErrorCode.ServiceDisconnected,
75
+ ),
76
+ [ErrorCode.QueryProduct]: toStandardizedCode(ErrorCode.QueryProduct),
77
+ [ErrorCode.SkuNotFound]: toStandardizedCode(ErrorCode.SkuNotFound),
78
+ [ErrorCode.SkuOfferMismatch]: toStandardizedCode(ErrorCode.SkuOfferMismatch),
79
+ [ErrorCode.ItemNotOwned]: toStandardizedCode(ErrorCode.ItemNotOwned),
80
+ [ErrorCode.BillingUnavailable]: toStandardizedCode(
81
+ ErrorCode.BillingUnavailable,
82
+ ),
83
+ [ErrorCode.FeatureNotSupported]: toStandardizedCode(
84
+ ErrorCode.FeatureNotSupported,
85
+ ),
86
+ [ErrorCode.EmptySkuList]: toStandardizedCode(ErrorCode.EmptySkuList),
87
+ };
88
+
89
+ /**
90
+ * Mapping between platforms and their canonical error codes.
91
+ * Values are platform-native string identifiers.
92
+ */
93
+ export const ErrorCodeMapping = {
94
+ ios: COMMON_ERROR_CODE_MAP,
95
+ android: COMMON_ERROR_CODE_MAP,
96
+ } as const;
97
+
98
+ /**
99
+ * Error thrown by expo-iap when purchases fail.
100
+ */
101
+ export class PurchaseError extends Error {
102
+ public responseCode?: number;
103
+ public debugMessage?: string;
104
+ public code?: ErrorCode;
105
+ public productId?: string;
106
+ public platform?: PurchaseErrorPlatform;
107
+
108
+ constructor(
109
+ message: string,
110
+ responseCode?: number,
111
+ debugMessage?: string,
112
+ code?: ErrorCode,
113
+ productId?: string,
114
+ platform?: PurchaseErrorPlatform,
115
+ );
116
+ constructor(props: PurchaseErrorProps);
117
+ constructor(
118
+ messageOrProps: string | PurchaseErrorProps,
119
+ responseCode?: number,
120
+ debugMessage?: string,
121
+ code?: ErrorCode,
122
+ productId?: string,
123
+ platform?: PurchaseErrorPlatform,
124
+ ) {
125
+ super(
126
+ typeof messageOrProps === 'string'
127
+ ? messageOrProps
128
+ : messageOrProps.message,
129
+ );
130
+ this.name = '[expo-iap]: PurchaseError';
131
+ Object.setPrototypeOf(this, new.target.prototype);
132
+
133
+ if (typeof messageOrProps === 'string') {
134
+ this.responseCode = responseCode;
135
+ this.debugMessage = debugMessage;
136
+ this.code = code;
137
+ this.productId = productId;
138
+ this.platform = platform;
139
+ } else {
140
+ this.responseCode = messageOrProps.responseCode;
141
+ this.debugMessage = messageOrProps.debugMessage;
142
+ this.code = messageOrProps.code;
143
+ this.productId = messageOrProps.productId;
144
+ this.platform = messageOrProps.platform;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Create a {@link PurchaseError} from raw platform error data.
150
+ */
151
+ static fromPlatformError(
152
+ errorData: PlatformErrorData,
153
+ platform: PurchaseErrorPlatform,
154
+ ): PurchaseError {
155
+ const normalizedPlatform = normalizePlatform(platform);
156
+
157
+ const errorCode = errorData.code
158
+ ? ErrorCodeUtils.fromPlatformCode(errorData.code, normalizedPlatform)
159
+ : ErrorCode.Unknown;
160
+
161
+ return new PurchaseError({
162
+ message: errorData.message ?? 'Unknown error occurred',
163
+ responseCode: errorData.responseCode,
164
+ debugMessage: errorData.debugMessage,
165
+ code: errorCode,
166
+ productId: errorData.productId,
167
+ platform,
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Returns the platform specific error code for this instance.
173
+ */
174
+ getPlatformCode(): string | number | undefined {
175
+ if (!this.code || !this.platform) {
176
+ return undefined;
177
+ }
178
+ return ErrorCodeUtils.toPlatformCode(this.code, this.platform);
179
+ }
180
+ }
181
+
182
+ /** Utility helpers for translating error codes between platforms. */
183
+ export const ErrorCodeUtils = {
184
+ /**
185
+ * Returns the native error code for the provided {@link ErrorCode}.
186
+ */
187
+ getNativeErrorCode: (errorCode: ErrorCode): string => {
188
+ const standardized = toStandardizedCode(errorCode);
189
+ return (
190
+ (NATIVE_ERROR_CODES as Record<string, string | undefined>)[
191
+ standardized
192
+ ] ?? standardized
193
+ );
194
+ },
195
+ /**
196
+ * Converts a platform-specific error code into a standardized {@link ErrorCode}.
197
+ */
198
+ fromPlatformCode: (
199
+ platformCode: string | number,
200
+ _platform: PurchaseErrorPlatform,
201
+ ): ErrorCode => {
202
+ if (typeof platformCode === 'string' && platformCode.startsWith('E_')) {
203
+ if (OPENIAP_ERROR_CODE_SET.has(platformCode)) {
204
+ const match = Object.entries(COMMON_ERROR_CODE_MAP).find(
205
+ ([, value]) => value === platformCode,
206
+ );
207
+ if (match) {
208
+ return match[0] as ErrorCode;
209
+ }
210
+ }
211
+ }
212
+
213
+ for (const [standardized, nativeCode] of Object.entries(
214
+ (NATIVE_ERROR_CODES || {}) as Record<string, string | number>,
215
+ )) {
216
+ if (
217
+ nativeCode === platformCode &&
218
+ OPENIAP_ERROR_CODE_SET.has(standardized)
219
+ ) {
220
+ const match = Object.entries(COMMON_ERROR_CODE_MAP).find(
221
+ ([, mappedCode]) => mappedCode === standardized,
222
+ );
223
+ if (match) {
224
+ return match[0] as ErrorCode;
225
+ }
226
+ }
227
+ }
228
+
229
+ for (const [errorCode, mappedCode] of Object.entries(
230
+ COMMON_ERROR_CODE_MAP,
231
+ )) {
232
+ if (mappedCode === platformCode) {
233
+ return errorCode as ErrorCode;
234
+ }
235
+ }
236
+
237
+ return ErrorCode.Unknown;
238
+ },
239
+ /**
240
+ * Converts a standardized {@link ErrorCode} into its platform-specific value.
241
+ */
242
+ toPlatformCode: (
243
+ errorCode: ErrorCode,
244
+ _platform: PurchaseErrorPlatform,
245
+ ): string | number => {
246
+ const standardized = toStandardizedCode(errorCode);
247
+ const native = (NATIVE_ERROR_CODES as Record<string, string | number>)[
248
+ standardized
249
+ ];
250
+ return native ?? COMMON_ERROR_CODE_MAP[errorCode] ?? 'E_UNKNOWN';
251
+ },
252
+ /**
253
+ * Determines whether the error code is supported on the given platform.
254
+ */
255
+ isValidForPlatform: (
256
+ errorCode: ErrorCode,
257
+ platform: PurchaseErrorPlatform,
258
+ ): boolean => {
259
+ const standardized = toStandardizedCode(errorCode);
260
+ if (
261
+ (NATIVE_ERROR_CODES as Record<string, unknown>)[standardized] !==
262
+ undefined
263
+ ) {
264
+ return true;
265
+ }
266
+ return standardized in ErrorCodeMapping[normalizePlatform(platform)];
267
+ },
268
+ };