From dba8a0caa8ee32361db254cc85b55d1554aa7beb Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Tue, 17 Dec 2019 07:07:11 -0800 Subject: [PATCH] feat: enable explicit IAP restoration (#21461) --- docs/api/in-app-purchase.md | 10 ++++-- shell/browser/api/atom_api_in_app_purchase.cc | 2 ++ shell/browser/mac/in_app_purchase.h | 8 +++-- shell/browser/mac/in_app_purchase.mm | 4 +++ shell/browser/mac/in_app_purchase_observer.h | 2 +- shell/browser/mac/in_app_purchase_observer.mm | 4 +-- spec-main/api-in-app-purchase-spec.ts | 36 +++++++++++-------- 7 files changed, 43 insertions(+), 23 deletions(-) diff --git a/docs/api/in-app-purchase.md b/docs/api/in-app-purchase.md index c30f3c2c9fae..b9af8e8e5906 100644 --- a/docs/api/in-app-purchase.md +++ b/docs/api/in-app-purchase.md @@ -40,11 +40,17 @@ Retrieves the product descriptions. ### `inAppPurchase.canMakePayments()` -Returns `Boolean`, whether a user can make a payment. +Returns `Boolean` - whether a user can make a payment. + +### `inAppPurchase.restoreCompletedTransactions()` + +Restores finished transactions. This method can be called either to install purchases on additional devices, or to restore purchases for an application that the user deleted and reinstalled. + +[The payment queue](https://developer.apple.com/documentation/storekit/skpaymentqueue?language=objc) delivers a new transaction for each previously completed transaction that can be restored. Each transaction includes a copy of the original transaction. ### `inAppPurchase.getReceiptURL()` -Returns `String`, the path to the receipt. +Returns `String` - the path to the receipt. ### `inAppPurchase.finishAllTransactions()` diff --git a/shell/browser/api/atom_api_in_app_purchase.cc b/shell/browser/api/atom_api_in_app_purchase.cc index 293eb549f0f7..c56349407742 100644 --- a/shell/browser/api/atom_api_in_app_purchase.cc +++ b/shell/browser/api/atom_api_in_app_purchase.cc @@ -85,6 +85,8 @@ void InAppPurchase::BuildPrototype(v8::Isolate* isolate, prototype->SetClassName(gin::StringToV8(isolate, "InAppPurchase")); gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) .SetMethod("canMakePayments", &in_app_purchase::CanMakePayments) + .SetMethod("restoreCompletedTransactions", + &in_app_purchase::RestoreCompletedTransactions) .SetMethod("getReceiptURL", &in_app_purchase::GetReceiptURL) .SetMethod("purchaseProduct", &InAppPurchase::PurchaseProduct) .SetMethod("finishAllTransactions", diff --git a/shell/browser/mac/in_app_purchase.h b/shell/browser/mac/in_app_purchase.h index 7534823aa10c..6411035d6485 100644 --- a/shell/browser/mac/in_app_purchase.h +++ b/shell/browser/mac/in_app_purchase.h @@ -17,13 +17,15 @@ typedef base::OnceCallback InAppPurchaseCallback; // --------------------------- Functions --------------------------- -bool CanMakePayments(void); +bool CanMakePayments(); -void FinishAllTransactions(void); +void RestoreCompletedTransactions(); + +void FinishAllTransactions(); void FinishTransactionByDate(const std::string& date); -std::string GetReceiptURL(void); +std::string GetReceiptURL(); void PurchaseProduct(const std::string& productID, int quantity, diff --git a/shell/browser/mac/in_app_purchase.mm b/shell/browser/mac/in_app_purchase.mm index ee9842ba9066..23c8ad22a676 100644 --- a/shell/browser/mac/in_app_purchase.mm +++ b/shell/browser/mac/in_app_purchase.mm @@ -141,6 +141,10 @@ bool CanMakePayments() { return [SKPaymentQueue canMakePayments]; } +void RestoreCompletedTransactions() { + [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; +} + void FinishAllTransactions() { for (SKPaymentTransaction* transaction in SKPaymentQueue.defaultQueue .transactions) { diff --git a/shell/browser/mac/in_app_purchase_observer.h b/shell/browser/mac/in_app_purchase_observer.h index f16af8696f02..20130ae0723a 100644 --- a/shell/browser/mac/in_app_purchase_observer.h +++ b/shell/browser/mac/in_app_purchase_observer.h @@ -51,7 +51,7 @@ class TransactionObserver { const std::vector& transactions) = 0; private: - InAppTransactionObserver* obeserver_; + InAppTransactionObserver* observer_; base::WeakPtrFactory weak_ptr_factory_; diff --git a/shell/browser/mac/in_app_purchase_observer.mm b/shell/browser/mac/in_app_purchase_observer.mm index 7133628a4197..fb9273313142 100644 --- a/shell/browser/mac/in_app_purchase_observer.mm +++ b/shell/browser/mac/in_app_purchase_observer.mm @@ -183,14 +183,14 @@ Transaction::Transaction(const Transaction&) = default; Transaction::~Transaction() = default; TransactionObserver::TransactionObserver() : weak_ptr_factory_(this) { - obeserver_ = [[InAppTransactionObserver alloc] + observer_ = [[InAppTransactionObserver alloc] initWithCallback:base::BindRepeating( &TransactionObserver::OnTransactionsUpdated, weak_ptr_factory_.GetWeakPtr())]; } TransactionObserver::~TransactionObserver() { - [obeserver_ release]; + [observer_ release]; } } // namespace in_app_purchase diff --git a/spec-main/api-in-app-purchase-spec.ts b/spec-main/api-in-app-purchase-spec.ts index f69fca38a530..9a29d89566da 100644 --- a/spec-main/api-in-app-purchase-spec.ts +++ b/spec-main/api-in-app-purchase-spec.ts @@ -6,9 +6,14 @@ describe('inAppPurchase module', function () { this.timeout(3 * 60 * 1000) - it('canMakePayments() does not throw', () => { + it('canMakePayments() returns a boolean', () => { + const canMakePayments = inAppPurchase.canMakePayments() + expect(canMakePayments).to.be.a('boolean') + }) + + it('restoreCompletedTransactions() does not throw', () => { expect(() => { - inAppPurchase.canMakePayments() + inAppPurchase.restoreCompletedTransactions() }).to.not.throw() }) @@ -29,22 +34,23 @@ describe('inAppPurchase module', function () { }) // The following three tests are disabled because they hit Apple servers, and - // Apple started blocking requests from AWS IPs (we think), so they fail on - // CI. + // Apple started blocking requests from AWS IPs (we think), so they fail on CI. // TODO: find a way to mock out the server requests so we can test these APIs // without relying on a remote service. - xit('purchaseProduct() fails when buying invalid product', async () => { - const success = await inAppPurchase.purchaseProduct('non-exist', 1) - expect(success).to.be.false('failed to purchase non-existent product') - }) + xdescribe('handles product purchases', () => { + it('purchaseProduct() fails when buying invalid product', async () => { + const success = await inAppPurchase.purchaseProduct('non-exist', 1) + expect(success).to.be.false('failed to purchase non-existent product') + }) - xit('purchaseProduct() accepts optional arguments', async () => { - const success = await inAppPurchase.purchaseProduct('non-exist') - expect(success).to.be.false('failed to purchase non-existent product') - }) + it('purchaseProduct() accepts optional arguments', async () => { + const success = await inAppPurchase.purchaseProduct('non-exist') + expect(success).to.be.false('failed to purchase non-existent product') + }) - xit('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) + 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) + }) }) })