expo-iap 2.9.0-rc.4 → 2.9.1
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/CHANGELOG.md +16 -1
- package/CONTRIBUTING.md +2 -2
- package/README.md +3 -3
- package/build/ExpoIap.types.d.ts +33 -15
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js +64 -17
- package/build/ExpoIap.types.js.map +1 -1
- package/build/helpers/subscription.d.ts.map +1 -1
- package/build/helpers/subscription.js +2 -3
- package/build/helpers/subscription.js.map +1 -1
- package/build/index.d.ts +0 -13
- package/build/index.d.ts.map +1 -1
- package/build/index.js +22 -22
- package/build/index.js.map +1 -1
- package/build/modules/ios.d.ts +3 -7
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +1 -6
- package/build/modules/ios.js.map +1 -1
- package/build/types/ExpoIapAndroid.types.d.ts +0 -32
- package/build/types/ExpoIapAndroid.types.d.ts.map +1 -1
- package/build/types/ExpoIapAndroid.types.js +1 -5
- package/build/types/ExpoIapAndroid.types.js.map +1 -1
- package/build/types/ExpoIapIOS.types.d.ts +3 -27
- package/build/types/ExpoIapIOS.types.d.ts.map +1 -1
- package/build/types/ExpoIapIOS.types.js.map +1 -1
- package/build/useIAP.d.ts +1 -5
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +35 -26
- package/build/useIAP.js.map +1 -1
- package/build/utils/errorMapping.d.ts.map +1 -1
- package/build/utils/errorMapping.js +24 -0
- package/build/utils/errorMapping.js.map +1 -1
- package/ios/ExpoIap.podspec +1 -1
- package/ios/ExpoIapModule.swift +54 -34
- package/package.json +2 -3
- package/plugin/build/withIAP.js +8 -4
- package/plugin/build/withLocalOpenIAP.js +5 -1
- package/plugin/src/withIAP.ts +12 -7
- package/plugin/src/withLocalOpenIAP.ts +5 -1
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/ExpoIap.types.ts +84 -37
- package/src/helpers/subscription.ts +2 -3
- package/src/index.ts +25 -49
- package/src/modules/ios.ts +4 -9
- package/src/types/ExpoIapAndroid.types.ts +2 -36
- package/src/types/ExpoIapIOS.types.ts +3 -27
- package/src/useIAP.ts +41 -34
- package/src/utils/errorMapping.ts +24 -0
- package/ios/expoiap.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/expoiap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
package/src/useIAP.ts
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
purchaseUpdatedListener,
|
|
12
12
|
promotedProductListenerIOS,
|
|
13
13
|
getAvailablePurchases,
|
|
14
|
-
getPurchaseHistories,
|
|
15
14
|
finishTransaction as finishTransactionInternal,
|
|
16
15
|
requestPurchase as requestPurchaseInternal,
|
|
17
16
|
fetchProducts,
|
|
@@ -35,7 +34,13 @@ import {
|
|
|
35
34
|
SubscriptionProduct,
|
|
36
35
|
RequestPurchaseProps,
|
|
37
36
|
RequestSubscriptionProps,
|
|
37
|
+
ErrorCode,
|
|
38
38
|
} from './ExpoIap.types';
|
|
39
|
+
import {
|
|
40
|
+
getUserFriendlyErrorMessage,
|
|
41
|
+
isUserCancelledError,
|
|
42
|
+
isRecoverableError,
|
|
43
|
+
} from './utils/errorMapping';
|
|
39
44
|
|
|
40
45
|
type UseIap = {
|
|
41
46
|
connected: boolean;
|
|
@@ -43,7 +48,6 @@ type UseIap = {
|
|
|
43
48
|
promotedProductsIOS: Purchase[];
|
|
44
49
|
promotedProductIdIOS?: string;
|
|
45
50
|
subscriptions: SubscriptionProduct[];
|
|
46
|
-
purchaseHistories: Purchase[];
|
|
47
51
|
availablePurchases: Purchase[];
|
|
48
52
|
currentPurchase?: Purchase;
|
|
49
53
|
currentPurchaseError?: PurchaseError;
|
|
@@ -59,7 +63,6 @@ type UseIap = {
|
|
|
59
63
|
isConsumable?: boolean;
|
|
60
64
|
}) => Promise<PurchaseResult | boolean>;
|
|
61
65
|
getAvailablePurchases: (skus: string[]) => Promise<void>;
|
|
62
|
-
getPurchaseHistories: (skus: string[]) => Promise<void>;
|
|
63
66
|
fetchProducts: (params: {
|
|
64
67
|
skus: string[];
|
|
65
68
|
type?: 'inapp' | 'subs';
|
|
@@ -98,8 +101,6 @@ type UseIap = {
|
|
|
98
101
|
restorePurchases: () => Promise<void>;
|
|
99
102
|
getPromotedProductIOS: () => Promise<Product | null>;
|
|
100
103
|
requestPurchaseOnPromotedProductIOS: () => Promise<void>;
|
|
101
|
-
/** @deprecated Use requestPurchaseOnPromotedProductIOS instead */
|
|
102
|
-
buyPromotedProductIOS: () => Promise<void>;
|
|
103
104
|
getActiveSubscriptions: (subscriptionIds?: string[]) => Promise<void>;
|
|
104
105
|
hasActiveSubscriptions: (subscriptionIds?: string[]) => Promise<boolean>;
|
|
105
106
|
};
|
|
@@ -114,14 +115,14 @@ export interface UseIAPOptions {
|
|
|
114
115
|
|
|
115
116
|
/**
|
|
116
117
|
* React Hook for managing In-App Purchases.
|
|
117
|
-
* See documentation at https://expo-iap
|
|
118
|
+
* See documentation at https://hyochan.github.io/expo-iap/docs/hooks/useIAP
|
|
118
119
|
*/
|
|
119
120
|
export function useIAP(options?: UseIAPOptions): UseIap {
|
|
120
121
|
const [connected, setConnected] = useState<boolean>(false);
|
|
121
122
|
const [products, setProducts] = useState<Product[]>([]);
|
|
122
123
|
const [promotedProductsIOS] = useState<Purchase[]>([]);
|
|
123
124
|
const [subscriptions, setSubscriptions] = useState<SubscriptionProduct[]>([]);
|
|
124
|
-
|
|
125
|
+
// Removed in v2.9.0: purchaseHistories state and related API
|
|
125
126
|
const [availablePurchases, setAvailablePurchases] = useState<Purchase[]>([]);
|
|
126
127
|
const [currentPurchase, setCurrentPurchase] = useState<Purchase>();
|
|
127
128
|
const [promotedProductIOS, setPromotedProductIOS] = useState<Product>();
|
|
@@ -133,6 +134,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
133
134
|
>([]);
|
|
134
135
|
|
|
135
136
|
const optionsRef = useRef<UseIAPOptions | undefined>(options);
|
|
137
|
+
const connectedRef = useRef<boolean>(false);
|
|
136
138
|
|
|
137
139
|
// Helper function to merge arrays with duplicate checking
|
|
138
140
|
const mergeWithDuplicateCheck = useCallback(
|
|
@@ -159,6 +161,10 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
159
161
|
optionsRef.current = options;
|
|
160
162
|
}, [options]);
|
|
161
163
|
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
connectedRef.current = connected;
|
|
166
|
+
}, [connected]);
|
|
167
|
+
|
|
162
168
|
const subscriptionsRef = useRef<{
|
|
163
169
|
purchaseUpdate?: EventSubscription;
|
|
164
170
|
purchaseError?: EventSubscription;
|
|
@@ -298,13 +304,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
298
304
|
[],
|
|
299
305
|
);
|
|
300
306
|
|
|
301
|
-
|
|
302
|
-
* @deprecated Use getAvailablePurchases instead. This function is just calling getAvailablePurchases internally.
|
|
303
|
-
* Will be removed in v2.9.0
|
|
304
|
-
*/
|
|
305
|
-
const getPurchaseHistoriesInternal = useCallback(async (): Promise<void> => {
|
|
306
|
-
setPurchaseHistories(await getPurchaseHistories());
|
|
307
|
-
}, []);
|
|
307
|
+
// NOTE: getPurchaseHistories removed in v2.9.0. Use getAvailablePurchases instead.
|
|
308
308
|
|
|
309
309
|
const finishTransaction = useCallback(
|
|
310
310
|
async ({
|
|
@@ -420,9 +420,32 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
420
420
|
},
|
|
421
421
|
);
|
|
422
422
|
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
423
|
+
// Register purchase error listener EARLY. Ignore init-related errors until connected.
|
|
424
|
+
subscriptionsRef.current.purchaseError = purchaseErrorListener(
|
|
425
|
+
(error: PurchaseError) => {
|
|
426
|
+
if (
|
|
427
|
+
!connectedRef.current &&
|
|
428
|
+
error.code === ErrorCode.E_INIT_CONNECTION
|
|
429
|
+
) {
|
|
430
|
+
return; // Ignore initialization error before connected
|
|
431
|
+
}
|
|
432
|
+
const friendly = getUserFriendlyErrorMessage(error);
|
|
433
|
+
console.log('[useIAP] Purchase error callback triggered:', error);
|
|
434
|
+
if (isUserCancelledError(error)) {
|
|
435
|
+
console.log('[useIAP] User cancelled purchase');
|
|
436
|
+
} else if (isRecoverableError(error)) {
|
|
437
|
+
console.log('[useIAP] Recoverable purchase error:', friendly);
|
|
438
|
+
} else {
|
|
439
|
+
console.warn('[useIAP] Purchase error:', friendly);
|
|
440
|
+
}
|
|
441
|
+
setCurrentPurchase(undefined);
|
|
442
|
+
setCurrentPurchaseError(error);
|
|
443
|
+
|
|
444
|
+
if (optionsRef.current?.onPurchaseError) {
|
|
445
|
+
optionsRef.current.onPurchaseError(error);
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
);
|
|
426
449
|
|
|
427
450
|
if (Platform.OS === 'ios') {
|
|
428
451
|
// iOS promoted products listener
|
|
@@ -453,22 +476,9 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
453
476
|
subscriptionsRef.current.promotedProductsIOS?.remove();
|
|
454
477
|
subscriptionsRef.current.purchaseUpdate = undefined;
|
|
455
478
|
subscriptionsRef.current.promotedProductsIOS = undefined;
|
|
456
|
-
//
|
|
479
|
+
// Keep purchaseError listener registered to capture subsequent retries
|
|
457
480
|
return;
|
|
458
481
|
}
|
|
459
|
-
|
|
460
|
-
// Now that the connection is established, register the purchase error listener.
|
|
461
|
-
subscriptionsRef.current.purchaseError = purchaseErrorListener(
|
|
462
|
-
(error: PurchaseError) => {
|
|
463
|
-
console.log('[useIAP] Purchase error callback triggered:', error);
|
|
464
|
-
setCurrentPurchase(undefined);
|
|
465
|
-
setCurrentPurchaseError(error);
|
|
466
|
-
|
|
467
|
-
if (optionsRef.current?.onPurchaseError) {
|
|
468
|
-
optionsRef.current.onPurchaseError(error);
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
);
|
|
472
482
|
}, [refreshSubscriptionStatus]);
|
|
473
483
|
|
|
474
484
|
useEffect(() => {
|
|
@@ -491,7 +501,6 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
491
501
|
promotedProductsIOS,
|
|
492
502
|
promotedProductIdIOS,
|
|
493
503
|
subscriptions,
|
|
494
|
-
purchaseHistories,
|
|
495
504
|
finishTransaction,
|
|
496
505
|
availablePurchases,
|
|
497
506
|
currentPurchase,
|
|
@@ -501,7 +510,6 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
501
510
|
clearCurrentPurchase,
|
|
502
511
|
clearCurrentPurchaseError,
|
|
503
512
|
getAvailablePurchases: getAvailablePurchasesInternal,
|
|
504
|
-
getPurchaseHistories: getPurchaseHistoriesInternal,
|
|
505
513
|
fetchProducts: fetchProductsInternal,
|
|
506
514
|
requestProducts: requestProductsInternal,
|
|
507
515
|
requestPurchase: requestPurchaseWithReset,
|
|
@@ -511,7 +519,6 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
511
519
|
getSubscriptions: getSubscriptionsInternal,
|
|
512
520
|
getPromotedProductIOS,
|
|
513
521
|
requestPurchaseOnPromotedProductIOS,
|
|
514
|
-
buyPromotedProductIOS: requestPurchaseOnPromotedProductIOS, // deprecated alias
|
|
515
522
|
getActiveSubscriptions: getActiveSubscriptionsInternal,
|
|
516
523
|
hasActiveSubscriptions: hasActiveSubscriptionsInternal,
|
|
517
524
|
};
|
|
@@ -32,6 +32,8 @@ export function isNetworkError(error: any): boolean {
|
|
|
32
32
|
ErrorCode.E_NETWORK_ERROR,
|
|
33
33
|
ErrorCode.E_REMOTE_ERROR,
|
|
34
34
|
ErrorCode.E_SERVICE_ERROR,
|
|
35
|
+
ErrorCode.E_SERVICE_DISCONNECTED,
|
|
36
|
+
ErrorCode.E_BILLING_UNAVAILABLE,
|
|
35
37
|
];
|
|
36
38
|
|
|
37
39
|
const errorCode = typeof error === 'string' ? error : error?.code;
|
|
@@ -49,6 +51,10 @@ export function isRecoverableError(error: any): boolean {
|
|
|
49
51
|
ErrorCode.E_REMOTE_ERROR,
|
|
50
52
|
ErrorCode.E_SERVICE_ERROR,
|
|
51
53
|
ErrorCode.E_INTERRUPTED,
|
|
54
|
+
ErrorCode.E_SERVICE_DISCONNECTED,
|
|
55
|
+
ErrorCode.E_BILLING_UNAVAILABLE,
|
|
56
|
+
ErrorCode.E_QUERY_PRODUCT,
|
|
57
|
+
ErrorCode.E_INIT_CONNECTION,
|
|
52
58
|
];
|
|
53
59
|
|
|
54
60
|
const errorCode = typeof error === 'string' ? error : error?.code;
|
|
@@ -68,20 +74,38 @@ export function getUserFriendlyErrorMessage(error: any): string {
|
|
|
68
74
|
return 'Purchase was cancelled by user';
|
|
69
75
|
case ErrorCode.E_NETWORK_ERROR:
|
|
70
76
|
return 'Network connection error. Please check your internet connection and try again.';
|
|
77
|
+
case ErrorCode.E_SERVICE_DISCONNECTED:
|
|
78
|
+
return 'Billing service disconnected. Please try again.';
|
|
79
|
+
case ErrorCode.E_BILLING_UNAVAILABLE:
|
|
80
|
+
return 'Billing is unavailable on this device or account.';
|
|
71
81
|
case ErrorCode.E_ITEM_UNAVAILABLE:
|
|
72
82
|
return 'This item is not available for purchase';
|
|
83
|
+
case ErrorCode.E_ITEM_NOT_OWNED:
|
|
84
|
+
return "You don't own this item";
|
|
73
85
|
case ErrorCode.E_ALREADY_OWNED:
|
|
74
86
|
return 'You already own this item';
|
|
87
|
+
case ErrorCode.E_SKU_NOT_FOUND:
|
|
88
|
+
return 'Requested product could not be found';
|
|
89
|
+
case ErrorCode.E_SKU_OFFER_MISMATCH:
|
|
90
|
+
return 'Selected offer does not match the SKU';
|
|
75
91
|
case ErrorCode.E_DEFERRED_PAYMENT:
|
|
76
92
|
return 'Payment is pending approval';
|
|
77
93
|
case ErrorCode.E_NOT_PREPARED:
|
|
78
94
|
return 'In-app purchase is not ready. Please try again later.';
|
|
79
95
|
case ErrorCode.E_SERVICE_ERROR:
|
|
80
96
|
return 'Store service error. Please try again later.';
|
|
97
|
+
case ErrorCode.E_FEATURE_NOT_SUPPORTED:
|
|
98
|
+
return 'This feature is not supported on this device.';
|
|
81
99
|
case ErrorCode.E_TRANSACTION_VALIDATION_FAILED:
|
|
82
100
|
return 'Transaction could not be verified';
|
|
83
101
|
case ErrorCode.E_RECEIPT_FAILED:
|
|
84
102
|
return 'Receipt processing failed';
|
|
103
|
+
case ErrorCode.E_EMPTY_SKU_LIST:
|
|
104
|
+
return 'No product IDs provided';
|
|
105
|
+
case ErrorCode.E_INIT_CONNECTION:
|
|
106
|
+
return 'Failed to initialize billing connection';
|
|
107
|
+
case ErrorCode.E_QUERY_PRODUCT:
|
|
108
|
+
return 'Failed to query products. Please try again later.';
|
|
85
109
|
default:
|
|
86
110
|
return error?.message || 'An unexpected error occurred';
|
|
87
111
|
}
|