Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(93)

Unified Diff: ios/chrome/browser/ui/payments/payment_request_coordinator.mm

Issue 2844783002: [Payment Request] Adds address normalization on iOS. (Closed)
Patch Set: Addresses comments from mahmadi@. Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ios/chrome/browser/ui/payments/BUILD.gn ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « ios/chrome/browser/ui/payments/BUILD.gn ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698