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