blob: 30e7114dec46073768345c6b52486d05dcc98ef1 [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
Rouslan Solomakhin1804ee42017-10-03 14:27:4310#include "base/feature_list.h"
tmartino68c0a272017-01-19 17:44:0811#include "base/memory/ptr_util.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"
rouslan6e3cf7c62017-04-17 21:23:2814#include "components/payments/content/origin_security_checker.h"
Mohamad Ahmadif5544bb2017-09-01 21:48:2215#include "components/payments/content/payment_request_converter.h"
rouslan908248c2017-02-27 21:30:2416#include "components/payments/content/payment_request_web_contents_manager.h"
rouslan690997682017-05-09 18:07:3917#include "components/payments/core/can_make_payment_query.h"
Mohamad Ahmadif5544bb2017-09-01 21:48:2218#include "components/payments/core/payment_details.h"
19#include "components/payments/core/payment_details_validation.h"
anthonyvd6a43b932017-05-11 18:39:2720#include "components/payments/core/payment_prefs.h"
21#include "components/prefs/pref_service.h"
Rouslan Solomakhin6e979ab2017-08-30 17:30:3922#include "components/url_formatter/elide_url.h"
mathpf709499d2017-01-09 20:48:3623#include "content/public/browser/browser_thread.h"
rouslan690997682017-05-09 18:07:3924#include "content/public/browser/render_frame_host.h"
mathpf709499d2017-01-09 20:48:3625#include "content/public/browser/web_contents.h"
Rouslan Solomakhin1804ee42017-10-03 14:27:4326#include "content/public/common/content_features.h"
mathpf709499d2017-01-09 20:48:3627
28namespace payments {
29
30PaymentRequest::PaymentRequest(
rouslan690997682017-05-09 18:07:3931 content::RenderFrameHost* render_frame_host,
mathpf709499d2017-01-09 20:48:3632 content::WebContents* web_contents,
33 std::unique_ptr<PaymentRequestDelegate> delegate,
34 PaymentRequestWebContentsManager* manager,
rouslan6e3cf7c62017-04-17 21:23:2835 mojo::InterfaceRequest<mojom::PaymentRequest> request,
mathp300fa542017-03-27 19:29:3736 ObserverForTest* observer_for_testing)
mathpf709499d2017-01-09 20:48:3637 : web_contents_(web_contents),
38 delegate_(std::move(delegate)),
39 manager_(manager),
mathp300fa542017-03-27 19:29:3740 binding_(this, std::move(request)),
Rouslan Solomakhin6e979ab2017-08-30 17:30:3941 top_level_origin_(url_formatter::FormatUrlForSecurityDisplay(
42 web_contents_->GetLastCommittedURL())),
43 frame_origin_(url_formatter::FormatUrlForSecurityDisplay(
44 render_frame_host->GetLastCommittedURL())),
sebsg20b49d7b2017-05-04 20:23:1745 observer_for_testing_(observer_for_testing),
46 journey_logger_(delegate_->IsIncognito(),
47 web_contents_->GetLastCommittedURL(),
Anthony Vallee-Duboisdc1dbf1a2017-07-17 15:01:1348 delegate_->GetUkmRecorder()),
49 weak_ptr_factory_(this) {
mathpf4bc50e2017-01-24 05:17:5050 // OnConnectionTerminated will be called when the Mojo pipe is closed. This
51 // will happen as a result of many renderer-side events (both successful and
52 // erroneous in nature).
53 // TODO(crbug.com/683636): Investigate using
54 // set_connection_error_with_reason_handler with Binding::CloseWithReason.
55 binding_.set_connection_error_handler(base::Bind(
Anthony Vallee-Duboisdc1dbf1a2017-07-17 15:01:1356 &PaymentRequest::OnConnectionTerminated, weak_ptr_factory_.GetWeakPtr()));
mathpf709499d2017-01-09 20:48:3657}
58
59PaymentRequest::~PaymentRequest() {}
60
rouslan6e3cf7c62017-04-17 21:23:2861void PaymentRequest::Init(mojom::PaymentRequestClientPtr client,
62 std::vector<mojom::PaymentMethodDataPtr> method_data,
63 mojom::PaymentDetailsPtr details,
64 mojom::PaymentOptionsPtr options) {
mathpf709499d2017-01-09 20:48:3665 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
rouslan6e3cf7c62017-04-17 21:23:2866 client_ = std::move(client);
67
rouslanb28f4532017-05-08 15:41:4768 const GURL last_committed_url = delegate_->GetLastCommittedURL();
69 if (!OriginSecurityChecker::IsOriginSecure(last_committed_url)) {
rouslan6e3cf7c62017-04-17 21:23:2870 LOG(ERROR) << "Not in a secure origin";
71 OnConnectionTerminated();
72 return;
73 }
74
rouslanb28f4532017-05-08 15:41:4775 bool allowed_origin =
76 OriginSecurityChecker::IsSchemeCryptographic(last_committed_url) ||
77 OriginSecurityChecker::IsOriginLocalhostOrFile(last_committed_url);
78 if (!allowed_origin) {
79 LOG(ERROR) << "Only localhost, file://, and cryptographic scheme origins "
80 "allowed";
81 }
82
83 bool invalid_ssl =
84 OriginSecurityChecker::IsSchemeCryptographic(last_committed_url) &&
85 !delegate_->IsSslCertificateValid();
86 if (invalid_ssl)
rouslan6e3cf7c62017-04-17 21:23:2887 LOG(ERROR) << "SSL certificate is not valid";
rouslanb28f4532017-05-08 15:41:4788
89 if (!allowed_origin || invalid_ssl) {
rouslan6e3cf7c62017-04-17 21:23:2890 // Don't show UI. Resolve .canMakepayment() with "false". Reject .show()
91 // with "NotSupportedError".
92 spec_ = base::MakeUnique<PaymentRequestSpec>(
93 mojom::PaymentOptions::New(), mojom::PaymentDetails::New(),
94 std::vector<mojom::PaymentMethodDataPtr>(), this,
95 delegate_->GetApplicationLocale());
96 state_ = base::MakeUnique<PaymentRequestState>(
gogerald7a0cc3e2017-09-19 03:35:4897 web_contents_->GetBrowserContext(), top_level_origin_, frame_origin_,
98 spec_.get(), this, delegate_->GetApplicationLocale(),
99 delegate_->GetPersonalDataManager(), delegate_.get(), &journey_logger_);
rouslan6e3cf7c62017-04-17 21:23:28100 return;
101 }
102
mathpf709499d2017-01-09 20:48:36103 std::string error;
Mohamad Ahmadif5544bb2017-09-01 21:48:22104 if (!ValidatePaymentDetails(ConvertPaymentDetails(details), &error)) {
mathpf709499d2017-01-09 20:48:36105 LOG(ERROR) << error;
mathpf4bc50e2017-01-24 05:17:50106 OnConnectionTerminated();
mathpf709499d2017-01-09 20:48:36107 return;
108 }
rouslan6e3cf7c62017-04-17 21:23:28109
jinho.bangfcb5ec92017-03-29 08:08:02110 if (!details->total) {
111 LOG(ERROR) << "Missing total";
112 OnConnectionTerminated();
113 return;
114 }
rouslan6e3cf7c62017-04-17 21:23:28115
mathpf1a7a3752017-03-15 11:23:37116 spec_ = base::MakeUnique<PaymentRequestSpec>(
mathpc0d616a2017-03-15 14:09:33117 std::move(options), std::move(details), std::move(method_data), this,
118 delegate_->GetApplicationLocale());
119 state_ = base::MakeUnique<PaymentRequestState>(
gogerald7a0cc3e2017-09-19 03:35:48120 web_contents_->GetBrowserContext(), top_level_origin_, frame_origin_,
121 spec_.get(), this, delegate_->GetApplicationLocale(),
122 delegate_->GetPersonalDataManager(), delegate_.get(), &journey_logger_);
Mathieu Perreault627b97c2017-08-12 00:44:22123
124 journey_logger_.SetRequestedInformation(
125 spec_->request_shipping(), spec_->request_payer_email(),
126 spec_->request_payer_phone(), spec_->request_payer_name());
127
128 // Log metrics around which payment methods are requested by the merchant.
129 GURL google_pay_url(kGooglePayMethodName);
130 GURL android_pay_url(kAndroidPayMethodName);
131 // Looking for payment methods that are NOT google-related payment methods.
132 auto non_google_it =
133 std::find_if(spec_->url_payment_method_identifiers().begin(),
134 spec_->url_payment_method_identifiers().end(),
135 [google_pay_url, android_pay_url](const GURL& url) {
136 return url != google_pay_url && url != android_pay_url;
137 });
138 journey_logger_.SetRequestedPaymentMethodTypes(
139 /*requested_basic_card=*/!spec_->supported_card_networks().empty(),
140 /*requested_method_google=*/
141 base::ContainsValue(spec_->url_payment_method_identifiers(),
142 google_pay_url) ||
143 base::ContainsValue(spec_->url_payment_method_identifiers(),
144 android_pay_url),
145 /*requested_method_other=*/non_google_it !=
146 spec_->url_payment_method_identifiers().end());
mathpf709499d2017-01-09 20:48:36147}
148
149void PaymentRequest::Show() {
tmartino8ce922852017-01-09 22:23:10150 if (!client_.is_bound() || !binding_.is_bound()) {
mathpf4bc50e2017-01-24 05:17:50151 LOG(ERROR) << "Attempted Show(), but binding(s) missing.";
152 OnConnectionTerminated();
tmartino8ce922852017-01-09 22:23:10153 return;
154 }
rouslan6e3cf7c62017-04-17 21:23:28155
rouslan7d433cc22017-05-08 15:18:07156 // A tab can display only one PaymentRequest UI at a time.
157 if (!manager_->CanShow(this)) {
158 LOG(ERROR) << "A PaymentRequest UI is already showing";
sebsg828269bc2017-06-09 19:11:12159 journey_logger_.SetNotShown(
160 JourneyLogger::NOT_SHOWN_REASON_CONCURRENT_REQUESTS);
rouslan7d433cc22017-05-08 15:18:07161 client_->OnError(mojom::PaymentErrorReason::USER_CANCEL);
162 OnConnectionTerminated();
163 return;
164 }
165
Rouslan Solomakhin5b510432017-09-26 16:59:32166 if (!delegate_->IsBrowserWindowActive()) {
167 LOG(ERROR) << "Cannot show PaymentRequest UI in a background tab";
168 journey_logger_.SetNotShown(JourneyLogger::NOT_SHOWN_REASON_OTHER);
169 client_->OnError(mojom::PaymentErrorReason::USER_CANCEL);
170 OnConnectionTerminated();
171 return;
172 }
173
rouslan6e3cf7c62017-04-17 21:23:28174 if (!state_->AreRequestedMethodsSupported()) {
sebsg828269bc2017-06-09 19:11:12175 journey_logger_.SetNotShown(
176 JourneyLogger::NOT_SHOWN_REASON_NO_SUPPORTED_PAYMENT_METHOD);
rouslan6e3cf7c62017-04-17 21:23:28177 client_->OnError(mojom::PaymentErrorReason::NOT_SUPPORTED);
178 if (observer_for_testing_)
179 observer_for_testing_->OnNotSupportedError();
180 OnConnectionTerminated();
181 return;
182 }
183
mathp57c8c862017-06-16 20:15:45184 journey_logger_.SetEventOccurred(JourneyLogger::EVENT_SHOWN);
sebsg8f4fa4d2017-06-13 15:25:45185
mathpf4bc50e2017-01-24 05:17:50186 delegate_->ShowDialog(this);
mathpf709499d2017-01-09 20:48:36187}
188
mathp151bd31e2017-04-03 21:07:24189void PaymentRequest::UpdateWith(mojom::PaymentDetailsPtr details) {
190 std::string error;
Mohamad Ahmadif5544bb2017-09-01 21:48:22191 if (!ValidatePaymentDetails(ConvertPaymentDetails(details), &error)) {
mathp151bd31e2017-04-03 21:07:24192 LOG(ERROR) << error;
193 OnConnectionTerminated();
194 return;
195 }
Rouslan Solomakhin4cbda822017-08-23 18:50:39196
197 if (!details->total) {
198 LOG(ERROR) << "Missing total";
199 OnConnectionTerminated();
200 return;
201 }
202
mathp151bd31e2017-04-03 21:07:24203 spec_->UpdateWith(std::move(details));
204}
205
mathpf4bc50e2017-01-24 05:17:50206void PaymentRequest::Abort() {
Anthony Vallee-Dubois6813c1442017-05-17 19:32:56207 // The API user has decided to abort. If a successful abort message is
208 // returned to the renderer, the Mojo message pipe is closed, which triggers
mathpf4bc50e2017-01-24 05:17:50209 // PaymentRequest::OnConnectionTerminated, which destroys this object.
Anthony Vallee-Dubois6813c1442017-05-17 19:32:56210 // Otherwise, the abort promise is rejected and the pipe is not closed.
211 // The abort is only successful if the payment app wasn't yet invoked.
212 // TODO(crbug.com/716546): Add a merchant abort metric
213
214 bool accepting_abort = !state_->IsPaymentAppInvoked();
sebsgfcdd13c2017-06-08 15:49:33215 if (accepting_abort)
216 RecordFirstAbortReason(JourneyLogger::ABORT_REASON_ABORTED_BY_MERCHANT);
Anthony Vallee-Dubois6813c1442017-05-17 19:32:56217
mathpf4bc50e2017-01-24 05:17:50218 if (client_.is_bound())
Anthony Vallee-Dubois6813c1442017-05-17 19:32:56219 client_->OnAbort(accepting_abort);
220
221 if (observer_for_testing_)
222 observer_for_testing_->OnAbortCalled();
mathpf4bc50e2017-01-24 05:17:50223}
224
mathp218795892017-03-29 15:15:34225void PaymentRequest::Complete(mojom::PaymentComplete result) {
mathp4b85b582017-03-08 21:07:16226 if (!client_.is_bound())
227 return;
228
Rouslan Solomakhine3473192017-06-16 14:54:57229 // Failed transactions show an error. Successful and unknown-state
230 // transactions don't show an error.
231 if (result == mojom::PaymentComplete::FAIL) {
mathp218795892017-03-29 15:15:34232 delegate_->ShowErrorMessage();
233 } else {
sebsgfcdd13c2017-06-08 15:49:33234 DCHECK(!has_recorded_completion_);
sebsgf8272a22017-05-26 14:32:58235 journey_logger_.SetCompleted();
sebsgfcdd13c2017-06-08 15:49:33236 has_recorded_completion_ = true;
237
anthonyvd6a43b932017-05-11 18:39:27238 delegate_->GetPrefService()->SetBoolean(kPaymentsFirstTransactionCompleted,
239 true);
mathp218795892017-03-29 15:15:34240 // When the renderer closes the connection,
241 // PaymentRequest::OnConnectionTerminated will be called.
242 client_->OnComplete();
sebsg8a93b272017-05-11 19:30:22243 state_->RecordUseStats();
mathp218795892017-03-29 15:15:34244 }
mathp4b85b582017-03-08 21:07:16245}
246
247void PaymentRequest::CanMakePayment() {
gogerald8189d522017-09-15 17:52:18248 state()->CanMakePayment(base::BindOnce(
249 &PaymentRequest::CanMakePaymentCallback, weak_ptr_factory_.GetWeakPtr()));
250
251 if (observer_for_testing_)
252 observer_for_testing_->OnCanMakePaymentCalled();
253}
254
mathpf1a7a3752017-03-15 11:23:37255void PaymentRequest::OnPaymentResponseAvailable(
256 mojom::PaymentResponsePtr response) {
mathp57c8c862017-06-16 20:15:45257 journey_logger_.SetEventOccurred(
258 JourneyLogger::EVENT_RECEIVED_INSTRUMENT_DETAILS);
mathpf1a7a3752017-03-15 11:23:37259 client_->OnPaymentResponse(std::move(response));
mathp4b85b582017-03-08 21:07:16260}
261
mathp151bd31e2017-04-03 21:07:24262void PaymentRequest::OnShippingOptionIdSelected(
263 std::string shipping_option_id) {
264 client_->OnShippingOptionChange(shipping_option_id);
265}
266
267void PaymentRequest::OnShippingAddressSelected(
268 mojom::PaymentAddressPtr address) {
269 client_->OnShippingAddressChange(std::move(address));
270}
271
mathpf4bc50e2017-01-24 05:17:50272void PaymentRequest::UserCancelled() {
273 // If |client_| is not bound, then the object is already being destroyed as
274 // a result of a renderer event.
275 if (!client_.is_bound())
276 return;
277
sebsgfcdd13c2017-06-08 15:49:33278 RecordFirstAbortReason(JourneyLogger::ABORT_REASON_ABORTED_BY_USER);
sebsg20b49d7b2017-05-04 20:23:17279
mathpf4bc50e2017-01-24 05:17:50280 // This sends an error to the renderer, which informs the API user.
rouslan6e3cf7c62017-04-17 21:23:28281 client_->OnError(mojom::PaymentErrorReason::USER_CANCEL);
mathpf4bc50e2017-01-24 05:17:50282
283 // We close all bindings and ask to be destroyed.
284 client_.reset();
285 binding_.Close();
rouslanb28f4532017-05-08 15:41:47286 if (observer_for_testing_)
287 observer_for_testing_->OnConnectionTerminated();
mathpf4bc50e2017-01-24 05:17:50288 manager_->DestroyRequest(this);
mathpf709499d2017-01-09 20:48:36289}
290
sebsg2c8558a2017-05-17 18:54:10291void PaymentRequest::DidStartNavigation(bool is_user_initiated) {
sebsgfcdd13c2017-06-08 15:49:33292 RecordFirstAbortReason(is_user_initiated
293 ? JourneyLogger::ABORT_REASON_USER_NAVIGATION
294 : JourneyLogger::ABORT_REASON_MERCHANT_NAVIGATION);
sebsg2c8558a2017-05-17 18:54:10295}
296
mathpf4bc50e2017-01-24 05:17:50297void PaymentRequest::OnConnectionTerminated() {
298 // We are here because of a browser-side error, or likely as a result of the
299 // connection_error_handler on |binding_|, which can mean that the renderer
300 // has decided to close the pipe for various reasons (see all uses of
301 // PaymentRequest::clearResolversAndCloseMojoConnection() in Blink). We close
302 // the binding and the dialog, and ask to be deleted.
303 client_.reset();
mathpf709499d2017-01-09 20:48:36304 binding_.Close();
mathpf4bc50e2017-01-24 05:17:50305 delegate_->CloseDialog();
rouslanb28f4532017-05-08 15:41:47306 if (observer_for_testing_)
307 observer_for_testing_->OnConnectionTerminated();
sebsgfcdd13c2017-06-08 15:49:33308
309 RecordFirstAbortReason(JourneyLogger::ABORT_REASON_MOJO_CONNECTION_ERROR);
mathpf709499d2017-01-09 20:48:36310 manager_->DestroyRequest(this);
311}
312
mathpd4be8de82017-03-01 00:51:48313void PaymentRequest::Pay() {
mathp57c8c862017-06-16 20:15:45314 journey_logger_.SetEventOccurred(JourneyLogger::EVENT_PAY_CLICKED);
Mathieu Perreault49a97e52017-08-15 01:51:36315 journey_logger_.SetEventOccurred(JourneyLogger::EVENT_SELECTED_CREDIT_CARD);
mathpf1a7a3752017-03-15 11:23:37316 state_->GeneratePaymentResponse();
mathpd4be8de82017-03-01 00:51:48317}
318
sebsgfcdd13c2017-06-08 15:49:33319void PaymentRequest::RecordFirstAbortReason(
320 JourneyLogger::AbortReason abort_reason) {
321 if (!has_recorded_completion_) {
322 has_recorded_completion_ = true;
323 journey_logger_.SetAborted(abort_reason);
sebsg2c8558a2017-05-17 18:54:10324 }
325}
326
Rouslan Solomakhin1804ee42017-10-03 14:27:43327void PaymentRequest::CanMakePaymentCallback(bool can_make_payment) {
Rouslan Solomakhin16ee55202017-10-03 19:04:42328 if (CanMakePaymentQueryFactory::GetInstance()
329 ->GetForContext(web_contents_->GetBrowserContext())
330 ->CanQuery(top_level_origin_, frame_origin_,
331 spec()->stringified_method_data())) {
332 RespondToCanMakePaymentQuery(can_make_payment, false);
Rouslan Solomakhin1804ee42017-10-03 14:27:43333 } else if (OriginSecurityChecker::IsOriginLocalhostOrFile(frame_origin_)) {
Rouslan Solomakhin16ee55202017-10-03 19:04:42334 RespondToCanMakePaymentQuery(can_make_payment, true);
Rouslan Solomakhin1804ee42017-10-03 14:27:43335 } else {
336 client_->OnCanMakePayment(
337 mojom::CanMakePaymentQueryResult::QUERY_QUOTA_EXCEEDED);
338 }
339
340 if (observer_for_testing_)
341 observer_for_testing_->OnCanMakePaymentReturned();
342}
343
Rouslan Solomakhin16ee55202017-10-03 19:04:42344void PaymentRequest::RespondToCanMakePaymentQuery(bool can_make_payment,
345 bool warn_localhost_or_file) {
346 if (delegate_->IsIncognito()) {
347 can_make_payment =
348 spec()->HasBasicCardMethodName() ||
349 base::FeatureList::IsEnabled(features::kServiceWorkerPaymentApps);
350 }
351
352 mojom::CanMakePaymentQueryResult positive =
353 warn_localhost_or_file
354 ? mojom::CanMakePaymentQueryResult::WARNING_CAN_MAKE_PAYMENT
355 : mojom::CanMakePaymentQueryResult::CAN_MAKE_PAYMENT;
356 mojom::CanMakePaymentQueryResult negative =
357 warn_localhost_or_file
358 ? mojom::CanMakePaymentQueryResult::WARNING_CANNOT_MAKE_PAYMENT
359 : mojom::CanMakePaymentQueryResult::CANNOT_MAKE_PAYMENT;
360
361 client_->OnCanMakePayment(can_make_payment ? positive : negative);
Rouslan Solomakhin1804ee42017-10-03 14:27:43362 journey_logger_.SetCanMakePaymentValue(can_make_payment);
363}
364
mathpf709499d2017-01-09 20:48:36365} // namespace payments