expo-iap 2.5.0 → 2.5.2
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/.eslintignore +5 -0
- package/LICENSE +21 -0
- package/README.md +14 -8
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +14 -47
- package/android/src/main/java/expo/modules/iap/Types.kt +55 -0
- package/build/ExpoIap.types.d.ts +38 -13
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js +5 -0
- package/build/ExpoIap.types.js.map +1 -1
- package/build/useIap.d.ts +5 -3
- package/build/useIap.d.ts.map +1 -1
- package/build/useIap.js +24 -6
- package/build/useIap.js.map +1 -1
- package/build/utils/smartPurchase.d.ts +2 -0
- package/build/utils/smartPurchase.d.ts.map +1 -0
- package/build/utils/smartPurchase.js +2 -0
- package/build/utils/smartPurchase.js.map +1 -0
- package/ios/ExpoIapModule.swift +2 -27
- package/ios/Types.swift +34 -0
- package/package.json +7 -3
- package/src/ExpoIap.types.ts +77 -25
- package/src/useIap.ts +39 -24
- package/src/utils/smartPurchase.ts +0 -0
- package/docs/ERROR_CODES.md +0 -172
- package/docs/IAP.md +0 -500
package/build/useIap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIap.js","sourceRoot":"","sources":["../src/useIap.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n endConnection,\n initConnection,\n purchaseErrorListener,\n purchaseUpdatedListener,\n getProducts,\n getAvailablePurchases,\n getPurchaseHistory,\n finishTransaction as finishTransactionInternal,\n getSubscriptions,\n requestPurchase as requestPurchaseInternal,\n sync,\n validateReceiptIos,\n validateReceiptAndroid,\n} from './';\nimport {useCallback, useEffect, useState, useRef} from 'react';\nimport {\n Product,\n ProductPurchase,\n Purchase,\n PurchaseError,\n PurchaseResult,\n SubscriptionProduct,\n SubscriptionPurchase,\n} from './ExpoIap.types';\nimport {Platform} from 'react-native';\nimport {EventSubscription} from 'expo-modules-core';\n\ntype UseIap = {\n connected: boolean;\n products: Product[];\n promotedProductsIOS: ProductPurchase[];\n subscriptions: SubscriptionProduct[];\n purchaseHistories: ProductPurchase[];\n availablePurchases: ProductPurchase[];\n currentPurchase?: ProductPurchase;\n currentPurchaseError?: PurchaseError;\n clearCurrentPurchase: () => void;\n clearCurrentPurchaseError: () => void;\n finishTransaction: ({\n purchase,\n isConsumable,\n }: {\n purchase: Purchase;\n isConsumable?: boolean;\n }) => Promise<string | boolean | PurchaseResult | void>;\n getAvailablePurchases: (skus: string[]) => Promise<void>;\n getPurchaseHistories: (skus: string[]) => Promise<void>;\n getProducts: (skus: string[]) => Promise<void>;\n getSubscriptions: (skus: string[]) => Promise<void>;\n requestPurchase: typeof requestPurchaseInternal;\n validateReceipt: (\n sku: string,\n androidOptions?: {\n packageName: string;\n productToken: string;\n accessToken: string;\n isSub?: boolean;\n },\n ) => Promise<any>;\n restorePurchases: () => Promise<void>; // 구매 복원 함수 추가\n};\n\nexport interface UseIAPOptions {\n onPurchaseSuccess?: (\n purchase: ProductPurchase | SubscriptionPurchase,\n ) => void;\n onPurchaseError?: (error: PurchaseError) => void;\n onSyncError?: (error: Error) => void;\n shouldAutoSyncPurchases?: boolean; // New option to control auto-syncing\n}\n\nexport function useIAP(options?: UseIAPOptions): UseIap {\n const [connected, setConnected] = useState<boolean>(false);\n const [products, setProducts] = useState<Product[]>([]);\n const [promotedProductsIOS, setPromotedProductsIOS] = useState<\n ProductPurchase[]\n >([]);\n const [subscriptions, setSubscriptions] = useState<SubscriptionProduct[]>([]);\n const [purchaseHistories, setPurchaseHistories] = useState<ProductPurchase[]>(\n [],\n );\n const [availablePurchases, setAvailablePurchases] = useState<\n ProductPurchase[]\n >([]);\n const [currentPurchase, setCurrentPurchase] = useState<ProductPurchase>();\n const [currentPurchaseError, setCurrentPurchaseError] =\n useState<PurchaseError>();\n\n const optionsRef = useRef<UseIAPOptions | undefined>(options);\n\n // Helper function to merge arrays with duplicate checking\n const mergeWithDuplicateCheck = useCallback(\n <T>(\n existingItems: T[],\n newItems: T[],\n getKey: (item: T) => string,\n ): T[] => {\n const merged = [...existingItems];\n newItems.forEach((newItem) => {\n const isDuplicate = merged.some(\n (existingItem) => getKey(existingItem) === getKey(newItem),\n );\n if (!isDuplicate) {\n merged.push(newItem);\n }\n });\n return merged;\n },\n [],\n );\n\n useEffect(() => {\n optionsRef.current = options;\n }, [options]);\n\n const subscriptionsRef = useRef<{\n purchaseUpdate?: EventSubscription;\n purchaseError?: EventSubscription;\n promotedProductsIos?: EventSubscription;\n }>({});\n\n const subscriptionsRefState = useRef<SubscriptionProduct[]>([]);\n\n useEffect(() => {\n subscriptionsRefState.current = subscriptions;\n }, [subscriptions]);\n\n const clearCurrentPurchase = useCallback(() => {\n setCurrentPurchase(undefined);\n }, []);\n\n const clearCurrentPurchaseError = useCallback(() => {\n setCurrentPurchaseError(undefined);\n }, []);\n\n const getProductsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n const newProducts = await getProducts(skus);\n setProducts((prevProducts) =>\n mergeWithDuplicateCheck(\n prevProducts,\n newProducts,\n (product) => product.id,\n ),\n );\n },\n [mergeWithDuplicateCheck],\n );\n\n const getSubscriptionsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n const newSubscriptions = await getSubscriptions(skus);\n setSubscriptions((prevSubscriptions) =>\n mergeWithDuplicateCheck(\n prevSubscriptions,\n newSubscriptions,\n (subscription) => subscription.id,\n ),\n );\n },\n [mergeWithDuplicateCheck],\n );\n\n const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {\n setAvailablePurchases(await getAvailablePurchases());\n }, []);\n\n const getPurchaseHistoriesInternal = useCallback(async (): Promise<void> => {\n setPurchaseHistories(await getPurchaseHistory());\n }, []);\n\n const finishTransaction = useCallback(\n async ({\n purchase,\n isConsumable,\n }: {\n purchase: ProductPurchase;\n isConsumable?: boolean;\n }): Promise<string | boolean | PurchaseResult | void> => {\n try {\n return await finishTransactionInternal({\n purchase,\n isConsumable,\n });\n } catch (err) {\n throw err;\n } finally {\n if (purchase.id === currentPurchase?.id) {\n clearCurrentPurchase();\n }\n if (purchase.id === currentPurchaseError?.productId) {\n clearCurrentPurchaseError();\n }\n }\n },\n [\n currentPurchase?.id,\n currentPurchaseError?.productId,\n clearCurrentPurchase,\n clearCurrentPurchaseError,\n ],\n );\n\n const requestPurchaseWithReset = useCallback(\n async (requestObj: Parameters<typeof requestPurchaseInternal>[0]) => {\n clearCurrentPurchase();\n clearCurrentPurchaseError();\n\n try {\n return await requestPurchaseInternal(requestObj);\n } catch (error) {\n throw error;\n }\n },\n [clearCurrentPurchase, clearCurrentPurchaseError],\n );\n\n const refreshSubscriptionStatus = useCallback(\n async (productId: string) => {\n try {\n if (subscriptionsRefState.current.some((sub) => sub.id === productId)) {\n await getSubscriptionsInternal([productId]);\n await getAvailablePurchasesInternal();\n }\n } catch (error) {\n console.warn('Failed to refresh subscription status:', error);\n }\n },\n [getAvailablePurchasesInternal, getSubscriptionsInternal],\n );\n\n const restorePurchases = useCallback(async (): Promise<void> => {\n try {\n if (Platform.OS === 'ios') {\n await sync().catch((error) => {\n if (optionsRef.current?.onSyncError) {\n optionsRef.current.onSyncError(error);\n } else {\n console.warn('Error restoring purchases:', error);\n }\n });\n }\n await getAvailablePurchasesInternal();\n } catch (error) {\n console.warn('Failed to restore purchases:', error);\n }\n }, [getAvailablePurchasesInternal]);\n\n const validateReceipt = useCallback(\n async (\n sku: string,\n androidOptions?: {\n packageName: string;\n productToken: string;\n accessToken: string;\n isSub?: boolean;\n },\n ) => {\n if (Platform.OS === 'ios') {\n return await validateReceiptIos(sku);\n } else if (Platform.OS === 'android') {\n if (\n !androidOptions ||\n !androidOptions.packageName ||\n !androidOptions.productToken ||\n !androidOptions.accessToken\n ) {\n throw new Error(\n 'Android validation requires packageName, productToken, and accessToken',\n );\n }\n return await validateReceiptAndroid({\n packageName: androidOptions.packageName,\n productId: sku,\n productToken: androidOptions.productToken,\n accessToken: androidOptions.accessToken,\n isSub: androidOptions.isSub,\n });\n } else {\n throw new Error('Platform not supported');\n }\n },\n [],\n );\n\n const initIapWithSubscriptions = useCallback(async (): Promise<void> => {\n const result = await initConnection();\n setConnected(result);\n\n if (result) {\n subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(\n async (purchase: Purchase | SubscriptionPurchase) => {\n setCurrentPurchaseError(undefined);\n setCurrentPurchase(purchase);\n\n if ('expirationDateIos' in purchase) {\n await refreshSubscriptionStatus(purchase.id);\n }\n\n if (optionsRef.current?.onPurchaseSuccess) {\n optionsRef.current.onPurchaseSuccess(purchase);\n }\n },\n );\n\n subscriptionsRef.current.purchaseError = purchaseErrorListener(\n (error: PurchaseError) => {\n setCurrentPurchase(undefined);\n setCurrentPurchaseError(error);\n\n if (optionsRef.current?.onPurchaseError) {\n optionsRef.current.onPurchaseError(error);\n }\n },\n );\n\n if (Platform.OS === 'ios') {\n // iOS promoted products are handled through regular purchase updates\n subscriptionsRef.current.promotedProductsIos = purchaseUpdatedListener(\n async (purchase: Purchase | SubscriptionPurchase) => {\n // Add to promoted products if it's a promoted transaction (avoid duplicates)\n setPromotedProductsIOS((prevProducts) =>\n mergeWithDuplicateCheck(\n prevProducts,\n [purchase as ProductPurchase],\n (product) => product.transactionId || product.id,\n ),\n );\n\n // Refresh subscription status if it's a subscription purchase\n if ('expirationDateIos' in purchase) {\n await refreshSubscriptionStatus(purchase.id);\n }\n },\n );\n }\n }\n }, [refreshSubscriptionStatus, mergeWithDuplicateCheck]);\n\n useEffect(() => {\n initIapWithSubscriptions();\n const currentSubscriptions = subscriptionsRef.current;\n\n return () => {\n currentSubscriptions.purchaseUpdate?.remove();\n currentSubscriptions.purchaseError?.remove();\n currentSubscriptions.promotedProductsIos?.remove();\n endConnection();\n setConnected(false);\n };\n }, [initIapWithSubscriptions]);\n\n return {\n connected,\n products,\n promotedProductsIOS,\n subscriptions,\n purchaseHistories,\n finishTransaction,\n availablePurchases,\n currentPurchase,\n currentPurchaseError,\n clearCurrentPurchase,\n clearCurrentPurchaseError,\n getAvailablePurchases: getAvailablePurchasesInternal,\n getPurchaseHistories: getPurchaseHistoriesInternal,\n getProducts: getProductsInternal,\n getSubscriptions: getSubscriptionsInternal,\n requestPurchase: requestPurchaseWithReset,\n validateReceipt,\n restorePurchases,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useIap.js","sourceRoot":"","sources":["../src/useIap.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n endConnection,\n initConnection,\n purchaseErrorListener,\n purchaseUpdatedListener,\n getProducts,\n getAvailablePurchases,\n getPurchaseHistory,\n finishTransaction as finishTransactionInternal,\n getSubscriptions,\n requestPurchase as requestPurchaseInternal,\n} from './';\nimport {sync, validateReceiptIos} from './modules/ios';\nimport {validateReceiptAndroid} from './modules/android';\nimport {useCallback, useEffect, useState, useRef} from 'react';\nimport {\n Product,\n ProductPurchase,\n Purchase,\n PurchaseError,\n PurchaseResult,\n SubscriptionProduct,\n SubscriptionPurchase,\n} from './ExpoIap.types';\nimport {Platform} from 'react-native';\nimport {EventSubscription} from 'expo-modules-core';\n\ntype UseIap = {\n connected: boolean;\n products: Product[];\n promotedProductsIOS: ProductPurchase[];\n subscriptions: SubscriptionProduct[];\n purchaseHistories: ProductPurchase[];\n availablePurchases: ProductPurchase[];\n currentPurchase?: ProductPurchase;\n currentPurchaseError?: PurchaseError;\n clearCurrentPurchase: () => void;\n clearCurrentPurchaseError: () => void;\n finishTransaction: ({\n purchase,\n isConsumable,\n }: {\n purchase: Purchase;\n isConsumable?: boolean;\n }) => Promise<PurchaseResult | boolean>;\n getAvailablePurchases: (skus: string[]) => Promise<void>;\n getPurchaseHistories: (skus: string[]) => Promise<void>;\n getProducts: (skus: string[]) => Promise<void>;\n getSubscriptions: (skus: string[]) => Promise<void>;\n requestPurchase: (params: {\n request: any;\n type?: 'inapp' | 'subs';\n }) => Promise<any>;\n validateReceipt: (\n sku: string,\n androidOptions?: {\n packageName: string;\n productToken: string;\n accessToken: string;\n isSub?: boolean;\n },\n ) => Promise<any>;\n restorePurchases: () => Promise<void>; // 구매 복원 함수 추가\n};\n\nexport interface UseIAPOptions {\n onPurchaseSuccess?: (\n purchase: ProductPurchase | SubscriptionPurchase,\n ) => void;\n onPurchaseError?: (error: PurchaseError) => void;\n onSyncError?: (error: Error) => void;\n shouldAutoSyncPurchases?: boolean; // New option to control auto-syncing\n}\n\nexport function useIAP(options?: UseIAPOptions): UseIap {\n const [connected, setConnected] = useState<boolean>(false);\n const [products, setProducts] = useState<Product[]>([]);\n const [promotedProductsIOS, setPromotedProductsIOS] = useState<\n ProductPurchase[]\n >([]);\n const [subscriptions, setSubscriptions] = useState<SubscriptionProduct[]>([]);\n const [purchaseHistories, setPurchaseHistories] = useState<ProductPurchase[]>(\n [],\n );\n const [availablePurchases, setAvailablePurchases] = useState<\n ProductPurchase[]\n >([]);\n const [currentPurchase, setCurrentPurchase] = useState<ProductPurchase>();\n const [currentPurchaseError, setCurrentPurchaseError] =\n useState<PurchaseError>();\n\n const optionsRef = useRef<UseIAPOptions | undefined>(options);\n\n // Helper function to merge arrays with duplicate checking\n const mergeWithDuplicateCheck = useCallback(\n <T>(\n existingItems: T[],\n newItems: T[],\n getKey: (item: T) => string,\n ): T[] => {\n const merged = [...existingItems];\n newItems.forEach((newItem) => {\n const isDuplicate = merged.some(\n (existingItem) => getKey(existingItem) === getKey(newItem),\n );\n if (!isDuplicate) {\n merged.push(newItem);\n }\n });\n return merged;\n },\n [],\n );\n\n useEffect(() => {\n optionsRef.current = options;\n }, [options]);\n\n const subscriptionsRef = useRef<{\n purchaseUpdate?: EventSubscription;\n purchaseError?: EventSubscription;\n promotedProductsIos?: EventSubscription;\n }>({});\n\n const subscriptionsRefState = useRef<SubscriptionProduct[]>([]);\n\n useEffect(() => {\n subscriptionsRefState.current = subscriptions;\n }, [subscriptions]);\n\n const clearCurrentPurchase = useCallback(() => {\n setCurrentPurchase(undefined);\n }, []);\n\n const clearCurrentPurchaseError = useCallback(() => {\n setCurrentPurchaseError(undefined);\n }, []);\n\n const getProductsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n try {\n const result = await getProducts(skus);\n setProducts((prevProducts) =>\n mergeWithDuplicateCheck(\n prevProducts,\n result,\n (product) => product.id,\n ),\n );\n } catch (error) {\n console.error('Error fetching products:', error);\n }\n },\n [mergeWithDuplicateCheck],\n );\n\n const getSubscriptionsInternal = useCallback(\n async (skus: string[]): Promise<void> => {\n try {\n const result = await getSubscriptions(skus);\n setSubscriptions((prevSubscriptions) =>\n mergeWithDuplicateCheck(\n prevSubscriptions,\n result,\n (subscription) => subscription.id,\n ),\n );\n } catch (error) {\n console.error('Error fetching subscriptions:', error);\n }\n },\n [mergeWithDuplicateCheck],\n );\n\n const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {\n try {\n const result = await getAvailablePurchases();\n setAvailablePurchases(result);\n } catch (error) {\n console.error('Error fetching available purchases:', error);\n }\n }, []);\n\n const getPurchaseHistoriesInternal = useCallback(async (): Promise<void> => {\n setPurchaseHistories(await getPurchaseHistory());\n }, []);\n\n const finishTransaction = useCallback(\n async ({\n purchase,\n isConsumable,\n }: {\n purchase: ProductPurchase;\n isConsumable?: boolean;\n }): Promise<PurchaseResult | boolean> => {\n try {\n return await finishTransactionInternal({\n purchase,\n isConsumable,\n });\n } catch (err) {\n throw err;\n } finally {\n if (purchase.id === currentPurchase?.id) {\n clearCurrentPurchase();\n }\n if (purchase.id === currentPurchaseError?.productId) {\n clearCurrentPurchaseError();\n }\n }\n },\n [\n currentPurchase?.id,\n currentPurchaseError?.productId,\n clearCurrentPurchase,\n clearCurrentPurchaseError,\n ],\n );\n\n const requestPurchaseWithReset = useCallback(\n async (requestObj: {request: any; type?: 'inapp' | 'subs'}) => {\n clearCurrentPurchase();\n clearCurrentPurchaseError();\n\n try {\n return await requestPurchaseInternal(requestObj);\n } catch (error) {\n throw error;\n }\n },\n [clearCurrentPurchase, clearCurrentPurchaseError],\n );\n\n const refreshSubscriptionStatus = useCallback(\n async (productId: string) => {\n try {\n if (subscriptionsRefState.current.some((sub) => sub.id === productId)) {\n await getSubscriptionsInternal([productId]);\n await getAvailablePurchasesInternal();\n }\n } catch (error) {\n console.warn('Failed to refresh subscription status:', error);\n }\n },\n [getAvailablePurchasesInternal, getSubscriptionsInternal],\n );\n\n const restorePurchases = useCallback(async (): Promise<void> => {\n try {\n if (Platform.OS === 'ios') {\n await sync().catch((error) => {\n if (optionsRef.current?.onSyncError) {\n optionsRef.current.onSyncError(error);\n } else {\n console.warn('Error restoring purchases:', error);\n }\n });\n }\n await getAvailablePurchasesInternal();\n } catch (error) {\n console.warn('Failed to restore purchases:', error);\n }\n }, [getAvailablePurchasesInternal]);\n\n const validateReceipt = useCallback(\n async (\n sku: string,\n androidOptions?: {\n packageName: string;\n productToken: string;\n accessToken: string;\n isSub?: boolean;\n },\n ) => {\n if (Platform.OS === 'ios') {\n return await validateReceiptIos(sku);\n } else if (Platform.OS === 'android') {\n if (\n !androidOptions ||\n !androidOptions.packageName ||\n !androidOptions.productToken ||\n !androidOptions.accessToken\n ) {\n throw new Error(\n 'Android validation requires packageName, productToken, and accessToken',\n );\n }\n return await validateReceiptAndroid({\n packageName: androidOptions.packageName,\n productId: sku,\n productToken: androidOptions.productToken,\n accessToken: androidOptions.accessToken,\n isSub: androidOptions.isSub,\n });\n } else {\n throw new Error('Platform not supported');\n }\n },\n [],\n );\n\n const initIapWithSubscriptions = useCallback(async (): Promise<void> => {\n const result = await initConnection();\n setConnected(result);\n\n if (result) {\n subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(\n async (purchase: Purchase | SubscriptionPurchase) => {\n setCurrentPurchaseError(undefined);\n setCurrentPurchase(purchase);\n\n if ('expirationDateIos' in purchase) {\n await refreshSubscriptionStatus(purchase.id);\n }\n\n if (optionsRef.current?.onPurchaseSuccess) {\n optionsRef.current.onPurchaseSuccess(purchase);\n }\n },\n );\n\n subscriptionsRef.current.purchaseError = purchaseErrorListener(\n (error: PurchaseError) => {\n setCurrentPurchase(undefined);\n setCurrentPurchaseError(error);\n\n if (optionsRef.current?.onPurchaseError) {\n optionsRef.current.onPurchaseError(error);\n }\n },\n );\n\n if (Platform.OS === 'ios') {\n // iOS promoted products are handled through regular purchase updates\n subscriptionsRef.current.promotedProductsIos = purchaseUpdatedListener(\n async (purchase: Purchase | SubscriptionPurchase) => {\n // Add to promoted products if it's a promoted transaction (avoid duplicates)\n setPromotedProductsIOS((prevProducts) =>\n mergeWithDuplicateCheck(\n prevProducts,\n [purchase as ProductPurchase],\n (product) => product.transactionId || product.id,\n ),\n );\n\n // Refresh subscription status if it's a subscription purchase\n if ('expirationDateIos' in purchase) {\n await refreshSubscriptionStatus(purchase.id);\n }\n },\n );\n }\n }\n }, [refreshSubscriptionStatus, mergeWithDuplicateCheck]);\n\n useEffect(() => {\n initIapWithSubscriptions();\n const currentSubscriptions = subscriptionsRef.current;\n\n return () => {\n currentSubscriptions.purchaseUpdate?.remove();\n currentSubscriptions.purchaseError?.remove();\n currentSubscriptions.promotedProductsIos?.remove();\n endConnection();\n setConnected(false);\n };\n }, [initIapWithSubscriptions]);\n\n return {\n connected,\n products,\n promotedProductsIOS,\n subscriptions,\n purchaseHistories,\n finishTransaction,\n availablePurchases,\n currentPurchase,\n currentPurchaseError,\n clearCurrentPurchase,\n clearCurrentPurchaseError,\n getAvailablePurchases: getAvailablePurchasesInternal,\n getPurchaseHistories: getPurchaseHistoriesInternal,\n getProducts: getProductsInternal,\n getSubscriptions: getSubscriptionsInternal,\n requestPurchase: requestPurchaseWithReset,\n validateReceipt,\n restorePurchases,\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smartPurchase.d.ts","sourceRoot":"","sources":["../../src/utils/smartPurchase.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smartPurchase.js","sourceRoot":"","sources":["../../src/utils/smartPurchase.ts"],"names":[],"mappings":"","sourcesContent":[""]}
|
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -56,7 +56,7 @@ func serializeTransaction(_ transaction: Transaction, jwsRepresentationIos: Stri
|
|
|
56
56
|
|
|
57
57
|
"quantityIos": transaction.purchasedQuantity,
|
|
58
58
|
"originalTransactionDateIos": transaction.originalPurchaseDate.timeIntervalSince1970 * 1000,
|
|
59
|
-
"originalTransactionIdentifierIos": transaction.originalID,
|
|
59
|
+
"originalTransactionIdentifierIos": String(transaction.originalID),
|
|
60
60
|
"appAccountToken": transaction.appAccountToken?.uuidString,
|
|
61
61
|
|
|
62
62
|
"appBundleIdIos": transaction.appBundleID,
|
|
@@ -186,32 +186,7 @@ public class ExpoIapModule: Module {
|
|
|
186
186
|
Name("ExpoIap")
|
|
187
187
|
|
|
188
188
|
Constants([
|
|
189
|
-
"ERROR_CODES":
|
|
190
|
-
"E_UNKNOWN": IapErrorCode.unknown,
|
|
191
|
-
"E_SERVICE_ERROR": IapErrorCode.serviceError,
|
|
192
|
-
"E_USER_CANCELLED": IapErrorCode.userCancelled,
|
|
193
|
-
"E_USER_ERROR": IapErrorCode.userError,
|
|
194
|
-
"E_ITEM_UNAVAILABLE": IapErrorCode.itemUnavailable,
|
|
195
|
-
"E_REMOTE_ERROR": IapErrorCode.remoteError,
|
|
196
|
-
"E_NETWORK_ERROR": IapErrorCode.networkError,
|
|
197
|
-
"E_RECEIPT_FAILED": IapErrorCode.receiptFailed,
|
|
198
|
-
"E_RECEIPT_FINISHED_FAILED": IapErrorCode.receiptFinishedFailed,
|
|
199
|
-
"E_NOT_PREPARED": IapErrorCode.notPrepared,
|
|
200
|
-
"E_NOT_ENDED": IapErrorCode.notEnded,
|
|
201
|
-
"E_ALREADY_OWNED": IapErrorCode.alreadyOwned,
|
|
202
|
-
"E_DEVELOPER_ERROR": IapErrorCode.developerError,
|
|
203
|
-
"E_PURCHASE_ERROR": IapErrorCode.purchaseError,
|
|
204
|
-
"E_SYNC_ERROR": IapErrorCode.syncError,
|
|
205
|
-
"E_DEFERRED_PAYMENT": IapErrorCode.deferredPayment,
|
|
206
|
-
"E_TRANSACTION_VALIDATION_FAILED": IapErrorCode.transactionValidationFailed,
|
|
207
|
-
"E_BILLING_RESPONSE_JSON_PARSE_ERROR": IapErrorCode.billingResponseJsonParseError,
|
|
208
|
-
"E_INTERRUPTED": IapErrorCode.interrupted,
|
|
209
|
-
"E_IAP_NOT_AVAILABLE": IapErrorCode.iapNotAvailable,
|
|
210
|
-
"E_ACTIVITY_UNAVAILABLE": IapErrorCode.activityUnavailable,
|
|
211
|
-
"E_ALREADY_PREPARED": IapErrorCode.alreadyPrepared,
|
|
212
|
-
"E_PENDING": IapErrorCode.pending,
|
|
213
|
-
"E_CONNECTION_CLOSED": IapErrorCode.connectionClosed,
|
|
214
|
-
]
|
|
189
|
+
"ERROR_CODES": IapErrorCode.toDictionary()
|
|
215
190
|
])
|
|
216
191
|
|
|
217
192
|
Events(IapEvent.PurchaseUpdated, IapEvent.PurchaseError)
|
package/ios/Types.swift
CHANGED
|
@@ -14,6 +14,7 @@ public enum StoreError: Error {
|
|
|
14
14
|
|
|
15
15
|
// Error codes for IAP operations - centralized error code management
|
|
16
16
|
struct IapErrorCode {
|
|
17
|
+
// Constants for code usage - safe pattern without force unwrapping
|
|
17
18
|
static let unknown = "E_UNKNOWN"
|
|
18
19
|
static let serviceError = "E_SERVICE_ERROR"
|
|
19
20
|
static let userCancelled = "E_USER_CANCELLED"
|
|
@@ -38,6 +39,39 @@ struct IapErrorCode {
|
|
|
38
39
|
static let alreadyPrepared = "E_ALREADY_PREPARED"
|
|
39
40
|
static let pending = "E_PENDING"
|
|
40
41
|
static let connectionClosed = "E_CONNECTION_CLOSED"
|
|
42
|
+
|
|
43
|
+
// Cached dictionary for Constants export - using constants as keys to avoid duplication
|
|
44
|
+
private static let _cachedDictionary: [String: String] = [
|
|
45
|
+
unknown: unknown,
|
|
46
|
+
serviceError: serviceError,
|
|
47
|
+
userCancelled: userCancelled,
|
|
48
|
+
userError: userError,
|
|
49
|
+
itemUnavailable: itemUnavailable,
|
|
50
|
+
remoteError: remoteError,
|
|
51
|
+
networkError: networkError,
|
|
52
|
+
receiptFailed: receiptFailed,
|
|
53
|
+
receiptFinishedFailed: receiptFinishedFailed,
|
|
54
|
+
notPrepared: notPrepared,
|
|
55
|
+
notEnded: notEnded,
|
|
56
|
+
alreadyOwned: alreadyOwned,
|
|
57
|
+
developerError: developerError,
|
|
58
|
+
purchaseError: purchaseError,
|
|
59
|
+
syncError: syncError,
|
|
60
|
+
deferredPayment: deferredPayment,
|
|
61
|
+
transactionValidationFailed: transactionValidationFailed,
|
|
62
|
+
billingResponseJsonParseError: billingResponseJsonParseError,
|
|
63
|
+
interrupted: interrupted,
|
|
64
|
+
iapNotAvailable: iapNotAvailable,
|
|
65
|
+
activityUnavailable: activityUnavailable,
|
|
66
|
+
alreadyPrepared: alreadyPrepared,
|
|
67
|
+
pending: pending,
|
|
68
|
+
connectionClosed: connectionClosed
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
// Return cached dictionary - no allocation on repeated calls
|
|
72
|
+
static func toDictionary() -> [String: String] {
|
|
73
|
+
return _cachedDictionary
|
|
74
|
+
}
|
|
41
75
|
}
|
|
42
76
|
|
|
43
77
|
// Based on https://stackoverflow.com/a/40135192/570612
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-iap",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"description": "In App Purchase module in Expo",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"build": "expo-module build",
|
|
9
9
|
"clean": "expo-module clean",
|
|
10
10
|
"lint": "eslint --ext .ts,.tsx,.js,.jsx src",
|
|
11
|
-
"lint:eslint": "eslint --fix '
|
|
11
|
+
"lint:eslint": "eslint --fix 'src/**/*.{ts,tsx}' 'plugin/src/**/*.{ts,tsx}'",
|
|
12
12
|
"lint:prettier": "prettier --write \"**/*.{md,js,jsx,ts,tsx}\"",
|
|
13
13
|
"lint:tsc": "tsc -p tsconfig.json --noEmit --skipLibCheck",
|
|
14
14
|
"lint:ci": "yarn lint:tsc && yarn lint:eslint && yarn lint:prettier",
|
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
"lint:kotlin": "ktlint --format ./android",
|
|
21
21
|
"build:plugin": "tsc --build plugin",
|
|
22
22
|
"clean:plugin": "expo-module clean plugin",
|
|
23
|
-
"lint:plugin": "eslint plugin/src/*"
|
|
23
|
+
"lint:plugin": "eslint plugin/src/*",
|
|
24
|
+
"docs:start": "cd docs && bun run start",
|
|
25
|
+
"docs:build": "cd docs && bun run build",
|
|
26
|
+
"docs:serve": "cd docs && bun run serve",
|
|
27
|
+
"docs:install": "cd docs && bun install --yarn"
|
|
24
28
|
},
|
|
25
29
|
"keywords": [
|
|
26
30
|
"react-native",
|
package/src/ExpoIap.types.ts
CHANGED
|
@@ -18,9 +18,8 @@ export type ChangeEventPayload = {
|
|
|
18
18
|
value: string;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*/
|
|
21
|
+
export type ProductType = 'inapp' | 'subs';
|
|
22
|
+
|
|
24
23
|
export type ProductBase = {
|
|
25
24
|
id: string;
|
|
26
25
|
title: string;
|
|
@@ -32,12 +31,6 @@ export type ProductBase = {
|
|
|
32
31
|
price?: number;
|
|
33
32
|
};
|
|
34
33
|
|
|
35
|
-
// Define literal platform types for better type discrimination
|
|
36
|
-
export type IosPlatform = {platform: 'ios'};
|
|
37
|
-
export type AndroidPlatform = {platform: 'android'};
|
|
38
|
-
export type ProductType = 'inapp' | 'subs';
|
|
39
|
-
|
|
40
|
-
// Common base purchase type
|
|
41
34
|
export type PurchaseBase = {
|
|
42
35
|
id: string;
|
|
43
36
|
transactionId?: string;
|
|
@@ -45,35 +38,49 @@ export type PurchaseBase = {
|
|
|
45
38
|
transactionReceipt: string;
|
|
46
39
|
};
|
|
47
40
|
|
|
48
|
-
//
|
|
41
|
+
// Define literal platform types for better type discrimination
|
|
42
|
+
export type IosPlatform = {platform: 'ios'};
|
|
43
|
+
export type AndroidPlatform = {platform: 'android'};
|
|
44
|
+
|
|
45
|
+
// Platform-agnostic unified product types (public API)
|
|
49
46
|
export type Product =
|
|
50
47
|
| (ProductAndroid & AndroidPlatform)
|
|
51
48
|
| (ProductIos & IosPlatform);
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
export type SubscriptionProduct =
|
|
51
|
+
| (SubscriptionProductAndroid & AndroidPlatform)
|
|
52
|
+
| (SubscriptionProductIos & IosPlatform);
|
|
53
|
+
|
|
54
|
+
// Internal platform-specific types (used for native interop only)
|
|
55
|
+
export type RequestPurchaseProps =
|
|
56
|
+
| RequestPurchaseIosProps
|
|
57
|
+
| RequestPurchaseAndroidProps;
|
|
58
|
+
|
|
59
|
+
export type RequestSubscriptionProps =
|
|
60
|
+
| RequestSubscriptionAndroidProps
|
|
61
|
+
| RequestSubscriptionIosProps;
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Legacy Types (For backward compatibility with useIap hook)
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
// Re-export platform-specific purchase types for legacy compatibility
|
|
68
|
+
export type {ProductPurchaseAndroid} from './types/ExpoIapAndroid.types';
|
|
69
|
+
export type {ProductPurchaseIos} from './types/ExpoIapIos.types';
|
|
70
|
+
|
|
71
|
+
// Union type for platform-specific purchase types (legacy support)
|
|
54
72
|
export type ProductPurchase =
|
|
55
73
|
| (ProductPurchaseAndroid & AndroidPlatform)
|
|
56
74
|
| (ProductPurchaseIos & IosPlatform);
|
|
57
75
|
|
|
58
|
-
// Union type for platform-specific subscription purchase types
|
|
76
|
+
// Union type for platform-specific subscription purchase types (legacy support)
|
|
59
77
|
export type SubscriptionPurchase =
|
|
60
78
|
| (ProductPurchaseAndroid & AndroidPlatform & {autoRenewingAndroid: boolean})
|
|
61
79
|
| (ProductPurchaseIos & IosPlatform);
|
|
62
80
|
|
|
63
81
|
export type Purchase = ProductPurchase | SubscriptionPurchase;
|
|
64
82
|
|
|
65
|
-
|
|
66
|
-
| RequestPurchaseIosProps
|
|
67
|
-
| RequestPurchaseAndroidProps;
|
|
68
|
-
|
|
69
|
-
export type SubscriptionProduct =
|
|
70
|
-
| (SubscriptionProductAndroid & AndroidPlatform)
|
|
71
|
-
| (SubscriptionProductIos & IosPlatform);
|
|
72
|
-
|
|
73
|
-
export type RequestSubscriptionProps =
|
|
74
|
-
| RequestSubscriptionAndroidProps
|
|
75
|
-
| RequestSubscriptionIosProps;
|
|
76
|
-
|
|
83
|
+
// Legacy result type
|
|
77
84
|
export type PurchaseResult = {
|
|
78
85
|
responseCode?: number;
|
|
79
86
|
debugMessage?: string;
|
|
@@ -81,7 +88,6 @@ export type PurchaseResult = {
|
|
|
81
88
|
message?: string;
|
|
82
89
|
purchaseTokenAndroid?: string;
|
|
83
90
|
};
|
|
84
|
-
|
|
85
91
|
/**
|
|
86
92
|
* Centralized error codes for expo-iap
|
|
87
93
|
* These are mapped to platform-specific error codes and provide consistent error handling
|
|
@@ -291,3 +297,49 @@ export const ErrorCodeUtils = {
|
|
|
291
297
|
return errorCode in ErrorCodeMapping[platform];
|
|
292
298
|
},
|
|
293
299
|
};
|
|
300
|
+
|
|
301
|
+
// ============================================================================
|
|
302
|
+
// Enhanced Unified Request Types
|
|
303
|
+
// ============================================================================
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Unified request props that work on both iOS and Android platforms
|
|
307
|
+
* iOS will use 'sku', Android will use 'skus' (or convert sku to skus array)
|
|
308
|
+
*/
|
|
309
|
+
export interface UnifiedRequestPurchaseProps {
|
|
310
|
+
// Universal properties - works on both platforms
|
|
311
|
+
readonly sku?: string; // Single SKU (iOS native, Android fallback)
|
|
312
|
+
readonly skus?: string[]; // Multiple SKUs (Android native, iOS uses first item)
|
|
313
|
+
|
|
314
|
+
// iOS-specific properties (ignored on Android)
|
|
315
|
+
readonly andDangerouslyFinishTransactionAutomaticallyIOS?: boolean;
|
|
316
|
+
readonly appAccountToken?: string;
|
|
317
|
+
readonly quantity?: number;
|
|
318
|
+
readonly withOffer?: import('./types/ExpoIapIos.types').PaymentDiscount;
|
|
319
|
+
|
|
320
|
+
// Android-specific properties (ignored on iOS)
|
|
321
|
+
readonly obfuscatedAccountIdAndroid?: string;
|
|
322
|
+
readonly obfuscatedProfileIdAndroid?: string;
|
|
323
|
+
readonly isOfferPersonalized?: boolean;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Unified subscription request props
|
|
328
|
+
*/
|
|
329
|
+
export interface UnifiedRequestSubscriptionProps
|
|
330
|
+
extends UnifiedRequestPurchaseProps {
|
|
331
|
+
// Android subscription-specific properties
|
|
332
|
+
readonly purchaseTokenAndroid?: string;
|
|
333
|
+
readonly replacementModeAndroid?: number;
|
|
334
|
+
readonly subscriptionOffers?: {
|
|
335
|
+
sku: string;
|
|
336
|
+
offerToken: string;
|
|
337
|
+
}[];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ============================================================================
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Type Guards and Utility Functions
|
|
343
|
+
// ============================================================================
|
|
344
|
+
|
|
345
|
+
// Note: Type guard functions are exported from index.ts to avoid conflicts
|
package/src/useIap.ts
CHANGED
|
@@ -9,10 +9,9 @@ import {
|
|
|
9
9
|
finishTransaction as finishTransactionInternal,
|
|
10
10
|
getSubscriptions,
|
|
11
11
|
requestPurchase as requestPurchaseInternal,
|
|
12
|
-
sync,
|
|
13
|
-
validateReceiptIos,
|
|
14
|
-
validateReceiptAndroid,
|
|
15
12
|
} from './';
|
|
13
|
+
import {sync, validateReceiptIos} from './modules/ios';
|
|
14
|
+
import {validateReceiptAndroid} from './modules/android';
|
|
16
15
|
import {useCallback, useEffect, useState, useRef} from 'react';
|
|
17
16
|
import {
|
|
18
17
|
Product,
|
|
@@ -43,12 +42,15 @@ type UseIap = {
|
|
|
43
42
|
}: {
|
|
44
43
|
purchase: Purchase;
|
|
45
44
|
isConsumable?: boolean;
|
|
46
|
-
}) => Promise<
|
|
45
|
+
}) => Promise<PurchaseResult | boolean>;
|
|
47
46
|
getAvailablePurchases: (skus: string[]) => Promise<void>;
|
|
48
47
|
getPurchaseHistories: (skus: string[]) => Promise<void>;
|
|
49
48
|
getProducts: (skus: string[]) => Promise<void>;
|
|
50
49
|
getSubscriptions: (skus: string[]) => Promise<void>;
|
|
51
|
-
requestPurchase:
|
|
50
|
+
requestPurchase: (params: {
|
|
51
|
+
request: any;
|
|
52
|
+
type?: 'inapp' | 'subs';
|
|
53
|
+
}) => Promise<any>;
|
|
52
54
|
validateReceipt: (
|
|
53
55
|
sku: string,
|
|
54
56
|
androidOptions?: {
|
|
@@ -136,34 +138,47 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
136
138
|
|
|
137
139
|
const getProductsInternal = useCallback(
|
|
138
140
|
async (skus: string[]): Promise<void> => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
try {
|
|
142
|
+
const result = await getProducts(skus);
|
|
143
|
+
setProducts((prevProducts) =>
|
|
144
|
+
mergeWithDuplicateCheck(
|
|
145
|
+
prevProducts,
|
|
146
|
+
result,
|
|
147
|
+
(product) => product.id,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('Error fetching products:', error);
|
|
152
|
+
}
|
|
147
153
|
},
|
|
148
154
|
[mergeWithDuplicateCheck],
|
|
149
155
|
);
|
|
150
156
|
|
|
151
157
|
const getSubscriptionsInternal = useCallback(
|
|
152
158
|
async (skus: string[]): Promise<void> => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
try {
|
|
160
|
+
const result = await getSubscriptions(skus);
|
|
161
|
+
setSubscriptions((prevSubscriptions) =>
|
|
162
|
+
mergeWithDuplicateCheck(
|
|
163
|
+
prevSubscriptions,
|
|
164
|
+
result,
|
|
165
|
+
(subscription) => subscription.id,
|
|
166
|
+
),
|
|
167
|
+
);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error('Error fetching subscriptions:', error);
|
|
170
|
+
}
|
|
161
171
|
},
|
|
162
172
|
[mergeWithDuplicateCheck],
|
|
163
173
|
);
|
|
164
174
|
|
|
165
175
|
const getAvailablePurchasesInternal = useCallback(async (): Promise<void> => {
|
|
166
|
-
|
|
176
|
+
try {
|
|
177
|
+
const result = await getAvailablePurchases();
|
|
178
|
+
setAvailablePurchases(result);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error('Error fetching available purchases:', error);
|
|
181
|
+
}
|
|
167
182
|
}, []);
|
|
168
183
|
|
|
169
184
|
const getPurchaseHistoriesInternal = useCallback(async (): Promise<void> => {
|
|
@@ -177,7 +192,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
177
192
|
}: {
|
|
178
193
|
purchase: ProductPurchase;
|
|
179
194
|
isConsumable?: boolean;
|
|
180
|
-
}): Promise<
|
|
195
|
+
}): Promise<PurchaseResult | boolean> => {
|
|
181
196
|
try {
|
|
182
197
|
return await finishTransactionInternal({
|
|
183
198
|
purchase,
|
|
@@ -203,7 +218,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
203
218
|
);
|
|
204
219
|
|
|
205
220
|
const requestPurchaseWithReset = useCallback(
|
|
206
|
-
async (requestObj:
|
|
221
|
+
async (requestObj: {request: any; type?: 'inapp' | 'subs'}) => {
|
|
207
222
|
clearCurrentPurchase();
|
|
208
223
|
clearCurrentPurchaseError();
|
|
209
224
|
|
|
File without changes
|
package/docs/ERROR_CODES.md
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
# Error Code Management in expo-iap
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
expo-iap now provides a centralized error code management system that ensures consistent error handling across iOS and Android platforms. This system addresses the issue where different platforms used different error code formats (numeric codes on iOS, string codes on Android) and provides a unified approach.
|
|
6
|
-
|
|
7
|
-
## Problem Solved
|
|
8
|
-
|
|
9
|
-
Previously, users experienced inconsistent error codes:
|
|
10
|
-
|
|
11
|
-
- iOS returned numeric error codes (e.g., "2" for user cancellation)
|
|
12
|
-
- Android returned string error codes (e.g., "E_USER_CANCELLED" for user cancellation)
|
|
13
|
-
- The TypeScript enum didn't align with platform-specific implementations
|
|
14
|
-
|
|
15
|
-
## New Error Code System
|
|
16
|
-
|
|
17
|
-
### Centralized ErrorCode Enum
|
|
18
|
-
|
|
19
|
-
All error codes are now defined in a single `ErrorCode` enum:
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
export enum ErrorCode {
|
|
23
|
-
E_UNKNOWN = 'E_UNKNOWN',
|
|
24
|
-
E_USER_CANCELLED = 'E_USER_CANCELLED',
|
|
25
|
-
E_USER_ERROR = 'E_USER_ERROR',
|
|
26
|
-
E_ITEM_UNAVAILABLE = 'E_ITEM_UNAVAILABLE',
|
|
27
|
-
E_REMOTE_ERROR = 'E_REMOTE_ERROR',
|
|
28
|
-
E_NETWORK_ERROR = 'E_NETWORK_ERROR',
|
|
29
|
-
E_SERVICE_ERROR = 'E_SERVICE_ERROR',
|
|
30
|
-
// ... and more
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### Platform-Specific Mappings
|
|
35
|
-
|
|
36
|
-
The system automatically maps between the centralized enum and platform-specific codes:
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
export const ErrorCodeMapping = {
|
|
40
|
-
ios: {
|
|
41
|
-
[ErrorCode.E_USER_CANCELLED]: 2,
|
|
42
|
-
[ErrorCode.E_SERVICE_ERROR]: 1,
|
|
43
|
-
// ... more mappings
|
|
44
|
-
},
|
|
45
|
-
android: {
|
|
46
|
-
[ErrorCode.E_USER_CANCELLED]: 'E_USER_CANCELLED',
|
|
47
|
-
[ErrorCode.E_SERVICE_ERROR]: 'E_SERVICE_ERROR',
|
|
48
|
-
// ... more mappings
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Usage
|
|
54
|
-
|
|
55
|
-
### Basic Error Handling
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
import {ErrorCode, PurchaseError} from 'expo-iap';
|
|
59
|
-
|
|
60
|
-
// Handle purchase errors consistently
|
|
61
|
-
const handleError = (error: PurchaseError) => {
|
|
62
|
-
switch (error.code) {
|
|
63
|
-
case ErrorCode.E_USER_CANCELLED:
|
|
64
|
-
console.log('User cancelled the purchase');
|
|
65
|
-
break;
|
|
66
|
-
case ErrorCode.E_ITEM_UNAVAILABLE:
|
|
67
|
-
console.log('Item is not available');
|
|
68
|
-
break;
|
|
69
|
-
default:
|
|
70
|
-
console.log('Purchase failed:', error.message);
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Creating Errors from Platform Data
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
import {PurchaseError} from 'expo-iap';
|
|
79
|
-
|
|
80
|
-
// Create properly typed errors from platform-specific data
|
|
81
|
-
const error = PurchaseError.fromPlatformError(rawErrorData, 'ios');
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Error Code Utilities
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
import {ErrorCodeUtils, ErrorCode} from 'expo-iap';
|
|
88
|
-
|
|
89
|
-
// Convert platform-specific codes to standard enum
|
|
90
|
-
const errorCode = ErrorCodeUtils.fromPlatformCode(2, 'ios');
|
|
91
|
-
// Returns ErrorCode.E_USER_CANCELLED
|
|
92
|
-
|
|
93
|
-
// Convert standard enum to platform-specific code
|
|
94
|
-
const iosCode = ErrorCodeUtils.toPlatformCode(
|
|
95
|
-
ErrorCode.E_USER_CANCELLED,
|
|
96
|
-
'ios',
|
|
97
|
-
);
|
|
98
|
-
// Returns 2
|
|
99
|
-
|
|
100
|
-
// Check platform support
|
|
101
|
-
const isSupported = ErrorCodeUtils.isValidForPlatform(
|
|
102
|
-
ErrorCode.E_USER_CANCELLED,
|
|
103
|
-
'ios',
|
|
104
|
-
);
|
|
105
|
-
// Returns true
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## Migration Guide
|
|
109
|
-
|
|
110
|
-
### Before (Inconsistent)
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
// iOS would return numeric codes
|
|
114
|
-
if (error.code === '2') {
|
|
115
|
-
// Handle user cancellation
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Android would return string codes
|
|
119
|
-
if (error.code === 'E_USER_CANCELLED') {
|
|
120
|
-
// Handle user cancellation
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### After (Consistent)
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
import {ErrorCode} from 'expo-iap';
|
|
128
|
-
|
|
129
|
-
// Works consistently across platforms
|
|
130
|
-
if (error.code === ErrorCode.E_USER_CANCELLED) {
|
|
131
|
-
// Handle user cancellation on both iOS and Android
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Implementation Details
|
|
136
|
-
|
|
137
|
-
### iOS Changes
|
|
138
|
-
|
|
139
|
-
- Removed the `IapErrors` enum from `Types.swift` entirely to eliminate native error code duplication
|
|
140
|
-
- Updated all error throwing in `ExpoIapModule.swift` to use string error codes directly (e.g., "E_USER_CANCELLED", "E_SERVICE_ERROR")
|
|
141
|
-
- Fixed user cancellation error to return `E_USER_CANCELLED` instead of the previous numeric code "2"
|
|
142
|
-
- Native StoreKit errors are now passed through directly while custom errors use standardized string codes
|
|
143
|
-
|
|
144
|
-
### Android Changes
|
|
145
|
-
|
|
146
|
-
- Android implementation already used proper string codes
|
|
147
|
-
- No changes needed to Android error handling
|
|
148
|
-
|
|
149
|
-
### TypeScript Changes
|
|
150
|
-
|
|
151
|
-
- Added comprehensive `ErrorCode` enum with all error types
|
|
152
|
-
- Added `ErrorCodeMapping` for platform-specific code conversion
|
|
153
|
-
- Added `ErrorCodeUtils` utility functions for error code management
|
|
154
|
-
- Enhanced `PurchaseError` class with platform-aware error creation
|
|
155
|
-
|
|
156
|
-
## Benefits
|
|
157
|
-
|
|
158
|
-
1. **Consistency**: Same error codes across iOS and Android
|
|
159
|
-
2. **Type Safety**: Full TypeScript support with proper enums
|
|
160
|
-
3. **Maintainability**: Single source of truth for error codes
|
|
161
|
-
4. **Debugging**: Better error messages and debugging information
|
|
162
|
-
5. **Future-Proof**: Easy to add new error codes consistently
|
|
163
|
-
|
|
164
|
-
## Breaking Changes
|
|
165
|
-
|
|
166
|
-
This is a **non-breaking change** for most users:
|
|
167
|
-
|
|
168
|
-
- Existing error handling will continue to work
|
|
169
|
-
- New error code system provides additional functionality
|
|
170
|
-
- Users can migrate gradually to the new system
|
|
171
|
-
|
|
172
|
-
The only potential breaking change is for iOS users who were checking for specific numeric error codes like "2" for user cancellation, which now correctly returns `ErrorCode.E_USER_CANCELLED`.
|