react-native-iap 14.6.1 → 14.6.3-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.
@@ -88,7 +88,7 @@ class HybridRnIap: HybridRnIapSpec {
88
88
  ])
89
89
 
90
90
  if skus.isEmpty {
91
- throw PurchaseError.make(code: .emptySkuList)
91
+ throw OpenIapException.make(code: .emptySkuList)
92
92
  }
93
93
 
94
94
  var productsById: [String: NitroProduct] = [:]
@@ -183,6 +183,9 @@ class HybridRnIap: HybridRnIapSpec {
183
183
  if let withOffer = iosRequest.withOffer {
184
184
  iosPayload["withOffer"] = withOffer
185
185
  }
186
+ if let advancedCommerceData = iosRequest.advancedCommerceData {
187
+ iosPayload["advancedCommerceData"] = advancedCommerceData
188
+ }
186
189
 
187
190
  let cachedType = await MainActor.run { self.productTypeBySku[iosRequest.sku] }
188
191
  let resolvedType = RnIapHelper.parseProductQueryType(cachedType)
@@ -242,9 +245,12 @@ class HybridRnIap: HybridRnIapSpec {
242
245
  let payloads = RnIapHelper.sanitizeArray(OpenIapSerialization.purchases(purchases))
243
246
  RnIapLog.result("getAvailablePurchases", payloads)
244
247
  return payloads.map { RnIapHelper.convertPurchaseDictionary($0) }
248
+ } catch let purchaseError as PurchaseError {
249
+ RnIapLog.failure("getAvailablePurchases", error: purchaseError)
250
+ throw OpenIapException.from(purchaseError)
245
251
  } catch {
246
252
  RnIapLog.failure("getAvailablePurchases", error: error)
247
- throw error
253
+ throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
248
254
  }
249
255
  }
250
256
  }
@@ -259,9 +265,12 @@ class HybridRnIap: HybridRnIapSpec {
259
265
  let payloads = RnIapHelper.sanitizeArray(subscriptions.map { OpenIapSerialization.encode($0) })
260
266
  RnIapLog.result("getActiveSubscriptions", payloads)
261
267
  return payloads.map { RnIapHelper.convertActiveSubscriptionDictionary($0) }
268
+ } catch let purchaseError as PurchaseError {
269
+ RnIapLog.failure("getActiveSubscriptions", error: purchaseError)
270
+ throw OpenIapException.from(purchaseError)
262
271
  } catch {
263
272
  RnIapLog.failure("getActiveSubscriptions", error: error)
264
- throw error
273
+ throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
265
274
  }
266
275
  }
267
276
  }
@@ -274,9 +283,12 @@ class HybridRnIap: HybridRnIapSpec {
274
283
  let hasActive = try await OpenIapModule.shared.hasActiveSubscriptions(subscriptionIds)
275
284
  RnIapLog.result("hasActiveSubscriptions", hasActive)
276
285
  return hasActive
286
+ } catch let purchaseError as PurchaseError {
287
+ RnIapLog.failure("hasActiveSubscriptions", error: purchaseError)
288
+ throw OpenIapException.from(purchaseError)
277
289
  } catch {
278
290
  RnIapLog.failure("hasActiveSubscriptions", error: error)
279
- throw error
291
+ throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
280
292
  }
281
293
  }
282
294
  }
@@ -297,7 +309,7 @@ class HybridRnIap: HybridRnIapSpec {
297
309
  purchasePayload = ["transactionIdentifier": iosParams.transactionId]
298
310
  }
299
311
  guard let purchasePayload else {
300
- throw PurchaseError.make(code: .purchaseError, message: "Missing purchase context for \(iosParams.transactionId)")
312
+ throw OpenIapException.make(code: .purchaseError, message: "Missing purchase context for \(iosParams.transactionId)")
301
313
  }
302
314
  let sanitizedPayload = RnIapHelper.sanitizeDictionary(purchasePayload)
303
315
  RnIapLog.payload("finishTransaction.nativePayload", sanitizedPayload)
@@ -311,7 +323,7 @@ class HybridRnIap: HybridRnIapSpec {
311
323
  } catch {
312
324
  RnIapLog.failure("finishTransaction", error: error)
313
325
  let tid = iosParams.transactionId
314
- throw PurchaseError.make(code: .purchaseError, message: "Transaction not found: \(tid)")
326
+ throw OpenIapException.make(code: .purchaseError, message: "Transaction not found: \(tid)")
315
327
  }
316
328
  }
317
329
  }
@@ -321,7 +333,7 @@ class HybridRnIap: HybridRnIapSpec {
321
333
  do {
322
334
  // Extract SKU from apple options (new platform-specific structure)
323
335
  guard let appleOptions = params.apple, !appleOptions.sku.isEmpty else {
324
- throw PurchaseError.make(code: .developerError, message: "Missing required parameter: apple.sku")
336
+ throw OpenIapException.make(code: .developerError, message: "Missing required parameter: apple.sku")
325
337
  }
326
338
  let sku = appleOptions.sku
327
339
 
@@ -348,9 +360,12 @@ class HybridRnIap: HybridRnIapSpec {
348
360
  latestTransaction: latest
349
361
  )
350
362
  return .first(mapped)
363
+ } catch let purchaseError as PurchaseError {
364
+ RnIapLog.failure("validateReceiptIOS", error: purchaseError)
365
+ throw OpenIapException.from(purchaseError)
351
366
  } catch {
352
367
  RnIapLog.failure("validateReceiptIOS", error: error)
353
- throw PurchaseError.make(code: .receiptFailed, message: error.localizedDescription)
368
+ throw OpenIapException.make(code: .purchaseVerificationFailed, message: error.localizedDescription)
354
369
  }
355
370
  }
356
371
  }
@@ -407,9 +422,13 @@ class HybridRnIap: HybridRnIapSpec {
407
422
  errors: nitroErrors,
408
423
  provider: PurchaseVerificationProvider(fromString: result.provider.rawValue) ?? .iapkit
409
424
  )
425
+ } catch let purchaseError as PurchaseError {
426
+ // Convert PurchaseError to OpenIapException to preserve message through Nitro bridge
427
+ RnIapLog.failure("verifyPurchaseWithProvider", error: purchaseError)
428
+ throw OpenIapException.from(purchaseError)
410
429
  } catch {
411
430
  RnIapLog.failure("verifyPurchaseWithProvider", error: error)
412
- throw PurchaseError.make(code: .receiptFailed, message: error.localizedDescription)
431
+ throw OpenIapException.make(code: .purchaseVerificationFailed, message: error.localizedDescription)
413
432
  }
414
433
  }
415
434
  }
@@ -421,9 +440,12 @@ class HybridRnIap: HybridRnIapSpec {
421
440
  let storefront = try await OpenIapModule.shared.getStorefrontIOS()
422
441
  RnIapLog.result("getStorefront", storefront)
423
442
  return storefront
443
+ } catch let purchaseError as PurchaseError {
444
+ RnIapLog.failure("getStorefront", error: purchaseError)
445
+ throw OpenIapException.from(purchaseError)
424
446
  } catch {
425
447
  RnIapLog.failure("getStorefront", error: error)
426
- throw PurchaseError.make(code: .serviceError, message: error.localizedDescription)
448
+ throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
427
449
  }
428
450
  }
429
451
  }
@@ -484,9 +506,12 @@ class HybridRnIap: HybridRnIapSpec {
484
506
  let payload = RnIapHelper.sanitizeDictionary(OpenIapSerialization.encode(product))
485
507
  RnIapLog.result("getPromotedProductIOS", payload)
486
508
  return RnIapHelper.convertProductDictionary(payload)
509
+ } catch let purchaseError as PurchaseError {
510
+ RnIapLog.failure("getPromotedProductIOS", error: purchaseError)
511
+ throw OpenIapException.from(purchaseError)
487
512
  } catch {
488
513
  RnIapLog.failure("getPromotedProductIOS", error: error)
489
- throw PurchaseError.make(code: .serviceError, message: error.localizedDescription)
514
+ throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
490
515
  }
491
516
  }
492
517
  }
@@ -518,11 +543,11 @@ class HybridRnIap: HybridRnIapSpec {
518
543
  } catch {
519
544
  // Fallback with explicit error for simulator or unsupported cases
520
545
  RnIapLog.failure("presentCodeRedemptionSheetIOS", error: error)
521
- throw PurchaseError.make(code: .featureNotSupported)
546
+ throw OpenIapException.make(code: .featureNotSupported)
522
547
  }
523
548
  }
524
549
  }
525
-
550
+
526
551
  func clearTransactionIOS() throws -> Promise<Void> {
527
552
  return Promise.async {
528
553
  do {
@@ -590,11 +615,11 @@ class HybridRnIap: HybridRnIapSpec {
590
615
  return Optional<NitroPurchase>.none
591
616
  } catch {
592
617
  RnIapLog.failure("currentEntitlementIOS", error: error)
593
- throw PurchaseError.make(code: .skuNotFound, productId: sku)
618
+ throw OpenIapException.make(code: .skuNotFound, productId: sku)
594
619
  }
595
620
  }
596
621
  }
597
-
622
+
598
623
  func latestTransactionIOS(sku: String) throws -> Promise<NitroPurchase?> {
599
624
  return Promise.async {
600
625
  try self.ensureConnection()
@@ -616,11 +641,11 @@ class HybridRnIap: HybridRnIapSpec {
616
641
  return Optional<NitroPurchase>.none
617
642
  } catch {
618
643
  RnIapLog.failure("latestTransactionIOS", error: error)
619
- throw PurchaseError.make(code: .skuNotFound, productId: sku)
644
+ throw OpenIapException.make(code: .skuNotFound, productId: sku)
620
645
  }
621
646
  }
622
647
  }
623
-
648
+
624
649
  func getPendingTransactionsIOS() throws -> Promise<[NitroPurchase]> {
625
650
  return Promise.async {
626
651
  do {
@@ -654,13 +679,16 @@ class HybridRnIap: HybridRnIapSpec {
654
679
  let ok = try await OpenIapModule.shared.syncIOS()
655
680
  RnIapLog.result("syncIOS", ok)
656
681
  return ok
682
+ } catch let purchaseError as PurchaseError {
683
+ RnIapLog.failure("syncIOS", error: purchaseError)
684
+ throw OpenIapException.from(purchaseError)
657
685
  } catch {
658
686
  RnIapLog.failure("syncIOS", error: error)
659
- throw PurchaseError.make(code: .serviceError, message: error.localizedDescription)
687
+ throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
660
688
  }
661
689
  }
662
690
  }
663
-
691
+
664
692
  func showManageSubscriptionsIOS() throws -> Promise<[NitroPurchase]> {
665
693
  return Promise.async {
666
694
  try self.ensureConnection()
@@ -678,9 +706,12 @@ class HybridRnIap: HybridRnIapSpec {
678
706
  let payloads = RnIapHelper.sanitizeArray(OpenIapSerialization.purchases(purchases))
679
707
  RnIapLog.result("showManageSubscriptionsIOS", payloads)
680
708
  return payloads.map { RnIapHelper.convertPurchaseDictionary($0) }
709
+ } catch let purchaseError as PurchaseError {
710
+ RnIapLog.failure("showManageSubscriptionsIOS", error: purchaseError)
711
+ throw OpenIapException.from(purchaseError)
681
712
  } catch {
682
713
  RnIapLog.failure("showManageSubscriptionsIOS", error: error)
683
- throw PurchaseError.make(code: .serviceError, message: error.localizedDescription)
714
+ throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
684
715
  }
685
716
  }
686
717
  }
@@ -693,13 +724,16 @@ class HybridRnIap: HybridRnIapSpec {
693
724
  try await OpenIapModule.shared.deepLinkToSubscriptions(nil)
694
725
  RnIapLog.result("deepLinkToSubscriptionsIOS", true)
695
726
  return true
727
+ } catch let purchaseError as PurchaseError {
728
+ RnIapLog.failure("deepLinkToSubscriptionsIOS", error: purchaseError)
729
+ throw OpenIapException.from(purchaseError)
696
730
  } catch {
697
731
  RnIapLog.failure("deepLinkToSubscriptionsIOS", error: error)
698
- throw PurchaseError.make(code: .serviceError, message: error.localizedDescription)
732
+ throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
699
733
  }
700
734
  }
701
735
  }
702
-
736
+
703
737
  func isEligibleForIntroOfferIOS(groupID: String) throws -> Promise<Bool> {
704
738
  return Promise.async {
705
739
  RnIapLog.payload("isEligibleForIntroOfferIOS", ["groupID": groupID])
@@ -719,10 +753,10 @@ class HybridRnIap: HybridRnIapSpec {
719
753
  return receipt
720
754
  } catch let purchaseError as PurchaseError {
721
755
  RnIapLog.failure("getReceiptDataIOS", error: purchaseError)
722
- throw purchaseError
756
+ throw OpenIapException.from(purchaseError)
723
757
  } catch {
724
758
  RnIapLog.failure("getReceiptDataIOS", error: error)
725
- throw PurchaseError.make(code: .receiptFailed, message: error.localizedDescription)
759
+ throw OpenIapException.make(code: .receiptFailed, message: error.localizedDescription)
726
760
  }
727
761
  }
728
762
  }
@@ -737,10 +771,10 @@ class HybridRnIap: HybridRnIapSpec {
737
771
  return receipt
738
772
  } catch let purchaseError as PurchaseError {
739
773
  RnIapLog.failure("getReceiptIOS", error: purchaseError)
740
- throw purchaseError
774
+ throw OpenIapException.from(purchaseError)
741
775
  } catch {
742
776
  RnIapLog.failure("getReceiptIOS", error: error)
743
- throw PurchaseError.make(code: .receiptFailed, message: error.localizedDescription)
777
+ throw OpenIapException.make(code: .receiptFailed, message: error.localizedDescription)
744
778
  }
745
779
  }
746
780
  }
@@ -755,14 +789,14 @@ class HybridRnIap: HybridRnIapSpec {
755
789
  return receipt
756
790
  } catch let purchaseError as PurchaseError {
757
791
  RnIapLog.failure("requestReceiptRefreshIOS", error: purchaseError)
758
- throw purchaseError
792
+ throw OpenIapException.from(purchaseError)
759
793
  } catch {
760
794
  RnIapLog.failure("requestReceiptRefreshIOS", error: error)
761
- throw PurchaseError.make(code: .receiptFailed, message: error.localizedDescription)
795
+ throw OpenIapException.make(code: .receiptFailed, message: error.localizedDescription)
762
796
  }
763
797
  }
764
798
  }
765
-
799
+
766
800
  func isTransactionVerifiedIOS(sku: String) throws -> Promise<Bool> {
767
801
  return Promise.async {
768
802
  try self.ensureConnection()
@@ -784,11 +818,11 @@ class HybridRnIap: HybridRnIapSpec {
784
818
  return jws
785
819
  } catch {
786
820
  RnIapLog.failure("getTransactionJwsIOS", error: error)
787
- throw PurchaseError.make(code: .transactionValidationFailed, message: "Can't find transaction for sku \(sku)")
821
+ throw OpenIapException.make(code: .transactionValidationFailed, message: "Can't find transaction for sku \(sku)")
788
822
  }
789
823
  }
790
824
  }
791
-
825
+
792
826
  func beginRefundRequestIOS(sku: String) throws -> Promise<String?> {
793
827
  return Promise.async {
794
828
  do {
@@ -922,7 +956,7 @@ class HybridRnIap: HybridRnIapSpec {
922
956
 
923
957
  private func ensureConnection() throws {
924
958
  guard isInitialized else {
925
- throw PurchaseError.make(code: .initConnection, message: "Connection not initialized. Call initConnection() first.")
959
+ throw OpenIapException.make(code: .initConnection, message: "Connection not initialized. Call initConnection() first.")
926
960
  }
927
961
  }
928
962
 
@@ -1024,7 +1058,7 @@ class HybridRnIap: HybridRnIapSpec {
1024
1058
 
1025
1059
  func deepLinkToSubscriptionsAndroid(options: NitroDeepLinkOptionsAndroid) throws -> Promise<Void> {
1026
1060
  return Promise.async {
1027
- throw PurchaseError.make(code: .featureNotSupported)
1061
+ throw OpenIapException.make(code: .featureNotSupported)
1028
1062
  }
1029
1063
  }
1030
1064
 
@@ -1032,19 +1066,19 @@ class HybridRnIap: HybridRnIapSpec {
1032
1066
 
1033
1067
  func checkAlternativeBillingAvailabilityAndroid() throws -> Promise<Bool> {
1034
1068
  return Promise.async {
1035
- throw PurchaseError.make(code: .featureNotSupported)
1069
+ throw OpenIapException.make(code: .featureNotSupported)
1036
1070
  }
1037
1071
  }
1038
1072
 
1039
1073
  func showAlternativeBillingDialogAndroid() throws -> Promise<Bool> {
1040
1074
  return Promise.async {
1041
- throw PurchaseError.make(code: .featureNotSupported)
1075
+ throw OpenIapException.make(code: .featureNotSupported)
1042
1076
  }
1043
1077
  }
1044
1078
 
1045
1079
  func createAlternativeBillingTokenAndroid(sku: String?) throws -> Promise<String?> {
1046
1080
  return Promise.async {
1047
- throw PurchaseError.make(code: .featureNotSupported)
1081
+ throw OpenIapException.make(code: .featureNotSupported)
1048
1082
  }
1049
1083
  }
1050
1084
 
@@ -1064,19 +1098,19 @@ class HybridRnIap: HybridRnIapSpec {
1064
1098
 
1065
1099
  func isBillingProgramAvailableAndroid(program: BillingProgramAndroid) throws -> Promise<NitroBillingProgramAvailabilityResultAndroid> {
1066
1100
  return Promise.async {
1067
- throw PurchaseError.make(code: .featureNotSupported, message: "Billing Programs API is Android-only")
1101
+ throw OpenIapException.make(code: .featureNotSupported, message: "Billing Programs API is Android-only")
1068
1102
  }
1069
1103
  }
1070
1104
 
1071
1105
  func createBillingProgramReportingDetailsAndroid(program: BillingProgramAndroid) throws -> Promise<NitroBillingProgramReportingDetailsAndroid> {
1072
1106
  return Promise.async {
1073
- throw PurchaseError.make(code: .featureNotSupported, message: "Billing Programs API is Android-only")
1107
+ throw OpenIapException.make(code: .featureNotSupported, message: "Billing Programs API is Android-only")
1074
1108
  }
1075
1109
  }
1076
1110
 
1077
1111
  func launchExternalLinkAndroid(params: NitroLaunchExternalLinkParamsAndroid) throws -> Promise<Bool> {
1078
1112
  return Promise.async {
1079
- throw PurchaseError.make(code: .featureNotSupported, message: "Billing Programs API is Android-only")
1113
+ throw OpenIapException.make(code: .featureNotSupported, message: "Billing Programs API is Android-only")
1080
1114
  }
1081
1115
  }
1082
1116
 
@@ -1092,12 +1126,15 @@ class HybridRnIap: HybridRnIapSpec {
1092
1126
  let canPresent = try await OpenIapModule.shared.canPresentExternalPurchaseNoticeIOS()
1093
1127
  RnIapLog.result("canPresentExternalPurchaseNoticeIOS", canPresent)
1094
1128
  return canPresent
1129
+ } catch let purchaseError as PurchaseError {
1130
+ RnIapLog.failure("canPresentExternalPurchaseNoticeIOS", error: purchaseError)
1131
+ throw OpenIapException.from(purchaseError)
1095
1132
  } catch {
1096
1133
  RnIapLog.failure("canPresentExternalPurchaseNoticeIOS", error: error)
1097
- throw PurchaseError.make(code: .serviceError, message: error.localizedDescription)
1134
+ throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
1098
1135
  }
1099
1136
  } else {
1100
- let err = PurchaseError.make(code: .featureNotSupported, message: "External purchase notice requires iOS 16.0 or later")
1137
+ let err = OpenIapException.make(code: .featureNotSupported, message: "External purchase notice requires iOS 16.0 or later")
1101
1138
  RnIapLog.failure("canPresentExternalPurchaseNoticeIOS", error: err)
1102
1139
  throw err
1103
1140
  }
@@ -1116,7 +1153,7 @@ class HybridRnIap: HybridRnIapSpec {
1116
1153
  // Convert OpenIAP action to Nitro action via raw value
1117
1154
  let actionString = result.result.rawValue
1118
1155
  guard let nitroAction = ExternalPurchaseNoticeAction(fromString: actionString) else {
1119
- throw PurchaseError.make(code: .serviceError, message: "Invalid action: \(actionString)")
1156
+ throw OpenIapException.make(code: .serviceError, message: "Invalid action: \(actionString)")
1120
1157
  }
1121
1158
 
1122
1159
  let nitroResult = ExternalPurchaseNoticeResultIOS(
@@ -1125,12 +1162,15 @@ class HybridRnIap: HybridRnIapSpec {
1125
1162
  )
1126
1163
  RnIapLog.result("presentExternalPurchaseNoticeSheetIOS", result)
1127
1164
  return nitroResult
1165
+ } catch let purchaseError as PurchaseError {
1166
+ RnIapLog.failure("presentExternalPurchaseNoticeSheetIOS", error: purchaseError)
1167
+ throw OpenIapException.from(purchaseError)
1128
1168
  } catch {
1129
1169
  RnIapLog.failure("presentExternalPurchaseNoticeSheetIOS", error: error)
1130
- throw PurchaseError.make(code: .serviceError, message: error.localizedDescription)
1170
+ throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
1131
1171
  }
1132
1172
  } else {
1133
- let err = PurchaseError.make(code: .featureNotSupported, message: "External purchase notice requires iOS 16.0 or later")
1173
+ let err = OpenIapException.make(code: .featureNotSupported, message: "External purchase notice requires iOS 16.0 or later")
1134
1174
  RnIapLog.failure("presentExternalPurchaseNoticeSheetIOS", error: err)
1135
1175
  throw err
1136
1176
  }
@@ -1151,12 +1191,15 @@ class HybridRnIap: HybridRnIapSpec {
1151
1191
  )
1152
1192
  RnIapLog.result("presentExternalPurchaseLinkIOS", result)
1153
1193
  return nitroResult
1194
+ } catch let purchaseError as PurchaseError {
1195
+ RnIapLog.failure("presentExternalPurchaseLinkIOS", error: purchaseError)
1196
+ throw OpenIapException.from(purchaseError)
1154
1197
  } catch {
1155
1198
  RnIapLog.failure("presentExternalPurchaseLinkIOS", error: error)
1156
- throw PurchaseError.make(code: .serviceError, message: error.localizedDescription)
1199
+ throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
1157
1200
  }
1158
1201
  } else {
1159
- let err = PurchaseError.make(code: .featureNotSupported, message: "External purchase link requires iOS 16.0 or later")
1202
+ let err = OpenIapException.make(code: .featureNotSupported, message: "External purchase link requires iOS 16.0 or later")
1160
1203
  RnIapLog.failure("presentExternalPurchaseLinkIOS", error: err)
1161
1204
  throw err
1162
1205
  }
@@ -1,6 +1,39 @@
1
1
  import Foundation
2
2
  import OpenIAP
3
3
 
4
+ /// Custom error that preserves error messages through Nitro bridge.
5
+ /// Similar to Android's OpenIapException, this wraps errors with JSON-serialized messages.
6
+ /// Uses NSError for better compatibility with Objective-C bridging in Nitro.
7
+ @available(iOS 15.0, *)
8
+ class OpenIapException: NSError {
9
+ static let domain = "com.margelo.nitro.rniap"
10
+
11
+ convenience init(_ json: String) {
12
+ self.init(domain: OpenIapException.domain, code: -1, userInfo: [NSLocalizedDescriptionKey: json])
13
+ }
14
+
15
+ static func make(code: ErrorCode, message: String? = nil, productId: String? = nil) -> OpenIapException {
16
+ let errorMessage = message ?? code.rawValue
17
+ var dict: [String: Any] = [
18
+ "code": code.rawValue,
19
+ "message": errorMessage
20
+ ]
21
+ if let productId = productId {
22
+ dict["productId"] = productId
23
+ }
24
+
25
+ if let data = try? JSONSerialization.data(withJSONObject: dict),
26
+ let json = String(data: data, encoding: .utf8) {
27
+ return OpenIapException(json)
28
+ }
29
+ return OpenIapException("{\"code\":\"\(code.rawValue)\",\"message\":\"\(errorMessage)\"}")
30
+ }
31
+
32
+ static func from(_ error: PurchaseError) -> OpenIapException {
33
+ return make(code: error.code, message: error.message, productId: error.productId)
34
+ }
35
+ }
36
+
4
37
  @available(iOS 15.0, *)
5
38
  enum RnIapHelper {
6
39
  // MARK: - Sanitizers
@@ -272,13 +305,13 @@ enum RnIapHelper {
272
305
 
273
306
  do {
274
307
  guard let receipt = try await OpenIapModule.shared.getReceiptDataIOS(), !receipt.isEmpty else {
275
- throw PurchaseError.make(code: .receiptFailed)
308
+ throw OpenIapException.make(code: .receiptFailed)
276
309
  }
277
310
  return receipt
278
311
  } catch let error as PurchaseError {
279
- throw error
312
+ throw OpenIapException.from(error)
280
313
  } catch {
281
- throw PurchaseError.make(code: .receiptFailed, message: error.localizedDescription)
314
+ throw OpenIapException.make(code: .receiptFailed, message: error.localizedDescription)
282
315
  }
283
316
  }
284
317
 
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Workspace
3
+ version = "1.0">
4
+ <FileRef
5
+ location = "self:">
6
+ </FileRef>
7
+ </Workspace>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>IDEDidComputeMac32BitWarning</key>
6
+ <true/>
7
+ </dict>
8
+ </plist>
@@ -790,12 +790,14 @@ export const requestPurchase = async request => {
790
790
  throw new Error('Missing purchase request configuration');
791
791
  }
792
792
  if (Platform.OS === 'ios') {
793
- const iosRequest = perPlatformRequest.ios;
793
+ // Support both 'apple' (recommended) and 'ios' (deprecated) fields
794
+ const iosRequest = perPlatformRequest.apple ?? perPlatformRequest.ios;
794
795
  if (!iosRequest?.sku) {
795
796
  throw new Error('Invalid request for iOS. The `sku` property is required.');
796
797
  }
797
798
  } else if (Platform.OS === 'android') {
798
- const androidRequest = perPlatformRequest.android;
799
+ // Support both 'google' (recommended) and 'android' (deprecated) fields
800
+ const androidRequest = perPlatformRequest.google ?? perPlatformRequest.android;
799
801
  if (!androidRequest?.skus?.length) {
800
802
  throw new Error('Invalid request for Android. The `skus` property is required and must be a non-empty array.');
801
803
  }
@@ -803,8 +805,11 @@ export const requestPurchase = async request => {
803
805
  throw new Error('Unsupported platform');
804
806
  }
805
807
  const unifiedRequest = {};
806
- if (Platform.OS === 'ios' && perPlatformRequest.ios) {
807
- const iosRequest = isSubs ? perPlatformRequest.ios : perPlatformRequest.ios;
808
+
809
+ // Support both 'apple' (recommended) and 'ios' (deprecated) fields
810
+ const iosRequestSource = perPlatformRequest.apple ?? perPlatformRequest.ios;
811
+ if (Platform.OS === 'ios' && iosRequestSource) {
812
+ const iosRequest = isSubs ? iosRequestSource : iosRequestSource;
808
813
  const iosPayload = {
809
814
  sku: iosRequest.sku
810
815
  };
@@ -823,10 +828,16 @@ export const requestPurchase = async request => {
823
828
  if (offerRecord) {
824
829
  iosPayload.withOffer = offerRecord;
825
830
  }
831
+ if (iosRequest.advancedCommerceData) {
832
+ iosPayload.advancedCommerceData = iosRequest.advancedCommerceData;
833
+ }
826
834
  unifiedRequest.ios = iosPayload;
827
835
  }
828
- if (Platform.OS === 'android' && perPlatformRequest.android) {
829
- const androidRequest = isSubs ? perPlatformRequest.android : perPlatformRequest.android;
836
+
837
+ // Support both 'google' (recommended) and 'android' (deprecated) fields
838
+ const androidRequestSource = perPlatformRequest.google ?? perPlatformRequest.android;
839
+ if (Platform.OS === 'android' && androidRequestSource) {
840
+ const androidRequest = isSubs ? androidRequestSource : androidRequestSource;
830
841
  const androidPayload = {
831
842
  skus: androidRequest.skus
832
843
  };
@@ -1245,6 +1256,21 @@ export const presentCodeRedemptionSheetIOS = async () => {
1245
1256
 
1246
1257
  /**
1247
1258
  * Buy promoted product on iOS
1259
+ * @deprecated In StoreKit 2, promoted products can be purchased directly via the standard `requestPurchase()` flow.
1260
+ * Use `promotedProductListenerIOS` to receive the product ID when a user taps a promoted product,
1261
+ * then call `requestPurchase()` with the received SKU directly.
1262
+ *
1263
+ * @example
1264
+ * ```typescript
1265
+ * // Recommended approach
1266
+ * promotedProductListenerIOS(async (product) => {
1267
+ * await requestPurchase({
1268
+ * request: { apple: { sku: product.id } },
1269
+ * type: 'in-app'
1270
+ * });
1271
+ * });
1272
+ * ```
1273
+ *
1248
1274
  * @returns Promise<boolean> - true when the request triggers successfully
1249
1275
  * @platform iOS
1250
1276
  */