react-native-iap 14.6.0 → 14.6.2
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.
|
@@ -1222,7 +1222,9 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1222
1222
|
val propsMap = mutableMapOf<String, Any?>("provider" to providerString)
|
|
1223
1223
|
params.iapkit?.let { iapkit ->
|
|
1224
1224
|
val iapkitMap = mutableMapOf<String, Any?>()
|
|
1225
|
-
|
|
1225
|
+
// Use provided apiKey, or fallback to AndroidManifest meta-data (set by config plugin)
|
|
1226
|
+
val apiKey = iapkit.apiKey ?: getIapkitApiKeyFromManifest()
|
|
1227
|
+
apiKey?.let { iapkitMap["apiKey"] = it }
|
|
1226
1228
|
iapkit.google?.let { google ->
|
|
1227
1229
|
iapkitMap["google"] = mapOf("purchaseToken" to google.purchaseToken)
|
|
1228
1230
|
}
|
|
@@ -1656,6 +1658,22 @@ class HybridRnIap : HybridRnIapSpec() {
|
|
|
1656
1658
|
}
|
|
1657
1659
|
}
|
|
1658
1660
|
|
|
1661
|
+
/**
|
|
1662
|
+
* Read IAPKit API key from AndroidManifest.xml meta-data (set by config plugin).
|
|
1663
|
+
* Config plugin sets: <meta-data android:name="dev.iapkit.API_KEY" android:value="..." />
|
|
1664
|
+
*/
|
|
1665
|
+
private fun getIapkitApiKeyFromManifest(): String? {
|
|
1666
|
+
return try {
|
|
1667
|
+
val appInfo = context.packageManager.getApplicationInfo(
|
|
1668
|
+
context.packageName,
|
|
1669
|
+
android.content.pm.PackageManager.GET_META_DATA
|
|
1670
|
+
)
|
|
1671
|
+
appInfo.metaData?.getString("dev.iapkit.API_KEY")
|
|
1672
|
+
} catch (e: Exception) {
|
|
1673
|
+
null
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1659
1677
|
private fun toErrorResult(
|
|
1660
1678
|
error: OpenIAPError,
|
|
1661
1679
|
productId: String? = null,
|
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] = [:]
|
|
@@ -242,9 +242,12 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
242
242
|
let payloads = RnIapHelper.sanitizeArray(OpenIapSerialization.purchases(purchases))
|
|
243
243
|
RnIapLog.result("getAvailablePurchases", payloads)
|
|
244
244
|
return payloads.map { RnIapHelper.convertPurchaseDictionary($0) }
|
|
245
|
+
} catch let purchaseError as PurchaseError {
|
|
246
|
+
RnIapLog.failure("getAvailablePurchases", error: purchaseError)
|
|
247
|
+
throw OpenIapException.from(purchaseError)
|
|
245
248
|
} catch {
|
|
246
249
|
RnIapLog.failure("getAvailablePurchases", error: error)
|
|
247
|
-
throw error
|
|
250
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
248
251
|
}
|
|
249
252
|
}
|
|
250
253
|
}
|
|
@@ -259,9 +262,12 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
259
262
|
let payloads = RnIapHelper.sanitizeArray(subscriptions.map { OpenIapSerialization.encode($0) })
|
|
260
263
|
RnIapLog.result("getActiveSubscriptions", payloads)
|
|
261
264
|
return payloads.map { RnIapHelper.convertActiveSubscriptionDictionary($0) }
|
|
265
|
+
} catch let purchaseError as PurchaseError {
|
|
266
|
+
RnIapLog.failure("getActiveSubscriptions", error: purchaseError)
|
|
267
|
+
throw OpenIapException.from(purchaseError)
|
|
262
268
|
} catch {
|
|
263
269
|
RnIapLog.failure("getActiveSubscriptions", error: error)
|
|
264
|
-
throw error
|
|
270
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
265
271
|
}
|
|
266
272
|
}
|
|
267
273
|
}
|
|
@@ -274,9 +280,12 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
274
280
|
let hasActive = try await OpenIapModule.shared.hasActiveSubscriptions(subscriptionIds)
|
|
275
281
|
RnIapLog.result("hasActiveSubscriptions", hasActive)
|
|
276
282
|
return hasActive
|
|
283
|
+
} catch let purchaseError as PurchaseError {
|
|
284
|
+
RnIapLog.failure("hasActiveSubscriptions", error: purchaseError)
|
|
285
|
+
throw OpenIapException.from(purchaseError)
|
|
277
286
|
} catch {
|
|
278
287
|
RnIapLog.failure("hasActiveSubscriptions", error: error)
|
|
279
|
-
throw error
|
|
288
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
280
289
|
}
|
|
281
290
|
}
|
|
282
291
|
}
|
|
@@ -297,7 +306,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
297
306
|
purchasePayload = ["transactionIdentifier": iosParams.transactionId]
|
|
298
307
|
}
|
|
299
308
|
guard let purchasePayload else {
|
|
300
|
-
throw
|
|
309
|
+
throw OpenIapException.make(code: .purchaseError, message: "Missing purchase context for \(iosParams.transactionId)")
|
|
301
310
|
}
|
|
302
311
|
let sanitizedPayload = RnIapHelper.sanitizeDictionary(purchasePayload)
|
|
303
312
|
RnIapLog.payload("finishTransaction.nativePayload", sanitizedPayload)
|
|
@@ -311,7 +320,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
311
320
|
} catch {
|
|
312
321
|
RnIapLog.failure("finishTransaction", error: error)
|
|
313
322
|
let tid = iosParams.transactionId
|
|
314
|
-
throw
|
|
323
|
+
throw OpenIapException.make(code: .purchaseError, message: "Transaction not found: \(tid)")
|
|
315
324
|
}
|
|
316
325
|
}
|
|
317
326
|
}
|
|
@@ -321,7 +330,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
321
330
|
do {
|
|
322
331
|
// Extract SKU from apple options (new platform-specific structure)
|
|
323
332
|
guard let appleOptions = params.apple, !appleOptions.sku.isEmpty else {
|
|
324
|
-
throw
|
|
333
|
+
throw OpenIapException.make(code: .developerError, message: "Missing required parameter: apple.sku")
|
|
325
334
|
}
|
|
326
335
|
let sku = appleOptions.sku
|
|
327
336
|
|
|
@@ -348,9 +357,12 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
348
357
|
latestTransaction: latest
|
|
349
358
|
)
|
|
350
359
|
return .first(mapped)
|
|
360
|
+
} catch let purchaseError as PurchaseError {
|
|
361
|
+
RnIapLog.failure("validateReceiptIOS", error: purchaseError)
|
|
362
|
+
throw OpenIapException.from(purchaseError)
|
|
351
363
|
} catch {
|
|
352
364
|
RnIapLog.failure("validateReceiptIOS", error: error)
|
|
353
|
-
throw
|
|
365
|
+
throw OpenIapException.make(code: .purchaseVerificationFailed, message: error.localizedDescription)
|
|
354
366
|
}
|
|
355
367
|
}
|
|
356
368
|
}
|
|
@@ -364,8 +376,11 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
364
376
|
var propsDict: [String: Any] = ["provider": params.provider.stringValue]
|
|
365
377
|
if let iapkit = params.iapkit {
|
|
366
378
|
var iapkitDict: [String: Any] = [:]
|
|
379
|
+
// Use provided apiKey, or fallback to Info.plist IAPKitAPIKey (set by config plugin)
|
|
367
380
|
if let apiKey = iapkit.apiKey {
|
|
368
381
|
iapkitDict["apiKey"] = apiKey
|
|
382
|
+
} else if let plistApiKey = Bundle.main.object(forInfoDictionaryKey: "IAPKitAPIKey") as? String {
|
|
383
|
+
iapkitDict["apiKey"] = plistApiKey
|
|
369
384
|
}
|
|
370
385
|
if let apple = iapkit.apple {
|
|
371
386
|
iapkitDict["apple"] = ["jws": apple.jws]
|
|
@@ -404,9 +419,13 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
404
419
|
errors: nitroErrors,
|
|
405
420
|
provider: PurchaseVerificationProvider(fromString: result.provider.rawValue) ?? .iapkit
|
|
406
421
|
)
|
|
422
|
+
} catch let purchaseError as PurchaseError {
|
|
423
|
+
// Convert PurchaseError to OpenIapException to preserve message through Nitro bridge
|
|
424
|
+
RnIapLog.failure("verifyPurchaseWithProvider", error: purchaseError)
|
|
425
|
+
throw OpenIapException.from(purchaseError)
|
|
407
426
|
} catch {
|
|
408
427
|
RnIapLog.failure("verifyPurchaseWithProvider", error: error)
|
|
409
|
-
throw
|
|
428
|
+
throw OpenIapException.make(code: .purchaseVerificationFailed, message: error.localizedDescription)
|
|
410
429
|
}
|
|
411
430
|
}
|
|
412
431
|
}
|
|
@@ -418,9 +437,12 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
418
437
|
let storefront = try await OpenIapModule.shared.getStorefrontIOS()
|
|
419
438
|
RnIapLog.result("getStorefront", storefront)
|
|
420
439
|
return storefront
|
|
440
|
+
} catch let purchaseError as PurchaseError {
|
|
441
|
+
RnIapLog.failure("getStorefront", error: purchaseError)
|
|
442
|
+
throw OpenIapException.from(purchaseError)
|
|
421
443
|
} catch {
|
|
422
444
|
RnIapLog.failure("getStorefront", error: error)
|
|
423
|
-
throw
|
|
445
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
424
446
|
}
|
|
425
447
|
}
|
|
426
448
|
}
|
|
@@ -481,9 +503,12 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
481
503
|
let payload = RnIapHelper.sanitizeDictionary(OpenIapSerialization.encode(product))
|
|
482
504
|
RnIapLog.result("getPromotedProductIOS", payload)
|
|
483
505
|
return RnIapHelper.convertProductDictionary(payload)
|
|
506
|
+
} catch let purchaseError as PurchaseError {
|
|
507
|
+
RnIapLog.failure("getPromotedProductIOS", error: purchaseError)
|
|
508
|
+
throw OpenIapException.from(purchaseError)
|
|
484
509
|
} catch {
|
|
485
510
|
RnIapLog.failure("getPromotedProductIOS", error: error)
|
|
486
|
-
throw
|
|
511
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
487
512
|
}
|
|
488
513
|
}
|
|
489
514
|
}
|
|
@@ -515,11 +540,11 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
515
540
|
} catch {
|
|
516
541
|
// Fallback with explicit error for simulator or unsupported cases
|
|
517
542
|
RnIapLog.failure("presentCodeRedemptionSheetIOS", error: error)
|
|
518
|
-
throw
|
|
543
|
+
throw OpenIapException.make(code: .featureNotSupported)
|
|
519
544
|
}
|
|
520
545
|
}
|
|
521
546
|
}
|
|
522
|
-
|
|
547
|
+
|
|
523
548
|
func clearTransactionIOS() throws -> Promise<Void> {
|
|
524
549
|
return Promise.async {
|
|
525
550
|
do {
|
|
@@ -587,11 +612,11 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
587
612
|
return Optional<NitroPurchase>.none
|
|
588
613
|
} catch {
|
|
589
614
|
RnIapLog.failure("currentEntitlementIOS", error: error)
|
|
590
|
-
throw
|
|
615
|
+
throw OpenIapException.make(code: .skuNotFound, productId: sku)
|
|
591
616
|
}
|
|
592
617
|
}
|
|
593
618
|
}
|
|
594
|
-
|
|
619
|
+
|
|
595
620
|
func latestTransactionIOS(sku: String) throws -> Promise<NitroPurchase?> {
|
|
596
621
|
return Promise.async {
|
|
597
622
|
try self.ensureConnection()
|
|
@@ -613,11 +638,11 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
613
638
|
return Optional<NitroPurchase>.none
|
|
614
639
|
} catch {
|
|
615
640
|
RnIapLog.failure("latestTransactionIOS", error: error)
|
|
616
|
-
throw
|
|
641
|
+
throw OpenIapException.make(code: .skuNotFound, productId: sku)
|
|
617
642
|
}
|
|
618
643
|
}
|
|
619
644
|
}
|
|
620
|
-
|
|
645
|
+
|
|
621
646
|
func getPendingTransactionsIOS() throws -> Promise<[NitroPurchase]> {
|
|
622
647
|
return Promise.async {
|
|
623
648
|
do {
|
|
@@ -651,13 +676,16 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
651
676
|
let ok = try await OpenIapModule.shared.syncIOS()
|
|
652
677
|
RnIapLog.result("syncIOS", ok)
|
|
653
678
|
return ok
|
|
679
|
+
} catch let purchaseError as PurchaseError {
|
|
680
|
+
RnIapLog.failure("syncIOS", error: purchaseError)
|
|
681
|
+
throw OpenIapException.from(purchaseError)
|
|
654
682
|
} catch {
|
|
655
683
|
RnIapLog.failure("syncIOS", error: error)
|
|
656
|
-
throw
|
|
684
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
657
685
|
}
|
|
658
686
|
}
|
|
659
687
|
}
|
|
660
|
-
|
|
688
|
+
|
|
661
689
|
func showManageSubscriptionsIOS() throws -> Promise<[NitroPurchase]> {
|
|
662
690
|
return Promise.async {
|
|
663
691
|
try self.ensureConnection()
|
|
@@ -675,9 +703,12 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
675
703
|
let payloads = RnIapHelper.sanitizeArray(OpenIapSerialization.purchases(purchases))
|
|
676
704
|
RnIapLog.result("showManageSubscriptionsIOS", payloads)
|
|
677
705
|
return payloads.map { RnIapHelper.convertPurchaseDictionary($0) }
|
|
706
|
+
} catch let purchaseError as PurchaseError {
|
|
707
|
+
RnIapLog.failure("showManageSubscriptionsIOS", error: purchaseError)
|
|
708
|
+
throw OpenIapException.from(purchaseError)
|
|
678
709
|
} catch {
|
|
679
710
|
RnIapLog.failure("showManageSubscriptionsIOS", error: error)
|
|
680
|
-
throw
|
|
711
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
681
712
|
}
|
|
682
713
|
}
|
|
683
714
|
}
|
|
@@ -690,13 +721,16 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
690
721
|
try await OpenIapModule.shared.deepLinkToSubscriptions(nil)
|
|
691
722
|
RnIapLog.result("deepLinkToSubscriptionsIOS", true)
|
|
692
723
|
return true
|
|
724
|
+
} catch let purchaseError as PurchaseError {
|
|
725
|
+
RnIapLog.failure("deepLinkToSubscriptionsIOS", error: purchaseError)
|
|
726
|
+
throw OpenIapException.from(purchaseError)
|
|
693
727
|
} catch {
|
|
694
728
|
RnIapLog.failure("deepLinkToSubscriptionsIOS", error: error)
|
|
695
|
-
throw
|
|
729
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
696
730
|
}
|
|
697
731
|
}
|
|
698
732
|
}
|
|
699
|
-
|
|
733
|
+
|
|
700
734
|
func isEligibleForIntroOfferIOS(groupID: String) throws -> Promise<Bool> {
|
|
701
735
|
return Promise.async {
|
|
702
736
|
RnIapLog.payload("isEligibleForIntroOfferIOS", ["groupID": groupID])
|
|
@@ -716,10 +750,10 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
716
750
|
return receipt
|
|
717
751
|
} catch let purchaseError as PurchaseError {
|
|
718
752
|
RnIapLog.failure("getReceiptDataIOS", error: purchaseError)
|
|
719
|
-
throw purchaseError
|
|
753
|
+
throw OpenIapException.from(purchaseError)
|
|
720
754
|
} catch {
|
|
721
755
|
RnIapLog.failure("getReceiptDataIOS", error: error)
|
|
722
|
-
throw
|
|
756
|
+
throw OpenIapException.make(code: .receiptFailed, message: error.localizedDescription)
|
|
723
757
|
}
|
|
724
758
|
}
|
|
725
759
|
}
|
|
@@ -734,10 +768,10 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
734
768
|
return receipt
|
|
735
769
|
} catch let purchaseError as PurchaseError {
|
|
736
770
|
RnIapLog.failure("getReceiptIOS", error: purchaseError)
|
|
737
|
-
throw purchaseError
|
|
771
|
+
throw OpenIapException.from(purchaseError)
|
|
738
772
|
} catch {
|
|
739
773
|
RnIapLog.failure("getReceiptIOS", error: error)
|
|
740
|
-
throw
|
|
774
|
+
throw OpenIapException.make(code: .receiptFailed, message: error.localizedDescription)
|
|
741
775
|
}
|
|
742
776
|
}
|
|
743
777
|
}
|
|
@@ -752,14 +786,14 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
752
786
|
return receipt
|
|
753
787
|
} catch let purchaseError as PurchaseError {
|
|
754
788
|
RnIapLog.failure("requestReceiptRefreshIOS", error: purchaseError)
|
|
755
|
-
throw purchaseError
|
|
789
|
+
throw OpenIapException.from(purchaseError)
|
|
756
790
|
} catch {
|
|
757
791
|
RnIapLog.failure("requestReceiptRefreshIOS", error: error)
|
|
758
|
-
throw
|
|
792
|
+
throw OpenIapException.make(code: .receiptFailed, message: error.localizedDescription)
|
|
759
793
|
}
|
|
760
794
|
}
|
|
761
795
|
}
|
|
762
|
-
|
|
796
|
+
|
|
763
797
|
func isTransactionVerifiedIOS(sku: String) throws -> Promise<Bool> {
|
|
764
798
|
return Promise.async {
|
|
765
799
|
try self.ensureConnection()
|
|
@@ -781,11 +815,11 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
781
815
|
return jws
|
|
782
816
|
} catch {
|
|
783
817
|
RnIapLog.failure("getTransactionJwsIOS", error: error)
|
|
784
|
-
throw
|
|
818
|
+
throw OpenIapException.make(code: .transactionValidationFailed, message: "Can't find transaction for sku \(sku)")
|
|
785
819
|
}
|
|
786
820
|
}
|
|
787
821
|
}
|
|
788
|
-
|
|
822
|
+
|
|
789
823
|
func beginRefundRequestIOS(sku: String) throws -> Promise<String?> {
|
|
790
824
|
return Promise.async {
|
|
791
825
|
do {
|
|
@@ -919,7 +953,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
919
953
|
|
|
920
954
|
private func ensureConnection() throws {
|
|
921
955
|
guard isInitialized else {
|
|
922
|
-
throw
|
|
956
|
+
throw OpenIapException.make(code: .initConnection, message: "Connection not initialized. Call initConnection() first.")
|
|
923
957
|
}
|
|
924
958
|
}
|
|
925
959
|
|
|
@@ -1021,7 +1055,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1021
1055
|
|
|
1022
1056
|
func deepLinkToSubscriptionsAndroid(options: NitroDeepLinkOptionsAndroid) throws -> Promise<Void> {
|
|
1023
1057
|
return Promise.async {
|
|
1024
|
-
throw
|
|
1058
|
+
throw OpenIapException.make(code: .featureNotSupported)
|
|
1025
1059
|
}
|
|
1026
1060
|
}
|
|
1027
1061
|
|
|
@@ -1029,19 +1063,19 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1029
1063
|
|
|
1030
1064
|
func checkAlternativeBillingAvailabilityAndroid() throws -> Promise<Bool> {
|
|
1031
1065
|
return Promise.async {
|
|
1032
|
-
throw
|
|
1066
|
+
throw OpenIapException.make(code: .featureNotSupported)
|
|
1033
1067
|
}
|
|
1034
1068
|
}
|
|
1035
1069
|
|
|
1036
1070
|
func showAlternativeBillingDialogAndroid() throws -> Promise<Bool> {
|
|
1037
1071
|
return Promise.async {
|
|
1038
|
-
throw
|
|
1072
|
+
throw OpenIapException.make(code: .featureNotSupported)
|
|
1039
1073
|
}
|
|
1040
1074
|
}
|
|
1041
1075
|
|
|
1042
1076
|
func createAlternativeBillingTokenAndroid(sku: String?) throws -> Promise<String?> {
|
|
1043
1077
|
return Promise.async {
|
|
1044
|
-
throw
|
|
1078
|
+
throw OpenIapException.make(code: .featureNotSupported)
|
|
1045
1079
|
}
|
|
1046
1080
|
}
|
|
1047
1081
|
|
|
@@ -1061,19 +1095,19 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1061
1095
|
|
|
1062
1096
|
func isBillingProgramAvailableAndroid(program: BillingProgramAndroid) throws -> Promise<NitroBillingProgramAvailabilityResultAndroid> {
|
|
1063
1097
|
return Promise.async {
|
|
1064
|
-
throw
|
|
1098
|
+
throw OpenIapException.make(code: .featureNotSupported, message: "Billing Programs API is Android-only")
|
|
1065
1099
|
}
|
|
1066
1100
|
}
|
|
1067
1101
|
|
|
1068
1102
|
func createBillingProgramReportingDetailsAndroid(program: BillingProgramAndroid) throws -> Promise<NitroBillingProgramReportingDetailsAndroid> {
|
|
1069
1103
|
return Promise.async {
|
|
1070
|
-
throw
|
|
1104
|
+
throw OpenIapException.make(code: .featureNotSupported, message: "Billing Programs API is Android-only")
|
|
1071
1105
|
}
|
|
1072
1106
|
}
|
|
1073
1107
|
|
|
1074
1108
|
func launchExternalLinkAndroid(params: NitroLaunchExternalLinkParamsAndroid) throws -> Promise<Bool> {
|
|
1075
1109
|
return Promise.async {
|
|
1076
|
-
throw
|
|
1110
|
+
throw OpenIapException.make(code: .featureNotSupported, message: "Billing Programs API is Android-only")
|
|
1077
1111
|
}
|
|
1078
1112
|
}
|
|
1079
1113
|
|
|
@@ -1089,12 +1123,15 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1089
1123
|
let canPresent = try await OpenIapModule.shared.canPresentExternalPurchaseNoticeIOS()
|
|
1090
1124
|
RnIapLog.result("canPresentExternalPurchaseNoticeIOS", canPresent)
|
|
1091
1125
|
return canPresent
|
|
1126
|
+
} catch let purchaseError as PurchaseError {
|
|
1127
|
+
RnIapLog.failure("canPresentExternalPurchaseNoticeIOS", error: purchaseError)
|
|
1128
|
+
throw OpenIapException.from(purchaseError)
|
|
1092
1129
|
} catch {
|
|
1093
1130
|
RnIapLog.failure("canPresentExternalPurchaseNoticeIOS", error: error)
|
|
1094
|
-
throw
|
|
1131
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
1095
1132
|
}
|
|
1096
1133
|
} else {
|
|
1097
|
-
let err =
|
|
1134
|
+
let err = OpenIapException.make(code: .featureNotSupported, message: "External purchase notice requires iOS 16.0 or later")
|
|
1098
1135
|
RnIapLog.failure("canPresentExternalPurchaseNoticeIOS", error: err)
|
|
1099
1136
|
throw err
|
|
1100
1137
|
}
|
|
@@ -1113,7 +1150,7 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1113
1150
|
// Convert OpenIAP action to Nitro action via raw value
|
|
1114
1151
|
let actionString = result.result.rawValue
|
|
1115
1152
|
guard let nitroAction = ExternalPurchaseNoticeAction(fromString: actionString) else {
|
|
1116
|
-
throw
|
|
1153
|
+
throw OpenIapException.make(code: .serviceError, message: "Invalid action: \(actionString)")
|
|
1117
1154
|
}
|
|
1118
1155
|
|
|
1119
1156
|
let nitroResult = ExternalPurchaseNoticeResultIOS(
|
|
@@ -1122,12 +1159,15 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1122
1159
|
)
|
|
1123
1160
|
RnIapLog.result("presentExternalPurchaseNoticeSheetIOS", result)
|
|
1124
1161
|
return nitroResult
|
|
1162
|
+
} catch let purchaseError as PurchaseError {
|
|
1163
|
+
RnIapLog.failure("presentExternalPurchaseNoticeSheetIOS", error: purchaseError)
|
|
1164
|
+
throw OpenIapException.from(purchaseError)
|
|
1125
1165
|
} catch {
|
|
1126
1166
|
RnIapLog.failure("presentExternalPurchaseNoticeSheetIOS", error: error)
|
|
1127
|
-
throw
|
|
1167
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
1128
1168
|
}
|
|
1129
1169
|
} else {
|
|
1130
|
-
let err =
|
|
1170
|
+
let err = OpenIapException.make(code: .featureNotSupported, message: "External purchase notice requires iOS 16.0 or later")
|
|
1131
1171
|
RnIapLog.failure("presentExternalPurchaseNoticeSheetIOS", error: err)
|
|
1132
1172
|
throw err
|
|
1133
1173
|
}
|
|
@@ -1148,12 +1188,15 @@ class HybridRnIap: HybridRnIapSpec {
|
|
|
1148
1188
|
)
|
|
1149
1189
|
RnIapLog.result("presentExternalPurchaseLinkIOS", result)
|
|
1150
1190
|
return nitroResult
|
|
1191
|
+
} catch let purchaseError as PurchaseError {
|
|
1192
|
+
RnIapLog.failure("presentExternalPurchaseLinkIOS", error: purchaseError)
|
|
1193
|
+
throw OpenIapException.from(purchaseError)
|
|
1151
1194
|
} catch {
|
|
1152
1195
|
RnIapLog.failure("presentExternalPurchaseLinkIOS", error: error)
|
|
1153
|
-
throw
|
|
1196
|
+
throw OpenIapException.make(code: .serviceError, message: error.localizedDescription)
|
|
1154
1197
|
}
|
|
1155
1198
|
} else {
|
|
1156
|
-
let err =
|
|
1199
|
+
let err = OpenIapException.make(code: .featureNotSupported, message: "External purchase link requires iOS 16.0 or later")
|
|
1157
1200
|
RnIapLog.failure("presentExternalPurchaseLinkIOS", error: err)
|
|
1158
1201
|
throw err
|
|
1159
1202
|
}
|
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/package.json
CHANGED