| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "components/payments/content/payment_request.h" | 5 #include "components/payments/content/payment_request.h" |
| 6 | 6 |
| 7 #include <algorithm> | |
| 8 #include <set> | |
| 9 #include <unordered_map> | |
| 10 #include <utility> | 7 #include <utility> |
| 11 | 8 |
| 12 #include "base/memory/ptr_util.h" | 9 #include "base/memory/ptr_util.h" |
| 13 #include "components/autofill/core/browser/autofill_data_util.h" | |
| 14 #include "components/autofill/core/browser/field_types.h" | |
| 15 #include "components/autofill/core/browser/personal_data_manager.h" | 10 #include "components/autofill/core/browser/personal_data_manager.h" |
| 16 #include "components/payments/content/payment_details_validation.h" | 11 #include "components/payments/content/payment_details_validation.h" |
| 17 #include "components/payments/content/payment_request_web_contents_manager.h" | 12 #include "components/payments/content/payment_request_web_contents_manager.h" |
| 18 #include "components/payments/core/autofill_payment_instrument.h" | |
| 19 #include "components/payments/core/currency_formatter.h" | 13 #include "components/payments/core/currency_formatter.h" |
| 20 #include "content/public/browser/browser_thread.h" | 14 #include "content/public/browser/browser_thread.h" |
| 21 #include "content/public/browser/web_contents.h" | 15 #include "content/public/browser/web_contents.h" |
| 22 | 16 |
| 23 namespace payments { | 17 namespace payments { |
| 24 | 18 |
| 25 namespace { | |
| 26 // Identifier for the basic card payment method in the PaymentMethodData. | |
| 27 static const char* const kBasicCardMethodName = "basic-card"; | |
| 28 } // namespace | |
| 29 | |
| 30 PaymentRequest::PaymentRequest( | 19 PaymentRequest::PaymentRequest( |
| 31 content::WebContents* web_contents, | 20 content::WebContents* web_contents, |
| 32 std::unique_ptr<PaymentRequestDelegate> delegate, | 21 std::unique_ptr<PaymentRequestDelegate> delegate, |
| 33 PaymentRequestWebContentsManager* manager, | 22 PaymentRequestWebContentsManager* manager, |
| 34 mojo::InterfaceRequest<payments::mojom::PaymentRequest> request) | 23 mojo::InterfaceRequest<payments::mojom::PaymentRequest> request) |
| 35 : web_contents_(web_contents), | 24 : web_contents_(web_contents), |
| 36 delegate_(std::move(delegate)), | 25 delegate_(std::move(delegate)), |
| 37 manager_(manager), | 26 manager_(manager), |
| 38 binding_(this, std::move(request)), | 27 binding_(this, std::move(request)) { |
| 39 is_ready_to_pay_(false), | |
| 40 selected_shipping_profile_(nullptr), | |
| 41 selected_contact_profile_(nullptr), | |
| 42 selected_credit_card_(nullptr), | |
| 43 selected_shipping_option_(nullptr) { | |
| 44 // OnConnectionTerminated will be called when the Mojo pipe is closed. This | 28 // OnConnectionTerminated will be called when the Mojo pipe is closed. This |
| 45 // will happen as a result of many renderer-side events (both successful and | 29 // will happen as a result of many renderer-side events (both successful and |
| 46 // erroneous in nature). | 30 // erroneous in nature). |
| 47 // TODO(crbug.com/683636): Investigate using | 31 // TODO(crbug.com/683636): Investigate using |
| 48 // set_connection_error_with_reason_handler with Binding::CloseWithReason. | 32 // set_connection_error_with_reason_handler with Binding::CloseWithReason. |
| 49 binding_.set_connection_error_handler(base::Bind( | 33 binding_.set_connection_error_handler(base::Bind( |
| 50 &PaymentRequest::OnConnectionTerminated, base::Unretained(this))); | 34 &PaymentRequest::OnConnectionTerminated, base::Unretained(this))); |
| 51 } | 35 } |
| 52 | 36 |
| 53 PaymentRequest::~PaymentRequest() {} | 37 PaymentRequest::~PaymentRequest() {} |
| 54 | 38 |
| 55 void PaymentRequest::Init( | 39 void PaymentRequest::Init( |
| 56 payments::mojom::PaymentRequestClientPtr client, | 40 payments::mojom::PaymentRequestClientPtr client, |
| 57 std::vector<payments::mojom::PaymentMethodDataPtr> method_data, | 41 std::vector<payments::mojom::PaymentMethodDataPtr> method_data, |
| 58 payments::mojom::PaymentDetailsPtr details, | 42 payments::mojom::PaymentDetailsPtr details, |
| 59 payments::mojom::PaymentOptionsPtr options) { | 43 payments::mojom::PaymentOptionsPtr options) { |
| 60 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | 44 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 61 std::string error; | 45 std::string error; |
| 62 if (!payments::validatePaymentDetails(details, &error)) { | 46 if (!payments::validatePaymentDetails(details, &error)) { |
| 63 LOG(ERROR) << error; | 47 LOG(ERROR) << error; |
| 64 OnConnectionTerminated(); | 48 OnConnectionTerminated(); |
| 65 return; | 49 return; |
| 66 } | 50 } |
| 67 client_ = std::move(client); | 51 client_ = std::move(client); |
| 68 details_ = std::move(details); | 52 spec_ = base::MakeUnique<PaymentRequestSpec>( |
| 69 options_ = std::move(options); | 53 std::move(options), std::move(details), std::move(method_data), this); |
| 70 PopulateValidatedMethodData(method_data); | 54 state_ = base::MakeUnique<PaymentRequestState>(spec_.get(), this); |
| 71 PopulateProfileCache(); | |
| 72 SetDefaultProfileSelections(); | |
| 73 UpdateSelectedShippingOptionFromDetails(); | |
| 74 } | 55 } |
| 75 | 56 |
| 76 void PaymentRequest::Show() { | 57 void PaymentRequest::Show() { |
| 77 if (!client_.is_bound() || !binding_.is_bound()) { | 58 if (!client_.is_bound() || !binding_.is_bound()) { |
| 78 LOG(ERROR) << "Attempted Show(), but binding(s) missing."; | 59 LOG(ERROR) << "Attempted Show(), but binding(s) missing."; |
| 79 OnConnectionTerminated(); | 60 OnConnectionTerminated(); |
| 80 return; | 61 return; |
| 81 } | 62 } |
| 82 delegate_->ShowDialog(this); | 63 delegate_->ShowDialog(this); |
| 83 } | 64 } |
| (...skipping 15 matching lines...) Expand all Loading... |
| 99 // When the renderer closes the connection, | 80 // When the renderer closes the connection, |
| 100 // PaymentRequest::OnConnectionTerminated will be called. | 81 // PaymentRequest::OnConnectionTerminated will be called. |
| 101 client_->OnComplete(); | 82 client_->OnComplete(); |
| 102 } | 83 } |
| 103 | 84 |
| 104 void PaymentRequest::CanMakePayment() { | 85 void PaymentRequest::CanMakePayment() { |
| 105 // TODO(mathp): Return whether we can make payment. | 86 // TODO(mathp): Return whether we can make payment. |
| 106 client_->OnCanMakePayment(mojom::CanMakePaymentQueryResult::CAN_MAKE_PAYMENT); | 87 client_->OnCanMakePayment(mojom::CanMakePaymentQueryResult::CAN_MAKE_PAYMENT); |
| 107 } | 88 } |
| 108 | 89 |
| 109 void PaymentRequest::OnInstrumentDetailsReady( | 90 void PaymentRequest::OnInvalidSpecProvided() { |
| 110 const std::string& method_name, | 91 OnConnectionTerminated(); |
| 111 const std::string& stringified_details) { | 92 } |
| 112 payment_response_->method_name = method_name; | 93 |
| 113 payment_response_->stringified_details = stringified_details; | 94 const std::string& PaymentRequest::GetApplicationLocale() { |
| 114 client_->OnPaymentResponse(std::move(payment_response_)); | 95 return delegate_->GetApplicationLocale(); |
| 96 } |
| 97 |
| 98 autofill::PersonalDataManager* PaymentRequest::GetPersonalDataManager() { |
| 99 return delegate_->GetPersonalDataManager(); |
| 100 } |
| 101 |
| 102 void PaymentRequest::OnPaymentResponseAvailable( |
| 103 mojom::PaymentResponsePtr response) { |
| 104 client_->OnPaymentResponse(std::move(response)); |
| 115 } | 105 } |
| 116 | 106 |
| 117 void PaymentRequest::UserCancelled() { | 107 void PaymentRequest::UserCancelled() { |
| 118 // If |client_| is not bound, then the object is already being destroyed as | 108 // If |client_| is not bound, then the object is already being destroyed as |
| 119 // a result of a renderer event. | 109 // a result of a renderer event. |
| 120 if (!client_.is_bound()) | 110 if (!client_.is_bound()) |
| 121 return; | 111 return; |
| 122 | 112 |
| 123 // This sends an error to the renderer, which informs the API user. | 113 // This sends an error to the renderer, which informs the API user. |
| 124 client_->OnError(payments::mojom::PaymentErrorReason::USER_CANCEL); | 114 client_->OnError(payments::mojom::PaymentErrorReason::USER_CANCEL); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 135 // has decided to close the pipe for various reasons (see all uses of | 125 // has decided to close the pipe for various reasons (see all uses of |
| 136 // PaymentRequest::clearResolversAndCloseMojoConnection() in Blink). We close | 126 // PaymentRequest::clearResolversAndCloseMojoConnection() in Blink). We close |
| 137 // the binding and the dialog, and ask to be deleted. | 127 // the binding and the dialog, and ask to be deleted. |
| 138 client_.reset(); | 128 client_.reset(); |
| 139 binding_.Close(); | 129 binding_.Close(); |
| 140 delegate_->CloseDialog(); | 130 delegate_->CloseDialog(); |
| 141 manager_->DestroyRequest(this); | 131 manager_->DestroyRequest(this); |
| 142 } | 132 } |
| 143 | 133 |
| 144 void PaymentRequest::Pay() { | 134 void PaymentRequest::Pay() { |
| 145 DCHECK(is_ready_to_pay_); | 135 DCHECK(state_->is_ready_to_pay()); |
| 146 | 136 |
| 147 // TODO(mathp): Fill other fields in the PaymentResponsePtr object. | 137 state_->GeneratePaymentResponse(); |
| 148 payment_response_ = mojom::PaymentResponse::New(); | |
| 149 | |
| 150 // TODO(mathp): PaymentRequest should know about the currently selected | |
| 151 // instrument, and not |selected_credit_card_| which is too specific. | |
| 152 // TODO(mathp): The method_name should reflect what the merchant asked, and | |
| 153 // not necessarily basic-card. | |
| 154 selected_payment_instrument_.reset(new AutofillPaymentInstrument( | |
| 155 kBasicCardMethodName, *selected_credit_card_, shipping_profiles_, | |
| 156 delegate_->GetApplicationLocale())); | |
| 157 // Fetch the instrument details, will call back into | |
| 158 // PaymentRequest::OnInstrumentsDetailsReady. | |
| 159 selected_payment_instrument_->InvokePaymentApp(this); | |
| 160 } | |
| 161 | |
| 162 void PaymentRequest::AddObserver(Observer* observer) { | |
| 163 CHECK(observer); | |
| 164 observers_.AddObserver(observer); | |
| 165 } | |
| 166 | |
| 167 void PaymentRequest::RemoveObserver(Observer* observer) { | |
| 168 observers_.RemoveObserver(observer); | |
| 169 } | 138 } |
| 170 | 139 |
| 171 CurrencyFormatter* PaymentRequest::GetOrCreateCurrencyFormatter( | 140 CurrencyFormatter* PaymentRequest::GetOrCreateCurrencyFormatter( |
| 172 const std::string& currency_code, | 141 const std::string& currency_code, |
| 173 const std::string& currency_system, | 142 const std::string& currency_system, |
| 174 const std::string& locale_name) { | 143 const std::string& locale_name) { |
| 175 if (!currency_formatter_) { | 144 if (!currency_formatter_) { |
| 176 currency_formatter_.reset( | 145 currency_formatter_.reset( |
| 177 new CurrencyFormatter(currency_code, currency_system, locale_name)); | 146 new CurrencyFormatter(currency_code, currency_system, locale_name)); |
| 178 } | 147 } |
| 179 return currency_formatter_.get(); | 148 return currency_formatter_.get(); |
| 180 } | 149 } |
| 181 | 150 |
| 182 base::string16 PaymentRequest::GetFormattedCurrencyAmount( | 151 base::string16 PaymentRequest::GetFormattedCurrencyAmount( |
| 183 const std::string& amount) { | 152 const std::string& amount) { |
| 184 CurrencyFormatter* formatter = | 153 CurrencyFormatter* formatter = GetOrCreateCurrencyFormatter( |
| 185 GetOrCreateCurrencyFormatter(details()->total->amount->currency, | 154 spec_->details().total->amount->currency, |
| 186 details()->total->amount->currency_system, | 155 spec_->details().total->amount->currency_system, GetApplicationLocale()); |
| 187 delegate_->GetApplicationLocale()); | |
| 188 return formatter->Format(amount); | 156 return formatter->Format(amount); |
| 189 } | 157 } |
| 190 | 158 |
| 191 std::string PaymentRequest::GetFormattedCurrencyCode() { | 159 std::string PaymentRequest::GetFormattedCurrencyCode() { |
| 192 CurrencyFormatter* formatter = | 160 CurrencyFormatter* formatter = GetOrCreateCurrencyFormatter( |
| 193 GetOrCreateCurrencyFormatter(details()->total->amount->currency, | 161 spec_->details().total->amount->currency, |
| 194 details()->total->amount->currency_system, | 162 spec_->details().total->amount->currency_system, GetApplicationLocale()); |
| 195 delegate_->GetApplicationLocale()); | |
| 196 | 163 |
| 197 return formatter->formatted_currency_code(); | 164 return formatter->formatted_currency_code(); |
| 198 } | 165 } |
| 199 | 166 |
| 200 void PaymentRequest::SetSelectedShippingProfile( | |
| 201 autofill::AutofillProfile* profile) { | |
| 202 selected_shipping_profile_ = profile; | |
| 203 UpdateIsReadyToPayAndNotifyObservers(); | |
| 204 } | |
| 205 | |
| 206 void PaymentRequest::SetSelectedContactProfile( | |
| 207 autofill::AutofillProfile* profile) { | |
| 208 selected_contact_profile_ = profile; | |
| 209 UpdateIsReadyToPayAndNotifyObservers(); | |
| 210 } | |
| 211 | |
| 212 void PaymentRequest::SetSelectedCreditCard(autofill::CreditCard* card) { | |
| 213 selected_credit_card_ = card; | |
| 214 UpdateIsReadyToPayAndNotifyObservers(); | |
| 215 } | |
| 216 | |
| 217 void PaymentRequest::PopulateProfileCache() { | |
| 218 std::vector<autofill::AutofillProfile*> profiles = | |
| 219 personal_data_manager()->GetProfilesToSuggest(); | |
| 220 | |
| 221 // PaymentRequest may outlive the Profiles returned by the Data Manager. | |
| 222 // Thus, we store copies, and return a vector of pointers to these copies | |
| 223 // whenever Profiles are requested. The same is true for credit cards. | |
| 224 for (size_t i = 0; i < profiles.size(); i++) { | |
| 225 profile_cache_.push_back( | |
| 226 base::MakeUnique<autofill::AutofillProfile>(*profiles[i])); | |
| 227 | |
| 228 // TODO(tmartino): Implement deduplication rules specific to shipping and | |
| 229 // contact profiles. | |
| 230 shipping_profiles_.push_back(profile_cache_[i].get()); | |
| 231 contact_profiles_.push_back(profile_cache_[i].get()); | |
| 232 } | |
| 233 | |
| 234 const std::vector<autofill::CreditCard*>& cards = | |
| 235 personal_data_manager()->GetCreditCardsToSuggest(); | |
| 236 for (autofill::CreditCard* card : cards) { | |
| 237 card_cache_.push_back(base::MakeUnique<autofill::CreditCard>(*card)); | |
| 238 credit_cards_.push_back(card_cache_.back().get()); | |
| 239 } | |
| 240 } | |
| 241 | |
| 242 void PaymentRequest::SetDefaultProfileSelections() { | |
| 243 if (!shipping_profiles().empty()) | |
| 244 selected_shipping_profile_ = shipping_profiles()[0]; | |
| 245 | |
| 246 if (!contact_profiles().empty()) | |
| 247 selected_contact_profile_ = contact_profiles()[0]; | |
| 248 | |
| 249 // TODO(anthonyvd): Change this code to prioritize server cards and implement | |
| 250 // a way to modify this function's return value. | |
| 251 const std::vector<autofill::CreditCard*> cards = credit_cards(); | |
| 252 auto first_complete_card = | |
| 253 std::find_if(cards.begin(), cards.end(), | |
| 254 [](autofill::CreditCard* card) { return card->IsValid(); }); | |
| 255 | |
| 256 selected_credit_card_ = | |
| 257 first_complete_card == cards.end() ? nullptr : *first_complete_card; | |
| 258 | |
| 259 UpdateIsReadyToPayAndNotifyObservers(); | |
| 260 } | |
| 261 | |
| 262 void PaymentRequest::PopulateValidatedMethodData( | |
| 263 const std::vector<payments::mojom::PaymentMethodDataPtr>& method_data) { | |
| 264 if (method_data.empty()) { | |
| 265 LOG(ERROR) << "Invalid payment methods or data"; | |
| 266 OnConnectionTerminated(); | |
| 267 return; | |
| 268 } | |
| 269 | |
| 270 std::set<std::string> card_networks{"amex", "diners", "discover", | |
| 271 "jcb", "mastercard", "mir", | |
| 272 "unionpay", "visa"}; | |
| 273 for (const payments::mojom::PaymentMethodDataPtr& method_data_entry : | |
| 274 method_data) { | |
| 275 std::vector<std::string> supported_methods = | |
| 276 method_data_entry->supported_methods; | |
| 277 if (supported_methods.empty()) { | |
| 278 LOG(ERROR) << "Invalid payment methods or data"; | |
| 279 OnConnectionTerminated(); | |
| 280 return; | |
| 281 } | |
| 282 | |
| 283 for (const std::string& method : supported_methods) { | |
| 284 if (method.empty()) | |
| 285 continue; | |
| 286 | |
| 287 // If a card network is specified right in "supportedMethods", add it. | |
| 288 auto card_it = card_networks.find(method); | |
| 289 if (card_it != card_networks.end()) { | |
| 290 supported_card_networks_.push_back(method); | |
| 291 // |method| removed from |card_networks| so that it is not doubly added | |
| 292 // to |supported_card_networks_| if "basic-card" is specified with no | |
| 293 // supported networks. | |
| 294 card_networks.erase(card_it); | |
| 295 } else if (method == kBasicCardMethodName) { | |
| 296 // For the "basic-card" method, check "supportedNetworks". | |
| 297 if (method_data_entry->supported_networks.empty()) { | |
| 298 // Empty |supported_networks| means all networks are supported. | |
| 299 supported_card_networks_.insert(supported_card_networks_.end(), | |
| 300 card_networks.begin(), | |
| 301 card_networks.end()); | |
| 302 // Clear the set so that no further networks are added to | |
| 303 // |supported_card_networks_|. | |
| 304 card_networks.clear(); | |
| 305 } else { | |
| 306 // The merchant has specified a few basic card supported networks. Use | |
| 307 // the mapping to transform to known basic-card types. | |
| 308 using ::payments::mojom::BasicCardNetwork; | |
| 309 std::unordered_map<BasicCardNetwork, std::string> networks = { | |
| 310 {BasicCardNetwork::AMEX, "amex"}, | |
| 311 {BasicCardNetwork::DINERS, "diners"}, | |
| 312 {BasicCardNetwork::DISCOVER, "discover"}, | |
| 313 {BasicCardNetwork::JCB, "jcb"}, | |
| 314 {BasicCardNetwork::MASTERCARD, "mastercard"}, | |
| 315 {BasicCardNetwork::MIR, "mir"}, | |
| 316 {BasicCardNetwork::UNIONPAY, "unionpay"}, | |
| 317 {BasicCardNetwork::VISA, "visa"}}; | |
| 318 for (const BasicCardNetwork& supported_network : | |
| 319 method_data_entry->supported_networks) { | |
| 320 // Make sure that the network was not already added to | |
| 321 // |supported_card_networks_|. | |
| 322 auto card_it = card_networks.find(networks[supported_network]); | |
| 323 if (card_it != card_networks.end()) { | |
| 324 supported_card_networks_.push_back(networks[supported_network]); | |
| 325 card_networks.erase(card_it); | |
| 326 } | |
| 327 } | |
| 328 } | |
| 329 } | |
| 330 } | |
| 331 } | |
| 332 } | |
| 333 | |
| 334 void PaymentRequest::UpdateIsReadyToPayAndNotifyObservers() { | |
| 335 is_ready_to_pay_ = | |
| 336 ArePaymentDetailsSatisfied() && ArePaymentOptionsSatisfied(); | |
| 337 NotifyOnSelectedInformationChanged(); | |
| 338 } | |
| 339 | |
| 340 void PaymentRequest::NotifyOnSelectedInformationChanged() { | |
| 341 for (auto& observer : observers_) | |
| 342 observer.OnSelectedInformationChanged(); | |
| 343 } | |
| 344 | |
| 345 bool PaymentRequest::ArePaymentDetailsSatisfied() { | |
| 346 // TODO(mathp): A masked card may not satisfy IsValid(). | |
| 347 if (selected_credit_card_ == nullptr || !selected_credit_card_->IsValid()) | |
| 348 return false; | |
| 349 | |
| 350 const std::string basic_card_payment_type = | |
| 351 autofill::data_util::GetPaymentRequestData(selected_credit_card_->type()) | |
| 352 .basic_card_payment_type; | |
| 353 return !supported_card_networks_.empty() && | |
| 354 std::find(supported_card_networks_.begin(), | |
| 355 supported_card_networks_.end(), | |
| 356 basic_card_payment_type) != supported_card_networks_.end(); | |
| 357 } | |
| 358 | |
| 359 bool PaymentRequest::ArePaymentOptionsSatisfied() { | |
| 360 // TODO(mathp): Have a measure of shipping address completeness. | |
| 361 if (request_shipping() && selected_shipping_profile_ == nullptr) | |
| 362 return false; | |
| 363 | |
| 364 // TODO(mathp): Make an encompassing class to validate contact info. | |
| 365 const std::string& app_locale = delegate_->GetApplicationLocale(); | |
| 366 if (request_payer_name() && | |
| 367 (selected_contact_profile_ == nullptr || | |
| 368 selected_contact_profile_ | |
| 369 ->GetInfo(autofill::AutofillType(autofill::NAME_FULL), app_locale) | |
| 370 .empty())) { | |
| 371 return false; | |
| 372 } | |
| 373 if (request_payer_email() && | |
| 374 (selected_contact_profile_ == nullptr || | |
| 375 selected_contact_profile_ | |
| 376 ->GetInfo(autofill::AutofillType(autofill::EMAIL_ADDRESS), | |
| 377 app_locale) | |
| 378 .empty())) { | |
| 379 return false; | |
| 380 } | |
| 381 if (request_payer_phone() && | |
| 382 (selected_contact_profile_ == nullptr || | |
| 383 selected_contact_profile_ | |
| 384 ->GetInfo(autofill::AutofillType(autofill::PHONE_HOME_WHOLE_NUMBER), | |
| 385 app_locale) | |
| 386 .empty())) { | |
| 387 return false; | |
| 388 } | |
| 389 | |
| 390 return true; | |
| 391 } | |
| 392 | |
| 393 void PaymentRequest::UpdateSelectedShippingOptionFromDetails() { | |
| 394 selected_shipping_option_ = nullptr; | |
| 395 | |
| 396 // As per the spec, the selected shipping option should initially be the last | |
| 397 // one in the array that has its selected field set to true. | |
| 398 auto selected_shipping_option_it = std::find_if( | |
| 399 details()->shipping_options.rbegin(), details()->shipping_options.rend(), | |
| 400 [](const payments::mojom::PaymentShippingOptionPtr& element) { | |
| 401 return element->selected; | |
| 402 }); | |
| 403 if (selected_shipping_option_it != details()->shipping_options.rend()) { | |
| 404 selected_shipping_option_ = selected_shipping_option_it->get(); | |
| 405 } | |
| 406 } | |
| 407 | |
| 408 } // namespace payments | 167 } // namespace payments |
| OLD | NEW |