expo-iap 2.2.0-rc.1 → 2.2.0-rc.3
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/build/ExpoIap.types.js +0 -1
- package/build/ExpoIap.types.ts +125 -0
- package/build/ExpoIapModule.js +0 -1
- package/build/ExpoIapModule.ts +5 -0
- package/build/index.js +0 -1
- package/build/index.ts +354 -0
- package/build/modules/android.js +0 -1
- package/build/modules/android.ts +99 -0
- package/build/modules/ios.js +0 -1
- package/build/modules/ios.ts +84 -0
- package/build/types/ExpoIapAndroid.types.js +0 -1
- package/build/types/ExpoIapAndroid.types.ts +127 -0
- package/build/types/ExpoIapIos.types.js +0 -1
- package/build/types/ExpoIapIos.types.ts +136 -0
- package/build/useIap.js +0 -1
- package/build/useIap.ts +185 -0
- package/package.json +6 -5
- package/src/types/ExpoIapIos.types.ts +0 -1
- package/tsconfig.json +6 -1
- package/build/ExpoIap.types.d.ts +0 -80
- package/build/ExpoIap.types.d.ts.map +0 -1
- package/build/ExpoIap.types.js.map +0 -1
- package/build/ExpoIapModule.d.ts +0 -3
- package/build/ExpoIapModule.d.ts.map +0 -1
- package/build/ExpoIapModule.js.map +0 -1
- package/build/index.d.ts +0 -37
- package/build/index.d.ts.map +0 -1
- package/build/index.js.map +0 -1
- package/build/modules/android.d.ts +0 -48
- package/build/modules/android.d.ts.map +0 -1
- package/build/modules/android.js.map +0 -1
- package/build/modules/ios.d.ts +0 -49
- package/build/modules/ios.d.ts.map +0 -1
- package/build/modules/ios.js.map +0 -1
- package/build/types/ExpoIapAndroid.types.d.ts +0 -113
- package/build/types/ExpoIapAndroid.types.d.ts.map +0 -1
- package/build/types/ExpoIapAndroid.types.js.map +0 -1
- package/build/types/ExpoIapIos.types.d.ts +0 -119
- package/build/types/ExpoIapIos.types.d.ts.map +0 -1
- package/build/types/ExpoIapIos.types.js.map +0 -1
- package/build/useIap.d.ts +0 -23
- package/build/useIap.d.ts.map +0 -1
- package/build/useIap.js.map +0 -1
package/build/ExpoIap.types.js
CHANGED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProductAndroid,
|
|
3
|
+
ProductPurchaseAndroid,
|
|
4
|
+
RequestPurchaseAndroidProps,
|
|
5
|
+
RequestSubscriptionAndroidProps,
|
|
6
|
+
SubscriptionProductAndroid,
|
|
7
|
+
} from './types/ExpoIapAndroid.types';
|
|
8
|
+
import {
|
|
9
|
+
ProductIos,
|
|
10
|
+
ProductPurchaseIos,
|
|
11
|
+
RequestPurchaseIosProps,
|
|
12
|
+
RequestSubscriptionIosProps,
|
|
13
|
+
SubscriptionProductIos,
|
|
14
|
+
} from './types/ExpoIapIos.types';
|
|
15
|
+
|
|
16
|
+
export type ChangeEventPayload = {
|
|
17
|
+
value: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Base product type with common properties shared between iOS and Android
|
|
22
|
+
*/
|
|
23
|
+
export type ProductBase = {
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
description: string;
|
|
27
|
+
type: ProductType;
|
|
28
|
+
displayName?: string;
|
|
29
|
+
displayPrice?: string;
|
|
30
|
+
price?: number;
|
|
31
|
+
currency?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Define literal platform types for better type discrimination
|
|
35
|
+
export type IosPlatform = {platform: 'ios'};
|
|
36
|
+
export type AndroidPlatform = {platform: 'android'};
|
|
37
|
+
|
|
38
|
+
export enum ProductType {
|
|
39
|
+
InAppPurchase = 'inapp',
|
|
40
|
+
Subscription = 'subs',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Common base purchase type
|
|
44
|
+
export type PurchaseBase = {
|
|
45
|
+
id: string;
|
|
46
|
+
transactionId?: string;
|
|
47
|
+
transactionDate: number;
|
|
48
|
+
transactionReceipt: string;
|
|
49
|
+
purchaseToken?: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Union type for platform-specific product types with proper discriminators
|
|
53
|
+
export type Product =
|
|
54
|
+
| (ProductAndroid & AndroidPlatform)
|
|
55
|
+
| (ProductIos & IosPlatform);
|
|
56
|
+
|
|
57
|
+
// Union type for platform-specific purchase types with proper discriminators
|
|
58
|
+
export type ProductPurchase =
|
|
59
|
+
| (ProductPurchaseAndroid & AndroidPlatform)
|
|
60
|
+
| (ProductPurchaseIos & IosPlatform);
|
|
61
|
+
|
|
62
|
+
// Union type for platform-specific subscription purchase types with proper discriminators
|
|
63
|
+
export type SubscriptionPurchase =
|
|
64
|
+
| (ProductPurchaseAndroid & AndroidPlatform & { autoRenewingAndroid: boolean })
|
|
65
|
+
| (ProductPurchaseIos & IosPlatform);
|
|
66
|
+
|
|
67
|
+
export type Purchase = ProductPurchase | SubscriptionPurchase;
|
|
68
|
+
|
|
69
|
+
export type RequestPurchaseProps =
|
|
70
|
+
| RequestPurchaseIosProps
|
|
71
|
+
| RequestPurchaseAndroidProps;
|
|
72
|
+
|
|
73
|
+
export type SubscriptionProduct =
|
|
74
|
+
| (SubscriptionProductAndroid & AndroidPlatform)
|
|
75
|
+
| (SubscriptionProductIos & IosPlatform);
|
|
76
|
+
|
|
77
|
+
export type RequestSubscriptionProps =
|
|
78
|
+
| RequestSubscriptionAndroidProps
|
|
79
|
+
| RequestSubscriptionIosProps;
|
|
80
|
+
|
|
81
|
+
export type PurchaseResult = {
|
|
82
|
+
responseCode?: number;
|
|
83
|
+
debugMessage?: string;
|
|
84
|
+
code?: string;
|
|
85
|
+
message?: string;
|
|
86
|
+
purchaseToken?: string;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export enum ErrorCode {
|
|
90
|
+
E_UNKNOWN = 'E_UNKNOWN',
|
|
91
|
+
E_USER_CANCELLED = 'E_USER_CANCELLED',
|
|
92
|
+
E_USER_ERROR = 'E_USER_ERROR',
|
|
93
|
+
E_ITEM_UNAVAILABLE = 'E_ITEM_UNAVAILABLE',
|
|
94
|
+
E_REMOTE_ERROR = 'E_REMOTE_ERROR',
|
|
95
|
+
E_NETWORK_ERROR = 'E_NETWORK_ERROR',
|
|
96
|
+
E_SERVICE_ERROR = 'E_SERVICE_ERROR',
|
|
97
|
+
E_RECEIPT_FAILED = 'E_RECEIPT_FAILED',
|
|
98
|
+
E_RECEIPT_FINISHED_FAILED = 'E_RECEIPT_FINISHED_FAILED',
|
|
99
|
+
E_NOT_PREPARED = 'E_NOT_PREPARED',
|
|
100
|
+
E_NOT_ENDED = 'E_NOT_ENDED',
|
|
101
|
+
E_ALREADY_OWNED = 'E_ALREADY_OWNED',
|
|
102
|
+
E_DEVELOPER_ERROR = 'E_DEVELOPER_ERROR',
|
|
103
|
+
E_BILLING_RESPONSE_JSON_PARSE_ERROR = 'E_BILLING_RESPONSE_JSON_PARSE_ERROR',
|
|
104
|
+
E_DEFERRED_PAYMENT = 'E_DEFERRED_PAYMENT',
|
|
105
|
+
E_INTERRUPTED = 'E_INTERRUPTED',
|
|
106
|
+
E_IAP_NOT_AVAILABLE = 'E_IAP_NOT_AVAILABLE',
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class PurchaseError implements Error {
|
|
110
|
+
constructor(
|
|
111
|
+
public name: string,
|
|
112
|
+
public message: string,
|
|
113
|
+
public responseCode?: number,
|
|
114
|
+
public debugMessage?: string,
|
|
115
|
+
public code?: ErrorCode,
|
|
116
|
+
public productId?: string,
|
|
117
|
+
) {
|
|
118
|
+
this.name = '[expo-iap]: PurchaseError';
|
|
119
|
+
this.message = message;
|
|
120
|
+
this.responseCode = responseCode;
|
|
121
|
+
this.debugMessage = debugMessage;
|
|
122
|
+
this.code = code;
|
|
123
|
+
this.productId = productId;
|
|
124
|
+
}
|
|
125
|
+
}
|
package/build/ExpoIapModule.js
CHANGED
|
@@ -2,4 +2,3 @@ import { requireNativeModule } from 'expo-modules-core';
|
|
|
2
2
|
// It loads the native module object from the JSI or falls back to
|
|
3
3
|
// the bridge module (from NativeModulesProxy) if the remote debugger is on.
|
|
4
4
|
export default requireNativeModule('ExpoIap');
|
|
5
|
-
//# sourceMappingURL=ExpoIapModule.js.map
|
package/build/index.js
CHANGED
package/build/index.ts
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
// Import the native module. On web, it will be resolved to ExpoIap.web.ts
|
|
2
|
+
// and on native platforms to ExpoIap.ts
|
|
3
|
+
import {NativeModulesProxy, EventEmitter} from 'expo-modules-core';
|
|
4
|
+
import {Platform} from 'react-native';
|
|
5
|
+
import {
|
|
6
|
+
Product,
|
|
7
|
+
ProductPurchase,
|
|
8
|
+
ProductType,
|
|
9
|
+
Purchase,
|
|
10
|
+
PurchaseError,
|
|
11
|
+
PurchaseResult,
|
|
12
|
+
RequestSubscriptionProps,
|
|
13
|
+
SubscriptionProduct,
|
|
14
|
+
SubscriptionPurchase,
|
|
15
|
+
} from './ExpoIap.types';
|
|
16
|
+
import ExpoIapModule from './ExpoIapModule';
|
|
17
|
+
import {
|
|
18
|
+
RequestPurchaseAndroidProps,
|
|
19
|
+
RequestSubscriptionAndroidProps,
|
|
20
|
+
} from './types/ExpoIapAndroid.types';
|
|
21
|
+
import {
|
|
22
|
+
PaymentDiscount,
|
|
23
|
+
RequestPurchaseIosProps,
|
|
24
|
+
RequestSubscriptionIosProps,
|
|
25
|
+
} from './types/ExpoIapIos.types';
|
|
26
|
+
import {isProductIos, isSubscriptionProductIos} from './modules/ios';
|
|
27
|
+
import {
|
|
28
|
+
isProductAndroid,
|
|
29
|
+
isSubscriptionProductAndroid,
|
|
30
|
+
} from './modules/android';
|
|
31
|
+
|
|
32
|
+
export * from './modules/android';
|
|
33
|
+
export * from './modules/ios';
|
|
34
|
+
|
|
35
|
+
// Get the native constant value.
|
|
36
|
+
export const PI = ExpoIapModule.PI;
|
|
37
|
+
|
|
38
|
+
export enum IapEvent {
|
|
39
|
+
PurchaseUpdated = 'purchase-updated',
|
|
40
|
+
PurchaseError = 'purchase-error',
|
|
41
|
+
TransactionIapUpdated = 'iap-transaction-updated',
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function setValueAsync(value: string) {
|
|
45
|
+
return await ExpoIapModule.setValueAsync(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const emitter = new EventEmitter(
|
|
49
|
+
ExpoIapModule ?? NativeModulesProxy.ExpoIap,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export const purchaseUpdatedListener = (
|
|
53
|
+
listener: (event: Purchase) => void,
|
|
54
|
+
) => {
|
|
55
|
+
const emitterSubscription = emitter.addListener(
|
|
56
|
+
IapEvent.PurchaseUpdated,
|
|
57
|
+
listener,
|
|
58
|
+
);
|
|
59
|
+
return emitterSubscription;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const purchaseErrorListener = (
|
|
63
|
+
listener: (error: PurchaseError) => void,
|
|
64
|
+
) => {
|
|
65
|
+
return emitter.addListener<PurchaseError>(IapEvent.PurchaseError, listener);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export function initConnection() {
|
|
69
|
+
return ExpoIapModule.initConnection();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const getProducts = async (skus: string[]): Promise<Product[]> => {
|
|
73
|
+
console.log('getProducts', skus);
|
|
74
|
+
if (!skus?.length) {
|
|
75
|
+
return Promise.reject(new Error('"skus" is required'));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return Platform.select({
|
|
79
|
+
ios: async () => {
|
|
80
|
+
const items = await ExpoIapModule.getItems(skus);
|
|
81
|
+
console.log('items', items);
|
|
82
|
+
return items.filter((item: unknown) => isProductIos<Product>(item));
|
|
83
|
+
},
|
|
84
|
+
android: async () => {
|
|
85
|
+
const products = await ExpoIapModule.getItemsByType(
|
|
86
|
+
ProductType.InAppPurchase,
|
|
87
|
+
skus,
|
|
88
|
+
);
|
|
89
|
+
return products.filter((product: unknown) =>
|
|
90
|
+
isProductAndroid<Product>(product),
|
|
91
|
+
);
|
|
92
|
+
},
|
|
93
|
+
default: () => Promise.reject(new Error('Unsupported Platform')),
|
|
94
|
+
})();
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const getSubscriptions = async (
|
|
98
|
+
skus: string[],
|
|
99
|
+
): Promise<SubscriptionProduct[]> => {
|
|
100
|
+
if (!skus?.length) {
|
|
101
|
+
return Promise.reject(new Error('"skus" is required'));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Platform.select({
|
|
105
|
+
ios: async () => {
|
|
106
|
+
const rawItems = await ExpoIapModule.getItems(skus);
|
|
107
|
+
|
|
108
|
+
return rawItems.filter((item: unknown) => {
|
|
109
|
+
if (!isSubscriptionProductIos(item)) return false;
|
|
110
|
+
return (
|
|
111
|
+
typeof item === 'object' &&
|
|
112
|
+
item !== null &&
|
|
113
|
+
'id' in item &&
|
|
114
|
+
typeof item.id === 'string' &&
|
|
115
|
+
skus.includes(item.id)
|
|
116
|
+
);
|
|
117
|
+
}) as SubscriptionProduct[];
|
|
118
|
+
},
|
|
119
|
+
android: async () => {
|
|
120
|
+
const rawItems = await ExpoIapModule.getItemsByType('subs', skus);
|
|
121
|
+
|
|
122
|
+
return rawItems.filter((item: unknown) => {
|
|
123
|
+
if (!isSubscriptionProductAndroid(item)) return false;
|
|
124
|
+
return (
|
|
125
|
+
typeof item === 'object' &&
|
|
126
|
+
item !== null &&
|
|
127
|
+
'id' in item &&
|
|
128
|
+
typeof item.id === 'string' &&
|
|
129
|
+
skus.includes(item.id)
|
|
130
|
+
);
|
|
131
|
+
}) as SubscriptionProduct[];
|
|
132
|
+
},
|
|
133
|
+
default: () => Promise.reject(new Error('Unsupported Platform')),
|
|
134
|
+
})();
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export async function endConnection(): Promise<boolean> {
|
|
138
|
+
return ExpoIapModule.endConnection();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const getPurchaseHistory = ({
|
|
142
|
+
alsoPublishToEventListener = false,
|
|
143
|
+
onlyIncludeActiveItems = false,
|
|
144
|
+
}: {
|
|
145
|
+
alsoPublishToEventListener?: boolean;
|
|
146
|
+
onlyIncludeActiveItems?: boolean;
|
|
147
|
+
} = {}): Promise<ProductPurchase[]> =>
|
|
148
|
+
(
|
|
149
|
+
Platform.select({
|
|
150
|
+
ios: async () => {
|
|
151
|
+
return ExpoIapModule.getAvailableItems(
|
|
152
|
+
alsoPublishToEventListener,
|
|
153
|
+
onlyIncludeActiveItems,
|
|
154
|
+
);
|
|
155
|
+
},
|
|
156
|
+
android: async () => {
|
|
157
|
+
const products = await ExpoIapModule.getPurchaseHistoryByType(
|
|
158
|
+
ProductType.InAppPurchase,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const subscriptions = await ExpoIapModule.getPurchaseHistoryByType(
|
|
162
|
+
ProductType.Subscription,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return products.concat(subscriptions);
|
|
166
|
+
},
|
|
167
|
+
}) || (() => Promise.resolve([]))
|
|
168
|
+
)();
|
|
169
|
+
|
|
170
|
+
export const getAvailablePurchases = ({
|
|
171
|
+
alsoPublishToEventListener = false,
|
|
172
|
+
onlyIncludeActiveItems = true,
|
|
173
|
+
}: {
|
|
174
|
+
alsoPublishToEventListener?: boolean;
|
|
175
|
+
onlyIncludeActiveItems?: boolean;
|
|
176
|
+
} = {}): Promise<ProductPurchase[]> =>
|
|
177
|
+
(
|
|
178
|
+
Platform.select({
|
|
179
|
+
ios: () =>
|
|
180
|
+
ExpoIapModule.getAvailableItems(
|
|
181
|
+
alsoPublishToEventListener,
|
|
182
|
+
onlyIncludeActiveItems,
|
|
183
|
+
),
|
|
184
|
+
android: async () => {
|
|
185
|
+
const products = await ExpoIapModule.getAvailableItemsByType(
|
|
186
|
+
ProductType.InAppPurchase,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const subscriptions = await ExpoIapModule.getAvailableItemsByType(
|
|
190
|
+
ProductType.Subscription,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return products.concat(subscriptions);
|
|
194
|
+
},
|
|
195
|
+
}) || (() => Promise.resolve([]))
|
|
196
|
+
)();
|
|
197
|
+
|
|
198
|
+
const offerToRecordIos = (
|
|
199
|
+
offer: PaymentDiscount | undefined,
|
|
200
|
+
): Record<keyof PaymentDiscount, string> | undefined => {
|
|
201
|
+
if (!offer) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
identifier: offer.identifier,
|
|
206
|
+
keyIdentifier: offer.keyIdentifier,
|
|
207
|
+
nonce: offer.nonce,
|
|
208
|
+
signature: offer.signature,
|
|
209
|
+
timestamp: offer.timestamp.toString(),
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const requestPurchase = (
|
|
214
|
+
request: RequestPurchaseIosProps | RequestPurchaseAndroidProps,
|
|
215
|
+
): Promise<ProductPurchase | ProductPurchase[] | void> =>
|
|
216
|
+
(
|
|
217
|
+
Platform.select({
|
|
218
|
+
ios: async () => {
|
|
219
|
+
if (!('sku' in request)) {
|
|
220
|
+
throw new Error('sku is required for iOS purchase');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const {sku, appAccountToken, quantity, withOffer} = request;
|
|
224
|
+
|
|
225
|
+
const offer = offerToRecordIos(withOffer);
|
|
226
|
+
|
|
227
|
+
const purchase = await ExpoIapModule.buyProduct(
|
|
228
|
+
sku,
|
|
229
|
+
appAccountToken,
|
|
230
|
+
quantity ?? -1,
|
|
231
|
+
offer,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
return Promise.resolve(purchase);
|
|
235
|
+
},
|
|
236
|
+
android: async () => {
|
|
237
|
+
if (!('skus' in request) || !request.skus.length) {
|
|
238
|
+
throw new Error('skus is required for Android purchase');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const {
|
|
242
|
+
skus,
|
|
243
|
+
obfuscatedAccountIdAndroid,
|
|
244
|
+
obfuscatedProfileIdAndroid,
|
|
245
|
+
isOfferPersonalized,
|
|
246
|
+
} = request;
|
|
247
|
+
|
|
248
|
+
return ExpoIapModule.buyItemByType({
|
|
249
|
+
type: ProductType.InAppPurchase,
|
|
250
|
+
skuArr: skus,
|
|
251
|
+
purchaseToken: undefined,
|
|
252
|
+
replacementMode: -1,
|
|
253
|
+
obfuscatedAccountId: obfuscatedAccountIdAndroid,
|
|
254
|
+
obfuscatedProfileId: obfuscatedProfileIdAndroid,
|
|
255
|
+
offerTokenArr: [],
|
|
256
|
+
isOfferPersonalized: isOfferPersonalized ?? false,
|
|
257
|
+
});
|
|
258
|
+
},
|
|
259
|
+
}) || Promise.resolve
|
|
260
|
+
)();
|
|
261
|
+
|
|
262
|
+
export const requestSubscription = (
|
|
263
|
+
request: RequestSubscriptionProps,
|
|
264
|
+
): Promise<SubscriptionPurchase | SubscriptionPurchase[] | null | void> =>
|
|
265
|
+
(
|
|
266
|
+
Platform.select({
|
|
267
|
+
ios: async () => {
|
|
268
|
+
if (!('sku' in request)) {
|
|
269
|
+
throw new Error('sku is required for iOS subscriptions');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const {sku, appAccountToken, quantity, withOffer} =
|
|
273
|
+
request as RequestSubscriptionIosProps;
|
|
274
|
+
|
|
275
|
+
const offer = offerToRecordIos(withOffer);
|
|
276
|
+
|
|
277
|
+
const purchase = await ExpoIapModule.buyProduct(
|
|
278
|
+
sku,
|
|
279
|
+
appAccountToken,
|
|
280
|
+
quantity ?? -1,
|
|
281
|
+
offer,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
return Promise.resolve(purchase as SubscriptionPurchase);
|
|
285
|
+
},
|
|
286
|
+
android: async () => {
|
|
287
|
+
const {
|
|
288
|
+
skus,
|
|
289
|
+
isOfferPersonalized,
|
|
290
|
+
obfuscatedAccountIdAndroid,
|
|
291
|
+
obfuscatedProfileIdAndroid,
|
|
292
|
+
subscriptionOffers,
|
|
293
|
+
replacementModeAndroid,
|
|
294
|
+
purchaseTokenAndroid,
|
|
295
|
+
} = request as RequestSubscriptionAndroidProps;
|
|
296
|
+
|
|
297
|
+
return ExpoIapModule.buyItemByType({
|
|
298
|
+
type: ProductType.Subscription,
|
|
299
|
+
skuArr: skus.map((so) => so),
|
|
300
|
+
purchaseToken: purchaseTokenAndroid,
|
|
301
|
+
replacementMode: replacementModeAndroid,
|
|
302
|
+
obfuscatedAccountId: obfuscatedAccountIdAndroid,
|
|
303
|
+
obfuscatedProfileId: obfuscatedProfileIdAndroid,
|
|
304
|
+
offerTokenArr: subscriptionOffers.map((so) => so.offerToken),
|
|
305
|
+
isOfferPersonalized: isOfferPersonalized ?? false,
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
}) || (() => Promise.resolve(null))
|
|
309
|
+
)();
|
|
310
|
+
|
|
311
|
+
export const finishTransaction = ({
|
|
312
|
+
purchase,
|
|
313
|
+
isConsumable,
|
|
314
|
+
developerPayloadAndroid,
|
|
315
|
+
}: {
|
|
316
|
+
purchase: Purchase;
|
|
317
|
+
isConsumable?: boolean;
|
|
318
|
+
developerPayloadAndroid?: string;
|
|
319
|
+
}): Promise<PurchaseResult | boolean> => {
|
|
320
|
+
return (
|
|
321
|
+
Platform.select({
|
|
322
|
+
ios: async () => {
|
|
323
|
+
const transactionId = purchase.transactionId;
|
|
324
|
+
|
|
325
|
+
if (!transactionId) {
|
|
326
|
+
return Promise.reject(
|
|
327
|
+
new Error('transactionId required to finish iOS transaction'),
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
await ExpoIapModule.finishTransaction(transactionId);
|
|
331
|
+
return Promise.resolve(true);
|
|
332
|
+
},
|
|
333
|
+
android: async () => {
|
|
334
|
+
if (purchase?.purchaseToken) {
|
|
335
|
+
if (!isConsumable) {
|
|
336
|
+
return Promise.reject(
|
|
337
|
+
new Error('purchase is not suitable to be purchased'),
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return ExpoIapModule.consumeProduct(
|
|
342
|
+
purchase.purchaseToken,
|
|
343
|
+
developerPayloadAndroid,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
return Promise.reject(
|
|
347
|
+
new Error('purchase is not suitable to be purchased'),
|
|
348
|
+
);
|
|
349
|
+
},
|
|
350
|
+
}) || (() => Promise.reject(new Error('Unsupported Platform')))
|
|
351
|
+
)();
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export * from './useIap';
|
package/build/modules/android.js
CHANGED
|
@@ -55,4 +55,3 @@ export const validateReceiptAndroid = async ({ packageName, productId, productTo
|
|
|
55
55
|
export const acknowledgePurchaseAndroid = ({ token, developerPayload, }) => {
|
|
56
56
|
return ExpoIapModule.acknowledgePurchase(token, developerPayload);
|
|
57
57
|
};
|
|
58
|
-
//# sourceMappingURL=android.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {Linking} from 'react-native';
|
|
2
|
+
import {PurchaseResult} from '../ExpoIap.types';
|
|
3
|
+
import {ReceiptAndroid} from '../types/ExpoIapAndroid.types';
|
|
4
|
+
import ExpoIapModule from '../ExpoIapModule';
|
|
5
|
+
|
|
6
|
+
// Type guards
|
|
7
|
+
export function isProductAndroid<T extends {platform?: string}>(
|
|
8
|
+
item: unknown,
|
|
9
|
+
): item is T & {platform: 'android'} {
|
|
10
|
+
return (
|
|
11
|
+
item != null &&
|
|
12
|
+
typeof item === 'object' &&
|
|
13
|
+
'platform' in item &&
|
|
14
|
+
item.platform === 'android'
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isSubscriptionProductAndroid<T extends {platform?: string}>(
|
|
19
|
+
item: unknown,
|
|
20
|
+
): item is T & {platform: 'android'} {
|
|
21
|
+
return isProductAndroid(item);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Deep link to subscriptions screen on Android.
|
|
26
|
+
* @param {string} sku The product's SKU (on Android)
|
|
27
|
+
* @returns {Promise<void>}
|
|
28
|
+
*/
|
|
29
|
+
export const deepLinkToSubscriptionsAndroid = async ({
|
|
30
|
+
sku,
|
|
31
|
+
}: {
|
|
32
|
+
sku: string;
|
|
33
|
+
}): Promise<void> => {
|
|
34
|
+
return Linking.openURL(
|
|
35
|
+
`https://play.google.com/store/account/subscriptions?package=${await ExpoIapModule.getPackageName()}&sku=${sku}`,
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate receipt for Android. NOTE: This method is here for debugging purposes only. Including
|
|
41
|
+
* your access token in the binary you ship to users is potentially dangerous.
|
|
42
|
+
* Use server side validation instead for your production builds
|
|
43
|
+
* @param {string} packageName package name of your app.
|
|
44
|
+
* @param {string} productId product id for your in app product.
|
|
45
|
+
* @param {string} productToken token for your purchase.
|
|
46
|
+
* @param {string} accessToken accessToken from googleApis.
|
|
47
|
+
* @param {boolean} isSub whether this is subscription or inapp. `true` for subscription.
|
|
48
|
+
* @returns {Promise<object>}
|
|
49
|
+
*/
|
|
50
|
+
export const validateReceiptAndroid = async ({
|
|
51
|
+
packageName,
|
|
52
|
+
productId,
|
|
53
|
+
productToken,
|
|
54
|
+
accessToken,
|
|
55
|
+
isSub,
|
|
56
|
+
}: {
|
|
57
|
+
packageName: string;
|
|
58
|
+
productId: string;
|
|
59
|
+
productToken: string;
|
|
60
|
+
accessToken: string;
|
|
61
|
+
isSub?: boolean;
|
|
62
|
+
}): Promise<ReceiptAndroid> => {
|
|
63
|
+
const type = isSub ? 'subscriptions' : 'products';
|
|
64
|
+
|
|
65
|
+
const url =
|
|
66
|
+
'https://androidpublisher.googleapis.com/androidpublisher/v3/applications' +
|
|
67
|
+
`/${packageName}/purchases/${type}/${productId}` +
|
|
68
|
+
`/tokens/${productToken}?access_token=${accessToken}`;
|
|
69
|
+
|
|
70
|
+
const response = await fetch(url, {
|
|
71
|
+
method: 'GET',
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'application/json',
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw Object.assign(new Error(response.statusText), {
|
|
79
|
+
statusCode: response.status,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return response.json();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Acknowledge a product (on Android.) No-op on iOS.
|
|
88
|
+
* @param {string} token The product's token (on Android)
|
|
89
|
+
* @returns {Promise<PurchaseResult | void>}
|
|
90
|
+
*/
|
|
91
|
+
export const acknowledgePurchaseAndroid = ({
|
|
92
|
+
token,
|
|
93
|
+
developerPayload,
|
|
94
|
+
}: {
|
|
95
|
+
token: string;
|
|
96
|
+
developerPayload?: string;
|
|
97
|
+
}): Promise<PurchaseResult | boolean | void> => {
|
|
98
|
+
return ExpoIapModule.acknowledgePurchase(token, developerPayload);
|
|
99
|
+
};
|
package/build/modules/ios.js
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {Platform} from 'react-native';
|
|
2
|
+
import {emitter, IapEvent} from '..';
|
|
3
|
+
import {ProductPurchase, PurchaseError} from '../ExpoIap.types';
|
|
4
|
+
import type {ProductStatusIos} from '../types/ExpoIapIos.types';
|
|
5
|
+
import ExpoIapModule from '../ExpoIapModule';
|
|
6
|
+
|
|
7
|
+
export type TransactionEvent = {
|
|
8
|
+
transaction?: ProductPurchase;
|
|
9
|
+
error?: PurchaseError;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Listeners
|
|
13
|
+
export const transactionUpdatedIos = (
|
|
14
|
+
listener: (event: TransactionEvent) => void,
|
|
15
|
+
) => {
|
|
16
|
+
if (Platform.OS !== 'ios') {
|
|
17
|
+
throw new Error('This method is only available on iOS');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return emitter.addListener(IapEvent.TransactionIapUpdated, listener);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Type guards
|
|
24
|
+
export function isProductIos<T extends {platform?: string}>(
|
|
25
|
+
item: unknown,
|
|
26
|
+
): item is T & {platform: 'ios'} {
|
|
27
|
+
return (
|
|
28
|
+
item != null &&
|
|
29
|
+
typeof item === 'object' &&
|
|
30
|
+
'platform' in item &&
|
|
31
|
+
item.platform === 'ios'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function isSubscriptionProductIos<T extends {platform?: string}>(
|
|
36
|
+
item: unknown,
|
|
37
|
+
): item is T & {platform: 'ios'} {
|
|
38
|
+
return isProductIos(item);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Functions
|
|
42
|
+
/**
|
|
43
|
+
* Sync state with Appstore (iOS only)
|
|
44
|
+
* https://developer.apple.com/documentation/storekit/appstore/3791906-sync
|
|
45
|
+
*/
|
|
46
|
+
export const sync = (): Promise<null> => ExpoIapModule.sync();
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
*/
|
|
51
|
+
export const isEligibleForIntroOffer = (groupID: string): Promise<boolean> =>
|
|
52
|
+
ExpoIapModule.isEligibleForIntroOffer(groupID);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
export const subscriptionStatus = (sku: string): Promise<ProductStatusIos[]> =>
|
|
59
|
+
ExpoIapModule.subscriptionStatus(sku);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
*
|
|
63
|
+
*/
|
|
64
|
+
export const currentEntitlement = (sku: string): Promise<ProductPurchase> =>
|
|
65
|
+
ExpoIapModule.currentEntitlement(sku);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
*
|
|
69
|
+
*/
|
|
70
|
+
export const latestTransaction = (sku: string): Promise<ProductPurchase> =>
|
|
71
|
+
ExpoIapModule.latestTransaction(sku);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
*
|
|
75
|
+
*/
|
|
76
|
+
type RefundRequestStatus = 'success' | 'userCancelled';
|
|
77
|
+
export const beginRefundRequest = (sku: string): Promise<RefundRequestStatus> =>
|
|
78
|
+
ExpoIapModule.beginRefundRequest(sku);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
*
|
|
82
|
+
*/
|
|
83
|
+
export const showManageSubscriptions = (): Promise<null> =>
|
|
84
|
+
ExpoIapModule.showManageSubscriptions();
|
|
@@ -26,4 +26,3 @@ export var PurchaseStateAndroid;
|
|
|
26
26
|
PurchaseStateAndroid[PurchaseStateAndroid["PURCHASED"] = 1] = "PURCHASED";
|
|
27
27
|
PurchaseStateAndroid[PurchaseStateAndroid["PENDING"] = 2] = "PENDING";
|
|
28
28
|
})(PurchaseStateAndroid || (PurchaseStateAndroid = {}));
|
|
29
|
-
//# sourceMappingURL=ExpoIapAndroid.types.js.map
|