Skip to content

Commit

Permalink
Add additional event listener to handle success purchase after failure.
Browse files Browse the repository at this point in the history
Make workaround for #307. Resolve #307 for now.
  • Loading branch information
hyochan committed Dec 23, 2018
1 parent e62ccb4 commit 623d049
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 24 deletions.
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -215,6 +215,23 @@ RNIap.buyProduct('com.example.coins100').then(purchase => {
Subscribable products can be purchased just like consumable products.
Users can cancel subscriptions by using the iOS System Settings.

## Purchase Example 3 (Advanced)
```javascript
try {
const purchase: any = await RNIap.buyProduct(sku);
this.setState({ receipt: purchase.transactionReceipt }, () => this.goToNext());
} catch (err) {
console.warn(err.code, err.message);
const subscription = RNIap.addAdditionalSuccessPurchaseListenerIOS(async (purchase) => {
this.setState({ receipt: purchase.transactionReceipt }, () => this.goToNext());
subscription.remove();
});
}
```
If you need to handle the success of purchase which could be called even after purchase failed,
you can add `addAdditionalSuccessPurchaseListenerIOS` to handle nex `successPurchase`.
* This feature is provided from `react-native-iap` version `2.4.0-beta1`.


## Consumption and Restoring Purchases
You can use `getAvailablePurchases()` to do what's commonly understood as "restoring" purchases. Once an item is consumed, it will no longer be available in `getAvailablePurchases()` and will only be available via `getPurchaseHistory()`. However, this method has some caveats on Android -- namely, that purchase history only exists for the single most recent purchase of each SKU -- so your best bet is to track consumption in your app yourself. By default, all items that are purchased will not be consumed unless they are automatically consumed by the store (for example, if you create a consumable item for iOS.) This means that you must manage consumption yourself. Purchases can be consumed by calling `consumePurchase()`. If you want to consume all items, you have to iterate over the purchases returned by `getAvailablePurchases()`.
Expand Down
15 changes: 9 additions & 6 deletions RNIapExample/src/components/pages/First.js
Expand Up @@ -81,16 +81,19 @@ class Page extends Component {
}

buyItem = async(sku) => {
console.info('buyItem: ' + sku);
// const purchase = await RNIap.buyProduct(sku);
// const products = await RNIap.buySubscription(sku);
// const purchase = await RNIap.buyProductWithoutFinishTransaction(sku);
try {
console.info('buyItem: ' + sku);
// const purchase = await RNIap.buyProduct(sku);
// const products = await RNIap.buySubscription(sku);
const purchase = await RNIap.buyProductWithoutFinishTransaction(sku);
console.info(purchase);
const purchase: any = await RNIap.buyProduct(sku);
this.setState({ receipt: purchase.transactionReceipt }, () => this.goToNext());
} catch (err) {
console.warn(err.code, err.message);
Alert.alert(err.message);
const subscription = RNIap.addAdditionalSuccessPurchaseListenerIOS(async (purchase) => {
this.setState({ receipt: purchase.transactionReceipt }, () => this.goToNext());
subscription.remove();
});
}
}

Expand Down
13 changes: 12 additions & 1 deletion index.d.ts
Expand Up @@ -6,7 +6,6 @@ interface Common {
price: string
currency: string
localizedPrice: string
localeIOS: string
}

export interface Product<ID extends string> extends Common {
Expand All @@ -18,13 +17,19 @@ export interface Subscription<ID extends string> extends Common {
type: 'subs' | 'sub'
productId: ID

introductoryPrice?: string
introductoryPricePaymentModeIOS?: string
introductoryPriceNumberOfPeriods?: number
introductoryPriceSubscriptionPeriod: object

subscriptionPeriodNumberIOS?: string
subscriptionPeriodUnitIOS?: number

freeTrialPeriodAndroid?: string
introductoryPriceCyclesAndroid?: number
introductoryPricePeriodAndroid?: string
subscriptionPeriodAndroid?: string
freeTrialPeriodAndroid: string
}

export interface ProductPurchase {
Expand Down Expand Up @@ -180,3 +185,9 @@ export function validateReceiptIos(receiptBody: Apple.ReceiptValidationRequest,
* @param isSub whether this is subscription or inapp. `true` for subscription.
*/
export function validateReceiptAndroid(packageName: string, productId: string, productToken: string, accessToken: string, isSub: boolean): Promise<object | false>;

/**
* Add IAP purchase event in ios.
* @returns {callback(e: Event)}
*/
export function addAdditionalSuccessPurchaseListenerIOS(fn: Function);
16 changes: 15 additions & 1 deletion index.js
@@ -1,5 +1,5 @@

import { NativeModules, Platform } from 'react-native';
import { NativeModules, Platform, NativeEventEmitter } from 'react-native';

const { RNIapIos, RNIapModule } = NativeModules;

Expand Down Expand Up @@ -235,6 +235,19 @@ export const validateReceiptAndroid = async(packageName, productId, productToken
return response.json();
};

/**
* Add IAP purchase event in ios.
* @returns {callback(e: Event)}
*/
export const addAdditionalSuccessPurchaseListenerIOS = (e) => {
if (Platform.OS === 'ios') {
const myModuleEvt = new NativeEventEmitter(RNIapIos);
return myModuleEvt.addListener('iap-purchase-event', e);
} else {
console.log('adding purchase listener is only provided in ios.');
}
};

/**
* deprecagted codes
*/
Expand Down Expand Up @@ -287,4 +300,5 @@ export default {
consumePurchase,
validateReceiptIos,
validateReceiptAndroid,
addAdditionalSuccessPurchaseListenerIOS,
};
8 changes: 2 additions & 6 deletions ios/RNIapIos.h
@@ -1,12 +1,8 @@
#if __has_include("RCTBridgeModule.h")
#import "RCTBridgeModule.h"
#else
#import <React/RCTBridgeModule.h>
#endif

#import <React/RCTEventEmitter.h>
#import <StoreKit/StoreKit.h>

@interface RNIapIos : NSObject <RCTBridgeModule, SKProductsRequestDelegate,SKPaymentTransactionObserver>
@interface RNIapIos : RCTEventEmitter <RCTBridgeModule, SKProductsRequestDelegate,SKPaymentTransactionObserver>
{
SKProductsRequest *productsRequest;
NSMutableArray *validProducts;
Expand Down
17 changes: 8 additions & 9 deletions ios/RNIapIos.m
Expand Up @@ -73,6 +73,11 @@ -(void)rejectPromisesForKey:(NSString*)key code:(NSString*)code message:(NSStrin
//////////////////////////////////////////////////// _//////////_// EXPORT_MODULE
RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents
{
return @[@"iap-purchase-event"];
}

RCT_EXPORT_METHOD(canMakePayments:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
BOOL canMakePayments = [SKPaymentQueue canMakePayments];
Expand Down Expand Up @@ -287,6 +292,9 @@ -(void)purchaseProcess:(SKPaymentTransaction *)transaction {
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSDictionary* purchase = [self getPurchaseData:transaction];
[self resolvePromisesForKey:RCTKeyForInstance(transaction.payment.productIdentifier) value:purchase];

// additionally send event
[self sendEventWithName:@"iap-purchase-event" body: purchase];
}

-(NSString *)standardErrorCode:(int)code {
Expand Down Expand Up @@ -315,7 +323,6 @@ -(NSDictionary*)getProductObject:(SKProduct *)product {

NSString* localizedPrice = [formatter stringFromNumber:product.price];
NSString* introductoryPrice = localizedPrice;
NSString* locale = localeIdentifierToBCP47(product.priceLocale.localeIdentifier);

NSString* introductoryPricePaymentMode = @"";
NSString* introductoryPriceNumberOfPeriods = @"";
Expand Down Expand Up @@ -401,7 +408,6 @@ -(NSDictionary*)getProductObject:(SKProduct *)product {
product.localizedTitle ? product.localizedTitle : @"", @"title",
product.localizedDescription ? product.localizedDescription : @"", @"description",
localizedPrice, @"localizedPrice",
locale, @"localeIOS",
periodNumberIOS, @"subscriptionPeriodNumberIOS",
periodUnitIOS, @"subscriptionPeriodUnitIOS",
introductoryPrice, @"introductoryPrice",
Expand Down Expand Up @@ -454,11 +460,4 @@ - (NSDictionary *)getPurchaseData:(SKPaymentTransaction *)transaction {
return [NSString stringWithFormat:@"%p", instance];
}

static NSString *localeIdentifierToBCP47(NSString* localeIdentifier)
{
// e.g. `en_US@currency=USD` -> `en-US`.
NSString *identifier = [[product.priceLocale.localeIdentifier componentsSeparatedByString:@"@"] objectAtIndex:0];
return [identifier stringByReplacingOccurrencesOfString:@"_" withString:@"-"];
}

@end
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "react-native-iap",
"version": "2.3.25",
"version": "2.4.0-betal",
"description": "React Native In App Purchase Module.",
"main": "index.js",
"types": "index.d.ts",
Expand Down

0 comments on commit 623d049

Please sign in to comment.