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
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
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,71 @@ inAppPurchase
});
```


### Get Receipt Bundle

#### inAppPurchase.getReceiptBundle()

On ***iOS***, you can get the receipt bundle at any moment by calling the getReceiptBundle() function. Note that on iOS the receipt can contain multiple transactions. If successful, the promise returned by this function will resolve to an object with the following attributes:

- ```appVersion``` - The app’s version number
- ```originalAppVersion``` - The version of the app that was originally purchased (In the sandbox environment, the value of this field is always “1.0”)
- ```bundleIdentifier``` - The app’s bundle identifier
- ```inAppPurchases``` - an array containing all in-app purchase receipts
- The in-app purchase receipt for a **non-consumable product**, **auto-renewable subscription**, or **free subscription** remains in the receipt *indefinitely*.
- The in-app purchase receipt for a **consumable product** or **non-renewing subscription** is *added* to the receipt when the purchase is made. It is kept in the receipt until your app finishes that transaction. After that point, it is *removed* from the receipt the next time the receipt is updated—for example, when the user makes another purchase or if your app explicitly refreshes the receipt.

**in-app purchase receipts** contains an array of objects with the following attributes:

- ```transactionIdentifier``` - The transaction identifier of the item that was purchased.
- ```quantity``` - The number of items purchased.
- ```purchaseDate``` - The date and time that the item was purchased.
- ```productId``` - The product identifier of the item that was purchased.
- ```originalPurchaseDate``` - For a transaction that restores a previous transaction, the date of the original transaction.
- ```subscriptionExpirationDate``` - The expiration date for the subscription, expressed as the number of milliseconds since January 1, 1970, 00:00:00 GMT.
- ```originalTransactionIdentifier``` - For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier.
- ```cancellationDate``` - For a transaction that was canceled by Apple customer support, the time and date of the cancellation
- ```webOrderLineItemID``` - The primary key for identifying subscription purchases.

See Apple documentation: [App Receipt Fields](https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html)


On ***Android*** this function will always return an empty string since it's not needed for Android purchases.

___Example:___

```js
inAppPurchase
.getReceiptBundle()
.then(function (receiptBundle) {
console.log(receiptBundle);
/*
{
"originalAppVersion": "1.0",
"appVersion": "0.1.0",
"inAppPurchases": [
{
"transactionIdentifier":"123412341234",
"quantity":1,
"purchaseDate":"2016-07-05T10:15:21Z",
"productId":"com.mycompany.myapp.weekly.v1",
"originalPurchaseDate":"2016-07-05T10:15:22Z",
"subscriptionExpirationDate":"2016-07-05T10:18:21Z",
"originalTransactionIdentifier":"123412341234",
"webOrderLineItemID":-1497665198,
"cancellationDate":null
}
],
"bundleIdentifier": "com.mycompany.myapp"
}
*/
})
.catch(function (err) {
console.log(err);
});
```


## Developing

### Build:
Expand Down
5 changes: 5 additions & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@
</feature>
</config-file>

<header-file src="src/ios/RMAppReceipt.h" />
<source-file src="src/ios/RMAppReceipt.m" />
<header-file src="src/ios/RMStore.h" />
<source-file src="src/ios/RMStore.m" />
<header-file src="src/ios/PaymentsPlugin.h" />
<source-file src="src/ios/PaymentsPlugin.m" />

<framework src="StoreKit.framework" />

<!-- openssl library -->
<framework src="src/ios/openssl.framework" custom="true" />
</platform>

<platform name="android">
Expand Down
1 change: 1 addition & 0 deletions src/ios/PaymentsPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@
- (void)buy:(CDVInvokedUrlCommand *)command;
- (void)restorePurchases:(CDVInvokedUrlCommand *)command;
- (void)getReceipt:(CDVInvokedUrlCommand *)command;
- (void)getReceiptBundle:(CDVInvokedUrlCommand *)command;

@end
52 changes: 52 additions & 0 deletions src/ios/PaymentsPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#import "PaymentsPlugin.h"
#import "RMStore.h"
#import "RMAppReceipt.h"

#define NILABLE(obj) ((obj) != nil ? (NSObject *)(obj) : (NSObject *)[NSNull null])

Expand Down Expand Up @@ -116,6 +117,57 @@ - (void)restorePurchases:(CDVInvokedUrlCommand *)command {
}];
}


- (void)getReceiptBundle:(CDVInvokedUrlCommand *)command {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSMutableArray *inAppPurchases = [NSMutableArray array];

@try {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'Z'";

for (id iap in [[RMAppReceipt bundleReceipt] inAppPurchases]) {
if ([iap isKindOfClass:[RMAppReceiptIAP class]]) {
RMAppReceiptIAP *receiptIAP = (RMAppReceiptIAP *)iap;
NSString *purchaseDateString = [formatter stringFromDate:receiptIAP.purchaseDate];
NSString *subscriptionExpirationDateString = [formatter stringFromDate:receiptIAP.subscriptionExpirationDate];
NSString *cancellationDateString = [formatter stringFromDate:receiptIAP.cancellationDate];
NSString *originalPurchaseDateString = [formatter stringFromDate:receiptIAP.originalPurchaseDate];

[inAppPurchases addObject:@{
@"productId": NILABLE(receiptIAP.productIdentifier),
@"purchaseDate": NILABLE(purchaseDateString),
@"originalPurchaseDate": NILABLE(originalPurchaseDateString),
@"subscriptionExpirationDate": NILABLE(subscriptionExpirationDateString),
@"cancellationDate": NILABLE(cancellationDateString),
@"transactionIdentifier": NILABLE(receiptIAP.transactionIdentifier),
@"originalTransactionIdentifier": NILABLE(receiptIAP.originalTransactionIdentifier),
@"quantity": NILABLE([NSNumber numberWithInteger:receiptIAP.quantity]),
@"webOrderLineItemID": NILABLE([NSNumber numberWithInteger:receiptIAP.webOrderLineItemID])
}];
}
}

[result setObject:inAppPurchases forKey:@"inAppPurchases"];
[result setValue:[[RMAppReceipt bundleReceipt] appVersion] forKey:@"appVersion"];
[result setValue:[[RMAppReceipt bundleReceipt] originalAppVersion] forKey:@"originalAppVersion"];
[result setValue:[[RMAppReceipt bundleReceipt] bundleIdentifier] forKey:@"bundleIdentifier"];

CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

@catch (NSException *exception) {
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION
messageAsString:[exception reason]];
[pluginResult setKeepCallbackAsBool:YES];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}

- (void)getReceipt:(CDVInvokedUrlCommand *)command {
[[RMStore defaultStore] refreshReceiptOnSuccess:^{
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
Expand Down
178 changes: 178 additions & 0 deletions src/ios/RMAppReceipt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
//
// RMAppReceipt.h
// RMStore
//
// Created by Hermes on 10/12/13.
// Copyright (c) 2013 Robot Media. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#import <Foundation/Foundation.h>

/** Represents the app receipt.
*/
__attribute__((availability(ios,introduced=7.0)))
@interface RMAppReceipt : NSObject

/** The app’s bundle identifier.

This corresponds to the value of CFBundleIdentifier in the Info.plist file.
*/
@property (nonatomic, strong, readonly) NSString *bundleIdentifier;

/** The bundle identifier as data, as contained in the receipt. Used to verifiy the receipt's hash.
@see verifyReceiptHash
*/
@property (nonatomic, strong, readonly) NSData *bundleIdentifierData;

/** The app’s version number. This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in OS X) in the Info.plist.
*/
@property (nonatomic, strong, readonly) NSString *appVersion;

/** An opaque value used as part of the SHA-1 hash.
*/
@property (nonatomic, strong, readonly) NSData *opaqueValue;

/** A SHA-1 hash, used to validate the receipt.
*/
@property (nonatomic, strong, readonly) NSData *receiptHash;

/** Array of in-app purchases contained in the receipt.
@see RMAppReceiptIAP
*/
@property (nonatomic, strong, readonly) NSArray *inAppPurchases;

/** The version of the app that was originally purchased. This corresponds to the value of CFBundleVersion (in iOS) or CFBundleShortVersionString (in OS X) in the Info.plist file when the purchase was originally made. In the sandbox environment, the value of this field is always “1.0”.
*/
@property (nonatomic, strong, readonly) NSString *originalAppVersion;

/** The date that the app receipt expires. Only for apps purchased through the Volume Purchase Program. If nil, the receipt does not expire. When validating a receipt, compare this date to the current date to determine whether the receipt is expired. Do not try to use this date to calculate any other information, such as the time remaining before expiration.
*/
@property (nonatomic, strong, readonly) NSDate *expirationDate;

/** Returns an initialized app receipt from the given data.
@param asn1Data ASN1 data
@return An initialized app receipt from the given data.
*/
- (instancetype)initWithASN1Data:(NSData*)asn1Data NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;

/** Returns whether there is an in-app purchase in the receipt for the given product.
@param productIdentifier The identifier of the product.
@return YES if there is an in-app purchase for the given product, NO otherwise.
*/
- (BOOL)containsInAppPurchaseOfProductIdentifier:(NSString*)productIdentifier;

/** Returns whether the receipt contains an active auto-renewable subscription for the given product identifier and for the given date.
@param productIdentifier The identifier of the auto-renewable subscription.
@param date The date in which the latest auto-renewable subscription should be active. If you are using the current date, you might not want to take it from the device in case the user has changed it.
@return YES if the latest auto-renewable subscription is active for the given date, NO otherwise.
@warning Auto-renewable subscription lapses are possible. If you are checking against the current date, you might want to deduct some time as tolerance.
@warning If this method fails Apple recommends to refresh the receipt and try again once.
*/
- (BOOL)containsActiveAutoRenewableSubscriptionOfProductIdentifier:(NSString *)productIdentifier forDate:(NSDate *)date;

/** Returns wheter the receipt hash corresponds to the device's GUID by calcuting the expected hash using the GUID, bundleIdentifierData and opaqueValue.
@return YES if the hash contained in the receipt corresponds to the device's GUID, NO otherwise.
*/
- (BOOL)verifyReceiptHash;

/**
Returns the app receipt contained in the bundle, if any and valid. Extracts the receipt in ASN1 from the PKCS #7 container, and then parses the ASN1 data into a RMAppReceipt instance. If an Apple Root certificate is available, it will also verify that the signature of the receipt is valid.
@return The app receipt contained in the bundle, or nil if there is no receipt or if it is invalid.
@see refreshReceipt
@see setAppleRootCertificateURL:
*/
+ (RMAppReceipt*)bundleReceipt;

/**
Sets the url of the Apple Root certificate that will be used to verifiy the signature of the bundle receipt. If none is provided, the resource AppleIncRootCertificate.cer will be used. If no certificate is available, no signature verification will be performed.
@param url The url of the Apple Root certificate.
*/
+ (void)setAppleRootCertificateURL:(NSURL*)url;

@end

/** Represents an in-app purchase in the app receipt.
*/
@interface RMAppReceiptIAP : NSObject

/** The number of items purchased. This value corresponds to the quantity property of the SKPayment object stored in the transaction’s payment property.
*/
@property (nonatomic, readonly) NSInteger quantity;

/** The product identifier of the item that was purchased. This value corresponds to the productIdentifier property of the SKPayment object stored in the transaction’s payment property.
*/
@property (nonatomic, strong, readonly) NSString *productIdentifier;

/**
The transaction identifier of the item that was purchased. This value corresponds to the transaction’s transactionIdentifier property.
*/
@property (nonatomic, strong, readonly) NSString *transactionIdentifier;

/** For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier.

This value corresponds to the original transaction’s transactionIdentifier property.

All receipts in a chain of renewals for an auto-renewable subscription have the same value for this field.
*/
@property (nonatomic, strong, readonly) NSString *originalTransactionIdentifier;

/** The date and time that the item was purchased. This value corresponds to the transaction’s transactionDate property.

For a transaction that restores a previous transaction, the purchase date is the date of the restoration. Use `originalPurchaseDate` to get the date of the original transaction.

In an auto-renewable subscription receipt, this is always the date when the subscription was purchased or renewed, regardles of whether the transaction has been restored
*/
@property (nonatomic, strong, readonly) NSDate *purchaseDate;

/** For a transaction that restores a previous transaction, the date of the original transaction.

This value corresponds to the original transaction’s transactionDate property.

In an auto-renewable subscription receipt, this indicates the beginning of the subscription period, even if the subscription has been renewed.
*/
@property (nonatomic, strong, readonly) NSDate *originalPurchaseDate;

/**
The expiration date for the subscription.

Only present for auto-renewable subscription receipts.
*/
@property (nonatomic, strong, readonly) NSDate *subscriptionExpirationDate;

/** For a transaction that was canceled by Apple customer support, the date of the cancellation.
*/
@property (nonatomic, strong, readonly) NSDate *cancellationDate;

/** The primary key for identifying subscription purchases.
*/
@property (nonatomic, readonly) NSInteger webOrderLineItemID;

/** Returns an initialized in-app purchase from the given data.
@param asn1Data ASN1 data
@return An initialized in-app purchase from the given data.
*/
- (instancetype)initWithASN1Data:(NSData*)asn1Data NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;

/** Returns whether the auto renewable subscription is active for the given date.
@param date The date in which the auto-renewable subscription should be active. If you are using the current date, you might not want to take it from the device in case the user has changed it.
@return YES if the auto-renewable subscription is active for the given date, NO otherwise.
@warning Auto-renewable subscription lapses are possible. If you are checking against the current date, you might want to deduct some time as tolerance.
@warning If this method fails Apple recommends to refresh the receipt and try again once.
*/
- (BOOL)isActiveAutoRenewableSubscriptionForDate:(NSDate*)date;

@end
Loading