Commit 2b46718c authored by Glen Robertson's avatar Glen Robertson Committed by Commit Bot

DigitalGoods: Implement ListPurchases on Blink side.

Note this will need https://crrev.com/c/2484038 (ListPurchases mojo)
and a Clank-side implementation to land.

Explainer:
https://github.com/WICG/digital-goods/blob/master/explainer.md

Bug: 1139795
Change-Id: Ia70a8af535b14943322d1c2bd671bcf1d2726744
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2486007
Commit-Queue: Glen Robertson <glenrob@chromium.org>
Commit-Queue: Dominick Ng <dominickn@chromium.org>
Auto-Submit: Glen Robertson <glenrob@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarGlen Robertson <glenrob@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#824707}
parent c2e5609b
...@@ -430,6 +430,7 @@ static_idl_files_in_modules = get_path_info( ...@@ -430,6 +430,7 @@ static_idl_files_in_modules = get_path_info(
"//third_party/blink/renderer/modules/payments/goods/digital_goods_service.idl", "//third_party/blink/renderer/modules/payments/goods/digital_goods_service.idl",
"//third_party/blink/renderer/modules/payments/goods/dom_window_digital_goods.idl", "//third_party/blink/renderer/modules/payments/goods/dom_window_digital_goods.idl",
"//third_party/blink/renderer/modules/payments/goods/item_details.idl", "//third_party/blink/renderer/modules/payments/goods/item_details.idl",
"//third_party/blink/renderer/modules/payments/goods/purchase_details.idl",
"//third_party/blink/renderer/modules/payments/html_iframe_element_payments.idl", "//third_party/blink/renderer/modules/payments/html_iframe_element_payments.idl",
"//third_party/blink/renderer/modules/payments/image_object.idl", "//third_party/blink/renderer/modules/payments/image_object.idl",
"//third_party/blink/renderer/modules/payments/merchant_validation_event.idl", "//third_party/blink/renderer/modules/payments/merchant_validation_event.idl",
......
...@@ -4,11 +4,12 @@ ...@@ -4,11 +4,12 @@
#include <utility> #include <utility>
#include "third_party/blink/renderer/modules/payments/goods/digital_goods_service.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h" #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_item_details.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_item_details.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_purchase_details.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/modules/payments/goods/digital_goods_service.h"
#include "third_party/blink/renderer/modules/payments/goods/digital_goods_type_converters.h" #include "third_party/blink/renderer/modules/payments/goods/digital_goods_type_converters.h"
#include "third_party/blink/renderer/platform/heap/persistent.h" #include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/wtf/vector.h" #include "third_party/blink/renderer/platform/wtf/vector.h"
...@@ -43,6 +44,21 @@ void OnAcknowledgeResponse(ScriptPromiseResolver* resolver, ...@@ -43,6 +44,21 @@ void OnAcknowledgeResponse(ScriptPromiseResolver* resolver,
resolver->Resolve(); resolver->Resolve();
} }
void OnListPurchasesResponse(
ScriptPromiseResolver* resolver,
BillingResponseCode code,
Vector<payments::mojom::blink::PurchaseDetailsPtr> purchase_details_list) {
if (code != BillingResponseCode::kOk) {
resolver->Reject(mojo::ConvertTo<String>(code));
return;
}
HeapVector<Member<PurchaseDetails>> blink_purchase_details_list;
for (const auto& detail : purchase_details_list)
blink_purchase_details_list.push_back(detail.To<blink::PurchaseDetails*>());
resolver->Resolve(std::move(blink_purchase_details_list));
}
} // namespace } // namespace
DigitalGoodsService::DigitalGoodsService( DigitalGoodsService::DigitalGoodsService(
...@@ -101,6 +117,15 @@ ScriptPromise DigitalGoodsService::acknowledge(ScriptState* script_state, ...@@ -101,6 +117,15 @@ ScriptPromise DigitalGoodsService::acknowledge(ScriptState* script_state,
return promise; return promise;
} }
ScriptPromise DigitalGoodsService::listPurchases(ScriptState* script_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
mojo_service_->ListPurchases(
WTF::Bind(&OnListPurchasesResponse, WrapPersistent(resolver)));
return promise;
}
void DigitalGoodsService::Trace(Visitor* visitor) const { void DigitalGoodsService::Trace(Visitor* visitor) const {
ScriptWrappable::Trace(visitor); ScriptWrappable::Trace(visitor);
} }
......
...@@ -27,6 +27,7 @@ class DigitalGoodsService final : public ScriptWrappable { ...@@ -27,6 +27,7 @@ class DigitalGoodsService final : public ScriptWrappable {
ScriptPromise acknowledge(ScriptState*, ScriptPromise acknowledge(ScriptState*,
const String& purchase_token, const String& purchase_token,
const String& purchase_type); const String& purchase_type);
ScriptPromise listPurchases(ScriptState*);
void Trace(Visitor* visitor) const override; void Trace(Visitor* visitor) const override;
......
...@@ -12,6 +12,9 @@ ...@@ -12,6 +12,9 @@
[CallWith=ScriptState, MeasureAs=DigitalGoodsAcknowledge] [CallWith=ScriptState, MeasureAs=DigitalGoodsAcknowledge]
Promise<void> acknowledge(DOMString purchaseToken, PurchaseType purchaseType); Promise<void> acknowledge(DOMString purchaseToken, PurchaseType purchaseType);
[CallWith=ScriptState, MeasureAs=DigitalGoodsListPurchases]
Promise<sequence<PurchaseDetails>> listPurchases();
}; };
enum PurchaseType { enum PurchaseType {
......
...@@ -11,6 +11,7 @@ namespace mojo { ...@@ -11,6 +11,7 @@ namespace mojo {
using payments::mojom::blink::BillingResponseCode; using payments::mojom::blink::BillingResponseCode;
using payments::mojom::blink::ItemDetailsPtr; using payments::mojom::blink::ItemDetailsPtr;
using payments::mojom::blink::PurchaseDetailsPtr;
blink::ItemDetails* TypeConverter<blink::ItemDetails*, ItemDetailsPtr>::Convert( blink::ItemDetails* TypeConverter<blink::ItemDetails*, ItemDetailsPtr>::Convert(
const ItemDetailsPtr& input) { const ItemDetailsPtr& input) {
...@@ -60,4 +61,29 @@ WTF::String TypeConverter<WTF::String, BillingResponseCode>::Convert( ...@@ -60,4 +61,29 @@ WTF::String TypeConverter<WTF::String, BillingResponseCode>::Convert(
NOTREACHED(); NOTREACHED();
} }
blink::PurchaseDetails*
TypeConverter<blink::PurchaseDetails*, PurchaseDetailsPtr>::Convert(
const PurchaseDetailsPtr& input) {
if (!input)
return nullptr;
blink::PurchaseDetails* output = blink::PurchaseDetails::Create();
output->setItemId(input->item_id);
output->setPurchaseToken(input->purchase_token);
output->setAcknowledged(input->acknowledged);
switch (input->purchase_state) {
case payments::mojom::blink::PurchaseState::kUnknown:
// Omit setting PurchaseState on output.
break;
case payments::mojom::blink::PurchaseState::kPurchased:
output->setPurchaseState("purchased");
break;
case payments::mojom::blink::PurchaseState::kPending:
output->setPurchaseState("pending");
break;
}
output->setPurchaseTime(input->purchase_time.InMilliseconds());
output->setWillAutoRenew(input->will_auto_renew);
return output;
}
} // namespace mojo } // namespace mojo
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "mojo/public/cpp/bindings/type_converter.h" #include "mojo/public/cpp/bindings/type_converter.h"
#include "third_party/blink/public/mojom/digital_goods/digital_goods.mojom-blink-forward.h" #include "third_party/blink/public/mojom/digital_goods/digital_goods.mojom-blink-forward.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_item_details.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_item_details.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_purchase_details.h"
#include "third_party/blink/renderer/modules/modules_export.h" #include "third_party/blink/renderer/modules/modules_export.h"
namespace mojo { namespace mojo {
...@@ -30,6 +31,16 @@ struct MODULES_EXPORT ...@@ -30,6 +31,16 @@ struct MODULES_EXPORT
const payments::mojom::blink::BillingResponseCode& input); const payments::mojom::blink::BillingResponseCode& input);
}; };
// Converts a mojo PurchaseDetails into a WebIDL PurchaseDetails.
// Returns a null IDL struct when a null mojo struct is given as input.
template <>
struct MODULES_EXPORT
TypeConverter<blink::PurchaseDetails*,
payments::mojom::blink::PurchaseDetailsPtr> {
static blink::PurchaseDetails* Convert(
const payments::mojom::blink::PurchaseDetailsPtr& input);
};
} // namespace mojo } // namespace mojo
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_PAYMENTS_GOODS_DIGITAL_GOODS_TYPE_CONVERTERS_H_ #endif // THIRD_PARTY_BLINK_RENDERER_MODULES_PAYMENTS_GOODS_DIGITAL_GOODS_TYPE_CONVERTERS_H_
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include <string> #include <string>
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/digital_goods/digital_goods.mojom-blink.h" #include "third_party/blink/public/mojom/digital_goods/digital_goods.mojom-blink.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_item_details.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_item_details.h"
...@@ -14,14 +15,19 @@ namespace blink { ...@@ -14,14 +15,19 @@ namespace blink {
using payments::mojom::blink::BillingResponseCode; using payments::mojom::blink::BillingResponseCode;
TEST(DigitalGoodsTypeConvertersTest, MojoBillingResponseOkToIdl) { TEST(DigitalGoodsTypeConvertersTest, MojoBillingResponseToIdl) {
auto response_code = BillingResponseCode::kOk; EXPECT_EQ(mojo::ConvertTo<String>(BillingResponseCode::kOk), "ok");
EXPECT_EQ(mojo::ConvertTo<String>(response_code), "ok"); EXPECT_EQ(mojo::ConvertTo<String>(BillingResponseCode::kError), "error");
} EXPECT_EQ(mojo::ConvertTo<String>(BillingResponseCode::kItemAlreadyOwned),
"itemAlreadyOwned");
TEST(DigitalGoodsTypeConvertersTest, MojoBillingResponseErrorToIdl) { EXPECT_EQ(mojo::ConvertTo<String>(BillingResponseCode::kItemNotOwned),
auto response_code = BillingResponseCode::kError; "itemNotOwned");
EXPECT_EQ(mojo::ConvertTo<String>(response_code), "error"); EXPECT_EQ(mojo::ConvertTo<String>(BillingResponseCode::kItemUnavailable),
"itemUnavailable");
EXPECT_EQ(mojo::ConvertTo<String>(BillingResponseCode::kClientAppUnavailable),
"clientAppUnavailable");
EXPECT_EQ(mojo::ConvertTo<String>(BillingResponseCode::kClientAppError),
"clientAppError");
} }
TEST(DigitalGoodsTypeConvertersTest, MojoItemDetailsToIdl_WithOptionalFields) { TEST(DigitalGoodsTypeConvertersTest, MojoItemDetailsToIdl_WithOptionalFields) {
...@@ -102,4 +108,70 @@ TEST(DigitalGoodsTypeConvertersTest, NullMojoItemDetailsToIdl) { ...@@ -102,4 +108,70 @@ TEST(DigitalGoodsTypeConvertersTest, NullMojoItemDetailsToIdl) {
EXPECT_EQ(idl_item_details, nullptr); EXPECT_EQ(idl_item_details, nullptr);
} }
TEST(DigitalGoodsTypeConvertersTest, MojoPurchaseDetailsToIdl) {
auto mojo_purchase_details = payments::mojom::blink::PurchaseDetails::New();
const String item_id = "shiny-sword-id";
const String purchase_token = "purchase-token-for-shiny-sword";
const bool acknowledged = true;
// Time in ms since Unix epoch.
const uint64_t purchase_time_ms = 1600123456789L;
const base::TimeDelta purchase_time =
base::TimeDelta::FromMilliseconds(purchase_time_ms);
const bool will_auto_renew = false;
mojo_purchase_details->item_id = item_id;
mojo_purchase_details->purchase_token = purchase_token;
mojo_purchase_details->acknowledged = acknowledged;
mojo_purchase_details->purchase_state =
payments::mojom::blink::PurchaseState::kPurchased;
mojo_purchase_details->purchase_time = purchase_time;
mojo_purchase_details->will_auto_renew = will_auto_renew;
auto* idl_purchase_details = mojo_purchase_details.To<PurchaseDetails*>();
EXPECT_EQ(idl_purchase_details->itemId(), item_id);
EXPECT_EQ(idl_purchase_details->purchaseToken(), purchase_token);
EXPECT_EQ(idl_purchase_details->acknowledged(), acknowledged);
EXPECT_EQ(idl_purchase_details->purchaseState(), "purchased");
EXPECT_EQ(idl_purchase_details->purchaseTime(), purchase_time_ms);
EXPECT_EQ(idl_purchase_details->willAutoRenew(), will_auto_renew);
}
TEST(DigitalGoodsTypeConvertersTest, MojoPurchaseDetailsToIdl2) {
auto mojo_purchase_details = payments::mojom::blink::PurchaseDetails::New();
const bool acknowledged = false;
const bool will_auto_renew = true;
mojo_purchase_details->item_id = "item_id";
mojo_purchase_details->purchase_token = "purchase_token";
mojo_purchase_details->acknowledged = acknowledged;
mojo_purchase_details->purchase_state =
payments::mojom::blink::PurchaseState::kPending;
// "null" purchase time.
mojo_purchase_details->purchase_time = base::TimeDelta();
mojo_purchase_details->will_auto_renew = will_auto_renew;
auto* idl_purchase_details = mojo_purchase_details.To<PurchaseDetails*>();
EXPECT_EQ(idl_purchase_details->acknowledged(), acknowledged);
EXPECT_EQ(idl_purchase_details->purchaseState(), "pending");
EXPECT_EQ(idl_purchase_details->purchaseTime(), 0UL);
EXPECT_EQ(idl_purchase_details->willAutoRenew(), will_auto_renew);
}
TEST(DigitalGoodsTypeConvertersTest,
MojoPurchaseDetailsToIdlUnknownPurchaseState) {
auto mojo_purchase_details = payments::mojom::blink::PurchaseDetails::New();
mojo_purchase_details->purchase_state =
payments::mojom::blink::PurchaseState::kUnknown;
auto* idl_purchase_details = mojo_purchase_details.To<PurchaseDetails*>();
EXPECT_FALSE(idl_purchase_details->hasPurchaseState());
}
TEST(DigitalGoodsTypeConvertersTest, NullMojoPurchaseDetailsToIdl) {
payments::mojom::blink::PurchaseDetailsPtr mojo_purchase_details;
auto* idl_purchase_details = mojo_purchase_details.To<PurchaseDetails*>();
EXPECT_EQ(idl_purchase_details, nullptr);
}
} // namespace blink } // namespace blink
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
modules_idl_files = [ "digital_goods_service.idl" ] modules_idl_files = [ "digital_goods_service.idl" ]
modules_dictionary_idl_files = [ "item_details.idl" ] modules_dictionary_idl_files = [
"item_details.idl",
"purchase_details.idl",
]
modules_dependency_idl_files = [ "dom_window_digital_goods.idl" ] modules_dependency_idl_files = [ "dom_window_digital_goods.idl" ]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// https://github.com/WICG/digital-goods/blob/master/explainer.md
dictionary PurchaseDetails {
required DOMString itemId;
required DOMString purchaseToken;
boolean acknowledged = false;
PurchaseState purchaseState;
// Timestamp in ms since 1970-01-01 00:00 UTC.
DOMTimeStamp purchaseTime;
boolean willAutoRenew = false;
};
enum PurchaseState {
"purchased",
"pending",
};
...@@ -127,6 +127,31 @@ digital_goods_test(async service => { ...@@ -127,6 +127,31 @@ digital_goods_test(async service => {
} }
}, {title: 'Acknowledge bad purchase type should error.'}); }, {title: 'Acknowledge bad purchase type should error.'});
digital_goods_test(async service => {
const response = await service.listPurchases();
assert_equals(response.length, 10);
assert_equals(response[0].itemId, 'id:0');
assert_equals(response[0].purchaseToken, 'purchaseToken:0');
assert_equals(response[0].acknowledged, false);
assert_equals(response[0].purchaseState, undefined);
// 0 time value passed through as 0.
assert_equals(response[0].purchaseTime, 0);
assert_equals(response[0].willAutoRenew, false);
assert_equals(response[1].acknowledged, true);
assert_equals(response[1].purchaseState, 'purchased');
// Expecting 1 second in ms after Unix epoch.
assert_equals(response[1].purchaseTime, 1000);
assert_equals(response[1].willAutoRenew, true);
assert_equals(response[2].purchaseState, 'pending');
// Expecting 2 seconds in ms after Unix epoch.
assert_equals(response[2].purchaseTime, 2000);
}, {title: 'ListPurchases round trip.'});
digital_goods_test(async service => { digital_goods_test(async service => {
const response1 = await service.getDetails(['id1']); const response1 = await service.getDetails(['id1']);
assert_equals(response1.length, 1); assert_equals(response1.length, 1);
......
...@@ -28,7 +28,7 @@ class MockDigitalGoods { ...@@ -28,7 +28,7 @@ class MockDigitalGoods {
// price is a payments.mojom.PaymentCurrencyAmount. // price is a payments.mojom.PaymentCurrencyAmount.
itemDetails.price = {}; itemDetails.price = {};
itemDetails.price.currency = 'AUD'; itemDetails.price.currency = 'AUD';
// Set price.value as on number in |id| dollars. // Set price.value as a number in |id| dollars.
const matchNum = id.match(/\d+/); const matchNum = id.match(/\d+/);
const num = matchNum ? matchNum[0] : 0; const num = matchNum ? matchNum[0] : 0;
itemDetails.price.value = num + '.00'; itemDetails.price.value = num + '.00';
...@@ -74,11 +74,43 @@ class MockDigitalGoods { ...@@ -74,11 +74,43 @@ class MockDigitalGoods {
} }
return {code: /*BillingResponseCode.kOk=*/0}; return {code: /*BillingResponseCode.kOk=*/0};
} }
makePurchaseDetails_(id) {
// purchaseDetails is a payments.mojom.PurchaseDetails.
let purchaseDetails = {};
purchaseDetails.itemId = 'id:' + id;
purchaseDetails.purchaseToken = 'purchaseToken:' + id;
purchaseDetails.acknowledged = Boolean(id % 2);
const purchaseStates = [
payments.mojom.PurchaseState.kUnknown,
payments.mojom.PurchaseState.kPurchased,
payments.mojom.PurchaseState.kPending,
];
purchaseDetails.purchaseState = purchaseStates[id % 3];
purchaseDetails.purchaseTime = new mojoBase.mojom.Time();
// Use idNum as seconds. |microseconds| is since Unix epoch.
purchaseDetails.purchaseTime.microseconds = id * 1000 * 1000;
purchaseDetails.willAutoRenew = Boolean(id % 2);
return purchaseDetails;
}
async listPurchases() {
this.actionResolve_('listPurchases');
let result = [];
for (let i = 0; i < 10; i++) {
result.push(this.makePurchaseDetails_(i));
}
return {
code: /*BillingResponseCode.kOk=*/0,
purchaseDetailsList: result
};
}
} }
let mockDigitalGoods = new MockDigitalGoods(); let mockDigitalGoods = new MockDigitalGoods();
class MockDigitalGoodsFactory { class MockDigitalGoodsFactory {
constructor() { constructor() {
this.interceptor_ = this.interceptor_ =
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment