@idealyst/payments 1.2.109 → 1.2.110
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/package.json +1 -1
- package/src/payments.native.ts +70 -37
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/payments",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.110",
|
|
4
4
|
"description": "Cross-platform In-App Purchase wrapper for React Native (StoreKit 2, Google Play Billing)",
|
|
5
5
|
"documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/payments#readme",
|
|
6
6
|
"readme": "README.md",
|
package/src/payments.native.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// Native IAP Implementation
|
|
3
|
-
// Wraps react-native-iap for StoreKit 2 (iOS) and Google Play Billing (Android)
|
|
3
|
+
// Wraps react-native-iap v14.x for StoreKit 2 (iOS) and Google Play Billing (Android)
|
|
4
|
+
//
|
|
5
|
+
// v14 API changes from earlier versions:
|
|
6
|
+
// - getProducts/getSubscriptions → fetchProducts({ skus, type })
|
|
7
|
+
// - requestPurchase/requestSubscription → requestPurchase({ request, type })
|
|
8
|
+
// - Product fields: id (not productId), displayPrice (not localizedPrice)
|
|
9
|
+
// - Purchase fields: productId, id (transaction ID)
|
|
4
10
|
// ============================================================================
|
|
5
11
|
|
|
6
12
|
import { Platform } from 'react-native';
|
|
@@ -61,15 +67,6 @@ export async function initializeIAP(config?: IAPConfig): Promise<void> {
|
|
|
61
67
|
try {
|
|
62
68
|
await RNIap.initConnection();
|
|
63
69
|
|
|
64
|
-
// Flush failed purchases cached as pending on Android
|
|
65
|
-
if (Platform.OS === 'android') {
|
|
66
|
-
try {
|
|
67
|
-
await RNIap.flushFailedPurchasesCachedAsPendingAndroid();
|
|
68
|
-
} catch {
|
|
69
|
-
// Non-critical — ignore flush errors
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
70
|
_status = {
|
|
74
71
|
state: 'ready',
|
|
75
72
|
isStoreAvailable: true,
|
|
@@ -90,8 +87,8 @@ export async function getProducts(skus: string[]): Promise<IAPProduct[]> {
|
|
|
90
87
|
assertReady();
|
|
91
88
|
|
|
92
89
|
try {
|
|
93
|
-
const products = await RNIap!.
|
|
94
|
-
return products.map(mapProduct);
|
|
90
|
+
const products = await RNIap!.fetchProducts({ skus, type: 'in-app' });
|
|
91
|
+
return products.map((p: unknown) => mapProduct(p, 'iap'));
|
|
95
92
|
} catch (error) {
|
|
96
93
|
throw normalizeError(error);
|
|
97
94
|
}
|
|
@@ -106,8 +103,8 @@ export async function getSubscriptions(
|
|
|
106
103
|
assertReady();
|
|
107
104
|
|
|
108
105
|
try {
|
|
109
|
-
const
|
|
110
|
-
return
|
|
106
|
+
const products = await RNIap!.fetchProducts({ skus, type: 'subs' });
|
|
107
|
+
return products.map(mapSubscription);
|
|
111
108
|
} catch (error) {
|
|
112
109
|
throw normalizeError(error);
|
|
113
110
|
}
|
|
@@ -120,13 +117,21 @@ export async function purchaseProduct(sku: string): Promise<IAPPurchase> {
|
|
|
120
117
|
assertReady();
|
|
121
118
|
|
|
122
119
|
try {
|
|
123
|
-
const purchase = await RNIap!.requestPurchase({
|
|
120
|
+
const purchase = await RNIap!.requestPurchase({
|
|
121
|
+
request: {
|
|
122
|
+
apple: { sku },
|
|
123
|
+
google: { skus: [sku] },
|
|
124
|
+
},
|
|
125
|
+
type: 'in-app',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const mapped = mapPurchase(purchase);
|
|
124
129
|
|
|
125
130
|
if (_config.autoFinishTransactions) {
|
|
126
131
|
await RNIap!.finishTransaction({ purchase, isConsumable: false });
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
return
|
|
134
|
+
return mapped;
|
|
130
135
|
} catch (error) {
|
|
131
136
|
throw normalizeError(error);
|
|
132
137
|
}
|
|
@@ -143,19 +148,27 @@ export async function purchaseSubscription(
|
|
|
143
148
|
assertReady();
|
|
144
149
|
|
|
145
150
|
try {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
|
|
151
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
152
|
+
const googleRequest: any = { skus: [sku] };
|
|
153
|
+
if (offerToken) {
|
|
154
|
+
googleRequest.offerToken = offerToken;
|
|
150
155
|
}
|
|
151
156
|
|
|
152
|
-
const purchase = await RNIap!.
|
|
157
|
+
const purchase = await RNIap!.requestPurchase({
|
|
158
|
+
request: {
|
|
159
|
+
apple: { sku },
|
|
160
|
+
google: googleRequest,
|
|
161
|
+
},
|
|
162
|
+
type: 'subs',
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const mapped = mapPurchase(purchase);
|
|
153
166
|
|
|
154
167
|
if (_config.autoFinishTransactions) {
|
|
155
168
|
await RNIap!.finishTransaction({ purchase, isConsumable: false });
|
|
156
169
|
}
|
|
157
170
|
|
|
158
|
-
return
|
|
171
|
+
return mapped;
|
|
159
172
|
} catch (error) {
|
|
160
173
|
throw normalizeError(error);
|
|
161
174
|
}
|
|
@@ -174,8 +187,14 @@ export async function finishTransaction(
|
|
|
174
187
|
assertReady();
|
|
175
188
|
|
|
176
189
|
try {
|
|
190
|
+
// finishTransaction expects the raw purchase object; we reconstruct the
|
|
191
|
+
// minimal shape that react-native-iap needs (id + productId + purchaseToken)
|
|
177
192
|
await RNIap!.finishTransaction({
|
|
178
|
-
purchase: {
|
|
193
|
+
purchase: {
|
|
194
|
+
id: purchase.transactionId,
|
|
195
|
+
productId: purchase.sku,
|
|
196
|
+
purchaseToken: purchase.purchaseToken,
|
|
197
|
+
},
|
|
179
198
|
isConsumable: isConsumable ?? false,
|
|
180
199
|
});
|
|
181
200
|
} catch (error) {
|
|
@@ -235,26 +254,25 @@ function getPlatform(): ProductPlatform {
|
|
|
235
254
|
}
|
|
236
255
|
|
|
237
256
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
238
|
-
function mapProduct(raw: any): IAPProduct {
|
|
257
|
+
function mapProduct(raw: any, type: 'iap' | 'sub' = 'iap'): IAPProduct {
|
|
239
258
|
return {
|
|
240
|
-
sku: raw.
|
|
241
|
-
title: raw.title ?? '',
|
|
259
|
+
sku: raw.id ?? raw.productId ?? '',
|
|
260
|
+
title: raw.title ?? raw.displayName ?? '',
|
|
242
261
|
description: raw.description ?? '',
|
|
243
|
-
price: parseFloat(raw.price ?? '0'),
|
|
244
|
-
priceFormatted: raw.localizedPrice ?? raw.price ?? '',
|
|
262
|
+
price: typeof raw.price === 'number' ? raw.price : parseFloat(raw.price ?? '0'),
|
|
263
|
+
priceFormatted: raw.displayPrice ?? raw.localizedPrice ?? String(raw.price ?? ''),
|
|
245
264
|
currency: raw.currency ?? '',
|
|
246
|
-
type
|
|
265
|
+
type,
|
|
247
266
|
platform: getPlatform(),
|
|
248
267
|
};
|
|
249
268
|
}
|
|
250
269
|
|
|
251
270
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
252
271
|
function mapSubscription(raw: any): IAPSubscription {
|
|
253
|
-
const base = mapProduct(raw);
|
|
272
|
+
const base = mapProduct(raw, 'sub');
|
|
254
273
|
|
|
255
274
|
return {
|
|
256
275
|
...base,
|
|
257
|
-
type: 'sub',
|
|
258
276
|
subscriptionPeriod: parseSubscriptionPeriod(raw),
|
|
259
277
|
introductoryPrice: raw.introductoryPrice
|
|
260
278
|
? mapDiscount(raw.introductoryPrice, 'introductory')
|
|
@@ -275,7 +293,19 @@ function parseSubscriptionPeriod(raw: any): SubscriptionPeriod {
|
|
|
275
293
|
};
|
|
276
294
|
}
|
|
277
295
|
|
|
278
|
-
// Android:
|
|
296
|
+
// Android: subscriptionOfferDetailsAndroid may contain period info
|
|
297
|
+
if (raw.subscriptionOfferDetailsAndroid?.length) {
|
|
298
|
+
const offer = raw.subscriptionOfferDetailsAndroid[0];
|
|
299
|
+
const pricingPhases = offer?.pricingPhases?.pricingPhaseList;
|
|
300
|
+
if (pricingPhases?.length) {
|
|
301
|
+
const billingPeriod = pricingPhases[0]?.billingPeriod;
|
|
302
|
+
if (billingPeriod) {
|
|
303
|
+
return parseISO8601Period(billingPeriod);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Fallback: subscriptionPeriodAndroid (older format)
|
|
279
309
|
if (raw.subscriptionPeriodAndroid) {
|
|
280
310
|
return parseISO8601Period(raw.subscriptionPeriodAndroid);
|
|
281
311
|
}
|
|
@@ -332,7 +362,7 @@ function mapDiscount(raw: any, type: 'introductory' | 'promotional'): Subscripti
|
|
|
332
362
|
return {
|
|
333
363
|
identifier: raw.identifier,
|
|
334
364
|
price: parseFloat(raw.price ?? '0'),
|
|
335
|
-
priceFormatted: raw.localizedPrice ?? raw.price ?? '',
|
|
365
|
+
priceFormatted: raw.displayPrice ?? raw.localizedPrice ?? raw.price ?? '',
|
|
336
366
|
period: raw.subscriptionPeriod
|
|
337
367
|
? parseISO8601Period(raw.subscriptionPeriod)
|
|
338
368
|
: { unit: 'month', numberOfUnits: 1 },
|
|
@@ -344,13 +374,16 @@ function mapDiscount(raw: any, type: 'introductory' | 'promotional'): Subscripti
|
|
|
344
374
|
|
|
345
375
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
346
376
|
function mapPurchase(raw: any, stateOverride?: IAPPurchase['purchaseState']): IAPPurchase {
|
|
377
|
+
// v14: transaction ID is in `id`, product is in `productId`
|
|
347
378
|
return {
|
|
348
|
-
sku: raw.productId ?? raw.
|
|
349
|
-
transactionId: raw.transactionId ?? '',
|
|
379
|
+
sku: raw.productId ?? raw.id ?? '',
|
|
380
|
+
transactionId: raw.id ?? raw.transactionId ?? '',
|
|
350
381
|
transactionDate: raw.transactionDate
|
|
351
|
-
?
|
|
382
|
+
? (typeof raw.transactionDate === 'number'
|
|
383
|
+
? raw.transactionDate
|
|
384
|
+
: parseInt(raw.transactionDate, 10))
|
|
352
385
|
: Date.now(),
|
|
353
|
-
transactionReceipt: raw.transactionReceipt ?? '',
|
|
386
|
+
transactionReceipt: raw.transactionReceipt ?? raw.dataAndroid ?? '',
|
|
354
387
|
purchaseToken: raw.purchaseToken,
|
|
355
388
|
isAcknowledged: raw.isAcknowledgedAndroid,
|
|
356
389
|
purchaseState: stateOverride ?? 'purchased',
|