Skip to content

Commit

Permalink
feat: useIap impl
Browse files Browse the repository at this point in the history
  • Loading branch information
hyochan committed Sep 21, 2024
1 parent 01d5536 commit b7baaab
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,5 @@ export const finishTransaction = ({
}) || (() => Promise.reject(new Error('Unsupported Platform')))
)();
};

export * from './useIap';
2 changes: 1 addition & 1 deletion src/modules/ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
} from '../types/ExpoIapIos.types';
import ExpoIapModule from '../ExpoIapModule';

type TransactionEvent = {
export type TransactionEvent = {
transaction?: TransactionSk2;
error?: PurchaseError;
};
Expand Down
184 changes: 184 additions & 0 deletions src/useIap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import RNIap, {
endConnection,
initConnection,
purchaseErrorListener,
purchaseUpdatedListener,
transactionUpdatedIos,
} from '../';

Check failure on line 7 in src/useIap.ts

View workflow job for this annotation

GitHub Actions / build

Cannot find module '../' or its corresponding type declarations.

import {useCallback, useEffect, useState} from 'react';
import {
Product,
ProductPurchase,
PurchaseError,
PurchaseResult,
SubscriptionProduct,
SubscriptionPurchase,
} from './ExpoIap.types';
import {Purchase} from '../build/ExpoIap.types';

Check failure on line 18 in src/useIap.ts

View workflow job for this annotation

GitHub Actions / build

Cannot find module '../build/ExpoIap.types' or its corresponding type declarations.
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<string | boolean | PurchaseResult | void>;
getAvailablePurchases: () => Promise<void>;
getPurchaseHistories: () => Promise<void>;
getProducts: (skus: string[]) => Promise<void>;
getSubscriptions: (skus: string[]) => Promise<void>;
};

let purchaseUpdateSubscription: Subscription;
let purchaseErrorSubscription: Subscription;
let promotedProductsSubscription: Subscription;

export function useIAP(): IAP_STATUS {
const [connected, setConnected] = useState<boolean>(false);
const [products, setProducts] = useState<Product[]>([]);
const [promotedProductsIOS, setPromotedProductsIOS] = useState<
TransactionSk2[]
>([]);
const [subscriptions, setSubscriptions] = useState<SubscriptionProduct[]>([]);
const [purchaseHistories, setPurchaseHistories] = useState<ProductPurchase[]>(
[],
);
const [availablePurchases, setAvailablePurchases] = useState<
ProductPurchase[]
>([]);
const [currentPurchase, setCurrentPurchase] = useState<ProductPurchase>();

const [currentPurchaseError, setCurrentPurchaseError] =
useState<PurchaseError>();

const getProducts = useCallback(async (skus: string[]): Promise<void> => {
setProducts(await RNIap.getProducts(skus));
}, []);

const getSubscriptions = useCallback(
async (skus: string[]): Promise<void> => {
setSubscriptions(await RNIap.getSubscriptions(skus));
},
[],
);

const getAvailablePurchases = useCallback(async (): Promise<void> => {
setAvailablePurchases(await RNIap.getAvailablePurchases());
}, []);

const getPurchaseHistories = useCallback(async (): Promise<void> => {
setPurchaseHistories(await RNIap.getPurchaseHistory());
}, []);

const finishTransaction = useCallback(
async ({
purchase,
isConsumable,
developerPayloadAndroid,
}: {
purchase: ProductPurchase;
isConsumable?: boolean;
developerPayloadAndroid?: string;
}): Promise<string | boolean | PurchaseResult | void> => {
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<void> => {
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,
};
}

0 comments on commit b7baaab

Please sign in to comment.