diff --git a/atom/browser/api/atom_api_in_app_purchase.cc b/atom/browser/api/atom_api_in_app_purchase.cc new file mode 100644 index 000000000000..4f507fb86e41 --- /dev/null +++ b/atom/browser/api/atom_api_in_app_purchase.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2017 Amaplex Software, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_in_app_purchase.h" + +#include +#include +#include + +#include "atom/common/native_mate_converters/callback.h" +#include "native_mate/dictionary.h" + +#include "atom/common/node_includes.h" + +namespace mate { + +template <> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const in_app_purchase::Payment& payment) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.SetHidden("simple", true); + dict.Set("productIdentifier", payment.productIdentifier); + dict.Set("quantity", payment.quantity); + return dict.GetHandle(); + } +}; + +template <> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const in_app_purchase::Transaction& val) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.SetHidden("simple", true); + dict.Set("transactionIdentifier", val.transactionIdentifier); + dict.Set("transactionDate", val.transactionDate); + dict.Set("originalTransactionIdentifier", + val.originalTransactionIdentifier); + dict.Set("transactionState", val.transactionState); + dict.Set("errorCode", val.errorCode); + dict.Set("errorMessage", val.errorMessage); + dict.Set("payment", val.payment); + return dict.GetHandle(); + } +}; + +} // namespace mate + +namespace atom { + +namespace api { + +#if defined(OS_MACOSX) +// static +mate::Handle InAppPurchase::Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new InAppPurchase(isolate)); +} + +// static +void InAppPurchase::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + prototype->SetClassName(mate::StringToV8(isolate, "InAppPurchase")); + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .SetMethod("canMakePayments", &in_app_purchase::CanMakePayments) + .SetMethod("getReceiptURL", &in_app_purchase::GetReceiptURL) + .SetMethod("purchaseProduct", &InAppPurchase::PurchaseProduct); +} + +InAppPurchase::InAppPurchase(v8::Isolate* isolate) { + Init(isolate); +} + +InAppPurchase::~InAppPurchase() { +} + +void InAppPurchase::PurchaseProduct(const std::string& product_id, + mate::Arguments* args) { + int quantity = 1; + in_app_purchase::InAppPurchaseCallback callback; + args->GetNext(&quantity); + args->GetNext(&callback); + in_app_purchase::PurchaseProduct(product_id, quantity, callback); +} + +void InAppPurchase::OnTransactionsUpdated( + const std::vector& transactions) { + Emit("transactions-updated", transactions); +} +#endif + +} // namespace api + +} // namespace atom + +namespace { + +using atom::api::InAppPurchase; + +void Initialize(v8::Local exports, + v8::Local unused, + v8::Local context, + void* priv) { +#if defined(OS_MACOSX) + v8::Isolate* isolate = context->GetIsolate(); + mate::Dictionary dict(isolate, exports); + dict.Set("inAppPurchase", InAppPurchase::Create(isolate)); + dict.Set("InAppPurchase", + InAppPurchase::GetConstructor(isolate)->GetFunction()); +#endif +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_in_app_purchase, Initialize) diff --git a/atom/browser/api/atom_api_in_app_purchase.h b/atom/browser/api/atom_api_in_app_purchase.h new file mode 100644 index 000000000000..3646c9b8e8f7 --- /dev/null +++ b/atom/browser/api/atom_api_in_app_purchase.h @@ -0,0 +1,46 @@ +// Copyright (c) 2017 Amaplex Software, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_API_ATOM_API_IN_APP_PURCHASE_H_ +#define ATOM_BROWSER_API_ATOM_API_IN_APP_PURCHASE_H_ + +#include +#include + +#include "atom/browser/api/event_emitter.h" +#include "atom/browser/mac/in_app_purchase.h" +#include "atom/browser/mac/in_app_purchase_observer.h" +#include "native_mate/handle.h" + +namespace atom { + +namespace api { + +class InAppPurchase: public mate::EventEmitter, + public in_app_purchase::TransactionObserver { + public: + static mate::Handle Create(v8::Isolate* isolate); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + protected: + explicit InAppPurchase(v8::Isolate* isolate); + ~InAppPurchase() override; + + void PurchaseProduct(const std::string& product_id, mate::Arguments* args); + + // TransactionObserver: + void OnTransactionsUpdated( + const std::vector& transactions) override; + + private: + DISALLOW_COPY_AND_ASSIGN(InAppPurchase); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_IN_APP_PURCHASE_H_ diff --git a/atom/browser/mac/in_app_purchase.h b/atom/browser/mac/in_app_purchase.h new file mode 100644 index 000000000000..d014744d55c4 --- /dev/null +++ b/atom/browser/mac/in_app_purchase.h @@ -0,0 +1,30 @@ +// Copyright (c) 2017 Amaplex Software, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_MAC_IN_APP_PURCHASE_H_ +#define ATOM_BROWSER_MAC_IN_APP_PURCHASE_H_ + +#include + +#include "base/callback.h" + +namespace in_app_purchase { + +// --------------------------- Typedefs --------------------------- + +typedef base::Callback InAppPurchaseCallback; + +// --------------------------- Functions --------------------------- + +bool CanMakePayments(void); + +std::string GetReceiptURL(void); + +void PurchaseProduct(const std::string& productID, + int quantity, + const InAppPurchaseCallback& callback); + +} // namespace in_app_purchase + +#endif // ATOM_BROWSER_MAC_IN_APP_PURCHASE_H_ diff --git a/atom/browser/mac/in_app_purchase.mm b/atom/browser/mac/in_app_purchase.mm new file mode 100644 index 000000000000..f9f98332da19 --- /dev/null +++ b/atom/browser/mac/in_app_purchase.mm @@ -0,0 +1,157 @@ +// Copyright (c) 2017 Amaplex Software, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/mac/in_app_purchase.h" + +#include "base/bind.h" +#include "base/strings/sys_string_conversions.h" +#include "content/public/browser/browser_thread.h" + +#import +#import + +// ============================================================================ +// InAppPurchase +// ============================================================================ + +// --------------------------------- Interface -------------------------------- + +@interface InAppPurchase : NSObject { + @private + in_app_purchase::InAppPurchaseCallback callback_; + NSInteger quantity_; +} + +- (id)initWithCallback:(const in_app_purchase::InAppPurchaseCallback&)callback + quantity:(NSInteger)quantity; + +- (void)purchaseProduct:(NSString*)productID; + +@end + +// ------------------------------- Implementation ----------------------------- + +@implementation InAppPurchase + +/** + * Init with a callback. + * + * @param callback - The callback that will be called when the payment is added + * to the queue. + */ +- (id)initWithCallback:(const in_app_purchase::InAppPurchaseCallback&)callback + quantity:(NSInteger)quantity { + if ((self = [super init])) { + callback_ = callback; + quantity_ = quantity; + } + + return self; +} + +/** + * Start the in-app purchase process. + * + * @param productID - The id of the product to purchase (the id of + * com.example.app.product1 is product1). + */ +- (void)purchaseProduct:(NSString*)productID { + // Retrieve the product information. (The products request retrieves, + // information about valid products along with a list of the invalid product + // identifiers, and then calls its delegate to process the result). + SKProductsRequest* productsRequest; + productsRequest = [[SKProductsRequest alloc] + initWithProductIdentifiers:[NSSet setWithObject:productID]]; + + productsRequest.delegate = self; + [productsRequest start]; +} + +/** + * Process product informations and start the payment. + * + * @param request - The product request. + * @param response - The informations about the list of products. + */ +- (void)productsRequest:(SKProductsRequest*)request + didReceiveResponse:(SKProductsResponse*)response { + // Release request object. + [request release]; + + // Get the first product. + NSArray* products = response.products; + SKProduct* product = [products count] == 1 ? [products firstObject] : nil; + + // Return if the product is not found or invalid. + if (product == nil) { + [self runCallback:false]; + return; + } + + // Start the payment process. + [self checkout:product]; +} + +/** + * Submit a payment request to the App Store. + * + * @param product - The product to purchase. + */ +- (void)checkout:(SKProduct*)product { + // Add the payment to the transaction queue. (The observer will be called + // when the transaction is finished). + SKMutablePayment* payment = [SKMutablePayment paymentWithProduct:product]; + payment.quantity = quantity_; + + [[SKPaymentQueue defaultQueue] addPayment:payment]; + + // Notify that the payment has been added to the queue with success. + [self runCallback:true]; +} + +/** + * Submit a payment request to the App Store. + * + * @param product - The product to purchase. + */ +- (void)runCallback:(bool)isProductValid { + if (callback_) { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(callback_, isProductValid)); + } + // Release this delegate. + [self release]; +} + +@end + +// ============================================================================ +// C++ in_app_purchase +// ============================================================================ + +namespace in_app_purchase { + +bool CanMakePayments() { + return [SKPaymentQueue canMakePayments]; +} + +std::string GetReceiptURL() { + NSURL* receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; + if (receiptURL != nil) { + return [[receiptURL absoluteString] UTF8String]; + } else { + return ""; + } +} + +void PurchaseProduct(const std::string& productID, + int quantity, + const InAppPurchaseCallback& callback) { + auto* iap = + [[InAppPurchase alloc] initWithCallback:callback quantity:quantity]; + + [iap purchaseProduct:base::SysUTF8ToNSString(productID)]; +} + +} // namespace in_app_purchase diff --git a/atom/browser/mac/in_app_purchase_observer.h b/atom/browser/mac/in_app_purchase_observer.h new file mode 100644 index 000000000000..d2d0be2b0ae9 --- /dev/null +++ b/atom/browser/mac/in_app_purchase_observer.h @@ -0,0 +1,59 @@ +// Copyright (c) 2017 Amaplex Software, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_MAC_IN_APP_PURCHASE_OBSERVER_H_ +#define ATOM_BROWSER_MAC_IN_APP_PURCHASE_OBSERVER_H_ + +#include +#include + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" + +#if defined(__OBJC__) +@class InAppTransactionObserver; +#else // __OBJC__ +class InAppTransactionObserver; +#endif // __OBJC__ + +namespace in_app_purchase { + +// --------------------------- Structures --------------------------- + +struct Payment { + std::string productIdentifier = ""; + int quantity = 1; +}; + +struct Transaction { + std::string transactionIdentifier = ""; + std::string transactionDate = ""; + std::string originalTransactionIdentifier = ""; + int errorCode = 0; + std::string errorMessage = ""; + std::string transactionState = ""; + Payment payment; +}; + +// --------------------------- Classes --------------------------- + +class TransactionObserver { + public: + TransactionObserver(); + virtual ~TransactionObserver(); + + virtual void OnTransactionsUpdated( + const std::vector& transactions) = 0; + + private: + InAppTransactionObserver* obeserver_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(TransactionObserver); +}; + +} // namespace in_app_purchase + +#endif // ATOM_BROWSER_MAC_IN_APP_PURCHASE_OBSERVER_H_ diff --git a/atom/browser/mac/in_app_purchase_observer.mm b/atom/browser/mac/in_app_purchase_observer.mm new file mode 100644 index 000000000000..ef21228ad6e1 --- /dev/null +++ b/atom/browser/mac/in_app_purchase_observer.mm @@ -0,0 +1,188 @@ +// Copyright (c) 2017 Amaplex Software, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/mac/in_app_purchase_observer.h" + +#include "base/bind.h" +#include "base/strings/sys_string_conversions.h" +#include "content/public/browser/browser_thread.h" + +#import +#import + +// ============================================================================ +// InAppTransactionObserver +// ============================================================================ + +namespace { + +using InAppTransactionCallback = + base::RepeatingCallback< + void(const std::vector&)>; + +} // namespace + +@interface InAppTransactionObserver : NSObject { + @private + InAppTransactionCallback callback_; +} + +- (id)initWithCallback:(const InAppTransactionCallback&)callback; + +@end + +@implementation InAppTransactionObserver + +/** + * Init with a callback. + * + * @param callback - The callback that will be called for each transaction + * update. + */ +- (id)initWithCallback:(const InAppTransactionCallback&)callback { + if ((self = [super init])) { + callback_ = callback; + + [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; + } + + return self; +} + +/** + * Cleanup. + */ +- (void)dealloc { + [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; + [super dealloc]; +} + +/** + * Run the callback in the browser thread. + * + * @param transaction - The transaction to pass to the callback. + */ +- (void)runCallback:(NSArray*)transactions { + // Convert the transaction. + std::vector converted; + converted.reserve([transactions count]); + for (SKPaymentTransaction* transaction in transactions) { + converted.push_back([self skPaymentTransactionToStruct:transaction]); + } + + // Send the callback to the browser thread. + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, base::Bind(callback_, converted)); +} + +/** + * Convert an NSDate to ISO String. + * + * @param date - The date to convert. + */ +- (NSString*)dateToISOString:(NSDate*)date { + NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init]; + NSLocale* enUSPOSIXLocale = + [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + [dateFormatter setLocale:enUSPOSIXLocale]; + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"]; + + return [dateFormatter stringFromDate:date]; +} + +/** + * Convert a SKPayment object to a Payment structure. + * + * @param payment - The SKPayment object to convert. + */ +- (in_app_purchase::Payment)skPaymentToStruct:(SKPayment*)payment { + in_app_purchase::Payment paymentStruct; + + if (payment.productIdentifier != nil) { + paymentStruct.productIdentifier = [payment.productIdentifier UTF8String]; + } + + if (payment.quantity >= 1) { + paymentStruct.quantity = (int)payment.quantity; + } + + return paymentStruct; +} + +/** + * Convert a SKPaymentTransaction object to a Transaction structure. + * + * @param transaction - The SKPaymentTransaction object to convert. + */ +- (in_app_purchase::Transaction)skPaymentTransactionToStruct: + (SKPaymentTransaction*)transaction { + in_app_purchase::Transaction transactionStruct; + + if (transaction.transactionIdentifier != nil) { + transactionStruct.transactionIdentifier = + [transaction.transactionIdentifier UTF8String]; + } + + if (transaction.transactionDate != nil) { + transactionStruct.transactionDate = + [[self dateToISOString:transaction.transactionDate] UTF8String]; + } + + if (transaction.originalTransaction != nil) { + transactionStruct.originalTransactionIdentifier = + [transaction.originalTransaction.transactionIdentifier UTF8String]; + } + + if (transaction.error != nil) { + transactionStruct.errorCode = (int)transaction.error.code; + transactionStruct.errorMessage = + [[transaction.error localizedDescription] UTF8String]; + } + + if (transaction.transactionState < 5) { + transactionStruct.transactionState = [[@[ + @"purchasing", @"purchased", @"failed", @"restored", @"deferred" + ] objectAtIndex:transaction.transactionState] UTF8String]; + } + + if (transaction.payment != nil) { + transactionStruct.payment = [self skPaymentToStruct:transaction.payment]; + } + + return transactionStruct; +} + +#pragma mark - +#pragma mark SKPaymentTransactionObserver methods + +/** + * Executed when a transaction is updated. + * + * @param queue - The payment queue. + * @param transactions - The list of transactions updated. + */ +- (void)paymentQueue:(SKPaymentQueue*)queue + updatedTransactions:(NSArray*)transactions { + [self runCallback:transactions]; +} + +@end + +// ============================================================================ +// C++ in_app_purchase +// ============================================================================ + +namespace in_app_purchase { + +TransactionObserver::TransactionObserver() : weak_ptr_factory_(this) { + obeserver_ = [[InAppTransactionObserver alloc] + initWithCallback:base::Bind(&TransactionObserver::OnTransactionsUpdated, + weak_ptr_factory_.GetWeakPtr())]; +} + +TransactionObserver::~TransactionObserver() { + [obeserver_ release]; +} + +} // namespace in_app_purchase diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index cc4bc554ef9d..f184bcf8da35 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -40,6 +40,7 @@ REFERENCE_MODULE(atom_browser_desktop_capturer); REFERENCE_MODULE(atom_browser_dialog); REFERENCE_MODULE(atom_browser_download_item); REFERENCE_MODULE(atom_browser_global_shortcut); +REFERENCE_MODULE(atom_browser_in_app_purchase); REFERENCE_MODULE(atom_browser_menu); REFERENCE_MODULE(atom_browser_net); REFERENCE_MODULE(atom_browser_power_monitor); diff --git a/docs/README.md b/docs/README.md index 2ce70ca17250..5e97153cf0b5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -65,6 +65,7 @@ an issue: * [contentTracing](api/content-tracing.md) * [dialog](api/dialog.md) * [globalShortcut](api/global-shortcut.md) +* [inAppPurchase](api/in-app-purchase.md) * [ipcMain](api/ipc-main.md) * [Menu](api/menu.md) * [MenuItem](api/menu-item.md) diff --git a/docs/api/in-app-purchase.md b/docs/api/in-app-purchase.md new file mode 100644 index 000000000000..f9aaaa90ddd7 --- /dev/null +++ b/docs/api/in-app-purchase.md @@ -0,0 +1,37 @@ +# inAppPurchase + +> In-app purchases on Mac App Store. + +Process: [Main](../glossary.md#main-process) + +## Events + +The `inAppPurchase` module emits the following events: + +### Event: 'transactions-updated' + +Emitted when one or more transactions have been updated. + +Returns: + +* `event` Event +* `transactions` ([Transaction[]](structures/transaction.md) - Array of transactions. + +## Methods + +The `inAppPurchase` module has the following methods: + +### `inAppPurchase.purchaseProduct(productID, quantity, callback)` + +* `productID` String - The id of the product to purchase. (the id of `com.example.app.product1` is `product1`). +* `quantity` Integer (optional) - The number of items the user wants to purchase. +* `callback` Function (optional) - The callback called when the payment is added to the PaymentQueue. (You should add a listener with `inAppPurchase.addTransactionsListener` to get the transaction status). + * `isProductValid` Boolean - Determine if the product is valid and added to the payment queue. + +### `inAppPurchase.canMakePayments()` + +Returns `true` if the user can make a payment and `false` otherwise. + +### `inAppPurchase.getReceiptURL()` + +Returns `String`, the path to the receipt. diff --git a/docs/api/structures/transaction.md b/docs/api/structures/transaction.md new file mode 100644 index 000000000000..78ee4e8ad0c0 --- /dev/null +++ b/docs/api/structures/transaction.md @@ -0,0 +1,11 @@ +# Transaction Object + +* `transactionIdentifier` String +* `transactionDate` String +* `originalTransactionIdentifier` String +* `transactionState` String - The transaction sate (`"purchasing"`, `"purchased"`, `"failed"`, `"restored"`, or `"deferred"`) +* `errorCode` Integer +* `errorMessage` String +* `payment` Object + * `productIdentifier` String + * `quantity` Integer diff --git a/electron.gyp b/electron.gyp index 4f3554d88a9a..fcd003aba5df 100644 --- a/electron.gyp +++ b/electron.gyp @@ -591,6 +591,7 @@ '$(SDKROOT)/System/Library/Frameworks/Security.framework', '$(SDKROOT)/System/Library/Frameworks/SecurityInterface.framework', '$(SDKROOT)/System/Library/Frameworks/ServiceManagement.framework', + '$(SDKROOT)/System/Library/Frameworks/StoreKit.framework', ], }, 'mac_bundle': 1, diff --git a/filenames.gypi b/filenames.gypi index e9f0f43b97ec..40babc685d36 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -20,6 +20,7 @@ 'lib/browser/api/exports/electron.js', 'lib/browser/api/global-shortcut.js', 'lib/browser/api/ipc-main.js', + 'lib/browser/api/in-app-purchase.js', 'lib/browser/api/menu-item-roles.js', 'lib/browser/api/menu-item.js', 'lib/browser/api/menu.js', @@ -120,6 +121,8 @@ 'atom/browser/api/atom_api_download_item.h', 'atom/browser/api/atom_api_global_shortcut.cc', 'atom/browser/api/atom_api_global_shortcut.h', + 'atom/browser/api/atom_api_in_app_purchase.cc', + 'atom/browser/api/atom_api_in_app_purchase.h', 'atom/browser/api/atom_api_menu.cc', 'atom/browser/api/atom_api_menu.h', 'atom/browser/api/atom_api_menu_mac.h', @@ -231,6 +234,10 @@ 'atom/browser/mac/atom_application_delegate.mm', 'atom/browser/mac/dict_util.h', 'atom/browser/mac/dict_util.mm', + 'atom/browser/mac/in_app_purchase.h', + 'atom/browser/mac/in_app_purchase.mm', + 'atom/browser/mac/in_app_purchase_observer.h', + 'atom/browser/mac/in_app_purchase_observer.mm', 'atom/browser/native_browser_view.cc', 'atom/browser/native_browser_view.h', 'atom/browser/native_browser_view_mac.h', diff --git a/lib/browser/api/in-app-purchase.js b/lib/browser/api/in-app-purchase.js new file mode 100644 index 000000000000..48a35d2a4d42 --- /dev/null +++ b/lib/browser/api/in-app-purchase.js @@ -0,0 +1,14 @@ +'use strict' + +if (process.platform !== 'darwin') { + throw new Error('The inAppPurchase module can only be used on macOS') +} + +const {EventEmitter} = require('events') +const {inAppPurchase, InAppPurchase} = process.atomBinding('in_app_purchase') + +// inAppPurchase is an EventEmitter. +Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype) +EventEmitter.call(inAppPurchase) + +module.exports = inAppPurchase diff --git a/lib/browser/api/module-list.js b/lib/browser/api/module-list.js index d8b20c5bec19..e6f398b47d00 100644 --- a/lib/browser/api/module-list.js +++ b/lib/browser/api/module-list.js @@ -8,6 +8,7 @@ module.exports = [ {name: 'dialog', file: 'dialog'}, {name: 'globalShortcut', file: 'global-shortcut'}, {name: 'ipcMain', file: 'ipc-main'}, + {name: 'inAppPurchase', file: 'in-app-purchase'}, {name: 'Menu', file: 'menu'}, {name: 'MenuItem', file: 'menu-item'}, {name: 'net', file: 'net'}, diff --git a/spec/api-in-app-purchase-spec.js b/spec/api-in-app-purchase-spec.js new file mode 100644 index 000000000000..c47c8d752ee1 --- /dev/null +++ b/spec/api-in-app-purchase-spec.js @@ -0,0 +1,33 @@ +'use strict' + +const assert = require('assert') + +const {remote} = require('electron') + +describe('inAppPurchase module', () => { + if (process.platform !== 'darwin') return + + const {inAppPurchase} = remote + + it('canMakePayments() does not throw', () => { + inAppPurchase.canMakePayments() + }) + + it('getReceiptURL() returns receipt URL', () => { + assert.ok(inAppPurchase.getReceiptURL().endsWith('_MASReceipt/receipt')) + }) + + it('purchaseProduct() fails when buying invalid product', (done) => { + inAppPurchase.purchaseProduct('non-exist', 1, (success) => { + assert.ok(!success) + done() + }) + }) + + it('purchaseProduct() accepts optional arguments', (done) => { + inAppPurchase.purchaseProduct('non-exist', () => { + inAppPurchase.purchaseProduct('non-exist', 1) + done() + }) + }) +})