expo-iap 3.0.7 → 3.1.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.
Files changed (52) hide show
  1. package/CLAUDE.md +14 -2
  2. package/CONTRIBUTING.md +19 -0
  3. package/README.md +18 -6
  4. package/android/build.gradle +24 -1
  5. package/android/src/main/java/expo/modules/iap/ExpoIapLog.kt +69 -0
  6. package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +190 -59
  7. package/build/index.d.ts +32 -111
  8. package/build/index.d.ts.map +1 -1
  9. package/build/index.js +198 -243
  10. package/build/index.js.map +1 -1
  11. package/build/modules/android.d.ts +7 -12
  12. package/build/modules/android.d.ts.map +1 -1
  13. package/build/modules/android.js +15 -12
  14. package/build/modules/android.js.map +1 -1
  15. package/build/modules/ios.d.ts +35 -36
  16. package/build/modules/ios.d.ts.map +1 -1
  17. package/build/modules/ios.js +101 -35
  18. package/build/modules/ios.js.map +1 -1
  19. package/build/types.d.ts +107 -82
  20. package/build/types.d.ts.map +1 -1
  21. package/build/types.js +1 -0
  22. package/build/types.js.map +1 -1
  23. package/build/useIAP.d.ts +7 -12
  24. package/build/useIAP.d.ts.map +1 -1
  25. package/build/useIAP.js +49 -23
  26. package/build/useIAP.js.map +1 -1
  27. package/build/utils/errorMapping.d.ts +32 -23
  28. package/build/utils/errorMapping.d.ts.map +1 -1
  29. package/build/utils/errorMapping.js +117 -22
  30. package/build/utils/errorMapping.js.map +1 -1
  31. package/ios/ExpoIap.podspec +3 -2
  32. package/ios/ExpoIapHelper.swift +96 -0
  33. package/ios/ExpoIapLog.swift +127 -0
  34. package/ios/ExpoIapModule.swift +218 -340
  35. package/openiap-versions.json +5 -0
  36. package/package.json +2 -2
  37. package/plugin/build/withIAP.js +6 -4
  38. package/plugin/src/withIAP.ts +14 -4
  39. package/scripts/update-types.mjs +20 -1
  40. package/src/index.ts +280 -356
  41. package/src/modules/android.ts +25 -23
  42. package/src/modules/ios.ts +138 -48
  43. package/src/types.ts +139 -91
  44. package/src/useIAP.ts +91 -58
  45. package/src/utils/errorMapping.ts +203 -23
  46. package/.copilot-instructions.md +0 -321
  47. package/.cursorrules +0 -321
  48. package/build/purchase-error.d.ts +0 -67
  49. package/build/purchase-error.d.ts.map +0 -1
  50. package/build/purchase-error.js +0 -166
  51. package/build/purchase-error.js.map +0 -1
  52. package/src/purchase-error.ts +0 -265
@@ -1,9 +1,209 @@
1
1
  /**
2
- * Error mapping utilities for expo-iap
3
- * Provides helper functions for handling platform-specific errors
2
+ * Error mapping utilities for expo-iap.
3
+ * Provides helpers for working with platform-specific error codes
4
+ * and constructing structured purchase errors.
4
5
  */
5
6
 
6
- import {ErrorCode} from '../types';
7
+ import {NATIVE_ERROR_CODES} from '../ExpoIapModule';
8
+ import {ErrorCode, IapPlatform} from '../types';
9
+
10
+ export interface PurchaseErrorProps {
11
+ message: string;
12
+ responseCode?: number;
13
+ debugMessage?: string;
14
+ code?: ErrorCode;
15
+ productId?: string;
16
+ platform?: IapPlatform;
17
+ }
18
+
19
+ type PlatformErrorData = {
20
+ code?: string | number;
21
+ message?: string;
22
+ responseCode?: number;
23
+ debugMessage?: string;
24
+ productId?: string;
25
+ };
26
+
27
+ export type PurchaseError = Error & PurchaseErrorProps;
28
+
29
+ const toStandardizedCode = (errorCode: ErrorCode): string =>
30
+ errorCode.startsWith('E_') ? errorCode : `E_${errorCode}`;
31
+
32
+ const normalizePlatform = (platform: IapPlatform): 'ios' | 'android' =>
33
+ typeof platform === 'string' && platform.toLowerCase() === 'ios'
34
+ ? 'ios'
35
+ : 'android';
36
+
37
+ const COMMON_ERROR_CODE_MAP: Record<ErrorCode, string> = {
38
+ [ErrorCode.Unknown]: toStandardizedCode(ErrorCode.Unknown),
39
+ [ErrorCode.UserCancelled]: toStandardizedCode(ErrorCode.UserCancelled),
40
+ [ErrorCode.UserError]: toStandardizedCode(ErrorCode.UserError),
41
+ [ErrorCode.ItemUnavailable]: toStandardizedCode(ErrorCode.ItemUnavailable),
42
+ [ErrorCode.RemoteError]: toStandardizedCode(ErrorCode.RemoteError),
43
+ [ErrorCode.NetworkError]: toStandardizedCode(ErrorCode.NetworkError),
44
+ [ErrorCode.ServiceError]: toStandardizedCode(ErrorCode.ServiceError),
45
+ [ErrorCode.ReceiptFailed]: toStandardizedCode(ErrorCode.ReceiptFailed),
46
+ [ErrorCode.ReceiptFinished]: toStandardizedCode(ErrorCode.ReceiptFinished),
47
+ [ErrorCode.ReceiptFinishedFailed]: toStandardizedCode(
48
+ ErrorCode.ReceiptFinishedFailed,
49
+ ),
50
+ [ErrorCode.NotPrepared]: toStandardizedCode(ErrorCode.NotPrepared),
51
+ [ErrorCode.NotEnded]: toStandardizedCode(ErrorCode.NotEnded),
52
+ [ErrorCode.AlreadyOwned]: toStandardizedCode(ErrorCode.AlreadyOwned),
53
+ [ErrorCode.DeveloperError]: toStandardizedCode(ErrorCode.DeveloperError),
54
+ [ErrorCode.BillingResponseJsonParseError]: toStandardizedCode(
55
+ ErrorCode.BillingResponseJsonParseError,
56
+ ),
57
+ [ErrorCode.DeferredPayment]: toStandardizedCode(ErrorCode.DeferredPayment),
58
+ [ErrorCode.Interrupted]: toStandardizedCode(ErrorCode.Interrupted),
59
+ [ErrorCode.IapNotAvailable]: toStandardizedCode(ErrorCode.IapNotAvailable),
60
+ [ErrorCode.PurchaseError]: toStandardizedCode(ErrorCode.PurchaseError),
61
+ [ErrorCode.SyncError]: toStandardizedCode(ErrorCode.SyncError),
62
+ [ErrorCode.TransactionValidationFailed]: toStandardizedCode(
63
+ ErrorCode.TransactionValidationFailed,
64
+ ),
65
+ [ErrorCode.ActivityUnavailable]: toStandardizedCode(
66
+ ErrorCode.ActivityUnavailable,
67
+ ),
68
+ [ErrorCode.AlreadyPrepared]: toStandardizedCode(ErrorCode.AlreadyPrepared),
69
+ [ErrorCode.Pending]: toStandardizedCode(ErrorCode.Pending),
70
+ [ErrorCode.ConnectionClosed]: toStandardizedCode(ErrorCode.ConnectionClosed),
71
+ [ErrorCode.InitConnection]: toStandardizedCode(ErrorCode.InitConnection),
72
+ [ErrorCode.ServiceDisconnected]: toStandardizedCode(
73
+ ErrorCode.ServiceDisconnected,
74
+ ),
75
+ [ErrorCode.QueryProduct]: toStandardizedCode(ErrorCode.QueryProduct),
76
+ [ErrorCode.SkuNotFound]: toStandardizedCode(ErrorCode.SkuNotFound),
77
+ [ErrorCode.SkuOfferMismatch]: toStandardizedCode(ErrorCode.SkuOfferMismatch),
78
+ [ErrorCode.ItemNotOwned]: toStandardizedCode(ErrorCode.ItemNotOwned),
79
+ [ErrorCode.BillingUnavailable]: toStandardizedCode(
80
+ ErrorCode.BillingUnavailable,
81
+ ),
82
+ [ErrorCode.FeatureNotSupported]: toStandardizedCode(
83
+ ErrorCode.FeatureNotSupported,
84
+ ),
85
+ [ErrorCode.EmptySkuList]: toStandardizedCode(ErrorCode.EmptySkuList),
86
+ };
87
+
88
+ export const ErrorCodeMapping = {
89
+ ios: COMMON_ERROR_CODE_MAP,
90
+ android: COMMON_ERROR_CODE_MAP,
91
+ } as const;
92
+
93
+ const OPENIAP_ERROR_CODE_SET: Set<string> = new Set(
94
+ Object.values(ErrorCode).map((code) => toStandardizedCode(code)),
95
+ );
96
+
97
+ export const createPurchaseError = (
98
+ props: PurchaseErrorProps,
99
+ ): PurchaseError => {
100
+ const error = new Error(props.message) as PurchaseError;
101
+ error.name = '[expo-iap]: PurchaseError';
102
+ error.responseCode = props.responseCode;
103
+ error.debugMessage = props.debugMessage;
104
+ error.code = props.code;
105
+ error.productId = props.productId;
106
+ error.platform = props.platform;
107
+ return error;
108
+ };
109
+
110
+ export const createPurchaseErrorFromPlatform = (
111
+ errorData: PlatformErrorData,
112
+ platform: IapPlatform,
113
+ ): PurchaseError => {
114
+ const normalizedPlatform = normalizePlatform(platform);
115
+ const errorCode = errorData.code
116
+ ? ErrorCodeUtils.fromPlatformCode(errorData.code, normalizedPlatform)
117
+ : ErrorCode.Unknown;
118
+
119
+ return createPurchaseError({
120
+ message: errorData.message ?? 'Unknown error occurred',
121
+ responseCode: errorData.responseCode,
122
+ debugMessage: errorData.debugMessage,
123
+ code: errorCode,
124
+ productId: errorData.productId,
125
+ platform,
126
+ });
127
+ };
128
+
129
+ export const ErrorCodeUtils = {
130
+ getNativeErrorCode: (errorCode: ErrorCode): string => {
131
+ const standardized = toStandardizedCode(errorCode);
132
+ return (
133
+ (NATIVE_ERROR_CODES as Record<string, string | undefined>)[
134
+ standardized
135
+ ] ?? standardized
136
+ );
137
+ },
138
+ fromPlatformCode: (
139
+ platformCode: string | number,
140
+ _platform: IapPlatform,
141
+ ): ErrorCode => {
142
+ if (typeof platformCode === 'string' && platformCode.startsWith('E_')) {
143
+ if (OPENIAP_ERROR_CODE_SET.has(platformCode)) {
144
+ const match = Object.entries(COMMON_ERROR_CODE_MAP).find(
145
+ ([, value]) => value === platformCode,
146
+ );
147
+ if (match) {
148
+ return match[0] as ErrorCode;
149
+ }
150
+ }
151
+ }
152
+
153
+ for (const [standardized, nativeCode] of Object.entries(
154
+ (NATIVE_ERROR_CODES || {}) as Record<string, string | number>,
155
+ )) {
156
+ if (
157
+ nativeCode === platformCode &&
158
+ OPENIAP_ERROR_CODE_SET.has(standardized)
159
+ ) {
160
+ const match = Object.entries(COMMON_ERROR_CODE_MAP).find(
161
+ ([, mappedCode]) => mappedCode === standardized,
162
+ );
163
+ if (match) {
164
+ return match[0] as ErrorCode;
165
+ }
166
+ }
167
+ }
168
+
169
+ for (const [errorCode, mappedCode] of Object.entries(
170
+ COMMON_ERROR_CODE_MAP,
171
+ )) {
172
+ if (mappedCode === platformCode) {
173
+ return errorCode as ErrorCode;
174
+ }
175
+ }
176
+
177
+ return ErrorCode.Unknown;
178
+ },
179
+ toPlatformCode: (
180
+ errorCode: ErrorCode,
181
+ _platform: IapPlatform,
182
+ ): string | number => {
183
+ const standardized = toStandardizedCode(errorCode);
184
+ const native = (NATIVE_ERROR_CODES as Record<string, string | number>)[
185
+ standardized
186
+ ];
187
+ return native ?? COMMON_ERROR_CODE_MAP[errorCode] ?? 'E_UNKNOWN';
188
+ },
189
+ isValidForPlatform: (
190
+ errorCode: ErrorCode,
191
+ platform: IapPlatform,
192
+ ): boolean => {
193
+ const standardized = toStandardizedCode(errorCode);
194
+ if (
195
+ (NATIVE_ERROR_CODES as Record<string, unknown>)[standardized] !==
196
+ undefined
197
+ ) {
198
+ return true;
199
+ }
200
+ return standardized in ErrorCodeMapping[normalizePlatform(platform)];
201
+ },
202
+ };
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // Convenience helpers for interpreting error objects
206
+ // ---------------------------------------------------------------------------
7
207
 
8
208
  type ErrorLike = string | {code?: ErrorCode | string; message?: string};
9
209
 
@@ -40,20 +240,10 @@ function extractCode(error: unknown): string | undefined {
40
240
  return undefined;
41
241
  }
42
242
 
43
- /**
44
- * Checks if an error is a user cancellation
45
- * @param error Error object or error code
46
- * @returns True if the error represents user cancellation
47
- */
48
243
  export function isUserCancelledError(error: unknown): boolean {
49
244
  return extractCode(error) === ErrorCode.UserCancelled;
50
245
  }
51
246
 
52
- /**
53
- * Checks if an error is related to network connectivity
54
- * @param error Error object or error code
55
- * @returns True if the error is network-related
56
- */
57
247
  export function isNetworkError(error: unknown): boolean {
58
248
  const networkErrors: ErrorCode[] = [
59
249
  ErrorCode.NetworkError,
@@ -67,11 +257,6 @@ export function isNetworkError(error: unknown): boolean {
67
257
  return !!code && (networkErrors as string[]).includes(code);
68
258
  }
69
259
 
70
- /**
71
- * Checks if an error is recoverable (user can retry)
72
- * @param error Error object or error code
73
- * @returns True if the error is potentially recoverable
74
- */
75
260
  export function isRecoverableError(error: unknown): boolean {
76
261
  const recoverableErrors: ErrorCode[] = [
77
262
  ErrorCode.NetworkError,
@@ -88,11 +273,6 @@ export function isRecoverableError(error: unknown): boolean {
88
273
  return !!code && (recoverableErrors as string[]).includes(code);
89
274
  }
90
275
 
91
- /**
92
- * Gets a user-friendly error message for display
93
- * @param error Error object or error code
94
- * @returns User-friendly error message
95
- */
96
276
  export function getUserFriendlyErrorMessage(error: ErrorLike): string {
97
277
  const errorCode = extractCode(error);
98
278
 
@@ -1,321 +0,0 @@
1
- # GitHub Copilot Instructions for expo-iap
2
-
3
- ## Package Manager
4
-
5
- **IMPORTANT: This project uses Bun exclusively. Do not suggest npm, yarn, or pnpm commands.**
6
-
7
- - Install dependencies: `bun install`
8
- - Run scripts: `bun run <script>`
9
- - Run tests: `bun test`
10
- - Add packages: `bun add <package>`
11
- - Add dev dependencies: `bun add -d <package>`
12
-
13
- **NEVER suggest creating package-lock.json or yarn.lock files. Only bun.lock should exist.**
14
-
15
- ## Platform-Specific Function Naming
16
-
17
- When suggesting code for expo-iap, follow these naming conventions:
18
-
19
- ### Platform-Specific Functions
20
-
21
- Functions that only work on one platform MUST have platform suffixes:
22
-
23
- - iOS: `functionNameIOS()`
24
- - Android: `functionNameAndroid()`
25
-
26
- ```typescript
27
- // Correct examples:
28
- export const getStorefrontIOS = async (): Promise<string> => { ... }
29
- export const consumeProductAndroid = async (token: string): Promise<void> => { ... }
30
- export const getAppTransactionIOS = async (): Promise<AppTransactionIOS | null> => { ... }
31
- ```
32
-
33
- ### Cross-Platform Functions
34
-
35
- Functions that abstract platform differences don't need suffixes:
36
-
37
- ```typescript
38
- // Correct cross-platform example:
39
- export const getProducts = async (skus: string[]): Promise<Product[]> => {
40
- return Platform.select({
41
- ios: async () => {
42
- /* iOS implementation */
43
- },
44
- android: async () => {
45
- /* Android implementation */
46
- },
47
- })();
48
- };
49
- ```
50
-
51
- ## Project Overview
52
-
53
- This is the **expo-iap** library - a modern React Native/Expo module for handling in-app purchases across iOS and Android platforms. The library provides a unified, TypeScript-first API with automatic type inference and platform abstraction.
54
-
55
- ## Key Principles
56
-
57
- ### 🎯 Modern TypeScript-First API
58
-
59
- - **Automatic Type Inference**: No manual type casting required
60
- - **Unified Platform API**: Single API that works across iOS and Android
61
- - **Consistent Results**: Unified data structure regardless of platform
62
- - **Clean Error Handling**: Result pattern with detailed error information
63
-
64
- ### 🚀 Primary API Pattern
65
-
66
- ```typescript
67
- const result = await requestPurchase({
68
- request: {sku: 'product.id'},
69
- type: 'inapp',
70
- });
71
-
72
- if (result.success) {
73
- console.log('Purchase successful:', result.transactionId);
74
- } else {
75
- console.error('Purchase failed:', result.error.message);
76
- }
77
- ```
78
-
79
- ### 🏗️ Code Style Guidelines
80
-
81
- #### TypeScript Best Practices
82
-
83
- - **Explicit Type Definitions**: Always define types explicitly for public APIs
84
- - **Function Overloads**: Provide multiple signatures for better IntelliSense
85
- - **Discriminated Unions**: Use for platform-specific types and conditional returns
86
- - **Strict Type Safety**: No `any` types in production code
87
-
88
- #### Naming Conventions
89
-
90
- - **PascalCase**: Interfaces, types, enums, classes
91
- - **camelCase**: Functions, variables, methods, properties
92
- - **kebab-case**: File names (except for React components)
93
- - **Descriptive Names**: Avoid abbreviations, use clear intent-revealing names
94
-
95
- #### Error Handling Pattern
96
-
97
- ```typescript
98
- type PurchaseResult<T = unknown> =
99
- | {success: true; data: T; platform: 'ios' | 'android'}
100
- | {success: false; error: PurchaseError; platform: 'ios' | 'android'};
101
- ```
102
-
103
- ## API Implementation Patterns
104
-
105
- ### Core Purchase Functions
106
-
107
- ```typescript
108
- export function requestPurchase(params: {
109
- readonly request: {sku: string; quantity?: number};
110
- readonly type: 'inapp';
111
- }): Promise<ProductPurchase | ProductPurchase[]>;
112
-
113
- export function requestSubscription(params: {
114
- readonly request: {sku: string};
115
- }): Promise<SubscriptionPurchase | SubscriptionPurchase[]>;
116
- ```
117
-
118
- ### Platform Abstraction
119
-
120
- The library automatically handles platform differences:
121
-
122
- - **iOS**: Uses `sku` directly with StoreKit
123
- - **Android**: Converts to Google Play Billing format
124
- - **Unified Properties**: Both platforms return consistent data
125
-
126
- ### Type Guards for Advanced Usage
127
-
128
- ```typescript
129
- export const isPurchaseResult = (
130
- result: unknown,
131
- ): result is ProductPurchase | SubscriptionPurchase => {
132
- return (
133
- result !== null &&
134
- typeof result === 'object' &&
135
- 'transactionId' in result &&
136
- 'productId' in result
137
- );
138
- };
139
- ```
140
-
141
- ## File Organization
142
-
143
- ```
144
- src/
145
- ├── index.ts # Main exports
146
- ├── ExpoIap.types.ts # Core type definitions
147
- ├── ExpoIapModule.ts # Native module interface
148
- ├── useIAP.ts # React hook (legacy support)
149
- ├── modules/
150
- │ ├── android.ts # Android-specific utilities
151
- │ └── ios.ts # iOS-specific utilities
152
- └── types/
153
- ├── ExpoIapAndroid.types.ts # Android type definitions
154
- └── ExpoIapIos.types.ts # iOS type definitions
155
- ```
156
-
157
- ## Usage Examples
158
-
159
- ### Simple Product Purchase
160
-
161
- ```typescript
162
- import {requestPurchase} from 'expo-iap';
163
-
164
- const handlePurchase = async (productId: string) => {
165
- const result = await requestPurchase({
166
- request: {sku: productId},
167
- type: 'inapp',
168
- });
169
-
170
- if (result.success) {
171
- console.log('Purchase successful:', result.data);
172
- } else {
173
- console.error('Purchase failed:', result.error.message);
174
- }
175
- };
176
- ```
177
-
178
- ### Subscription Purchase
179
-
180
- ```typescript
181
- import {requestSubscription} from 'expo-iap';
182
-
183
- const handleSubscription = async (subscriptionId: string) => {
184
- const result = await requestSubscription({
185
- request: {sku: subscriptionId},
186
- });
187
-
188
- if (result.success) {
189
- console.log('Subscription activated:', result.data);
190
- } else {
191
- console.error('Subscription failed:', result.error.message);
192
- }
193
- };
194
- ```
195
-
196
- ## Type Naming
197
-
198
- - Platform-specific types: `ProductIOS`, `ProductAndroid`, `PurchaseErrorIOS`
199
- - Cross-platform types: `Product`, `Purchase`, `PurchaseError`
200
-
201
- ## Code Suggestions
202
-
203
- When generating code:
204
-
205
- 1. Check if the function is platform-specific
206
- 2. Add appropriate suffix if it only works on one platform
207
- 3. Use Platform.select() for cross-platform implementations
208
- 4. Always use TypeScript
209
- 5. Include proper error handling
210
- 6. Add JSDoc comments for public APIs
211
-
212
- ## Testing
213
-
214
- Suggest tests that:
215
-
216
- - Cover both iOS and Android paths
217
- - Use `bun test` commands
218
- - Include platform mocking when needed
219
- - Test error scenarios
220
-
221
- ## Common Patterns
222
-
223
- ### Platform Selection
224
-
225
- ```typescript
226
- Platform.select({
227
- ios: () => {
228
- /* iOS code */
229
- },
230
- android: () => {
231
- /* Android code */
232
- },
233
- default: () => {
234
- /* Fallback */
235
- },
236
- });
237
- ```
238
-
239
- ### Receipt Validation (Platform-specific parameters)
240
-
241
- ```typescript
242
- // iOS only needs SKU
243
- await validateReceiptIos(sku);
244
-
245
- // Android needs additional parameters
246
- await validateReceiptAndroid({
247
- packageName,
248
- productToken,
249
- accessToken,
250
- });
251
- ```
252
-
253
- ## Documentation Standards
254
-
255
- ### Code Comments
256
-
257
- - **Document the "why"**, not just the "what"
258
- - **Explain platform differences** and how they're handled
259
- - **Provide usage examples** for complex APIs
260
- - **Use JSDoc format** for all public APIs
261
-
262
- ### Example Documentation Pattern
263
-
264
- ````typescript
265
- /**
266
- * Enhanced requestPurchase with unified API support
267
- *
268
- * This function automatically handles platform differences:
269
- * - iOS: Uses `sku` directly with StoreKit
270
- * - Android: Converts to Google Play Billing format
271
- *
272
- * @param params - Purchase request parameters
273
- * @param params.request - Request object with product details
274
- * @param params.type - Purchase type: 'inapp' for products
275
- *
276
- * @returns Promise resolving to purchase result
277
- *
278
- * @example
279
- * ```typescript
280
- * const result = await requestPurchase({
281
- * request: { sku: 'com.example.premium' },
282
- * type: 'inapp'
283
- * });
284
- * ```
285
- */
286
- ````
287
-
288
- ## Development Philosophy
289
-
290
- The expo-iap library provides a **world-class developer experience**:
291
-
292
- ### ✅ Core Achievements
293
-
294
- - **🎯 Zero Manual Casting**: Automatic type inference
295
- - **🌍 Unified API**: Single codebase for iOS and Android
296
- - **⚡ Enhanced DX**: Better IntelliSense and error messages
297
- - **🛡️ Full Backward Compatibility**: Existing code continues to work
298
- - **📚 Modern Patterns**: TypeScript-first, result pattern error handling
299
-
300
- ### 🎯 When Contributing, Always Consider:
301
-
302
- 1. **Developer Experience First** - Eliminate manual work and reduce cognitive load
303
- 2. **Platform Differences** - Handle iOS/Android disparities transparently
304
- 3. **Type Safety** - Provide automatic inference with compile-time guarantees
305
- 4. **Documentation Excellence** - Explain the "why" behind solutions
306
- 5. **Backward Compatibility** - Never break existing code
307
- 6. **Performance** - Optimize for common use cases
308
- 7. **Testing** - Comprehensive coverage across platforms and scenarios
309
-
310
- ### 🚀 Ultimate Goal
311
-
312
- Transform in-app purchases from a complex, error-prone process into something as simple as:
313
-
314
- ```typescript
315
- const result = await requestPurchase({
316
- request: {sku: 'premium.product'},
317
- type: 'inapp',
318
- });
319
- ```
320
-
321
- While maintaining full power and flexibility for advanced enterprise use cases.