From f3ae5661641d78150473d0c81fd185d6892d38fe Mon Sep 17 00:00:00 2001 From: Adrien Fery Date: Thu, 30 Nov 2017 14:27:33 +0100 Subject: [PATCH 01/17] Add in-app purchase for MacOS --- atom/browser/api/atom_api_in_app_purchase.cc | 63 +++++++ atom/browser/api/atom_api_in_app_purchase.h | 36 ++++ atom/browser/in_app_purchase.h | 30 +++ atom/browser/in_app_purchase_mac.mm | 149 +++++++++++++++ atom/browser/in_app_purchase_observer.h | 41 +++++ atom/browser/in_app_purchase_observer_mac.mm | 182 +++++++++++++++++++ atom/common/node_bindings.cc | 1 + docs/api/in-app-purchase.md | 36 ++++ electron.gyp | 1 + filenames.gypi | 7 + lib/browser/api/in-app-purchase.js | 46 +++++ lib/browser/api/module-list.js | 1 + 12 files changed, 593 insertions(+) create mode 100644 atom/browser/api/atom_api_in_app_purchase.cc create mode 100644 atom/browser/api/atom_api_in_app_purchase.h create mode 100644 atom/browser/in_app_purchase.h create mode 100644 atom/browser/in_app_purchase_mac.mm create mode 100644 atom/browser/in_app_purchase_observer.h create mode 100644 atom/browser/in_app_purchase_observer_mac.mm create mode 100644 docs/api/in-app-purchase.md create mode 100644 lib/browser/api/in-app-purchase.js 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 00000000000..21bad43ee04 --- /dev/null +++ b/atom/browser/api/atom_api_in_app_purchase.cc @@ -0,0 +1,63 @@ +// 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 +#include +#include + +#include "atom/browser/api/atom_api_in_app_purchase.h" +#include "atom/common/native_mate_converters/callback.h" +#include "native_mate/dictionary.h" + +#include "atom/common/node_includes.h" + +namespace mate { + +v8::Local Converter::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(); +} + +v8::Local Converter::ToV8( + v8::Isolate* isolate, + const in_app_purchase::Transaction& transaction) { + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + dict.SetHidden("simple", true); + dict.Set("transactionIdentifier", transaction.transactionIdentifier); + dict.Set("transactionDate", transaction.transactionDate); + dict.Set("originalTransactionIdentifier", + transaction.originalTransactionIdentifier); + dict.Set("transactionState", transaction.transactionState); + + dict.Set("errorCode", transaction.errorCode); + dict.Set("errorMessage", transaction.errorMessage); + + return dict.GetHandle(); +} +} // namespace mate + +namespace { + +void Initialize(v8::Local exports, + v8::Local unused, + v8::Local context, + void* priv) { + mate::Dictionary dict(context->GetIsolate(), exports); +#if defined(OS_MACOSX) + dict.SetMethod("canMakePayments", &in_app_purchase::CanMakePayments); + dict.SetMethod("getReceiptURL", &in_app_purchase::GetReceiptURL); + dict.SetMethod("purchaseProduct", &in_app_purchase::PurchaseProduct); + dict.SetMethod("addTransactionListener", + &in_app_purchase::AddTransactionObserver); +#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 00000000000..b1feb7ec266 --- /dev/null +++ b/atom/browser/api/atom_api_in_app_purchase.h @@ -0,0 +1,36 @@ +// 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 "atom/browser/in_app_purchase.h" +#include "atom/browser/in_app_purchase_observer.h" +#include "native_mate/dictionary.h" + +namespace mate { + +template <> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const in_app_purchase::Payment& val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + in_app_purchase::Payment* out); +}; + +template <> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const in_app_purchase::Transaction& val); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + in_app_purchase::Transaction* out); +}; + +} // namespace mate + +#endif // ATOM_BROWSER_API_ATOM_API_IN_APP_PURCHASE_H_ diff --git a/atom/browser/in_app_purchase.h b/atom/browser/in_app_purchase.h new file mode 100644 index 00000000000..902d70deacf --- /dev/null +++ b/atom/browser/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_UI_IN_APP_PURCHASE_H_ +#define ATOM_BROWSER_UI_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, + const int quantity, + const InAppPurchaseCallback& callback); + +} // namespace in_app_purchase + +#endif // ATOM_BROWSER_UI_IN_APP_PURCHASE_H_ diff --git a/atom/browser/in_app_purchase_mac.mm b/atom/browser/in_app_purchase_mac.mm new file mode 100644 index 00000000000..9e859e7faac --- /dev/null +++ b/atom/browser/in_app_purchase_mac.mm @@ -0,0 +1,149 @@ +// 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/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 { + // 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 { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(callback_, isProductValid)); +} + +@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, + const 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/in_app_purchase_observer.h b/atom/browser/in_app_purchase_observer.h new file mode 100644 index 00000000000..450ee286b34 --- /dev/null +++ b/atom/browser/in_app_purchase_observer.h @@ -0,0 +1,41 @@ +// 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_UI_IN_APP_PURCHASE_OBSERVER_H_ +#define ATOM_BROWSER_UI_IN_APP_PURCHASE_OBSERVER_H_ + +#include + +#include "base/callback.h" + +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 = ""; +}; + +// --------------------------- Typedefs --------------------------- + +typedef base::RepeatingCallback + InAppTransactionCallback; + +// --------------------------- Functions --------------------------- + +void AddTransactionObserver(const InAppTransactionCallback& callback); + +} // namespace in_app_purchase + +#endif // ATOM_BROWSER_UI_IN_APP_PURCHASE_OBSERVER_H_ diff --git a/atom/browser/in_app_purchase_observer_mac.mm b/atom/browser/in_app_purchase_observer_mac.mm new file mode 100644 index 00000000000..0ec1b9bb613 --- /dev/null +++ b/atom/browser/in_app_purchase_observer_mac.mm @@ -0,0 +1,182 @@ +// 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/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 +// ============================================================================ + +@interface InAppTransactionObserver : NSObject { + @private + in_app_purchase::InAppTransactionCallback callback_; +} + +- (id)initWithCallback: + (const in_app_purchase::InAppTransactionCallback&)callback; + +@end + +@implementation InAppTransactionObserver + +/** + * Init with a callback. + * + * @param callback - The callback that will be called for each transaction + * update. + */ +- (id)initWithCallback: + (const in_app_purchase::InAppTransactionCallback&)callback { + if ((self = [super init])) { + callback_ = callback; + + [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; + } + + return self; +} + +/** + * Run the callback in the browser thread. + * + * @param transaction - The transaction to pass to the callback. + */ +- (void)runCallback:(SKPaymentTransaction*)transaction { + if (transaction == nil) { + return; + } + + // Convert the payment. + in_app_purchase::Payment paymentStruct; + paymentStruct = [self skPaymentToStruct:transaction.payment]; + + // Convert the transaction. + in_app_purchase::Transaction transactionStruct; + transactionStruct = [self skPaymentTransactionToStruct:transaction]; + + // Send the callback to the browser thread. + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(callback_, paymentStruct, transactionStruct)); +} + +/** + * 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 == nil) { + return 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 == nil) { + return 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 = [[@[ + @"SKPaymentTransactionStatePurchasing", + @"SKPaymentTransactionStatePurchased", @"SKPaymentTransactionStateFailed", + @"SKPaymentTransactionStateRestored", + @"SKPaymentTransactionStateDeferred" + ] objectAtIndex:transaction.transactionState] UTF8String]; + } + + 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 { + for (SKPaymentTransaction* transaction in transactions) { + [self runCallback:transaction]; + } +} + +@end + +// ============================================================================ +// C++ in_app_purchase +// ============================================================================ + +namespace in_app_purchase { + +void AddTransactionObserver(const InAppTransactionCallback& callback) { + [[InAppTransactionObserver alloc] initWithCallback:callback]; +} + +} // namespace in_app_purchase diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index cc4bc554ef9..f184bcf8da3 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/api/in-app-purchase.md b/docs/api/in-app-purchase.md new file mode 100644 index 00000000000..effbf22af0f --- /dev/null +++ b/docs/api/in-app-purchase.md @@ -0,0 +1,36 @@ +# inAppPurchase _macOS_ + +Your application should add a listener before to purchase a product. If there are no listener attached to the queue, the payment queue does not synchronize its list of pending transactions with the Apple App Store. + +## Methods + +The `inAppPurchase` module has the following methods: + +### `inAppPurchase.addTransactionListener(listener)` + +* `listener` Function - Called when transactions are updated by the payment queue. + * `payment` Object + * `productIdentifier` String + * `quantity` Integer + * `transaction` Object + * `transactionIdentifier` String + * `transactionDate` String + * `originalTransactionIdentifier` String + * `transactionState` String - The transaction sate (`"SKPaymentTransactionStatePurchasing"`, `"SKPaymentTransactionStatePurchased"`, `"SKPaymentTransactionStateFailed"`, `"SKPaymentTransactionStateRestored"`, or `"SKPaymentTransactionStateDeferred"`) + * `errorCode` Integer + * `errorMessage` String + + +### `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. \ No newline at end of file diff --git a/electron.gyp b/electron.gyp index 4f3554d88a9..fcd003aba5d 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 e9f0f43b97e..80eaffaa85a 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', @@ -184,6 +187,10 @@ 'atom/browser/atom_browser_context.h', 'atom/browser/atom_download_manager_delegate.cc', 'atom/browser/atom_download_manager_delegate.h', + 'atom/browser/in_app_purchase.h', + 'atom/browser/in_app_purchase_mac.mm', + 'atom/browser/in_app_purchase_observer.h', + 'atom/browser/in_app_purchase_observer_mac.mm', 'atom/browser/atom_browser_main_parts.cc', 'atom/browser/atom_browser_main_parts.h', 'atom/browser/atom_browser_main_parts_mac.mm', diff --git a/lib/browser/api/in-app-purchase.js b/lib/browser/api/in-app-purchase.js new file mode 100644 index 00000000000..a0585e80dd9 --- /dev/null +++ b/lib/browser/api/in-app-purchase.js @@ -0,0 +1,46 @@ +'use strict' + +const binding = process.atomBinding('in_app_purchase') +const v8Util = process.atomBinding('v8_util') + +module.exports = { + + canMakePayments: function() { + return binding.canMakePayments(); + }, + + getReceiptURL: function() { + return binding.getReceiptURL(); + }, + + purchaseProduct: function(productID, quantity, callback) { + + if (typeof productID !== 'string') { + throw new TypeError('productID must be a string') + } + + if (typeof quantity !== 'number') { + quantity = 1 + } + + if (typeof callback !== 'function') { + callback = function() {}; + } + + return binding.purchaseProduct(productID, quantity, callback) + }, + + addTransactionListener: function(callback) { + + if (typeof callback !== 'function') { + throw new TypeError('callback must be a function') + } + return binding.addTransactionListener(callback) + } +} + + // Mark standard asynchronous functions. + v8Util.setHiddenValue( + module.exports.purchaseProduct, 'asynchronous', true) +v8Util.setHiddenValue( + module.exports.addTransactionListener, 'asynchronous', true) diff --git a/lib/browser/api/module-list.js b/lib/browser/api/module-list.js index d8b20c5bec1..e6f398b47d0 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'}, From 329fddaed13d14fce9879164d3b9a51ff6198149 Mon Sep 17 00:00:00 2001 From: Adrien Fery Date: Sat, 2 Dec 2017 10:49:46 +0100 Subject: [PATCH 02/17] Put in-app purchase files under macOS check --- filenames.gypi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/filenames.gypi b/filenames.gypi index 80eaffaa85a..c2942c951a3 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -733,6 +733,15 @@ 'atom/browser/osr/osr_view_proxy.h', ], }], # enable_osr==1 + ['OS=="win"', { + 'app_sources': [ + 'atom/browser/in_app_purchase.h', + 'atom/browser/in_app_purchase_mac.mm', + 'atom/browser/in_app_purchase_observer.h', + 'atom/browser/in_app_purchase_observer_mac.mm' + ], + }], # OS=="mac" + ], }, } From eb2520488f8660bfaf8b6baf4adf89bb1f49a81d Mon Sep 17 00:00:00 2001 From: Adrien Fery Date: Sat, 2 Dec 2017 10:59:13 +0100 Subject: [PATCH 03/17] Adapt code style --- atom/browser/in_app_purchase.h | 6 +++--- atom/browser/in_app_purchase_mac.mm | 6 ++++-- atom/browser/in_app_purchase_observer.h | 6 +++--- lib/browser/api/in-app-purchase.js | 17 +++++------------ 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/atom/browser/in_app_purchase.h b/atom/browser/in_app_purchase.h index 902d70deacf..d4957e8c97f 100644 --- a/atom/browser/in_app_purchase.h +++ b/atom/browser/in_app_purchase.h @@ -2,8 +2,8 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#ifndef ATOM_BROWSER_UI_IN_APP_PURCHASE_H_ -#define ATOM_BROWSER_UI_IN_APP_PURCHASE_H_ +#ifndef ATOM_BROWSER_IN_APP_PURCHASE_H_ +#define ATOM_BROWSER_IN_APP_PURCHASE_H_ #include @@ -27,4 +27,4 @@ void PurchaseProduct(const std::string& productID, } // namespace in_app_purchase -#endif // ATOM_BROWSER_UI_IN_APP_PURCHASE_H_ +#endif // ATOM_BROWSER_IN_APP_PURCHASE_H_ diff --git a/atom/browser/in_app_purchase_mac.mm b/atom/browser/in_app_purchase_mac.mm index 9e859e7faac..5f4c994a0bc 100644 --- a/atom/browser/in_app_purchase_mac.mm +++ b/atom/browser/in_app_purchase_mac.mm @@ -112,8 +112,10 @@ * @param product - The product to purchase. */ - (void)runCallback:(bool)isProductValid { - content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, - base::Bind(callback_, isProductValid)); + if (callback_) { + content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + base::Bind(callback_, isProductValid)); + } } @end diff --git a/atom/browser/in_app_purchase_observer.h b/atom/browser/in_app_purchase_observer.h index 450ee286b34..4b9f1d7b984 100644 --- a/atom/browser/in_app_purchase_observer.h +++ b/atom/browser/in_app_purchase_observer.h @@ -2,8 +2,8 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#ifndef ATOM_BROWSER_UI_IN_APP_PURCHASE_OBSERVER_H_ -#define ATOM_BROWSER_UI_IN_APP_PURCHASE_OBSERVER_H_ +#ifndef ATOM_BROWSER_IN_APP_PURCHASE_OBSERVER_H_ +#define ATOM_BROWSER_IN_APP_PURCHASE_OBSERVER_H_ #include @@ -38,4 +38,4 @@ void AddTransactionObserver(const InAppTransactionCallback& callback); } // namespace in_app_purchase -#endif // ATOM_BROWSER_UI_IN_APP_PURCHASE_OBSERVER_H_ +#endif // ATOM_BROWSER_IN_APP_PURCHASE_OBSERVER_H_ diff --git a/lib/browser/api/in-app-purchase.js b/lib/browser/api/in-app-purchase.js index a0585e80dd9..e88b210ea9c 100644 --- a/lib/browser/api/in-app-purchase.js +++ b/lib/browser/api/in-app-purchase.js @@ -6,32 +6,25 @@ const v8Util = process.atomBinding('v8_util') module.exports = { canMakePayments: function() { - return binding.canMakePayments(); + return binding.canMakePayments() }, getReceiptURL: function() { - return binding.getReceiptURL(); + return binding.getReceiptURL() }, - purchaseProduct: function(productID, quantity, callback) { - + purchaseProduct: function(productID, quantity = 1, callback) { if (typeof productID !== 'string') { throw new TypeError('productID must be a string') } - - if (typeof quantity !== 'number') { + if (typeof quantity === 'function') { quantity = 1 + callback = quantity } - - if (typeof callback !== 'function') { - callback = function() {}; - } - return binding.purchaseProduct(productID, quantity, callback) }, addTransactionListener: function(callback) { - if (typeof callback !== 'function') { throw new TypeError('callback must be a function') } From d32632f768d43037d09714aac529d1e194ac2a3d Mon Sep 17 00:00:00 2001 From: Adrien Fery Date: Sat, 2 Dec 2017 11:13:02 +0100 Subject: [PATCH 04/17] Update filenames.gypi --- filenames.gypi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filenames.gypi b/filenames.gypi index c2942c951a3..7b8d9637bb5 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -733,7 +733,7 @@ 'atom/browser/osr/osr_view_proxy.h', ], }], # enable_osr==1 - ['OS=="win"', { + ['OS=="mac"', { 'app_sources': [ 'atom/browser/in_app_purchase.h', 'atom/browser/in_app_purchase_mac.mm', From b2542a27c3dd93203475b102f9699286ca29d1a5 Mon Sep 17 00:00:00 2001 From: Adrien Fery Date: Sat, 2 Dec 2017 11:17:55 +0100 Subject: [PATCH 05/17] Update code style --- lib/browser/api/in-app-purchase.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/browser/api/in-app-purchase.js b/lib/browser/api/in-app-purchase.js index e88b210ea9c..44487ae02e3 100644 --- a/lib/browser/api/in-app-purchase.js +++ b/lib/browser/api/in-app-purchase.js @@ -5,15 +5,15 @@ const v8Util = process.atomBinding('v8_util') module.exports = { - canMakePayments: function() { + canMakePayments: function () { return binding.canMakePayments() }, - getReceiptURL: function() { + getReceiptURL: function () { return binding.getReceiptURL() }, - purchaseProduct: function(productID, quantity = 1, callback) { + purchaseProduct: function (productID, quantity = 1, callback) { if (typeof productID !== 'string') { throw new TypeError('productID must be a string') } @@ -24,7 +24,7 @@ module.exports = { return binding.purchaseProduct(productID, quantity, callback) }, - addTransactionListener: function(callback) { + addTransactionListener: function (callback) { if (typeof callback !== 'function') { throw new TypeError('callback must be a function') } @@ -32,8 +32,8 @@ module.exports = { } } - // Mark standard asynchronous functions. - v8Util.setHiddenValue( - module.exports.purchaseProduct, 'asynchronous', true) +// Mark standard asynchronous functions. v8Util.setHiddenValue( - module.exports.addTransactionListener, 'asynchronous', true) + module.exports.purchaseProduct, 'asynchronous', true) +v8Util.setHiddenValue( + module.exports.addTransactionListener, 'asynchronous', true) From 5f1c76c688b85e30a5ad6e87e775107b18af13e9 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 14:45:42 +0900 Subject: [PATCH 06/17] Reorgnize the platform-specific files --- atom/browser/api/atom_api_in_app_purchase.cc | 3 ++- atom/browser/api/atom_api_in_app_purchase.h | 4 ++-- atom/browser/{ => mac}/in_app_purchase.h | 6 +++--- .../in_app_purchase.mm} | 3 ++- .../{ => mac}/in_app_purchase_observer.h | 6 +++--- .../in_app_purchase_observer.mm} | 3 ++- filenames.gypi | 17 ++++------------- 7 files changed, 18 insertions(+), 24 deletions(-) rename atom/browser/{ => mac}/in_app_purchase.h (83%) rename atom/browser/{in_app_purchase_mac.mm => mac/in_app_purchase.mm} (98%) rename atom/browser/{ => mac}/in_app_purchase_observer.h (85%) rename atom/browser/{in_app_purchase_observer_mac.mm => mac/in_app_purchase_observer.mm} (98%) diff --git a/atom/browser/api/atom_api_in_app_purchase.cc b/atom/browser/api/atom_api_in_app_purchase.cc index 21bad43ee04..c6815c8c065 100644 --- a/atom/browser/api/atom_api_in_app_purchase.cc +++ b/atom/browser/api/atom_api_in_app_purchase.cc @@ -2,11 +2,12 @@ // 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/browser/api/atom_api_in_app_purchase.h" #include "atom/common/native_mate_converters/callback.h" #include "native_mate/dictionary.h" diff --git a/atom/browser/api/atom_api_in_app_purchase.h b/atom/browser/api/atom_api_in_app_purchase.h index b1feb7ec266..7362f7da6ee 100644 --- a/atom/browser/api/atom_api_in_app_purchase.h +++ b/atom/browser/api/atom_api_in_app_purchase.h @@ -7,8 +7,8 @@ #include -#include "atom/browser/in_app_purchase.h" -#include "atom/browser/in_app_purchase_observer.h" +#include "atom/browser/mac/in_app_purchase.h" +#include "atom/browser/mac/in_app_purchase_observer.h" #include "native_mate/dictionary.h" namespace mate { diff --git a/atom/browser/in_app_purchase.h b/atom/browser/mac/in_app_purchase.h similarity index 83% rename from atom/browser/in_app_purchase.h rename to atom/browser/mac/in_app_purchase.h index d4957e8c97f..fc4177affe5 100644 --- a/atom/browser/in_app_purchase.h +++ b/atom/browser/mac/in_app_purchase.h @@ -2,8 +2,8 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#ifndef ATOM_BROWSER_IN_APP_PURCHASE_H_ -#define ATOM_BROWSER_IN_APP_PURCHASE_H_ +#ifndef ATOM_BROWSER_MAC_IN_APP_PURCHASE_H_ +#define ATOM_BROWSER_MAC_IN_APP_PURCHASE_H_ #include @@ -27,4 +27,4 @@ void PurchaseProduct(const std::string& productID, } // namespace in_app_purchase -#endif // ATOM_BROWSER_IN_APP_PURCHASE_H_ +#endif // ATOM_BROWSER_MAC_IN_APP_PURCHASE_H_ diff --git a/atom/browser/in_app_purchase_mac.mm b/atom/browser/mac/in_app_purchase.mm similarity index 98% rename from atom/browser/in_app_purchase_mac.mm rename to atom/browser/mac/in_app_purchase.mm index 5f4c994a0bc..c3ff67f462e 100644 --- a/atom/browser/in_app_purchase_mac.mm +++ b/atom/browser/mac/in_app_purchase.mm @@ -2,7 +2,8 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#include "atom/browser/in_app_purchase.h" +#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" diff --git a/atom/browser/in_app_purchase_observer.h b/atom/browser/mac/in_app_purchase_observer.h similarity index 85% rename from atom/browser/in_app_purchase_observer.h rename to atom/browser/mac/in_app_purchase_observer.h index 4b9f1d7b984..fadd9037eb8 100644 --- a/atom/browser/in_app_purchase_observer.h +++ b/atom/browser/mac/in_app_purchase_observer.h @@ -2,8 +2,8 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#ifndef ATOM_BROWSER_IN_APP_PURCHASE_OBSERVER_H_ -#define ATOM_BROWSER_IN_APP_PURCHASE_OBSERVER_H_ +#ifndef ATOM_BROWSER_MAC_IN_APP_PURCHASE_OBSERVER_H_ +#define ATOM_BROWSER_MAC_IN_APP_PURCHASE_OBSERVER_H_ #include @@ -38,4 +38,4 @@ void AddTransactionObserver(const InAppTransactionCallback& callback); } // namespace in_app_purchase -#endif // ATOM_BROWSER_IN_APP_PURCHASE_OBSERVER_H_ +#endif // ATOM_BROWSER_MAC_IN_APP_PURCHASE_OBSERVER_H_ diff --git a/atom/browser/in_app_purchase_observer_mac.mm b/atom/browser/mac/in_app_purchase_observer.mm similarity index 98% rename from atom/browser/in_app_purchase_observer_mac.mm rename to atom/browser/mac/in_app_purchase_observer.mm index 0ec1b9bb613..a56f6b72298 100644 --- a/atom/browser/in_app_purchase_observer_mac.mm +++ b/atom/browser/mac/in_app_purchase_observer.mm @@ -2,7 +2,8 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#include "atom/browser/in_app_purchase_observer.h" +#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" diff --git a/filenames.gypi b/filenames.gypi index 7b8d9637bb5..40babc685d3 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -187,10 +187,6 @@ 'atom/browser/atom_browser_context.h', 'atom/browser/atom_download_manager_delegate.cc', 'atom/browser/atom_download_manager_delegate.h', - 'atom/browser/in_app_purchase.h', - 'atom/browser/in_app_purchase_mac.mm', - 'atom/browser/in_app_purchase_observer.h', - 'atom/browser/in_app_purchase_observer_mac.mm', 'atom/browser/atom_browser_main_parts.cc', 'atom/browser/atom_browser_main_parts.h', 'atom/browser/atom_browser_main_parts_mac.mm', @@ -238,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', @@ -733,15 +733,6 @@ 'atom/browser/osr/osr_view_proxy.h', ], }], # enable_osr==1 - ['OS=="mac"', { - 'app_sources': [ - 'atom/browser/in_app_purchase.h', - 'atom/browser/in_app_purchase_mac.mm', - 'atom/browser/in_app_purchase_observer.h', - 'atom/browser/in_app_purchase_observer_mac.mm' - ], - }], # OS=="mac" - ], }, } From bdeb979d412e727bf5727da4fe053bcf94420966 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 15:21:53 +0900 Subject: [PATCH 07/17] spec: Simple tests for inAppPurchase module --- spec/api-in-app-purchase-spec.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 spec/api-in-app-purchase-spec.js diff --git a/spec/api-in-app-purchase-spec.js b/spec/api-in-app-purchase-spec.js new file mode 100644 index 00000000000..541a2af2f63 --- /dev/null +++ b/spec/api-in-app-purchase-spec.js @@ -0,0 +1,25 @@ +'use strict' + +const assert = require('assert') + +const {remote} = require('electron') +const {inAppPurchase} = remote + +describe('inAppPurchase module', () => { + if (process.platform !== 'darwin') return + + 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() + }) + }) +}) From 400bfb3c5a1aaf4e275325fb4e2d8dbc1af56a3f Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 15:29:32 +0900 Subject: [PATCH 08/17] mac: SKProductsRequest should be freed on end --- atom/browser/mac/in_app_purchase.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/atom/browser/mac/in_app_purchase.mm b/atom/browser/mac/in_app_purchase.mm index c3ff67f462e..4204fa509a0 100644 --- a/atom/browser/mac/in_app_purchase.mm +++ b/atom/browser/mac/in_app_purchase.mm @@ -76,6 +76,9 @@ */ - (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; @@ -117,6 +120,8 @@ content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, base::Bind(callback_, isProductValid)); } + // Release this delegate. + [self release]; } @end From ac6f895f64d4fa36448f18dc138fc81cc1a05969 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 16:37:05 +0900 Subject: [PATCH 09/17] Turn InAppPurchase into an EventEmitter --- atom/browser/api/atom_api_in_app_purchase.cc | 105 +++++++++++++------ atom/browser/api/atom_api_in_app_purchase.h | 40 +++---- atom/browser/mac/in_app_purchase.h | 2 +- atom/browser/mac/in_app_purchase.mm | 4 +- atom/browser/mac/in_app_purchase_observer.mm | 2 + docs/api/in-app-purchase.md | 12 ++- lib/browser/api/in-app-purchase.js | 41 ++------ spec/api-in-app-purchase-spec.js | 7 ++ 8 files changed, 123 insertions(+), 90 deletions(-) diff --git a/atom/browser/api/atom_api_in_app_purchase.cc b/atom/browser/api/atom_api_in_app_purchase.cc index c6815c8c065..66ab3883010 100644 --- a/atom/browser/api/atom_api_in_app_purchase.cc +++ b/atom/browser/api/atom_api_in_app_purchase.cc @@ -15,47 +15,92 @@ namespace mate { -v8::Local Converter::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::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(); + } +}; -v8::Local Converter::ToV8( - v8::Isolate* isolate, - const in_app_purchase::Transaction& transaction) { - mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); - dict.SetHidden("simple", true); - dict.Set("transactionIdentifier", transaction.transactionIdentifier); - dict.Set("transactionDate", transaction.transactionDate); - dict.Set("originalTransactionIdentifier", - transaction.originalTransactionIdentifier); - dict.Set("transactionState", transaction.transactionState); +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); + return dict.GetHandle(); + } +}; - dict.Set("errorCode", transaction.errorCode); - dict.Set("errorMessage", transaction.errorMessage); - - return dict.GetHandle(); -} } // namespace mate +namespace atom { + +namespace api { + +// 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) + .SetMethod("addTransactionListener", + &in_app_purchase::AddTransactionObserver); +} + +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); +} + +} // namespace api + +} // namespace atom + namespace { +using atom::api::InAppPurchase; + void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { - mate::Dictionary dict(context->GetIsolate(), exports); #if defined(OS_MACOSX) - dict.SetMethod("canMakePayments", &in_app_purchase::CanMakePayments); - dict.SetMethod("getReceiptURL", &in_app_purchase::GetReceiptURL); - dict.SetMethod("purchaseProduct", &in_app_purchase::PurchaseProduct); - dict.SetMethod("addTransactionListener", - &in_app_purchase::AddTransactionObserver); + v8::Isolate* isolate = context->GetIsolate(); + mate::Dictionary dict(isolate, exports); + dict.Set("inAppPurchase", InAppPurchase::Create(isolate)); + dict.Set("InAppPurchase", + InAppPurchase::GetConstructor(isolate)->GetFunction()); #endif } diff --git a/atom/browser/api/atom_api_in_app_purchase.h b/atom/browser/api/atom_api_in_app_purchase.h index 7362f7da6ee..b11ccbc6607 100644 --- a/atom/browser/api/atom_api_in_app_purchase.h +++ b/atom/browser/api/atom_api_in_app_purchase.h @@ -7,30 +7,34 @@ #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/dictionary.h" +#include "native_mate/handle.h" -namespace mate { +namespace atom { -template <> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - const in_app_purchase::Payment& val); - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - in_app_purchase::Payment* out); +namespace api { + +class InAppPurchase: public mate::EventEmitter { + 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); + + private: + DISALLOW_COPY_AND_ASSIGN(InAppPurchase); }; -template <> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - const in_app_purchase::Transaction& val); - static bool FromV8(v8::Isolate* isolate, - v8::Local val, - in_app_purchase::Transaction* out); -}; +} // namespace api -} // namespace mate +} // 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 index fc4177affe5..d014744d55c 100644 --- a/atom/browser/mac/in_app_purchase.h +++ b/atom/browser/mac/in_app_purchase.h @@ -22,7 +22,7 @@ bool CanMakePayments(void); std::string GetReceiptURL(void); void PurchaseProduct(const std::string& productID, - const int quantity, + int quantity, const InAppPurchaseCallback& callback); } // namespace in_app_purchase diff --git a/atom/browser/mac/in_app_purchase.mm b/atom/browser/mac/in_app_purchase.mm index 4204fa509a0..f9f98332da1 100644 --- a/atom/browser/mac/in_app_purchase.mm +++ b/atom/browser/mac/in_app_purchase.mm @@ -146,9 +146,9 @@ std::string GetReceiptURL() { } void PurchaseProduct(const std::string& productID, - const int quantity, + int quantity, const InAppPurchaseCallback& callback) { - auto iap = + auto* iap = [[InAppPurchase alloc] initWithCallback:callback quantity:quantity]; [iap purchaseProduct:base::SysUTF8ToNSString(productID)]; diff --git a/atom/browser/mac/in_app_purchase_observer.mm b/atom/browser/mac/in_app_purchase_observer.mm index a56f6b72298..1cd0ab9e075 100644 --- a/atom/browser/mac/in_app_purchase_observer.mm +++ b/atom/browser/mac/in_app_purchase_observer.mm @@ -177,6 +177,8 @@ namespace in_app_purchase { void AddTransactionObserver(const InAppTransactionCallback& callback) { + // This is leaked, but we should be fine since we don't have a way to remove + // callback and the inAppPurchase module is never unloaded. [[InAppTransactionObserver alloc] initWithCallback:callback]; } diff --git a/docs/api/in-app-purchase.md b/docs/api/in-app-purchase.md index effbf22af0f..9019e41c08e 100644 --- a/docs/api/in-app-purchase.md +++ b/docs/api/in-app-purchase.md @@ -1,14 +1,16 @@ # inAppPurchase _macOS_ +> In-App Purchase on Mac App Store. + Your application should add a listener before to purchase a product. If there are no listener attached to the queue, the payment queue does not synchronize its list of pending transactions with the Apple App Store. -## Methods +## Methods The `inAppPurchase` module has the following methods: ### `inAppPurchase.addTransactionListener(listener)` -* `listener` Function - Called when transactions are updated by the payment queue. +* `listener` Function - Called when transactions are updated by the payment queue. * `payment` Object * `productIdentifier` String * `quantity` Integer @@ -19,9 +21,11 @@ The `inAppPurchase` module has the following methods: * `transactionState` String - The transaction sate (`"SKPaymentTransactionStatePurchasing"`, `"SKPaymentTransactionStatePurchased"`, `"SKPaymentTransactionStateFailed"`, `"SKPaymentTransactionStateRestored"`, or `"SKPaymentTransactionStateDeferred"`) * `errorCode` Integer * `errorMessage` String - + +Add a listener to transactions. ### `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). @@ -33,4 +37,4 @@ Returns `true` if the user can make a payment and `false` otherwise. ### `inAppPurchase.getReceiptURL()` -Returns `String`, the path to the receipt. \ No newline at end of file +Returns `String`, the path to the receipt. diff --git a/lib/browser/api/in-app-purchase.js b/lib/browser/api/in-app-purchase.js index 44487ae02e3..dc0bffa9c97 100644 --- a/lib/browser/api/in-app-purchase.js +++ b/lib/browser/api/in-app-purchase.js @@ -1,39 +1,10 @@ 'use strict' -const binding = process.atomBinding('in_app_purchase') -const v8Util = process.atomBinding('v8_util') +const {EventEmitter} = require('events') +const {inAppPurchase, InAppPurchase} = process.atomBinding('in_app_purchase') -module.exports = { +// inAppPurchase is an EventEmitter. +Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype) +EventEmitter.call(inAppPurchase) - canMakePayments: function () { - return binding.canMakePayments() - }, - - getReceiptURL: function () { - return binding.getReceiptURL() - }, - - purchaseProduct: function (productID, quantity = 1, callback) { - if (typeof productID !== 'string') { - throw new TypeError('productID must be a string') - } - if (typeof quantity === 'function') { - quantity = 1 - callback = quantity - } - return binding.purchaseProduct(productID, quantity, callback) - }, - - addTransactionListener: function (callback) { - if (typeof callback !== 'function') { - throw new TypeError('callback must be a function') - } - return binding.addTransactionListener(callback) - } -} - -// Mark standard asynchronous functions. -v8Util.setHiddenValue( - module.exports.purchaseProduct, 'asynchronous', true) -v8Util.setHiddenValue( - module.exports.addTransactionListener, 'asynchronous', true) +module.exports = inAppPurchase diff --git a/spec/api-in-app-purchase-spec.js b/spec/api-in-app-purchase-spec.js index 541a2af2f63..961ae22fe19 100644 --- a/spec/api-in-app-purchase-spec.js +++ b/spec/api-in-app-purchase-spec.js @@ -22,4 +22,11 @@ describe('inAppPurchase module', () => { done() }) }) + + it('purchaseProduct() accepts optional arguments', (done) => { + inAppPurchase.purchaseProduct('non-exist', () => { + inAppPurchase.purchaseProduct('non-exist', 1) + done() + }) + }) }) From 133bef3deb14ab55470336a2201a79ca9e5dd2f8 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 16:55:49 +0900 Subject: [PATCH 10/17] Turn addTransactionListener into transaction-updated event --- atom/browser/api/atom_api_in_app_purchase.cc | 10 +++-- atom/browser/api/atom_api_in_app_purchase.h | 8 +++- atom/browser/mac/in_app_purchase_observer.h | 26 +++++++++--- atom/browser/mac/in_app_purchase_observer.mm | 31 ++++++++++----- docs/api/in-app-purchase.md | 42 +++++++++++--------- 5 files changed, 80 insertions(+), 37 deletions(-) diff --git a/atom/browser/api/atom_api_in_app_purchase.cc b/atom/browser/api/atom_api_in_app_purchase.cc index 66ab3883010..6c9d3794189 100644 --- a/atom/browser/api/atom_api_in_app_purchase.cc +++ b/atom/browser/api/atom_api_in_app_purchase.cc @@ -62,9 +62,7 @@ void InAppPurchase::BuildPrototype(v8::Isolate* isolate, mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) .SetMethod("canMakePayments", &in_app_purchase::CanMakePayments) .SetMethod("getReceiptURL", &in_app_purchase::GetReceiptURL) - .SetMethod("purchaseProduct", &InAppPurchase::PurchaseProduct) - .SetMethod("addTransactionListener", - &in_app_purchase::AddTransactionObserver); + .SetMethod("purchaseProduct", &InAppPurchase::PurchaseProduct); } InAppPurchase::InAppPurchase(v8::Isolate* isolate) { @@ -83,6 +81,12 @@ void InAppPurchase::PurchaseProduct(const std::string& product_id, in_app_purchase::PurchaseProduct(product_id, quantity, callback); } +void InAppPurchase::OnTransactionUpdated( + const in_app_purchase::Payment& payment, + const in_app_purchase::Transaction& transaction) { + Emit("transaction-updated", payment, transaction); +} + } // namespace api } // namespace atom diff --git a/atom/browser/api/atom_api_in_app_purchase.h b/atom/browser/api/atom_api_in_app_purchase.h index b11ccbc6607..6ab25cb69f1 100644 --- a/atom/browser/api/atom_api_in_app_purchase.h +++ b/atom/browser/api/atom_api_in_app_purchase.h @@ -16,7 +16,8 @@ namespace atom { namespace api { -class InAppPurchase: public mate::EventEmitter { +class InAppPurchase: public mate::EventEmitter, + public in_app_purchase::TransactionObserver { public: static mate::Handle Create(v8::Isolate* isolate); @@ -29,6 +30,11 @@ class InAppPurchase: public mate::EventEmitter { void PurchaseProduct(const std::string& product_id, mate::Arguments* args); + // TransactionObserver: + void OnTransactionUpdated( + const in_app_purchase::Payment& payment, + const in_app_purchase::Transaction& transaction) override; + private: DISALLOW_COPY_AND_ASSIGN(InAppPurchase); }; diff --git a/atom/browser/mac/in_app_purchase_observer.h b/atom/browser/mac/in_app_purchase_observer.h index fadd9037eb8..2e80cd48e9e 100644 --- a/atom/browser/mac/in_app_purchase_observer.h +++ b/atom/browser/mac/in_app_purchase_observer.h @@ -8,6 +8,13 @@ #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 { @@ -27,14 +34,23 @@ struct Transaction { std::string transactionState = ""; }; -// --------------------------- Typedefs --------------------------- +// --------------------------- Classes --------------------------- -typedef base::RepeatingCallback - InAppTransactionCallback; +class TransactionObserver { + public: + TransactionObserver(); + virtual ~TransactionObserver(); -// --------------------------- Functions --------------------------- + virtual void OnTransactionUpdated(const Payment& payment, + const Transaction& transaction) = 0; -void AddTransactionObserver(const InAppTransactionCallback& callback); + private: + InAppTransactionObserver* obeserver_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(TransactionObserver); +}; } // namespace in_app_purchase diff --git a/atom/browser/mac/in_app_purchase_observer.mm b/atom/browser/mac/in_app_purchase_observer.mm index 1cd0ab9e075..59b4aecc4f6 100644 --- a/atom/browser/mac/in_app_purchase_observer.mm +++ b/atom/browser/mac/in_app_purchase_observer.mm @@ -15,13 +15,20 @@ // InAppTransactionObserver // ============================================================================ +namespace { + +using InAppTransactionCallback = + base::RepeatingCallback; + +} // namespace + @interface InAppTransactionObserver : NSObject { @private - in_app_purchase::InAppTransactionCallback callback_; + InAppTransactionCallback callback_; } -- (id)initWithCallback: - (const in_app_purchase::InAppTransactionCallback&)callback; +- (id)initWithCallback:(const InAppTransactionCallback&)callback; @end @@ -33,8 +40,7 @@ * @param callback - The callback that will be called for each transaction * update. */ -- (id)initWithCallback: - (const in_app_purchase::InAppTransactionCallback&)callback { +- (id)initWithCallback:(const InAppTransactionCallback&)callback { if ((self = [super init])) { callback_ = callback; @@ -143,7 +149,8 @@ if (transaction.transactionState < 5) { transactionStruct.transactionState = [[@[ @"SKPaymentTransactionStatePurchasing", - @"SKPaymentTransactionStatePurchased", @"SKPaymentTransactionStateFailed", + @"SKPaymentTransactionStatePurchased", + @"SKPaymentTransactionStateFailed", @"SKPaymentTransactionStateRestored", @"SKPaymentTransactionStateDeferred" ] objectAtIndex:transaction.transactionState] UTF8String]; @@ -176,10 +183,14 @@ namespace in_app_purchase { -void AddTransactionObserver(const InAppTransactionCallback& callback) { - // This is leaked, but we should be fine since we don't have a way to remove - // callback and the inAppPurchase module is never unloaded. - [[InAppTransactionObserver alloc] initWithCallback:callback]; +TransactionObserver::TransactionObserver() : weak_ptr_factory_(this) { + obeserver_ = [[InAppTransactionObserver alloc] + initWithCallback:base::Bind(&TransactionObserver::OnTransactionUpdated, + weak_ptr_factory_.GetWeakPtr())]; +} + +TransactionObserver::~TransactionObserver() { + [obeserver_ release]; } } // namespace in_app_purchase diff --git a/docs/api/in-app-purchase.md b/docs/api/in-app-purchase.md index 9019e41c08e..6eaa55961a7 100644 --- a/docs/api/in-app-purchase.md +++ b/docs/api/in-app-purchase.md @@ -1,29 +1,35 @@ # inAppPurchase _macOS_ -> In-App Purchase on Mac App Store. +> In-app purchases on Mac App Store. -Your application should add a listener before to purchase a product. If there are no listener attached to the queue, the payment queue does not synchronize its list of pending transactions with the Apple App Store. +Process: [Main](../glossary.md#main-process) + +## Events + +The `inAppPurchase` module emits the following events: + +### Event: 'transaction-updated' + +Emitted when a transaction has been updated. + +Returns: + +* `event` Event +* `payment` Object + * `productIdentifier` String + * `quantity` Integer +* `transaction` Object + * `transactionIdentifier` String + * `transactionDate` String + * `originalTransactionIdentifier` String + * `transactionState` String - The transaction sate (`"SKPaymentTransactionStatePurchasing"`, `"SKPaymentTransactionStatePurchased"`, `"SKPaymentTransactionStateFailed"`, `"SKPaymentTransactionStateRestored"`, or `"SKPaymentTransactionStateDeferred"`) + * `errorCode` Integer + * `errorMessage` String ## Methods The `inAppPurchase` module has the following methods: -### `inAppPurchase.addTransactionListener(listener)` - -* `listener` Function - Called when transactions are updated by the payment queue. - * `payment` Object - * `productIdentifier` String - * `quantity` Integer - * `transaction` Object - * `transactionIdentifier` String - * `transactionDate` String - * `originalTransactionIdentifier` String - * `transactionState` String - The transaction sate (`"SKPaymentTransactionStatePurchasing"`, `"SKPaymentTransactionStatePurchased"`, `"SKPaymentTransactionStateFailed"`, `"SKPaymentTransactionStateRestored"`, or `"SKPaymentTransactionStateDeferred"`) - * `errorCode` Integer - * `errorMessage` String - -Add a listener to transactions. - ### `inAppPurchase.purchaseProduct(productID, quantity, callback)` * `productID` String - The id of the product to purchase. (the id of `com.example.app.product1` is `product1`). From 635b753ecd2ac33801c4d606e125892d220c8f63 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 16:59:17 +0900 Subject: [PATCH 11/17] No need for SKPaymentTransactionState prefix for states --- atom/browser/mac/in_app_purchase_observer.mm | 6 +----- docs/api/in-app-purchase.md | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/atom/browser/mac/in_app_purchase_observer.mm b/atom/browser/mac/in_app_purchase_observer.mm index 59b4aecc4f6..955c4a11350 100644 --- a/atom/browser/mac/in_app_purchase_observer.mm +++ b/atom/browser/mac/in_app_purchase_observer.mm @@ -148,11 +148,7 @@ using InAppTransactionCallback = if (transaction.transactionState < 5) { transactionStruct.transactionState = [[@[ - @"SKPaymentTransactionStatePurchasing", - @"SKPaymentTransactionStatePurchased", - @"SKPaymentTransactionStateFailed", - @"SKPaymentTransactionStateRestored", - @"SKPaymentTransactionStateDeferred" + @"purchasing", @"purchased", @"failed", @"restored", @"deferred" ] objectAtIndex:transaction.transactionState] UTF8String]; } diff --git a/docs/api/in-app-purchase.md b/docs/api/in-app-purchase.md index 6eaa55961a7..8c53832f827 100644 --- a/docs/api/in-app-purchase.md +++ b/docs/api/in-app-purchase.md @@ -22,7 +22,7 @@ Returns: * `transactionIdentifier` String * `transactionDate` String * `originalTransactionIdentifier` String - * `transactionState` String - The transaction sate (`"SKPaymentTransactionStatePurchasing"`, `"SKPaymentTransactionStatePurchased"`, `"SKPaymentTransactionStateFailed"`, `"SKPaymentTransactionStateRestored"`, or `"SKPaymentTransactionStateDeferred"`) + * `transactionState` String - The transaction sate (`"purchasing"`, `"purchased"`, `"failed"`, `"restored"`, or `"deferred"`) * `errorCode` Integer * `errorMessage` String From 2dd545ebdab0310606629ed17c3a137c398ab899 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 17:06:27 +0900 Subject: [PATCH 12/17] Payment should be part of Transaction This follows The SKPayment API, and makes our JS wrappers easier to implement. --- atom/browser/api/atom_api_in_app_purchase.cc | 4 ++-- atom/browser/api/atom_api_in_app_purchase.h | 1 - atom/browser/mac/in_app_purchase_observer.h | 4 ++-- atom/browser/mac/in_app_purchase_observer.mm | 21 ++++++-------------- docs/api/in-app-purchase.md | 6 +++--- 5 files changed, 13 insertions(+), 23 deletions(-) diff --git a/atom/browser/api/atom_api_in_app_purchase.cc b/atom/browser/api/atom_api_in_app_purchase.cc index 6c9d3794189..9c10388bc6b 100644 --- a/atom/browser/api/atom_api_in_app_purchase.cc +++ b/atom/browser/api/atom_api_in_app_purchase.cc @@ -40,6 +40,7 @@ struct Converter { dict.Set("transactionState", val.transactionState); dict.Set("errorCode", val.errorCode); dict.Set("errorMessage", val.errorMessage); + dict.Set("payment", val.payment); return dict.GetHandle(); } }; @@ -82,9 +83,8 @@ void InAppPurchase::PurchaseProduct(const std::string& product_id, } void InAppPurchase::OnTransactionUpdated( - const in_app_purchase::Payment& payment, const in_app_purchase::Transaction& transaction) { - Emit("transaction-updated", payment, transaction); + Emit("transaction-updated", transaction); } } // namespace api diff --git a/atom/browser/api/atom_api_in_app_purchase.h b/atom/browser/api/atom_api_in_app_purchase.h index 6ab25cb69f1..326c28ff020 100644 --- a/atom/browser/api/atom_api_in_app_purchase.h +++ b/atom/browser/api/atom_api_in_app_purchase.h @@ -32,7 +32,6 @@ class InAppPurchase: public mate::EventEmitter, // TransactionObserver: void OnTransactionUpdated( - const in_app_purchase::Payment& payment, const in_app_purchase::Transaction& transaction) override; private: diff --git a/atom/browser/mac/in_app_purchase_observer.h b/atom/browser/mac/in_app_purchase_observer.h index 2e80cd48e9e..6bec6c237d9 100644 --- a/atom/browser/mac/in_app_purchase_observer.h +++ b/atom/browser/mac/in_app_purchase_observer.h @@ -32,6 +32,7 @@ struct Transaction { int errorCode = 0; std::string errorMessage = ""; std::string transactionState = ""; + Payment payment; }; // --------------------------- Classes --------------------------- @@ -41,8 +42,7 @@ class TransactionObserver { TransactionObserver(); virtual ~TransactionObserver(); - virtual void OnTransactionUpdated(const Payment& payment, - const Transaction& transaction) = 0; + virtual void OnTransactionUpdated(const Transaction& transaction) = 0; private: InAppTransactionObserver* obeserver_; diff --git a/atom/browser/mac/in_app_purchase_observer.mm b/atom/browser/mac/in_app_purchase_observer.mm index 955c4a11350..be9c4fa4c41 100644 --- a/atom/browser/mac/in_app_purchase_observer.mm +++ b/atom/browser/mac/in_app_purchase_observer.mm @@ -18,8 +18,7 @@ namespace { using InAppTransactionCallback = - base::RepeatingCallback; + base::RepeatingCallback; } // namespace @@ -60,10 +59,6 @@ using InAppTransactionCallback = return; } - // Convert the payment. - in_app_purchase::Payment paymentStruct; - paymentStruct = [self skPaymentToStruct:transaction.payment]; - // Convert the transaction. in_app_purchase::Transaction transactionStruct; transactionStruct = [self skPaymentTransactionToStruct:transaction]; @@ -71,7 +66,7 @@ using InAppTransactionCallback = // Send the callback to the browser thread. content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(callback_, paymentStruct, transactionStruct)); + base::Bind(callback_, transactionStruct)); } /** @@ -97,10 +92,6 @@ using InAppTransactionCallback = - (in_app_purchase::Payment)skPaymentToStruct:(SKPayment*)payment { in_app_purchase::Payment paymentStruct; - if (payment == nil) { - return paymentStruct; - } - if (payment.productIdentifier != nil) { paymentStruct.productIdentifier = [payment.productIdentifier UTF8String]; } @@ -121,10 +112,6 @@ using InAppTransactionCallback = (SKPaymentTransaction*)transaction { in_app_purchase::Transaction transactionStruct; - if (transaction == nil) { - return transactionStruct; - } - if (transaction.transactionIdentifier != nil) { transactionStruct.transactionIdentifier = [transaction.transactionIdentifier UTF8String]; @@ -152,6 +139,10 @@ using InAppTransactionCallback = ] objectAtIndex:transaction.transactionState] UTF8String]; } + if (transaction.payment != nil) { + transactionStruct.payment = [self skPaymentToStruct:transaction.payment]; + } + return transactionStruct; } diff --git a/docs/api/in-app-purchase.md b/docs/api/in-app-purchase.md index 8c53832f827..0dd1ea5fdfc 100644 --- a/docs/api/in-app-purchase.md +++ b/docs/api/in-app-purchase.md @@ -15,9 +15,6 @@ Emitted when a transaction has been updated. Returns: * `event` Event -* `payment` Object - * `productIdentifier` String - * `quantity` Integer * `transaction` Object * `transactionIdentifier` String * `transactionDate` String @@ -25,6 +22,9 @@ Returns: * `transactionState` String - The transaction sate (`"purchasing"`, `"purchased"`, `"failed"`, `"restored"`, or `"deferred"`) * `errorCode` Integer * `errorMessage` String + * `payment` Object + * `productIdentifier` String + * `quantity` Integer ## Methods From e77ddd3221df0fd5c843c8545228caf225b30777 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 17:18:23 +0900 Subject: [PATCH 13/17] Pass multiple transactions at the same time This follows the design of SKPayment API. --- atom/browser/api/atom_api_in_app_purchase.cc | 6 ++--- atom/browser/api/atom_api_in_app_purchase.h | 5 ++-- atom/browser/mac/in_app_purchase_observer.h | 6 +++-- atom/browser/mac/in_app_purchase_observer.mm | 25 +++++++++----------- docs/api/in-app-purchase.md | 15 +++--------- docs/api/structures/transaction.md | 11 +++++++++ 6 files changed, 35 insertions(+), 33 deletions(-) create mode 100644 docs/api/structures/transaction.md diff --git a/atom/browser/api/atom_api_in_app_purchase.cc b/atom/browser/api/atom_api_in_app_purchase.cc index 9c10388bc6b..aa6a89eeff7 100644 --- a/atom/browser/api/atom_api_in_app_purchase.cc +++ b/atom/browser/api/atom_api_in_app_purchase.cc @@ -82,9 +82,9 @@ void InAppPurchase::PurchaseProduct(const std::string& product_id, in_app_purchase::PurchaseProduct(product_id, quantity, callback); } -void InAppPurchase::OnTransactionUpdated( - const in_app_purchase::Transaction& transaction) { - Emit("transaction-updated", transaction); +void InAppPurchase::OnTransactionsUpdated( + const std::vector& transactions) { + Emit("transactions-updated", transactions); } } // namespace api diff --git a/atom/browser/api/atom_api_in_app_purchase.h b/atom/browser/api/atom_api_in_app_purchase.h index 326c28ff020..3646c9b8e8f 100644 --- a/atom/browser/api/atom_api_in_app_purchase.h +++ b/atom/browser/api/atom_api_in_app_purchase.h @@ -6,6 +6,7 @@ #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" @@ -31,8 +32,8 @@ class InAppPurchase: public mate::EventEmitter, void PurchaseProduct(const std::string& product_id, mate::Arguments* args); // TransactionObserver: - void OnTransactionUpdated( - const in_app_purchase::Transaction& transaction) override; + void OnTransactionsUpdated( + const std::vector& transactions) override; private: DISALLOW_COPY_AND_ASSIGN(InAppPurchase); diff --git a/atom/browser/mac/in_app_purchase_observer.h b/atom/browser/mac/in_app_purchase_observer.h index 6bec6c237d9..d2d0be2b0ae 100644 --- a/atom/browser/mac/in_app_purchase_observer.h +++ b/atom/browser/mac/in_app_purchase_observer.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_MAC_IN_APP_PURCHASE_OBSERVER_H_ #include +#include #include "base/callback.h" #include "base/memory/weak_ptr.h" @@ -14,7 +15,7 @@ @class InAppTransactionObserver; #else // __OBJC__ class InAppTransactionObserver; -#endif // __OBJC__ +#endif // __OBJC__ namespace in_app_purchase { @@ -42,7 +43,8 @@ class TransactionObserver { TransactionObserver(); virtual ~TransactionObserver(); - virtual void OnTransactionUpdated(const Transaction& transaction) = 0; + virtual void OnTransactionsUpdated( + const std::vector& transactions) = 0; private: InAppTransactionObserver* obeserver_; diff --git a/atom/browser/mac/in_app_purchase_observer.mm b/atom/browser/mac/in_app_purchase_observer.mm index be9c4fa4c41..379f405c2b3 100644 --- a/atom/browser/mac/in_app_purchase_observer.mm +++ b/atom/browser/mac/in_app_purchase_observer.mm @@ -18,7 +18,8 @@ namespace { using InAppTransactionCallback = - base::RepeatingCallback; + base::RepeatingCallback< + void(const std::vector&)>; } // namespace @@ -54,19 +55,17 @@ using InAppTransactionCallback = * * @param transaction - The transaction to pass to the callback. */ -- (void)runCallback:(SKPaymentTransaction*)transaction { - if (transaction == nil) { - return; - } - +- (void)runCallback:(NSArray*)transactions { // Convert the transaction. - in_app_purchase::Transaction transactionStruct; - transactionStruct = [self skPaymentTransactionToStruct: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_, transactionStruct)); + content::BrowserThread::UI, FROM_HERE, base::Bind(callback_, converted)); } /** @@ -157,9 +156,7 @@ using InAppTransactionCallback = */ - (void)paymentQueue:(SKPaymentQueue*)queue updatedTransactions:(NSArray*)transactions { - for (SKPaymentTransaction* transaction in transactions) { - [self runCallback:transaction]; - } + [self runCallback:transactions]; } @end @@ -172,7 +169,7 @@ namespace in_app_purchase { TransactionObserver::TransactionObserver() : weak_ptr_factory_(this) { obeserver_ = [[InAppTransactionObserver alloc] - initWithCallback:base::Bind(&TransactionObserver::OnTransactionUpdated, + initWithCallback:base::Bind(&TransactionObserver::OnTransactionsUpdated, weak_ptr_factory_.GetWeakPtr())]; } diff --git a/docs/api/in-app-purchase.md b/docs/api/in-app-purchase.md index 0dd1ea5fdfc..c5c97cf2036 100644 --- a/docs/api/in-app-purchase.md +++ b/docs/api/in-app-purchase.md @@ -8,23 +8,14 @@ Process: [Main](../glossary.md#main-process) The `inAppPurchase` module emits the following events: -### Event: 'transaction-updated' +### Event: 'transactions-updated' -Emitted when a transaction has been updated. +Emitted when one or more transactions have been updated. Returns: * `event` Event -* `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 +* `transactions` ([Transaction[]](structures/transaction.md) - Array of transactions. ## Methods diff --git a/docs/api/structures/transaction.md b/docs/api/structures/transaction.md new file mode 100644 index 00000000000..78ee4e8ad0c --- /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 From 6d9c6645a88f37e437e54a795115a2b917fdb9f0 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 17:21:52 +0900 Subject: [PATCH 14/17] Add inAppPurchase to docs index --- docs/README.md | 1 + docs/api/in-app-purchase.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 2ce70ca1725..5e97153cf0b 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 index c5c97cf2036..f9aaaa90ddd 100644 --- a/docs/api/in-app-purchase.md +++ b/docs/api/in-app-purchase.md @@ -1,4 +1,4 @@ -# inAppPurchase _macOS_ +# inAppPurchase > In-app purchases on Mac App Store. From 839df0ee5a1cdf34f09280f6343e091b52a55393 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 17:30:30 +0900 Subject: [PATCH 15/17] Remove observer on cleanup --- atom/browser/mac/in_app_purchase_observer.mm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/atom/browser/mac/in_app_purchase_observer.mm b/atom/browser/mac/in_app_purchase_observer.mm index 379f405c2b3..47e35e22e73 100644 --- a/atom/browser/mac/in_app_purchase_observer.mm +++ b/atom/browser/mac/in_app_purchase_observer.mm @@ -50,6 +50,14 @@ using InAppTransactionCallback = return self; } +/** + * Cleanup. + */ +- (void)dealloc { + [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; + [super dealloc]; +} + /** * Run the callback in the browser thread. * From a883d3d50dc9417a718f8d83337542edbc01f8dd Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 17:39:16 +0900 Subject: [PATCH 16/17] Fix build on other platforms --- atom/browser/api/atom_api_in_app_purchase.cc | 2 ++ atom/browser/mac/in_app_purchase_observer.mm | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/atom/browser/api/atom_api_in_app_purchase.cc b/atom/browser/api/atom_api_in_app_purchase.cc index aa6a89eeff7..4f507fb86e4 100644 --- a/atom/browser/api/atom_api_in_app_purchase.cc +++ b/atom/browser/api/atom_api_in_app_purchase.cc @@ -51,6 +51,7 @@ namespace atom { namespace api { +#if defined(OS_MACOSX) // static mate::Handle InAppPurchase::Create(v8::Isolate* isolate) { return mate::CreateHandle(isolate, new InAppPurchase(isolate)); @@ -86,6 +87,7 @@ void InAppPurchase::OnTransactionsUpdated( const std::vector& transactions) { Emit("transactions-updated", transactions); } +#endif } // namespace api diff --git a/atom/browser/mac/in_app_purchase_observer.mm b/atom/browser/mac/in_app_purchase_observer.mm index 47e35e22e73..ef21228ad6e 100644 --- a/atom/browser/mac/in_app_purchase_observer.mm +++ b/atom/browser/mac/in_app_purchase_observer.mm @@ -63,7 +63,7 @@ using InAppTransactionCallback = * * @param transaction - The transaction to pass to the callback. */ -- (void)runCallback:(NSArray*)transactions { +- (void)runCallback:(NSArray*)transactions { // Convert the transaction. std::vector converted; converted.reserve([transactions count]); From bd271cffb98d5f6b2f6b696df3ecde5300a13132 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 10 Jan 2018 18:53:55 +0900 Subject: [PATCH 17/17] Throw when inAppPurchase is used on unsupported platforms --- lib/browser/api/in-app-purchase.js | 4 ++++ spec/api-in-app-purchase-spec.js | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/browser/api/in-app-purchase.js b/lib/browser/api/in-app-purchase.js index dc0bffa9c97..48a35d2a4d4 100644 --- a/lib/browser/api/in-app-purchase.js +++ b/lib/browser/api/in-app-purchase.js @@ -1,5 +1,9 @@ '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') diff --git a/spec/api-in-app-purchase-spec.js b/spec/api-in-app-purchase-spec.js index 961ae22fe19..c47c8d752ee 100644 --- a/spec/api-in-app-purchase-spec.js +++ b/spec/api-in-app-purchase-spec.js @@ -3,11 +3,12 @@ const assert = require('assert') const {remote} = require('electron') -const {inAppPurchase} = remote describe('inAppPurchase module', () => { if (process.platform !== 'darwin') return + const {inAppPurchase} = remote + it('canMakePayments() does not throw', () => { inAppPurchase.canMakePayments() })