expo-iap 3.0.3 → 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 +7 -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 +8 -6
  8. package/build/index.d.ts.map +1 -1
  9. package/build/index.js +4 -2
  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 +5 -4
  28. package/build/useIAP.d.ts.map +1 -1
  29. package/build/useIAP.js +1 -1
  30. package/build/useIAP.js.map +1 -1
  31. package/build/utils/errorMapping.d.ts +1 -1
  32. package/build/utils/errorMapping.d.ts.map +1 -1
  33. package/build/utils/errorMapping.js +19 -3
  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 +14 -14
  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 +8 -8
  44. package/src/utils/errorMapping.ts +24 -3
  45. package/build/ExpoIap.types.d.ts +0 -307
  46. package/build/ExpoIap.types.d.ts.map +0 -1
  47. package/build/ExpoIap.types.js +0 -235
  48. package/build/ExpoIap.types.js.map +0 -1
  49. package/build/types/ExpoIapAndroid.types.d.ts +0 -114
  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 -149
  54. package/build/types/ExpoIapIOS.types.d.ts.map +0 -1
  55. package/build/types/ExpoIapIOS.types.js +0 -8
  56. package/build/types/ExpoIapIOS.types.js.map +0 -1
  57. package/src/ExpoIap.types.ts +0 -444
  58. package/src/types/ExpoIapAndroid.types.ts +0 -133
  59. package/src/types/ExpoIapIOS.types.ts +0 -172
@@ -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
+ };