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 68fb049eeeebcfc84a9b3a85841c38baa8bcec04..40b76f67f1547f172e243f0fb8fc34606dc5dc1d 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 mechanism is needed |
+// in order to keep track of which address this delegate was created for. |
+class AddressNormalizerDelegate : public payments::AddressNormalizer::Delegate { |
+ 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; } |
marq (ping after 24h)
2017/05/02 08:24:45
Since there is only one callback that is used, why
|
+ |
+ // payments::AddressNormalizer::Delegate: |
+ void OnAddressNormalized( |
+ const autofill::AutofillProfile& normalizedProfile) override { |
+ DCHECK(callback_); |
+ 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_); |
+ callback_(profile); |
+ } |
+ |
+ private: |
+ Callback callback_ = nullptr; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(AddressNormalizerDelegate); |
+}; |
// The unmask prompt UI for Payment Request. |
class PRCardUnmaskPromptViewBridge |
: 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,26 @@ class FullCardRequester |
DISALLOW_COPY_AND_ASSIGN(FullCardRequester); |
}; |
+struct FullCard { |
+ autofill::CreditCard creditCard; |
+ base::string16 cvc; |
+ autofill::AutofillProfile billingAddress; |
+}; |
+ |
+} // namespace |
+ |
+@interface PaymentRequestCoordinator () |
+ |
+// Starts normalization of |profileToNormalize| (usually asynchronous). The |
+// |delegate| will be notified on success/failure, and |
+// |normalizationRequestPendingFlag| will be updated to NO when normalization |
+// compleptes. |
+- (void)normalizeProfile:(autofill::AutofillProfile*)profileToNormalize |
+ delegate:(AddressNormalizerDelegate*)delegate |
+ flag:(BOOL*)normalizationRequestPendingFlag; |
+ |
+@end |
+ |
@implementation PaymentRequestCoordinator { |
UINavigationController* _navigationController; |
PaymentRequestViewController* _viewController; |
@@ -142,6 +202,22 @@ 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; |
+ AddressNormalizerDelegate _billingAddressNormalizerDelegate; |
+ |
+ // Member variables to keep track of which operations are still pending. |
+ BOOL _contactAddressNormalizationPending; |
+ BOOL _shippingAddressNormalizationPending; |
+ BOOL _fullCardRequestPending; |
+ BOOL _billingAddressNormalizationPending; |
+ |
+ // Storage for the full card. |
+ FullCard _fullCard; |
} |
@synthesize paymentRequest = _paymentRequest; |
@@ -152,6 +228,33 @@ class FullCardRequester |
@synthesize pageHost = _pageHost; |
@synthesize delegate = _delegate; |
+- (void)startAddressNormalizer { |
marq (ping after 24h)
2017/05/02 08:24:44
Here and elsewhere: Every method requires a commen
|
+ autofill::PersonalDataManager* personalDataManager = |
+ _paymentRequest->GetPersonalDataManager(); |
+ |
+ std::unique_ptr<i18n::addressinput::Source> addressNormalizerSource = |
+ base::MakeUnique<autofill::ChromeMetadataSource>( |
+ 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); |
+ } |
+ } |
+} |
+ |
- (void)start { |
_viewController = [[PaymentRequestViewController alloc] |
initWithPaymentRequest:_paymentRequest]; |
@@ -176,6 +279,8 @@ class FullCardRequester |
[[self baseViewController] presentViewController:_navigationController |
animated:YES |
completion:nil]; |
+ |
+ [self startAddressNormalizer]; |
} |
- (void)stop { |
@@ -196,102 +301,166 @@ class FullCardRequester |
_navigationController = nil; |
} |
-- (void)sendPaymentResponse { |
+- (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| |
marq (ping after 24h)
2017/05/02 08:24:44
But the delegate is a property of this coordinator
|
+ // and |normalizationRequestPendingFlag| are member variables of that |
+ // object. |
+ if (!weakSelf) |
+ return; |
+ |
+ *profileToNormalize = normalizedProfile; |
marq (ping after 24h)
2017/05/02 08:24:45
Instead of updating instance variables passed in b
|
+ *normalizationRequestPendingFlag = NO; |
+ [weakSelf sendPaymentResponseIfAllAsyncCallsCompleted]; |
+ }); |
+ |
+ std::string country_code = base::UTF16ToUTF8( |
+ profileToNormalize->GetRawInfo(autofill::ADDRESS_HOME_COUNTRY)); |
+ if (!autofill::data_util::IsValidCountryCode(country_code)) { |
+ country_code = base::SysNSStringToUTF8( |
+ [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode]); |
+ DCHECK(autofill::data_util::IsValidCountryCode(country_code)); |
+ } |
+ _addressNormalizer->StartAddressNormalization( |
+ *profileToNormalize, country_code, kAddressNormalizationTimeoutSeconds, |
+ delegate); |
+} |
+ |
+- (void)requestFullCardAndNormalizeProfiles { |
+ // 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); |
+ |
+ if (_paymentRequest->request_shipping()) { |
+ [self normalizeProfile:_paymentRequest->selected_shipping_profile() |
+ delegate:&_shippingAddressNormalizerDelegate |
+ flag:&_shippingAddressNormalizationPending]; |
+ } |
+ |
+ [self normalizeProfile:_paymentRequest->selected_contact_profile() |
+ delegate:&_contactAddressNormalizerDelegate |
+ flag:&_contactAddressNormalizationPending]; |
} |
- (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); |
- |
- // Get the billing address |
- autofill::AutofillProfile billingAddress; |
- |
// TODO(crbug.com/714768): Make sure the billing address is set and valid |
// before getting here. Once the bug is addressed, there will be no need to |
// copy the address, *billing_address_ptr can be used to get the basic card |
// response. |
if (!card.billing_address_id().empty()) { |
- autofill::AutofillProfile* billingAddressPtr = |
+ autofill::AutofillProfile* billingAddress = |
autofill::PersonalDataManager::GetProfileFromProfilesByGUID( |
card.billing_address_id(), _paymentRequest->billing_profiles()); |
- if (billingAddressPtr) |
- billingAddress = *billingAddressPtr; |
- } |
- |
- paymentResponse.details = GetBasicCardResponseFromAutofillCreditCard( |
- card, cvc, billingAddress, |
- 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 (billingAddress) { |
+ _fullCard.billingAddress = *billingAddress; |
+ _billingAddressNormalizationPending = YES; |
- 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:&_fullCard.billingAddress |
+ delegate:&_billingAddressNormalizerDelegate |
+ flag:&_billingAddressNormalizationPending]; |
+ } |
} |
- 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); |
- } |
+ _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 sendPaymentResponseIfAllAsyncCallsCompleted]; |
+} |
+ |
+- (void)sendPaymentResponseIfAllAsyncCallsCompleted { |
+ if (_contactAddressNormalizationPending == NO && |
+ _shippingAddressNormalizationPending == NO && |
+ _billingAddressNormalizationPending == 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, _fullCard.billingAddress, |
+ 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 { |
@@ -355,7 +524,7 @@ class FullCardRequester |
- (void)paymentRequestViewControllerDidConfirm: |
(PaymentRequestViewController*)controller { |
- [self sendPaymentResponse]; |
+ [self requestFullCardAndNormalizeProfiles]; |
} |
- (void)paymentRequestViewControllerDidSelectSettings: |
@@ -431,7 +600,7 @@ class FullCardRequester |
- (void)paymentItemsDisplayCoordinatorDidConfirm: |
(PaymentItemsDisplayCoordinator*)coordinator { |
- [self sendPaymentResponse]; |
+ [self requestFullCardAndNormalizeProfiles]; |
} |
#pragma mark - ShippingAddressSelectionCoordinatorDelegate |