@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/payments",
3
- "version": "1.2.109",
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",
@@ -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!.getProducts({ skus });
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 subscriptions = await RNIap!.getSubscriptions({ skus });
110
- return subscriptions.map(mapSubscription);
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({ sku });
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 mapPurchase(purchase);
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
- const request: Record<string, unknown> = { sku };
147
-
148
- if (Platform.OS === 'android' && offerToken) {
149
- request.subscriptionOffers = [{ sku, offerToken }];
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!.requestSubscription(request);
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 mapPurchase(purchase);
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: { transactionId: purchase.transactionId },
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.productId ?? raw.sku ?? '',
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: 'iap',
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: subscriptionPeriodAndroid (ISO 8601 duration, e.g., "P1M", "P1Y")
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.sku ?? '',
349
- transactionId: raw.transactionId ?? '',
379
+ sku: raw.productId ?? raw.id ?? '',
380
+ transactionId: raw.id ?? raw.transactionId ?? '',
350
381
  transactionDate: raw.transactionDate
351
- ? parseInt(raw.transactionDate, 10)
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',