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.
Files changed (43) hide show
  1. package/README.md +15 -19
  2. package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +10 -9
  3. package/build/ExpoIap.types.d.ts +31 -40
  4. package/build/ExpoIap.types.d.ts.map +1 -1
  5. package/build/ExpoIap.types.js +0 -11
  6. package/build/ExpoIap.types.js.map +1 -1
  7. package/build/index.d.ts +3 -4
  8. package/build/index.d.ts.map +1 -1
  9. package/build/index.js +33 -46
  10. package/build/index.js.map +1 -1
  11. package/build/modules/android.d.ts +12 -4
  12. package/build/modules/android.d.ts.map +1 -1
  13. package/build/modules/android.js +8 -4
  14. package/build/modules/android.js.map +1 -1
  15. package/build/modules/ios.d.ts +16 -8
  16. package/build/modules/ios.d.ts.map +1 -1
  17. package/build/modules/ios.js +9 -5
  18. package/build/modules/ios.js.map +1 -1
  19. package/build/types/ExpoIapAndroid.types.d.ts +31 -31
  20. package/build/types/ExpoIapAndroid.types.d.ts.map +1 -1
  21. package/build/types/ExpoIapAndroid.types.js +6 -0
  22. package/build/types/ExpoIapAndroid.types.js.map +1 -1
  23. package/build/types/ExpoIapIos.types.d.ts +31 -34
  24. package/build/types/ExpoIapIos.types.d.ts.map +1 -1
  25. package/build/types/ExpoIapIos.types.js.map +1 -1
  26. package/build/useIap.d.ts +23 -0
  27. package/build/useIap.d.ts.map +1 -0
  28. package/build/useIap.js +100 -0
  29. package/build/useIap.js.map +1 -0
  30. package/bun.lockb +0 -0
  31. package/iap.md +161 -0
  32. package/ios/ExpoIapModule.swift +124 -31
  33. package/package.json +5 -5
  34. package/plugin/build/withIAP.d.ts +0 -3
  35. package/plugin/build/withIAP.js +31 -49
  36. package/plugin/src/withIAP.ts +39 -86
  37. package/src/ExpoIap.types.ts +48 -48
  38. package/src/index.ts +50 -96
  39. package/src/modules/android.ts +16 -12
  40. package/src/modules/ios.ts +21 -18
  41. package/src/types/ExpoIapAndroid.types.ts +37 -33
  42. package/src/types/ExpoIapIos.types.ts +36 -40
  43. 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 SubscriptionOfferDetail = {
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
- export type ProductAndroid = {
29
- productId: string;
30
- title: string;
31
- description: string;
32
- productType: string;
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: {pricingPhaseList: PricingPhaseAndroid[]};
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
- id: string;
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
+ }