Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: getStorefront method implementation for fetching App Store and Google Play region #2877

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions android/src/play/java/com/dooboolab/rniap/RNIapModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import android.util.Log
import com.android.billingclient.api.AcknowledgePurchaseParams
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingConfig
import com.android.billingclient.api.BillingConfigResponseListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingFlowParams.SubscriptionUpdateParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ConsumeParams
import com.android.billingclient.api.ConsumeResponseListener
import com.android.billingclient.api.GetBillingConfigParams
import com.android.billingclient.api.GetBillingConfigParams.Builder
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.PurchaseHistoryRecord
Expand Down Expand Up @@ -707,6 +711,24 @@ class RNIapModule(
@ReactMethod
fun getPackageName(promise: Promise) = promise.resolve(reactApplicationContext.packageName)

@ReactMethod
fun getStorefront(promise: Promise) {
ensureConnection(
promise,
) { billingClient ->
billingClient.getBillingConfigAsync(
GetBillingConfigParams.newBuilder().build(),
BillingConfigResponseListener { result: BillingResult, config: BillingConfig? ->
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
promise.safeResolve(config?.getCountryCode())
} else {
promise.safeReject(result?.getResponseCode().toString(), result?.getDebugMessage())
}
},
)
}
}

private fun sendEvent(
reactContext: ReactContext,
eventName: String,
Expand Down
4 changes: 4 additions & 0 deletions ios/RNIapIosSk2.m
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,9 @@ @interface RCT_EXTERN_MODULE (RNIapIosSk2, NSObject)
(NSString*)sku
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(getStorefront:
(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
@end
#endif
26 changes: 26 additions & 0 deletions ios/RNIapIosSk2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ protocol Sk2Delegate {
reject: @escaping RCTPromiseRejectBlock
)

func getStorefront(
_ resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
)

func startObserving()
func stopObserving()
}
Expand Down Expand Up @@ -240,6 +245,13 @@ class DummySk2: Sk2Delegate {
reject(errorCode, errorMessage, nil)
}

func getStorefront(
_ resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) {
reject(errorCode, errorMessage, nil)
}

func startObserving() {
}

Expand Down Expand Up @@ -416,6 +428,13 @@ class RNIapIosSk2: RCTEventEmitter, Sk2Delegate {
) {
delegate.beginRefundRequest(sku, resolve: resolve, reject: reject)
}

@objc public func getStorefront(
_ resolve: @escaping RCTPromiseResolveBlock = { _ in },
reject: @escaping RCTPromiseRejectBlock = { _, _, _ in }
) {
delegate.getStorefront(resolve, reject: reject)
}
}

@available(iOS 15.0, tvOS 15.0, *)
Expand Down Expand Up @@ -1046,4 +1065,11 @@ class RNIapIosSk2iOS15: Sk2Delegate {
reject(IapErrors.E_USER_CANCELLED.rawValue, "This method is not available on tvOS", nil)
#endif
}

public func getStorefront(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
Task {
let storefront = await Storefront.current
resolve(storefront?.countryCode)
}
}
}
37 changes: 35 additions & 2 deletions src/iap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ Note that this is only for backaward compatiblity. It won't publish to transacti
@param {automaticallyFinishRestoredTransactions}:boolean. (IOS Sk1 only) When `true`, all the transactions that are returned are automatically
finished. This means that if you call this method again you won't get the same result on the same device. On the other hand, if `false` you'd
have to manually finish the returned transaction once you have delivered the content to your user.
@param {onlyIncludeActiveItems}:boolean. (IOS Sk2 only). Defaults to false, meaning that it will return one transaction per item purchased.
@param {onlyIncludeActiveItems}:boolean. (IOS Sk2 only). Defaults to false, meaning that it will return one transaction per item purchased.
@See https://developer.apple.com/documentation/storekit/transaction/3851204-currententitlements for details
*/
export const getPurchaseHistory = ({
Expand Down Expand Up @@ -457,7 +457,7 @@ const App = () => {
```
@param {alsoPublishToEventListener}:boolean When `true`, every element will also be pushed to the purchaseUpdated listener.
Note that this is only for backaward compatiblity. It won't publish to transactionUpdated (Storekit2) Defaults to `false`
@param {onlyIncludeActiveItems}:boolean. (IOS Sk2 only). Defaults to true, meaning that it will return the transaction if suscription has not expired.
@param {onlyIncludeActiveItems}:boolean. (IOS Sk2 only). Defaults to true, meaning that it will return the transaction if suscription has not expired.
@See https://developer.apple.com/documentation/storekit/transaction/3851204-currententitlements for details
*
*/
Expand Down Expand Up @@ -933,3 +933,36 @@ export const deepLinkToSubscriptions = ({
}) || (() => Promise.reject(new Error('Unsupported Platform')))
)();
};

/**
* Get App Store and Google Play device region.
*
* App Store: string - ISO 3166-1 Alpha-3 country code representation https://developer.apple.com/documentation/storekit/storefront.
*
* Google Play: string - ISO-3166-1 alpha2 country code representation https://developer.android.com/reference/com/android/billingclient/api/BillingConfig#getCountryCode()
*
* ```tsx
* import React from 'react';
* import {getStorefront} from 'react-native-iap';
*
* const App = () => {
* React.useEffect(() => {
* getStorefront().then((countryCode) => {
* // ... handle region
* });
* }, []);
* };
* ```
*/
export const getStorefront = (): Promise<string> => {
return (
Platform.select({
android: async () => {
return await RNIapModule.getStorefront();
},
ios: async () => {
return await RNIapIosSk2.getStorefront();
},
}) || (() => Promise.reject(new Error('Unsupported Platform')))
)();
};
2 changes: 2 additions & 0 deletions src/modules/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type ConsumeProduct = (

type StartListening = () => Promise<void>;
type GetPackageName = () => Promise<string>;
type GetStorefront = () => Promise<string>;

export interface AndroidModuleProps extends NativeModuleProps {
flushFailedPurchasesCachedAsPending: FlushFailedPurchasesCachedAsPending;
Expand All @@ -70,6 +71,7 @@ export interface AndroidModuleProps extends NativeModuleProps {
/** @deprecated to be renamed to sendUnconsumedPurchases if not removed completely */
startListening: StartListening;
getPackageName: GetPackageName;
getStorefront: GetStorefront;
isFeatureSupported: (feature: Android.FeatureType) => Promise<boolean>;
}

Expand Down
2 changes: 2 additions & 0 deletions src/modules/iosSk2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type finishTransaction = (transactionIdentifier: string) => Promise<boolean>;
type getPendingTransactions = () => Promise<ProductPurchase[]>;
type presentCodeRedemptionSheet = () => Promise<null>;
type showManageSubscriptions = () => Promise<null>;
type getStorefront = () => Promise<string>;

export interface IosModulePropsSk2 extends NativeModuleProps {
isAvailable(): number;
Expand All @@ -58,6 +59,7 @@ export interface IosModulePropsSk2 extends NativeModuleProps {
showManageSubscriptions: showManageSubscriptions;
disable: () => Promise<null>;
beginRefundRequest: (sku: string) => Promise<RefundRequestStatus>;
getStorefront: getStorefront;
}

/**
Expand Down
Loading