Skip to content
This repository was archived by the owner on Oct 19, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
36e6089
add currency and country info to loadProducts and add transactionFini…
briantobo Aug 12, 2016
aa7a134
add raw price + currency for android
briantobo Oct 18, 2016
669b58a
update plugin.xml
briantobo Oct 18, 2016
872ed9c
change plugin name to cordova-plugin-inapppurchase-tobo
briantobo Oct 18, 2016
fb166d0
get currency / country / priceRaw from javascript
briantobo Oct 18, 2016
bb592e9
get currency / country / priceRaw from javascript
briantobo Oct 18, 2016
fae14c6
update version to 1.2.0 and fix package.json
briantobo Oct 21, 2016
7ab8e9a
fix decimal separator for android, update version to 1.2.1
briantobo Oct 24, 2016
6214fb3
fix import for android
briantobo Oct 24, 2016
49bcbc9
fix ios transactionfinished callback
briantobo Nov 18, 2016
592a909
bump version to 1.2.2
briantobo Nov 18, 2016
9fa36e0
remove Promise polyfill for android, bump version to 1.2.3
briantobo Apr 13, 2017
e96f426
remove Promise polyfill for android because it causes problems with r…
briantobo Apr 17, 2017
502c46d
fix crash in transaction finished when receipt is empty (should only …
briantobo May 5, 2017
9405bb4
add support for introductory pricing on iOS (cherry-picked)
briantobo Mar 15, 2018
794a554
Merge support for introductory price and other improvements (priceRaw…
briantobo Mar 16, 2018
a75912a
fix build on cordova-android 7
briantobo Mar 16, 2018
98af7b1
update tests with new fields
briantobo Apr 27, 2018
3300b8a
iOS: add check for SDK >= 11.2 around introductory-price code to allo…
briantobo May 4, 2018
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,068 changes: 4,068 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-inapppurchase",
"version": "1.2.0",
"version": "1.3.0",
"description": "A lightweight cordova plugin for in app purchases on iOS and Android with a simple promise based API.",
"main": "www/index-ios.js",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
xmlns:android="http://schemas.android.com/apk/res/android"
id="cordova-plugin-inapppurchase"
version="1.1.0">
version="1.3.0">

<name>In App Purchase</name>
<description>A lightweight cordova plugin for in app purchases on iOS and Android with a simple promise based API.</description>
Expand Down Expand Up @@ -60,7 +60,7 @@
<source-file src="src/android/Purchase.java" target-dir="src/com/alexdisler/inapppurchases" />
<source-file src="src/android/Security.java" target-dir="src/com/alexdisler/inapppurchases" />
<source-file src="src/android/SkuDetails.java" target-dir="src/com/alexdisler/inapppurchases" />
<source-file src="src/android/billing/IInAppBillingService.aidl" target-dir="src/com/android/vending/billing" />
<source-file src="src/android/billing/IInAppBillingService.aidl" target-dir="app/src/main/aidl/com/android/vending/billing" />
</platform>

</plugin>
3 changes: 3 additions & 0 deletions src/android/InAppBillingV3.java
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,9 @@ public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
detailsJson.put("description", skuDetails.getDescription());
detailsJson.put("priceAsDecimal", skuDetails.getPriceAsDecimal());
detailsJson.put("price", skuDetails.getPrice());
detailsJson.put("priceRaw", skuDetails.getPriceRaw());
detailsJson.put("currency", skuDetails.getPriceCurrency());
detailsJson.put("country", "-");
detailsJson.put("type", skuDetails.getType());
detailsJson.put("currency", skuDetails.getPriceCurrency());
response.put(detailsJson);
Expand Down
11 changes: 11 additions & 0 deletions src/android/SkuDetails.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

import org.json.JSONException;
import org.json.JSONObject;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

/**
* Represents an in-app product's listing details.
Expand All @@ -28,6 +31,7 @@ public class SkuDetails {
Double mPriceAsDecimal;
String mPrice;
String mPriceCurrency;
String mPriceRaw;
String mTitle;
String mDescription;
String mJson;
Expand All @@ -47,13 +51,20 @@ public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
mPriceAsDecimal = Double.parseDouble(o.optString("price_amount_micros"))/Double.valueOf(1000000);
mTitle = o.optString("title");
mDescription = o.optString("description");

String priceMicrosStr = o.optString("price_amount_micros");
long priceMicros = Long.parseLong(priceMicrosStr);
DecimalFormat formatter = new DecimalFormat("#.00####");
formatter.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ENGLISH));
mPriceRaw = formatter.format(priceMicros / 1000000.0);
}

public String getSku() { return mSku; }
public String getType() { return mType; }
public String getPrice() { return mPrice; }
public String getPriceCurrency() { return mPriceCurrency; }
public Double getPriceAsDecimal() { return mPriceAsDecimal; }
public String getPriceRaw() { return mPriceRaw; }
public String getTitle() { return mTitle; }
public String getDescription() { return mDescription; }

Expand Down
79 changes: 70 additions & 9 deletions src/ios/PaymentsPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
@implementation PaymentsPlugin

- (void)pluginInitialize {
[[RMStore defaultStore] addStoreObserver:self];
}

- (void)getProducts:(CDVInvokedUrlCommand *)command {
Expand All @@ -33,20 +34,51 @@ - (void)getProducts:(CDVInvokedUrlCommand *)command {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSMutableArray *validProducts = [NSMutableArray array];
for (SKProduct *product in products) {
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setLocale:product.priceLocale];
NSString *currencyCode = [numberFormatter currencyCode];

[validProducts addObject:@{

NSString *country = [product.priceLocale objectForKey:NSLocaleCountryCode];
NSString *currencyCode = [product.priceLocale objectForKey:NSLocaleCurrencyCode];

NSNumber *isIntroductoryPriceSupported = @0;
NSDictionary *introductoryPriceInfo = nil;

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110200
if (@available(iOS 11.2, *)) {
isIntroductoryPriceSupported = @1;
if (product.introductoryPrice) {
SKProductDiscount *ip = product.introductoryPrice;
NSLocale *ipPriceLocale = ip.priceLocale;
if (!ipPriceLocale) {
ipPriceLocale = product.priceLocale;
}

introductoryPriceInfo = @{
@"price": NILABLE([RMStore localizedPriceStringWithPrice:ip.price priceLocale:ipPriceLocale]),
@"priceRaw": NILABLE([ip.price stringValue]),
@"country": NILABLE([ipPriceLocale objectForKey:NSLocaleCountryCode]),
@"currency": NILABLE([ipPriceLocale objectForKey:NSLocaleCurrencyCode]),
@"paymentMode": NILABLE([RMStore stringForPaymentMode:ip.paymentMode]),
@"numberOfPeriods": [NSString stringWithFormat:@"%lu", ip.numberOfPeriods],
@"subscriptionPeriod": @{
@"unit": [RMStore stringForPeriodUnit:ip.subscriptionPeriod.unit],
@"numberOfUnits": [NSString stringWithFormat:@"%lu", ip.subscriptionPeriod.numberOfUnits],
}
};
}
}
#endif

[validProducts addObject:@{
@"productId": NILABLE(product.productIdentifier),
@"title": NILABLE(product.localizedTitle),
@"description": NILABLE(product.localizedDescription),
@"priceAsDecimal": NILABLE(product.price),
@"priceRaw": NILABLE([product.price stringValue]),
@"price": NILABLE([RMStore localizedPriceOfProduct:product]),
@"currency": NILABLE(currencyCode)
}];
@"country": NILABLE(country),
@"currency": NILABLE(currencyCode),
@"introductoryPrice": NILABLE(introductoryPriceInfo),
@"introductoryPriceSupported": isIntroductoryPriceSupported
}];
}
[result setObject:validProducts forKey:@"products"];
[result setObject:invalidProductIdentifiers forKey:@"invalidProductsIds"];
Expand Down Expand Up @@ -142,4 +174,33 @@ - (void)getReceipt:(CDVInvokedUrlCommand *)command {
}];
}


#pragma mark -
#pragma mark Store Observer

- (void)storePaymentTransactionFinished:(NSNotification*)notification
{
NSDictionary *userInfo = notification.userInfo;
SKPaymentTransaction *transaction = userInfo[@"transaction"];
NSString *productId = userInfo[@"productIdentifier"];

// NSLog(@"Transaction Finished : %@ (productId: %@)", transaction, productId);

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
NSString *encReceipt = [receiptData base64EncodedStringWithOptions:0];

if (!encReceipt) {
encReceipt = @"";
}

NSDictionary *event = @{@"productId": productId, @"transactionId": transaction.transactionIdentifier, @"receipt": encReceipt};
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:event options:0 error:&error];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

NSString *js = [NSString stringWithFormat:@"cordova.fireDocumentEvent('transactionfinished', %@);", jsonString];
[self.commandDelegate evalJs:js];
}

@end
7 changes: 7 additions & 0 deletions src/ios/RMStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ extern NSInteger const RMStoreErrorCodeUnableToCompleteVerification;

+ (NSString*)localizedPriceOfProduct:(SKProduct*)product;

+ (NSString*)localizedPriceStringWithPrice:(NSDecimalNumber*)price priceLocale:(NSLocale*)priceLocale;

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110200
+ (NSString*)stringForPaymentMode:(SKProductDiscountPaymentMode)paymentMode;
+ (NSString*)stringForPeriodUnit:(SKProductPeriodUnit)unit;
#endif

#pragma mark Notifications
///---------------------------------------------
/// @name Managing Observers
Expand Down
85 changes: 57 additions & 28 deletions src/ios/RMStore.m
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,16 @@ @implementation RMStore {
NSMutableDictionary *_addPaymentParameters; // HACK: We use a dictionary of product identifiers because the returned SKPayment is different from the one we add to the queue. Bad Apple.
NSMutableDictionary *_products;
NSMutableSet *_productsRequestDelegates;

NSMutableArray *_restoredTransactions;

NSInteger _pendingRestoredTransactionsCount;
BOOL _restoredCompletedTransactionsFinished;

SKReceiptRefreshRequest *_refreshReceiptRequest;
void (^_refreshReceiptFailureBlock)(NSError* error);
void (^_refreshReceiptSuccessBlock)();

void (^_restoreTransactionsFailureBlock)(NSError* error);
void (^_restoreTransactionsSuccessBlock)(NSArray* transactions);
}
Expand Down Expand Up @@ -215,12 +215,12 @@ - (void)addPayment:(NSString*)productIdentifier
{
payment.applicationUsername = userIdentifier;
}

RMAddPaymentParameters *parameters = [[RMAddPaymentParameters alloc] init];
parameters.successBlock = successBlock;
parameters.failureBlock = failureBlock;
_addPaymentParameters[productIdentifier] = parameters;

[[SKPaymentQueue defaultQueue] addPayment:payment];
}

Expand All @@ -238,10 +238,10 @@ - (void)requestProducts:(NSSet*)identifiers
delegate.successBlock = successBlock;
delegate.failureBlock = failureBlock;
[_productsRequestDelegates addObject:delegate];

SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers];
productsRequest.delegate = delegate;

[productsRequest start];
}

Expand Down Expand Up @@ -307,13 +307,42 @@ - (SKProduct*)productForIdentifier:(NSString*)productIdentifier

+ (NSString*)localizedPriceOfProduct:(SKProduct*)product
{
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
numberFormatter.locale = product.priceLocale;
NSString *formattedString = [numberFormatter stringFromNumber:product.price];
return formattedString;
return [self localizedPriceStringWithPrice:product.price priceLocale:product.priceLocale];
}

+ (NSString*)localizedPriceStringWithPrice:(NSDecimalNumber*)price priceLocale:(NSLocale*)priceLocale
{
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
numberFormatter.locale = priceLocale;
NSString *formattedString = [numberFormatter stringFromNumber:price];
return formattedString;
}

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110200
+ (NSString*)stringForPaymentMode:(SKProductDiscountPaymentMode)paymentMode
{
switch (paymentMode) {
case SKProductDiscountPaymentModeFreeTrial: return @"FreeTrial";
case SKProductDiscountPaymentModePayAsYouGo: return @"PayAsYouGo";
case SKProductDiscountPaymentModePayUpFront: return @"PayUpFront";
}
return @"unknown";
}

+ (NSString*)stringForPeriodUnit:(SKProductPeriodUnit)unit
{
switch (unit) {
case SKProductPeriodUnitDay: return @"day";
case SKProductPeriodUnitWeek: return @"week";
case SKProductPeriodUnitMonth: return @"month";
case SKProductPeriodUnitYear: return @"year";
}
return @"unknown";

}
#endif

#pragma mark Observers

- (void)addStoreObserver:(id<RMStoreObserver>)observer
Expand Down Expand Up @@ -392,7 +421,7 @@ - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
RMStoreLog(@"restore transactions finished");
_restoredCompletedTransactionsFinished = YES;

[self notifyRestoreTransactionFinishedIfApplicableAfterTransaction:nil];
}

Expand Down Expand Up @@ -478,7 +507,7 @@ - (void)didFinishDownload:(SKDownload*)download queue:(SKPaymentQueue*)queue
{
SKPaymentTransaction *transaction = download.transaction;
RMStoreLog(@"download %@ for product %@ finished", download.contentIdentifier, transaction.payment.productIdentifier);

[self postNotificationWithName:RMSKDownloadFinished download:download userInfoExtras:nil];

const BOOL hasPendingDownloads = [self.class hasPendingDownloadsInTransaction:transaction];
Expand Down Expand Up @@ -525,7 +554,7 @@ + (BOOL)hasPendingDownloadsInTransaction:(SKPaymentTransaction*)transaction
- (void)didPurchaseTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue
{
RMStoreLog(@"transaction purchased with product %@", transaction.payment.productIdentifier);

if (self.receiptVerifier != nil)
{
[self.receiptVerifier verifyTransaction:transaction success:^{
Expand All @@ -546,21 +575,21 @@ - (void)didFailTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQ
SKPayment *payment = transaction.payment;
NSString* productIdentifier = payment.productIdentifier;
RMStoreLog(@"transaction failed with product %@ and error %@", productIdentifier, error.debugDescription);

if (error.code != RMStoreErrorCodeUnableToCompleteVerification)
{ // If we were unable to complete the verification we want StoreKit to keep reminding us of the transaction
[queue finishTransaction:transaction];
}

RMAddPaymentParameters *parameters = [self popAddPaymentParametersForIdentifier:productIdentifier];
if (parameters.failureBlock != nil)
{
parameters.failureBlock(transaction, error);
}

NSDictionary *extras = error ? @{RMStoreNotificationStoreError : error} : nil;
[self postNotificationWithName:RMSKPaymentTransactionFailed transaction:transaction userInfoExtras:extras];

if (transaction.transactionState == SKPaymentTransactionStateRestored)
{
[self notifyRestoreTransactionFinishedIfApplicableAfterTransaction:transaction];
Expand All @@ -570,7 +599,7 @@ - (void)didFailTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQ
- (void)didRestoreTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQueue*)queue
{
RMStoreLog(@"transaction restored with product %@", transaction.originalTransaction.payment.productIdentifier);

_pendingRestoredTransactionsCount++;
if (self.receiptVerifier != nil)
{
Expand Down Expand Up @@ -634,15 +663,15 @@ - (void)finishTransaction:(SKPaymentTransaction *)transaction queue:(SKPaymentQu
NSString* productIdentifier = payment.productIdentifier;
[queue finishTransaction:transaction];
[self.transactionPersistor persistTransaction:transaction];

RMAddPaymentParameters *wrapper = [self popAddPaymentParametersForIdentifier:productIdentifier];
if (wrapper.successBlock != nil)
{
wrapper.successBlock(transaction);
}

[self postNotificationWithName:RMSKPaymentTransactionFinished transaction:transaction userInfoExtras:nil];

if (transaction.transactionState == SKPaymentTransactionStateRestored)
{
[self notifyRestoreTransactionFinishedIfApplicableAfterTransaction:transaction];
Expand Down Expand Up @@ -711,7 +740,7 @@ - (void)request:(SKRequest *)request didFailWithError:(NSError *)error

- (void)addProduct:(SKProduct*)product
{
_products[product.productIdentifier] = product;
_products[product.productIdentifier] = product;
}

- (void)postNotificationWithName:(NSString*)notificationName download:(SKDownload*)download userInfoExtras:(NSDictionary*)extras
Expand Down Expand Up @@ -748,17 +777,17 @@ - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProdu
RMStoreLog(@"products request received response");
NSArray *products = [NSArray arrayWithArray:response.products];
NSArray *invalidProductIdentifiers = [NSArray arrayWithArray:response.invalidProductIdentifiers];

for (SKProduct *product in products)
{
RMStoreLog(@"received product with id %@", product.productIdentifier);
[self.store addProduct:product];
}

[invalidProductIdentifiers enumerateObjectsUsingBlock:^(NSString *invalid, NSUInteger idx, BOOL *stop) {
RMStoreLog(@"invalid product with id %@", invalid);
}];

if (self.successBlock)
{
self.successBlock(products, invalidProductIdentifiers);
Expand Down
Loading