blob: 57696469e15cccb11533bad0d06817c56eb06dab [file] [log] [blame]
mathpf709499d2017-01-09 20:48:361// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
rouslan908248c2017-02-27 21:30:245#include "components/payments/content/payment_request.h"
6
anthonyvdd23ed702017-04-05 15:29:007#include <string>
rouslan908248c2017-02-27 21:30:248#include <utility>
mathpf709499d2017-01-09 20:48:369
Sebastien Marchand53801a32019-01-25 16:26:1110#include "base/bind.h"
Rouslan Solomakhin1804ee42017-10-03 14:27:4311#include "base/feature_list.h"
Mathieu Perreault627b97c2017-08-12 00:44:2212#include "base/stl_util.h"
rouslan690997682017-05-09 18:07:3913#include "components/payments/content/can_make_payment_query_factory.h"
Rouslan Solomakhin4eea9bc22017-10-10 15:18:5114#include "components/payments/content/content_payment_request_delegate.h"
Rouslan Solomakhincf9093f2019-05-20 15:32:1715#include "components/payments/content/payment_details_converter.h"
Mohamad Ahmadif5544bb2017-09-01 21:48:2216#include "components/payments/content/payment_request_converter.h"
rouslan908248c2017-02-27 21:30:2417#include "components/payments/content/payment_request_web_contents_manager.h"
Rouslan Solomakhina480efa2019-05-06 15:37:2218#include "components/payments/content/service_worker_payment_instrument.h"
rouslan690997682017-05-09 18:07:3919#include "components/payments/core/can_make_payment_query.h"
Rouslan Solomakhina480efa2019-05-06 15:37:2220#include "components/payments/core/error_strings.h"
Anthony Vallee-Dubois968ae4d2018-03-15 16:56:3621#include "components/payments/core/features.h"
Mohamad Ahmadif5544bb2017-09-01 21:48:2222#include "components/payments/core/payment_details.h"
23#include "components/payments/core/payment_details_validation.h"
Mathieu Perreault23d25bfb82018-05-11 14:45:3724#include "components/payments/core/payment_instrument.h"
anthonyvd6a43b932017-05-11 18:39:2725#include "components/payments/core/payment_prefs.h"
Danyao Wang50ccb9f2019-05-09 23:28:0326#include "components/payments/core/payments_experimental_features.h"
Jinho Bangbe463a22018-08-02 10:26:5027#include "components/payments/core/payments_validators.h"
Rouslan Solomakhin77a7e1a2019-05-23 17:37:5828#include "components/payments/core/url_util.h"
anthonyvd6a43b932017-05-11 18:39:2729#include "components/prefs/pref_service.h"
Steven Holte2083e8bc2018-07-16 23:50:3630#include "components/ukm/content/source_url_recorder.h"
Rouslan Solomakhin6e979ab2017-08-30 17:30:3931#include "components/url_formatter/elide_url.h"
mathpf709499d2017-01-09 20:48:3632#include "content/public/browser/browser_thread.h"
rouslan690997682017-05-09 18:07:3933#include "content/public/browser/render_frame_host.h"
mathpf709499d2017-01-09 20:48:3634#include "content/public/browser/web_contents.h"
Rouslan Solomakhin1804ee42017-10-03 14:27:4335#include "content/public/common/content_features.h"
Rouslan Solomakhin77a7e1a2019-05-23 17:37:5836#include "content/public/common/origin_util.h"
mathpf709499d2017-01-09 20:48:3637
Rouslan Solomakhina480efa2019-05-06 15:37:2238namespace payments {
Danyao Wangce175bf2018-12-21 22:35:5839namespace {
40
Danyao Wang57aa0442019-01-31 04:06:4141using ::payments::mojom::CanMakePaymentQueryResult;
Danyao Wangce175bf2018-12-21 22:35:5842using ::payments::mojom::HasEnrolledInstrumentQueryResult;
43
Sahel Sharify26884382019-05-07 16:23:5144bool IsGooglePaymentMethodInstrumentSelected(const std::string& method_name) {
45 return method_name == kGooglePayMethodName ||
46 method_name == kAndroidPayMethodName;
47}
48
Rouslan Solomakhina480efa2019-05-06 15:37:2249} // namespace
mathpf709499d2017-01-09 20:48:3650
51PaymentRequest::PaymentRequest(
rouslan690997682017-05-09 18:07:3952 content::RenderFrameHost* render_frame_host,
mathpf709499d2017-01-09 20:48:3653 content::WebContents* web_contents,
Rouslan Solomakhin4eea9bc22017-10-10 15:18:5154 std::unique_ptr<ContentPaymentRequestDelegate> delegate,
mathpf709499d2017-01-09 20:48:3655 PaymentRequestWebContentsManager* manager,
Anthony Vallee-Duboisc7ae7332017-12-19 20:44:0756 PaymentRequestDisplayManager* display_manager,
rouslan6e3cf7c62017-04-17 21:23:2857 mojo::InterfaceRequest<mojom::PaymentRequest> request,
mathp300fa542017-03-27 19:29:3758 ObserverForTest* observer_for_testing)
mathpf709499d2017-01-09 20:48:3659 : web_contents_(web_contents),
Rouslan Solomakhin27064702018-12-14 21:15:3360 log_(web_contents_),
mathpf709499d2017-01-09 20:48:3661 delegate_(std::move(delegate)),
62 manager_(manager),
Anthony Vallee-Duboisc7ae7332017-12-19 20:44:0763 display_manager_(display_manager),
64 display_handle_(nullptr),
mathp300fa542017-03-27 19:29:3765 binding_(this, std::move(request)),
Rouslan Solomakhin8e9f149b22019-05-10 17:43:0266 payment_handler_host_(this),
Rouslan Solomakhin6e979ab2017-08-30 17:30:3967 top_level_origin_(url_formatter::FormatUrlForSecurityDisplay(
68 web_contents_->GetLastCommittedURL())),
69 frame_origin_(url_formatter::FormatUrlForSecurityDisplay(
70 render_frame_host->GetLastCommittedURL())),
sebsg20b49d7b2017-05-04 20:23:1771 observer_for_testing_(observer_for_testing),
72 journey_logger_(delegate_->IsIncognito(),
Steven Holte2083e8bc2018-07-16 23:50:3673 ukm::GetSourceIdForWebContentsDocument(web_contents)),
Anthony Vallee-Duboisdc1dbf1a2017-07-17 15:01:1374 weak_ptr_factory_(this) {
mathpf4bc50e2017-01-24 05:17:5075 // OnConnectionTerminated will be called when the Mojo pipe is closed. This
76 // will happen as a result of many renderer-side events (both successful and
77 // erroneous in nature).
78 // TODO(crbug.com/683636): Investigate using
79 // set_connection_error_with_reason_handler with Binding::CloseWithReason.
tzik2bcf8e42018-07-31 11:22:1580 binding_.set_connection_error_handler(base::BindOnce(
Anthony Vallee-Duboisdc1dbf1a2017-07-17 15:01:1381 &PaymentRequest::OnConnectionTerminated, weak_ptr_factory_.GetWeakPtr()));
mathpf709499d2017-01-09 20:48:3682}
83
84PaymentRequest::~PaymentRequest() {}
85
rouslan6e3cf7c62017-04-17 21:23:2886void PaymentRequest::Init(mojom::PaymentRequestClientPtr client,
87 std::vector<mojom::PaymentMethodDataPtr> method_data,
88 mojom::PaymentDetailsPtr details,
89 mojom::PaymentOptionsPtr options) {
mathpf709499d2017-01-09 20:48:3690 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Rouslan Solomakhin27064702018-12-14 21:15:3391
92 if (is_initialized_) {
Rouslan Solomakhina480efa2019-05-06 15:37:2293 log_.Error(errors::kAttemptedInitializationTwice);
Rouslan Solomakhin27064702018-12-14 21:15:3394 OnConnectionTerminated();
95 return;
96 }
97
98 is_initialized_ = true;
rouslan6e3cf7c62017-04-17 21:23:2899 client_ = std::move(client);
100
rouslanb28f4532017-05-08 15:41:47101 const GURL last_committed_url = delegate_->GetLastCommittedURL();
Rouslan Solomakhin77a7e1a2019-05-23 17:37:58102 if (!content::IsOriginSecure(last_committed_url)) {
Rouslan Solomakhina480efa2019-05-06 15:37:22103 log_.Error(errors::kNotInASecureOrigin);
rouslan6e3cf7c62017-04-17 21:23:28104 OnConnectionTerminated();
105 return;
106 }
107
rouslanb28f4532017-05-08 15:41:47108 bool allowed_origin =
Rouslan Solomakhin77a7e1a2019-05-23 17:37:58109 UrlUtil::IsOriginAllowedToUseWebPaymentApis(last_committed_url);
rouslanb28f4532017-05-08 15:41:47110 if (!allowed_origin) {
Rouslan Solomakhina480efa2019-05-06 15:37:22111 log_.Error(errors::kProhibitedOrigin);
rouslanb28f4532017-05-08 15:41:47112 }
113
Rouslan Solomakhin77a7e1a2019-05-23 17:37:58114 bool invalid_ssl = last_committed_url.SchemeIsCryptographic() &&
115 !delegate_->IsSslCertificateValid();
Rouslan Solomakhin27064702018-12-14 21:15:33116 if (invalid_ssl) {
Rouslan Solomakhina480efa2019-05-06 15:37:22117 log_.Error(errors::kInvalidSslCertificate);
Rouslan Solomakhin27064702018-12-14 21:15:33118 }
rouslanb28f4532017-05-08 15:41:47119
120 if (!allowed_origin || invalid_ssl) {
Rouslan Solomakhin27064702018-12-14 21:15:33121 // Intentionally don't set |spec_| and |state_|, so the UI is never shown.
Rouslan Solomakhina480efa2019-05-06 15:37:22122 log_.Error(errors::kProhibitedOriginOrInvalidSslExplanation);
rouslan6e3cf7c62017-04-17 21:23:28123 return;
124 }
125
mathpf709499d2017-01-09 20:48:36126 std::string error;
Mohamad Ahmadif5544bb2017-09-01 21:48:22127 if (!ValidatePaymentDetails(ConvertPaymentDetails(details), &error)) {
Rouslan Solomakhin27064702018-12-14 21:15:33128 log_.Error(error);
mathpf4bc50e2017-01-24 05:17:50129 OnConnectionTerminated();
mathpf709499d2017-01-09 20:48:36130 return;
131 }
rouslan6e3cf7c62017-04-17 21:23:28132
jinho.bangfcb5ec92017-03-29 08:08:02133 if (!details->total) {
Rouslan Solomakhina480efa2019-05-06 15:37:22134 log_.Error(errors::kTotalRequired);
jinho.bangfcb5ec92017-03-29 08:08:02135 OnConnectionTerminated();
136 return;
137 }
rouslan6e3cf7c62017-04-17 21:23:28138
sebsga70a6da2017-12-21 22:27:02139 spec_ = std::make_unique<PaymentRequestSpec>(
mathpc0d616a2017-03-15 14:09:33140 std::move(options), std::move(details), std::move(method_data), this,
141 delegate_->GetApplicationLocale());
sebsga70a6da2017-12-21 22:27:02142 state_ = std::make_unique<PaymentRequestState>(
Rouslan Solomakhindbf593d92017-11-21 19:20:57143 web_contents_, top_level_origin_, frame_origin_, spec_.get(), this,
144 delegate_->GetApplicationLocale(), delegate_->GetPersonalDataManager(),
145 delegate_.get(), &journey_logger_);
Mathieu Perreault627b97c2017-08-12 00:44:22146
147 journey_logger_.SetRequestedInformation(
148 spec_->request_shipping(), spec_->request_payer_email(),
149 spec_->request_payer_phone(), spec_->request_payer_name());
150
151 // Log metrics around which payment methods are requested by the merchant.
152 GURL google_pay_url(kGooglePayMethodName);
153 GURL android_pay_url(kAndroidPayMethodName);
154 // Looking for payment methods that are NOT google-related payment methods.
155 auto non_google_it =
156 std::find_if(spec_->url_payment_method_identifiers().begin(),
157 spec_->url_payment_method_identifiers().end(),
158 [google_pay_url, android_pay_url](const GURL& url) {
159 return url != google_pay_url && url != android_pay_url;
160 });
161 journey_logger_.SetRequestedPaymentMethodTypes(
162 /*requested_basic_card=*/!spec_->supported_card_networks().empty(),
163 /*requested_method_google=*/
164 base::ContainsValue(spec_->url_payment_method_identifiers(),
165 google_pay_url) ||
166 base::ContainsValue(spec_->url_payment_method_identifiers(),
167 android_pay_url),
168 /*requested_method_other=*/non_google_it !=
169 spec_->url_payment_method_identifiers().end());
mathpf709499d2017-01-09 20:48:36170}
171
Rouslan Solomakhin9788d4b2019-04-09 13:10:23172void PaymentRequest::Show(bool is_user_gesture, bool wait_for_updated_details) {
Rouslan Solomakhin27064702018-12-14 21:15:33173 if (!IsInitialized()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22174 log_.Error(errors::kCannotShowWithoutInit);
mathpf4bc50e2017-01-24 05:17:50175 OnConnectionTerminated();
tmartino8ce922852017-01-09 22:23:10176 return;
177 }
rouslan6e3cf7c62017-04-17 21:23:28178
Rouslan Solomakhin27064702018-12-14 21:15:33179 if (is_show_called_) {
Rouslan Solomakhina480efa2019-05-06 15:37:22180 log_.Error(errors::kCannotShowTwice);
Rouslan Solomakhin27064702018-12-14 21:15:33181 OnConnectionTerminated();
182 return;
183 }
184
185 is_show_called_ = true;
186
rouslan7d433cc22017-05-08 15:18:07187 // A tab can display only one PaymentRequest UI at a time.
Anthony Vallee-Dubois8f5e7e12018-01-12 16:14:06188 display_handle_ = display_manager_->TryShow(delegate_.get());
Anthony Vallee-Duboisc7ae7332017-12-19 20:44:07189 if (!display_handle_) {
Rouslan Solomakhina480efa2019-05-06 15:37:22190 log_.Error(errors::kAnotherUiShowing);
sebsg828269bc2017-06-09 19:11:12191 journey_logger_.SetNotShown(
192 JourneyLogger::NOT_SHOWN_REASON_CONCURRENT_REQUESTS);
Rouslan Solomakhinb2d233c42018-08-30 16:18:40193 client_->OnError(mojom::PaymentErrorReason::ALREADY_SHOWING);
rouslan7d433cc22017-05-08 15:18:07194 OnConnectionTerminated();
195 return;
196 }
197
Rouslan Solomakhin5b510432017-09-26 16:59:32198 if (!delegate_->IsBrowserWindowActive()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22199 log_.Error(errors::kCannotShowInBackgroundTab);
Rouslan Solomakhin5b510432017-09-26 16:59:32200 journey_logger_.SetNotShown(JourneyLogger::NOT_SHOWN_REASON_OTHER);
201 client_->OnError(mojom::PaymentErrorReason::USER_CANCEL);
202 OnConnectionTerminated();
203 return;
204 }
205
Rouslan Solomakhind2cae95a2018-08-09 00:16:10206 if (!state_) {
Rouslan Solomakhin27064702018-12-14 21:15:33207 // SSL is not valid. Reject show with NotSupportedError, disconnect the
208 // mojo pipe, and destroy this object.
Rouslan Solomakhind2cae95a2018-08-09 00:16:10209 AreRequestedMethodsSupportedCallback(false);
210 return;
211 }
212
Rouslan Solomakhin833f8512018-04-03 23:19:25213 is_show_user_gesture_ = is_user_gesture;
214
Rouslan Solomakhin9788d4b2019-04-09 13:10:23215 if (wait_for_updated_details) {
216 // Put |spec_| into uninitialized state, so the UI knows to show a spinner.
217 // This method does not block.
218 spec_->StartWaitingForUpdateWith(
219 PaymentRequestSpec::UpdateReason::INITIAL_PAYMENT_DETAILS);
220 }
Takashi Sakamoto48a29702019-04-08 05:06:32221
Rouslan Solomakhin9788d4b2019-04-09 13:10:23222 display_handle_->Show(this);
gogerald0a7ee6c2017-11-13 18:23:19223 state_->AreRequestedMethodsSupported(
224 base::BindOnce(&PaymentRequest::AreRequestedMethodsSupportedCallback,
225 weak_ptr_factory_.GetWeakPtr()));
226}
227
Jinho Bangbe463a22018-08-02 10:26:50228void PaymentRequest::Retry(mojom::PaymentValidationErrorsPtr errors) {
Rouslan Solomakhin27064702018-12-14 21:15:33229 if (!IsInitialized()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22230 log_.Error(errors::kCannotRetryWithoutInit);
Jinho Bangcac8d9a02018-08-23 19:47:22231 OnConnectionTerminated();
232 return;
233 }
234
Rouslan Solomakhin27064702018-12-14 21:15:33235 if (!IsThisPaymentRequestShowing()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22236 log_.Error(errors::kCannotRetryWithoutShow);
Jinho Bangcac8d9a02018-08-23 19:47:22237 OnConnectionTerminated();
238 return;
239 }
240
Jinho Bangbe463a22018-08-02 10:26:50241 std::string error;
242 if (!PaymentsValidators::IsValidPaymentValidationErrorsFormat(errors,
243 &error)) {
Rouslan Solomakhin27064702018-12-14 21:15:33244 log_.Error(error);
Jinho Bangbe463a22018-08-02 10:26:50245 client_->OnError(mojom::PaymentErrorReason::USER_CANCEL);
246 OnConnectionTerminated();
247 return;
248 }
249
Jinho Bang092e7162018-09-06 23:41:19250 spec()->Retry(std::move(errors));
Jinho Bangcac8d9a02018-08-23 19:47:22251 display_handle_->Retry();
Jinho Bangbe463a22018-08-02 10:26:50252}
253
mathp151bd312017-04-03 21:07:24254void PaymentRequest::UpdateWith(mojom::PaymentDetailsPtr details) {
Rouslan Solomakhin27064702018-12-14 21:15:33255 if (!IsInitialized()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22256 log_.Error(errors::kCannotUpdateWithoutInit);
Rouslan Solomakhin27064702018-12-14 21:15:33257 OnConnectionTerminated();
258 return;
259 }
260
261 if (!IsThisPaymentRequestShowing()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22262 log_.Error(errors::kCannotUpdateWithoutShow);
Rouslan Solomakhin27064702018-12-14 21:15:33263 OnConnectionTerminated();
264 return;
265 }
266
mathp151bd312017-04-03 21:07:24267 std::string error;
Mohamad Ahmadif5544bb2017-09-01 21:48:22268 if (!ValidatePaymentDetails(ConvertPaymentDetails(details), &error)) {
Rouslan Solomakhin27064702018-12-14 21:15:33269 log_.Error(error);
mathp151bd312017-04-03 21:07:24270 OnConnectionTerminated();
271 return;
272 }
Rouslan Solomakhin4cbda822017-08-23 18:50:39273
Jinho Bang092e7162018-09-06 23:41:19274 if (details->shipping_address_errors &&
275 !PaymentsValidators::IsValidAddressErrorsFormat(
276 details->shipping_address_errors, &error)) {
Rouslan Solomakhin27064702018-12-14 21:15:33277 log_.Error(error);
Jinho Bang092e7162018-09-06 23:41:19278 OnConnectionTerminated();
279 return;
280 }
281
Rouslan Solomakhincf9093f2019-05-20 15:32:17282 if (state()->selected_instrument() && state()->IsPaymentAppInvoked() &&
283 payment_handler_host_.is_changing_payment_method()) {
Rouslan Solomakhin8e9f149b22019-05-10 17:43:02284 payment_handler_host_.UpdateWith(
Rouslan Solomakhincf9093f2019-05-20 15:32:17285 PaymentDetailsConverter::ConvertToPaymentMethodChangeResponse(
286 details, base::BindRepeating(
287 &PaymentInstrument::IsValidForPaymentMethodIdentifier,
288 base::Unretained(state()->selected_instrument()))));
Rouslan Solomakhina480efa2019-05-06 15:37:22289 }
290
Rouslan Solomakhin6ba46fd2019-04-11 23:44:01291 bool is_resolving_promise_passed_into_show_method = !spec_->IsInitialized();
292
mathp151bd312017-04-03 21:07:24293 spec_->UpdateWith(std::move(details));
Rouslan Solomakhin9788d4b2019-04-09 13:10:23294
295 if (is_resolving_promise_passed_into_show_method) {
296 if (SatisfiesSkipUIConstraints()) {
Rouslan Solomakhin9788d4b2019-04-09 13:10:23297 Pay();
298 } else if (spec_->request_shipping()) {
299 state_->SelectDefaultShippingAddressAndNotifyObservers();
300 }
301 }
mathp151bd312017-04-03 21:07:24302}
303
Rouslan Solomakhina9ff9282017-10-31 21:58:05304void PaymentRequest::NoUpdatedPaymentDetails() {
Rouslan Solomakhin27064702018-12-14 21:15:33305 // This Mojo call is triggered by the user of the API doing nothing in
306 // response to a shipping address update event, so the error messages cannot
307 // be more verbose.
308 if (!IsInitialized()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22309 log_.Error(errors::kNotInitialized);
Rouslan Solomakhin27064702018-12-14 21:15:33310 OnConnectionTerminated();
311 return;
312 }
313
314 if (!IsThisPaymentRequestShowing()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22315 log_.Error(errors::kNotShown);
Rouslan Solomakhin27064702018-12-14 21:15:33316 OnConnectionTerminated();
317 return;
318 }
319
Rouslan Solomakhina9ff9282017-10-31 21:58:05320 spec_->RecomputeSpecForDetails();
Rouslan Solomakhina480efa2019-05-06 15:37:22321
Rouslan Solomakhincf9093f2019-05-20 15:32:17322 if (state()->IsPaymentAppInvoked() &&
323 payment_handler_host_.is_changing_payment_method()) {
Rouslan Solomakhin8e9f149b22019-05-10 17:43:02324 payment_handler_host_.NoUpdatedPaymentDetails();
Rouslan Solomakhincf9093f2019-05-20 15:32:17325 }
Rouslan Solomakhina9ff9282017-10-31 21:58:05326}
327
mathpf4bc50e2017-01-24 05:17:50328void PaymentRequest::Abort() {
Rouslan Solomakhin27064702018-12-14 21:15:33329 if (!IsInitialized()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22330 log_.Error(errors::kCannotAbortWithoutInit);
Rouslan Solomakhin27064702018-12-14 21:15:33331 OnConnectionTerminated();
332 return;
333 }
334
335 if (!IsThisPaymentRequestShowing()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22336 log_.Error(errors::kCannotAbortWithoutShow);
Rouslan Solomakhin27064702018-12-14 21:15:33337 OnConnectionTerminated();
338 return;
339 }
340
Anthony Vallee-Dubois6813c1442017-05-17 19:32:56341 // The API user has decided to abort. If a successful abort message is
342 // returned to the renderer, the Mojo message pipe is closed, which triggers
mathpf4bc50e2017-01-24 05:17:50343 // PaymentRequest::OnConnectionTerminated, which destroys this object.
Anthony Vallee-Dubois6813c1442017-05-17 19:32:56344 // Otherwise, the abort promise is rejected and the pipe is not closed.
345 // The abort is only successful if the payment app wasn't yet invoked.
346 // TODO(crbug.com/716546): Add a merchant abort metric
347
348 bool accepting_abort = !state_->IsPaymentAppInvoked();
sebsgfcdd13c2017-06-08 15:49:33349 if (accepting_abort)
350 RecordFirstAbortReason(JourneyLogger::ABORT_REASON_ABORTED_BY_MERCHANT);
Anthony Vallee-Dubois6813c1442017-05-17 19:32:56351
mathpf4bc50e2017-01-24 05:17:50352 if (client_.is_bound())
Anthony Vallee-Dubois6813c1442017-05-17 19:32:56353 client_->OnAbort(accepting_abort);
354
355 if (observer_for_testing_)
356 observer_for_testing_->OnAbortCalled();
mathpf4bc50e2017-01-24 05:17:50357}
358
mathp218795892017-03-29 15:15:34359void PaymentRequest::Complete(mojom::PaymentComplete result) {
Rouslan Solomakhin27064702018-12-14 21:15:33360 if (!IsInitialized()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22361 log_.Error(errors::kCannotCompleteWithoutInit);
Rouslan Solomakhin27064702018-12-14 21:15:33362 OnConnectionTerminated();
mathp4b85b582017-03-08 21:07:16363 return;
Rouslan Solomakhin27064702018-12-14 21:15:33364 }
365
366 if (!IsThisPaymentRequestShowing()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22367 log_.Error(errors::kCannotAbortWithoutShow);
Rouslan Solomakhin27064702018-12-14 21:15:33368 OnConnectionTerminated();
369 return;
370 }
mathp4b85b582017-03-08 21:07:16371
Rouslan Solomakhine3473192017-06-16 14:54:57372 // Failed transactions show an error. Successful and unknown-state
373 // transactions don't show an error.
374 if (result == mojom::PaymentComplete::FAIL) {
mathp218795892017-03-29 15:15:34375 delegate_->ShowErrorMessage();
376 } else {
sebsgfcdd13c2017-06-08 15:49:33377 DCHECK(!has_recorded_completion_);
sebsgf8272a22017-05-26 14:32:58378 journey_logger_.SetCompleted();
sebsgfcdd13c2017-06-08 15:49:33379 has_recorded_completion_ = true;
380
anthonyvd6a43b932017-05-11 18:39:27381 delegate_->GetPrefService()->SetBoolean(kPaymentsFirstTransactionCompleted,
382 true);
mathp218795892017-03-29 15:15:34383 // When the renderer closes the connection,
384 // PaymentRequest::OnConnectionTerminated will be called.
385 client_->OnComplete();
sebsg8a93b272017-05-11 19:30:22386 state_->RecordUseStats();
mathp218795892017-03-29 15:15:34387 }
mathp4b85b582017-03-08 21:07:16388}
389
Danyao Wang57aa0442019-01-31 04:06:41390void PaymentRequest::CanMakePayment(bool legacy_mode) {
Rouslan Solomakhin27064702018-12-14 21:15:33391 if (!IsInitialized()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22392 log_.Error(errors::kCannotCallCanMakePaymentWithoutInit);
Rouslan Solomakhin27064702018-12-14 21:15:33393 OnConnectionTerminated();
394 return;
395 }
396
397 // It's valid to call canMakePayment() without calling show() first.
398
gogerald8189d522017-09-15 17:52:18399 if (observer_for_testing_)
400 observer_for_testing_->OnCanMakePaymentCalled();
Mathieu Perreaultcacb85e2018-06-06 20:40:13401
Rouslan Solomakhind2cae95a2018-08-09 00:16:10402 if (!delegate_->GetPrefService()->GetBoolean(kCanMakePaymentEnabled) ||
403 !state_) {
Danyao Wang57aa0442019-01-31 04:06:41404 CanMakePaymentCallback(legacy_mode, /*can_make_payment=*/false);
Mathieu Perreaultcacb85e2018-06-06 20:40:13405 } else {
Rouslan Solomakhind2cae95a2018-08-09 00:16:10406 state_->CanMakePayment(
Danyao Wang57aa0442019-01-31 04:06:41407 legacy_mode,
Mathieu Perreaultcacb85e2018-06-06 20:40:13408 base::BindOnce(&PaymentRequest::CanMakePaymentCallback,
Danyao Wang57aa0442019-01-31 04:06:41409 weak_ptr_factory_.GetWeakPtr(), legacy_mode));
Mathieu Perreaultcacb85e2018-06-06 20:40:13410 }
gogerald8189d522017-09-15 17:52:18411}
412
Rouslan Solomakhin5683eb282019-01-29 18:06:03413void PaymentRequest::HasEnrolledInstrument(bool per_method_quota) {
Danyao Wangce175bf2018-12-21 22:35:58414 if (!IsInitialized()) {
Rouslan Solomakhina480efa2019-05-06 15:37:22415 log_.Error(errors::kCannotCallHasEnrolledInstrumentWithoutInit);
Danyao Wangce175bf2018-12-21 22:35:58416 OnConnectionTerminated();
417 return;
418 }
419
420 // It's valid to call hasEnrolledInstrument() without calling show() first.
421
422 if (observer_for_testing_)
423 observer_for_testing_->OnHasEnrolledInstrumentCalled();
424
425 if (!delegate_->GetPrefService()->GetBoolean(kCanMakePaymentEnabled) ||
426 !state_) {
Rouslan Solomakhin5683eb282019-01-29 18:06:03427 HasEnrolledInstrumentCallback(per_method_quota,
428 /*has_enrolled_instrument=*/false);
Danyao Wangce175bf2018-12-21 22:35:58429 } else {
430 state_->HasEnrolledInstrument(
431 base::BindOnce(&PaymentRequest::HasEnrolledInstrumentCallback,
Rouslan Solomakhin5683eb282019-01-29 18:06:03432 weak_ptr_factory_.GetWeakPtr(), per_method_quota));
Danyao Wangce175bf2018-12-21 22:35:58433 }
434}
435
Rouslan Solomakhin8e9f149b22019-05-10 17:43:02436bool PaymentRequest::ChangePaymentMethod(const std::string& method_name,
437 const std::string& stringified_data) {
Rouslan Solomakhina480efa2019-05-06 15:37:22438 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Rouslan Solomakhin8e9f149b22019-05-10 17:43:02439 DCHECK(!method_name.empty());
Rouslan Solomakhina480efa2019-05-06 15:37:22440
Rouslan Solomakhin8e9f149b22019-05-10 17:43:02441 if (!state_ || !state_->IsPaymentAppInvoked() || !client_)
442 return false;
Rouslan Solomakhina480efa2019-05-06 15:37:22443
Rouslan Solomakhin8e9f149b22019-05-10 17:43:02444 client_->OnPaymentMethodChange(method_name, stringified_data);
445 return true;
Rouslan Solomakhina480efa2019-05-06 15:37:22446}
447
Rouslan Solomakhin27064702018-12-14 21:15:33448void PaymentRequest::AreRequestedMethodsSupportedCallback(
449 bool methods_supported) {
450 if (methods_supported) {
Sahel Sharifyd3f1bc82019-05-21 18:48:46451 if (SatisfiesSkipUIConstraints())
Rouslan Solomakhin27064702018-12-14 21:15:33452 Pay();
Rouslan Solomakhin27064702018-12-14 21:15:33453 } else {
454 journey_logger_.SetNotShown(
455 JourneyLogger::NOT_SHOWN_REASON_NO_SUPPORTED_PAYMENT_METHOD);
456 client_->OnError(mojom::PaymentErrorReason::NOT_SUPPORTED);
457 if (observer_for_testing_)
458 observer_for_testing_->OnNotSupportedError();
459 OnConnectionTerminated();
460 }
461}
462
463bool PaymentRequest::IsInitialized() const {
464 return is_initialized_ && client_ && client_.is_bound() &&
465 binding_.is_bound();
466}
467
468bool PaymentRequest::IsThisPaymentRequestShowing() const {
469 return is_show_called_ && display_handle_ && spec_ && state_;
470}
471
Sahel Sharifyd3f1bc82019-05-21 18:48:46472bool PaymentRequest::SatisfiesSkipUIConstraints() {
Rouslan Solomakhin9788d4b2019-04-09 13:10:23473 // Only allowing URL base payment apps to skip the payment sheet.
Sahel Sharifyd3f1bc82019-05-21 18:48:46474 skipped_payment_request_ui_ =
475 (spec()->url_payment_method_identifiers().size() == 1 ||
476 skip_ui_for_non_url_payment_method_identifiers_for_test_) &&
477 base::FeatureList::IsEnabled(features::kWebPaymentsSingleAppUiSkip) &&
478 base::FeatureList::IsEnabled(::features::kServiceWorkerPaymentApps) &&
479 is_show_user_gesture_ && state()->IsInitialized() &&
480 spec()->IsInitialized() && state()->available_instruments().size() == 1 &&
481 spec()->stringified_method_data().size() == 1 &&
482 !spec()->request_shipping() && !spec()->request_payer_name() &&
483 !spec()->request_payer_phone() && !spec()->request_payer_email();
484 if (skipped_payment_request_ui_) {
485 DCHECK(state()->IsInitialized() && spec()->IsInitialized());
486 journey_logger_.SetEventOccurred(JourneyLogger::EVENT_SKIPPED_SHOW);
487 } else if (state()->IsInitialized() && spec()->IsInitialized()) {
488 // Set EVENT_SHOWN only after state() and spec() initialization.
489 journey_logger_.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
490 }
491 return skipped_payment_request_ui_;
Rouslan Solomakhin27064702018-12-14 21:15:33492}
493
mathpf1a7a3752017-03-15 11:23:37494void PaymentRequest::OnPaymentResponseAvailable(
495 mojom::PaymentResponsePtr response) {
mathp57c8c862017-06-16 20:15:45496 journey_logger_.SetEventOccurred(
497 JourneyLogger::EVENT_RECEIVED_INSTRUMENT_DETAILS);
gogerald7a2b761e2017-11-09 18:30:19498
499 // Do not send invalid response to client.
500 if (response->method_name.empty() || response->stringified_details.empty()) {
501 RecordFirstAbortReason(
502 JourneyLogger::ABORT_REASON_INSTRUMENT_DETAILS_ERROR);
503 delegate_->ShowErrorMessage();
504 return;
505 }
506
Sahel Sharify26884382019-05-07 16:23:51507 // Log the correct "selected instrument" metric according to its type and
508 // the method name in response.
509 DCHECK(state_->selected_instrument());
510 JourneyLogger::Event selected_event =
511 JourneyLogger::Event::EVENT_SELECTED_OTHER;
512 switch (state_->selected_instrument()->type()) {
513 case PaymentInstrument::Type::AUTOFILL:
514 selected_event = JourneyLogger::Event::EVENT_SELECTED_CREDIT_CARD;
515 break;
516 case PaymentInstrument::Type::SERVICE_WORKER_APP: {
517 selected_event =
518 IsGooglePaymentMethodInstrumentSelected(response->method_name)
519 ? JourneyLogger::Event::EVENT_SELECTED_GOOGLE
520 : JourneyLogger::Event::EVENT_SELECTED_OTHER;
521 break;
522 }
523 case PaymentInstrument::Type::NATIVE_MOBILE_APP:
524 NOTREACHED();
525 break;
526 }
527 journey_logger_.SetEventOccurred(selected_event);
528
Rouslan Solomakhin02d086ec2019-01-31 23:10:39529 // If currently interactive, show the processing spinner. Autofill payment
530 // instruments request a CVC, so they are always interactive at this point. A
531 // payment handler may elect to be non-interactive by not showing a
532 // confirmation page to the user.
533 if (delegate_->IsInteractive())
534 delegate_->ShowProcessingSpinner();
535
mathpf1a7a3752017-03-15 11:23:37536 client_->OnPaymentResponse(std::move(response));
mathp4b85b582017-03-08 21:07:16537}
538
mathp151bd312017-04-03 21:07:24539void PaymentRequest::OnShippingOptionIdSelected(
540 std::string shipping_option_id) {
541 client_->OnShippingOptionChange(shipping_option_id);
542}
543
544void PaymentRequest::OnShippingAddressSelected(
545 mojom::PaymentAddressPtr address) {
Danyao Wang50ccb9f2019-05-09 23:28:03546 // Redact shipping address before exposing it in ShippingAddressChangeEvent.
547 // https://w3c.github.io/payment-request/#shipping-address-changed-algorithm
548 if (PaymentsExperimentalFeatures::IsEnabled(
549 features::kWebPaymentsRedactShippingAddress)) {
550 address->organization.clear();
551 address->phone.clear();
552 address->recipient.clear();
553 address->address_line.clear();
554 }
mathp151bd312017-04-03 21:07:24555 client_->OnShippingAddressChange(std::move(address));
556}
557
Jinho Bangbb178152018-09-13 09:44:43558void PaymentRequest::OnPayerInfoSelected(mojom::PayerDetailPtr payer_info) {
559 client_->OnPayerDetailChange(std::move(payer_info));
560}
561
mathpf4bc50e2017-01-24 05:17:50562void PaymentRequest::UserCancelled() {
563 // If |client_| is not bound, then the object is already being destroyed as
564 // a result of a renderer event.
565 if (!client_.is_bound())
566 return;
567
sebsgfcdd13c2017-06-08 15:49:33568 RecordFirstAbortReason(JourneyLogger::ABORT_REASON_ABORTED_BY_USER);
sebsg20b49d7b2017-05-04 20:23:17569
mathpf4bc50e2017-01-24 05:17:50570 // This sends an error to the renderer, which informs the API user.
rouslan6e3cf7c62017-04-17 21:23:28571 client_->OnError(mojom::PaymentErrorReason::USER_CANCEL);
mathpf4bc50e2017-01-24 05:17:50572
573 // We close all bindings and ask to be destroyed.
574 client_.reset();
575 binding_.Close();
Rouslan Solomakhin8e9f149b22019-05-10 17:43:02576 payment_handler_host_.Disconnect();
rouslanb28f4532017-05-08 15:41:47577 if (observer_for_testing_)
578 observer_for_testing_->OnConnectionTerminated();
mathpf4bc50e2017-01-24 05:17:50579 manager_->DestroyRequest(this);
mathpf709499d2017-01-09 20:48:36580}
581
sebsgd56b3e422017-10-20 18:08:08582void PaymentRequest::DidStartMainFrameNavigationToDifferentDocument(
583 bool is_user_initiated) {
sebsgfcdd13c2017-06-08 15:49:33584 RecordFirstAbortReason(is_user_initiated
585 ? JourneyLogger::ABORT_REASON_USER_NAVIGATION
586 : JourneyLogger::ABORT_REASON_MERCHANT_NAVIGATION);
sebsg2c8558a2017-05-17 18:54:10587}
588
mathpf4bc50e2017-01-24 05:17:50589void PaymentRequest::OnConnectionTerminated() {
590 // We are here because of a browser-side error, or likely as a result of the
591 // connection_error_handler on |binding_|, which can mean that the renderer
592 // has decided to close the pipe for various reasons (see all uses of
593 // PaymentRequest::clearResolversAndCloseMojoConnection() in Blink). We close
594 // the binding and the dialog, and ask to be deleted.
595 client_.reset();
mathpf709499d2017-01-09 20:48:36596 binding_.Close();
Rouslan Solomakhin8e9f149b22019-05-10 17:43:02597 payment_handler_host_.Disconnect();
mathpf4bc50e2017-01-24 05:17:50598 delegate_->CloseDialog();
rouslanb28f4532017-05-08 15:41:47599 if (observer_for_testing_)
600 observer_for_testing_->OnConnectionTerminated();
sebsgfcdd13c2017-06-08 15:49:33601
602 RecordFirstAbortReason(JourneyLogger::ABORT_REASON_MOJO_CONNECTION_ERROR);
mathpf709499d2017-01-09 20:48:36603 manager_->DestroyRequest(this);
604}
605
mathpd4be8de82017-03-01 00:51:48606void PaymentRequest::Pay() {
mathp57c8c862017-06-16 20:15:45607 journey_logger_.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
Mathieu Perreault23d25bfb82018-05-11 14:45:37608 DCHECK(state_->selected_instrument());
Sahel Sharify26884382019-05-07 16:23:51609 if (state_->selected_instrument()->type() ==
610 PaymentInstrument::Type::SERVICE_WORKER_APP) {
Rouslan Solomakhin8e9f149b22019-05-10 17:43:02611 static_cast<ServiceWorkerPaymentInstrument*>(state_->selected_instrument())
612 ->set_payment_handler_host(payment_handler_host_.Bind());
Mathieu Perreault23d25bfb82018-05-11 14:45:37613 }
mathpf1a7a3752017-03-15 11:23:37614 state_->GeneratePaymentResponse();
mathpd4be8de82017-03-01 00:51:48615}
616
Anthony Vallee-Duboisc7ae7332017-12-19 20:44:07617void PaymentRequest::HideIfNecessary() {
618 display_handle_.reset();
619}
620
Anthony Vallee-Dubois10d131a2018-02-22 15:41:04621bool PaymentRequest::IsIncognito() const {
622 return delegate_->IsIncognito();
623}
624
Sahel Sharifyd3f1bc82019-05-21 18:48:46625void PaymentRequest::SetSkipUiForNonUrlPaymentMethodIdentifiersForTest() {
626 journey_logger_.set_skip_ui_for_non_url_payment_method_identifiers_for_test();
627 skip_ui_for_non_url_payment_method_identifiers_for_test_ = true;
628}
629
sebsgfcdd13c2017-06-08 15:49:33630void PaymentRequest::RecordFirstAbortReason(
631 JourneyLogger::AbortReason abort_reason) {
632 if (!has_recorded_completion_) {
633 has_recorded_completion_ = true;
634 journey_logger_.SetAborted(abort_reason);
sebsg2c8558a2017-05-17 18:54:10635 }
636}
637
Danyao Wang57aa0442019-01-31 04:06:41638void PaymentRequest::CanMakePaymentCallback(bool legacy_mode,
639 bool can_make_payment) {
640 // Only need to enforce query quota in legacy mode. Per-method quota not
641 // supported.
642 if (legacy_mode && spec_ &&
643 !CanMakePaymentQueryFactory::GetInstance()
644 ->GetForContext(web_contents_->GetBrowserContext())
645 ->CanQuery(top_level_origin_, frame_origin_,
646 spec_->stringified_method_data(),
647 /*per_method_quota=*/false)) {
Rouslan Solomakhin77a7e1a2019-05-23 17:37:58648 if (UrlUtil::IsLocalDevelopmentUrl(frame_origin_)) {
Danyao Wang57aa0442019-01-31 04:06:41649 client_->OnCanMakePayment(
650 can_make_payment
651 ? CanMakePaymentQueryResult::WARNING_CAN_MAKE_PAYMENT
652 : CanMakePaymentQueryResult::WARNING_CANNOT_MAKE_PAYMENT);
653 } else {
654 client_->OnCanMakePayment(
655 CanMakePaymentQueryResult::QUERY_QUOTA_EXCEEDED);
656 }
657 } else {
658 client_->OnCanMakePayment(
659 can_make_payment
660 ? mojom::CanMakePaymentQueryResult::CAN_MAKE_PAYMENT
661 : mojom::CanMakePaymentQueryResult::CANNOT_MAKE_PAYMENT);
662 }
Danyao Wang4bc0606a2018-12-27 16:54:53663
Danyao Wang57aa0442019-01-31 04:06:41664 journey_logger_.SetCanMakePaymentValue(can_make_payment);
Rouslan Solomakhin1804ee42017-10-03 14:27:43665
666 if (observer_for_testing_)
667 observer_for_testing_->OnCanMakePaymentReturned();
668}
669
Danyao Wangce175bf2018-12-21 22:35:58670void PaymentRequest::HasEnrolledInstrumentCallback(
Rouslan Solomakhin5683eb282019-01-29 18:06:03671 bool per_method_quota,
Danyao Wangce175bf2018-12-21 22:35:58672 bool has_enrolled_instrument) {
Rouslan Solomakhin5683eb282019-01-29 18:06:03673 if (!spec_ ||
674 CanMakePaymentQueryFactory::GetInstance()
675 ->GetForContext(web_contents_->GetBrowserContext())
676 ->CanQuery(top_level_origin_, frame_origin_,
677 spec_->stringified_method_data(), per_method_quota)) {
Danyao Wangce175bf2018-12-21 22:35:58678 RespondToHasEnrolledInstrumentQuery(has_enrolled_instrument,
Rouslan Solomakhin77a7e1a2019-05-23 17:37:58679 /*warn_local_development=*/false);
680 } else if (UrlUtil::IsLocalDevelopmentUrl(frame_origin_)) {
Danyao Wangce175bf2018-12-21 22:35:58681 RespondToHasEnrolledInstrumentQuery(has_enrolled_instrument,
Rouslan Solomakhin77a7e1a2019-05-23 17:37:58682 /*warn_local_development=*/true);
Danyao Wangce175bf2018-12-21 22:35:58683 } else {
684 client_->OnHasEnrolledInstrument(
685 HasEnrolledInstrumentQueryResult::QUERY_QUOTA_EXCEEDED);
686 }
687
688 if (observer_for_testing_)
689 observer_for_testing_->OnHasEnrolledInstrumentReturned();
690}
691
Danyao Wangce175bf2018-12-21 22:35:58692void PaymentRequest::RespondToHasEnrolledInstrumentQuery(
693 bool has_enrolled_instrument,
Rouslan Solomakhin77a7e1a2019-05-23 17:37:58694 bool warn_local_development) {
Danyao Wangce175bf2018-12-21 22:35:58695 HasEnrolledInstrumentQueryResult positive =
Rouslan Solomakhin77a7e1a2019-05-23 17:37:58696 warn_local_development
Danyao Wangce175bf2018-12-21 22:35:58697 ? HasEnrolledInstrumentQueryResult::WARNING_HAS_ENROLLED_INSTRUMENT
698 : HasEnrolledInstrumentQueryResult::HAS_ENROLLED_INSTRUMENT;
699 HasEnrolledInstrumentQueryResult negative =
Rouslan Solomakhin77a7e1a2019-05-23 17:37:58700 warn_local_development
Danyao Wangce175bf2018-12-21 22:35:58701 ? HasEnrolledInstrumentQueryResult::WARNING_HAS_NO_ENROLLED_INSTRUMENT
702 : HasEnrolledInstrumentQueryResult::HAS_NO_ENROLLED_INSTRUMENT;
703
704 client_->OnHasEnrolledInstrument(has_enrolled_instrument ? positive
705 : negative);
Danyao Wang57aa0442019-01-31 04:06:41706 journey_logger_.SetHasEnrolledInstrumentValue(has_enrolled_instrument);
Danyao Wangce175bf2018-12-21 22:35:58707}
708
mathpf709499d2017-01-09 20:48:36709} // namespace payments