expo-iap 2.3.4 → 2.3.5-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.
@@ -1 +1 @@
1
- {"version":3,"file":"ios.js","sourceRoot":"","sources":["../../src/modules/ios.ts"],"names":[],"mappings":"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","sourcesContent":["import {Platform} from 'react-native';\nimport {emitter, IapEvent} from '..';\nimport {ProductPurchase, PurchaseError} from '../ExpoIap.types';\nimport type {ProductStatusIos} from '../types/ExpoIapIos.types';\nimport ExpoIapModule from '../ExpoIapModule';\n\nexport type TransactionEvent = {\n transaction?: ProductPurchase;\n error?: PurchaseError;\n};\n\n// Listeners\nexport const transactionUpdatedIos = (\n listener: (event: TransactionEvent) => void,\n) => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n\n return emitter.addListener(IapEvent.TransactionIapUpdated, listener);\n};\n\n// Type guards\nexport function isProductIos<T extends {platform?: string}>(\n item: unknown,\n): item is T & {platform: 'ios'} {\n return (\n item != null &&\n typeof item === 'object' &&\n 'platform' in item &&\n item.platform === 'ios'\n );\n}\n\n// Functions\n/**\n * Sync state with Appstore (iOS only)\n * https://developer.apple.com/documentation/storekit/appstore/3791906-sync\n */\nexport const sync = (): Promise<null> => ExpoIapModule.sync();\n\n/**\n *\n */\nexport const isEligibleForIntroOffer = (groupID: string): Promise<boolean> =>\n ExpoIapModule.isEligibleForIntroOffer(groupID);\n\n/**\n *\n */\n\nexport const subscriptionStatus = (sku: string): Promise<ProductStatusIos[]> =>\n ExpoIapModule.subscriptionStatus(sku);\n\n/**\n *\n */\nexport const currentEntitlement = (sku: string): Promise<ProductPurchase> =>\n ExpoIapModule.currentEntitlement(sku);\n\n/**\n *\n */\nexport const latestTransaction = (sku: string): Promise<ProductPurchase> =>\n ExpoIapModule.latestTransaction(sku);\n\n/**\n *\n */\ntype RefundRequestStatus = 'success' | 'userCancelled';\nexport const beginRefundRequest = (sku: string): Promise<RefundRequestStatus> =>\n ExpoIapModule.beginRefundRequest(sku);\n\n/**\n * Shows the system UI for managing subscriptions.\n * When the user changes subscription renewal status, the system will emit events to\n * purchaseUpdatedListener and transactionUpdatedIos listeners.\n * @returns {Promise<null>}\n */\nexport const showManageSubscriptions = (): Promise<null> =>\n ExpoIapModule.showManageSubscriptions();\n\n/**\n * Get the receipt data from the iOS device.\n * This returns the base64 encoded receipt data which can be sent to your server\n * for verification with Apple's server.\n *\n * NOTE: For proper security, always verify receipts on your server using\n * Apple's verifyReceipt endpoint, not directly from the app.\n *\n * @returns {Promise<string>} Base64 encoded receipt data\n */\nexport const getReceiptIos = (): Promise<string> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n return ExpoIapModule.getReceiptData();\n};\n\n/**\n * Check if a transaction is verified through StoreKit 2.\n * StoreKit 2 performs local verification of transaction JWS signatures.\n *\n * @param {string} sku The product's SKU (on iOS)\n * @returns {Promise<boolean>} True if the transaction is verified\n */\nexport const isTransactionVerified = (sku: string): Promise<boolean> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n return ExpoIapModule.isTransactionVerified(sku);\n};\n\n/**\n * Get the JWS representation of a purchase for server-side verification.\n * The JWS (JSON Web Signature) can be verified on your server using Apple's public keys.\n *\n * @param {string} sku The product's SKU (on iOS)\n * @returns {Promise<string>} JWS representation of the transaction\n */\nexport const getTransactionJws = (sku: string): Promise<string> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n return ExpoIapModule.getTransactionJws(sku);\n};\n\n/**\n * Validate receipt for iOS using StoreKit 2's built-in verification.\n * Returns receipt data and verification information to help with server-side validation.\n *\n * NOTE: For proper security, Apple recommends verifying receipts on your server using\n * the verifyReceipt endpoint rather than relying solely on client-side verification.\n *\n * @param {string} sku The product's SKU (on iOS)\n * @returns {Promise<{\n * isValid: boolean;\n * receiptData: string;\n * jwsRepresentation: string;\n * latestTransaction?: ProductPurchase;\n * }>}\n */\nexport const validateReceiptIos = async (\n sku: string,\n): Promise<{\n isValid: boolean;\n receiptData: string;\n jwsRepresentation: string;\n latestTransaction?: ProductPurchase;\n}> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n\n const result = await ExpoIapModule.validateReceiptIos(sku);\n return result;\n};\n"]}
1
+ {"version":3,"file":"ios.js","sourceRoot":"","sources":["../../src/modules/ios.ts"],"names":[],"mappings":"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","sourcesContent":["import {Platform} from 'react-native';\nimport {emitter, IapEvent} from '..';\nimport {ProductPurchase, PurchaseError} from '../ExpoIap.types';\nimport type {ProductStatusIos} from '../types/ExpoIapIos.types';\nimport ExpoIapModule from '../ExpoIapModule';\n\nexport type TransactionEvent = {\n transaction?: ProductPurchase;\n error?: PurchaseError;\n};\n\n// Listeners\nexport const transactionUpdatedIos = (\n listener: (event: TransactionEvent) => void,\n) => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n\n return emitter.addListener(IapEvent.TransactionIapUpdated, listener);\n};\n\n// Type guards\nexport function isProductIos<T extends {platform?: string}>(\n item: unknown,\n): item is T & {platform: 'ios'} {\n return (\n item != null &&\n typeof item === 'object' &&\n 'platform' in item &&\n item.platform === 'ios'\n );\n}\n\n// Functions\n/**\n * Sync state with Appstore (iOS only)\n * https://developer.apple.com/documentation/storekit/appstore/3791906-sync\n */\nexport const sync = (): Promise<null> => ExpoIapModule.sync();\n\n/**\n *\n */\nexport const isEligibleForIntroOffer = (groupID: string): Promise<boolean> =>\n ExpoIapModule.isEligibleForIntroOffer(groupID);\n\n/**\n *\n */\n\nexport const subscriptionStatus = (sku: string): Promise<ProductStatusIos[]> =>\n ExpoIapModule.subscriptionStatus(sku);\n\n/**\n *\n */\nexport const currentEntitlement = (sku: string): Promise<ProductPurchase> =>\n ExpoIapModule.currentEntitlement(sku);\n\n/**\n *\n */\nexport const latestTransaction = (sku: string): Promise<ProductPurchase> =>\n ExpoIapModule.latestTransaction(sku);\n\n/**\n *\n */\ntype RefundRequestStatus = 'success' | 'userCancelled';\nexport const beginRefundRequest = (sku: string): Promise<RefundRequestStatus> =>\n ExpoIapModule.beginRefundRequest(sku);\n\n/**\n * Shows the system UI for managing subscriptions.\n * When the user changes subscription renewal status, the system will emit events to \n * purchaseUpdatedListener and transactionUpdatedIos listeners.\n * @returns {Promise<null>}\n */\nexport const showManageSubscriptions = (): Promise<null> =>\n ExpoIapModule.showManageSubscriptions();\n\n/**\n * Get the receipt data from the iOS device.\n * This returns the base64 encoded receipt data which can be sent to your server\n * for verification with Apple's server.\n * \n * NOTE: For proper security, always verify receipts on your server using\n * Apple's verifyReceipt endpoint, not directly from the app.\n * \n * @returns {Promise<string>} Base64 encoded receipt data\n */\nexport const getReceiptIos = (): Promise<string> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n return ExpoIapModule.getReceiptData();\n};\n\n/**\n * Check if a transaction is verified through StoreKit 2.\n * StoreKit 2 performs local verification of transaction JWS signatures.\n * \n * @param {string} sku The product's SKU (on iOS)\n * @returns {Promise<boolean>} True if the transaction is verified\n */\nexport const isTransactionVerified = (sku: string): Promise<boolean> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n return ExpoIapModule.isTransactionVerified(sku);\n};\n\n/**\n * Get the JWS representation of a purchase for server-side verification.\n * The JWS (JSON Web Signature) can be verified on your server using Apple's public keys.\n * \n * @param {string} sku The product's SKU (on iOS)\n * @returns {Promise<string>} JWS representation of the transaction\n */\nexport const getTransactionJws = (sku: string): Promise<string> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n return ExpoIapModule.getTransactionJws(sku);\n};\n\n/**\n * Validate receipt for iOS using StoreKit 2's built-in verification.\n * Returns receipt data and verification information to help with server-side validation.\n *\n * NOTE: For proper security, Apple recommends verifying receipts on your server using\n * the verifyReceipt endpoint rather than relying solely on client-side verification.\n *\n * @param {string} sku The product's SKU (on iOS)\n * @returns {Promise<{\n * isValid: boolean;\n * receiptData: string;\n * jwsRepresentation: string;\n * latestTransaction?: ProductPurchase;\n * }>}\n */\nexport const validateReceiptIos = async (\n sku: string,\n): Promise<{\n isValid: boolean;\n receiptData: string;\n jwsRepresentation: string;\n latestTransaction?: ProductPurchase;\n}> => {\n if (Platform.OS !== 'ios') {\n throw new Error('This method is only available on iOS');\n }\n\n const result = await ExpoIapModule.validateReceiptIos(sku);\n return result;\n};\n"]}
package/iap.md CHANGED
@@ -209,8 +209,8 @@ Transactions map to `Purchase` or `SubscriptionPurchase` with platform-specific
209
209
  Below is a simple example of fetching products and making a purchase with `expo-iap` in a managed workflow, updated to use the new `requestPurchase` signature:
210
210
 
211
211
  ```tsx
212
- import {useEffect, useState} from 'react';
213
- import {Button, Text, View} from 'react-native';
212
+ import { useEffect, useState } from 'react';
213
+ import { Button, Text, View } from 'react-native';
214
214
  import {
215
215
  initConnection,
216
216
  endConnection,
@@ -237,7 +237,7 @@ export default function SimpleIAP() {
237
237
 
238
238
  const purchaseListener = purchaseUpdatedListener(async (purchase) => {
239
239
  if (purchase) {
240
- await finishTransaction({purchase, isConsumable: true});
240
+ await finishTransaction({ purchase, isConsumable: true });
241
241
  alert('Purchase completed!');
242
242
  }
243
243
  });
@@ -252,12 +252,12 @@ export default function SimpleIAP() {
252
252
  const buyItem = async () => {
253
253
  if (!product) return;
254
254
  await requestPurchase({
255
- request: {skus: [product.id]}, // Android expects 'skus'; iOS would use 'sku'
255
+ request: { skus: [product.id] }, // Android expects 'skus'; iOS would use 'sku'
256
256
  });
257
257
  };
258
258
 
259
259
  return (
260
- <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
260
+ <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
261
261
  <Text>{isConnected ? 'Connected' : 'Connecting...'}</Text>
262
262
  {product ? (
263
263
  <>
@@ -277,7 +277,7 @@ export default function SimpleIAP() {
277
277
  The `useIAP` hook simplifies managing in-app purchases. Below is an example updated to use the new `requestPurchase` signature:
278
278
 
279
279
  ```tsx
280
- import {useEffect, useState} from 'react';
280
+ import { useEffect, useState } from 'react';
281
281
  import {
282
282
  SafeAreaView,
283
283
  ScrollView,
@@ -289,20 +289,12 @@ import {
289
289
  InteractionManager,
290
290
  Alert,
291
291
  } from 'react-native';
292
- import {useIAP} from 'expo-iap';
293
- import type {ProductPurchase, SubscriptionProduct} from 'expo-iap';
292
+ import { useIAP } from 'expo-iap';
293
+ import type { ProductPurchase, SubscriptionProduct } from 'expo-iap';
294
294
 
295
295
  // Define SKUs
296
- const productSkus = [
297
- 'cpk.points.1000',
298
- 'cpk.points.5000',
299
- 'cpk.points.10000',
300
- 'cpk.points.30000',
301
- ];
302
- const subscriptionSkus = [
303
- 'cpk.membership.monthly.bronze',
304
- 'cpk.membership.monthly.silver',
305
- ];
296
+ const productSkus = ['cpk.points.1000', 'cpk.points.5000', 'cpk.points.10000', 'cpk.points.30000'];
297
+ const subscriptionSkus = ['cpk.membership.monthly.bronze', 'cpk.membership.monthly.silver'];
306
298
 
307
299
  // Define operations
308
300
  const operations = ['getProducts', 'getSubscriptions'] as const;
@@ -329,10 +321,7 @@ export default function IAPWithHook() {
329
321
 
330
322
  const initializeIAP = async () => {
331
323
  try {
332
- await Promise.all([
333
- getProducts(productSkus),
334
- getSubscriptions(subscriptionSkus),
335
- ]);
324
+ await Promise.all([getProducts(productSkus), getSubscriptions(subscriptionSkus)]);
336
325
  setIsReady(true);
337
326
  } catch (error) {
338
327
  console.error('Error initializing IAP:', error);
@@ -400,10 +389,7 @@ export default function IAPWithHook() {
400
389
  <View style={styles.buttons}>
401
390
  <ScrollView contentContainerStyle={styles.buttonsWrapper} horizontal>
402
391
  {operations.map((operation) => (
403
- <Pressable
404
- key={operation}
405
- onPress={() => handleOperation(operation)}
406
- >
392
+ <Pressable key={operation} onPress={() => handleOperation(operation)}>
407
393
  <View style={styles.buttonView}>
408
394
  <Text>{operation}</Text>
409
395
  </View>
@@ -415,10 +401,10 @@ export default function IAPWithHook() {
415
401
  {!isReady ? (
416
402
  <Text>Loading...</Text>
417
403
  ) : (
418
- <View style={{gap: 12}}>
419
- <Text style={{fontSize: 20}}>Products</Text>
404
+ <View style={{ gap: 12 }}>
405
+ <Text style={{ fontSize: 20 }}>Products</Text>
420
406
  {products.map((item) => (
421
- <View key={item.id} style={{gap: 12}}>
407
+ <View key={item.id} style={{ gap: 12 }}>
422
408
  <Text>
423
409
  {item.title} -{' '}
424
410
  {item.platform === 'android'
@@ -429,24 +415,20 @@ export default function IAPWithHook() {
429
415
  title="Buy"
430
416
  onPress={() =>
431
417
  requestPurchase({
432
- request:
433
- item.platform === 'android'
434
- ? {skus: [item.id]}
435
- : {sku: item.id},
418
+ request: item.platform === 'android' ? { skus: [item.id] } : { sku: item.id },
436
419
  })
437
420
  }
438
421
  />
439
422
  </View>
440
423
  ))}
441
424
 
442
- <Text style={{fontSize: 20}}>Subscriptions</Text>
425
+ <Text style={{ fontSize: 20 }}>Subscriptions</Text>
443
426
  {subscriptions.map((item) => (
444
- <View key={item.id} style={{gap: 12}}>
427
+ <View key={item.id} style={{ gap: 12 }}>
445
428
  <Text>
446
429
  {item.title || item.displayName} -{' '}
447
430
  {item.platform === 'android' && item.subscriptionOfferDetails
448
- ? item.subscriptionOfferDetails[0]?.pricingPhases
449
- .pricingPhaseList[0].formattedPrice
431
+ ? item.subscriptionOfferDetails[0]?.pricingPhases.pricingPhaseList[0].formattedPrice
450
432
  : item.displayPrice}
451
433
  </Text>
452
434
  <Button
@@ -463,7 +445,7 @@ export default function IAPWithHook() {
463
445
  offerToken: offer.offerToken,
464
446
  })) || [],
465
447
  }
466
- : {sku: item.id},
448
+ : { sku: item.id },
467
449
  type: 'subs',
468
450
  })
469
451
  }
@@ -71,7 +71,10 @@ func serializeTransaction(_ transaction: Transaction, jwsRepresentationIos: Stri
71
71
  ]
72
72
 
73
73
  if (jwsRepresentationIos != nil) {
74
+ print("DEBUG - serializeTransaction adding jwsRepresentationIos with length: \(jwsRepresentationIos!.count)")
74
75
  purchaseMap["jwsRepresentationIos"] = jwsRepresentationIos
76
+ } else {
77
+ print("DEBUG - serializeTransaction jwsRepresentationIos is nil")
75
78
  }
76
79
 
77
80
  if #available(iOS 16.0, *) {
@@ -237,15 +240,20 @@ public class ExpoIapModule: Module {
237
240
  var purchasedItemsSerialized: [[String: Any?]] = []
238
241
 
239
242
  func addTransaction(transaction: Transaction, jwsRepresentationIos: String? = nil) {
240
- purchasedItemsSerialized.append(serializeTransaction(transaction, jwsRepresentationIos: jwsRepresentationIos))
241
- if alsoPublishToEventListener {
242
- self.sendEvent(IapEvent.PurchaseUpdated, serializeTransaction(transaction, jwsRepresentationIos: jwsRepresentationIos))
243
+ // Debug: Log JWS representation
244
+ print("DEBUG - getAvailableItems JWS: \(jwsRepresentationIos != nil ? "exists" : "nil")")
245
+ if let jws = jwsRepresentationIos {
246
+ print("DEBUG - getAvailableItems JWS length: \(jws.count)")
243
247
  }
244
- }
245
-
246
- func addError(error: Error, errorDict: [String: String]) {
248
+
249
+ let serialized = serializeTransaction(transaction, jwsRepresentationIos: jwsRepresentationIos)
250
+ purchasedItemsSerialized.append(serialized)
251
+
252
+ // Debug: Check if jwsRepresentationIos is included in serialized result
253
+ print("DEBUG - getAvailableItems serialized includes JWS: \(serialized["jwsRepresentationIos"] != nil)")
254
+
247
255
  if alsoPublishToEventListener {
248
- self.sendEvent(IapEvent.PurchaseError, errorDict)
256
+ self.sendEvent(IapEvent.PurchaseUpdated, serialized)
249
257
  }
250
258
  }
251
259
 
@@ -287,7 +295,9 @@ public class ExpoIapModule: Module {
287
295
  "message": StoreError.failedVerification.localizedDescription,
288
296
  "productId": "unknown",
289
297
  ]
290
- addError(error: StoreError.failedVerification, errorDict: err)
298
+ if alsoPublishToEventListener {
299
+ self.sendEvent(IapEvent.PurchaseError, err)
300
+ }
291
301
  } catch {
292
302
  let err = [
293
303
  "responseCode": IapErrors.E_UNKNOWN.rawValue,
@@ -296,10 +306,11 @@ public class ExpoIapModule: Module {
296
306
  "message": error.localizedDescription,
297
307
  "productId": "unknown",
298
308
  ]
299
- addError(error: error, errorDict: err)
309
+ if alsoPublishToEventListener {
310
+ self.sendEvent(IapEvent.PurchaseError, err)
311
+ }
300
312
  }
301
313
  }
302
-
303
314
  return purchasedItemsSerialized
304
315
  }
305
316
 
@@ -362,12 +373,21 @@ public class ExpoIapModule: Module {
362
373
  switch result {
363
374
  case .success(let verification):
364
375
  let transaction = try self.checkVerified(verification)
376
+
377
+ // Debug: Log JWS representation
378
+ print("DEBUG - buyProduct JWS: exists")
379
+ print("DEBUG - buyProduct JWS length: \(verification.jwsRepresentation.count)")
380
+
365
381
  if andDangerouslyFinishTransactionAutomatically {
366
382
  await transaction.finish()
367
383
  return nil
368
384
  } else {
369
385
  self.transactions[String(transaction.id)] = transaction
370
386
  let serialized = serializeTransaction(transaction, jwsRepresentationIos: verification.jwsRepresentation)
387
+
388
+ // Debug: Check if jwsRepresentationIos is included in serialized result
389
+ print("DEBUG - buyProduct serialized includes JWS: \(serialized["jwsRepresentationIos"] != nil)")
390
+
371
391
  self.sendEvent(IapEvent.PurchaseUpdated, serialized)
372
392
  return serialized
373
393
  }
@@ -458,9 +478,7 @@ public class ExpoIapModule: Module {
458
478
  } else {
459
479
  throw NSError(
460
480
  domain: "ExpoIapModule", code: 4,
461
- userInfo: [
462
- NSLocalizedDescriptionKey: "Can't find entitlement for sku \(sku)"
463
- ])
481
+ userInfo: [NSLocalizedDescriptionKey: "Can't find entitlement for sku \(sku)"])
464
482
  }
465
483
  } else {
466
484
  throw NSError(
@@ -499,10 +517,7 @@ public class ExpoIapModule: Module {
499
517
  } else {
500
518
  throw NSError(
501
519
  domain: "ExpoIapModule", code: 4,
502
- userInfo: [
503
- NSLocalizedDescriptionKey:
504
- "Can't find latest transaction for sku \(sku)"
505
- ])
520
+ userInfo: [NSLocalizedDescriptionKey: "Can't find latest transaction for sku \(sku)"])
506
521
  }
507
522
  } else {
508
523
  throw NSError(
@@ -562,17 +577,13 @@ public class ExpoIapModule: Module {
562
577
  "Cannot find window scene or not available on macOS"
563
578
  ])
564
579
  }
565
-
566
580
  // Get all subscription products before showing the management UI
567
581
  let subscriptionSkus = await self.getAllSubscriptionProductIds()
568
582
  self.pollingSkus = Set(subscriptionSkus)
569
-
570
583
  // Show the management UI
571
584
  try await AppStore.showManageSubscriptions(in: windowScene)
572
-
573
585
  // Start polling for status changes
574
586
  self.pollForSubscriptionStatusChanges()
575
-
576
587
  return true
577
588
  #else
578
589
  throw NSError(
@@ -649,7 +660,7 @@ public class ExpoIapModule: Module {
649
660
  AsyncFunction("getReceiptData") { () -> String? in
650
661
  return try self.getReceiptDataInternal()
651
662
  }
652
-
663
+
653
664
  AsyncFunction("isTransactionVerified") { (sku: String) -> Bool in
654
665
  guard let productStore = self.productStore else {
655
666
  throw NSError(
@@ -669,7 +680,7 @@ public class ExpoIapModule: Module {
669
680
  }
670
681
  return false
671
682
  }
672
-
683
+
673
684
  AsyncFunction("getTransactionJws") { (sku: String) -> String? in
674
685
  guard let productStore = self.productStore else {
675
686
  throw NSError(
@@ -686,7 +697,7 @@ public class ExpoIapModule: Module {
686
697
  userInfo: [NSLocalizedDescriptionKey: "Can't find transaction for sku \(sku)"])
687
698
  }
688
699
  }
689
-
700
+
690
701
  AsyncFunction("validateReceiptIos") { (sku: String) -> [String: Any] in
691
702
  guard let productStore = self.productStore else {
692
703
  throw NSError(
@@ -721,7 +732,7 @@ public class ExpoIapModule: Module {
721
732
  isValid = false
722
733
  }
723
734
  }
724
-
735
+
725
736
  return [
726
737
  "isValid": isValid,
727
738
  "receiptData": receiptData,
@@ -808,7 +819,6 @@ public class ExpoIapModule: Module {
808
819
 
809
820
  private func pollForSubscriptionStatusChanges() {
810
821
  subscriptionPollingTask?.cancel()
811
-
812
822
  subscriptionPollingTask = Task {
813
823
  try? await Task.sleep(nanoseconds: 1_500_000_000) // 1.5 seconds
814
824
 
@@ -828,7 +838,6 @@ public class ExpoIapModule: Module {
828
838
 
829
839
  for _ in 1...5 {
830
840
  try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 seconds
831
-
832
841
  if Task.isCancelled {
833
842
  return
834
843
  }
@@ -837,7 +846,6 @@ public class ExpoIapModule: Module {
837
846
  guard let product = await self.productStore?.getProduct(productID: sku),
838
847
  let status = try? await product.subscription?.status.first,
839
848
  let result = await product.latestTransaction else { continue }
840
-
841
849
  // Try to verify the transaction
842
850
  let transaction: Transaction
843
851
  do {
@@ -866,12 +874,10 @@ public class ExpoIapModule: Module {
866
874
 
867
875
  self.sendEvent(IapEvent.PurchaseUpdated, purchaseMap)
868
876
  self.sendEvent(IapEvent.TransactionIapUpdated, ["transaction": purchaseMap])
869
-
870
877
  previousStatuses[sku] = currentWillAutoRenew
871
878
  }
872
879
  }
873
880
  }
874
-
875
881
  self.pollingSkus.removeAll()
876
882
  }
877
883
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-iap",
3
- "version": "2.3.4",
3
+ "version": "2.3.5-rc.1",
4
4
  "description": "In App Purchase module in Expo",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -73,7 +73,7 @@ export const beginRefundRequest = (sku: string): Promise<RefundRequestStatus> =>
73
73
 
74
74
  /**
75
75
  * Shows the system UI for managing subscriptions.
76
- * When the user changes subscription renewal status, the system will emit events to
76
+ * When the user changes subscription renewal status, the system will emit events to
77
77
  * purchaseUpdatedListener and transactionUpdatedIos listeners.
78
78
  * @returns {Promise<null>}
79
79
  */
@@ -84,10 +84,10 @@ export const showManageSubscriptions = (): Promise<null> =>
84
84
  * Get the receipt data from the iOS device.
85
85
  * This returns the base64 encoded receipt data which can be sent to your server
86
86
  * for verification with Apple's server.
87
- *
87
+ *
88
88
  * NOTE: For proper security, always verify receipts on your server using
89
89
  * Apple's verifyReceipt endpoint, not directly from the app.
90
- *
90
+ *
91
91
  * @returns {Promise<string>} Base64 encoded receipt data
92
92
  */
93
93
  export const getReceiptIos = (): Promise<string> => {
@@ -100,7 +100,7 @@ export const getReceiptIos = (): Promise<string> => {
100
100
  /**
101
101
  * Check if a transaction is verified through StoreKit 2.
102
102
  * StoreKit 2 performs local verification of transaction JWS signatures.
103
- *
103
+ *
104
104
  * @param {string} sku The product's SKU (on iOS)
105
105
  * @returns {Promise<boolean>} True if the transaction is verified
106
106
  */
@@ -114,7 +114,7 @@ export const isTransactionVerified = (sku: string): Promise<boolean> => {
114
114
  /**
115
115
  * Get the JWS representation of a purchase for server-side verification.
116
116
  * The JWS (JSON Web Signature) can be verified on your server using Apple's public keys.
117
- *
117
+ *
118
118
  * @param {string} sku The product's SKU (on iOS)
119
119
  * @returns {Promise<string>} JWS representation of the transaction
120
120
  */