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.
- package/ios/HybridRnIap.swift +89 -46
- package/ios/RnIapHelper.swift +36 -3
- package/ios/reactnativeiap.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/reactnativeiap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/lib/module/index.js +32 -6
- package/lib/module/index.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/src/index.d.ts +16 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/RnIap.nitro.d.ts +7 -0
- package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +23 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JNitroRequestPurchaseIos.hpp +7 -3
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroRequestPurchaseIos.kt +6 -3
- package/nitrogen/generated/ios/swift/NitroRequestPurchaseIos.swift +31 -1
- package/nitrogen/generated/shared/c++/NitroRequestPurchaseIos.hpp +6 -2
- package/openiap-versions.json +4 -3
- package/package.json +1 -1
- package/plugin/build/src/withIAP.d.ts +3 -0
- package/plugin/build/src/withIAP.js +81 -0
- package/plugin/build/tsconfig.tsbuildinfo +1 -0
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/index.ts +67 -39
- package/src/specs/RnIap.nitro.ts +7 -0
- package/src/types.ts +23 -2
package/ios/HybridRnIap.swift
CHANGED
|
@@ -88,7 +88,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
88
88
|
])
|
|
89
89
|
|
|
90
90
|
if skus.isEmpty {
|
|
91
|
-
throw
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1134
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
1098
1135
|
}
|
|
1099
1136
|
} else {
|
|
1100
|
-
let err =
|
|
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
|
|
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
|
|
1170
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
1131
1171
|
}
|
|
1132
1172
|
} else {
|
|
1133
|
-
let err =
|
|
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
|
|
1199
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
1157
1200
|
}
|
|
1158
1201
|
} else {
|
|
1159
|
-
let err =
|
|
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
|
}
|
package/ios/RnIapHelper.swift
CHANGED
|
@@ -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
|
|
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
|
|
314
|
+
throw OpenIapException.make(code: .receiptFailed, message: error.localizedDescription)
|
|
282
315
|
}
|
|
283
316
|
}
|
|
284
317
|
|
package/lib/module/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
807
|
-
|
|
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
|
-
|
|
829
|
-
|
|
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
|
*/
|