| // Copyright 2017 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. |
| |
| #import "ios/chrome/browser/payments/ios_payment_instrument_launcher.h" |
| |
| #include <map> |
| #include <memory> |
| |
| #include "base/base64.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "components/payments/core/payment_details.h" |
| #include "components/payments/core/payment_instrument.h" |
| #import "ios/chrome/browser/payments/payment_request_constants.h" |
| #include "ios/web/public/navigation_item.h" |
| #include "ios/web/public/navigation_manager.h" |
| #include "ios/web/public/security/ssl_status.h" |
| #import "ios/web/public/web_state/web_state.h" |
| #include "net/base/mac/url_conversions.h" |
| #include "net/base/url_util.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/cert/x509_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| |
| // Parameters sent to a payment app. |
| static const char kMethodNames[] = "methodNames"; |
| static const char kMethodData[] = "methodData"; |
| static const char kMerchantName[] = "merchantName"; |
| static const char kTopLevelOrigin[] = "topLevelOrigin"; |
| static const char kTopLevelCertificateChain[] = "topLevelCertificateChain"; |
| static const char kCertificate[] = "cert"; |
| static const char kTotal[] = "total"; |
| static const char kModifiers[] = "modifiers"; |
| |
| // Parameters received from a payment app. |
| static const char kSuccess[] = "success"; |
| static const char kMethodName[] = "methodName"; |
| static const char kDetails[] = "details"; |
| |
| } // namespace |
| |
| namespace payments { |
| |
| IOSPaymentInstrumentLauncher::IOSPaymentInstrumentLauncher() |
| : delegate_(nullptr), payment_request_id_("") {} |
| |
| IOSPaymentInstrumentLauncher::~IOSPaymentInstrumentLauncher() {} |
| |
| bool IOSPaymentInstrumentLauncher::LaunchIOSPaymentInstrument( |
| payments::PaymentRequest* payment_request, |
| web::WebState* active_web_state, |
| GURL& universal_link, |
| payments::PaymentInstrument::Delegate* delegate) { |
| DCHECK(delegate); |
| |
| // Only one request can be handled at a time. |
| if (delegate_ || !payment_request_id_.empty()) { |
| return false; |
| } |
| |
| delegate_ = delegate; |
| |
| // TODO(crbug.com/748556): Filter the following list to only show method names |
| // that we know the payment app supports. For now, sending all the requested |
| // method names i.e., 'basic-card' and 'https://alice-pay.com" to the payment |
| // app works as the payment app provider can then decide what to do with that |
| // information, but this is not ideal nor is this consistent with Android |
| // implementation. |
| base::Value method_names(base::Value::Type::LIST); |
| for (auto const& pair : payment_request->stringified_method_data()) |
| method_names.GetList().emplace_back(pair.first); |
| |
| base::Value params_to_payment_app(base::Value::Type::DICTIONARY); |
| params_to_payment_app.SetKey(kMethodNames, std::move(method_names)); |
| |
| params_to_payment_app.SetKey( |
| kMethodData, |
| SerializeMethodData(payment_request->stringified_method_data())); |
| |
| params_to_payment_app.SetKey(kMerchantName, |
| base::Value(active_web_state->GetTitle())); |
| |
| params_to_payment_app.SetKey( |
| kTopLevelOrigin, |
| base::Value(active_web_state->GetLastCommittedURL().host())); |
| |
| params_to_payment_app.SetKey( |
| kTopLevelCertificateChain, |
| SerializeCertificateChain( |
| active_web_state->GetNavigationManager()->GetVisibleItem())); |
| |
| DCHECK(payment_request->web_payment_request().details.total); |
| params_to_payment_app.SetKey( |
| kTotal, |
| base::Value::FromUniquePtrValue(PaymentCurrencyAmountToDictionaryValue( |
| *(payment_request->web_payment_request().details.total->amount)))); |
| |
| params_to_payment_app.SetKey( |
| kModifiers, |
| SerializeModifiers(payment_request->web_payment_request().details)); |
| |
| // JSON stringify the object so that it can be encoded in base-64. |
| std::string stringified_parameters; |
| base::JSONWriter::Write(params_to_payment_app, &stringified_parameters); |
| |
| std::string base_64_params; |
| base::Base64Encode(stringified_parameters, &base_64_params); |
| |
| payment_request_id_ = |
| payment_request->web_payment_request().payment_request_id; |
| |
| universal_link = net::AppendQueryParameter( |
| universal_link, payments::kPaymentRequestIDExternal, payment_request_id_); |
| universal_link = net::AppendQueryParameter( |
| universal_link, payments::kPaymentRequestDataExternal, base_64_params); |
| NSURL* url = net::NSURLWithGURL(universal_link); |
| |
| [[UIApplication sharedApplication] openURL:url |
| options:@{ |
| UIApplicationOpenURLOptionUniversalLinksOnly : @YES |
| } |
| completionHandler:^(BOOL success) { |
| if (!success) { |
| CompleteLaunchRequest(std::string(), std::string()); |
| } |
| }]; |
| |
| return true; |
| } |
| |
| void IOSPaymentInstrumentLauncher::ReceiveResponseFromIOSPaymentInstrument( |
| const std::string& base_64_response) { |
| DCHECK(delegate_); |
| |
| std::string stringified_parameters; |
| base::Base64Decode(base_64_response, &stringified_parameters); |
| |
| base::Optional<base::Value> value = |
| base::JSONReader::Read(stringified_parameters); |
| if (!value) { |
| CompleteLaunchRequest(std::string(), std::string()); |
| return; |
| } |
| |
| if (!value->is_dict()) { |
| CompleteLaunchRequest(std::string(), std::string()); |
| return; |
| } |
| |
| base::Optional<int> success = value->FindIntKey(kSuccess); |
| if (!success || *success == 0) { |
| CompleteLaunchRequest(std::string(), std::string()); |
| return; |
| } |
| |
| const std::string* method_name = value->FindStringKey(kMethodName); |
| if (!method_name || method_name->empty()) { |
| CompleteLaunchRequest(std::string(), std::string()); |
| return; |
| } |
| |
| const std::string* stringified_details = value->FindStringKey(kDetails); |
| if (!stringified_details || stringified_details->empty()) { |
| CompleteLaunchRequest(std::string(), std::string()); |
| return; |
| } |
| |
| CompleteLaunchRequest(*method_name, *stringified_details); |
| } |
| |
| base::Value IOSPaymentInstrumentLauncher::SerializeMethodData( |
| const std::map<std::string, std::set<std::string>>& |
| stringified_method_data) { |
| base::Value method_data(base::Value::Type::DICTIONARY); |
| for (auto const& map_it : stringified_method_data) { |
| base::Value data_list(base::Value::Type::LIST); |
| for (auto const& data_it : map_it.second) { |
| // We insert the stringified data, not the JSON object and only if the |
| // corresponding JSON object is valid. |
| if (base::JSONReader::Read(data_it)) |
| data_list.GetList().emplace_back(data_it); |
| } |
| method_data.SetKey(map_it.first, std::move(data_list)); |
| } |
| return method_data; |
| } |
| |
| base::Value IOSPaymentInstrumentLauncher::SerializeCertificateChain( |
| web::NavigationItem* item) { |
| base::Value cert_chain_list(base::Value::Type::LIST); |
| if (!item) |
| return cert_chain_list; |
| |
| scoped_refptr<net::X509Certificate> cert = item->GetSSL().certificate; |
| std::vector<base::StringPiece> cert_chain; |
| |
| cert_chain.reserve(1 + cert->intermediate_buffers().size()); |
| cert_chain.push_back( |
| net::x509_util::CryptoBufferAsStringPiece(cert->cert_buffer())); |
| for (const auto& handle : cert->intermediate_buffers()) { |
| cert_chain.push_back( |
| net::x509_util::CryptoBufferAsStringPiece(handle.get())); |
| } |
| |
| for (const auto& cert_string : cert_chain) { |
| base::Value byte_array(base::Value::Type::LIST); |
| byte_array.GetList().reserve(cert_string.size()); |
| for (const char byte : cert_string) |
| byte_array.GetList().emplace_back(byte); |
| |
| base::Value cert_chain_dict(base::Value::Type::DICTIONARY); |
| cert_chain_dict.SetKey(kCertificate, std::move(byte_array)); |
| cert_chain_list.GetList().push_back(std::move(cert_chain_dict)); |
| } |
| |
| return cert_chain_list; |
| } |
| |
| base::Value IOSPaymentInstrumentLauncher::SerializeModifiers( |
| PaymentDetails details) { |
| base::Value modifiers(base::Value::Type::LIST); |
| size_t numModifiers = details.modifiers.size(); |
| for (size_t i = 0; i < numModifiers; ++i) { |
| modifiers.GetList().push_back(base::Value::FromUniquePtrValue( |
| details.modifiers[i].ToDictionaryValue())); |
| } |
| return modifiers; |
| } |
| |
| void IOSPaymentInstrumentLauncher::CompleteLaunchRequest( |
| const std::string& method_name, |
| const std::string& details) { |
| if (!method_name.empty() && !details.empty()) |
| delegate_->OnInstrumentDetailsReady(method_name, details); |
| else |
| delegate_->OnInstrumentDetailsError(); |
| delegate_ = nullptr; |
| payment_request_id_ = ""; |
| } |
| |
| } // namespace payments |