From b7baaab7ad17910af7e822c391e1b9baa657ef3a Mon Sep 17 00:00:00 2001 From: hyochan Date: Sun, 22 Sep 2024 00:14:25 +0900 Subject: [PATCH 1/2] feat: useIap impl --- src/index.ts | 2 + src/modules/ios.ts | 2 +- src/useIap.ts | 184 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/useIap.ts diff --git a/src/index.ts b/src/index.ts index 3fca17c..0ff8725 100644 --- a/src/index.ts +++ b/src/index.ts @@ -398,3 +398,5 @@ export const finishTransaction = ({ }) || (() => Promise.reject(new Error('Unsupported Platform'))) )(); }; + +export * from './useIap'; diff --git a/src/modules/ios.ts b/src/modules/ios.ts index 7b1a3c8..84a5e05 100644 --- a/src/modules/ios.ts +++ b/src/modules/ios.ts @@ -9,7 +9,7 @@ import type { } from '../types/ExpoIapIos.types'; import ExpoIapModule from '../ExpoIapModule'; -type TransactionEvent = { +export type TransactionEvent = { transaction?: TransactionSk2; error?: PurchaseError; }; diff --git a/src/useIap.ts b/src/useIap.ts new file mode 100644 index 0000000..a7aecdf --- /dev/null +++ b/src/useIap.ts @@ -0,0 +1,184 @@ +import RNIap, { + endConnection, + initConnection, + purchaseErrorListener, + purchaseUpdatedListener, + transactionUpdatedIos, +} from '../'; + +import {useCallback, useEffect, useState} from 'react'; +import { + Product, + ProductPurchase, + PurchaseError, + PurchaseResult, + SubscriptionProduct, + SubscriptionPurchase, +} from './ExpoIap.types'; +import {Purchase} from '../build/ExpoIap.types'; +import {TransactionEvent} from './modules/ios'; +import {TransactionSk2} from './types/ExpoIapIos.types'; +import {Subscription} from 'expo-modules-core'; + +type IAP_STATUS = { + connected: boolean; + products: Product[]; + promotedProductsIOS: TransactionSk2[]; + subscriptions: SubscriptionProduct[]; + purchaseHistories: ProductPurchase[]; + availablePurchases: ProductPurchase[]; + currentPurchase?: ProductPurchase; + currentPurchaseError?: PurchaseError; + finishTransaction: ({ + purchase, + isConsumable, + developerPayloadAndroid, + }: { + purchase: Purchase; + isConsumable?: boolean; + developerPayloadAndroid?: string; + }) => Promise; + getAvailablePurchases: () => Promise; + getPurchaseHistories: () => Promise; + getProducts: (skus: string[]) => Promise; + getSubscriptions: (skus: string[]) => Promise; +}; + +let purchaseUpdateSubscription: Subscription; +let purchaseErrorSubscription: Subscription; +let promotedProductsSubscription: Subscription; + +export function useIAP(): IAP_STATUS { + const [connected, setConnected] = useState(false); + const [products, setProducts] = useState([]); + const [promotedProductsIOS, setPromotedProductsIOS] = useState< + TransactionSk2[] + >([]); + const [subscriptions, setSubscriptions] = useState([]); + const [purchaseHistories, setPurchaseHistories] = useState( + [], + ); + const [availablePurchases, setAvailablePurchases] = useState< + ProductPurchase[] + >([]); + const [currentPurchase, setCurrentPurchase] = useState(); + + const [currentPurchaseError, setCurrentPurchaseError] = + useState(); + + const getProducts = useCallback(async (skus: string[]): Promise => { + setProducts(await RNIap.getProducts(skus)); + }, []); + + const getSubscriptions = useCallback( + async (skus: string[]): Promise => { + setSubscriptions(await RNIap.getSubscriptions(skus)); + }, + [], + ); + + const getAvailablePurchases = useCallback(async (): Promise => { + setAvailablePurchases(await RNIap.getAvailablePurchases()); + }, []); + + const getPurchaseHistories = useCallback(async (): Promise => { + setPurchaseHistories(await RNIap.getPurchaseHistory()); + }, []); + + const finishTransaction = useCallback( + async ({ + purchase, + isConsumable, + developerPayloadAndroid, + }: { + purchase: ProductPurchase; + isConsumable?: boolean; + developerPayloadAndroid?: string; + }): Promise => { + try { + return await finishTransaction({ + purchase, + isConsumable, + developerPayloadAndroid, + }); + } catch (err) { + throw err; + } finally { + if (purchase.productId === currentPurchase?.productId) { + setCurrentPurchase(undefined); + } + + if (purchase.productId === currentPurchaseError?.productId) { + setCurrentPurchaseError(undefined); + } + } + }, + [ + currentPurchase?.productId, + currentPurchaseError?.productId, + setCurrentPurchase, + setCurrentPurchaseError, + ], + ); + + const initIapWithSubscriptions = useCallback(async (): Promise => { + const result = await initConnection(); + + setConnected(result); + + if (result) { + purchaseUpdateSubscription = purchaseUpdatedListener( + async (purchase: Purchase | SubscriptionPurchase) => { + setCurrentPurchaseError(undefined); + setCurrentPurchase(purchase); + }, + ); + + purchaseErrorSubscription = purchaseErrorListener( + (error: PurchaseError) => { + setCurrentPurchase(undefined); + setCurrentPurchaseError(error); + }, + ); + + promotedProductsSubscription = transactionUpdatedIos( + (event: TransactionEvent) => { + setPromotedProductsIOS((prevProducts) => + event.transaction + ? [...prevProducts, event.transaction] + : prevProducts, + ); + }, + ); + } + }, []); + + useEffect(() => { + initIapWithSubscriptions(); + + return (): void => { + if (purchaseUpdateSubscription) purchaseUpdateSubscription.remove(); + if (purchaseErrorSubscription) purchaseErrorSubscription.remove(); + if (promotedProductsSubscription) promotedProductsSubscription.remove(); + + endConnection(); + setConnected(false); + }; + }, [initIapWithSubscriptions]); + + return { + connected, + products, + promotedProductsIOS, + subscriptions, + purchaseHistories, + finishTransaction, + availablePurchases, + currentPurchase, + currentPurchaseError, + getProducts, + getSubscriptions, + getAvailablePurchases, + getPurchaseHistories, + }; +} From ff037e3f13bd82aff875ce249fabbbd9d079f910 Mon Sep 17 00:00:00 2001 From: hyochan Date: Sun, 27 Oct 2024 15:17:47 +0900 Subject: [PATCH 2/2] fix: tsc --- src/useIap.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/useIap.ts b/src/useIap.ts index a7aecdf..0276487 100644 --- a/src/useIap.ts +++ b/src/useIap.ts @@ -1,21 +1,25 @@ -import RNIap, { +import { endConnection, initConnection, purchaseErrorListener, purchaseUpdatedListener, transactionUpdatedIos, -} from '../'; + getProducts, + getAvailablePurchases, + getPurchaseHistory, + getSubscriptions, +} from './'; import {useCallback, useEffect, useState} from 'react'; import { Product, ProductPurchase, + Purchase, PurchaseError, PurchaseResult, SubscriptionProduct, SubscriptionPurchase, } from './ExpoIap.types'; -import {Purchase} from '../build/ExpoIap.types'; import {TransactionEvent} from './modules/ios'; import {TransactionSk2} from './types/ExpoIapIos.types'; import {Subscription} from 'expo-modules-core'; @@ -66,23 +70,23 @@ export function useIAP(): IAP_STATUS { const [currentPurchaseError, setCurrentPurchaseError] = useState(); - const getProducts = useCallback(async (skus: string[]): Promise => { - setProducts(await RNIap.getProducts(skus)); + const requestProducts = useCallback(async (skus: string[]): Promise => { + setProducts(await getProducts(skus)); }, []); - const getSubscriptions = useCallback( + const requestSubscriptions = useCallback( async (skus: string[]): Promise => { - setSubscriptions(await RNIap.getSubscriptions(skus)); + setSubscriptions(await getSubscriptions(skus)); }, [], ); - const getAvailablePurchases = useCallback(async (): Promise => { - setAvailablePurchases(await RNIap.getAvailablePurchases()); + const requestAvailablePurchases = useCallback(async (): Promise => { + setAvailablePurchases(await getAvailablePurchases()); }, []); - const getPurchaseHistories = useCallback(async (): Promise => { - setPurchaseHistories(await RNIap.getPurchaseHistory()); + const requestPurchaseHistories = useCallback(async (): Promise => { + setPurchaseHistories(await getPurchaseHistory()); }, []); const finishTransaction = useCallback( @@ -176,9 +180,9 @@ export function useIAP(): IAP_STATUS { availablePurchases, currentPurchase, currentPurchaseError, - getProducts, - getSubscriptions, - getAvailablePurchases, - getPurchaseHistories, + getProducts: requestProducts, + getSubscriptions: requestSubscriptions, + getAvailablePurchases: requestAvailablePurchases, + getPurchaseHistories: requestPurchaseHistories, }; }