|  | // Copyright 2020 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "components/payments/content/android/payment_app_service_bridge.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  | #include "base/memory/scoped_refptr.h" | 
|  | #include "base/memory/weak_ptr.h" | 
|  | #include "chrome/test/base/testing_profile.h" | 
|  | #include "components/payments/content/payment_app_service.h" | 
|  | #include "components/payments/content/payment_request_spec.h" | 
|  | #include "components/payments/content/web_payments_web_data_service.h" | 
|  | #include "components/payments/core/const_csp_checker.h" | 
|  | #include "components/payments/core/payment_prefs.h" | 
|  | #include "components/prefs/pref_service.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "content/public/test/browser_task_environment.h" | 
|  | #include "content/public/test/navigation_simulator.h" | 
|  | #include "content/public/test/test_web_contents_factory.h" | 
|  | #include "testing/gmock/include/gmock/gmock.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "third_party/blink/public/mojom/payments/payment_app.mojom.h" | 
|  |  | 
|  | namespace payments { | 
|  |  | 
|  | class MockCallback { | 
|  | public: | 
|  | MockCallback() = default; | 
|  | MOCK_METHOD1(NotifyPaymentAppCreated, void(std::unique_ptr<PaymentApp> app)); | 
|  | MOCK_METHOD1(NotifyCanMakePaymentCalculated, void(bool can_make_payment)); | 
|  | MOCK_METHOD2(NotifyPaymentAppCreationError, | 
|  | void(const std::string& error, AppCreationFailureReason reason)); | 
|  | MOCK_METHOD0(NotifyDoneCreatingPaymentApps, void(void)); | 
|  | MOCK_METHOD0(SetCanMakePaymentEvenWithoutApps, void(void)); | 
|  | MOCK_METHOD0(SetOptOutOffered, void(void)); | 
|  | }; | 
|  |  | 
|  | class MockApp : public PaymentApp { | 
|  | public: | 
|  | MockApp() | 
|  | : PaymentApp(/*icon_resource_id=*/0, | 
|  | PaymentApp::Type::SERVICE_WORKER_APP) {} | 
|  |  | 
|  | ~MockApp() override = default; | 
|  |  | 
|  | // PaymentApp implementation: | 
|  | MOCK_METHOD1(InvokePaymentApp, void(base::WeakPtr<Delegate> delegate)); | 
|  | MOCK_CONST_METHOD0(IsCompleteForPayment, bool()); | 
|  | MOCK_CONST_METHOD0(CanPreselect, bool()); | 
|  | MOCK_CONST_METHOD0(GetMissingInfoLabel, std::u16string()); | 
|  | MOCK_CONST_METHOD0(HasEnrolledInstrument, bool()); | 
|  | MOCK_CONST_METHOD0(NeedsInstallation, bool()); | 
|  | MOCK_CONST_METHOD0(GetId, std::string()); | 
|  | MOCK_CONST_METHOD0(GetLabel, std::u16string()); | 
|  | MOCK_CONST_METHOD0(GetSublabel, std::u16string()); | 
|  | MOCK_CONST_METHOD1(IsValidForModifier, bool(const std::string& method)); | 
|  | MOCK_METHOD0(AsWeakPtr, base::WeakPtr<PaymentApp>()); | 
|  | MOCK_CONST_METHOD0(HandlesShippingAddress, bool()); | 
|  | MOCK_CONST_METHOD0(HandlesPayerName, bool()); | 
|  | MOCK_CONST_METHOD0(HandlesPayerEmail, bool()); | 
|  | MOCK_CONST_METHOD0(HandlesPayerPhone, bool()); | 
|  | }; | 
|  |  | 
|  | class PaymentAppServiceBridgeUnitTest | 
|  | : public ::testing::TestWithParam<std::string> { | 
|  | public: | 
|  | PaymentAppServiceBridgeUnitTest() | 
|  | : top_origin_("https://shop.example"), | 
|  | frame_origin_("https://merchant.example") { | 
|  | web_contents_ = | 
|  | test_web_contents_factory_.CreateWebContents(&browser_context_); | 
|  | content::NavigationSimulator::NavigateAndCommitFromBrowser(web_contents_, | 
|  | frame_origin_); | 
|  | } | 
|  |  | 
|  | mojom::PaymentMethodDataPtr MakePaymentMethodData( | 
|  | const std::string& supported_method) { | 
|  | mojom::PaymentMethodDataPtr out = mojom::PaymentMethodData::New(); | 
|  | out->supported_method = supported_method; | 
|  | return out; | 
|  | } | 
|  |  | 
|  | protected: | 
|  | content::BrowserTaskEnvironment task_environment_; | 
|  | TestingProfile browser_context_; | 
|  | content::TestWebContentsFactory test_web_contents_factory_; | 
|  | raw_ptr<content::WebContents> web_contents_; | 
|  | GURL top_origin_; | 
|  | GURL frame_origin_; | 
|  | scoped_refptr<WebPaymentsWebDataService> web_data_service_; | 
|  | }; | 
|  |  | 
|  | TEST_P(PaymentAppServiceBridgeUnitTest, Smoke) { | 
|  | std::vector<mojom::PaymentMethodDataPtr> method_data; | 
|  | method_data.push_back(MakePaymentMethodData("basic-card")); | 
|  | method_data.push_back(MakePaymentMethodData("https://ph.example")); | 
|  | PaymentRequestSpec spec(mojom::PaymentOptions::New(), | 
|  | mojom::PaymentDetails::New(), std::move(method_data), | 
|  | /*observer=*/nullptr, /*app_locale=*/"en-US"); | 
|  |  | 
|  | ConstCSPChecker const_csp_checker(/*allow=*/true); | 
|  | MockCallback mock_callback; | 
|  | base::WeakPtr<PaymentAppServiceBridge> bridge = | 
|  | PaymentAppServiceBridge::Create( | 
|  | std::make_unique<PaymentAppService>( | 
|  | web_contents_->GetBrowserContext()), | 
|  | web_contents_->GetPrimaryMainFrame(), top_origin_, spec.AsWeakPtr(), | 
|  | /*twa_package_name=*/GetParam(), web_data_service_, | 
|  | /*is_off_the_record=*/false, const_csp_checker.GetWeakPtr(), | 
|  | base::BindRepeating(&MockCallback::NotifyCanMakePaymentCalculated, | 
|  | base::Unretained(&mock_callback)), | 
|  | base::BindRepeating(&MockCallback::NotifyPaymentAppCreated, | 
|  | base::Unretained(&mock_callback)), | 
|  | base::BindRepeating(&MockCallback::NotifyPaymentAppCreationError, | 
|  | base::Unretained(&mock_callback)), | 
|  | base::BindOnce(&MockCallback::NotifyDoneCreatingPaymentApps, | 
|  | base::Unretained(&mock_callback)), | 
|  | base::BindRepeating(&MockCallback::SetCanMakePaymentEvenWithoutApps, | 
|  | base::Unretained(&mock_callback)), | 
|  | base::BindRepeating(&MockCallback::SetOptOutOffered, | 
|  | base::Unretained(&mock_callback))) | 
|  | ->GetWeakPtrForTest(); | 
|  |  | 
|  | EXPECT_EQ(web_contents_, bridge->GetWebContents()); | 
|  | EXPECT_EQ(top_origin_, bridge->GetTopOrigin()); | 
|  | EXPECT_EQ(frame_origin_, bridge->GetFrameOrigin()); | 
|  | EXPECT_EQ("https://merchant.example", | 
|  | bridge->GetFrameSecurityOrigin().Serialize()); | 
|  | EXPECT_EQ(web_contents_->GetPrimaryMainFrame(), | 
|  | bridge->GetInitiatorRenderFrameHost()); | 
|  | EXPECT_EQ(2U, bridge->GetMethodData().size()); | 
|  | EXPECT_EQ("basic-card", bridge->GetMethodData()[0]->supported_method); | 
|  | EXPECT_EQ("https://ph.example", bridge->GetMethodData()[1]->supported_method); | 
|  |  | 
|  | auto app = std::make_unique<MockApp>(); | 
|  | EXPECT_CALL(mock_callback, NotifyPaymentAppCreated(::testing::_)); | 
|  | bridge->OnPaymentAppCreated(std::move(app)); | 
|  |  | 
|  | EXPECT_CALL(mock_callback, SetCanMakePaymentEvenWithoutApps()); | 
|  | bridge->SetCanMakePaymentEvenWithoutApps(); | 
|  |  | 
|  | EXPECT_CALL(mock_callback, SetOptOutOffered()); | 
|  | bridge->SetOptOutOffered(); | 
|  |  | 
|  | EXPECT_CALL(mock_callback, | 
|  | NotifyPaymentAppCreationError("some error", | 
|  | AppCreationFailureReason::UNKNOWN)); | 
|  | bridge->OnPaymentAppCreationError("some error", | 
|  | AppCreationFailureReason::UNKNOWN); | 
|  |  | 
|  | // NotifyDoneCreatingPaymentApps() is only called after | 
|  | // OnDoneCreatingPaymentApps() is called for each payment factories in | 
|  | // |bridge|. | 
|  | bridge->OnDoneCreatingPaymentApps(); | 
|  | bridge->OnDoneCreatingPaymentApps(); | 
|  |  | 
|  | EXPECT_CALL(mock_callback, NotifyDoneCreatingPaymentApps()); | 
|  | bridge->OnDoneCreatingPaymentApps(); | 
|  |  | 
|  | // |bridge| cleans itself up after NotifyDoneCreatingPaymentApps(). | 
|  | CHECK_EQ(nullptr, bridge.get()); | 
|  | } | 
|  |  | 
|  | TEST_P(PaymentAppServiceBridgeUnitTest, PrefsCanMakePayment) { | 
|  | PaymentRequestSpec spec(mojom::PaymentOptions::New(), | 
|  | mojom::PaymentDetails::New(), {}, | 
|  | /*observer=*/nullptr, /*app_locale=*/"en-US"); | 
|  | ConstCSPChecker const_csp_checker(/*allow=*/true); | 
|  | MockCallback mock_callback; | 
|  | base::WeakPtr<PaymentAppServiceBridge> bridge = | 
|  | PaymentAppServiceBridge::Create( | 
|  | std::make_unique<PaymentAppService>( | 
|  | web_contents_->GetBrowserContext()), | 
|  | web_contents_->GetPrimaryMainFrame(), top_origin_, spec.AsWeakPtr(), | 
|  | /*twa_package_name=*/GetParam(), web_data_service_, | 
|  | /*is_off_the_record=*/false, const_csp_checker.GetWeakPtr(), | 
|  | base::BindRepeating(&MockCallback::NotifyCanMakePaymentCalculated, | 
|  | base::Unretained(&mock_callback)), | 
|  | base::BindRepeating(&MockCallback::NotifyPaymentAppCreated, | 
|  | base::Unretained(&mock_callback)), | 
|  | base::BindRepeating(&MockCallback::NotifyPaymentAppCreationError, | 
|  | base::Unretained(&mock_callback)), | 
|  | base::BindOnce(&MockCallback::NotifyDoneCreatingPaymentApps, | 
|  | base::Unretained(&mock_callback)), | 
|  | base::BindRepeating(&MockCallback::SetCanMakePaymentEvenWithoutApps, | 
|  | base::Unretained(&mock_callback)), | 
|  | base::BindRepeating(&MockCallback::SetOptOutOffered, | 
|  | base::Unretained(&mock_callback))) | 
|  | ->GetWeakPtrForTest(); | 
|  |  | 
|  | PrefService* prefs = browser_context_.GetPrefs(); | 
|  | prefs->SetBoolean(kCanMakePaymentEnabled, false); | 
|  | EXPECT_FALSE(bridge->PrefsCanMakePayment()); | 
|  |  | 
|  | prefs->SetBoolean(kCanMakePaymentEnabled, true); | 
|  | EXPECT_TRUE(bridge->PrefsCanMakePayment()); | 
|  |  | 
|  | test_web_contents_factory_.DestroyWebContents(web_contents_); | 
|  | EXPECT_FALSE(bridge->PrefsCanMakePayment()); | 
|  |  | 
|  | // Clean up the bridge. | 
|  | bridge->OnDoneCreatingPaymentApps(); | 
|  | bridge->OnDoneCreatingPaymentApps(); | 
|  | EXPECT_CALL(mock_callback, NotifyDoneCreatingPaymentApps()); | 
|  | bridge->OnDoneCreatingPaymentApps(); | 
|  | EXPECT_EQ(nullptr, bridge.get()); | 
|  | } | 
|  |  | 
|  | // An empty string indicates running outside of a TWA. A non-empty string is the | 
|  | // package name of the TWA when running in a TWA mode. | 
|  | INSTANTIATE_TEST_SUITE_P(WithAndWithoutTwaPackageName, | 
|  | PaymentAppServiceBridgeUnitTest, | 
|  | ::testing::Values("", "com.example.twa.app")); | 
|  |  | 
|  | }  // namespace payments |