Chromium Code Reviews| Index: ios/chrome/browser/ui/payments/payment_request_coordinator.mm |
| diff --git a/ios/chrome/browser/ui/payments/payment_request_coordinator.mm b/ios/chrome/browser/ui/payments/payment_request_coordinator.mm |
| index f0e9e6d6f8b7d94acc91ed0a1c017eb57f0f314a..295ced5411a2d5a2146dc12a9eab2e9ac793e80b 100644 |
| --- a/ios/chrome/browser/ui/payments/payment_request_coordinator.mm |
| +++ b/ios/chrome/browser/ui/payments/payment_request_coordinator.mm |
| @@ -19,16 +19,20 @@ |
| #include "components/autofill/core/browser/payments/full_card_request.h" |
| #include "components/autofill/core/browser/personal_data_manager.h" |
| #include "components/autofill/core/browser/ui/card_unmask_prompt_controller_impl.h" |
| +#include "components/payments/core/address_normalizer_impl.h" |
| #include "components/payments/core/payment_address.h" |
| #include "components/payments/core/payment_request_data_util.h" |
| #include "components/signin/core/browser/signin_manager.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "ios/chrome/browser/application_context.h" |
| +#include "ios/chrome/browser/autofill/validation_rules_storage_factory.h" |
| #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| #include "ios/chrome/browser/payments/payment_request.h" |
| #include "ios/chrome/browser/payments/payment_request_util.h" |
| #include "ios/chrome/browser/signin/signin_manager_factory.h" |
| #include "ios/chrome/browser/ui/autofill/card_unmask_prompt_view_bridge.h" |
| +#include "third_party/libaddressinput/chromium/chrome_metadata_source.h" |
| +#include "third_party/libaddressinput/chromium/chrome_storage_impl.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| @@ -38,15 +42,51 @@ |
| namespace { |
| using ::payments::data_util::GetBasicCardResponseFromAutofillCreditCard; |
| using ::payments::data_util::GetPaymentAddressFromAutofillProfile; |
| -} // namespace |
| + |
| +constexpr int kAddressNormalizationTimeoutSeconds = 5; |
| + |
| +// Receives notifications from the AddressNormalizer, and forwards the details |
| +// to a callback. This is needed as there is currently no way to know, when |
| +// the |payments::AddressNormalizer::Delegate| methods are called, which address |
| +// has been normalized (shipping or contact). The callback block can keep track |
| +// of which address this delegate was created for. |
| +class AddressNormalizerDelegate : public payments::AddressNormalizer::Delegate { |
|
Moe
2017/04/27 12:19:53
Please move this to its own files in i/c/b/payment
macourteau
2017/04/27 18:07:52
This is just a helper class, I think it makes sens
Moe
2017/04/28 09:04:44
Ah I see. Forgot about the need to keep track of t
macourteau
2017/05/01 15:56:22
Acknowledged.
|
| + public: |
| + // Block to be called with the profile information, once normalized. |
| + typedef void (^Callback)(const autofill::AutofillProfile&); |
| + |
| + AddressNormalizerDelegate() {} |
| + ~AddressNormalizerDelegate() override {} |
| + |
| + void SetCallback(Callback callback) { callback_ = callback; } |
| + |
| + // payments::AddressNormalizer::Delegate: |
| + void OnAddressNormalized( |
| + const autofill::AutofillProfile& normalizedProfile) override { |
| + DCHECK(callback_ != nil); |
|
Moe
2017/04/27 12:19:53
nit? DCHECK(callback_)
macourteau
2017/04/27 18:07:52
Done.
|
| + callback_(normalizedProfile); |
| + } |
| + |
| + // payments::AddressNormalizer::Delegate: |
| + void OnCouldNotNormalize(const autofill::AutofillProfile& profile) override { |
| + // Since the phone number is formatted in either case, this profile should |
| + // be used. |
| + DCHECK(callback_ != nil); |
| + callback_(profile); |
| + } |
| + |
| + private: |
| + Callback callback_ = nil; |
|
Moe
2017/04/27 12:19:53
s/nil/nullptr for C++ objects
macourteau
2017/04/27 18:07:52
Done.
|
| + |
| + DISALLOW_COPY_AND_ASSIGN(AddressNormalizerDelegate); |
| +}; |
| // The unmask prompt UI for Payment Request. |
| class PRCardUnmaskPromptViewBridge |
|
Moe
2017/04/27 12:19:53
I'll move PRCardUnmaskPromptViewBridge and FullCar
macourteau
2017/04/27 18:07:52
Acknowledged.
|
| : public autofill::CardUnmaskPromptViewBridge { |
| public: |
| - explicit PRCardUnmaskPromptViewBridge( |
| - autofill::CardUnmaskPromptController* controller, |
| - UIViewController* base_view_controller) |
| + PRCardUnmaskPromptViewBridge(autofill::CardUnmaskPromptController* controller, |
| + UIViewController* base_view_controller) |
| : autofill::CardUnmaskPromptViewBridge(controller), |
| base_view_controller_(base_view_controller) {} |
| @@ -70,9 +110,9 @@ class FullCardRequester |
| public autofill::payments::FullCardRequest::UIDelegate, |
| public base::SupportsWeakPtr<FullCardRequester> { |
| public: |
| - explicit FullCardRequester(PaymentRequestCoordinator* owner, |
| - UIViewController* base_view_controller, |
| - ios::ChromeBrowserState* browser_state) |
| + FullCardRequester(PaymentRequestCoordinator* owner, |
| + UIViewController* base_view_controller, |
| + ios::ChromeBrowserState* browser_state) |
| : owner_(owner), |
| base_view_controller_(base_view_controller), |
| unmask_controller_(browser_state->GetPrefs(), |
| @@ -127,6 +167,8 @@ class FullCardRequester |
| DISALLOW_COPY_AND_ASSIGN(FullCardRequester); |
| }; |
| +} // namespace |
| + |
| @implementation PaymentRequestCoordinator { |
| UINavigationController* _navigationController; |
| PaymentRequestViewController* _viewController; |
| @@ -142,6 +184,23 @@ class FullCardRequester |
| // The selected shipping address, pending approval from the page. |
| autofill::AutofillProfile* _pendingShippingAddress; |
| + |
| + // The address normalizer, to normalize contact & shipping addresses. Also, |
| + // delegates to handle callbacks for normalization of each address. |
| + std::unique_ptr<payments::AddressNormalizer> _addressNormalizer; |
| + AddressNormalizerDelegate _contactAddressNormalizerDelegate; |
| + AddressNormalizerDelegate _shippingAddressNormalizerDelegate; |
| + |
| + // Member variables to keep track of which operations are still pending. |
| + BOOL _contactAddressNormalizationPending; |
| + BOOL _shippingAddressNormalizationPending; |
| + BOOL _fullCardRequestPending; |
| + |
| + // Storage for the full card. |
| + struct { |
| + autofill::CreditCard creditCard; |
| + base::string16 cvc; |
| + } _fullCard; |
|
Moe
2017/04/27 12:19:52
please typedef this.
macourteau
2017/04/27 18:07:52
Done.
|
| } |
| @synthesize paymentRequest = _paymentRequest; |
| @@ -152,6 +211,33 @@ class FullCardRequester |
| @synthesize pageHost = _pageHost; |
| @synthesize delegate = _delegate; |
| +- (void)initAddressNormalizer { |
| + autofill::PersonalDataManager* personalDataManager = |
| + _paymentRequest->GetPersonalDataManager(); |
| + |
| + std::unique_ptr<i18n::addressinput::Source> addressNormalizerSource = |
| + base::WrapUnique(new autofill::ChromeMetadataSource( |
|
Moe
2017/04/27 12:19:53
MakeUnique is preferred.
macourteau
2017/04/27 18:07:52
Done.
|
| + I18N_ADDRESS_VALIDATION_DATA_URL, |
| + personalDataManager->GetURLRequestContextGetter())); |
| + |
| + std::unique_ptr<i18n::addressinput::Storage> addressNormalizerStorage = |
| + autofill::ValidationRulesStorageFactory::CreateStorage(); |
| + |
| + _addressNormalizer.reset(new payments::AddressNormalizerImpl( |
| + std::move(addressNormalizerSource), std::move(addressNormalizerStorage))); |
| + |
| + // Kickoff the process of loading the rules (which is asynchronous) for each |
| + // profile's country, to get faster address normalization later. |
| + for (const autofill::AutofillProfile* profile : |
| + personalDataManager->GetProfilesToSuggest()) { |
| + std::string countryCode = |
| + base::UTF16ToUTF8(profile->GetRawInfo(autofill::ADDRESS_HOME_COUNTRY)); |
| + if (autofill::data_util::IsValidCountryCode(countryCode)) { |
| + _addressNormalizer->LoadRulesForRegion(countryCode); |
| + } |
|
sebsg
2017/04/26 20:52:51
I don't know if you have access to that here but i
|
| + } |
| +} |
| + |
| - (void)start { |
| _viewController = [[PaymentRequestViewController alloc] |
| initWithPaymentRequest:_paymentRequest]; |
| @@ -176,6 +262,8 @@ class FullCardRequester |
| [[self baseViewController] presentViewController:_navigationController |
| animated:YES |
| completion:nil]; |
| + |
| + [self initAddressNormalizer]; |
| } |
| - (void)stop { |
| @@ -196,87 +284,148 @@ class FullCardRequester |
| _navigationController = nil; |
| } |
| +- (void)normalizeProfile:(autofill::AutofillProfile*)profileToNormalize |
| + delegate:(AddressNormalizerDelegate*)delegate |
| + flag:(BOOL*)normalizationRequestPendingFlag { |
| + DCHECK(normalizationRequestPendingFlag); |
| + |
| + __weak PaymentRequestCoordinator* weakSelf = self; |
| + |
| + delegate->SetCallback(^(const autofill::AutofillProfile& normalizedProfile) { |
| + // Check that PaymentRequestCoordinator still exists as |profileToNormalize| |
| + // and |normalizationRequestPendingFlag| are member variables of that |
| + // object. |
| + PaymentRequestCoordinator* strongSelf = weakSelf; |
| + if (!strongSelf) |
| + return; |
| + |
| + *profileToNormalize = normalizedProfile; |
| + *normalizationRequestPendingFlag = NO; |
| + [strongSelf maybeDone]; |
| + }); |
| + |
| + const std::string country_code = base::UTF16ToUTF8( |
| + profileToNormalize->GetRawInfo(autofill::ADDRESS_HOME_COUNTRY)); |
| + if (autofill::data_util::IsValidCountryCode(country_code)) { |
|
sebsg
2017/04/26 20:52:51
If the country code is not valid, you could fallba
macourteau
2017/04/27 18:07:52
Done (with iOS APIs).
|
| + _addressNormalizer->StartAddressNormalization( |
| + *profileToNormalize, country_code, kAddressNormalizationTimeoutSeconds, |
| + delegate); |
| + } else { |
| + *normalizationRequestPendingFlag = NO; |
| + } |
| +} |
| + |
| - (void)sendPaymentResponse { |
| + // Set these before starting any potentially asynchronous calls, to ensure we |
| + // wait for completion of all operations before proceeding. |
| + _contactAddressNormalizationPending = YES; |
| + _shippingAddressNormalizationPending = _paymentRequest->request_shipping(); |
| + _fullCardRequestPending = YES; |
| + |
| DCHECK(_paymentRequest->selected_credit_card()); |
| autofill::CreditCard* card = _paymentRequest->selected_credit_card(); |
| _fullCardRequester = base::MakeUnique<FullCardRequester>( |
| self, _navigationController, _browserState); |
| _fullCardRequester->GetFullCard(card, _autofillManager); |
| -} |
| - |
| -- (void)fullCardRequestDidSucceedWithCard:(const autofill::CreditCard&)card |
| - CVC:(const base::string16&)cvc { |
| - web::PaymentResponse paymentResponse; |
| - |
| - // If the merchant specified the card network as part of the "basic-card" |
| - // payment method, return "basic-card" as the method_name. Otherwise, return |
| - // the name of the network directly. |
| - std::string basic_card_type = |
| - autofill::data_util::GetPaymentRequestData(card.type()) |
| - .basic_card_payment_type; |
| - paymentResponse.method_name = |
| - _paymentRequest->basic_card_specified_networks().find(basic_card_type) != |
| - _paymentRequest->basic_card_specified_networks().end() |
| - ? base::ASCIIToUTF16("basic-card") |
| - : base::ASCIIToUTF16(basic_card_type); |
| - |
| - paymentResponse.details = GetBasicCardResponseFromAutofillCreditCard( |
| - card, cvc, _paymentRequest->billing_profiles(), |
| - GetApplicationContext()->GetApplicationLocale()); |
| if (_paymentRequest->request_shipping()) { |
| - autofill::AutofillProfile* shippingAddress = |
| - _paymentRequest->selected_shipping_profile(); |
| - // TODO(crbug.com/602666): User should get here only if they have selected |
| - // a shipping address. |
| - DCHECK(shippingAddress); |
| - paymentResponse.shipping_address = GetPaymentAddressFromAutofillProfile( |
| - *shippingAddress, GetApplicationContext()->GetApplicationLocale()); |
| - |
| - web::PaymentShippingOption* shippingOption = |
| - _paymentRequest->selected_shipping_option(); |
| - DCHECK(shippingOption); |
| - paymentResponse.shipping_option = shippingOption->id; |
| + [self normalizeProfile:_paymentRequest->selected_shipping_profile() |
| + delegate:&_shippingAddressNormalizerDelegate |
| + flag:&_shippingAddressNormalizationPending]; |
| } |
| - if (_paymentRequest->request_payer_name()) { |
| - autofill::AutofillProfile* contactInfo = |
| - _paymentRequest->selected_contact_profile(); |
| - // TODO(crbug.com/602666): User should get here only if they have selected |
| - // a contact info. |
| - DCHECK(contactInfo); |
| - paymentResponse.payer_name = |
| - contactInfo->GetInfo(autofill::AutofillType(autofill::NAME_FULL), |
| - GetApplicationContext()->GetApplicationLocale()); |
| - } |
| - |
| - if (_paymentRequest->request_payer_email()) { |
| - autofill::AutofillProfile* contactInfo = |
| - _paymentRequest->selected_contact_profile(); |
| - // TODO(crbug.com/602666): User should get here only if they have selected |
| - // a contact info. |
| - DCHECK(contactInfo); |
| - paymentResponse.payer_email = |
| - contactInfo->GetRawInfo(autofill::EMAIL_ADDRESS); |
| - } |
| + [self normalizeProfile:_paymentRequest->selected_contact_profile() |
| + delegate:&_contactAddressNormalizerDelegate |
| + flag:&_contactAddressNormalizationPending]; |
| +} |
| - if (_paymentRequest->request_payer_phone()) { |
| - autofill::AutofillProfile* contactInfo = |
| - _paymentRequest->selected_contact_profile(); |
| - // TODO(crbug.com/602666): User should get here only if they have selected |
| - // a contact info. |
| - DCHECK(contactInfo); |
| - paymentResponse.payer_phone = |
| - contactInfo->GetRawInfo(autofill::PHONE_HOME_WHOLE_NUMBER); |
| - } |
| +- (void)fullCardRequestDidSucceedWithCard:(const autofill::CreditCard&)card |
| + CVC:(const base::string16&)cvc { |
| + _fullCard.creditCard = card; |
| + _fullCard.cvc = cvc; |
| + _fullCardRequestPending = NO; |
| _viewController.view.userInteractionEnabled = NO; |
| [_viewController setPending:YES]; |
| [_viewController loadModel]; |
| [[_viewController collectionView] reloadData]; |
| - [_delegate paymentRequestCoordinator:self |
| - didConfirmWithPaymentResponse:paymentResponse]; |
| + [self maybeDone]; |
| +} |
| + |
| +- (void)maybeDone { |
|
Moe
2017/04/27 12:19:53
nit: please change to something more descriptive l
macourteau
2017/04/27 18:07:52
Done. That *is* quite verbose 😳
|
| + if (_contactAddressNormalizationPending == NO && |
| + _shippingAddressNormalizationPending == NO && |
| + _fullCardRequestPending == NO) { |
| + web::PaymentResponse paymentResponse; |
| + |
| + // If the merchant specified the card network as part of the "basic-card" |
| + // payment method, return "basic-card" as the method_name. Otherwise, return |
| + // the name of the network directly. |
| + std::string basic_card_type = |
| + autofill::data_util::GetPaymentRequestData(_fullCard.creditCard.type()) |
| + .basic_card_payment_type; |
| + paymentResponse.method_name = |
| + _paymentRequest->basic_card_specified_networks().find( |
| + basic_card_type) != |
| + _paymentRequest->basic_card_specified_networks().end() |
| + ? base::ASCIIToUTF16("basic-card") |
| + : base::ASCIIToUTF16(basic_card_type); |
| + |
| + paymentResponse.details = GetBasicCardResponseFromAutofillCreditCard( |
| + _fullCard.creditCard, _fullCard.cvc, |
| + _paymentRequest->billing_profiles(), |
|
sebsg
2017/04/26 20:52:51
Also, you will eventually want to normalize that b
sebsg
2017/04/26 20:52:51
Just a heads-up I just landed a CL which adds a bi
macourteau
2017/04/27 18:07:52
Acknowledged, taken into account.
macourteau
2017/04/27 18:07:52
Done.
|
| + GetApplicationContext()->GetApplicationLocale()); |
| + |
| + if (_paymentRequest->request_shipping()) { |
| + autofill::AutofillProfile* shippingAddress = |
| + _paymentRequest->selected_shipping_profile(); |
| + // TODO(crbug.com/602666): User should get here only if they have selected |
| + // a shipping address. |
| + DCHECK(shippingAddress); |
| + paymentResponse.shipping_address = GetPaymentAddressFromAutofillProfile( |
| + *shippingAddress, GetApplicationContext()->GetApplicationLocale()); |
| + |
| + web::PaymentShippingOption* shippingOption = |
| + _paymentRequest->selected_shipping_option(); |
| + DCHECK(shippingOption); |
| + paymentResponse.shipping_option = shippingOption->id; |
| + } |
| + |
| + if (_paymentRequest->request_payer_name()) { |
| + autofill::AutofillProfile* contactInfo = |
| + _paymentRequest->selected_contact_profile(); |
| + // TODO(crbug.com/602666): User should get here only if they have selected |
| + // a contact info. |
| + DCHECK(contactInfo); |
| + paymentResponse.payer_name = |
| + contactInfo->GetInfo(autofill::AutofillType(autofill::NAME_FULL), |
| + GetApplicationContext()->GetApplicationLocale()); |
| + } |
| + |
| + if (_paymentRequest->request_payer_email()) { |
| + autofill::AutofillProfile* contactInfo = |
| + _paymentRequest->selected_contact_profile(); |
| + // TODO(crbug.com/602666): User should get here only if they have selected |
| + // a contact info. |
| + DCHECK(contactInfo); |
| + paymentResponse.payer_email = |
| + contactInfo->GetRawInfo(autofill::EMAIL_ADDRESS); |
| + } |
| + |
| + if (_paymentRequest->request_payer_phone()) { |
| + autofill::AutofillProfile* contactInfo = |
| + _paymentRequest->selected_contact_profile(); |
| + // TODO(crbug.com/602666): User should get here only if they have selected |
| + // a contact info. |
| + DCHECK(contactInfo); |
| + paymentResponse.payer_phone = |
| + contactInfo->GetRawInfo(autofill::PHONE_HOME_WHOLE_NUMBER); |
| + } |
| + |
| + [_delegate paymentRequestCoordinator:self |
| + didConfirmWithPaymentResponse:paymentResponse]; |
| + } |
| } |
| - (void)updatePaymentDetails:(web::PaymentDetails)paymentDetails { |