blob: 4ac6502bbe242143848ab3c1195aa56d77a8faa4 [file] [log] [blame]
// Copyright 2019 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.
#include "components/payments/content/payment_app.h"
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
#include "components/payments/content/autofill_payment_app.h"
#include "components/payments/content/service_worker_payment_app.h"
#include "components/payments/core/features.h"
#include "content/public/browser/stored_payment_app.h"
#include "content/public/browser/supported_delegations.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_web_contents_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
namespace payments {
namespace {
enum class RequiredPaymentOptions {
// None of the shipping address or contact information is required.
kNone,
// Shipping Address is required.
kShippingAddress,
// Payer's contact information(phone, name, email) is required.
kContactInformation,
// Payer's email is required.
kPayerEmail,
// Both contact information and shipping address are required.
kContactInformationAndShippingAddress,
};
} // namespace
class PaymentAppTest : public testing::TestWithParam<RequiredPaymentOptions>,
public PaymentRequestSpec::Observer {
protected:
PaymentAppTest()
: address_(autofill::test::GetFullProfile()),
local_card_(autofill::test::GetCreditCard()),
billing_profiles_({&address_}),
required_options_(GetParam()) {
local_card_.set_billing_address_id(address_.guid());
CreateSpec();
web_contents_ =
test_web_contents_factory_.CreateWebContents(&browser_context_);
}
std::unique_ptr<ServiceWorkerPaymentApp> CreateServiceWorkerPaymentApp(
bool can_preselect,
bool handles_shipping,
bool handles_name,
bool handles_phone,
bool handles_email) {
std::unique_ptr<content::StoredPaymentApp> stored_app =
std::make_unique<content::StoredPaymentApp>();
stored_app->registration_id = 123456;
stored_app->scope = GURL("https://bobpay.com");
stored_app->name = "bobpay";
stored_app->icon = std::make_unique<SkBitmap>();
if (can_preselect) {
PopulateIcon(stored_app->icon.get());
}
if (handles_shipping) {
stored_app->supported_delegations.shipping_address = true;
}
if (handles_name) {
stored_app->supported_delegations.payer_name = true;
}
if (handles_phone) {
stored_app->supported_delegations.payer_phone = true;
}
if (handles_email) {
stored_app->supported_delegations.payer_email = true;
}
return std::make_unique<ServiceWorkerPaymentApp>(
web_contents_, GURL("https://testmerchant.com"),
GURL("https://testmerchant.com/bobpay"), spec_->AsWeakPtr(),
std::move(stored_app), /*is_incognito=*/false,
/*show_processing_spinner=*/base::DoNothing());
}
std::unique_ptr<ServiceWorkerPaymentApp>
CreateInstallableServiceWorkerPaymentApp(bool can_preselect,
bool handles_shipping,
bool handles_name,
bool handles_phone,
bool handles_email) {
auto installable_app = std::make_unique<WebAppInstallationInfo>();
installable_app->name = "installable_pay";
installable_app->sw_js_url = "https://pay.example/app.js";
installable_app->sw_scope = "https://pay.example";
installable_app->icon = std::make_unique<SkBitmap>();
if (can_preselect)
PopulateIcon(installable_app->icon.get());
if (handles_shipping)
installable_app->supported_delegations.shipping_address = true;
if (handles_name)
installable_app->supported_delegations.payer_name = true;
if (handles_phone)
installable_app->supported_delegations.payer_phone = true;
if (handles_email)
installable_app->supported_delegations.payer_email = true;
return std::make_unique<ServiceWorkerPaymentApp>(
web_contents_, GURL("https://merchant.example"),
GURL("https://merchant.example/iframe"), spec_->AsWeakPtr(),
std::move(installable_app), "https://pay.example",
/*is_incognito=*/false, /*show_processing_spinner=*/base::DoNothing());
}
static void PopulateIcon(SkBitmap* icon) {
constexpr int kBitmapDimension = 16;
icon->allocN32Pixels(kBitmapDimension, kBitmapDimension);
icon->eraseColor(SK_ColorRED);
}
autofill::CreditCard& local_credit_card() { return local_card_; }
std::vector<autofill::AutofillProfile*>& billing_profiles() {
return billing_profiles_;
}
RequiredPaymentOptions required_options() const { return required_options_; }
private:
// PaymentRequestSpec::Observer
void OnSpecUpdated() override {}
void CreateSpec() {
std::vector<mojom::PaymentMethodDataPtr> method_data;
mojom::PaymentOptionsPtr payment_options = mojom::PaymentOptions::New();
switch (required_options_) {
case RequiredPaymentOptions::kNone:
break;
case RequiredPaymentOptions::kShippingAddress:
payment_options->request_shipping = true;
break;
case RequiredPaymentOptions::kContactInformation:
payment_options->request_payer_name = true;
payment_options->request_payer_email = true;
payment_options->request_payer_phone = true;
break;
case RequiredPaymentOptions::kPayerEmail:
payment_options->request_payer_email = true;
break;
case RequiredPaymentOptions::kContactInformationAndShippingAddress:
payment_options->request_shipping = true;
payment_options->request_payer_name = true;
payment_options->request_payer_email = true;
payment_options->request_payer_phone = true;
break;
}
spec_ = std::make_unique<PaymentRequestSpec>(
std::move(payment_options), mojom::PaymentDetails::New(),
std::move(method_data), weak_ptr_factory_.GetWeakPtr(), "en-US");
}
content::BrowserTaskEnvironment task_environment_;
content::TestBrowserContext browser_context_;
content::TestWebContentsFactory test_web_contents_factory_;
content::WebContents* web_contents_;
autofill::AutofillProfile address_;
autofill::CreditCard local_card_;
std::vector<autofill::AutofillProfile*> billing_profiles_;
RequiredPaymentOptions required_options_;
std::unique_ptr<PaymentRequestSpec> spec_;
base::WeakPtrFactory<PaymentAppTest> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(PaymentAppTest);
};
INSTANTIATE_TEST_SUITE_P(
All,
PaymentAppTest,
::testing::Values(
RequiredPaymentOptions::kNone,
RequiredPaymentOptions::kShippingAddress,
RequiredPaymentOptions::kContactInformation,
RequiredPaymentOptions::kPayerEmail,
RequiredPaymentOptions::kContactInformationAndShippingAddress));
TEST_P(PaymentAppTest, SortApps) {
std::vector<PaymentApp*> apps;
// Add a card with no billing address.
autofill::CreditCard card_with_no_address = local_credit_card();
card_with_no_address.set_billing_address_id("");
AutofillPaymentApp cc_app_with_no_address(
"visa", card_with_no_address, billing_profiles(), "en-US", nullptr);
apps.push_back(&cc_app_with_no_address);
// Add an expired card.
autofill::CreditCard expired_card = local_credit_card();
expired_card.SetExpirationYear(2016);
AutofillPaymentApp expired_cc_app("visa", expired_card, billing_profiles(),
"en-US", nullptr);
apps.push_back(&expired_cc_app);
// Add a non-preselectable sw based payment app.
std::unique_ptr<ServiceWorkerPaymentApp> non_preselectable_sw_app =
CreateServiceWorkerPaymentApp(
false /* = can_preselect */, false /* = handles_shipping */,
false /* = handles_name */, false /* = handles_phone */,
false /* = handles_email */);
apps.push_back(non_preselectable_sw_app.get());
// Add a preselectable sw based payment app.
std::unique_ptr<ServiceWorkerPaymentApp> preselectable_sw_app =
CreateServiceWorkerPaymentApp(
true /* = can_preselect */, false /* = handles_shipping */,
false /* = handles_name */, false /* = handles_phone */,
false /* = handles_email */);
apps.push_back(preselectable_sw_app.get());
// Add a card with no name.
autofill::CreditCard card_with_no_name = local_credit_card();
card_with_no_name.SetInfo(
autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL),
base::ASCIIToUTF16(""), "en-US");
AutofillPaymentApp cc_app_with_no_name("visa", card_with_no_name,
billing_profiles(), "en-US", nullptr);
apps.push_back(&cc_app_with_no_name);
// Add a complete card.
autofill::CreditCard complete_card = local_credit_card();
AutofillPaymentApp complete_cc_app("visa", complete_card, billing_profiles(),
"en-US", nullptr);
apps.push_back(&complete_cc_app);
// Add a card with no number.
autofill::CreditCard card_with_no_number = local_credit_card();
card_with_no_number.SetNumber(base::ASCIIToUTF16(""));
AutofillPaymentApp cc_app_with_no_number(
"visa", card_with_no_number, billing_profiles(), "en-US", nullptr);
apps.push_back(&cc_app_with_no_number);
// Add a complete matching card that is most frequently used.
autofill::CreditCard complete_frequently_used_card = local_credit_card();
AutofillPaymentApp complete_frequently_used_cc_app(
"visa", complete_frequently_used_card, billing_profiles(), "en-US",
nullptr);
apps.push_back(&complete_frequently_used_cc_app);
// Record use of this card.
complete_frequently_used_cc_app.credit_card()->RecordAndLogUse();
// Sort the apps and validate the new order.
PaymentApp::SortApps(&apps);
size_t i = 0;
EXPECT_EQ(apps[i++], preselectable_sw_app.get());
EXPECT_EQ(apps[i++], non_preselectable_sw_app.get());
// Autfill apps (credit cards) come after sw apps.
EXPECT_EQ(apps[i++], &complete_frequently_used_cc_app);
EXPECT_EQ(apps[i++], &complete_cc_app);
EXPECT_EQ(apps[i++], &expired_cc_app);
EXPECT_EQ(apps[i++], &cc_app_with_no_name);
EXPECT_EQ(apps[i++], &cc_app_with_no_address);
EXPECT_EQ(apps[i++], &cc_app_with_no_number);
}
TEST_P(PaymentAppTest, SortAppsBasedOnSupportedDelegations) {
std::vector<PaymentApp*> apps;
// Add a preselectable sw based payment app which does not support
// shipping or contact delegation.
std::unique_ptr<ServiceWorkerPaymentApp> does_not_support_delegations =
CreateServiceWorkerPaymentApp(
true /* = can_preselect */, false /* = handles_shipping */,
false /* = handles_name */, false /* = handles_phone */,
false /* = handles_email */);
apps.push_back(does_not_support_delegations.get());
// Add a preselectable sw based payment app which handles shipping.
std::unique_ptr<ServiceWorkerPaymentApp> handles_shipping_address =
CreateServiceWorkerPaymentApp(
true /* = can_preselect */, true /* = handles_shipping */,
false /* = handles_name */, false /* = handles_phone */,
false /* = handles_email */);
apps.push_back(handles_shipping_address.get());
// Add a preselectable sw based payment app which handles payer's
// email.
std::unique_ptr<ServiceWorkerPaymentApp> handles_payer_email =
CreateServiceWorkerPaymentApp(
true /* = can_preselect */, false /* = handles_shipping */,
false /* = handles_name */, false /* = handles_phone */,
true /* = handles_email */);
apps.push_back(handles_payer_email.get());
// Add a preselectable sw based payment app which handles contact
// information.
std::unique_ptr<ServiceWorkerPaymentApp> handles_contact_info =
CreateServiceWorkerPaymentApp(
true /* = can_preselect */, false /* = handles_shipping */,
true /* = handles_name */, true /* = handles_phone */,
true /* = handles_email */);
apps.push_back(handles_contact_info.get());
// Add a preselectable sw based payment app which handles both shipping
// address and contact information.
std::unique_ptr<ServiceWorkerPaymentApp> handles_shipping_and_contact =
CreateServiceWorkerPaymentApp(
true /* = can_preselect */, true /* = handles_shipping */,
true /* = handles_name */, true /* = handles_phone */,
true /* = handles_email */);
apps.push_back(handles_shipping_and_contact.get());
PaymentApp::SortApps(&apps);
size_t i = 0;
switch (required_options()) {
case RequiredPaymentOptions::kNone: {
// When no payemnt option is required the order of the payment apps
// does not change.
EXPECT_EQ(apps[i++], does_not_support_delegations.get());
EXPECT_EQ(apps[i++], handles_shipping_address.get());
EXPECT_EQ(apps[i++], handles_payer_email.get());
EXPECT_EQ(apps[i++], handles_contact_info.get());
EXPECT_EQ(apps[i++], handles_shipping_and_contact.get());
break;
}
case RequiredPaymentOptions::kShippingAddress: {
// apps that handle shipping address come first.
EXPECT_EQ(apps[i++], handles_shipping_address.get());
EXPECT_EQ(apps[i++], handles_shipping_and_contact.get());
// The order is unchanged for apps that do not handle shipping.
EXPECT_EQ(apps[i++], does_not_support_delegations.get());
EXPECT_EQ(apps[i++], handles_payer_email.get());
EXPECT_EQ(apps[i++], handles_contact_info.get());
break;
}
case RequiredPaymentOptions::kContactInformation: {
// apps that handle all required contact information come first.
EXPECT_EQ(apps[i++], handles_contact_info.get());
EXPECT_EQ(apps[i++], handles_shipping_and_contact.get());
// The app that partially handles contact information comes next.
EXPECT_EQ(apps[i++], handles_payer_email.get());
// The order for apps that do not handle contact information is not
// changed.
EXPECT_EQ(apps[i++], does_not_support_delegations.get());
EXPECT_EQ(apps[i++], handles_shipping_address.get());
break;
}
case RequiredPaymentOptions::kPayerEmail: {
EXPECT_EQ(apps[i++], handles_payer_email.get());
EXPECT_EQ(apps[i++], handles_contact_info.get());
EXPECT_EQ(apps[i++], handles_shipping_and_contact.get());
EXPECT_EQ(apps[i++], does_not_support_delegations.get());
EXPECT_EQ(apps[i++], handles_shipping_address.get());
break;
}
case RequiredPaymentOptions::kContactInformationAndShippingAddress: {
EXPECT_EQ(apps[i++], handles_shipping_and_contact.get());
EXPECT_EQ(apps[i++], handles_shipping_address.get());
EXPECT_EQ(apps[i++], handles_contact_info.get());
EXPECT_EQ(apps[i++], handles_payer_email.get());
EXPECT_EQ(apps[i++], does_not_support_delegations.get());
break;
}
}
}
TEST_P(PaymentAppTest, SortApps_DownRankJustInTimePaymentApp) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(features::kDownRankJustInTimePaymentApp);
std::vector<PaymentApp*> apps;
// Add a card with no billing address.
autofill::CreditCard card_with_no_address = local_credit_card();
card_with_no_address.set_billing_address_id("");
AutofillPaymentApp cc_app_with_no_address(
"visa", card_with_no_address, billing_profiles(), "en-US", nullptr);
apps.push_back(&cc_app_with_no_address);
// Add an expired card.
autofill::CreditCard expired_card = local_credit_card();
expired_card.SetExpirationYear(2016);
AutofillPaymentApp expired_cc_app("visa", expired_card, billing_profiles(),
"en-US", nullptr);
apps.push_back(&expired_cc_app);
// Add a card with no number.
autofill::CreditCard card_with_no_number = local_credit_card();
card_with_no_number.SetNumber(base::ASCIIToUTF16(""));
AutofillPaymentApp cc_app_with_no_number(
"visa", card_with_no_number, billing_profiles(), "en-US", nullptr);
apps.push_back(&cc_app_with_no_number);
// Add a card with no name.
autofill::CreditCard card_with_no_name = local_credit_card();
card_with_no_name.SetInfo(
autofill::AutofillType(autofill::CREDIT_CARD_NAME_FULL),
base::ASCIIToUTF16(""), "en-US");
AutofillPaymentApp cc_app_with_no_name("visa", card_with_no_name,
billing_profiles(), "en-US", nullptr);
apps.push_back(&cc_app_with_no_name);
// Add a just-in-time installable sw based payment app.
std::unique_ptr<ServiceWorkerPaymentApp> installable_sw_app =
CreateInstallableServiceWorkerPaymentApp(
true /* = can_preselect */, false /* = handles_shipping */,
false /* = handles_name */, false /* = handles_phone */,
false /* = handles_email */);
apps.push_back(installable_sw_app.get());
// Add an installed, non-preselectable sw based payment app.
std::unique_ptr<ServiceWorkerPaymentApp> non_preselectable_sw_app =
CreateServiceWorkerPaymentApp(
false /* = can_preselect */, false /* = handles_shipping */,
false /* = handles_name */, false /* = handles_phone */,
false /* = handles_email */);
apps.push_back(non_preselectable_sw_app.get());
// Add an installed, preselectable, sw based payment app.
std::unique_ptr<ServiceWorkerPaymentApp> preselectable_sw_app =
CreateServiceWorkerPaymentApp(
true /* = can_preselect */, false /* = handles_shipping */,
false /* = handles_name */, false /* = handles_phone */,
false /* = handles_email */);
apps.push_back(preselectable_sw_app.get());
// Add a complete card.
autofill::CreditCard complete_card = local_credit_card();
AutofillPaymentApp complete_cc_app("visa", complete_card, billing_profiles(),
"en-US", nullptr);
apps.push_back(&complete_cc_app);
// Add a complete matching card that is most frequently used.
autofill::CreditCard complete_frequently_used_card = local_credit_card();
AutofillPaymentApp complete_frequently_used_cc_app(
"visa", complete_frequently_used_card, billing_profiles(), "en-US",
nullptr);
apps.push_back(&complete_frequently_used_cc_app);
// Record use of this card.
complete_frequently_used_cc_app.credit_card()->RecordAndLogUse();
// Sort the apps and validate the new order.
PaymentApp::SortApps(&apps);
size_t i = 0;
// Installed sw based payment handlers come first.
EXPECT_EQ(apps[i++], preselectable_sw_app.get());
EXPECT_EQ(apps[i++], non_preselectable_sw_app.get());
// Complete autofill apps are sorted by frecency.
EXPECT_EQ(apps[i++], &complete_frequently_used_cc_app);
EXPECT_EQ(apps[i++], &complete_cc_app);
EXPECT_EQ(apps[i++], &expired_cc_app);
// Just-in-time installable sw based payment apps come after autofill apps.
EXPECT_EQ(apps[i++], installable_sw_app.get());
// Incomplete autofill apps (credit cards) come last.
EXPECT_EQ(apps[i++], &cc_app_with_no_name);
EXPECT_EQ(apps[i++], &cc_app_with_no_address);
EXPECT_EQ(apps[i++], &cc_app_with_no_number);
}
} // namespace payments