expo-iap 2.0.0 → 2.2.0-rc.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/README.md +15 -19
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +10 -9
- package/build/ExpoIap.types.d.ts +31 -40
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js +0 -11
- package/build/ExpoIap.types.js.map +1 -1
- package/build/index.d.ts +3 -4
- package/build/index.d.ts.map +1 -1
- package/build/index.js +33 -46
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +12 -4
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +8 -4
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +16 -8
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +9 -5
- package/build/modules/ios.js.map +1 -1
- package/build/types/ExpoIapAndroid.types.d.ts +31 -31
- package/build/types/ExpoIapAndroid.types.d.ts.map +1 -1
- package/build/types/ExpoIapAndroid.types.js +6 -0
- package/build/types/ExpoIapAndroid.types.js.map +1 -1
- package/build/types/ExpoIapIos.types.d.ts +31 -34
- package/build/types/ExpoIapIos.types.d.ts.map +1 -1
- package/build/types/ExpoIapIos.types.js.map +1 -1
- package/build/useIap.d.ts +23 -0
- package/build/useIap.d.ts.map +1 -0
- package/build/useIap.js +100 -0
- package/build/useIap.js.map +1 -0
- package/bun.lockb +0 -0
- package/iap.md +161 -0
- package/ios/ExpoIapModule.swift +124 -31
- package/package.json +5 -5
- package/plugin/build/withIAP.d.ts +0 -3
- package/plugin/build/withIAP.js +31 -49
- package/plugin/src/withIAP.ts +39 -86
- package/src/ExpoIap.types.ts +48 -48
- package/src/index.ts +50 -96
- package/src/modules/android.ts +16 -12
- package/src/modules/ios.ts +21 -18
- package/src/types/ExpoIapAndroid.types.ts +37 -33
- package/src/types/ExpoIapIos.types.ts +36 -40
- package/src/useIap.ts +185 -0
|
@@ -1,63 +1,48 @@
|
|
|
1
|
+
import {PurchaseBase, ProductBase} from '../ExpoIap.types';
|
|
2
|
+
|
|
1
3
|
type OneTimePurchaseOfferDetails = {
|
|
2
4
|
priceCurrencyCode: string;
|
|
3
5
|
formattedPrice: string;
|
|
4
6
|
priceAmountMicros: string;
|
|
5
7
|
};
|
|
6
8
|
|
|
7
|
-
type
|
|
8
|
-
basePlanId: string;
|
|
9
|
-
offerId: string;
|
|
10
|
-
offerToken: string;
|
|
11
|
-
offerTags: string[];
|
|
12
|
-
pricingPhases: PricingPhases;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
type PricingPhases = {
|
|
16
|
-
pricingPhaseList: PricingPhase[];
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type PricingPhase = {
|
|
9
|
+
type PricingPhaseAndroid = {
|
|
20
10
|
formattedPrice: string;
|
|
21
11
|
priceCurrencyCode: string;
|
|
12
|
+
// P1W, P1M, P1Y
|
|
22
13
|
billingPeriod: string;
|
|
23
14
|
billingCycleCount: number;
|
|
24
15
|
priceAmountMicros: string;
|
|
25
16
|
recurrenceMode: number;
|
|
26
17
|
};
|
|
27
18
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
type PricingPhasesAndroid = {
|
|
20
|
+
pricingPhaseList: PricingPhaseAndroid[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type SubscriptionOfferDetail = {
|
|
24
|
+
basePlanId: string;
|
|
25
|
+
offerId: string;
|
|
26
|
+
offerToken: string;
|
|
27
|
+
offerTags: string[];
|
|
28
|
+
pricingPhases: PricingPhasesAndroid;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type ProductAndroid = ProductBase & {
|
|
33
32
|
name: string;
|
|
34
33
|
oneTimePurchaseOfferDetails?: OneTimePurchaseOfferDetails;
|
|
35
34
|
subscriptionOfferDetails?: SubscriptionOfferDetail[];
|
|
36
35
|
};
|
|
37
36
|
|
|
38
|
-
type PricingPhaseAndroid = {
|
|
39
|
-
formattedPrice: string;
|
|
40
|
-
priceCurrencyCode: string;
|
|
41
|
-
// P1W, P1M, P1Y
|
|
42
|
-
billingPeriod: string;
|
|
43
|
-
billingCycleCount: number;
|
|
44
|
-
priceAmountMicros: string;
|
|
45
|
-
recurrenceMode: number;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
37
|
type SubscriptionOfferAndroid = {
|
|
49
38
|
basePlanId: string;
|
|
50
39
|
offerId: string | null;
|
|
51
40
|
offerToken: string;
|
|
52
|
-
pricingPhases:
|
|
41
|
+
pricingPhases: PricingPhasesAndroid;
|
|
53
42
|
offerTags: string[];
|
|
54
43
|
};
|
|
55
44
|
|
|
56
45
|
export type SubscriptionProductAndroid = ProductAndroid & {
|
|
57
|
-
name: string;
|
|
58
|
-
title: string;
|
|
59
|
-
description: string;
|
|
60
|
-
productId: string;
|
|
61
46
|
subscriptionOfferDetails: SubscriptionOfferAndroid[];
|
|
62
47
|
};
|
|
63
48
|
|
|
@@ -121,3 +106,22 @@ export enum FeatureTypeAndroid {
|
|
|
121
106
|
/** Subscriptions update/replace. */
|
|
122
107
|
SUBSCRIPTIONS_UPDATE = 'SUBSCRIPTIONS_UPDATE',
|
|
123
108
|
}
|
|
109
|
+
|
|
110
|
+
export enum PurchaseStateAndroid {
|
|
111
|
+
UNSPECIFIED_STATE = 0,
|
|
112
|
+
PURCHASED = 1,
|
|
113
|
+
PENDING = 2,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export type ProductPurchaseAndroid = PurchaseBase & {
|
|
117
|
+
ids?: string[];
|
|
118
|
+
dataAndroid?: string;
|
|
119
|
+
signatureAndroid?: string;
|
|
120
|
+
autoRenewingAndroid?: boolean;
|
|
121
|
+
purchaseStateAndroid?: PurchaseStateAndroid;
|
|
122
|
+
isAcknowledgedAndroid?: boolean;
|
|
123
|
+
packageNameAndroid?: string;
|
|
124
|
+
developerPayloadAndroid?: string;
|
|
125
|
+
obfuscatedAccountIdAndroid?: string;
|
|
126
|
+
obfuscatedProfileIdAndroid?: string;
|
|
127
|
+
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {PurchaseBase, ProductBase} from '../ExpoIap.types';
|
|
2
|
+
|
|
1
3
|
type SubscriptionIosPeriod = 'DAY' | 'WEEK' | 'MONTH' | 'YEAR' | '';
|
|
2
4
|
type PaymentMode = '' | 'FREETRIAL' | 'PAYASYOUGO' | 'PAYUPFRONT';
|
|
3
5
|
|
|
@@ -18,22 +20,18 @@ type SubscriptionInfo = {
|
|
|
18
20
|
subscriptionPeriod: SubscriptionIosPeriod;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
|
-
export type ProductIos = {
|
|
22
|
-
currency: string;
|
|
23
|
-
description: string;
|
|
23
|
+
export type ProductIos = ProductBase & {
|
|
24
24
|
displayName: string;
|
|
25
25
|
displayPrice: string;
|
|
26
|
-
|
|
27
|
-
type: 'autoRenewable' | 'consumable' | 'nonConsumable' | 'nonRenewable';
|
|
26
|
+
type: 'autoRenewable' | 'consumable' | 'nonConsumable' | 'nonRenewable'; // Maps to 'type' in base Product
|
|
28
27
|
isFamilyShareable: boolean;
|
|
29
28
|
jsonRepresentation: string;
|
|
30
|
-
price: number;
|
|
31
29
|
subscription: SubscriptionInfo;
|
|
32
30
|
introductoryPriceNumberOfPeriodsIOS?: string;
|
|
33
31
|
introductoryPriceSubscriptionPeriodIOS?: SubscriptionIosPeriod;
|
|
34
32
|
};
|
|
35
33
|
|
|
36
|
-
type Discount = {
|
|
34
|
+
export type Discount = {
|
|
37
35
|
identifier: string;
|
|
38
36
|
type: string;
|
|
39
37
|
numberOfPeriods: string;
|
|
@@ -59,22 +57,18 @@ export type PaymentDiscount = {
|
|
|
59
57
|
* A string used to uniquely identify a discount offer for a product.
|
|
60
58
|
*/
|
|
61
59
|
identifier: string;
|
|
62
|
-
|
|
63
60
|
/**
|
|
64
61
|
* A string that identifies the key used to generate the signature.
|
|
65
62
|
*/
|
|
66
63
|
keyIdentifier: string;
|
|
67
|
-
|
|
68
64
|
/**
|
|
69
65
|
* A universally unique ID (UUID) value that you define.
|
|
70
66
|
*/
|
|
71
67
|
nonce: string;
|
|
72
|
-
|
|
73
68
|
/**
|
|
74
69
|
* A UTF-8 string representing the properties of a specific discount offer, cryptographically signed.
|
|
75
70
|
*/
|
|
76
71
|
signature: string;
|
|
77
|
-
|
|
78
72
|
/**
|
|
79
73
|
* The date and time of the signature's creation in milliseconds, formatted in Unix epoch time.
|
|
80
74
|
*/
|
|
@@ -83,7 +77,6 @@ export type PaymentDiscount = {
|
|
|
83
77
|
|
|
84
78
|
export type RequestPurchaseIosProps = {
|
|
85
79
|
sku: string;
|
|
86
|
-
andDangerouslyFinishTransactionAutomaticallyIOS?: boolean;
|
|
87
80
|
/**
|
|
88
81
|
* UUID representing user account
|
|
89
82
|
*/
|
|
@@ -94,34 +87,6 @@ export type RequestPurchaseIosProps = {
|
|
|
94
87
|
|
|
95
88
|
export type RequestSubscriptionIosProps = RequestPurchaseIosProps;
|
|
96
89
|
|
|
97
|
-
export type TransactionSk2 = {
|
|
98
|
-
appAccountToken: string;
|
|
99
|
-
appBundleID: string;
|
|
100
|
-
debugDescription: string;
|
|
101
|
-
deviceVerification: string;
|
|
102
|
-
deviceVerificationNonce: string;
|
|
103
|
-
expirationDate: number;
|
|
104
|
-
environment?: 'Production' | 'Sandbox' | 'Xcode'; // Could be undefined in some cases on iOS 15, but it's stable since iOS 16
|
|
105
|
-
id: number;
|
|
106
|
-
isUpgraded: boolean;
|
|
107
|
-
jsonRepresentation: string;
|
|
108
|
-
offerID: string;
|
|
109
|
-
offerType: string;
|
|
110
|
-
originalID: string;
|
|
111
|
-
originalPurchaseDate: number;
|
|
112
|
-
ownershipType: string;
|
|
113
|
-
productID: string;
|
|
114
|
-
productType: string;
|
|
115
|
-
purchaseDate: number;
|
|
116
|
-
purchasedQuantity: number;
|
|
117
|
-
revocationDate: number;
|
|
118
|
-
revocationReason: string;
|
|
119
|
-
signedDate: number;
|
|
120
|
-
subscriptionGroupID: number;
|
|
121
|
-
webOrderLineItemID: number;
|
|
122
|
-
verificationResult?: string;
|
|
123
|
-
};
|
|
124
|
-
|
|
125
90
|
type SubscriptionStatus =
|
|
126
91
|
| 'expired'
|
|
127
92
|
| 'inBillingRetryPeriod'
|
|
@@ -139,3 +104,34 @@ export type ProductStatusIos = {
|
|
|
139
104
|
state: SubscriptionStatus;
|
|
140
105
|
renewalInfo?: RenewalInfo;
|
|
141
106
|
};
|
|
107
|
+
|
|
108
|
+
export type ProductPurchaseIos = PurchaseBase & {
|
|
109
|
+
// iOS basic fields
|
|
110
|
+
quantityIos?: number;
|
|
111
|
+
originalTransactionDateIos?: number;
|
|
112
|
+
originalTransactionIdentifierIos?: string;
|
|
113
|
+
verificationResultIos?: string;
|
|
114
|
+
appAccountToken?: string;
|
|
115
|
+
// iOS additional fields from StoreKit 2
|
|
116
|
+
expirationDateIos?: number;
|
|
117
|
+
webOrderLineItemIdIos?: number;
|
|
118
|
+
environmentIos?: string;
|
|
119
|
+
storefrontCountryCodeIos?: string;
|
|
120
|
+
appBundleIdIos?: string;
|
|
121
|
+
productTypeIos?: string;
|
|
122
|
+
subscriptionGroupIdIos?: string;
|
|
123
|
+
isUpgradedIos?: boolean;
|
|
124
|
+
ownershipTypeIos?: string;
|
|
125
|
+
reasonIos?: string;
|
|
126
|
+
reasonStringRepresentationIos?: string;
|
|
127
|
+
transactionReasonIos?: 'PURCHASE' | 'RENEWAL' | string;
|
|
128
|
+
revocationDateIos?: number;
|
|
129
|
+
revocationReasonIos?: string;
|
|
130
|
+
offerIos?: {
|
|
131
|
+
id: string;
|
|
132
|
+
type: string;
|
|
133
|
+
paymentMode: string;
|
|
134
|
+
};
|
|
135
|
+
priceIos?: number;
|
|
136
|
+
currencyIos?: string;
|
|
137
|
+
};
|
package/src/useIap.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
endConnection,
|
|
3
|
+
initConnection,
|
|
4
|
+
purchaseErrorListener,
|
|
5
|
+
purchaseUpdatedListener,
|
|
6
|
+
transactionUpdatedIos,
|
|
7
|
+
getProducts,
|
|
8
|
+
getAvailablePurchases,
|
|
9
|
+
getPurchaseHistory,
|
|
10
|
+
getSubscriptions,
|
|
11
|
+
} from './';
|
|
12
|
+
import {useCallback, useEffect, useState} from 'react';
|
|
13
|
+
import {
|
|
14
|
+
Product,
|
|
15
|
+
ProductPurchase,
|
|
16
|
+
Purchase,
|
|
17
|
+
PurchaseError,
|
|
18
|
+
PurchaseResult,
|
|
19
|
+
SubscriptionProduct,
|
|
20
|
+
SubscriptionPurchase,
|
|
21
|
+
} from './ExpoIap.types';
|
|
22
|
+
import {TransactionEvent} from './modules/ios';
|
|
23
|
+
import {Subscription} from 'expo-modules-core';
|
|
24
|
+
|
|
25
|
+
type IAP_STATUS = {
|
|
26
|
+
connected: boolean;
|
|
27
|
+
products: Product[];
|
|
28
|
+
promotedProductsIOS: ProductPurchase[];
|
|
29
|
+
subscriptions: SubscriptionProduct[];
|
|
30
|
+
purchaseHistories: ProductPurchase[];
|
|
31
|
+
availablePurchases: ProductPurchase[];
|
|
32
|
+
currentPurchase?: ProductPurchase;
|
|
33
|
+
currentPurchaseError?: PurchaseError;
|
|
34
|
+
finishTransaction: ({
|
|
35
|
+
purchase,
|
|
36
|
+
isConsumable,
|
|
37
|
+
developerPayloadAndroid,
|
|
38
|
+
}: {
|
|
39
|
+
purchase: Purchase;
|
|
40
|
+
isConsumable?: boolean;
|
|
41
|
+
developerPayloadAndroid?: string;
|
|
42
|
+
}) => Promise<string | boolean | PurchaseResult | void>;
|
|
43
|
+
getAvailablePurchases: () => Promise<void>;
|
|
44
|
+
getPurchaseHistories: () => Promise<void>;
|
|
45
|
+
getProducts: (skus: string[]) => Promise<void>;
|
|
46
|
+
getSubscriptions: (skus: string[]) => Promise<void>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
let purchaseUpdateSubscription: Subscription;
|
|
50
|
+
let purchaseErrorSubscription: Subscription;
|
|
51
|
+
let promotedProductsSubscription: Subscription;
|
|
52
|
+
|
|
53
|
+
export function useIAP(): IAP_STATUS {
|
|
54
|
+
const [connected, setConnected] = useState<boolean>(false);
|
|
55
|
+
const [products, setProducts] = useState<Product[]>([]);
|
|
56
|
+
const [promotedProductsIOS, setPromotedProductsIOS] = useState<
|
|
57
|
+
ProductPurchase[]
|
|
58
|
+
>([]);
|
|
59
|
+
const [subscriptions, setSubscriptions] = useState<SubscriptionProduct[]>([]);
|
|
60
|
+
const [purchaseHistories, setPurchaseHistories] = useState<ProductPurchase[]>(
|
|
61
|
+
[],
|
|
62
|
+
);
|
|
63
|
+
const [availablePurchases, setAvailablePurchases] = useState<
|
|
64
|
+
ProductPurchase[]
|
|
65
|
+
>([]);
|
|
66
|
+
const [currentPurchase, setCurrentPurchase] = useState<ProductPurchase>();
|
|
67
|
+
const [currentPurchaseError, setCurrentPurchaseError] =
|
|
68
|
+
useState<PurchaseError>();
|
|
69
|
+
|
|
70
|
+
const requestProducts = useCallback(async (skus: string[]): Promise<void> => {
|
|
71
|
+
setProducts(await getProducts(skus));
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
const requestSubscriptions = useCallback(
|
|
75
|
+
async (skus: string[]): Promise<void> => {
|
|
76
|
+
setSubscriptions(await getSubscriptions(skus));
|
|
77
|
+
},
|
|
78
|
+
[],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const requestAvailablePurchases = useCallback(async (): Promise<void> => {
|
|
82
|
+
setAvailablePurchases(await getAvailablePurchases());
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const requestPurchaseHistories = useCallback(async (): Promise<void> => {
|
|
86
|
+
setPurchaseHistories(await getPurchaseHistory());
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
const finishTransaction = useCallback(
|
|
90
|
+
async ({
|
|
91
|
+
purchase,
|
|
92
|
+
isConsumable,
|
|
93
|
+
developerPayloadAndroid,
|
|
94
|
+
}: {
|
|
95
|
+
purchase: ProductPurchase;
|
|
96
|
+
isConsumable?: boolean;
|
|
97
|
+
developerPayloadAndroid?: string;
|
|
98
|
+
}): Promise<string | boolean | PurchaseResult | void> => {
|
|
99
|
+
try {
|
|
100
|
+
return await finishTransaction({
|
|
101
|
+
purchase,
|
|
102
|
+
isConsumable,
|
|
103
|
+
developerPayloadAndroid,
|
|
104
|
+
});
|
|
105
|
+
} catch (err) {
|
|
106
|
+
throw err;
|
|
107
|
+
} finally {
|
|
108
|
+
if (purchase.id === currentPurchase?.id) {
|
|
109
|
+
setCurrentPurchase(undefined);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (purchase.id === currentPurchaseError?.productId) { // Note that PurchaseError still uses productId
|
|
113
|
+
setCurrentPurchaseError(undefined);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
[
|
|
118
|
+
currentPurchase?.id,
|
|
119
|
+
currentPurchaseError?.productId,
|
|
120
|
+
setCurrentPurchase,
|
|
121
|
+
setCurrentPurchaseError,
|
|
122
|
+
],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const initIapWithSubscriptions = useCallback(async (): Promise<void> => {
|
|
126
|
+
const result = await initConnection();
|
|
127
|
+
|
|
128
|
+
setConnected(result);
|
|
129
|
+
|
|
130
|
+
if (result) {
|
|
131
|
+
purchaseUpdateSubscription = purchaseUpdatedListener(
|
|
132
|
+
async (purchase: Purchase | SubscriptionPurchase) => {
|
|
133
|
+
setCurrentPurchaseError(undefined);
|
|
134
|
+
setCurrentPurchase(purchase);
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
purchaseErrorSubscription = purchaseErrorListener(
|
|
139
|
+
(error: PurchaseError) => {
|
|
140
|
+
setCurrentPurchase(undefined);
|
|
141
|
+
setCurrentPurchaseError(error);
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
promotedProductsSubscription = transactionUpdatedIos(
|
|
146
|
+
(event: TransactionEvent) => {
|
|
147
|
+
setPromotedProductsIOS((prevProducts) =>
|
|
148
|
+
event.transaction
|
|
149
|
+
? [...prevProducts, event.transaction]
|
|
150
|
+
: prevProducts,
|
|
151
|
+
);
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
initIapWithSubscriptions();
|
|
159
|
+
|
|
160
|
+
return (): void => {
|
|
161
|
+
if (purchaseUpdateSubscription) purchaseUpdateSubscription.remove();
|
|
162
|
+
if (purchaseErrorSubscription) purchaseErrorSubscription.remove();
|
|
163
|
+
if (promotedProductsSubscription) promotedProductsSubscription.remove();
|
|
164
|
+
|
|
165
|
+
endConnection();
|
|
166
|
+
setConnected(false);
|
|
167
|
+
};
|
|
168
|
+
}, [initIapWithSubscriptions]);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
connected,
|
|
172
|
+
products,
|
|
173
|
+
promotedProductsIOS,
|
|
174
|
+
subscriptions,
|
|
175
|
+
purchaseHistories,
|
|
176
|
+
finishTransaction,
|
|
177
|
+
availablePurchases,
|
|
178
|
+
currentPurchase,
|
|
179
|
+
currentPurchaseError,
|
|
180
|
+
getProducts: requestProducts,
|
|
181
|
+
getSubscriptions: requestSubscriptions,
|
|
182
|
+
getAvailablePurchases: requestAvailablePurchases,
|
|
183
|
+
getPurchaseHistories: requestPurchaseHistories,
|
|
184
|
+
};
|
|
185
|
+
}
|