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

Add additional event listener to handle success purchase after failure. #348

Merged
merged 2 commits into from
Dec 24, 2018
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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
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`. Currently this feature is in test.


## 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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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