react-native-iap 13.0.4 → 14.0.0-rc.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.
- package/NitroIap.podspec +33 -0
- package/README.md +163 -51
- package/android/CMakeLists.txt +32 -0
- package/android/build.gradle +107 -138
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -13
- package/android/src/main/AndroidManifest.xml +1 -4
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +843 -0
- package/android/src/main/java/com/margelo/nitro/iap/NitroIapPackage.java +33 -0
- package/android/src/main/java/com/margelo/nitro/iap/Types.kt +173 -0
- package/ios/Bridge.h +8 -0
- package/ios/ErrorUtils.swift +153 -0
- package/ios/HybridRnIap.swift +1326 -0
- package/ios/ProductStore.swift +33 -0
- package/ios/reactnativeiap.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/reactnativeiap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/lib/module/helpers/subscription.js +56 -0
- package/lib/module/helpers/subscription.js.map +1 -0
- package/lib/module/hooks/useIAP.js +175 -123
- package/lib/module/hooks/useIAP.js.map +1 -1
- package/lib/module/index.js +1015 -9
- package/lib/module/index.js.map +1 -1
- package/lib/module/package.json +1 -0
- package/lib/module/specs/RnIap.nitro.js +4 -0
- package/lib/{commonjs/modules/common.js.map → module/specs/RnIap.nitro.js.map} +1 -1
- package/lib/module/types.js +187 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/error.js +80 -0
- package/lib/module/utils/error.js.map +1 -0
- package/lib/module/utils/type-bridge.js +202 -0
- package/lib/module/utils/type-bridge.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/plugin/src/withIAP.d.ts +2 -7
- package/lib/typescript/plugin/src/withIAP.d.ts.map +1 -1
- package/lib/typescript/scripts/check-nitro-versions.d.ts +3 -0
- package/lib/typescript/scripts/check-nitro-versions.d.ts.map +1 -0
- package/lib/typescript/src/helpers/subscription.d.ts +14 -0
- package/lib/typescript/src/helpers/subscription.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useIAP.d.ts +42 -26
- package/lib/typescript/src/hooks/useIAP.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +356 -8
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/RnIap.nitro.d.ts +404 -0
- package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +544 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/utils/error.d.ts +30 -0
- package/lib/typescript/src/utils/error.d.ts.map +1 -0
- package/lib/typescript/src/utils/type-bridge.d.ts +40 -0
- package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -0
- package/nitro.json +17 -0
- package/nitrogen/generated/android/NitroIap+autolinking.cmake +80 -0
- package/nitrogen/generated/android/NitroIap+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroIapOnLoad.cpp +50 -0
- package/nitrogen/generated/android/NitroIapOnLoad.hpp +25 -0
- package/nitrogen/generated/android/c++/JFunc_void_NitroProduct.hpp +78 -0
- package/nitrogen/generated/android/c++/JFunc_void_NitroPurchase.hpp +78 -0
- package/nitrogen/generated/android/c++/JFunc_void_NitroPurchaseResult.hpp +78 -0
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +577 -0
- package/nitrogen/generated/android/c++/JHybridRnIapSpec.hpp +93 -0
- package/nitrogen/generated/android/c++/JNitroAndroidReceiptValidationOptions.hpp +66 -0
- package/nitrogen/generated/android/c++/JNitroAvailablePurchasesAndroidOptions.hpp +54 -0
- package/nitrogen/generated/android/c++/JNitroAvailablePurchasesIosOptions.hpp +65 -0
- package/nitrogen/generated/android/c++/JNitroAvailablePurchasesOptions.hpp +62 -0
- package/nitrogen/generated/android/c++/JNitroFinishTransactionAndroidParams.hpp +58 -0
- package/nitrogen/generated/android/c++/JNitroFinishTransactionIosParams.hpp +53 -0
- package/nitrogen/generated/android/c++/JNitroFinishTransactionParams.hpp +62 -0
- package/nitrogen/generated/android/c++/JNitroProduct.hpp +154 -0
- package/nitrogen/generated/android/c++/JNitroPurchase.hpp +122 -0
- package/nitrogen/generated/android/c++/JNitroPurchaseRequest.hpp +66 -0
- package/nitrogen/generated/android/c++/JNitroPurchaseResult.hpp +70 -0
- package/nitrogen/generated/android/c++/JNitroReceiptValidationParams.hpp +60 -0
- package/nitrogen/generated/android/c++/JNitroReceiptValidationResultAndroid.hpp +122 -0
- package/nitrogen/generated/android/c++/JNitroReceiptValidationResultIOS.hpp +68 -0
- package/nitrogen/generated/android/c++/JNitroRequestPurchaseAndroid.hpp +115 -0
- package/nitrogen/generated/android/c++/JNitroRequestPurchaseIos.hpp +84 -0
- package/nitrogen/generated/android/c++/JNitroSubscriptionOffer.hpp +57 -0
- package/nitrogen/generated/android/c++/JNitroSubscriptionRenewalInfo.hpp +74 -0
- package/nitrogen/generated/android/c++/JNitroSubscriptionStatus.hpp +64 -0
- package/nitrogen/generated/android/c++/JVariant_Boolean_NitroPurchaseResult.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_Boolean_NitroPurchaseResult.hpp +71 -0
- package/nitrogen/generated/android/c++/JVariant_NitroReceiptValidationResultIOS_NitroReceiptValidationResultAndroid.cpp +26 -0
- package/nitrogen/generated/android/c++/JVariant_NitroReceiptValidationResultIOS_NitroReceiptValidationResultAndroid.hpp +75 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Func_void_NitroProduct.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Func_void_NitroPurchase.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Func_void_NitroPurchaseResult.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/HybridRnIapSpec.kt +198 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroAndroidReceiptValidationOptions.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroAvailablePurchasesAndroidOptions.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroAvailablePurchasesIosOptions.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroAvailablePurchasesOptions.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroFinishTransactionAndroidParams.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroFinishTransactionIosParams.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroFinishTransactionParams.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroIapOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroProduct.kt +104 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchase.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchaseRequest.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroPurchaseResult.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroReceiptValidationParams.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroReceiptValidationResultAndroid.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroReceiptValidationResultIOS.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroRequestPurchaseAndroid.kt +47 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroRequestPurchaseIos.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroSubscriptionOffer.kt +32 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroSubscriptionRenewalInfo.kt +44 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroSubscriptionStatus.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_Boolean_NitroPurchaseResult.kt +42 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NitroReceiptValidationResultIOS_NitroReceiptValidationResultAndroid.kt +42 -0
- package/nitrogen/generated/ios/NitroIap+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.cpp +152 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +1061 -0
- package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +107 -0
- package/nitrogen/generated/ios/NitroIapAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NitroIapAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +355 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_NitroProduct.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_NitroPurchase.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_NitroPurchaseResult.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_NitroProduct_.swift +54 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_NitroPurchase_.swift +54 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +54 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_std__vector_NitroSubscriptionStatus__.swift +54 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__variant_NitroReceiptValidationResultIOS__NitroReceiptValidationResultAndroid_.swift +59 -0
- package/nitrogen/generated/ios/swift/Func_void_std__variant_bool__NitroPurchaseResult_.swift +59 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_NitroProduct_.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_NitroPurchase_.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridRnIapSpec.swift +78 -0
- package/nitrogen/generated/ios/swift/HybridRnIapSpec_cxx.swift +750 -0
- package/nitrogen/generated/ios/swift/NitroAndroidReceiptValidationOptions.swift +80 -0
- package/nitrogen/generated/ios/swift/NitroAvailablePurchasesAndroidOptions.swift +54 -0
- package/nitrogen/generated/ios/swift/NitroAvailablePurchasesIosOptions.swift +116 -0
- package/nitrogen/generated/ios/swift/NitroAvailablePurchasesOptions.swift +84 -0
- package/nitrogen/generated/ios/swift/NitroFinishTransactionAndroidParams.swift +58 -0
- package/nitrogen/generated/ios/swift/NitroFinishTransactionIosParams.swift +35 -0
- package/nitrogen/generated/ios/swift/NitroFinishTransactionParams.swift +84 -0
- package/nitrogen/generated/ios/swift/NitroProduct.swift +653 -0
- package/nitrogen/generated/ios/swift/NitroPurchase.swift +453 -0
- package/nitrogen/generated/ios/swift/NitroPurchaseRequest.swift +84 -0
- package/nitrogen/generated/ios/swift/NitroPurchaseResult.swift +117 -0
- package/nitrogen/generated/ios/swift/NitroReceiptValidationParams.swift +65 -0
- package/nitrogen/generated/ios/swift/NitroReceiptValidationResultAndroid.swift +258 -0
- package/nitrogen/generated/ios/swift/NitroReceiptValidationResultIOS.swift +87 -0
- package/nitrogen/generated/ios/swift/NitroRequestPurchaseAndroid.swift +225 -0
- package/nitrogen/generated/ios/swift/NitroRequestPurchaseIos.swift +161 -0
- package/nitrogen/generated/ios/swift/NitroSubscriptionOffer.swift +46 -0
- package/nitrogen/generated/ios/swift/NitroSubscriptionRenewalInfo.swift +152 -0
- package/nitrogen/generated/ios/swift/NitroSubscriptionStatus.swift +76 -0
- package/nitrogen/generated/ios/swift/Variant_Bool_NitroPurchaseResult.swift +18 -0
- package/nitrogen/generated/ios/swift/Variant_NitroReceiptValidationResultIOS_NitroReceiptValidationResultAndroid.swift +18 -0
- package/nitrogen/generated/shared/c++/HybridRnIapSpec.cpp +50 -0
- package/nitrogen/generated/shared/c++/HybridRnIapSpec.hpp +125 -0
- package/nitrogen/generated/shared/c++/NitroAndroidReceiptValidationOptions.hpp +80 -0
- package/nitrogen/generated/shared/c++/NitroAvailablePurchasesAndroidOptions.hpp +68 -0
- package/nitrogen/generated/shared/c++/NitroAvailablePurchasesIosOptions.hpp +79 -0
- package/nitrogen/generated/shared/c++/NitroAvailablePurchasesOptions.hpp +76 -0
- package/nitrogen/generated/shared/c++/NitroFinishTransactionAndroidParams.hpp +72 -0
- package/nitrogen/generated/shared/c++/NitroFinishTransactionIosParams.hpp +67 -0
- package/nitrogen/generated/shared/c++/NitroFinishTransactionParams.hpp +76 -0
- package/nitrogen/generated/shared/c++/NitroProduct.hpp +168 -0
- package/nitrogen/generated/shared/c++/NitroPurchase.hpp +136 -0
- package/nitrogen/generated/shared/c++/NitroPurchaseRequest.hpp +76 -0
- package/nitrogen/generated/shared/c++/NitroPurchaseResult.hpp +84 -0
- package/nitrogen/generated/shared/c++/NitroReceiptValidationParams.hpp +74 -0
- package/nitrogen/generated/shared/c++/NitroReceiptValidationResultAndroid.hpp +136 -0
- package/nitrogen/generated/shared/c++/NitroReceiptValidationResultIOS.hpp +82 -0
- package/nitrogen/generated/shared/c++/NitroRequestPurchaseAndroid.hpp +95 -0
- package/nitrogen/generated/shared/c++/NitroRequestPurchaseIos.hpp +85 -0
- package/nitrogen/generated/shared/c++/NitroSubscriptionOffer.hpp +71 -0
- package/nitrogen/generated/shared/c++/NitroSubscriptionRenewalInfo.hpp +88 -0
- package/nitrogen/generated/shared/c++/NitroSubscriptionStatus.hpp +78 -0
- package/package.json +144 -104
- 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/build/withIAP.d.ts +2 -7
- package/plugin/build/withIAP.js +62 -51
- package/plugin/src/withIAP.ts +119 -0
- package/plugin/tsconfig.json +18 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
- package/src/helpers/subscription.ts +65 -0
- package/src/hooks/useIAP.ts +361 -222
- package/src/index.ts +1194 -9
- package/src/specs/RnIap.nitro.ts +501 -0
- package/src/types.ts +680 -0
- package/src/utils/error.ts +97 -0
- package/src/utils/type-bridge.ts +209 -0
- package/LICENSE +0 -21
- package/RNIap.podspec +0 -36
- package/android/src/amazon/AndroidManifest.xml +0 -14
- package/android/src/amazon/java/com/dooboolab/rniap/EventSender.kt +0 -10
- package/android/src/amazon/java/com/dooboolab/rniap/PurchasingServiceProxy.kt +0 -29
- package/android/src/amazon/java/com/dooboolab/rniap/PurchasingServiceProxyAmazonImpl.kt +0 -31
- package/android/src/amazon/java/com/dooboolab/rniap/RNIapActivityListener.kt +0 -55
- package/android/src/amazon/java/com/dooboolab/rniap/RNIapAmazonListener.kt +0 -325
- package/android/src/amazon/java/com/dooboolab/rniap/RNIapAmazonModule.kt +0 -244
- package/android/src/amazon/java/com/dooboolab/rniap/RNIapPackage.kt +0 -17
- package/android/src/amazon/java/com/dooboolab/rniap/modifysubscription/RNIapAmazonModifySubscriptionListener.kt +0 -88
- package/android/src/amazon/java/com/dooboolab/rniap/utils/Extensions.kt +0 -22
- package/android/src/main/java/com/dooboolab/rniap/PromiseUtils.kt +0 -62
- package/android/src/main/java/com/dooboolab/rniap/PromiseUtlis.kt +0 -43
- package/android/src/play/java/com/dooboolab/rniap/PlayUtils.kt +0 -114
- package/android/src/play/java/com/dooboolab/rniap/RNIapActivityListener.kt +0 -15
- package/android/src/play/java/com/dooboolab/rniap/RNIapModule.kt +0 -911
- package/android/src/play/java/com/dooboolab/rniap/RNIapPackage.kt +0 -17
- package/android/src/testAmazon/java/com/dooboolab/rniap/RNIapAmazonModuleTest.kt +0 -170
- package/app.plugin.js +0 -1
- package/ios/IapSerializationUtils.swift +0 -290
- package/ios/IapTypes.swift +0 -56
- package/ios/IapUtils.swift +0 -40
- package/ios/LatestPromiseKeeper.swift +0 -52
- package/ios/RNIapIos-Bridging-Header.h +0 -2
- package/ios/RNIapIos.m +0 -69
- package/ios/RNIapIos.swift +0 -868
- package/ios/RNIapIos.xcodeproj/project.pbxproj +0 -289
- package/ios/RNIapIosSk2.m +0 -126
- package/ios/RNIapIosSk2.swift +0 -1413
- package/ios/ThreadSafe.swift +0 -18
- package/lib/commonjs/eventEmitter.js +0 -195
- package/lib/commonjs/eventEmitter.js.map +0 -1
- package/lib/commonjs/hooks/index.js +0 -17
- package/lib/commonjs/hooks/index.js.map +0 -1
- package/lib/commonjs/hooks/useIAP.js +0 -203
- package/lib/commonjs/hooks/useIAP.js.map +0 -1
- package/lib/commonjs/hooks/withIAPContext.js +0 -95
- package/lib/commonjs/hooks/withIAPContext.js.map +0 -1
- package/lib/commonjs/iap.js +0 -836
- package/lib/commonjs/iap.js.map +0 -1
- package/lib/commonjs/index.js +0 -105
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/internal/enhancedFetch.js +0 -26
- package/lib/commonjs/internal/enhancedFetch.js.map +0 -1
- package/lib/commonjs/internal/fillProductsWithAdditionalData.js +0 -50
- package/lib/commonjs/internal/fillProductsWithAdditionalData.js.map +0 -1
- package/lib/commonjs/internal/index.js +0 -39
- package/lib/commonjs/internal/index.js.map +0 -1
- package/lib/commonjs/internal/platform.js +0 -119
- package/lib/commonjs/internal/platform.js.map +0 -1
- package/lib/commonjs/modules/amazon.js +0 -50
- package/lib/commonjs/modules/amazon.js.map +0 -1
- package/lib/commonjs/modules/android.js +0 -92
- package/lib/commonjs/modules/android.js.map +0 -1
- package/lib/commonjs/modules/common.js +0 -2
- package/lib/commonjs/modules/index.js +0 -50
- package/lib/commonjs/modules/index.js.map +0 -1
- package/lib/commonjs/modules/ios.js +0 -160
- package/lib/commonjs/modules/ios.js.map +0 -1
- package/lib/commonjs/modules/iosSk2.js +0 -113
- package/lib/commonjs/modules/iosSk2.js.map +0 -1
- package/lib/commonjs/purchaseError.js +0 -170
- package/lib/commonjs/purchaseError.js.map +0 -1
- package/lib/commonjs/types/amazon.js +0 -2
- package/lib/commonjs/types/amazon.js.map +0 -1
- package/lib/commonjs/types/android.js +0 -67
- package/lib/commonjs/types/android.js.map +0 -1
- package/lib/commonjs/types/apple.js +0 -24
- package/lib/commonjs/types/apple.js.map +0 -1
- package/lib/commonjs/types/appleSk2.js +0 -137
- package/lib/commonjs/types/appleSk2.js.map +0 -1
- package/lib/commonjs/types/index.js +0 -67
- package/lib/commonjs/types/index.js.map +0 -1
- package/lib/commonjs/utils/errorMapping.js +0 -83
- package/lib/commonjs/utils/errorMapping.js.map +0 -1
- package/lib/commonjs/utils/typeGuards.js +0 -74
- package/lib/commonjs/utils/typeGuards.js.map +0 -1
- package/lib/module/eventEmitter.js +0 -185
- package/lib/module/eventEmitter.js.map +0 -1
- package/lib/module/hooks/index.js +0 -2
- package/lib/module/hooks/index.js.map +0 -1
- package/lib/module/hooks/withIAPContext.js +0 -86
- package/lib/module/hooks/withIAPContext.js.map +0 -1
- package/lib/module/iap.js +0 -805
- package/lib/module/iap.js.map +0 -1
- package/lib/module/internal/enhancedFetch.js +0 -19
- package/lib/module/internal/enhancedFetch.js.map +0 -1
- package/lib/module/internal/fillProductsWithAdditionalData.js +0 -43
- package/lib/module/internal/fillProductsWithAdditionalData.js.map +0 -1
- package/lib/module/internal/index.js +0 -4
- package/lib/module/internal/index.js.map +0 -1
- package/lib/module/internal/platform.js +0 -102
- package/lib/module/internal/platform.js.map +0 -1
- package/lib/module/modules/amazon.js +0 -42
- package/lib/module/modules/amazon.js.map +0 -1
- package/lib/module/modules/android.js +0 -81
- package/lib/module/modules/android.js.map +0 -1
- package/lib/module/modules/common.js +0 -2
- package/lib/module/modules/common.js.map +0 -1
- package/lib/module/modules/index.js +0 -5
- package/lib/module/modules/index.js.map +0 -1
- package/lib/module/modules/ios.js +0 -145
- package/lib/module/modules/ios.js.map +0 -1
- package/lib/module/modules/iosSk2.js +0 -96
- package/lib/module/modules/iosSk2.js.map +0 -1
- package/lib/module/purchaseError.js +0 -164
- package/lib/module/purchaseError.js.map +0 -1
- package/lib/module/types/amazon.js +0 -2
- package/lib/module/types/amazon.js.map +0 -1
- package/lib/module/types/android.js +0 -63
- package/lib/module/types/android.js.map +0 -1
- package/lib/module/types/apple.js +0 -17
- package/lib/module/types/apple.js.map +0 -1
- package/lib/module/types/appleSk2.js +0 -129
- package/lib/module/types/appleSk2.js.map +0 -1
- package/lib/module/types/index.js +0 -75
- package/lib/module/types/index.js.map +0 -1
- package/lib/module/utils/errorMapping.js +0 -75
- package/lib/module/utils/errorMapping.js.map +0 -1
- package/lib/module/utils/typeGuards.js +0 -61
- package/lib/module/utils/typeGuards.js.map +0 -1
- package/lib/typescript/src/eventEmitter.d.ts +0 -151
- package/lib/typescript/src/eventEmitter.d.ts.map +0 -1
- package/lib/typescript/src/hooks/index.d.ts +0 -2
- package/lib/typescript/src/hooks/index.d.ts.map +0 -1
- package/lib/typescript/src/hooks/withIAPContext.d.ts +0 -27
- package/lib/typescript/src/hooks/withIAPContext.d.ts.map +0 -1
- package/lib/typescript/src/iap.d.ts +0 -503
- package/lib/typescript/src/iap.d.ts.map +0 -1
- package/lib/typescript/src/internal/enhancedFetch.d.ts +0 -6
- package/lib/typescript/src/internal/enhancedFetch.d.ts.map +0 -1
- package/lib/typescript/src/internal/fillProductsWithAdditionalData.d.ts +0 -7
- package/lib/typescript/src/internal/fillProductsWithAdditionalData.d.ts.map +0 -1
- package/lib/typescript/src/internal/index.d.ts +0 -4
- package/lib/typescript/src/internal/index.d.ts.map +0 -1
- package/lib/typescript/src/internal/platform.d.ts +0 -27
- package/lib/typescript/src/internal/platform.d.ts.map +0 -1
- package/lib/typescript/src/modules/amazon.d.ts +0 -55
- package/lib/typescript/src/modules/amazon.d.ts.map +0 -1
- package/lib/typescript/src/modules/android.d.ts +0 -74
- package/lib/typescript/src/modules/android.d.ts.map +0 -1
- package/lib/typescript/src/modules/common.d.ts +0 -14
- package/lib/typescript/src/modules/common.d.ts.map +0 -1
- package/lib/typescript/src/modules/index.d.ts +0 -5
- package/lib/typescript/src/modules/index.d.ts.map +0 -1
- package/lib/typescript/src/modules/ios.d.ts +0 -117
- package/lib/typescript/src/modules/ios.d.ts.map +0 -1
- package/lib/typescript/src/modules/iosSk2.d.ts +0 -140
- package/lib/typescript/src/modules/iosSk2.d.ts.map +0 -1
- package/lib/typescript/src/purchaseError.d.ts +0 -133
- package/lib/typescript/src/purchaseError.d.ts.map +0 -1
- package/lib/typescript/src/types/amazon.d.ts +0 -33
- package/lib/typescript/src/types/amazon.d.ts.map +0 -1
- package/lib/typescript/src/types/android.d.ts +0 -66
- package/lib/typescript/src/types/android.d.ts.map +0 -1
- package/lib/typescript/src/types/apple.d.ts +0 -27
- package/lib/typescript/src/types/apple.d.ts.map +0 -1
- package/lib/typescript/src/types/appleSk2.d.ts +0 -129
- package/lib/typescript/src/types/appleSk2.d.ts.map +0 -1
- package/lib/typescript/src/types/index.d.ts +0 -302
- package/lib/typescript/src/types/index.d.ts.map +0 -1
- package/lib/typescript/src/utils/errorMapping.d.ts +0 -29
- package/lib/typescript/src/utils/errorMapping.d.ts.map +0 -1
- package/lib/typescript/src/utils/typeGuards.d.ts +0 -53
- package/lib/typescript/src/utils/typeGuards.d.ts.map +0 -1
- package/src/eventEmitter.ts +0 -212
- package/src/hooks/index.ts +0 -1
- package/src/hooks/withIAPContext.tsx +0 -179
- package/src/iap.ts +0 -1046
- package/src/internal/enhancedFetch.ts +0 -25
- package/src/internal/fillProductsWithAdditionalData.ts +0 -47
- package/src/internal/index.ts +0 -3
- package/src/internal/platform.ts +0 -135
- package/src/modules/amazon.ts +0 -94
- package/src/modules/android.ts +0 -179
- package/src/modules/common.ts +0 -16
- package/src/modules/index.ts +0 -4
- package/src/modules/ios.ts +0 -229
- package/src/modules/iosSk2.ts +0 -194
- package/src/purchaseError.ts +0 -196
- package/src/types/amazon.ts +0 -40
- package/src/types/android.ts +0 -89
- package/src/types/apple.ts +0 -44
- package/src/types/appleSk2.ts +0 -275
- package/src/types/index.ts +0 -379
- package/src/utils/errorMapping.ts +0 -88
- package/src/utils/typeGuards.ts +0 -90
|
@@ -0,0 +1,1326 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import NitroModules
|
|
3
|
+
import StoreKit
|
|
4
|
+
#if os(iOS) || os(tvOS)
|
|
5
|
+
import UIKit
|
|
6
|
+
#endif
|
|
7
|
+
|
|
8
|
+
@available(iOS 15.0, *)
|
|
9
|
+
class HybridRnIap: HybridRnIapSpec {
|
|
10
|
+
// MARK: - Properties
|
|
11
|
+
|
|
12
|
+
private var updateListenerTask: Task<Void, Never>?
|
|
13
|
+
private var isInitialized: Bool = false
|
|
14
|
+
private var productStore: ProductStore?
|
|
15
|
+
private var transactions: [String: Transaction] = [:]
|
|
16
|
+
private var paymentObserver: PaymentObserver?
|
|
17
|
+
|
|
18
|
+
// Promoted products
|
|
19
|
+
private var promotedProduct: Product?
|
|
20
|
+
private var promotedPayment: SKPayment?
|
|
21
|
+
private var promotedSKProduct: SKProduct?
|
|
22
|
+
|
|
23
|
+
// Event listeners
|
|
24
|
+
private var purchaseUpdatedListeners: [(NitroPurchase) -> Void] = []
|
|
25
|
+
private var purchaseErrorListeners: [(NitroPurchaseResult) -> Void] = []
|
|
26
|
+
private var promotedProductListeners: [(NitroProduct) -> Void] = []
|
|
27
|
+
|
|
28
|
+
// MARK: - Initialization
|
|
29
|
+
|
|
30
|
+
override init() {
|
|
31
|
+
super.init()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
deinit {
|
|
35
|
+
updateListenerTask?.cancel()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// MARK: - Public Methods (Cross-platform)
|
|
39
|
+
|
|
40
|
+
func initConnection() throws -> Promise<Bool> {
|
|
41
|
+
return Promise.async {
|
|
42
|
+
// Clean up any existing state first (important for hot reload)
|
|
43
|
+
self.cleanupExistingState()
|
|
44
|
+
|
|
45
|
+
// StoreKit 2 doesn't require explicit connection initialization
|
|
46
|
+
// Just verify that the store is available
|
|
47
|
+
let canMakePayments = SKPaymentQueue.canMakePayments()
|
|
48
|
+
if canMakePayments {
|
|
49
|
+
self.isInitialized = true
|
|
50
|
+
self.productStore = ProductStore()
|
|
51
|
+
|
|
52
|
+
// Set up PaymentObserver for promoted products
|
|
53
|
+
if self.paymentObserver == nil {
|
|
54
|
+
self.paymentObserver = PaymentObserver(iapModule: self)
|
|
55
|
+
SKPaymentQueue.default().add(self.paymentObserver!)
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
59
|
+
code: IapErrorCode.iapNotAvailable,
|
|
60
|
+
message: "In-app purchases are not available on this device"
|
|
61
|
+
)
|
|
62
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
63
|
+
}
|
|
64
|
+
return canMakePayments
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func endConnection() throws -> Promise<Bool> {
|
|
69
|
+
return Promise.async {
|
|
70
|
+
self.cleanupExistingState()
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func fetchProducts(skus: [String], type: String) throws -> Promise<[NitroProduct]> {
|
|
76
|
+
return Promise.async {
|
|
77
|
+
do {
|
|
78
|
+
try self.ensureConnection()
|
|
79
|
+
print("[RnIap] fetchProducts called with skus: \(skus), type: \(type)")
|
|
80
|
+
|
|
81
|
+
// Fetch products from StoreKit 2
|
|
82
|
+
let storeProducts = try await StoreKit.Product.products(for: Set(skus))
|
|
83
|
+
print("[RnIap] StoreKit returned \(storeProducts.count) products")
|
|
84
|
+
|
|
85
|
+
// Store products in ProductStore
|
|
86
|
+
if let productStore = self.productStore {
|
|
87
|
+
await productStore.addProducts(storeProducts)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Convert StoreKit products to NitroProduct
|
|
91
|
+
let nitroProducts = storeProducts.map { storeProduct in
|
|
92
|
+
print("[RnIap] Converting product: \(storeProduct.id)")
|
|
93
|
+
return self.convertToNitroProduct(storeProduct, type: type)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
print("[RnIap] Returning \(nitroProducts.count) NitroProducts")
|
|
97
|
+
return nitroProducts
|
|
98
|
+
} catch {
|
|
99
|
+
print("[RnIap] Error fetching products: \(error)")
|
|
100
|
+
let errorJson = ErrorUtils.createErrorJson(from: error)
|
|
101
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func requestPurchase(request: NitroPurchaseRequest) throws -> Promise<Void> {
|
|
107
|
+
return Promise.async {
|
|
108
|
+
// iOS implementation
|
|
109
|
+
guard let iosRequest = request.ios else {
|
|
110
|
+
// No iOS request, send error event
|
|
111
|
+
let error = self.createPurchaseErrorResult(
|
|
112
|
+
code: IapErrorCode.userError,
|
|
113
|
+
message: "No iOS request provided"
|
|
114
|
+
)
|
|
115
|
+
self.sendPurchaseError(error)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
do {
|
|
120
|
+
try self.ensureConnection()
|
|
121
|
+
guard let productStore = self.productStore else {
|
|
122
|
+
let error = self.createPurchaseErrorResult(
|
|
123
|
+
code: IapErrorCode.notPrepared,
|
|
124
|
+
message: "Product store not initialized",
|
|
125
|
+
productId: iosRequest.sku
|
|
126
|
+
)
|
|
127
|
+
self.sendPurchaseError(error)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Get product from store or fetch if not cached
|
|
132
|
+
var product: Product? = await productStore.getProduct(productID: iosRequest.sku)
|
|
133
|
+
|
|
134
|
+
if product == nil {
|
|
135
|
+
// Try fetching from StoreKit if not in cache
|
|
136
|
+
let products = try await Product.products(for: [iosRequest.sku])
|
|
137
|
+
guard let fetchedProduct = products.first else {
|
|
138
|
+
let error = self.createPurchaseErrorResult(
|
|
139
|
+
code: IapErrorCode.itemUnavailable,
|
|
140
|
+
message: "Invalid product ID: \(iosRequest.sku)",
|
|
141
|
+
productId: iosRequest.sku
|
|
142
|
+
)
|
|
143
|
+
self.sendPurchaseError(error)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
// Store for future use
|
|
147
|
+
await productStore.addProduct(fetchedProduct)
|
|
148
|
+
product = fetchedProduct
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Purchase the product - this will send events internally
|
|
152
|
+
try await self.purchaseProductWithEvents(
|
|
153
|
+
product!,
|
|
154
|
+
sku: iosRequest.sku,
|
|
155
|
+
andDangerouslyFinishTransactionAutomatically: iosRequest.andDangerouslyFinishTransactionAutomatically ?? false,
|
|
156
|
+
appAccountToken: iosRequest.appAccountToken,
|
|
157
|
+
quantity: iosRequest.quantity,
|
|
158
|
+
withOffer: iosRequest.withOffer
|
|
159
|
+
)
|
|
160
|
+
} catch {
|
|
161
|
+
// Map StoreKit errors to proper error codes
|
|
162
|
+
if let nsError = error as NSError? {
|
|
163
|
+
var errorCode = IapErrorCode.purchaseError
|
|
164
|
+
var errorMessage = error.localizedDescription
|
|
165
|
+
|
|
166
|
+
switch nsError.domain {
|
|
167
|
+
case "SKErrorDomain":
|
|
168
|
+
switch nsError.code {
|
|
169
|
+
case 0: // SKError.unknown
|
|
170
|
+
errorCode = IapErrorCode.unknown
|
|
171
|
+
case 1: // SKError.clientInvalid
|
|
172
|
+
errorCode = IapErrorCode.serviceError
|
|
173
|
+
case 2: // SKError.paymentCancelled
|
|
174
|
+
errorCode = IapErrorCode.userCancelled
|
|
175
|
+
errorMessage = "User cancelled the purchase"
|
|
176
|
+
case 3: // SKError.paymentInvalid
|
|
177
|
+
errorCode = IapErrorCode.userError
|
|
178
|
+
case 4: // SKError.paymentNotAllowed
|
|
179
|
+
errorCode = IapErrorCode.userError
|
|
180
|
+
errorMessage = "Payment not allowed"
|
|
181
|
+
case 5: // SKError.storeProductNotAvailable
|
|
182
|
+
errorCode = IapErrorCode.itemUnavailable
|
|
183
|
+
case 6: // SKError.cloudServicePermissionDenied
|
|
184
|
+
errorCode = IapErrorCode.serviceError
|
|
185
|
+
case 7: // SKError.cloudServiceNetworkConnectionFailed
|
|
186
|
+
errorCode = IapErrorCode.networkError
|
|
187
|
+
case 8: // SKError.cloudServiceRevoked
|
|
188
|
+
errorCode = IapErrorCode.serviceError
|
|
189
|
+
default:
|
|
190
|
+
errorCode = IapErrorCode.purchaseError
|
|
191
|
+
}
|
|
192
|
+
case "NSURLErrorDomain":
|
|
193
|
+
errorCode = IapErrorCode.networkError
|
|
194
|
+
errorMessage = "Network error: \(error.localizedDescription)"
|
|
195
|
+
default:
|
|
196
|
+
if error.localizedDescription.lowercased().contains("network") {
|
|
197
|
+
errorCode = IapErrorCode.networkError
|
|
198
|
+
} else if error.localizedDescription.lowercased().contains("cancelled") {
|
|
199
|
+
errorCode = IapErrorCode.userCancelled
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let error = self.createPurchaseErrorResult(
|
|
204
|
+
code: errorCode,
|
|
205
|
+
message: errorMessage,
|
|
206
|
+
productId: iosRequest.sku
|
|
207
|
+
)
|
|
208
|
+
self.sendPurchaseError(error)
|
|
209
|
+
} else {
|
|
210
|
+
let error = self.createPurchaseErrorResult(
|
|
211
|
+
code: IapErrorCode.purchaseError,
|
|
212
|
+
message: error.localizedDescription,
|
|
213
|
+
productId: iosRequest.sku
|
|
214
|
+
)
|
|
215
|
+
self.sendPurchaseError(error)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
func getAvailablePurchases(options: NitroAvailablePurchasesOptions?) throws -> Promise<[NitroPurchase]> {
|
|
222
|
+
return Promise.async {
|
|
223
|
+
try self.ensureConnection()
|
|
224
|
+
|
|
225
|
+
// Support both new IOS suffixed and deprecated parameters
|
|
226
|
+
let _ = options?.ios?.alsoPublishToEventListenerIOS ?? options?.ios?.alsoPublishToEventListener ?? false
|
|
227
|
+
let onlyIncludeActiveItems = options?.ios?.onlyIncludeActiveItemsIOS ?? options?.ios?.onlyIncludeActiveItems ?? false
|
|
228
|
+
|
|
229
|
+
var purchases: [NitroPurchase] = []
|
|
230
|
+
|
|
231
|
+
// Get all transactions
|
|
232
|
+
for await verification in Transaction.currentEntitlements {
|
|
233
|
+
switch verification {
|
|
234
|
+
case .verified(let transaction):
|
|
235
|
+
// Get JWS representation from verification result
|
|
236
|
+
let jwsRepresentation = verification.jwsRepresentation
|
|
237
|
+
|
|
238
|
+
if onlyIncludeActiveItems {
|
|
239
|
+
// Only include active subscriptions and non-consumables
|
|
240
|
+
if transaction.productType == .nonConsumable ||
|
|
241
|
+
(transaction.productType == .autoRenewable && transaction.revocationDate == nil) {
|
|
242
|
+
if let products = try? await StoreKit.Product.products(for: [transaction.productID]),
|
|
243
|
+
let product = products.first {
|
|
244
|
+
purchases.append(self.convertToNitroPurchase(transaction, product: product, jwsRepresentation: jwsRepresentation))
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
// Include all transactions
|
|
249
|
+
if let products = try? await StoreKit.Product.products(for: [transaction.productID]),
|
|
250
|
+
let product = products.first {
|
|
251
|
+
purchases.append(self.convertToNitroPurchase(transaction, product: product, jwsRepresentation: jwsRepresentation))
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
case .unverified:
|
|
255
|
+
continue
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return purchases
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
func finishTransaction(params: NitroFinishTransactionParams) throws -> Promise<Variant_Bool_NitroPurchaseResult> {
|
|
264
|
+
return Promise.async {
|
|
265
|
+
// iOS implementation
|
|
266
|
+
guard let iosParams = params.ios else {
|
|
267
|
+
// No iOS params, return success
|
|
268
|
+
return .first(true)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
try self.ensureConnection()
|
|
272
|
+
|
|
273
|
+
// Find the transaction
|
|
274
|
+
for await verification in Transaction.all {
|
|
275
|
+
switch verification {
|
|
276
|
+
case .verified(let transaction):
|
|
277
|
+
if String(transaction.id) == iosParams.transactionId {
|
|
278
|
+
await transaction.finish()
|
|
279
|
+
return .first(true)
|
|
280
|
+
}
|
|
281
|
+
case .unverified:
|
|
282
|
+
continue
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
287
|
+
code: IapErrorCode.itemUnavailable,
|
|
288
|
+
message: "Transaction not found: \(iosParams.transactionId)"
|
|
289
|
+
)
|
|
290
|
+
throw NSError(domain: "RnIap", code: 0, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
func validateReceipt(params: NitroReceiptValidationParams) throws -> Promise<Variant_NitroReceiptValidationResultIOS_NitroReceiptValidationResultAndroid> {
|
|
295
|
+
return Promise.async {
|
|
296
|
+
do {
|
|
297
|
+
// Get the app receipt data
|
|
298
|
+
guard let receiptURL = Bundle.main.appStoreReceiptURL,
|
|
299
|
+
let receiptData = try? Data(contentsOf: receiptURL) else {
|
|
300
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
301
|
+
code: IapErrorCode.receiptFailed,
|
|
302
|
+
message: "App receipt not found or could not be read"
|
|
303
|
+
)
|
|
304
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let receiptDataBase64 = receiptData.base64EncodedString()
|
|
308
|
+
|
|
309
|
+
// For StoreKit 2, we can use Transaction.currentEntitlements or Transaction.all
|
|
310
|
+
// to get the latest transaction for the specified SKU
|
|
311
|
+
var latestTransaction: NitroPurchase? = nil
|
|
312
|
+
|
|
313
|
+
// Find the latest transaction for the specified SKU
|
|
314
|
+
for await verificationResult in Transaction.currentEntitlements {
|
|
315
|
+
switch verificationResult {
|
|
316
|
+
case .verified(let transaction):
|
|
317
|
+
if transaction.productID == params.sku {
|
|
318
|
+
// Fetch the product details for this transaction
|
|
319
|
+
if let products = try? await StoreKit.Product.products(for: [transaction.productID]),
|
|
320
|
+
let product = products.first {
|
|
321
|
+
latestTransaction = self.convertToNitroPurchase(transaction, product: product, jwsRepresentation: nil)
|
|
322
|
+
}
|
|
323
|
+
break
|
|
324
|
+
}
|
|
325
|
+
case .unverified(_, let verificationError):
|
|
326
|
+
// Handle unverified transactions if needed
|
|
327
|
+
print("Unverified transaction for SKU \(params.sku): \(verificationError)")
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// For StoreKit 2, the receipt is always considered valid if we can read it
|
|
332
|
+
// and the transaction verification passed
|
|
333
|
+
let isValid = latestTransaction != nil
|
|
334
|
+
|
|
335
|
+
// Generate JWS representation (simplified for now)
|
|
336
|
+
let jwsRepresentation = receiptDataBase64 // In a real implementation, this would be the actual JWS
|
|
337
|
+
|
|
338
|
+
let result = NitroReceiptValidationResultIOS(
|
|
339
|
+
isValid: isValid,
|
|
340
|
+
receiptData: receiptDataBase64,
|
|
341
|
+
jwsRepresentation: jwsRepresentation,
|
|
342
|
+
latestTransaction: latestTransaction
|
|
343
|
+
)
|
|
344
|
+
return Variant_NitroReceiptValidationResultIOS_NitroReceiptValidationResultAndroid.first(result)
|
|
345
|
+
|
|
346
|
+
} catch {
|
|
347
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
348
|
+
code: IapErrorCode.receiptFailed,
|
|
349
|
+
message: "Receipt validation failed: \(error.localizedDescription)"
|
|
350
|
+
)
|
|
351
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// MARK: - iOS-specific Public Methods
|
|
357
|
+
|
|
358
|
+
func getStorefrontIOS() throws -> Promise<String> {
|
|
359
|
+
return Promise.async {
|
|
360
|
+
// Get the current storefront from StoreKit 2
|
|
361
|
+
if let storefront = await Storefront.current {
|
|
362
|
+
// Return the country code (e.g., "USA", "GBR", "KOR")
|
|
363
|
+
return storefront.countryCode
|
|
364
|
+
} else {
|
|
365
|
+
// If no storefront is available, throw an error
|
|
366
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
367
|
+
code: IapErrorCode.unknown,
|
|
368
|
+
message: "Unable to retrieve storefront information"
|
|
369
|
+
)
|
|
370
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
func getAppTransactionIOS() throws -> Promise<String?> {
|
|
376
|
+
return Promise.async {
|
|
377
|
+
if #available(iOS 16.0, *) {
|
|
378
|
+
#if compiler(>=5.7)
|
|
379
|
+
let verificationResult = try await AppTransaction.shared
|
|
380
|
+
|
|
381
|
+
let appTransaction: AppTransaction
|
|
382
|
+
switch verificationResult {
|
|
383
|
+
case .verified(let verified):
|
|
384
|
+
appTransaction = verified
|
|
385
|
+
case .unverified(_, _):
|
|
386
|
+
return nil
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
var result: [String: Any?] = [
|
|
390
|
+
"bundleId": appTransaction.bundleID,
|
|
391
|
+
"appVersion": appTransaction.appVersion,
|
|
392
|
+
"originalAppVersion": appTransaction.originalAppVersion,
|
|
393
|
+
"originalPurchaseDate": appTransaction.originalPurchaseDate.timeIntervalSince1970 * 1000,
|
|
394
|
+
"deviceVerification": appTransaction.deviceVerification.base64EncodedString(),
|
|
395
|
+
"deviceVerificationNonce": appTransaction.deviceVerificationNonce.uuidString,
|
|
396
|
+
"environment": appTransaction.environment.rawValue,
|
|
397
|
+
"signedDate": appTransaction.signedDate.timeIntervalSince1970 * 1000,
|
|
398
|
+
"appId": appTransaction.appID,
|
|
399
|
+
"appVersionId": appTransaction.appVersionID,
|
|
400
|
+
"preorderDate": appTransaction.preorderDate.map { $0.timeIntervalSince1970 * 1000 }
|
|
401
|
+
]
|
|
402
|
+
|
|
403
|
+
// iOS 18.4+ properties - only compile with Xcode 16.4+ (Swift 6.1+)
|
|
404
|
+
// This prevents build failures on Xcode 16.3 and below
|
|
405
|
+
#if swift(>=6.1)
|
|
406
|
+
if #available(iOS 18.4, *) {
|
|
407
|
+
result["appTransactionId"] = appTransaction.appTransactionID
|
|
408
|
+
result["originalPlatform"] = appTransaction.originalPlatform.rawValue
|
|
409
|
+
}
|
|
410
|
+
#endif
|
|
411
|
+
|
|
412
|
+
// Convert dictionary to JSON string
|
|
413
|
+
let jsonData = try JSONSerialization.data(withJSONObject: result, options: [])
|
|
414
|
+
return String(data: jsonData, encoding: .utf8)
|
|
415
|
+
#else
|
|
416
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
417
|
+
code: IapErrorCode.unknown,
|
|
418
|
+
message: "getAppTransaction requires Xcode 15.0+ with iOS 16.0 SDK for compilation"
|
|
419
|
+
)
|
|
420
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
421
|
+
#endif
|
|
422
|
+
} else {
|
|
423
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
424
|
+
code: IapErrorCode.unknown,
|
|
425
|
+
message: "getAppTransaction requires iOS 16.0 or later"
|
|
426
|
+
)
|
|
427
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
func requestPromotedProductIOS() throws -> Promise<NitroProduct?> {
|
|
433
|
+
return Promise.async {
|
|
434
|
+
// Return the stored promoted product if available
|
|
435
|
+
if let product = self.promotedProduct {
|
|
436
|
+
// Convert Product to NitroProduct
|
|
437
|
+
return self.convertProductToNitroProduct(product, type: "inapp")
|
|
438
|
+
} else if let skProduct = self.promotedSKProduct {
|
|
439
|
+
// Convert SKProduct to NitroProduct (backward compatibility)
|
|
440
|
+
// First get the StoreKit 2 Product
|
|
441
|
+
if let products = try? await StoreKit.Product.products(for: [skProduct.productIdentifier]),
|
|
442
|
+
let product = products.first {
|
|
443
|
+
return self.convertProductToNitroProduct(product, type: "inapp")
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return nil
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
func buyPromotedProductIOS() throws -> Promise<Void> {
|
|
451
|
+
return Promise.async {
|
|
452
|
+
// Check if we have a promoted payment to process
|
|
453
|
+
guard let payment = self.promotedPayment else {
|
|
454
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
455
|
+
code: IapErrorCode.itemUnavailable,
|
|
456
|
+
message: "No promoted product available"
|
|
457
|
+
)
|
|
458
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Add the deferred payment to the queue
|
|
462
|
+
SKPaymentQueue.default().add(payment)
|
|
463
|
+
|
|
464
|
+
// Clear the promoted product data
|
|
465
|
+
self.promotedPayment = nil
|
|
466
|
+
self.promotedProduct = nil
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
func presentCodeRedemptionSheetIOS() throws -> Promise<Bool> {
|
|
471
|
+
return Promise.async {
|
|
472
|
+
// Present the App Store's code redemption sheet
|
|
473
|
+
#if !targetEnvironment(simulator)
|
|
474
|
+
await MainActor.run {
|
|
475
|
+
SKPaymentQueue.default().presentCodeRedemptionSheet()
|
|
476
|
+
}
|
|
477
|
+
return true
|
|
478
|
+
#else
|
|
479
|
+
// Not available on simulator
|
|
480
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
481
|
+
code: IapErrorCode.itemUnavailable,
|
|
482
|
+
message: "Code redemption sheet is not available on simulator"
|
|
483
|
+
)
|
|
484
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
485
|
+
#endif
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
func clearTransactionIOS() throws -> Promise<Void> {
|
|
490
|
+
return Promise.async {
|
|
491
|
+
// Clear all unfinished transactions
|
|
492
|
+
for await result in Transaction.unfinished {
|
|
493
|
+
do {
|
|
494
|
+
let transaction = try self.checkVerified(result)
|
|
495
|
+
await transaction.finish()
|
|
496
|
+
self.transactions.removeValue(forKey: String(transaction.id))
|
|
497
|
+
} catch {
|
|
498
|
+
print("Failed to finish transaction: \(error.localizedDescription)")
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Additional iOS-only functions for feature parity with expo-iap
|
|
505
|
+
|
|
506
|
+
func subscriptionStatusIOS(sku: String) throws -> Promise<[NitroSubscriptionStatus]?> {
|
|
507
|
+
return Promise.async {
|
|
508
|
+
try self.ensureConnection()
|
|
509
|
+
|
|
510
|
+
// Get the product from the store
|
|
511
|
+
guard let products = try? await StoreKit.Product.products(for: [sku]),
|
|
512
|
+
let product = products.first,
|
|
513
|
+
let subscription = product.subscription else {
|
|
514
|
+
return []
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Get subscription status
|
|
518
|
+
let statuses = try await subscription.status
|
|
519
|
+
|
|
520
|
+
return statuses.map { status in
|
|
521
|
+
// Get renewal info if available
|
|
522
|
+
var renewalInfo: NitroSubscriptionRenewalInfo? = nil
|
|
523
|
+
switch status.renewalInfo {
|
|
524
|
+
case .verified(let info):
|
|
525
|
+
renewalInfo = NitroSubscriptionRenewalInfo(
|
|
526
|
+
autoRenewStatus: info.willAutoRenew,
|
|
527
|
+
autoRenewPreference: info.autoRenewPreference,
|
|
528
|
+
expirationReason: info.expirationReason.map { Double($0.rawValue) },
|
|
529
|
+
gracePeriodExpirationDate: info.gracePeriodExpirationDate?.timeIntervalSince1970,
|
|
530
|
+
currentProductID: info.currentProductID,
|
|
531
|
+
platform: "ios"
|
|
532
|
+
)
|
|
533
|
+
case .unverified:
|
|
534
|
+
renewalInfo = nil
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return NitroSubscriptionStatus(
|
|
538
|
+
state: Double(status.state.rawValue),
|
|
539
|
+
platform: "ios",
|
|
540
|
+
renewalInfo: renewalInfo
|
|
541
|
+
)
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
func currentEntitlementIOS(sku: String) throws -> Promise<NitroPurchase?> {
|
|
547
|
+
return Promise.async {
|
|
548
|
+
try self.ensureConnection()
|
|
549
|
+
|
|
550
|
+
// Get the product from the store
|
|
551
|
+
guard let products = try? await StoreKit.Product.products(for: [sku]),
|
|
552
|
+
let product = products.first else {
|
|
553
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
554
|
+
code: IapErrorCode.itemUnavailable,
|
|
555
|
+
message: "Can't find product for sku \(sku)"
|
|
556
|
+
)
|
|
557
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Get current entitlement
|
|
561
|
+
guard let entitlement = await product.currentEntitlement else {
|
|
562
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
563
|
+
code: IapErrorCode.itemUnavailable,
|
|
564
|
+
message: "Can't find entitlement for sku \(sku)"
|
|
565
|
+
)
|
|
566
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
switch entitlement {
|
|
570
|
+
case .verified(let transaction):
|
|
571
|
+
return self.convertToNitroPurchase(transaction, product: product, jwsRepresentation: entitlement.jwsRepresentation)
|
|
572
|
+
case .unverified:
|
|
573
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
574
|
+
code: IapErrorCode.transactionValidationFailed,
|
|
575
|
+
message: "Failed to verify transaction for sku \(sku)"
|
|
576
|
+
)
|
|
577
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
func latestTransactionIOS(sku: String) throws -> Promise<NitroPurchase?> {
|
|
583
|
+
return Promise.async {
|
|
584
|
+
try self.ensureConnection()
|
|
585
|
+
|
|
586
|
+
// Get the product from the store
|
|
587
|
+
guard let products = try? await StoreKit.Product.products(for: [sku]),
|
|
588
|
+
let product = products.first else {
|
|
589
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
590
|
+
code: IapErrorCode.itemUnavailable,
|
|
591
|
+
message: "Can't find product for sku \(sku)"
|
|
592
|
+
)
|
|
593
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Get latest transaction
|
|
597
|
+
guard let latestTransaction = await product.latestTransaction else {
|
|
598
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
599
|
+
code: IapErrorCode.itemUnavailable,
|
|
600
|
+
message: "Can't find latest transaction for sku \(sku)"
|
|
601
|
+
)
|
|
602
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
switch latestTransaction {
|
|
606
|
+
case .verified(let transaction):
|
|
607
|
+
return self.convertToNitroPurchase(transaction, product: product, jwsRepresentation: latestTransaction.jwsRepresentation)
|
|
608
|
+
case .unverified:
|
|
609
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
610
|
+
code: IapErrorCode.transactionValidationFailed,
|
|
611
|
+
message: "Failed to verify transaction for sku \(sku)"
|
|
612
|
+
)
|
|
613
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
func getPendingTransactionsIOS() throws -> Promise<[NitroPurchase]> {
|
|
619
|
+
return Promise.async {
|
|
620
|
+
// Return the transactions we're currently tracking
|
|
621
|
+
var pendingPurchases: [NitroPurchase] = []
|
|
622
|
+
|
|
623
|
+
for (_, transaction) in self.transactions {
|
|
624
|
+
// Get product info for each transaction
|
|
625
|
+
if let products = try? await StoreKit.Product.products(for: [transaction.productID]),
|
|
626
|
+
let product = products.first {
|
|
627
|
+
pendingPurchases.append(self.convertToNitroPurchase(transaction, product: product))
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return pendingPurchases
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
func syncIOS() throws -> Promise<Bool> {
|
|
636
|
+
return Promise.async {
|
|
637
|
+
do {
|
|
638
|
+
// Sync with the App Store
|
|
639
|
+
try await AppStore.sync()
|
|
640
|
+
return true
|
|
641
|
+
} catch {
|
|
642
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
643
|
+
code: IapErrorCode.syncError,
|
|
644
|
+
message: "Error synchronizing with the AppStore: \(error.localizedDescription)"
|
|
645
|
+
)
|
|
646
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
func showManageSubscriptionsIOS() throws -> Promise<Bool> {
|
|
652
|
+
return Promise.async {
|
|
653
|
+
#if !os(tvOS)
|
|
654
|
+
// Get the active window scene
|
|
655
|
+
guard let windowScene = await MainActor.run(body: {
|
|
656
|
+
UIApplication.shared.connectedScenes
|
|
657
|
+
.compactMap({ $0 as? UIWindowScene })
|
|
658
|
+
.first(where: { $0.activationState == .foregroundActive })
|
|
659
|
+
}) else {
|
|
660
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
661
|
+
code: IapErrorCode.serviceError,
|
|
662
|
+
message: "Cannot find active window scene"
|
|
663
|
+
)
|
|
664
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Show the subscription management UI
|
|
668
|
+
try await AppStore.showManageSubscriptions(in: windowScene)
|
|
669
|
+
return true
|
|
670
|
+
#else
|
|
671
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
672
|
+
code: IapErrorCode.itemUnavailable,
|
|
673
|
+
message: "This method is not available on tvOS"
|
|
674
|
+
)
|
|
675
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
676
|
+
#endif
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
func isEligibleForIntroOfferIOS(groupID: String) throws -> Promise<Bool> {
|
|
681
|
+
return Promise.async {
|
|
682
|
+
return await Product.SubscriptionInfo.isEligibleForIntroOffer(for: groupID)
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
func getReceiptDataIOS() throws -> Promise<String> {
|
|
687
|
+
return Promise.async {
|
|
688
|
+
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
|
|
689
|
+
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
|
|
690
|
+
do {
|
|
691
|
+
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
|
|
692
|
+
return receiptData.base64EncodedString(options: [])
|
|
693
|
+
} catch {
|
|
694
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
695
|
+
code: IapErrorCode.receiptFailed,
|
|
696
|
+
message: "Error reading receipt data: \(error.localizedDescription)"
|
|
697
|
+
)
|
|
698
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
702
|
+
code: IapErrorCode.receiptFailed,
|
|
703
|
+
message: "App Store receipt not found"
|
|
704
|
+
)
|
|
705
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
func isTransactionVerifiedIOS(sku: String) throws -> Promise<Bool> {
|
|
711
|
+
return Promise.async {
|
|
712
|
+
try self.ensureConnection()
|
|
713
|
+
|
|
714
|
+
// Get the product from the store
|
|
715
|
+
guard let products = try? await StoreKit.Product.products(for: [sku]),
|
|
716
|
+
let product = products.first,
|
|
717
|
+
let result = await product.latestTransaction else {
|
|
718
|
+
return false
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Check if transaction is verified
|
|
722
|
+
switch result {
|
|
723
|
+
case .verified:
|
|
724
|
+
return true
|
|
725
|
+
case .unverified:
|
|
726
|
+
return false
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
func getTransactionJwsIOS(sku: String) throws -> Promise<String?> {
|
|
732
|
+
return Promise.async {
|
|
733
|
+
try self.ensureConnection()
|
|
734
|
+
|
|
735
|
+
// Get the product from the store
|
|
736
|
+
guard let products = try? await StoreKit.Product.products(for: [sku]),
|
|
737
|
+
let product = products.first,
|
|
738
|
+
let result = await product.latestTransaction else {
|
|
739
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
740
|
+
code: IapErrorCode.itemUnavailable,
|
|
741
|
+
message: "Can't find transaction for sku \(sku)"
|
|
742
|
+
)
|
|
743
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
return result.jwsRepresentation
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
func beginRefundRequestIOS(sku: String) throws -> Promise<String?> {
|
|
751
|
+
return Promise.async {
|
|
752
|
+
#if !os(tvOS)
|
|
753
|
+
if #available(macOS 12.0, *) {
|
|
754
|
+
// Find the latest transaction for the SKU
|
|
755
|
+
var latestTransaction: Transaction? = nil
|
|
756
|
+
|
|
757
|
+
for await result in Transaction.currentEntitlements {
|
|
758
|
+
switch result {
|
|
759
|
+
case .verified(let transaction):
|
|
760
|
+
if transaction.productID == sku {
|
|
761
|
+
latestTransaction = transaction
|
|
762
|
+
break
|
|
763
|
+
}
|
|
764
|
+
case .unverified(_, _):
|
|
765
|
+
continue
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
guard let transaction = latestTransaction else {
|
|
770
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
771
|
+
code: IapErrorCode.itemUnavailable,
|
|
772
|
+
message: "Can't find transaction for SKU \(sku)"
|
|
773
|
+
)
|
|
774
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Begin refund request
|
|
778
|
+
do {
|
|
779
|
+
// Get the active window scene
|
|
780
|
+
guard let windowScene = await MainActor.run(body: {
|
|
781
|
+
UIApplication.shared.connectedScenes
|
|
782
|
+
.compactMap({ $0 as? UIWindowScene })
|
|
783
|
+
.first(where: { $0.activationState == .foregroundActive })
|
|
784
|
+
}) else {
|
|
785
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
786
|
+
code: IapErrorCode.serviceError,
|
|
787
|
+
message: "Cannot find active window scene"
|
|
788
|
+
)
|
|
789
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
let refundStatus = try await transaction.beginRefundRequest(in: windowScene)
|
|
793
|
+
|
|
794
|
+
// Convert refund status to string
|
|
795
|
+
switch refundStatus {
|
|
796
|
+
case .success:
|
|
797
|
+
return "success"
|
|
798
|
+
case .userCancelled:
|
|
799
|
+
return "userCancelled"
|
|
800
|
+
@unknown default:
|
|
801
|
+
return "unknown"
|
|
802
|
+
}
|
|
803
|
+
} catch {
|
|
804
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
805
|
+
code: IapErrorCode.serviceError,
|
|
806
|
+
message: "Failed to begin refund request: \(error.localizedDescription)"
|
|
807
|
+
)
|
|
808
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
809
|
+
}
|
|
810
|
+
} else {
|
|
811
|
+
// Refund request is only available on iOS 15+
|
|
812
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
813
|
+
code: IapErrorCode.itemUnavailable,
|
|
814
|
+
message: "Refund request requires iOS 15.0 or later"
|
|
815
|
+
)
|
|
816
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
817
|
+
}
|
|
818
|
+
#else
|
|
819
|
+
// Not available on tvOS
|
|
820
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
821
|
+
code: IapErrorCode.itemUnavailable,
|
|
822
|
+
message: "Refund request is not available on tvOS"
|
|
823
|
+
)
|
|
824
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
825
|
+
#endif
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
func addPromotedProductListenerIOS(listener: @escaping (NitroProduct) -> Void) throws {
|
|
830
|
+
promotedProductListeners.append(listener)
|
|
831
|
+
|
|
832
|
+
// If we already have a promoted product, notify the new listener immediately
|
|
833
|
+
if let product = promotedProduct {
|
|
834
|
+
let nitroProduct = convertProductToNitroProduct(product, type: "inapp")
|
|
835
|
+
listener(nitroProduct)
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
func removePromotedProductListenerIOS(listener: @escaping (NitroProduct) -> Void) throws {
|
|
840
|
+
// Note: In Swift, comparing closures is not straightforward, so we'll clear all listeners
|
|
841
|
+
// In a real implementation, you might want to use a unique identifier for each listener
|
|
842
|
+
promotedProductListeners.removeAll()
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// MARK: - Event Listener Methods
|
|
846
|
+
|
|
847
|
+
func addPurchaseUpdatedListener(listener: @escaping (NitroPurchase) -> Void) throws {
|
|
848
|
+
purchaseUpdatedListeners.append(listener)
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
func addPurchaseErrorListener(listener: @escaping (NitroPurchaseResult) -> Void) throws {
|
|
852
|
+
purchaseErrorListeners.append(listener)
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
func removePurchaseUpdatedListener(listener: @escaping (NitroPurchase) -> Void) throws {
|
|
856
|
+
// Note: This is a limitation of Swift closures - we can't easily remove by reference
|
|
857
|
+
// For now, we'll just clear all listeners when requested
|
|
858
|
+
purchaseUpdatedListeners.removeAll()
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
func removePurchaseErrorListener(listener: @escaping (NitroPurchaseResult) -> Void) throws {
|
|
862
|
+
// Note: This is a limitation of Swift closures - we can't easily remove by reference
|
|
863
|
+
// For now, we'll just clear all listeners when requested
|
|
864
|
+
purchaseErrorListeners.removeAll()
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// MARK: - Private Helper Methods
|
|
868
|
+
|
|
869
|
+
private func purchaseProduct(
|
|
870
|
+
_ product: Product,
|
|
871
|
+
sku: String,
|
|
872
|
+
andDangerouslyFinishTransactionAutomatically: Bool,
|
|
873
|
+
appAccountToken: String?,
|
|
874
|
+
quantity: Double?,
|
|
875
|
+
withOffer: [String: String]?
|
|
876
|
+
) async throws -> NitroPurchase {
|
|
877
|
+
// Prepare purchase options
|
|
878
|
+
var options: Set<Product.PurchaseOption> = []
|
|
879
|
+
|
|
880
|
+
// Add quantity if specified
|
|
881
|
+
if let quantity = quantity, quantity > 0 {
|
|
882
|
+
options.insert(.quantity(Int(quantity)))
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Add promotional offer if provided
|
|
886
|
+
if let offerID = withOffer?["identifier"],
|
|
887
|
+
let keyID = withOffer?["keyIdentifier"],
|
|
888
|
+
let nonce = withOffer?["nonce"],
|
|
889
|
+
let signature = withOffer?["signature"],
|
|
890
|
+
let timestamp = withOffer?["timestamp"],
|
|
891
|
+
let uuidNonce = UUID(uuidString: nonce),
|
|
892
|
+
let signatureData = Data(base64Encoded: signature),
|
|
893
|
+
let timestampInt = Int(timestamp) {
|
|
894
|
+
options.insert(
|
|
895
|
+
.promotionalOffer(
|
|
896
|
+
offerID: offerID,
|
|
897
|
+
keyID: keyID,
|
|
898
|
+
nonce: uuidNonce,
|
|
899
|
+
signature: signatureData,
|
|
900
|
+
timestamp: timestampInt
|
|
901
|
+
)
|
|
902
|
+
)
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Add app account token if provided
|
|
906
|
+
if let appAccountToken = appAccountToken,
|
|
907
|
+
let appAccountUUID = UUID(uuidString: appAccountToken) {
|
|
908
|
+
options.insert(.appAccountToken(appAccountUUID))
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Get window scene for iOS 17+ purchase confirmation
|
|
912
|
+
let windowScene = await currentWindowScene()
|
|
913
|
+
|
|
914
|
+
// Perform the purchase
|
|
915
|
+
let result: Product.PurchaseResult
|
|
916
|
+
#if swift(>=5.9)
|
|
917
|
+
if #available(iOS 17.0, tvOS 17.0, *) {
|
|
918
|
+
if let windowScene = windowScene {
|
|
919
|
+
result = try await product.purchase(confirmIn: windowScene, options: options)
|
|
920
|
+
} else {
|
|
921
|
+
result = try await product.purchase(options: options)
|
|
922
|
+
}
|
|
923
|
+
} else {
|
|
924
|
+
#if !os(visionOS)
|
|
925
|
+
result = try await product.purchase(options: options)
|
|
926
|
+
#endif
|
|
927
|
+
}
|
|
928
|
+
#elseif !os(visionOS)
|
|
929
|
+
result = try await product.purchase(options: options)
|
|
930
|
+
#endif
|
|
931
|
+
|
|
932
|
+
switch result {
|
|
933
|
+
case .success(let verification):
|
|
934
|
+
let transaction = try checkVerified(verification)
|
|
935
|
+
|
|
936
|
+
// Store transaction if not auto-finishing
|
|
937
|
+
if !andDangerouslyFinishTransactionAutomatically {
|
|
938
|
+
self.transactions[String(transaction.id)] = transaction
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Get JWS representation
|
|
942
|
+
let jwsRepresentation = verification.jwsRepresentation
|
|
943
|
+
|
|
944
|
+
// Create purchase object
|
|
945
|
+
let purchase = self.convertToNitroPurchase(
|
|
946
|
+
transaction,
|
|
947
|
+
product: product,
|
|
948
|
+
jwsRepresentation: jwsRepresentation
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
// Finish transaction if requested
|
|
952
|
+
if andDangerouslyFinishTransactionAutomatically {
|
|
953
|
+
await transaction.finish()
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
return purchase
|
|
957
|
+
|
|
958
|
+
case .userCancelled:
|
|
959
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
960
|
+
code: IapErrorCode.userCancelled,
|
|
961
|
+
message: "User cancelled the purchase",
|
|
962
|
+
productId: sku
|
|
963
|
+
)
|
|
964
|
+
throw NSError(domain: "RnIap", code: 1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
965
|
+
|
|
966
|
+
case .pending:
|
|
967
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
968
|
+
code: IapErrorCode.deferredPayment,
|
|
969
|
+
message: "The payment was deferred",
|
|
970
|
+
productId: sku
|
|
971
|
+
)
|
|
972
|
+
throw NSError(domain: "RnIap", code: 2, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
973
|
+
|
|
974
|
+
@unknown default:
|
|
975
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
976
|
+
code: IapErrorCode.unknown,
|
|
977
|
+
message: "Unknown purchase result",
|
|
978
|
+
productId: sku
|
|
979
|
+
)
|
|
980
|
+
throw NSError(domain: "RnIap", code: 0, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
private func purchaseProductWithEvents(
|
|
985
|
+
_ product: Product,
|
|
986
|
+
sku: String,
|
|
987
|
+
andDangerouslyFinishTransactionAutomatically: Bool,
|
|
988
|
+
appAccountToken: String?,
|
|
989
|
+
quantity: Double?,
|
|
990
|
+
withOffer: [String: String]?
|
|
991
|
+
) async throws {
|
|
992
|
+
// Prepare purchase options
|
|
993
|
+
var options: Set<Product.PurchaseOption> = []
|
|
994
|
+
|
|
995
|
+
// Add quantity if specified
|
|
996
|
+
if let quantity = quantity, quantity > 0 {
|
|
997
|
+
options.insert(.quantity(Int(quantity)))
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Add promotional offer if provided
|
|
1001
|
+
if let offerID = withOffer?["identifier"],
|
|
1002
|
+
let keyID = withOffer?["keyIdentifier"],
|
|
1003
|
+
let nonce = withOffer?["nonce"],
|
|
1004
|
+
let signature = withOffer?["signature"],
|
|
1005
|
+
let timestamp = withOffer?["timestamp"],
|
|
1006
|
+
let uuidNonce = UUID(uuidString: nonce),
|
|
1007
|
+
let signatureData = Data(base64Encoded: signature),
|
|
1008
|
+
let timestampInt = Int(timestamp) {
|
|
1009
|
+
options.insert(
|
|
1010
|
+
.promotionalOffer(
|
|
1011
|
+
offerID: offerID,
|
|
1012
|
+
keyID: keyID,
|
|
1013
|
+
nonce: uuidNonce,
|
|
1014
|
+
signature: signatureData,
|
|
1015
|
+
timestamp: timestampInt
|
|
1016
|
+
)
|
|
1017
|
+
)
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Add app account token if provided
|
|
1021
|
+
if let appAccountToken = appAccountToken,
|
|
1022
|
+
let appAccountUUID = UUID(uuidString: appAccountToken) {
|
|
1023
|
+
options.insert(.appAccountToken(appAccountUUID))
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Get window scene for iOS 17+ purchase confirmation
|
|
1027
|
+
let windowScene = await currentWindowScene()
|
|
1028
|
+
|
|
1029
|
+
// Perform the purchase
|
|
1030
|
+
let result: Product.PurchaseResult
|
|
1031
|
+
#if swift(>=5.9)
|
|
1032
|
+
if #available(iOS 17.0, tvOS 17.0, *) {
|
|
1033
|
+
if let windowScene = windowScene {
|
|
1034
|
+
result = try await product.purchase(confirmIn: windowScene, options: options)
|
|
1035
|
+
} else {
|
|
1036
|
+
result = try await product.purchase(options: options)
|
|
1037
|
+
}
|
|
1038
|
+
} else {
|
|
1039
|
+
#if !os(visionOS)
|
|
1040
|
+
result = try await product.purchase(options: options)
|
|
1041
|
+
#endif
|
|
1042
|
+
}
|
|
1043
|
+
#elseif !os(visionOS)
|
|
1044
|
+
result = try await product.purchase(options: options)
|
|
1045
|
+
#endif
|
|
1046
|
+
|
|
1047
|
+
switch result {
|
|
1048
|
+
case .success(let verification):
|
|
1049
|
+
let transaction = try checkVerified(verification)
|
|
1050
|
+
|
|
1051
|
+
// Store transaction if not auto-finishing
|
|
1052
|
+
if !andDangerouslyFinishTransactionAutomatically {
|
|
1053
|
+
self.transactions[String(transaction.id)] = transaction
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// Get JWS representation
|
|
1057
|
+
let jwsRepresentation = verification.jwsRepresentation
|
|
1058
|
+
|
|
1059
|
+
// Create purchase object
|
|
1060
|
+
let purchase = self.convertToNitroPurchase(
|
|
1061
|
+
transaction,
|
|
1062
|
+
product: product,
|
|
1063
|
+
jwsRepresentation: jwsRepresentation
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
// Finish transaction if requested
|
|
1067
|
+
if andDangerouslyFinishTransactionAutomatically {
|
|
1068
|
+
await transaction.finish()
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// Send purchase update event
|
|
1072
|
+
self.sendPurchaseUpdate(purchase)
|
|
1073
|
+
|
|
1074
|
+
case .userCancelled:
|
|
1075
|
+
let error = self.createPurchaseErrorResult(
|
|
1076
|
+
code: IapErrorCode.userCancelled,
|
|
1077
|
+
message: "User cancelled the purchase",
|
|
1078
|
+
productId: sku
|
|
1079
|
+
)
|
|
1080
|
+
self.sendPurchaseError(error)
|
|
1081
|
+
|
|
1082
|
+
case .pending:
|
|
1083
|
+
let error = self.createPurchaseErrorResult(
|
|
1084
|
+
code: IapErrorCode.deferredPayment,
|
|
1085
|
+
message: "The payment was deferred",
|
|
1086
|
+
productId: sku
|
|
1087
|
+
)
|
|
1088
|
+
self.sendPurchaseError(error)
|
|
1089
|
+
|
|
1090
|
+
@unknown default:
|
|
1091
|
+
let error = self.createPurchaseErrorResult(
|
|
1092
|
+
code: IapErrorCode.unknown,
|
|
1093
|
+
message: "Unknown purchase result",
|
|
1094
|
+
productId: sku
|
|
1095
|
+
)
|
|
1096
|
+
self.sendPurchaseError(error)
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
private func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
|
|
1101
|
+
switch result {
|
|
1102
|
+
case .verified(let safe):
|
|
1103
|
+
return safe
|
|
1104
|
+
case .unverified(_, let error):
|
|
1105
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
1106
|
+
code: IapErrorCode.transactionValidationFailed,
|
|
1107
|
+
message: "Transaction verification failed: \(error)",
|
|
1108
|
+
underlyingError: error
|
|
1109
|
+
)
|
|
1110
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
private func ensureConnection() throws {
|
|
1115
|
+
guard isInitialized else {
|
|
1116
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
1117
|
+
code: IapErrorCode.notPrepared,
|
|
1118
|
+
message: "Connection not initialized. Call initConnection() first."
|
|
1119
|
+
)
|
|
1120
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
guard SKPaymentQueue.canMakePayments() else {
|
|
1124
|
+
let errorJson = ErrorUtils.createErrorJson(
|
|
1125
|
+
code: IapErrorCode.iapNotAvailable,
|
|
1126
|
+
message: "In-app purchases are not available on this device"
|
|
1127
|
+
)
|
|
1128
|
+
throw NSError(domain: "RnIap", code: -1, userInfo: [NSLocalizedDescriptionKey: errorJson])
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
@MainActor
|
|
1133
|
+
private func currentWindowScene() async -> UIWindowScene? {
|
|
1134
|
+
#if os(iOS) || os(tvOS)
|
|
1135
|
+
// Find the active window scene
|
|
1136
|
+
for scene in UIApplication.shared.connectedScenes {
|
|
1137
|
+
if let windowScene = scene as? UIWindowScene,
|
|
1138
|
+
windowScene.activationState == .foregroundActive {
|
|
1139
|
+
return windowScene
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Fallback to first window scene if no active one found
|
|
1144
|
+
for scene in UIApplication.shared.connectedScenes {
|
|
1145
|
+
if let windowScene = scene as? UIWindowScene {
|
|
1146
|
+
return windowScene
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
#endif
|
|
1150
|
+
return nil
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
private func convertToNitroProduct(_ storeProduct: StoreKit.Product, type: String) -> NitroProduct {
|
|
1154
|
+
var product = NitroProduct()
|
|
1155
|
+
|
|
1156
|
+
// Basic fields
|
|
1157
|
+
product.id = storeProduct.id
|
|
1158
|
+
product.title = storeProduct.displayName
|
|
1159
|
+
product.description = storeProduct.description
|
|
1160
|
+
product.type = type
|
|
1161
|
+
product.displayName = storeProduct.displayName
|
|
1162
|
+
product.displayPrice = storeProduct.displayPrice
|
|
1163
|
+
product.platform = "ios"
|
|
1164
|
+
|
|
1165
|
+
// Price and currency - priceFormatStyle.currencyCode is not optional
|
|
1166
|
+
product.currency = storeProduct.priceFormatStyle.currencyCode
|
|
1167
|
+
|
|
1168
|
+
// Convert Decimal price to Double - price is not optional
|
|
1169
|
+
product.price = NSDecimalNumber(decimal: storeProduct.price).doubleValue
|
|
1170
|
+
|
|
1171
|
+
// iOS specific fields
|
|
1172
|
+
product.isFamilyShareable = storeProduct.isFamilyShareable
|
|
1173
|
+
product.jsonRepresentation = storeProduct.jsonRepresentation.base64EncodedString()
|
|
1174
|
+
|
|
1175
|
+
// Subscription information
|
|
1176
|
+
if let subscription = storeProduct.subscription {
|
|
1177
|
+
// Subscription period - value is Int, need to convert to Double
|
|
1178
|
+
product.subscriptionPeriodUnitIOS = getPeriodString(subscription.subscriptionPeriod.unit)
|
|
1179
|
+
product.subscriptionPeriodNumberIOS = Double(subscription.subscriptionPeriod.value)
|
|
1180
|
+
|
|
1181
|
+
// Introductory offer
|
|
1182
|
+
if let introOffer = subscription.introductoryOffer {
|
|
1183
|
+
product.introductoryPriceIOS = introOffer.displayPrice
|
|
1184
|
+
product.introductoryPriceAsAmountIOS = NSDecimalNumber(decimal: introOffer.price).doubleValue
|
|
1185
|
+
product.introductoryPricePaymentModeIOS = String(introOffer.paymentMode.rawValue)
|
|
1186
|
+
product.introductoryPriceNumberOfPeriodsIOS = Double(introOffer.periodCount)
|
|
1187
|
+
product.introductoryPriceSubscriptionPeriodIOS = getPeriodString(introOffer.period.unit)
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
return product
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
private func convertProductToNitroProduct(_ product: Product, type: String) -> NitroProduct {
|
|
1195
|
+
return convertToNitroProduct(product, type: type)
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
private func getPeriodString(_ unit: StoreKit.Product.SubscriptionPeriod.Unit) -> String {
|
|
1199
|
+
switch unit {
|
|
1200
|
+
case .day: return "DAY"
|
|
1201
|
+
case .week: return "WEEK"
|
|
1202
|
+
case .month: return "MONTH"
|
|
1203
|
+
case .year: return "YEAR"
|
|
1204
|
+
@unknown default: return ""
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
private func convertToNitroPurchase(_ transaction: Transaction, product: StoreKit.Product, jwsRepresentation: String? = nil) -> NitroPurchase {
|
|
1209
|
+
var purchase = NitroPurchase()
|
|
1210
|
+
|
|
1211
|
+
// Basic fields
|
|
1212
|
+
purchase.id = String(transaction.id)
|
|
1213
|
+
purchase.productId = transaction.productID
|
|
1214
|
+
purchase.transactionDate = transaction.purchaseDate.timeIntervalSince1970 * 1000 // Convert to milliseconds
|
|
1215
|
+
purchase.platform = "ios"
|
|
1216
|
+
|
|
1217
|
+
// iOS specific fields
|
|
1218
|
+
purchase.quantityIOS = Double(transaction.purchasedQuantity)
|
|
1219
|
+
|
|
1220
|
+
// originalID is not optional in StoreKit 2
|
|
1221
|
+
purchase.originalTransactionIdentifierIOS = String(transaction.originalID)
|
|
1222
|
+
|
|
1223
|
+
// originalPurchaseDate is not optional
|
|
1224
|
+
purchase.originalTransactionDateIOS = transaction.originalPurchaseDate.timeIntervalSince1970 * 1000
|
|
1225
|
+
|
|
1226
|
+
if let appAccountToken = transaction.appAccountToken {
|
|
1227
|
+
purchase.appAccountToken = appAccountToken.uuidString
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Store the JWS representation as purchaseToken for verification
|
|
1231
|
+
// JWS is passed from VerificationResult
|
|
1232
|
+
if let jws = jwsRepresentation {
|
|
1233
|
+
purchase.purchaseToken = jws
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
return purchase
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
private func sendPurchaseUpdate(_ purchase: NitroPurchase) {
|
|
1240
|
+
for listener in purchaseUpdatedListeners {
|
|
1241
|
+
listener(purchase)
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
private func sendPurchaseError(_ error: NitroPurchaseResult) {
|
|
1246
|
+
for listener in purchaseErrorListeners {
|
|
1247
|
+
listener(error)
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
private func createPurchaseErrorResult(code: String, message: String, productId: String? = nil) -> NitroPurchaseResult {
|
|
1252
|
+
var result = NitroPurchaseResult()
|
|
1253
|
+
result.responseCode = 0
|
|
1254
|
+
result.code = code
|
|
1255
|
+
result.message = message
|
|
1256
|
+
result.purchaseToken = productId
|
|
1257
|
+
return result
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
private func cleanupExistingState() {
|
|
1261
|
+
// Cancel transaction listener if any
|
|
1262
|
+
updateListenerTask?.cancel()
|
|
1263
|
+
updateListenerTask = nil
|
|
1264
|
+
isInitialized = false
|
|
1265
|
+
productStore = nil
|
|
1266
|
+
transactions.removeAll()
|
|
1267
|
+
|
|
1268
|
+
// Remove payment observer if exists
|
|
1269
|
+
if let observer = paymentObserver {
|
|
1270
|
+
SKPaymentQueue.default().remove(observer)
|
|
1271
|
+
paymentObserver = nil
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// Clear promoted products
|
|
1275
|
+
promotedProduct = nil
|
|
1276
|
+
promotedPayment = nil
|
|
1277
|
+
promotedSKProduct = nil
|
|
1278
|
+
|
|
1279
|
+
// Clear event listeners
|
|
1280
|
+
purchaseUpdatedListeners.removeAll()
|
|
1281
|
+
purchaseErrorListeners.removeAll()
|
|
1282
|
+
promotedProductListeners.removeAll()
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// Method called by PaymentObserver when a promoted product is received
|
|
1286
|
+
func handlePromotedProduct(payment: SKPayment, product: SKProduct) {
|
|
1287
|
+
self.promotedPayment = payment
|
|
1288
|
+
self.promotedSKProduct = product
|
|
1289
|
+
|
|
1290
|
+
// Convert SKProduct to StoreKit 2 Product and then to NitroProduct
|
|
1291
|
+
Task {
|
|
1292
|
+
if let products = try? await StoreKit.Product.products(for: [product.productIdentifier]),
|
|
1293
|
+
let storeKit2Product = products.first {
|
|
1294
|
+
self.promotedProduct = storeKit2Product
|
|
1295
|
+
|
|
1296
|
+
// Notify all promoted product listeners
|
|
1297
|
+
let nitroProduct = self.convertProductToNitroProduct(storeKit2Product, type: "inapp")
|
|
1298
|
+
for listener in self.promotedProductListeners {
|
|
1299
|
+
listener(nitroProduct)
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// PaymentObserver for handling promoted products
|
|
1307
|
+
@available(iOS 15.0, *)
|
|
1308
|
+
class PaymentObserver: NSObject, SKPaymentTransactionObserver {
|
|
1309
|
+
weak var iapModule: HybridRnIap?
|
|
1310
|
+
|
|
1311
|
+
init(iapModule: HybridRnIap) {
|
|
1312
|
+
self.iapModule = iapModule
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// Required by SKPaymentTransactionObserver protocol but not used
|
|
1316
|
+
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
|
1317
|
+
// We don't handle transactions here as StoreKit 2 handles them in HybridRnIap
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// Handle promoted products from App Store
|
|
1321
|
+
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
|
|
1322
|
+
iapModule?.handlePromotedProduct(payment: payment, product: product)
|
|
1323
|
+
// Return false to defer the payment
|
|
1324
|
+
return false
|
|
1325
|
+
}
|
|
1326
|
+
}
|