8000 feat: promisify In-App Purchase by codebytere · Pull Request #17355 · electron/electron · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: promisify In-App Purchase #17355

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions atom/browser/api/atom_api_in_app_purchase.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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(
Expand Down
7 changes: 6 additions & 1 deletion atom/browser/api/atom_api_in_app_purchase.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions atom/browser/mac/in_app_purchase.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace in_app_purchase {

// --------------------------- Typedefs ---------------------------

typedef base::Callback<void(bool isProductValid)> InAppPurchaseCallback;
typedef base::OnceCallback<void(bool isProductValid)> InAppPurchaseCallback;

// --------------------------- Functions ---------------------------

Expand All @@ -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

Expand Down
17 changes: 9 additions & 8 deletions atom/browser/mac/in_app_purchase.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ @interface InAppPurchase : NSObject <SKProductsRequestDelegate> {
NSInteger quantity_;
}

- (id)initWithCallback:(const in_app_purchase::InAppPurchaseCallback&)callback
- (id)initWithCallback:(in_app_purchase::InAppPurchaseCallback)callback
quantity:(NSInteger)quantity;

- (void)purchaseProduct:(NSString*)productID;
Expand All @@ -42,10 +42,10 @@ @implementation InAppPurchase
* @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;
}

Expand Down Expand Up @@ -119,8 +119,9 @@ - (void)checkout:(SKProduct*)product {
*/
- (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];
Expand Down Expand Up @@ -177,9 +178,9 @@ void FinishTransactionByDate(const std::string& date) {

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)];
}
Expand Down
4 changes: 2 additions & 2 deletions atom/browser/mac/in_app_purchase_product.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 7 additions & 7 deletions atom/browser/mac/in_app_purchase_product.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ @interface InAppPurchaseProduct : NSObject <SKProductsRequestDelegate> {
in_app_purchase::InAppPurchaseProductsCallback callback_;
}

- (id)initWithCallback:
(const in_app_purchase::InAppPurchaseProductsCallback&)callback;
- (id)initWithCallback:(in_app_purchase::InAppPurchaseProductsCallback)callback;

@end

Expand All @@ -38,9 +37,9 @@ @implementation InAppPurchaseProduct
* @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;
Expand Down Expand Up @@ -81,7 +80,7 @@ - (void)productsRequest:(SKProductsRequest*)request

// 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];
}
Expand Down Expand Up @@ -167,8 +166,9 @@ - (NSString*)formatPrice:(NSDecimalNumber*)price
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 =
Expand Down
28 changes: 23 additions & 5 deletions docs/api/in-app-purchase.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,41 @@ 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`.

### `inAppPurchase.getProducts(productIDs, callback)`

* `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.

Expand All @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions docs/api/promisification.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
12 changes: 4 additions & 8 deletions docs/tutorial/in-app-purchases.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Preparing

### Paid Applications Agreement
If you haven't already, you’ll need to sign the Paid Applications Agreement and set up your banking and tax information in iTunes Connect.
If you haven't already, you’ll need to sign the Paid Applications Agreement and set up your banking and tax information in iTunes Connect.

[iTunes Connect Developer Help: Agreements, tax, and banking overview](https://help.apple.com/itunes-connect/developer/#/devb6df5ee51)

Expand All @@ -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.
Expand All @@ -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']
Expand Down Expand Up @@ -95,25 +92,24 @@ 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.')
return
}

// 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
Expand Down
5 changes: 5 additions & 0 deletions lib/browser/api/in-app-purchase.js
Original file line number Diff line number Diff line change
@@ -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')
Expand All @@ -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)
28 changes: 23 additions & 5 deletions spec/api-in-app-purchase-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
0