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

Reverted some files to maintain backwards compatibility #1897

Merged
merged 2 commits into from
Aug 30, 2022
Merged
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
4 changes: 2 additions & 2 deletions IapExample/src/screens/ClassSetup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export class ClassSetup extends Component<{}, State> {

{productList.map((product, index) => (
<Row
key={product.id}
key={product.productId}
fields={[
{
label: 'Product JSON',
Expand All @@ -185,7 +185,7 @@ export class ClassSetup extends Component<{}, State> {
>
<Button
title="Buy"
onPress={() => this.requestSubscription(product.id)}
onPress={() => this.requestSubscription(product.productId)}
/>
</Row>
))}
Expand Down
12 changes: 5 additions & 7 deletions IapExample/src/screens/Products.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export const Products = () => {
initConnectionError,
finishTransaction,
getProducts,
setCurrentPurchase,
} = useIAP();

const handleGetProducts = async () => {
Expand All @@ -34,8 +33,7 @@ export const Products = () => {

const handleBuyProduct = async (sku: Sku) => {
try {
const transaction = await requestPurchase({sku});
setCurrentPurchase(transaction);
await requestPurchase({sku});
} catch (error) {
if (error instanceof PurchaseError) {
errorLog({message: `[${error.code}]: ${error.message}`, error});
Expand All @@ -48,7 +46,7 @@ export const Products = () => {
useEffect(() => {
const checkCurrentPurchase = async () => {
try {
if (currentPurchase?.id) {
if (currentPurchase?.productId) {
await finishTransaction({
purchase: currentPurchase,
isConsumable: true,
Expand Down Expand Up @@ -103,11 +101,11 @@ export const Products = () => {

{products.map((product, index) => (
<Row
key={product.id}
key={product.productId}
fields={[
{
label: 'Product Id',
value: product.id,
value: product.productId,
},
{
label: 'type',
Expand All @@ -118,7 +116,7 @@ export const Products = () => {
>
<Button
title="Buy"
onPress={() => handleBuyProduct(product.id)}
onPress={() => handleBuyProduct(product.productId)}
/>
</Row>
))}
Expand Down
60 changes: 44 additions & 16 deletions IapExample/src/screens/Subscriptions.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import React, {useState} from 'react';
import React, {useEffect, useState} from 'react';
import {Platform, ScrollView, StyleSheet, Text, View} from 'react-native';
import {PurchaseError, requestSubscription, useIAP} from 'react-native-iap';

import {Box, Button, Heading, Row, State} from '../components';
import {constants, contentContainerStyle, errorLog} from '../utils';

export const Subscriptions = () => {
const {connected, subscriptions, getSubscriptions, setCurrentPurchase} =
useIAP();
const {
connected,
subscriptions,
getSubscriptions,
currentPurchase,
finishTransaction,
} = useIAP();
const [ownedSubscriptions, setOwnedSubscriptions] = useState(new Set());
const handleGetSubscriptions = async () => {
try {
Expand All @@ -27,17 +32,12 @@ export const Subscriptions = () => {
);
}
try {
const transaction = await requestSubscription({
await requestSubscription({
sku: productId,
...(offerToken && {
subscriptionOffers: [{sku: productId, offerToken}],
}),
});
console.warn('transaction', transaction);
setOwnedSubscriptions((prev) => {
return new Set([...prev, transaction?.productID]);
});
transaction && setCurrentPurchase(transaction);
} catch (error) {
if (error instanceof PurchaseError) {
errorLog({message: `[${error.code}]: ${error.message}`, error});
Expand All @@ -47,6 +47,31 @@ export const Subscriptions = () => {
}
};

useEffect(() => {
const checkCurrentPurchase = async () => {
try {
if (currentPurchase?.productId) {
await finishTransaction({
purchase: currentPurchase,
isConsumable: true,
});

setOwnedSubscriptions(
(prev) => new Set([...prev, currentPurchase?.productId]),
);
}
} catch (error) {
if (error instanceof PurchaseError) {
errorLog({message: `[${error.code}]: ${error.message}`, error});
} else {
errorLog({message: 'handleBuyProduct', error});
}
}
};

checkCurrentPurchase();
}, [currentPurchase, finishTransaction]);

return (
<ScrollView contentContainerStyle={contentContainerStyle}>
<State connected={connected} />
Expand All @@ -57,11 +82,11 @@ export const Subscriptions = () => {

{subscriptions.map((subscription, index) => (
<Row
key={subscription.id}
key={subscription.productId}
fields={[
{
label: 'Subscription Id',
value: subscription.id,
value: subscription.productId,
},
{
label: 'type',
Expand All @@ -70,10 +95,10 @@ export const Subscriptions = () => {
]}
isLast={subscriptions.length - 1 === index}
>
{ownedSubscriptions.has(subscription.id) && (
{ownedSubscriptions.has(subscription.productId) && (
<Text>Subscribed</Text>
)}
{!ownedSubscriptions.has(subscription.id) &&
{!ownedSubscriptions.has(subscription.productId) &&
Platform.OS === 'android' &&
// On Google Play Billing V5 you might have multiple offers for a single sku
subscription?.subscriptionOfferDetails?.map((offer) => (
Expand All @@ -82,16 +107,19 @@ export const Subscriptions = () => {
.map((ppl) => ppl.billingPeriod)
.join(',')}`}
onPress={() => {
handleBuySubscription(subscription.id, offer.offerToken);
handleBuySubscription(
subscription.productId,
offer.offerToken,
);
}}
/>
))}
{!ownedSubscriptions.has(subscription.id) &&
{!ownedSubscriptions.has(subscription.productId) &&
Platform.OS === 'ios' && (
<Button
title="Subscribe"
onPress={() => {
handleBuySubscription(subscription.id);
handleBuySubscription(subscription.productId);
}}
/>
)}
Expand Down
35 changes: 28 additions & 7 deletions ios/RNIapIos.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class RNIapIos: RCTEventEmitter {
}

override func supportedEvents() -> [String]? {
return [ "transaction-updated"]
return [ "iap-promoted-product", "purchase-updated", "purchase-error"]
}

@objc public func initConnection(
Expand Down Expand Up @@ -135,6 +135,15 @@ class RNIapIos: RCTEventEmitter {
) {
Task {
var purchasedItems: [ProductOrError] = []

func addPurchase(transaction: Transaction, product: Product) {
purchasedItems.append(ProductOrError(product: product, error: nil))
sendEvent(withName: "purchase-update", body: serialize(transaction))
}
func addError( error: Error, errorDict: [String: String]) {
purchasedItems.append(ProductOrError(product: nil, error: error))
sendEvent(withName: "purchase-error", body: errorDict)
}
// Iterate through all of the user's purchased products.
for await result in Transaction.currentEntitlements {
do {
Expand All @@ -144,7 +153,7 @@ class RNIapIos: RCTEventEmitter {
switch transaction.productType {
case .nonConsumable:
if let product = products[transaction.productID] {
purchasedItems.append(ProductOrError(product: product, error: nil))
addPurchase(transaction: transaction, product: product)
}

case .nonRenewable:
Expand All @@ -159,23 +168,35 @@ class RNIapIos: RCTEventEmitter {
to: transaction.purchaseDate)!

if currentDate < expirationDate {
purchasedItems.append(ProductOrError(product: nonRenewable, error: nil))
addPurchase(transaction: transaction, product: nonRenewable)
}
}

case .autoRenewable:
if let subscription = products[transaction.productID] {
purchasedItems.append(ProductOrError(product: subscription, error: nil))
addPurchase(transaction: transaction, product: subscription)
}

default:
break
}
} catch StoreError.failedVerification {
purchasedItems.append(ProductOrError(product: nil, error: StoreError.failedVerification))
let err = [ "responseCode": "1", // TODO
"debugMessage": StoreError.failedVerification.localizedDescription,
"code": "1", // TODO
"message": StoreError.failedVerification.localizedDescription,
"productId": "unknown"// TODO
]
addError(error: StoreError.failedVerification, errorDict: err)
} catch {
debugMessage(error)
purchasedItems.append(ProductOrError(product: nil, error: error))
let err = [ "responseCode": "1", // TODO
"debugMessage": error.localizedDescription,
"code": "1", // TODO
"message": error.localizedDescription,
"productId": "unknown"// TODO
]
addError(error: StoreError.failedVerification, errorDict: err)
}
}
// Check the `subscriptionGroupStatus` to learn the auto-renewable subscription state to determine whether the customer
Expand Down Expand Up @@ -282,7 +303,7 @@ class RNIapIos: RCTEventEmitter {
error)
}
} else {
reject("E_DEVELOPER_ERROR", "Invalid product ID.", nil)
reject("E_DEVELOPER_ERROR", "Invalid product ID. Did you call getProducts/Subscriptions", nil)
}
}
}
Expand Down
33 changes: 28 additions & 5 deletions src/eventEmitter.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import {NativeEventEmitter} from 'react-native';
import {EmitterSubscription, NativeEventEmitter} from 'react-native';

import {getAndroidModule, getNativeModule} from './iap';
import {isAndroid} from './internal';
import {getAndroidModule, getIosModule, getNativeModule} from './iap';
import {isAndroid, isIos} from './internal';
import type {PurchaseError} from './purchaseError';
import type {Purchase} from './types';

const eventEmitter = new NativeEventEmitter(getNativeModule());

/**
* Add IAP purchase event
* TODO: Rename to transactionUpdatedListener
*/
export const purchaseUpdatedListener = (
listener: (event: Purchase) => void,
) => {
const emitterSubscription = eventEmitter.addListener(
'transaction-updated',
'purchase-updated',
listener,
);

Expand All @@ -24,3 +24,26 @@ export const purchaseUpdatedListener = (

return emitterSubscription;
};

/**
* Add IAP purchase error event
*/
export const purchaseErrorListener = (
listener: (error: PurchaseError) => void,
): EmitterSubscription => eventEmitter.addListener('purchase-error', listener);

/**
* Add IAP promoted subscription event
*
* @platform iOS
*/
export const promotedProductListener = (listener: () => void) => {
if (isIos) {
return new NativeEventEmitter(getIosModule()).addListener(
'iap-promoted-product',
listener,
);
}

return null;
};
17 changes: 8 additions & 9 deletions src/hooks/useIAP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {useIAPContext} from './withIAPContext';
type IAP_STATUS = {
connected: boolean;
products: Product[];
promotedProductsIOS: Product[];
subscriptions: Subscription[];
purchaseHistory: Purchase[];
availablePurchases: Purchase[];
Expand All @@ -34,13 +35,13 @@ type IAP_STATUS = {
getPurchaseHistory: () => Promise<void>;
getProducts: ({skus}: {skus: string[]}) => Promise<void>;
getSubscriptions: ({skus}: {skus: string[]}) => Promise<void>;
setCurrentPurchase: (currentPurchase: Purchase | undefined) => void;
};

export function useIAP(): IAP_STATUS {
const {
connected,
products,
promotedProductsIOS,
subscriptions,
purchaseHistory,
availablePurchases,
Expand All @@ -57,9 +58,7 @@ export function useIAP(): IAP_STATUS {

const getProducts = useCallback(
async ({skus}: {skus: string[]}): Promise<void> => {
const prods = await iapGetProducts({skus});
console.log(prods);
setProducts(prods);
setProducts(await iapGetProducts({skus}));
},
[setProducts],
);
Expand Down Expand Up @@ -98,18 +97,18 @@ export function useIAP(): IAP_STATUS {
} catch (err) {
throw err;
} finally {
if (purchase.id === currentPurchase?.id) {
if (purchase.productId === currentPurchase?.productId) {
setCurrentPurchase(undefined);
}

if (purchase.id === currentPurchaseError?.id) {
if (purchase.productId === currentPurchaseError?.productId) {
setCurrentPurchaseError(undefined);
}
}
},
[
currentPurchase?.id,
currentPurchaseError?.id,
currentPurchase?.productId,
currentPurchaseError?.productId,
setCurrentPurchase,
setCurrentPurchaseError,
],
Expand All @@ -118,6 +117,7 @@ export function useIAP(): IAP_STATUS {
return {
connected,
products,
promotedProductsIOS,
subscriptions,
purchaseHistory,
availablePurchases,
Expand All @@ -129,6 +129,5 @@ export function useIAP(): IAP_STATUS {
getSubscriptions,
getAvailablePurchases,
getPurchaseHistory,
setCurrentPurchase,
};
}
Loading