feat: promisify In-App Purchase (#17355)
* feat: promisify In-App Purchase * use mate::Arguments in GetProducts
This commit is contained in:
parent
faabd0cc8b
commit
3e5a98b5f4
11 changed files with 113 additions and 46 deletions
|
@ -91,7 +91,7 @@ void InAppPurchase::BuildPrototype(v8::Isolate* isolate,
|
|||
&in_app_purchase::FinishAllTransactions)
|
||||
.SetMethod("finishTransactionByDate",
|
||||
&in_app_purchase::FinishTransactionByDate)
|
||||
.SetMethod("getProducts", &in_app_purchase::GetProducts);
|
||||
.SetMethod("getProducts", &InAppPurchase::GetProducts);
|
||||
}
|
||||
|
||||
InAppPurchase::InAppPurchase(v8::Isolate* isolate) {
|
||||
|
@ -100,13 +100,37 @@ InAppPurchase::InAppPurchase(v8::Isolate* isolate) {
|
|||
|
||||
InAppPurchase::~InAppPurchase() {}
|
||||
|
||||
void InAppPurchase::PurchaseProduct(const std::string& product_id,
|
||||
mate::Arguments* args) {
|
||||
v8::Local<v8::Promise> InAppPurchase::PurchaseProduct(
|
||||
const std::string& product_id,
|
||||
mate::Arguments* args) {
|
||||
v8::Isolate* isolate = args->isolate();
|
||||
atom::util::Promise promise(isolate);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
|
||||
int quantity = 1;
|
||||
in_app_purchase::InAppPurchaseCallback callback;
|
||||
args->GetNext(&quantity);
|
||||
args->GetNext(&callback);
|
||||
in_app_purchase::PurchaseProduct(product_id, quantity, callback);
|
||||
|
||||
in_app_purchase::PurchaseProduct(
|
||||
product_id, quantity,
|
||||
base::BindOnce(atom::util::Promise::ResolvePromise<bool>,
|
||||
std::move(promise)));
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
v8::Local<v8::Promise> InAppPurchase::GetProducts(
|
||||
const std::vector<std::string>& productIDs,
|
||||
mate::Arguments* args) {
|
||||
v8::Isolate* isolate = args->isolate();
|
||||
atom::util::Promise promise(isolate);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
|
||||
in_app_purchase::GetProducts(
|
||||
productIDs, base::BindOnce(atom::util::Promise::ResolvePromise<
|
||||
std::vector<in_app_purchase::Product>>,
|
||||
std::move(promise)));
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void InAppPurchase::OnTransactionsUpdated(
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "atom/browser/mac/in_app_purchase.h"
|
||||
#include "atom/browser/mac/in_app_purchase_observer.h"
|
||||
#include "atom/browser/mac/in_app_purchase_product.h"
|
||||
#include "atom/common/promise_util.h"
|
||||
#include "native_mate/handle.h"
|
||||
|
||||
namespace atom {
|
||||
|
@ -30,7 +31,11 @@ class InAppPurchase : public mate::EventEmitter<InAppPurchase>,
|
|||
explicit InAppPurchase(v8::Isolate* isolate);
|
||||
~InAppPurchase() override;
|
||||
|
||||
void PurchaseProduct(const std::string& product_id, mate::Arguments* args);
|
||||
v8::Local<v8::Promise> PurchaseProduct(const std::string& product_id,
|
||||
mate::Arguments* args);
|
||||
|
||||
v8::Local<v8::Promise> GetProducts(const std::vector<std::string>& productIDs,
|
||||
mate::Arguments* args);
|
||||
|
||||
// TransactionObserver:
|
||||
void OnTransactionsUpdated(
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace in_app_purchase {
|
|||
|
||||
// --------------------------- Typedefs ---------------------------
|
||||
|
||||
typedef base::Callback<void(bool isProductValid)> InAppPurchaseCallback;
|
||||
typedef base::OnceCallback<void(bool isProductValid)> InAppPurchaseCallback;
|
||||
|
||||
// --------------------------- Functions ---------------------------
|
||||
|
||||
|
@ -27,7 +27,7 @@ std::string GetReceiptURL(void);
|
|||
|
||||
void PurchaseProduct(const std::string& productID,
|
||||
int quantity,
|
||||
const InAppPurchaseCallback& callback);
|
||||
InAppPurchaseCallback callback);
|
||||
|
||||
} // namespace in_app_purchase
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
NSInteger quantity_;
|
||||
}
|
||||
|
||||
- (id)initWithCallback:(const in_app_purchase::InAppPurchaseCallback&)callback
|
||||
- (id)initWithCallback:(in_app_purchase::InAppPurchaseCallback)callback
|
||||
quantity:(NSInteger)quantity;
|
||||
|
||||
- (void)purchaseProduct:(NSString*)productID;
|
||||
|
@ -42,10 +42,10 @@
|
|||
* @param callback - The callback that will be called when the payment is added
|
||||
* to the queue.
|
||||
*/
|
||||
- (id)initWithCallback:(const in_app_purchase::InAppPurchaseCallback&)callback
|
||||
- (id)initWithCallback:(in_app_purchase::InAppPurchaseCallback)callback
|
||||
quantity:(NSInteger)quantity {
|
||||
if ((self = [super init])) {
|
||||
callback_ = callback;
|
||||
callback_ = std::move(callback);
|
||||
quantity_ = quantity;
|
||||
}
|
||||
|
||||
|
@ -119,8 +119,9 @@
|
|||
*/
|
||||
- (void)runCallback:(bool)isProductValid {
|
||||
if (callback_) {
|
||||
base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI},
|
||||
base::Bind(callback_, isProductValid));
|
||||
base::PostTaskWithTraits(
|
||||
FROM_HERE, {content::BrowserThread::UI},
|
||||
base::BindOnce(std::move(callback_), isProductValid));
|
||||
}
|
||||
// Release this delegate.
|
||||
[self release];
|
||||
|
@ -177,9 +178,9 @@ std::string GetReceiptURL() {
|
|||
|
||||
void PurchaseProduct(const std::string& productID,
|
||||
int quantity,
|
||||
const InAppPurchaseCallback& callback) {
|
||||
auto* iap =
|
||||
[[InAppPurchase alloc] initWithCallback:callback quantity:quantity];
|
||||
InAppPurchaseCallback callback) {
|
||||
auto* iap = [[InAppPurchase alloc] initWithCallback:std::move(callback)
|
||||
quantity:quantity];
|
||||
|
||||
[iap purchaseProduct:base::SysUTF8ToNSString(productID)];
|
||||
}
|
||||
|
|
|
@ -38,13 +38,13 @@ struct Product {
|
|||
|
||||
// --------------------------- Typedefs ---------------------------
|
||||
|
||||
typedef base::Callback<void(const std::vector<in_app_purchase::Product>&)>
|
||||
typedef base::OnceCallback<void(std::vector<in_app_purchase::Product>)>
|
||||
InAppPurchaseProductsCallback;
|
||||
|
||||
// --------------------------- Functions ---------------------------
|
||||
|
||||
void GetProducts(const std::vector<std::string>& productIDs,
|
||||
const InAppPurchaseProductsCallback& callback);
|
||||
InAppPurchaseProductsCallback callback);
|
||||
|
||||
} // namespace in_app_purchase
|
||||
|
||||
|
|
|
@ -23,8 +23,7 @@
|
|||
in_app_purchase::InAppPurchaseProductsCallback callback_;
|
||||
}
|
||||
|
||||
- (id)initWithCallback:
|
||||
(const in_app_purchase::InAppPurchaseProductsCallback&)callback;
|
||||
- (id)initWithCallback:(in_app_purchase::InAppPurchaseProductsCallback)callback;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -38,9 +37,9 @@
|
|||
* @param callback - The callback that will be called to return the products.
|
||||
*/
|
||||
- (id)initWithCallback:
|
||||
(const in_app_purchase::InAppPurchaseProductsCallback&)callback {
|
||||
(in_app_purchase::InAppPurchaseProductsCallback)callback {
|
||||
if ((self = [super init])) {
|
||||
callback_ = callback;
|
||||
callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -81,7 +80,7 @@
|
|||
|
||||
// Send the callback to the browser thread.
|
||||
base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI},
|
||||
base::Bind(callback_, converted));
|
||||
base::BindOnce(std::move(callback_), converted));
|
||||
|
||||
[self release];
|
||||
}
|
||||
|
@ -167,8 +166,9 @@ Product::Product(const Product&) = default;
|
|||
Product::~Product() = default;
|
||||
|
||||
void GetProducts(const std::vector<std::string>& productIDs,
|
||||
const InAppPurchaseProductsCallback& callback) {
|
||||
auto* iapProduct = [[InAppPurchaseProduct alloc] initWithCallback:callback];
|
||||
InAppPurchaseProductsCallback callback) {
|
||||
auto* iapProduct =
|
||||
[[InAppPurchaseProduct alloc] initWithCallback:std::move(callback)];
|
||||
|
||||
// Convert the products' id to NSSet.
|
||||
NSMutableSet* productsIDSet =
|
||||
|
|
|
@ -21,13 +21,23 @@ Returns:
|
|||
|
||||
The `inAppPurchase` module has the following methods:
|
||||
|
||||
|
||||
### `inAppPurchase.purchaseProduct(productID, quantity, callback)`
|
||||
|
||||
* `productID` String - The identifiers of the product to purchase. (The identifier 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.
|
||||
* `isProductValid` Boolean - Determine if the product is valid and added to the payment queue.
|
||||
* `isProductValid` Boolean - Determine if the product is valid and added to the payment queue.
|
||||
|
||||
You should listen for the `transactions-updated` event as soon as possible and certainly before you call `purchaseProduct`.
|
||||
|
||||
**[Deprecated Soon](promisification.md)**
|
||||
|
||||
### `inAppPurchase.purchaseProduct(productID, quantity)`
|
||||
|
||||
* `productID` String - The identifiers of the product to purchase. (The identifier of `com.example.app.product1` is `product1`).
|
||||
* `quantity` Integer (optional) - The number of items the user wants to purchase.
|
||||
|
||||
Returns `Promise<Boolean>` - Returns `true` if the product is valid and added to the payment queue.
|
||||
|
||||
You should listen for the `transactions-updated` event as soon as possible and certainly before you call `purchaseProduct`.
|
||||
|
||||
|
@ -35,7 +45,17 @@ You should listen for the `transactions-updated` event as soon as possible and c
|
|||
|
||||
* `productIDs` String[] - The identifiers of the products to get.
|
||||
* `callback` Function - The callback called with the products or an empty array if the products don't exist.
|
||||
* `products` Product[] - Array of [`Product`](structures/product.md) objects
|
||||
* `products` Product[] - Array of [`Product`](structures/product.md) objects
|
||||
|
||||
Retrieves the product descriptions.
|
||||
|
||||
**[Deprecated Soon](promisification.md)**
|
||||
|
||||
### `inAppPurchase.getProducts(productIDs)`
|
||||
|
||||
* `productIDs` String[] - The identifiers of the products to get.
|
||||
|
||||
Returns `Promise<Product[]>` - Resolves with an array of [`Product`](structures/product.md) objects.
|
||||
|
||||
Retrieves the product descriptions.
|
||||
|
||||
|
@ -47,12 +67,10 @@ Returns `Boolean`, whether a user can make a payment.
|
|||
|
||||
Returns `String`, the path to the receipt.
|
||||
|
||||
|
||||
### `inAppPurchase.finishAllTransactions()`
|
||||
|
||||
Completes all pending transactions.
|
||||
|
||||
|
||||
### `inAppPurchase.finishTransactionByDate(date)`
|
||||
|
||||
* `date` String - The ISO formatted date of the transaction to finish.
|
||||
|
|
|
@ -11,8 +11,6 @@ When a majority of affected functions are migrated, this flag will be enabled by
|
|||
- [app.importCertificate(options, callback)](https://github.com/electron/electron/blob/master/docs/api/app.md#importCertificate)
|
||||
- [dialog.showMessageBox([browserWindow, ]options[, callback])](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showMessageBox)
|
||||
- [dialog.showCertificateTrustDialog([browserWindow, ]options, callback)](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showCertificateTrustDialog)
|
||||
- [inAppPurchase.purchaseProduct(productID, quantity, callback)](https://github.com/electron/electron/blob/master/docs/api/in-app-purchase.md#purchaseProduct)
|
||||
- [inAppPurchase.getProducts(productIDs, callback)](https://github.com/electron/electron/blob/master/docs/api/in-app-purchase.md#getProducts)
|
||||
- [ses.getBlobData(identifier, callback)](https://github.com/electron/electron/blob/master/docs/api/session.md#getBlobData)
|
||||
- [contents.executeJavaScript(code[, userGesture, callback])](https://github.com/electron/electron/blob/master/docs/api/web-contents.md#executeJavaScript)
|
||||
- [contents.print([options], [callback])](https://github.com/electron/electron/blob/master/docs/api/web-contents.md#print)
|
||||
|
@ -38,6 +36,8 @@ When a majority of affected functions are migrated, this flag will be enabled by
|
|||
- [desktopCapturer.getSources(options, callback)](https://github.com/electron/electron/blob/master/docs/api/desktop-capturer.md#getSources)
|
||||
- [dialog.showOpenDialog([browserWindow, ]options[, callback])](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showOpenDialog)
|
||||
- [dialog.showSaveDialog([browserWindow, ]options[, callback])](https://github.com/electron/electron/blob/master/docs/api/dialog.md#showSaveDialog)
|
||||
- [inAppPurchase.purchaseProduct(productID, quantity, callback)](https://github.com/electron/electron/blob/master/docs/api/in-app-purchase.md#purchaseProduct)
|
||||
- [inAppPurchase.getProducts(productIDs, callback)](https://github.com/electron/electron/blob/master/docs/api/in-app-purchase.md#getProducts)
|
||||
- [netLog.stopLogging([callback])](https://github.com/electron/electron/blob/master/docs/api/net-log.md#stopLogging)
|
||||
- [protocol.isProtocolHandled(scheme, callback)](https://github.com/electron/electron/blob/master/docs/api/protocol.md#isProtocolHandled)
|
||||
- [ses.clearHostResolverCache([callback])](https://github.com/electron/electron/blob/master/docs/api/session.md#clearHostResolverCache)
|
||||
|
|
|
@ -12,7 +12,6 @@ Then, you'll need to configure your in-app purchases in iTunes Connect, and incl
|
|||
|
||||
[iTunes Connect Developer Help: Create an in-app purchase](https://help.apple.com/itunes-connect/developer/#/devae49fb316)
|
||||
|
||||
|
||||
### Change the CFBundleIdentifier
|
||||
|
||||
To test In-App Purchase in development with Electron you'll have to change the `CFBundleIdentifier` in `node_modules/electron/dist/Electron.app/Contents/Info.plist`. You have to replace `com.github.electron` by the bundle identifier of the application you created with iTunes Connect.
|
||||
|
@ -22,12 +21,10 @@ To test In-App Purchase in development with Electron you'll have to change the `
|
|||
<string>com.example.app</string>
|
||||
```
|
||||
|
||||
|
||||
## Code example
|
||||
|
||||
Here is an example that shows how to use In-App Purchases in Electron. You'll have to replace the product ids by the identifiers of the products created with iTunes Connect (the identifier of `com.example.app.product1` is `product1`). Note that you have to listen to the `transactions-updated` event as soon as possible in your app.
|
||||
|
||||
|
||||
```javascript
|
||||
const { inAppPurchase } = require('electron').remote
|
||||
const PRODUCT_IDS = ['id1', 'id2']
|
||||
|
@ -95,7 +92,7 @@ if (!inAppPurchase.canMakePayments()) {
|
|||
}
|
||||
|
||||
// Retrieve and display the product descriptions.
|
||||
inAppPurchase.getProducts(PRODUCT_IDS, (products) => {
|
||||
inAppPurchase.getProducts(PRODUCT_IDS).then(products => {
|
||||
// Check the parameters.
|
||||
if (!Array.isArray(products) || products.length <= 0) {
|
||||
console.log('Unable to retrieve the product informations.')
|
||||
|
@ -103,17 +100,16 @@ inAppPurchase.getProducts(PRODUCT_IDS, (products) => {
|
|||
}
|
||||
|
||||
// Display the name and price of each product.
|
||||
products.forEach((product) => {
|
||||
products.forEach(product => {
|
||||
console.log(`The price of ${product.localizedTitle} is ${product.formattedPrice}.`)
|
||||
})
|
||||
|
||||
// Ask the user which product he/she wants to purchase.
|
||||
// ...
|
||||
let selectedProduct = products[0]
|
||||
let selectedQuantity = 1
|
||||
|
||||
// Purchase the selected product.
|
||||
inAppPurchase.purchaseProduct(selectedProduct.productIdentifier, selectedQuantity, (isProductValid) => {
|
||||
inAppPurchase.purchaseProduct(selectedProduct.productIdentifier, selectedQuantity).then(isProductValid => {
|
||||
if (!isProductValid) {
|
||||
console.log('The product is not valid.')
|
||||
return
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
'use strict'
|
||||
|
||||
const { deprecate } = require('electron')
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
const { EventEmitter } = require('events')
|
||||
const { inAppPurchase, InAppPurchase } = process.atomBinding('in_app_purchase')
|
||||
|
@ -18,3 +20,6 @@ if (process.platform === 'darwin') {
|
|||
getReceiptURL: () => ''
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.purchaseProduct = deprecate.promisify(module.exports.purchaseProduct)
|
||||
module.exports.getProducts = deprecate.promisify(module.exports.getProducts)
|
||||
|
|
|
@ -38,21 +38,39 @@ describe('inAppPurchase module', function () {
|
|||
expect(correctUrlEnd).to.be.true()
|
||||
})
|
||||
|
||||
it('purchaseProduct() fails when buying invalid product', done => {
|
||||
it('purchaseProduct() fails when buying invalid product', async () => {
|
||||
const success = await inAppPurchase.purchaseProduct('non-exist', 1)
|
||||
expect(success).to.be.false()
|
||||
})
|
||||
|
||||
// TODO(codebytere): remove when promisification is complete
|
||||
it('purchaseProduct() fails when buying invalid product (callback)', done => {
|
||||
inAppPurchase.purchaseProduct('non-exist', 1, success => {
|
||||
expect(success).to.be.false()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('purchaseProduct() accepts optional arguments', done => {
|
||||
inAppPurchase.purchaseProduct('non-exist', () => {
|
||||
inAppPurchase.purchaseProduct('non-exist', 1)
|
||||
it('purchaseProduct() accepts optional arguments', async () => {
|
||||
const success = await inAppPurchase.purchaseProduct('non-exist')
|
||||
expect(success).to.be.false()
|
||||
})
|
||||
|
||||
// TODO(codebytere): remove when promisification is complete
|
||||
it('purchaseProduct() accepts optional arguments (callback)', done => {
|
||||
inAppPurchase.purchaseProduct('non-exist', success => {
|
||||
expect(success).to.be.false()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('getProducts() returns an empty list when getting invalid product', done => {
|
||||
it('getProducts() returns an empty list when getting invalid product', async () => {
|
||||
const products = await inAppPurchase.getProducts(['non-exist'])
|
||||
expect(products).to.be.an('array').of.length(0)
|
||||
})
|
||||
|
||||
// TODO(codebytere): remove when promisification is complete
|
||||
it('getProducts() returns an empty list when getting invalid product (callback)', done => {
|
||||
inAppPurchase.getProducts(['non-exist'], products => {
|
||||
expect(products).to.be.an('array').of.length(0)
|
||||
done()
|
||||
|
|
Loading…
Reference in a new issue