chromium/ios/web_view/internal/autofill/cwv_credit_card_saver.mm

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

#include "base/functional/bind.h"
#include "base/strings/sys_string_conversions.h"
#include "components/autofill/core/browser/payments/legal_message_line.h"
#import "components/autofill/core/browser/payments/payments_autofill_client.h"
#include "ios/web/public/thread/web_task_traits.h"
#include "ios/web/public/thread/web_thread.h"
#import "ios/web_view/internal/autofill/cwv_credit_card_internal.h"
#import "ios/web_view/internal/autofill/cwv_credit_card_saver_internal.h"
#import "net/base/apple/url_conversions.h"
#include "ui/gfx/range/range.h"

NS_ASSUME_NONNULL_BEGIN

namespace {
// Converts |autofill::LegalMessageLines| into |NSArray<NSAttributedString*>*|.
NSArray<NSAttributedString*>* CWVLegalMessagesFromLegalMessageLines(
    const autofill::LegalMessageLines& legalMessageLines) {
  NSMutableArray<NSAttributedString*>* legalMessages = [NSMutableArray array];
  for (const autofill::LegalMessageLine& legalMessageLine : legalMessageLines) {
    NSString* text = base::SysUTF16ToNSString(legalMessageLine.text());
    NSMutableAttributedString* legalMessage =
        [[NSMutableAttributedString alloc] initWithString:text];
    for (const autofill::LegalMessageLine::Link& link :
         legalMessageLine.links()) {
      NSURL* url = net::NSURLWithGURL(link.url);
      NSRange range = link.range.ToNSRange();
      [legalMessage addAttribute:NSLinkAttributeName value:url range:range];
    }
    [legalMessages addObject:[legalMessage copy]];
  }
  return [legalMessages copy];
}
}  // namespace

@implementation CWVCreditCardSaver {
  autofill::payments::PaymentsAutofillClient::SaveCreditCardOptions
      _saveOptions;
  autofill::payments::PaymentsAutofillClient::UploadSaveCardPromptCallback
      _saveCardCallback;

  // The callback to invoke for save completion results.
  void (^_Nullable _saveCompletionHandler)(BOOL);

  // The callback to invoke for returning risk data.
  base::OnceCallback<void(const std::string&)> _riskDataCallback;

  // Whether or not either |acceptCreditCardWithRiskData:completionHandler:| or
  // |declineCreditCardSave| has been called.
  BOOL _decisionMade;
}

@synthesize creditCard = _creditCard;
@synthesize legalMessages = _legalMessages;

- (instancetype)
    initWithCreditCard:(const autofill::CreditCard&)creditCard
           saveOptions:(autofill::payments::PaymentsAutofillClient::
                            SaveCreditCardOptions)saveOptions
     legalMessageLines:(autofill::LegalMessageLines)legalMessageLines
    savePromptCallback:(autofill::payments::PaymentsAutofillClient::
                            UploadSaveCardPromptCallback)savePromptCallback {
  self = [super init];
  if (self) {
    _creditCard = [[CWVCreditCard alloc] initWithCreditCard:creditCard];
    _saveOptions = saveOptions;
    _legalMessages = CWVLegalMessagesFromLegalMessageLines(legalMessageLines);
    _saveCardCallback = std::move(savePromptCallback);
    _decisionMade = NO;
  }
  return self;
}

- (void)dealloc {
  // If the user did not choose, the decision should be marked as ignored.
  if (_saveCardCallback) {
    std::move(_saveCardCallback)
        .Run(autofill::payments::PaymentsAutofillClient::
                 SaveCardOfferUserDecision::kIgnored,
             /*user_provided_card_details=*/{});
  }
}

#pragma mark - Public Methods

- (void)acceptWithCardHolderFullName:(NSString*)cardHolderFullName
                     expirationMonth:(NSString*)expirationMonth
                      expirationYear:(NSString*)expirationYear
                            riskData:(NSString*)riskData
                   completionHandler:(void (^)(BOOL))completionHandler {
  DCHECK(!_decisionMade)
      << "You may only call -acceptWithRiskData:completionHandler: or "
         "-decline: once per instance.";
  DCHECK(_riskDataCallback && riskData);
  std::move(_riskDataCallback).Run(base::SysNSStringToUTF8(riskData));

  _saveCompletionHandler = completionHandler;
  DCHECK(_saveCardCallback);
  std::move(_saveCardCallback)
      .Run(autofill::payments::PaymentsAutofillClient::
               SaveCardOfferUserDecision::kAccepted,
           {
               .cardholder_name = base::SysNSStringToUTF16(cardHolderFullName),
               .expiration_date_month =
                   base::SysNSStringToUTF16(expirationMonth),
               .expiration_date_year = base::SysNSStringToUTF16(expirationYear),
           });
  _decisionMade = YES;
}

- (void)decline {
  DCHECK(!_decisionMade)
      << "You may only call -acceptWithRiskData:completionHandler: or "
         "-decline: once per instance.";
  DCHECK(_saveCardCallback);
  std::move(_saveCardCallback)
      .Run(autofill::payments::PaymentsAutofillClient::
               SaveCardOfferUserDecision::kDeclined,
           /*user_provided_card_details=*/{});
  _decisionMade = YES;
}

#pragma mark - Internal Methods

- (void)handleCreditCardUploadCompleted:(BOOL)cardSaved {
  if (_saveCompletionHandler) {
    _saveCompletionHandler(cardSaved);
    _saveCompletionHandler = nil;
  }
}

- (void)loadRiskData:(base::OnceCallback<void(const std::string&)>)callback {
  _riskDataCallback = std::move(callback);
}

@end

NS_ASSUME_NONNULL_END