1// Copyright 2018 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "ios/web_view/internal/autofill/cwv_credit_card_verifier_internal.h" 6 7#include <memory> 8 9#include "base/strings/sys_string_conversions.h" 10#include "components/autofill/core/browser/data_model/credit_card.h" 11#include "components/autofill/core/browser/ui/payments/card_unmask_prompt_controller_impl.h" 12#include "components/autofill/core/browser/ui/payments/card_unmask_prompt_view.h" 13#import "ios/web_view/internal/autofill/cwv_credit_card_internal.h" 14#include "ui/base/resource/resource_bundle.h" 15 16#if !defined(__has_feature) || !__has_feature(objc_arc) 17#error "This file requires ARC support." 18#endif 19 20NSErrorDomain const CWVCreditCardVerifierErrorDomain = 21 @"org.chromium.chromewebview.CreditCardVerifierErrorDomain"; 22NSErrorUserInfoKey const CWVCreditCardVerifierRetryAllowedKey = 23 @"retry_allowed"; 24 25namespace { 26// Converts an autofill::AutofillClient::PaymentsRpcResult to a 27// CWVCreditCardVerificationError. 28CWVCreditCardVerificationError CWVConvertPaymentsRPCResult( 29 autofill::AutofillClient::PaymentsRpcResult result) { 30 switch (result) { 31 case autofill::AutofillClient::NONE: 32 case autofill::AutofillClient::SUCCESS: 33 NOTREACHED(); 34 return CWVCreditCardVerificationErrorNone; 35 case autofill::AutofillClient::TRY_AGAIN_FAILURE: 36 return CWVCreditCardVerificationErrorTryAgainFailure; 37 case autofill::AutofillClient::PERMANENT_FAILURE: 38 return CWVCreditCardVerificationErrorPermanentFailure; 39 case autofill::AutofillClient::NETWORK_ERROR: 40 return CWVCreditCardVerificationErrorNetworkFailure; 41 } 42} 43} // namespace 44 45@interface CWVCreditCardVerifier () 46 47// Used to receive |GotVerificationResult| from WebViewCardUnmaskPromptView. 48- (void)didReceiveVerificationResultWithErrorMessage:(NSString*)errorMessage 49 retryAllowed:(BOOL)retryAllowed; 50 51@end 52 53namespace ios_web_view { 54// Webview implementation of CardUnmaskPromptView. 55class WebViewCardUnmaskPromptView : public autofill::CardUnmaskPromptView { 56 public: 57 explicit WebViewCardUnmaskPromptView(CWVCreditCardVerifier* verifier) 58 : verifier_(verifier) {} 59 60 // CardUnmaskPromptView: 61 void Show() override { 62 // No op. 63 } 64 void ControllerGone() override { 65 // No op. 66 } 67 void DisableAndWaitForVerification() override { 68 // No op. 69 } 70 void GotVerificationResult(const base::string16& error_message, 71 bool allow_retry) override { 72 NSString* ns_error_message = base::SysUTF16ToNSString(error_message); 73 [verifier_ didReceiveVerificationResultWithErrorMessage:ns_error_message 74 retryAllowed:allow_retry]; 75 } 76 77 private: 78 __weak CWVCreditCardVerifier* verifier_; 79}; 80} // namespace ios_web_view 81 82@implementation CWVCreditCardVerifier { 83 // Used to interface with |_unmaskingController|. 84 std::unique_ptr<ios_web_view::WebViewCardUnmaskPromptView> _unmaskingView; 85 // The main class that is wrapped by this class. 86 std::unique_ptr<autofill::CardUnmaskPromptControllerImpl> 87 _unmaskingController; 88 // Completion handler to be called when verification completes. 89 void (^_Nullable _completionHandler)(NSError* _Nullable); 90 // The callback to invoke for returning risk data. 91 base::OnceCallback<void(const std::string&)> _riskDataCallback; 92 // Verification was attempted at least once. 93 BOOL _verificationAttempted; 94} 95 96@synthesize creditCard = _creditCard; 97 98- (instancetype)initWithPrefs:(PrefService*)prefs 99 isOffTheRecord:(BOOL)isOffTheRecord 100 creditCard:(const autofill::CreditCard&)creditCard 101 reason:(autofill::AutofillClient::UnmaskCardReason)reason 102 delegate: 103 (base::WeakPtr<autofill::CardUnmaskDelegate>)delegate { 104 self = [super init]; 105 if (self) { 106 _creditCard = [[CWVCreditCard alloc] initWithCreditCard:creditCard]; 107 _unmaskingView = 108 std::make_unique<ios_web_view::WebViewCardUnmaskPromptView>(self); 109 _unmaskingController = 110 std::make_unique<autofill::CardUnmaskPromptControllerImpl>(prefs); 111 _unmaskingController->ShowPrompt( 112 base::BindOnce(^autofill::CardUnmaskPromptView*() { 113 return _unmaskingView.get(); 114 }), 115 creditCard, reason, delegate); 116 } 117 return self; 118} 119 120- (void)dealloc { 121 // autofill::CardUnmaskPromptControllerImpl::OnUnmaskDialogClosed, despite its 122 // name, should only be called if the user does not attempt any verification 123 // at all. 124 if (!_verificationAttempted) { 125 _unmaskingController->OnUnmaskDialogClosed(); 126 } 127} 128 129#pragma mark - Public Methods 130 131- (NSString*)navigationTitle { 132 return base::SysUTF16ToNSString(_unmaskingController->GetWindowTitle()); 133} 134 135- (NSString*)instructionMessage { 136 return base::SysUTF16ToNSString( 137 _unmaskingController->GetInstructionsMessage()); 138} 139 140- (NSString*)confirmButtonLabel { 141 return base::SysUTF16ToNSString(_unmaskingController->GetOkButtonLabel()); 142} 143 144- (UIImage*)CVCHintImage { 145 int resourceID = _unmaskingController->GetCvcImageRid(); 146 return ui::ResourceBundle::GetSharedInstance() 147 .GetNativeImageNamed(resourceID) 148 .ToUIImage(); 149} 150 151- (NSInteger)expectedCVCLength { 152 return _unmaskingController->GetExpectedCvcLength(); 153} 154 155- (BOOL)shouldRequestUpdateForExpirationDate { 156 return _unmaskingController->ShouldRequestExpirationDate(); 157} 158 159- (void)verifyWithCVC:(NSString*)CVC 160 expirationMonth:(nullable NSString*)expirationMonth 161 expirationYear:(nullable NSString*)expirationYear 162 riskData:(NSString*)riskData 163 completionHandler:(void (^)(NSError* _Nullable error))completionHandler { 164 _verificationAttempted = YES; 165 _completionHandler = completionHandler; 166 167 // It is possible for |_riskDataCallback| to be null when a failed 168 // verification attempt is retried. 169 if (_riskDataCallback) { 170 std::move(_riskDataCallback).Run(base::SysNSStringToUTF8(riskData)); 171 } 172 173 _unmaskingController->OnUnmaskPromptAccepted( 174 base::SysNSStringToUTF16(CVC), base::SysNSStringToUTF16(expirationMonth), 175 base::SysNSStringToUTF16(expirationYear), /*should_store_pan=*/false, 176 /*enable_fido_auth=*/false); 177} 178 179- (BOOL)isCVCValid:(NSString*)CVC { 180 return _unmaskingController->InputCvcIsValid(base::SysNSStringToUTF16(CVC)); 181} 182 183- (BOOL)isExpirationDateValidForMonth:(NSString*)month year:(NSString*)year { 184 return _unmaskingController->InputExpirationIsValid( 185 base::SysNSStringToUTF16(month), base::SysNSStringToUTF16(year)); 186} 187 188- (void)requestUpdateForExpirationDate { 189 _unmaskingController->NewCardLinkClicked(); 190} 191 192#pragma mark - Private Methods 193 194- (void)didReceiveVerificationResultWithErrorMessage:(NSString*)errorMessage 195 retryAllowed:(BOOL)retryAllowed { 196 if (_completionHandler) { 197 NSError* error; 198 autofill::AutofillClient::PaymentsRpcResult result = 199 _unmaskingController->GetVerificationResult(); 200 if (errorMessage.length > 0 && result != autofill::AutofillClient::NONE && 201 result != autofill::AutofillClient::SUCCESS) { 202 NSDictionary* userInfo = @{ 203 NSLocalizedDescriptionKey : errorMessage, 204 CWVCreditCardVerifierRetryAllowedKey : @(retryAllowed), 205 }; 206 error = [NSError errorWithDomain:CWVCreditCardVerifierErrorDomain 207 code:CWVConvertPaymentsRPCResult(result) 208 userInfo:userInfo]; 209 } 210 _completionHandler(error); 211 _completionHandler = nil; 212 } 213} 214 215#pragma mark - Internal Methods 216 217- (void)didReceiveUnmaskVerificationResult: 218 (autofill::AutofillClient::PaymentsRpcResult)result { 219 _unmaskingController->OnVerificationResult(result); 220} 221 222- (void)loadRiskData:(base::OnceCallback<void(const std::string&)>)callback { 223 _riskDataCallback = std::move(callback); 224} 225 226@end 227