diff --git a/ios/IapSerializationUtils.swift b/ios/IapSerializationUtils.swift index 7c76f8f20..fc16ca77d 100644 --- a/ios/IapSerializationUtils.swift +++ b/ios/IapSerializationUtils.swift @@ -245,3 +245,19 @@ func serialize(_ pt: Product.ProductType?) -> String? { return nil } } +@available(iOS 15.0, tvOS 15.0, *) +func serialize(_ s: Storefront?) -> [String: Any]? { + guard let s = s else {return nil} + + var result: [String: Any] = [ + "countryCode": s.countryCode, + ] + + if #available(iOS 17.0, tvOS 17.0, *) { + result["currencyCode"] = s.currency?.identifier + } else { + result["currencyCode"] = nil + } + + return result; +} diff --git a/ios/RNIapIosSk2.swift b/ios/RNIapIosSk2.swift index a1fbd4a9c..bede028d4 100644 --- a/ios/RNIapIosSk2.swift +++ b/ios/RNIapIosSk2.swift @@ -299,7 +299,7 @@ class RNIapIosSk2: RCTEventEmitter, Sk2Delegate { "purchase-updated", "purchase-error" are for backward compatibility */ override func supportedEvents() -> [String]? { - return [ "purchase-updated", "purchase-error", "iap-transaction-updated"] + return [ "purchase-updated", "purchase-error", "iap-transaction-updated", "storefront-updated"] } @objc public func initConnection( @@ -451,6 +451,7 @@ class RNIapIosSk2iOS15: Sk2Delegate { private let _hasListenersQueue = DispatchQueue(label: "com.dooboolab.rniap.hasListenersQueue", attributes: .concurrent) private var transactions: [String: Transaction] private var updateListenerTask: Task? + private var storefrontListenerTask: Task? fileprivate var sendEvent: ((String?, Any?) -> Void)? var hasListeners: Bool { get { @@ -509,11 +510,16 @@ class RNIapIosSk2iOS15: Sk2Delegate { if updateListenerTask == nil { updateListenerTask = listenForTransactions() } + if storefrontListenerTask == nil { + storefrontListenerTask = listenForStorefrontChanges() + } } func removeTransactionObserver() { updateListenerTask?.cancel() updateListenerTask = nil + storefrontListenerTask?.cancel() + storefrontListenerTask = nil } func addTransaction(_ transaction: Transaction) { @@ -521,6 +527,16 @@ class RNIapIosSk2iOS15: Sk2Delegate { self.transactions[transactionId] = transaction } + func listenForStorefrontChanges() -> Task { + return Task.detached { + for await storefront in Storefront.updates { + if self.hasListeners { + self.sendEvent?("storefront-updated", serialize(storefront)) + } + } + } + } + func listenForTransactions() -> Task { return Task.detached { // Iterate through any transactions that don't come from a direct call to `purchase()`. @@ -846,7 +862,7 @@ class RNIapIosSk2iOS15: Sk2Delegate { reject( IapErrors.E_UNKNOWN.rawValue, - "Purchased failed for sku:\(sku): \(error.localizedDescription)", + "Purchased failed for sku:\(sku): \(error.localizedDescription); error: \(error)", error) } } else { @@ -1087,9 +1103,9 @@ class RNIapIosSk2iOS15: Sk2Delegate { } public func getStorefront(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { - Task { + Task { let storefront = await Storefront.current - resolve(storefront?.countryCode) + resolve(serialize(storefront)) } } } diff --git a/plugin/build/withIAP.js b/plugin/build/withIAP.js index 53f1357cd..290c60306 100644 --- a/plugin/build/withIAP.js +++ b/plugin/build/withIAP.js @@ -56,15 +56,15 @@ const withIAPAndroid = (config, { paymentProvider }) => { const withIAP = (config, props) => { const paymentProvider = props?.paymentProvider ?? 'Play Store'; if (!hasPaymentProviderProperValue(paymentProvider)) { - config_plugins_2.WarningAggregator.addWarningAndroid('react-native-iap', `The payment provider '${paymentProvider}' is not supported. Please update your app.json file with one of the following supported values: 'Play Store', 'Amazon AppStore', or 'both'.`); + config_plugins_1.WarningAggregator.addWarningAndroid('react-native-iap', `The payment provider '${paymentProvider}' is not supported. Please update your app.json file with one of the following supported values: 'Play Store', 'Amazon AppStore', or 'both'.`); return config; } try { config = withIAPAndroid(config, { paymentProvider }); } catch (error) { - config_plugins_2.WarningAggregator.addWarningAndroid('react-native-iap', `There was a problem configuring react-native-iap in your native Android project: ${error}`); + config_plugins_1.WarningAggregator.addWarningAndroid('react-native-iap', `There was a problem configuring react-native-iap in your native Android project: ${error}`); } return config; }; -exports.default = (0, config_plugins_1.createRunOncePlugin)(withIAP, pkg.name, pkg.version); +exports.default = (0, config_plugins_2.createRunOncePlugin)(withIAP, pkg.name, pkg.version); diff --git a/src/iap.ts b/src/iap.ts index dd2e0c407..5db210931 100644 --- a/src/iap.ts +++ b/src/iap.ts @@ -975,11 +975,18 @@ export const deepLinkToSubscriptions = ({ * }; * ``` */ -export const getStorefront = (): Promise => { +export const getStorefront = (): Promise<{ + countryCode: string; + currency: string | null; +} | null> => { return ( Platform.select({ android: async () => { - return await RNIapModule.getStorefront(); + const countryCode = await RNIapModule.getStorefront(); + return { + countryCode, + currency: null, + }; }, ios: async () => { return await RNIapIosSk2.getStorefront(); diff --git a/src/modules/iosSk2.ts b/src/modules/iosSk2.ts index 0fd0de76b..23da26b6e 100644 --- a/src/modules/iosSk2.ts +++ b/src/modules/iosSk2.ts @@ -39,7 +39,10 @@ type finishTransaction = (transactionIdentifier: string) => Promise; type getPendingTransactions = () => Promise; type presentCodeRedemptionSheet = () => Promise; type showManageSubscriptions = () => Promise; -type getStorefront = () => Promise; +type getStorefront = () => Promise<{ + countryCode: string; + currency: string | null; +} | null>; export interface IosModulePropsSk2 extends NativeModuleProps { isAvailable(): number;