// 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 "components/autofill/core/browser/payments/credit_card_access_manager.h"

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/check_deref.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/autofill/core/browser/data_manager/payments/payments_data_manager.h"
#include "components/autofill/core/browser/data_model/payments/credit_card.h"
#include "components/autofill/core/browser/form_import/form_data_importer.h"
#include "components/autofill/core/browser/form_import/payments/payments_form_data_importer.h"
#include "components/autofill/core/browser/foundations/autofill_client.h"
#include "components/autofill/core/browser/foundations/autofill_driver.h"
#include "components/autofill/core/browser/foundations/browser_autofill_manager.h"
#include "components/autofill/core/browser/metrics/form_events/credit_card_form_event_logger.h"
#include "components/autofill/core/browser/metrics/payments/better_auth_metrics.h"
#include "components/autofill/core/browser/metrics/payments/card_info_retrieval_enrolled_metrics.h"
#include "components/autofill/core/browser/metrics/payments/card_unmask_flow_metrics.h"
#include "components/autofill/core/browser/metrics/payments/mandatory_reauth_metrics.h"
#include "components/autofill/core/browser/payments/autofill_error_dialog_context.h"
#include "components/autofill/core/browser/payments/autofill_payments_feature_availability.h"
#include "components/autofill/core/browser/payments/card_unmask_challenge_option.h"
#include "components/autofill/core/browser/payments/credit_card_cvc_authenticator.h"
#include "components/autofill/core/browser/payments/credit_card_risk_based_authenticator.h"
#include "components/autofill/core/browser/payments/full_card_request.h"
#include "components/autofill/core/browser/payments/mandatory_reauth_manager.h"
#include "components/autofill/core/browser/payments/payments_autofill_client.h"
#include "components/autofill/core/browser/payments/payments_network_interface.h"
#include "components/autofill/core/browser/payments/payments_request_details.h"
#include "components/autofill/core/browser/payments/payments_util.h"
#include "components/autofill/core/browser/payments/payments_window_manager.h"
#include "components/autofill/core/browser/payments/virtual_card_enrollment_flow.h"
#include "components/autofill/core/browser/payments/virtual_card_enrollment_manager.h"
#include "components/autofill/core/browser/payments/webauthn_callback_types.h"
#include "components/autofill/core/browser/suggestions/payments/payments_suggestion_generator_util.h"
#include "components/autofill/core/browser/ui/payments/autofill_progress_ui_type.h"
#include "components/autofill/core/common/autofill_clock.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"

#if !BUILDFLAG(IS_IOS)
#include "components/autofill/core/browser/payments/credit_card_fido_authenticator.h"
#include "components/autofill/core/browser/strike_databases/payments/fido_authentication_strike_database.h"
#endif

namespace autofill {
namespace {

using CardInfoRetrievalEnrolledUnmaskResult =
    autofill_metrics::CardInfoRetrievalEnrolledUnmaskResult;
using PaymentsRpcCardType =
    payments::PaymentsAutofillClient::PaymentsRpcCardType;
using PaymentsRpcResult = payments::PaymentsAutofillClient::PaymentsRpcResult;
using Result =
    CreditCardRiskBasedAuthenticator::RiskBasedAuthenticationResponse::Result;
using ServerCardUnmaskFlowType = autofill_metrics::ServerCardUnmaskFlowType;
using ServerCardUnmaskResult = autofill_metrics::ServerCardUnmaskResult;

// Timeout to wait for unmask details from Google Payments.
constexpr auto kUnmaskDetailsResponseTimeout = base::Seconds(3);
// Time to wait between multiple calls to GetUnmaskDetails().
constexpr auto kDelayForGetUnmaskDetails = base::Minutes(3);

// Suffix for server IDs in the cache indicating that a card is a virtual card.
constexpr char kVirtualCardIdentifier[] = "_vcn";

bool IsEligibleForCardInfoRetrievalAuthentication(
    const CreditCard& card,
    const std::vector<CardUnmaskChallengeOption>& challenge_options) {
  if (card.card_info_retrieval_enrollment_state() !=
      CreditCard::CardInfoRetrievalEnrollmentState::kRetrievalEnrolled) {
    return false;
  }

  if (challenge_options.empty()) {
    return false;
  }

  // We currently only support SMS OTP challenge for CardInfoRetrieval.
  for (const CardUnmaskChallengeOption& challenge_option : challenge_options) {
    if (challenge_option.type != CardUnmaskChallengeOptionType::kSmsOtp) {
      return false;
    }
  }

  return true;
}

}  // namespace

CreditCardAccessManager::CreditCardAccessManager(
    BrowserAutofillManager* manager)
    : manager_(CHECK_DEREF(manager)) {}

CreditCardAccessManager::~CreditCardAccessManager() {
  // This clears the record type of the most recently autofilled card with no
  // interactive authentication flow upon page navigation, as page navigation
  // results in us destroying the current CreditCardAccessManager and creating a
  // new one.
  if (auto* form_data_importer = autofill_client().GetFormDataImporter()) {
    form_data_importer->GetPaymentsFormDataImporter()
        .SetPaymentMethodTypeIfNonInteractiveAuthenticationFlowCompleted(
            std::nullopt);
  }
  // Reset informs observers that pending fetch requests are now considered
  // failed.
  Reset();
  NotifyObservers(&Observer::OnCreditCardAccessManagerDestroyed);
}

void CreditCardAccessManager::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void CreditCardAccessManager::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void CreditCardAccessManager::UpdateCreditCardFormEventLogger() {
  size_t server_record_type_count = 0;
  size_t local_record_type_count = 0;
  for (const CreditCard* credit_card :
       payments_data_manager().GetCreditCards()) {
    if (credit_card->record_type() == CreditCard::RecordType::kLocalCard) {
      local_record_type_count++;
    } else {
      server_record_type_count++;
    }
  }
  form_event_logger().set_server_record_type_count(server_record_type_count);
  form_event_logger().set_local_record_type_count(local_record_type_count);
}

bool CreditCardAccessManager::UnmaskedCardCacheIsEmpty() {
  return unmasked_card_cache_.empty();
}

std::vector<const CachedServerCardInfo*>
CreditCardAccessManager::GetCachedUnmaskedCards() const {
  std::vector<const CachedServerCardInfo*> unmasked_cards;
  for (const auto& [key, card_info] : unmasked_card_cache_) {
    unmasked_cards.push_back(&card_info);
  }
  return unmasked_cards;
}

bool CreditCardAccessManager::IsCardPresentInUnmaskedCache(
    const CreditCard& card) const {
  return unmasked_card_cache_.find(GetKeyForUnmaskedCardsCache(card)) !=
         unmasked_card_cache_.end();
}

bool CreditCardAccessManager::ShouldClearPreviewedForm() {
  return !is_authentication_in_progress_;
}

void CreditCardAccessManager::PrepareToFetchCreditCard() {
#if !BUILDFLAG(IS_IOS)
  // No need to fetch details if there are no server cards.
  if (std::ranges::all_of(GetCreditCardsToSuggest(payments_data_manager()),
                          &CreditCard::IsLocalCard)) {
    return;
  }

  // Do not make a preflight call if unnecessary, such as if one is already in
  // progress or a recently-returned call should be currently used.
  if (!can_fetch_unmask_details_) {
    return;
  }
  // As we may now be making a GetUnmaskDetails call, disallow further calls
  // until the current preflight call has been used or has timed out.
  can_fetch_unmask_details_ = false;

  // Reset in case a late response was ignored.
  ready_to_start_authentication_.Reset();

  // Prefetching risk data for latency optimization and caching it for upcoming
  // use in retrieval.
  if (base::FeatureList::IsEnabled(
          features::kAutofillEnablePrefetchingRiskDataForRetrieval)) {
    autofill_client().GetPaymentsAutofillClient()->LoadRiskData(
        base::DoNothing());
  }

  // If `is_user_verifiable_` is set, then directly call
  // `GetUnmaskDetailsIfUserIsVerifiable()`, otherwise fetch value for
  // `is_user_verifiable_`.
  if (is_user_verifiable_.has_value()) {
    GetUnmaskDetailsIfUserIsVerifiable(is_user_verifiable_.value());
  } else {
    is_user_verifiable_called_timestamp_ = base::TimeTicks::Now();

    GetOrCreateFidoAuthenticator()->IsUserVerifiable(base::BindOnce(
        &CreditCardAccessManager::GetUnmaskDetailsIfUserIsVerifiable,
        GetWeakPtr()));
  }
#endif
}

void CreditCardAccessManager::GetUnmaskDetailsIfUserIsVerifiable(
    bool is_user_verifiable) {
#if !BUILDFLAG(IS_IOS)
  is_user_verifiable_ = is_user_verifiable;

  if (is_user_verifiable_called_timestamp_.has_value()) {
    autofill_metrics::LogUserVerifiabilityCheckDuration(
        base::TimeTicks::Now() - is_user_verifiable_called_timestamp_.value());
  }

  // If there is already an unmask details request in progress, do not initiate
  // another one and return early.
  if (unmask_details_request_in_progress_) {
    return;
  }

  // Log that we are initiating the card unmask preflight flow.
  autofill_metrics::LogCardUnmaskPreflightInitiated();

  // If user is verifiable, then make preflight call to payments to fetch unmask
  // details, otherwise the only option is to perform CVC Auth, which does not
  // require any.
  if (is_user_verifiable_.value_or(false)) {
    unmask_details_request_in_progress_ = true;
    preflight_call_timestamp_ = base::TimeTicks::Now();
    payments_autofill_client().GetPaymentsNetworkInterface()->GetUnmaskDetails(
        base::BindOnce(&CreditCardAccessManager::OnDidGetUnmaskDetails,
                       GetWeakPtr()),
        payments_data_manager().app_locale());
    autofill_metrics::LogCardUnmaskPreflightCalled(
        GetOrCreateFidoAuthenticator()->IsUserOptedIn());
  }
#endif
}

void CreditCardAccessManager::LogMetricsAndFillFormForServerUnmaskFlows(
    UnmaskAuthFlowType unmask_auth_flow_type) {
  ServerCardUnmaskFlowType flow_type = ServerCardUnmaskFlowType::kUnspecified;
  switch (unmask_auth_flow_type) {
    case UnmaskAuthFlowType::kCvcThenFido:
    case UnmaskAuthFlowType::kFido:
      form_event_logger().LogCardUnmaskAuthenticationPromptCompleted(
          unmask_auth_flow_type_);
      if (card_->record_type() == CreditCard::RecordType::kVirtualCard) {
        flow_type = ServerCardUnmaskFlowType::kFidoOnly;
      }
      if (!card_->cvc().empty()) {
        autofill_metrics::LogCvcFilling(
            autofill_metrics::CvcFillingFlowType::kFido, card_->record_type());
      }
      break;
    case UnmaskAuthFlowType::kOtp:
      flow_type = ServerCardUnmaskFlowType::kOtpOnly;
      break;
    case UnmaskAuthFlowType::kOtpFallbackFromFido:
      flow_type = ServerCardUnmaskFlowType::kOtpFallbackFromFido;
      break;
    case UnmaskAuthFlowType::kThreeDomainSecure:
      // TODO(crbug.com/41494927): Add logging for kThreeDomainSecure.
    case UnmaskAuthFlowType::kThreeDomainSecureConsentAlreadyGiven:
      // TODO(crbug.com/41494927): Add logging for
      // kThreeDomainSecureConsentAlreadyGiven.
    case UnmaskAuthFlowType::kCvc:
    case UnmaskAuthFlowType::kCvcFallbackFromFido:
    case UnmaskAuthFlowType::kNone:
      NOTREACHED();
  }
  autofill_metrics::LogServerCardUnmaskResult(
      ServerCardUnmaskResult::kAuthenticationUnmasked,
      card_->record_type() == CreditCard::RecordType::kVirtualCard
          ? PaymentsRpcCardType::kVirtualCard
          : PaymentsRpcCardType::kServerCard,
      flow_type);
  if (card_->IsEnrolledInCardInfoRetrieval() &&
      (unmask_auth_flow_type == UnmaskAuthFlowType::kOtp ||
       unmask_auth_flow_type == UnmaskAuthFlowType::kOtpFallbackFromFido)) {
    autofill_metrics::LogCardInfoRetrievalEnrolledUnmaskResult(
        CardInfoRetrievalEnrolledUnmaskResult::kAuthenticationUnmasked);
  }
  OnCreditCardFetched(*card_, /*card_was_fetched_from_cache=*/false);
}

void CreditCardAccessManager::OnDidGetUnmaskDetails(
    PaymentsRpcResult result,
    payments::UnmaskDetails& unmask_details) {
  // Log latency for preflight call.
  if (preflight_call_timestamp_.has_value()) {
    autofill_metrics::LogCardUnmaskPreflightDuration(
        base::TimeTicks::Now() - *preflight_call_timestamp_);
  }

  unmask_details_request_in_progress_ = false;
  unmask_details_ = unmask_details;

  // TODO(crbug.com/40253859): Check that the user is off the record separately.
  unmask_details_.server_denotes_fido_eligible_but_not_opted_in =
      unmask_details_.server_denotes_fido_eligible_but_not_opted_in &&
      !autofill_client().IsOffTheRecord();

  // Set delay as FIDO request timeout if available, otherwise set to default.
  base::TimeDelta delay = kDelayForGetUnmaskDetails;
  if (!unmask_details_.fido_request_options.empty()) {
    if (std::optional<int> request_timeout =
            unmask_details_.fido_request_options.FindInt("timeout_millis")) {
      delay = base::Milliseconds(*request_timeout);
    }
  }

#if !BUILDFLAG(IS_IOS)
  opt_in_intention_ =
      GetOrCreateFidoAuthenticator()->GetUserOptInIntention(unmask_details);
#endif
  ready_to_start_authentication_.Signal();

  // Use the weak_ptr here so that the delayed task won't be executed if the
  // |credit_card_access_manager| is reset.
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&CreditCardAccessManager::SignalCanFetchUnmaskDetails,
                     GetWeakPtr()),
      delay);
}

void CreditCardAccessManager::FetchCreditCard(
    const CreditCard* card,
    OnCreditCardFetchedCallback on_credit_card_fetched) {
  auto* form_data_importer = autofill_client().GetFormDataImporter();
  CHECK(form_data_importer);
  form_data_importer->GetPaymentsFormDataImporter()
      .fetched_payments_data_context() =
      payments::PaymentsFormDataImporter::FetchedPaymentsDataContext();
  // Reset the variable in PaymentsFormDataImporter that denotes if
  // non-interactive authentication happened. This variable will be set to a
  // value if a payments autofill non-interactive flow successfully completes.
  form_data_importer->GetPaymentsFormDataImporter()
      .SetPaymentMethodTypeIfNonInteractiveAuthenticationFlowCompleted(
          std::nullopt);

  // Abort if authentication is already in progress, but don't reset status.
  if (is_authentication_in_progress_) {
    return;
  }

  // If card is nullptr we reset all states and abort authentication.
  if (!card) {
    Reset();
    return;
  }

  // Get the card's record type to correctly handle its fetching.
  NotifyObservers(&Observer::OnCreditCardFetchStarted, *card);
  CreditCard::RecordType record_type = card->record_type();
  on_credit_card_fetched_callback_ = std::move(on_credit_card_fetched);

  // Log the server card unmasking attempt, and differentiate based on server
  // card or virtual card.
  if (ShouldLogServerCardUnmaskAttemptMetrics(record_type)) {
    autofill_metrics::LogServerCardUnmaskAttempt(
        record_type == CreditCard::RecordType::kVirtualCard
            ? PaymentsRpcCardType::kVirtualCard
            : PaymentsRpcCardType::kServerCard);
  }

  // If the to-be-unmasked card is a virtual card eligible card (this also
  // implicitly checks the RecordType to be masked server card), send the
  // virtual card enrollment preflight call early.
  auto* virtual_card_enrollment_manager =
      payments_autofill_client().GetVirtualCardEnrollmentManager();
  if (card->virtual_card_enrollment_state() ==
          CreditCard::VirtualCardEnrollmentState::kUnenrolledAndEligible &&
      virtual_card_enrollment_manager) {
    // Set empty callback as we need to wait for form submission & card
    // extraction from the form, before we start the next step.
    virtual_card_enrollment_manager->InitVirtualCardEnroll(
        *card, VirtualCardEnrollmentSource::kDownstream,
        /*virtual_card_enrollment_fields_loaded_callback=*/base::DoNothing());
  }

  // If card has been previously unmasked, use cached data.
  std::unordered_map<std::string, CachedServerCardInfo>::iterator it =
      unmasked_card_cache_.find(GetKeyForUnmaskedCardsCache(*card));
  if (it != unmasked_card_cache_.end()) {  // key is in cache
    it->second.card.set_cvc(it->second.cvc);
    OnCreditCardFetched(it->second.card, /*card_was_fetched_from_cache=*/true);

    autofill_metrics::LogServerCardUnmaskResult(
        ServerCardUnmaskResult::kLocalCacheHit, record_type,
        ServerCardUnmaskFlowType::kUnspecified);

    Reset();
    return;
  }

  card_ = std::make_unique<CreditCard>(*card);
  switch (record_type) {
    case CreditCard::RecordType::kVirtualCard:
      return FetchVirtualCard();
    case CreditCard::RecordType::kMaskedServerCard:
      return FetchMaskedServerCard();
    case CreditCard::RecordType::kLocalCard:
      return FetchLocalCard();
    case CreditCard::RecordType::kFullServerCard:
      // Full server cards are only possible after a previous unmasking on the
      // same page. When that happens, subsequent repeat fills of the unmasked
      // card should flow through the cache and not reach here.
      NOTREACHED();
  }
}

bool CreditCardAccessManager::IsMaskedServerCardRiskBasedAuthAvailable() const {
  bool is_card_info_retrieval_enrolled =
      (card_->card_info_retrieval_enrollment_state() ==
       CreditCard::CardInfoRetrievalEnrollmentState::kRetrievalEnrolled);
  return !card_->IsExpired(AutofillClock::Now()) &&
         (base::FeatureList::IsEnabled(
              features::kAutofillEnableFpanRiskBasedAuthentication) ||
          is_card_info_retrieval_enrolled);
}

void CreditCardAccessManager::FIDOAuthOptChange(bool opt_in) {
#if BUILDFLAG(IS_IOS)
  return;
#else
  if (opt_in) {
    ShowWebauthnOfferDialog(/*card_authorization_token=*/std::string());
  } else {
    // We should not offer to update any user preferences when the user is off
    // the record. This also protects against a possible crash when attempting
    // to add the maximum amount of strikes to the FIDO auth strike database, as
    // strike databases are not present in incognito mode and should not be
    // used.
    if (autofill_client().IsOffTheRecord()) {
      return;
    }

    GetOrCreateFidoAuthenticator()->OptOut();
    if (auto* strike_database =
            GetOrCreateFidoAuthenticator()
                ->GetOrCreateFidoAuthenticationStrikeDatabase()) {
      strike_database->AddStrikes(
          FidoAuthenticationStrikeDatabase::kStrikesToAddWhenUserOptsOut);
    }
  }
#endif
}

void CreditCardAccessManager::OnSettingsPageFIDOAuthToggled(bool opt_in) {
#if BUILDFLAG(IS_IOS)
  return;
#else
  // TODO(crbug.com/40621544): Add a rate limiter to counter spam clicking.
  FIDOAuthOptChange(opt_in);
#endif
}

void CreditCardAccessManager::SignalCanFetchUnmaskDetails() {
  can_fetch_unmask_details_ = true;
}

void CreditCardAccessManager::CacheUnmaskedCardInfo(const CreditCard& card,
                                                    const std::u16string& cvc) {
  DCHECK(card.record_type() == CreditCard::RecordType::kFullServerCard ||
         card.record_type() == CreditCard::RecordType::kVirtualCard);
  std::string identifier =
      card.record_type() == CreditCard::RecordType::kVirtualCard
          ? card.server_id() + kVirtualCardIdentifier
          : card.server_id();
  CachedServerCardInfo card_info = {card, cvc, /*cache_uses=*/0};
  unmasked_card_cache_[identifier] = card_info;
}

void CreditCardAccessManager::StartAuthenticationFlow(bool fido_auth_enabled) {
  if (card_->record_type() == CreditCard::RecordType::kVirtualCard) {
    StartAuthenticationFlowForVirtualCard(fido_auth_enabled);
  } else {
    StartAuthenticationFlowForMaskedServerCard(fido_auth_enabled);
  }
}

void CreditCardAccessManager::StartAuthenticationFlowForVirtualCard(
    bool fido_auth_enabled) {
  // TODO(crbug.com/40195445): Currently if the card is a virtual card and FIDO
  // auth was provided by issuer, we prefer FIDO auth. Remove FIDO preference
  // and allow user selections later.
  if (fido_auth_enabled) {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
    ShowVerifyPendingDialog();
#endif
    Authenticate(UnmaskAuthFlowType::kFido);
    return;
  }

  // Otherwise, we first check if other options are provided. If not, end the
  // session and display an error dialog.
  std::vector<CardUnmaskChallengeOption>& challenge_options =
      risk_based_authentication_response_.card_unmask_challenge_options;
  if (challenge_options.empty()) {
    payments_autofill_client().ShowAutofillErrorDialog(
        AutofillErrorDialogContext::WithVirtualCardPermanentOrTemporaryError(
            /*is_permanent_error=*/true));
    Reset();
    autofill_metrics::LogServerCardUnmaskResult(
        ServerCardUnmaskResult::kOnlyFidoAvailableButNotOptedIn,
        PaymentsRpcCardType::kVirtualCard, ServerCardUnmaskFlowType::kFidoOnly);
    return;
  }

  // If we only have one challenge option, and it is a CVC challenge option, go
  // directly to the CVC input dialog.
  if (challenge_options.size() == 1 &&
      challenge_options[0].type == CardUnmaskChallengeOptionType::kCvc) {
    selected_challenge_option_ = &challenge_options[0];
    Authenticate(UnmaskAuthFlowType::kCvc);
    return;
  }

  // If we only have one challenge option, and it is a 3DS challenge option,
  // authenticate as kThreeDomainSecure flow type.
  if (challenge_options.size() == 1 &&
      challenge_options[0].type ==
          CardUnmaskChallengeOptionType::kThreeDomainSecure) {
    selected_challenge_option_ = &challenge_options[0];
    Authenticate(UnmaskAuthFlowType::kThreeDomainSecure);
    return;
  }

  // If we have multiple challenge options available, render the challenge
  // option selection dialog. This dialog also handles the case where we only
  // have an OTP challenge option.
  ShowUnmaskAuthenticatorSelectionDialog();
}

void CreditCardAccessManager::StartAuthenticationFlowForMaskedServerCard(
    bool fido_auth_enabled) {
  // We check if the card is enrolled in runtime retrieval and only SMS OTP
  // challenge options are present, then render the challenge option selection
  // dialog. Currently the selection dialog box is only supported for SMS OTP
  // challenges for masked server cards.
  if (IsEligibleForCardInfoRetrievalAuthentication(
          *card_,
          risk_based_authentication_response_.card_unmask_challenge_options)) {
    ShowUnmaskAuthenticatorSelectionDialog();
    return;
  }

  UnmaskAuthFlowType flow_type;
#if BUILDFLAG(IS_IOS)
  // On iOS only the CVC auth is available for masked server card.
  flow_type = UnmaskAuthFlowType::kCvc;
#else
  // If not enrolled in runtime retrieval then currently only FIDO and CVC auth
  // are available for masked server card.
  if (!fido_auth_enabled) {
    // If FIDO auth is not enabled we offer CVC auth.
    flow_type = UnmaskAuthFlowType::kCvc;
  } else if (!IsSelectedCardFidoAuthorized()) {
    // If FIDO auth is enabled but the card has not been authorized for FIDO, we
    // offer CVC auth followed with a FIDO authorization.
    flow_type = UnmaskAuthFlowType::kCvcThenFido;
  } else if (!card_->IsExpired(AutofillClock::Now())) {
    // If the FIDO auth is enabled and card has been authorized and card is not
    // expired, we offer FIDO auth.
    flow_type = UnmaskAuthFlowType::kFido;
  } else {
    // For other cases we offer CVC auth as well. E.g. A card that has been
    // authorized but is expired.
    flow_type = UnmaskAuthFlowType::kCvc;
  }
#endif

  Authenticate(flow_type);
}

void CreditCardAccessManager::Authenticate(
    UnmaskAuthFlowType unmask_auth_flow_type) {
  unmask_auth_flow_type_ = unmask_auth_flow_type;

  // Reset now that we have started authentication.
  ready_to_start_authentication_.Reset();
  unmask_details_request_in_progress_ = false;

  form_event_logger().LogCardUnmaskAuthenticationPromptShown(
      unmask_auth_flow_type_);

  // If FIDO auth was suggested, log which authentication method was
  // actually used.
  switch (unmask_auth_flow_type_) {
    case UnmaskAuthFlowType::kFido: {
      autofill_metrics::LogCardUnmaskTypeDecision(
          autofill_metrics::CardUnmaskTypeDecisionMetric::kFidoOnly);
#if BUILDFLAG(IS_IOS)
      NOTREACHED();
#else
      // If |is_authentication_in_progress_| is false, it means the process has
      // been cancelled via the verification pending dialog. Do not run
      // CreditCardFidoAuthenticator::Authenticate() in this case (should not
      // fall back to CVC auth either).
      if (!is_authentication_in_progress_) {
        Reset();
        return;
      }

      // For virtual cards and masked server cards, the `fido_request_options`
      // come from the risk based authentication response. But for masked server
      // cards if the risk based auth is not available, `fido_request_options`
      // comes from the UnmaskDetails.
      base::DictValue fido_request_options;
      std::optional<std::string> context_token;
      if (card_->record_type() == CreditCard::RecordType::kVirtualCard) {
        context_token = risk_based_authentication_response_.context_token;
        fido_request_options =
            std::move(risk_based_authentication_response_.fido_request_options);
      } else {
        CHECK_EQ(card_->record_type(),
                 CreditCard::RecordType::kMaskedServerCard);
        if (IsMaskedServerCardRiskBasedAuthAvailable()) {
          // If risk-based authentication is available, `context_token` is
          // needed for further authentication. `fido_request_options` is
          // returned within the risk-based authentication response.
          CHECK(!risk_based_authentication_response_.context_token.empty());
          context_token = risk_based_authentication_response_.context_token;
          fido_request_options = std::move(
              risk_based_authentication_response_.fido_request_options);
        } else {
          // If risk-based authentication is not available, the response of
          // UnmaskDetails preflight call will be used as the resource of
          // `fido_request_options`.
          fido_request_options =
              std::move(unmask_details_.fido_request_options);
        }
      }
      GetOrCreateFidoAuthenticator()->Authenticate(
          *card_, GetWeakPtr(), std::move(fido_request_options), context_token);
      break;
#endif
    }
    case UnmaskAuthFlowType::kCvcThenFido:
      autofill_metrics::LogCardUnmaskTypeDecision(
          autofill_metrics::CardUnmaskTypeDecisionMetric::kCvcThenFido);
      [[fallthrough]];
    case UnmaskAuthFlowType::kCvc:
    case UnmaskAuthFlowType::kCvcFallbackFromFido: {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
      // Close the Webauthn verify pending dialog if it enters CVC
      // authentication flow since the card unmask prompt will pop up.
      payments_autofill_client().CloseWebauthnDialog();
#endif

      // Delegate the task to CreditCardCvcAuthenticator.
      // If we are in the virtual card CVC auth case, we must also pass in
      // the vcn context token and the selected challenge option.
      if (card_->record_type() == CreditCard::RecordType::kVirtualCard) {
        DCHECK(selected_challenge_option_);
        payments_autofill_client().GetCvcAuthenticator().Authenticate(
            *card_, GetWeakPtr(),
            risk_based_authentication_response_.context_token,
            *selected_challenge_option_);
      } else if (IsMaskedServerCardRiskBasedAuthAvailable()) {
        CHECK(!risk_based_authentication_response_.context_token.empty());
        payments_autofill_client().GetCvcAuthenticator().Authenticate(
            *card_, GetWeakPtr(),
            risk_based_authentication_response_.context_token);
      } else {
        payments_autofill_client().GetCvcAuthenticator().Authenticate(
            *card_, GetWeakPtr());
      }
      break;
    }
    case UnmaskAuthFlowType::kOtp:
    case UnmaskAuthFlowType::kOtpFallbackFromFido: {
      CHECK(card_->record_type() == CreditCard::RecordType::kVirtualCard ||
            card_->record_type() == CreditCard::RecordType::kMaskedServerCard);
      // Delegate the task to CreditCardOtpAuthenticator.
      DCHECK(selected_challenge_option_);
      payments_autofill_client()
          .GetOtpAuthenticator()
          ->OnChallengeOptionSelected(
              *card_, *selected_challenge_option_, GetWeakPtr(),
              risk_based_authentication_response_.context_token,
              payments::GetBillingCustomerId(payments_data_manager()));
      break;
    }
    case UnmaskAuthFlowType::kThreeDomainSecure:
    case UnmaskAuthFlowType::kThreeDomainSecureConsentAlreadyGiven: {
      CHECK(card_);
      CHECK(selected_challenge_option_);
      payments::PaymentsWindowManager::Vcn3dsContext vcn_3ds_context;
      vcn_3ds_context.context_token =
          risk_based_authentication_response_.context_token;
      vcn_3ds_context.card = *card_;
      vcn_3ds_context.challenge_option = *selected_challenge_option_;
      vcn_3ds_context.completion_callback = base::BindOnce(
          &CreditCardAccessManager::OnVcn3dsAuthenticationComplete,
          GetWeakPtr());
      vcn_3ds_context.user_consent_already_given =
          unmask_auth_flow_type_ ==
          UnmaskAuthFlowType::kThreeDomainSecureConsentAlreadyGiven;
      payments_autofill_client()
          .GetPaymentsWindowManager()
          ->InitVcn3dsAuthentication(std::move(vcn_3ds_context));
      break;
    }
    case UnmaskAuthFlowType::kNone:
      NOTREACHED();
  }
}

#if !BUILDFLAG(IS_IOS)
CreditCardFidoAuthenticator*
CreditCardAccessManager::GetOrCreateFidoAuthenticator() {
  if (!fido_authenticator_) {
    fido_authenticator_ = std::make_unique<CreditCardFidoAuthenticator>(
        &manager_->driver(), &autofill_client());
  }
  return fido_authenticator_.get();
}
#endif

void CreditCardAccessManager::OnCvcAuthenticationComplete(
    const CreditCardCvcAuthenticator::CvcAuthenticationResponse& response) {
  is_authentication_in_progress_ = false;
  can_fetch_unmask_details_ = true;

  // Save credit card for caching purpose. CVC is also saved if response
  // contains CVC. `response.card` can be nullptr in the case of an error in the
  // response. If the response has an error, the `ShouldRespondImmediately()`
  // call below will return true and we will safely pass nullptr and that it is
  // an error into the `OnCreditCardFetchedCallback`, and end the flow.
  if (response.card) {
    // TODO(crbug.com/40929439): Deprecate `response.cvc` and
    // `response.card.cvc`.
    card_ = std::make_unique<CreditCard>(*response.card);
    card_->set_cvc(response.cvc);
  }

  // Log completed CVC authentication if auth was successful. Do not log for
  // kCvcThenFido flow since that is yet to be completed.
  // TODO(crbug.com/40280410): Refactor log into the new
  // LogMetricsAndFillFormForServerUnmaskFlows().
  if (response.did_succeed &&
      unmask_auth_flow_type_ != UnmaskAuthFlowType::kCvcThenFido) {
    form_event_logger().LogCardUnmaskAuthenticationPromptCompleted(
        unmask_auth_flow_type_);
  }

  bool should_register_card_with_fido = ShouldRegisterCardWithFido(response);
  if (ShouldRespondImmediately(response)) {
    DCHECK(!should_register_card_with_fido);
    if (response.did_succeed) {
      OnCreditCardFetched(*card_, /*card_was_fetched_from_cache=*/false);
    }
    unmask_auth_flow_type_ = UnmaskAuthFlowType::kNone;
  } else if (should_register_card_with_fido) {
#if !BUILDFLAG(IS_IOS)
    base::DictValue request_options;
    if (!unmask_details_.fido_request_options.empty()) {
      // For opted-in user (CVC then FIDO case), request options are returned in
      // unmask detail response.
      request_options = unmask_details_.fido_request_options.Clone();
    } else if (!response.request_options.empty()) {
      // For Android users, request_options are provided from GetRealPan if the
      // user has chosen to opt-in.
      request_options = response.request_options.Clone();
    }

    // Additionally authorizes the card with FIDO. It also delays the form
    // filling.
    GetOrCreateFidoAuthenticator()->Authorize(GetWeakPtr(),
                                              response.card_authorization_token,
                                              std::move(request_options));
#endif
  }
  if (ShouldOfferFidoOptInDialog(response)) {
    // CreditCardFidoAuthenticator will handle enrollment completely.
    ShowWebauthnOfferDialog(response.card_authorization_token);
  }

  HandleFidoOptInStatusChange();
  // TODO(crbug.com/40197696): The result of ShouldOfferFidoOptInDialog() and
  // `opt_in_intention_` are to some extent duplicate. We should be able to
  // combine them into one function.
  if (!response.did_succeed) {
    Reset();
  }
}

#if BUILDFLAG(IS_ANDROID)
bool CreditCardAccessManager::ShouldOfferFidoAuth() const {
  if (!unmask_details_.server_denotes_fido_eligible_but_not_opted_in &&
      !unmask_details_.fido_request_options.empty()) {
    // Server instructed the client to not offer FIDO because the client is
    // already opted in. This can be verified with the presence of request
    // options in the server response.
    autofill_metrics::LogWebauthnOptInPromoNotOfferedReason(
        autofill_metrics::WebauthnOptInPromoNotOfferedReason::kAlreadyOptedIn);
    return false;
  }

  if (!unmask_details_.server_denotes_fido_eligible_but_not_opted_in) {
    // If the server thinks FIDO opt-in is not required for this user, then we
    // won't offer the FIDO opt-in checkbox on the card unmask dialog. Since the
    // client is not opted-in and device is eligible, this could mean that the
    // server does not have a valid key for this device or the server is in a
    // bad state.
    autofill_metrics::LogWebauthnOptInPromoNotOfferedReason(
        autofill_metrics::WebauthnOptInPromoNotOfferedReason::
            kUnmaskDetailsOfferFidoOptInFalse);
    return false;
  }

  if (opt_in_intention_ == UserOptInIntention::kIntentToOptIn) {
    // If the user opted-in through the settings page, do not show checkbox.
    autofill_metrics::LogWebauthnOptInPromoNotOfferedReason(
        autofill_metrics::WebauthnOptInPromoNotOfferedReason::
            kOptedInFromSettings);
    return false;
  }

  if (card_->record_type() == CreditCard::RecordType::kVirtualCard) {
    // We should not offer FIDO opt-in for virtual cards.
    autofill_metrics::LogWebauthnOptInPromoNotOfferedReason(
        autofill_metrics::WebauthnOptInPromoNotOfferedReason::kVirtualCard);
    return false;
  }

  // No situations were found where we should not show the checkbox, so we
  // should return true to indicate that we should display the checkbox to the
  // user.
  return true;
}

bool CreditCardAccessManager::UserOptedInToFidoFromSettingsPageOnMobile()
    const {
  return opt_in_intention_ == UserOptInIntention::kIntentToOptIn;
}
#endif

#if !BUILDFLAG(IS_IOS)
void CreditCardAccessManager::OnFIDOAuthenticationComplete(
    const CreditCardFidoAuthenticator::FidoAuthenticationResponse& response) {
#if !BUILDFLAG(IS_ANDROID)
  // Close the Webauthn verify pending dialog. If FIDO authentication succeeded,
  // card is filled to the form, otherwise fall back to CVC authentication which
  // does not need the verify pending dialog either.
  payments_autofill_client().CloseWebauthnDialog();
#endif

  if (response.did_succeed) {
    // Save credit card for caching purpose.
    if (response.card) {
      card_ = std::make_unique<CreditCard>(*response.card);
    }
    LogMetricsAndFillFormForServerUnmaskFlows(unmask_auth_flow_type_);
    Reset();
  } else if (
      response.failure_type ==
          payments::FullCardRequest::VIRTUAL_CARD_RETRIEVAL_TRANSIENT_FAILURE ||
      response.failure_type ==
          payments::FullCardRequest::VIRTUAL_CARD_RETRIEVAL_PERMANENT_FAILURE) {
    // If it is an virtual card retrieval error, we don't want to invoke the CVC
    // authentication afterwards. Instead reset all states and invoke the error
    // dialog.
    payments_autofill_client().ShowAutofillErrorDialog(
        AutofillErrorDialogContext::WithVirtualCardPermanentOrTemporaryError(
            /*is_permanent_error=*/response.failure_type ==
            payments::FullCardRequest::
                VIRTUAL_CARD_RETRIEVAL_PERMANENT_FAILURE));

    if (card_->record_type() == CreditCard::RecordType::kVirtualCard) {
      autofill_metrics::LogServerCardUnmaskResult(
          ServerCardUnmaskResult::kVirtualCardRetrievalError,
          PaymentsRpcCardType::kVirtualCard,
          ServerCardUnmaskFlowType::kFidoOnly);
    }
    Reset();
  } else {
    // If it is an authentication error, start the CVC authentication process
    // for masked server cards or the virtual card authentication process for
    // virtual cards.
    if (card_->record_type() == CreditCard::RecordType::kVirtualCard) {
      StartAuthenticationFlowForVirtualCard(/*fido_auth_enabled=*/false);
    } else {
      Authenticate(UnmaskAuthFlowType::kCvcFallbackFromFido);
    }
  }
}

void CreditCardAccessManager::OnFidoAuthorizationComplete(bool did_succeed) {
  if (did_succeed) {
    OnCreditCardFetched(*card_, /*card_was_fetched_from_cache=*/false);
    form_event_logger().LogCardUnmaskAuthenticationPromptCompleted(
        unmask_auth_flow_type_);
  }
  Reset();
}
#endif

void CreditCardAccessManager::OnOtpAuthenticationComplete(
    const OtpAuthenticationResponse& response) {
  // Save credit card for caching purpose. CVC is also saved if response
  // contains CVC. `response.card` can be nullptr in the case of an error in the
  // response. If the response has an error, we will safely pass nullptr and
  // that it is an error into the `OnCreditCardFetchedCallback` and end the
  // flow.
  if (response.card) {
    card_ = std::make_unique<CreditCard>(*response.card);
    card_->set_cvc(response.cvc);
  }
  if (response.result == OtpAuthenticationResponse::Result::kSuccess) {
    // Authentication succeeded, log metrics and fill the form.
    LogMetricsAndFillFormForServerUnmaskFlows(unmask_auth_flow_type_);
  } else {
    // Authentication failed, log the proper error metrics.
    ServerCardUnmaskResult server_card_unmask_result =
        ServerCardUnmaskResult::kUnknown;
    CardInfoRetrievalEnrolledUnmaskResult
        card_info_retrieval_enrolled_card_unmask_result =
            CardInfoRetrievalEnrolledUnmaskResult::kUnknown;
    switch (response.result) {
      case OtpAuthenticationResponse::Result::kFlowCancelled:
        server_card_unmask_result = ServerCardUnmaskResult::kFlowCancelled;
        card_info_retrieval_enrolled_card_unmask_result =
            CardInfoRetrievalEnrolledUnmaskResult::kFlowCancelled;
        break;
      case OtpAuthenticationResponse::Result::kGenericError:
        server_card_unmask_result = ServerCardUnmaskResult::kUnexpectedError;
        card_info_retrieval_enrolled_card_unmask_result =
            CardInfoRetrievalEnrolledUnmaskResult::kUnexpectedError;
        break;
      case OtpAuthenticationResponse::Result::kAuthenticationError:
        server_card_unmask_result =
            ServerCardUnmaskResult::kAuthenticationError;
        card_info_retrieval_enrolled_card_unmask_result =
            CardInfoRetrievalEnrolledUnmaskResult::kAuthenticationError;
        break;
      case OtpAuthenticationResponse::Result::kVirtualCardRetrievalError:
        server_card_unmask_result =
            ServerCardUnmaskResult::kVirtualCardRetrievalError;
        break;
      case OtpAuthenticationResponse::Result::kSuccess:
      case OtpAuthenticationResponse::Result::kUnknown:
        NOTREACHED();
    }

    ServerCardUnmaskFlowType flow_type;
    if (unmask_auth_flow_type_ == UnmaskAuthFlowType::kOtp) {
      flow_type = ServerCardUnmaskFlowType::kOtpOnly;
    } else {
      DCHECK(unmask_auth_flow_type_ ==
             UnmaskAuthFlowType::kOtpFallbackFromFido);
      flow_type = ServerCardUnmaskFlowType::kOtpFallbackFromFido;
    }
    autofill_metrics::LogServerCardUnmaskResult(
        server_card_unmask_result, PaymentsRpcCardType::kVirtualCard,
        flow_type);
    if (card_->IsEnrolledInCardInfoRetrieval()) {
      autofill_metrics::LogCardInfoRetrievalEnrolledUnmaskResult(
          card_info_retrieval_enrolled_card_unmask_result);
    }
  }

  HandleFidoOptInStatusChange();
  Reset();
}

bool CreditCardAccessManager::IsUserOptedInToFidoAuth() {
#if BUILDFLAG(IS_IOS)
  return false;
#else
  return is_user_verifiable_.value_or(false) &&
         GetOrCreateFidoAuthenticator()->IsUserOptedIn();
#endif
}

bool CreditCardAccessManager::IsFidoAuthEnabled(bool fido_auth_offered) {
  // FIDO auth is enabled if payments offers FIDO auth, and local pref
  // indicates that the user is opted-in.
  return fido_auth_offered && IsUserOptedInToFidoAuth();
}

bool CreditCardAccessManager::IsSelectedCardFidoAuthorized() {
  // The selected card is not FIDO authorized if the user has not opted in to
  // FIDO auth.
  if (!IsUserOptedInToFidoAuth()) {
    return false;
  }
  // If risk-based authentication is available, `fido_request_options` returned
  // by the risk-based authentication call will be used as the indicator about
  // whether the selected card is FIDO authorized.
  if (IsMaskedServerCardRiskBasedAuthAvailable()) {
    return !risk_based_authentication_response_.fido_request_options.empty();
  }
  DCHECK_NE(unmask_details_.unmask_auth_method,
            payments::PaymentsAutofillClient::UnmaskAuthMethod::kUnknown);
  return unmask_details_.fido_eligible_card_ids.find(card_->server_id()) !=
         unmask_details_.fido_eligible_card_ids.end();
}

bool CreditCardAccessManager::ShouldRespondImmediately(
    const CreditCardCvcAuthenticator::CvcAuthenticationResponse& response) {
#if BUILDFLAG(IS_ANDROID)
  // GetRealPan did not return valid RequestOptions (user did not specify intent
  // to opt-in or the server returned invalid RequestOptions) AND flow is not
  // registering a new card, so fill the form directly.
  if (!GetOrCreateFidoAuthenticator()->IsValidRequestOptions(
          response.request_options) &&
      unmask_auth_flow_type_ != UnmaskAuthFlowType::kCvcThenFido) {
    return true;
  }
#else
  if (unmask_auth_flow_type_ != UnmaskAuthFlowType::kCvcThenFido) {
    return true;
  }
#endif  // BUILDFLAG(IS_ANDROID)

#if !BUILDFLAG(IS_IOS)
  // If the current flow is `kCvcThenFido` and there are no valid FIDO request
  // options present, fill the form immediately, as FIDO registration is not
  // possible.
  if (unmask_auth_flow_type_ == UnmaskAuthFlowType::kCvcThenFido &&
      !GetOrCreateFidoAuthenticator()->IsValidRequestOptions(
          unmask_details_.fido_request_options)) {
    return true;
  }
#endif  // !BUILDFLAG(IS_IOS)

  // If the response did not succeed, report the error immediately. If
  // GetRealPan did not return a card authorization token (we can't call any
  // FIDO-related flows, either opt-in or register new card, without the token),
  // fill the form immediately.
  return !response.did_succeed || response.card_authorization_token.empty();
}

bool CreditCardAccessManager::ShouldRegisterCardWithFido(
    const CreditCardCvcAuthenticator::CvcAuthenticationResponse& response) {
  // Card authorization token is required in order to call
  // CreditCardFidoAuthenticator::Authorize(), so if we do not have a card
  // authorization token populated we immediately return false.
  if (response.card_authorization_token.empty()) {
    return false;
  }

#if !BUILDFLAG(IS_IOS)
  // `unmask_auth_flow_type_` is kCvcThenFido, and there are valid FIDO request
  // options present, so the user is already opted-in and the new card must
  // additionally be authorized through WebAuthn.
  if (unmask_auth_flow_type_ == UnmaskAuthFlowType::kCvcThenFido &&
      GetOrCreateFidoAuthenticator()->IsValidRequestOptions(
          unmask_details_.fido_request_options)) {
    return true;
  }
#endif

#if BUILDFLAG(IS_ANDROID)
  // For Android, we will delay the form filling for both intent-to-opt-in user
  // opting in and opted-in user registering a new card (kCvcThenFido). So we
  // check one more scenario for Android here. If the GetRealPan response
  // includes valid `request_options`, that means the user showed intention to
  // opt-in while unmasking and must complete the challenge before successfully
  // opting-in and filling the form.
  if (GetOrCreateFidoAuthenticator()->IsValidRequestOptions(
          response.request_options)) {
    return true;
  }
#endif

  // No conditions to offer FIDO registration are met, so we return false.
  return false;
}

bool CreditCardAccessManager::ShouldOfferFidoOptInDialog(
    const CreditCardCvcAuthenticator::CvcAuthenticationResponse& response) {
#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
  // We should not offer FIDO opt-in dialog on mobile.
  return false;
#else
  if (!unmask_details_.server_denotes_fido_eligible_but_not_opted_in &&
      !unmask_details_.fido_request_options.empty()) {
    // Server instructed the client to not offer FIDO because the client is
    // already opted in. This can be verified with the presence of request
    // options in the server response.
    autofill_metrics::LogWebauthnOptInPromoNotOfferedReason(
        autofill_metrics::WebauthnOptInPromoNotOfferedReason::kAlreadyOptedIn);
    return false;
  }

  // If this card is not eligible for offering FIDO opt-in, we should not offer
  // the FIDO opt-in dialog. Since the client is not opted-in and device is
  // eligible, this could mean that the server does not have a valid key for
  // this device or the server is in a bad state.
  if (!unmask_details_.server_denotes_fido_eligible_but_not_opted_in) {
    autofill_metrics::LogWebauthnOptInPromoNotOfferedReason(
        autofill_metrics::WebauthnOptInPromoNotOfferedReason::
            kUnmaskDetailsOfferFidoOptInFalse);
    return false;
  }

  // A card authorization token is required for FIDO opt-in, so if we did not
  // receive one from the server we should not offer the FIDO opt-in dialog.
  if (response.card_authorization_token.empty()) {
    autofill_metrics::LogWebauthnOptInPromoNotOfferedReason(
        autofill_metrics::WebauthnOptInPromoNotOfferedReason::
            kCardAuthorizationTokenEmpty);
    return false;
  }

  if (auto* strike_database =
          GetOrCreateFidoAuthenticator()
              ->GetOrCreateFidoAuthenticationStrikeDatabase();
      strike_database && strike_database->ShouldBlockFeature()) {
    autofill_metrics::LogWebauthnOptInPromoNotOfferedReason(
        autofill_metrics::WebauthnOptInPromoNotOfferedReason::
            kBlockedByStrikeDatabase);
    return false;
  }

  // We should not offer FIDO opt-in for virtual cards.
  if (!card_ || card_->record_type() == CreditCard::RecordType::kVirtualCard) {
    autofill_metrics::LogWebauthnOptInPromoNotOfferedReason(
        autofill_metrics::WebauthnOptInPromoNotOfferedReason::kVirtualCard);
    return false;
  }

  // None of the cases where we should not offer the FIDO opt-in dialog were
  // true, so we should offer it.
  return true;
#endif
}

void CreditCardAccessManager::ShowWebauthnOfferDialog(
    std::string card_authorization_token) {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  GetOrCreateFidoAuthenticator()->OnWebauthnOfferDialogRequested(
      card_authorization_token);
  payments_autofill_client().ShowWebauthnOfferDialog(base::BindRepeating(
      &CreditCardAccessManager::HandleDialogUserResponse, GetWeakPtr()));
#endif
}

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
void CreditCardAccessManager::ShowVerifyPendingDialog() {
  payments_autofill_client().ShowWebauthnVerifyPendingDialog(
      base::BindRepeating(&CreditCardAccessManager::HandleDialogUserResponse,
                          GetWeakPtr()));
}

void CreditCardAccessManager::HandleDialogUserResponse(
    WebauthnDialogCallbackType type) {
  switch (type) {
    case WebauthnDialogCallbackType::kOfferAccepted:
      GetOrCreateFidoAuthenticator()->OnWebauthnOfferDialogUserResponse(
          /*did_accept=*/true);
      break;
    case WebauthnDialogCallbackType::kOfferCancelled:
      GetOrCreateFidoAuthenticator()->OnWebauthnOfferDialogUserResponse(
          /*did_accept=*/false);
      break;
    case WebauthnDialogCallbackType::kVerificationCancelled:
      // TODO(crbug.com/40621544): Add tests and logging for canceling verify
      // pending dialog.
      payments_autofill_client().GetPaymentsNetworkInterface()->CancelRequest();
      SignalCanFetchUnmaskDetails();
      ready_to_start_authentication_.Reset();
      unmask_details_request_in_progress_ = false;
      GetOrCreateFidoAuthenticator()->CancelVerification();

      // Indicate that FIDO authentication was canceled, resulting in falling
      // back to CVC auth.
      CreditCardFidoAuthenticator::FidoAuthenticationResponse response{
          .did_succeed = false};
      OnFIDOAuthenticationComplete(response);
      break;
  }
}
#endif

std::string CreditCardAccessManager::GetKeyForUnmaskedCardsCache(
    const CreditCard& card) const {
  std::string key = card.server_id();
  if (card.record_type() == CreditCard::RecordType::kVirtualCard) {
    key += kVirtualCardIdentifier;
  }
  return key;
}

void CreditCardAccessManager::FetchMaskedServerCard() {
  is_authentication_in_progress_ = true;

  bool get_unmask_details_returned =
      ready_to_start_authentication_.IsSignaled();

  if (IsMaskedServerCardRiskBasedAuthAvailable()) {
    // Preflight call response time metrics should only be logged if the user is
    // verifiable.
#if !BUILDFLAG(IS_IOS)
    if (is_user_verifiable_.value_or(false)) {
      autofill_metrics::LogPreflightCallResponseReceivedOnCardSelection(
          get_unmask_details_returned
              ? autofill_metrics::PreflightCallEvent::
                    kPreflightCallReturnedBeforeCardChosen
              : autofill_metrics::PreflightCallEvent::
                    kCardChosenBeforePreflightCallReturned,
          GetOrCreateFidoAuthenticator()->IsUserOptedIn(),
          CreditCard::RecordType::kMaskedServerCard);
    }
#endif

    payments_autofill_client().ShowAutofillProgressDialog(
        card_->card_info_retrieval_enrollment_state() ==
                CreditCard::CardInfoRetrievalEnrollmentState::kRetrievalEnrolled
            ? AutofillProgressUiType::kCardInfoRetrievalEnrolledUnmaskProgressUi
            : AutofillProgressUiType::kServerCardUnmaskProgressUi,
        /*cancel_callback=*/base::BindOnce(
            &CreditCardRiskBasedAuthenticator::OnUnmaskCancelled,
            payments_autofill_client()
                .GetRiskBasedAuthenticator()
                ->AsWeakPtr()));

    payments_autofill_client().GetRiskBasedAuthenticator()->Authenticate(
        *card_, GetWeakPtr());
    // Risk-based authentication is handled in CreditCardRiskBasedAuthenticator.
    // Further delegation will be handled in
    // CreditCardAccessManager::OnRiskBasedAuthenticationResponseReceived.
    return;
  }

  // Latency metrics should only be logged if the user is verifiable.
#if !BUILDFLAG(IS_IOS)
  if (is_user_verifiable_.value_or(false)) {
    autofill_metrics::LogUserPerceivedLatencyOnCardSelection(
        get_unmask_details_returned
            ? autofill_metrics::PreflightCallEvent::
                  kPreflightCallReturnedBeforeCardChosen
            : autofill_metrics::PreflightCallEvent::
                  kCardChosenBeforePreflightCallReturned,
        GetOrCreateFidoAuthenticator()->IsUserOptedIn());
  }
#endif

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  // On desktop, show the verify pending dialog for opted-in user, unless it is
  // already known that selected card requires CVC.
  if (IsUserOptedInToFidoAuth() &&
      (!get_unmask_details_returned || IsSelectedCardFidoAuthorized())) {
    ShowVerifyPendingDialog();
  }
#endif

  bool should_wait_to_authenticate =
      IsUserOptedInToFidoAuth() && !get_unmask_details_returned;

  if (should_wait_to_authenticate) {
    card_selected_without_unmask_details_timestamp_ = base::TimeTicks::Now();

    // Wait for |ready_to_start_authentication_| to be signaled by
    // OnDidGetUnmaskDetails() or until timeout before calling
    // OnStopWaitingForUnmaskDetails().
    ready_to_start_authentication_.OnEventOrTimeOut(
        base::BindOnce(&CreditCardAccessManager::OnStopWaitingForUnmaskDetails,
                       GetWeakPtr()),
        kUnmaskDetailsResponseTimeout);
  } else {
    StartAuthenticationFlow(IsFidoAuthEnabled(
        get_unmask_details_returned &&
        unmask_details_.unmask_auth_method ==
            payments::PaymentsAutofillClient::UnmaskAuthMethod::kFido));
  }
}

void CreditCardAccessManager::FetchVirtualCard() {
  is_authentication_in_progress_ = true;
  payments_autofill_client().ShowAutofillProgressDialog(
      AutofillProgressUiType::kVirtualCardUnmaskProgressUi,
      base::BindOnce(&CreditCardAccessManager::OnVirtualCardUnmaskCancelled,
                     GetWeakPtr()));
  payments_autofill_client().GetRiskBasedAuthenticator()->Authenticate(
      *card_, GetWeakPtr());
}

void CreditCardAccessManager::FetchLocalCard() {
  CHECK_EQ(card_->record_type(), CreditCard::RecordType::kLocalCard);

#if !BUILDFLAG(IS_IOS)
  // Latency metrics should only be logged if the user is verifiable.
  if (is_user_verifiable_.value_or(false)) {
    autofill_metrics::LogUserPerceivedLatencyOnCardSelection(
        autofill_metrics::PreflightCallEvent::kDidNotChooseMaskedCard,
        GetOrCreateFidoAuthenticator()->IsUserOptedIn());
  }
#endif

  // Check if we need to authenticate the user before filling the local card.
  if (auto* mandatory_reauth_manager =
          autofill_client()
              .GetPaymentsAutofillClient()
              ->GetOrCreatePaymentsMandatoryReauthManager();
      mandatory_reauth_manager && autofill_client()
                                      .GetPaymentsAutofillClient()
                                      ->IsMandatoryReauthEnabled()) {
    // `StartDeviceAuthenticationForFilling()` will asynchronously trigger
    // the re-authentication flow, so we should avoid calling `Reset()`
    // until the re-authentication flow is complete.
    StartDeviceAuthenticationForFilling(card_.get());
  } else {
    if (!card_->cvc().empty()) {
      autofill_metrics::LogCvcFilling(
          autofill_metrics::CvcFillingFlowType::kNoInteractiveAuthentication,
          CreditCard::RecordType::kLocalCard);
    }
    // Fill immediately if local card, as we do not need to authenticate the
    // user.
    OnCreditCardFetched(*card_, /*card_was_fetched_from_cache=*/false);

    // This local card autofill flow did not have any interactive
    // authentication, so notify the PaymentsFormDataImporter of this.
    autofill_client()
        .GetFormDataImporter()
        ->GetPaymentsFormDataImporter()
        .SetPaymentMethodTypeIfNonInteractiveAuthenticationFlowCompleted(
            payments::MandatoryReauthManager::
                GetNonInteractivePaymentMethodType(
                    CreditCard::RecordType::kLocalCard));

    // `OnCreditCardFetchedCallback` makes a copy of `card` and `cvc` before it
    // asynchronously fills them into the form. Thus we can safely call
    // `Reset()` here, and we should as from this class' point of view the
    // authentication flow is complete.
    Reset();
  }
}

void CreditCardAccessManager::OnRiskBasedAuthenticationResponseReceived(
    const CreditCardRiskBasedAuthenticator::RiskBasedAuthenticationResponse&
        response) {
  selected_challenge_option_ = nullptr;
  switch (response.result) {
    case Result::kNoAuthenticationRequired:
      // If the response indicates no further authentication is required, then
      // complete card information has been fetched from the server (this is
      // ensured in CreditCardRiskBasedAuthenticator).
      CHECK(response.card.has_value());
      card_ = std::make_unique<CreditCard>(response.card.value());
      // When the card being retrieved is a masked server card, the
      // `record_type` was set to kFullServerCard in the
      // CreditCardRiskBasedAuthenticator due to the fact that masked server
      // cards are treated as full server cards after unmasking. Hence we need
      // to explicitly set `record type` to kMaskedServerCard for non virtual
      // cards.
      OnNonInteractiveAuthenticationSuccess(
          response.card->record_type() == CreditCard::RecordType::kVirtualCard
              ? CreditCard::RecordType::kVirtualCard
              : CreditCard::RecordType::kMaskedServerCard);
      break;
    case Result::kAuthenticationRequired:
      // Authenticates users to unmask the card if the response indicates
      // further authentication is required.
      payments_autofill_client().CloseAutofillProgressDialog(
          /*show_confirmation_before_closing=*/false,
          /*no_interactive_authentication_callback=*/base::OnceClosure());
      CHECK(!response.context_token.empty());
      risk_based_authentication_response_ = response;
      // If `fido_request_options` is present, FIDO auth is offered to the card
      // being retrieved. Otherwise, we need to check the returned value of
      // GetUnmaskDetails to determine whether the card can be enrolled into
      // FIDO.
      StartAuthenticationFlow(IsFidoAuthEnabled(
          /*fido_auth_offered=*/!response.fido_request_options.empty() ||
          // FIDO enrollment is not supported for virtual cards, so skip
          // checking `unmask_details_.unmask_auth_method`, which is used for
          // FIDO enrollment, in the virtual card case.
          (card_->record_type() != CreditCard::RecordType::kVirtualCard &&
           unmask_details_.unmask_auth_method ==
               payments::PaymentsAutofillClient::UnmaskAuthMethod::kFido)));
      break;
    case Result::kAuthenticationCancelled:
      autofill_metrics::LogServerCardUnmaskResult(
          ServerCardUnmaskResult::kFlowCancelled, card_->record_type(),
          ServerCardUnmaskFlowType::kRiskBased);
      if (card_->IsEnrolledInCardInfoRetrieval()) {
        autofill_metrics::LogCardInfoRetrievalEnrolledUnmaskResult(
            CardInfoRetrievalEnrolledUnmaskResult::kFlowCancelled);
      }
      Reset();
      break;
    case Result::kError:
      // Shows error dialog to users if the authentication failed.
      payments_autofill_client().CloseAutofillProgressDialog(
          /*show_confirmation_before_closing=*/false,
          /*no_interactive_authentication_callback=*/base::OnceClosure());
      payments_autofill_client().ShowAutofillErrorDialog(
          response.error_dialog_context);
      autofill_metrics::LogServerCardUnmaskResult(
          ServerCardUnmaskResult::kAuthenticationError,
          card_->record_type() == CreditCard::RecordType::kVirtualCard
              ? PaymentsRpcCardType::kVirtualCard
              : PaymentsRpcCardType::kServerCard,
          ServerCardUnmaskFlowType::kRiskBased);
      if (card_->IsEnrolledInCardInfoRetrieval()) {
        autofill_metrics::LogCardInfoRetrievalEnrolledUnmaskResult(
            CardInfoRetrievalEnrolledUnmaskResult::kAuthenticationError);
      }
      Reset();
      break;
    case Result::kVirtualCardRetrievalError:
      // Shows error dialog to users if the authentication failed.
      payments_autofill_client().CloseAutofillProgressDialog(
          /*show_confirmation_before_closing=*/false,
          /*no_interactive_authentication_callback=*/base::OnceClosure());
      payments_autofill_client().ShowAutofillErrorDialog(
          response.error_dialog_context);
      autofill_metrics::LogServerCardUnmaskResult(
          ServerCardUnmaskResult::kVirtualCardRetrievalError,
          PaymentsRpcCardType::kVirtualCard,
          ServerCardUnmaskFlowType::kRiskBased);
      Reset();
      break;
    case Result::kUnknown:
      NOTREACHED();
  }
}

void CreditCardAccessManager::OnNonInteractiveAuthenticationSuccess(
    CreditCard::RecordType record_type) {
  if (auto* mandatory_reauth_manager =
          autofill_client()
              .GetPaymentsAutofillClient()
              ->GetOrCreatePaymentsMandatoryReauthManager();
      mandatory_reauth_manager && autofill_client()
                                      .GetPaymentsAutofillClient()
                                      ->IsMandatoryReauthEnabled()) {
    // On some operating systems (for example, macOS and Windows), the
    // device authentication prompt freezes Chrome. Thus we can only trigger
    // the prompt after the progress dialog has been closed, which we can do
    // by using the `no_interactive_authentication_callback` parameter in
    // `PaymentsAutofillClient::CloseAutofillProgressDialog()`.
    payments_autofill_client().CloseAutofillProgressDialog(
        /*show_confirmation_before_closing=*/false,
        /*no_interactive_authentication_callback=*/base::BindOnce(
            // `StartDeviceAuthenticationForFilling()` will asynchronously
            // trigger the re-authentication flow, so we should avoid
            // calling `Reset()` until the re-authentication flow is
            // complete.
            &CreditCardAccessManager::StartDeviceAuthenticationForFilling,
            GetWeakPtr(), card_.get()));
  } else {
    payments_autofill_client().CloseAutofillProgressDialog(
        /*show_confirmation_before_closing=*/true,
        /*no_interactive_authentication_callback=*/base::OnceClosure());
    OnCreditCardFetched(*card_, /*card_was_fetched_from_cache=*/false);

    // If the server returned a successful response along with the card's
    // real PAN without requiring interactive authentication, set the
    // `card_record_type_if_non_interactive_authentication_flow_completed_`
    // field in PaymentsFormDataImporter so that MandatoryReauthManager can
    // decide whether to offer mandatory re-auth opt-in for this user.
    autofill_client()
        .GetFormDataImporter()
        ->GetPaymentsFormDataImporter()
        .SetPaymentMethodTypeIfNonInteractiveAuthenticationFlowCompleted(
            payments::MandatoryReauthManager::
                GetNonInteractivePaymentMethodType(record_type));

    autofill_metrics::LogServerCardUnmaskResult(
        ServerCardUnmaskResult::kRiskBasedUnmasked,
        record_type == CreditCard::RecordType::kVirtualCard
            ? PaymentsRpcCardType::kVirtualCard
            : PaymentsRpcCardType::kServerCard,
        ServerCardUnmaskFlowType::kRiskBased);

    if (card_->card_info_retrieval_enrollment_state() ==
        CreditCard::CardInfoRetrievalEnrollmentState::kRetrievalEnrolled) {
      autofill_metrics::LogCardInfoRetrievalEnrolledUnmaskResult(
          CardInfoRetrievalEnrolledUnmaskResult::kRiskBasedUnmasked);
    }

    if (!card_->cvc().empty()) {
      autofill_metrics::LogCvcFilling(
          autofill_metrics::CvcFillingFlowType::kNoInteractiveAuthentication,
          record_type);
    }

    // `OnCreditCardFetchedCallback` makes a copy of `card` and `cvc` before it
    // asynchronously fills them into the form. Thus we can safely call
    // `Reset()` here, and we should as from this class' point of view the
    // authentication flow is complete.
    Reset();
  }
}

void CreditCardAccessManager::OnStopWaitingForUnmaskDetails(
    bool get_unmask_details_returned) {
  // If the user had to wait for Unmask Details, log the latency.
  if (card_selected_without_unmask_details_timestamp_.has_value()) {
    autofill_metrics::LogUserPerceivedLatencyOnCardSelectionDuration(
        base::TimeTicks::Now() -
        card_selected_without_unmask_details_timestamp_.value());
    autofill_metrics::LogUserPerceivedLatencyOnCardSelectionTimedOut(
        /*did_time_out=*/!get_unmask_details_returned);
    card_selected_without_unmask_details_timestamp_ = std::nullopt;
  }

  // Start the authentication after the wait ends.
  StartAuthenticationFlow(IsFidoAuthEnabled(
      get_unmask_details_returned &&
      unmask_details_.unmask_auth_method ==
          payments::PaymentsAutofillClient::UnmaskAuthMethod::kFido));
}

void CreditCardAccessManager::OnUserAcceptedAuthenticationSelectionDialog(
    const std::string& selected_challenge_option_id) {
  selected_challenge_option_ =
      GetCardUnmaskChallengeOptionForChallengeId(selected_challenge_option_id);

  // This if-statement should never be entered. We will only be selecting
  // challenge options that we passed into the controller from
  // `risk_based_authentication_response_.card_unmask_challenge_options`,
  // which is also the vector that we search for challenge options in. For the
  // context token, the server is guaranteed to always return a VCN context
  // token for the virtual card authentication flow. This if-statement is just
  // here as a safety.
  if (!selected_challenge_option_ ||
      risk_based_authentication_response_.context_token.empty()) {
    NOTREACHED();
  }

  UnmaskAuthFlowType selected_authentication_type = UnmaskAuthFlowType::kNone;
  switch (selected_challenge_option_->type) {
    case CardUnmaskChallengeOptionType::kCvc:
      selected_authentication_type = UnmaskAuthFlowType::kCvc;
      break;
    case CardUnmaskChallengeOptionType::kSmsOtp:
    case CardUnmaskChallengeOptionType::kEmailOtp:
      selected_authentication_type =
          unmask_auth_flow_type_ == UnmaskAuthFlowType::kFido
              ? UnmaskAuthFlowType::kOtpFallbackFromFido
              : UnmaskAuthFlowType::kOtp;
      break;
    case CardUnmaskChallengeOptionType::kThreeDomainSecure:
      selected_authentication_type =
          UnmaskAuthFlowType::kThreeDomainSecureConsentAlreadyGiven;
      break;
    case CardUnmaskChallengeOptionType::kUnknownType:
      NOTREACHED();
  }
  Authenticate(selected_authentication_type);
}

void CreditCardAccessManager::OnVirtualCardUnmaskCancelled() {
  if (unmask_auth_flow_type_ == UnmaskAuthFlowType::kOtp ||
      unmask_auth_flow_type_ == UnmaskAuthFlowType::kOtpFallbackFromFido) {
    // It is possible to have the user hit the cancel button during an in-flight
    // Virtual Card Unmask request, so we need to reset the state of the
    // CreditCardOtpAuthenticator as well to ensure the flow does not continue,
    // as continuing the flow can cause a crash.
    payments_autofill_client().GetOtpAuthenticator()->Reset();
  }

  ServerCardUnmaskFlowType flow_type;
  switch (unmask_auth_flow_type_) {
    case UnmaskAuthFlowType::kOtp:
      flow_type = ServerCardUnmaskFlowType::kOtpOnly;
      break;
    case UnmaskAuthFlowType::kOtpFallbackFromFido:
      flow_type = ServerCardUnmaskFlowType::kOtpFallbackFromFido;
      break;
    case UnmaskAuthFlowType::kNone:
      flow_type = ServerCardUnmaskFlowType::kRiskBased;
      break;
    case UnmaskAuthFlowType::kFido:
    case UnmaskAuthFlowType::kCvcThenFido:
    case UnmaskAuthFlowType::kCvcFallbackFromFido:
      DUMP_WILL_BE_NOTREACHED();
      [[fallthrough]];
    case UnmaskAuthFlowType::kThreeDomainSecure:
      // TODO(crbug.com/40240970): Add a flow type for the kThreeDomainSecure
      // flow for metrics.
    case UnmaskAuthFlowType::kThreeDomainSecureConsentAlreadyGiven:
      // TODO(crbug.com/40240970): Add a flow type for the
      // kThreeDomainSecureConsentAlreadyGiven flow for metrics.
    case UnmaskAuthFlowType::kCvc:
      // TODO(crbug.com/40240970): Add a flow type for the CVC flow for metrics.
      Reset();
      return;
  }
  autofill_metrics::LogServerCardUnmaskResult(
      ServerCardUnmaskResult::kFlowCancelled, PaymentsRpcCardType::kVirtualCard,
      flow_type);
  Reset();
}

void CreditCardAccessManager::Reset() {
  weak_ptr_factory_.InvalidateWeakPtrs();
  unmask_auth_flow_type_ = UnmaskAuthFlowType::kNone;
  is_authentication_in_progress_ = false;
  preflight_call_timestamp_ = std::nullopt;
  card_selected_without_unmask_details_timestamp_ = std::nullopt;
  is_user_verifiable_called_timestamp_ = std::nullopt;
#if !BUILDFLAG(IS_IOS)
  opt_in_intention_ = UserOptInIntention::kUnspecified;
#endif
  unmask_details_ = payments::UnmaskDetails();
  selected_challenge_option_ = nullptr;
  risk_based_authentication_response_ = {};
  ready_to_start_authentication_.Reset();
  can_fetch_unmask_details_ = true;
  unmask_details_request_in_progress_ = false;

  // If the success callback was never called, then there must have been an
  // error.
  if (std::exchange(on_credit_card_fetched_callback_, {})) {
    NotifyObservers(&Observer::OnCreditCardFetchFailed, card_.get());
  }
  card_.reset();
}

void CreditCardAccessManager::HandleFidoOptInStatusChange() {
#if !BUILDFLAG(IS_IOS)
  // If user intended to opt out, we will opt user out after CVC/OTP auth
  // completes (no matter it succeeded or failed).
  if (opt_in_intention_ == UserOptInIntention::kIntentToOptOut) {
    FIDOAuthOptChange(/*opt_in=*/false);
  }
  // Reset |opt_in_intention_| after the authentication completes.
  opt_in_intention_ = UserOptInIntention::kUnspecified;
#endif
}

void CreditCardAccessManager::ShowUnmaskAuthenticatorSelectionDialog() {
  payments_autofill_client().ShowUnmaskAuthenticatorSelectionDialog(
      risk_based_authentication_response_.card_unmask_challenge_options,
      base::BindOnce(
          &CreditCardAccessManager::OnUserAcceptedAuthenticationSelectionDialog,
          GetWeakPtr()),
      base::BindOnce(&CreditCardAccessManager::OnVirtualCardUnmaskCancelled,
                     GetWeakPtr()));
}

CardUnmaskChallengeOption*
CreditCardAccessManager::GetCardUnmaskChallengeOptionForChallengeId(
    const std::string& challenge_id) {
  CreditCard::RecordType card_record_type = card_->record_type();
  CHECK(card_record_type == CreditCard::RecordType::kVirtualCard ||
        card_record_type == CreditCard::RecordType::kMaskedServerCard);
  std::vector<CardUnmaskChallengeOption>& challenge_options =
      risk_based_authentication_response_.card_unmask_challenge_options;
  auto card_unmask_challenge_options_it = std::ranges::find(
      challenge_options,
      CardUnmaskChallengeOption::ChallengeOptionId(challenge_id),
      &CardUnmaskChallengeOption::id);
  return card_unmask_challenge_options_it != challenge_options.end()
             ? &(*card_unmask_challenge_options_it)
             : nullptr;
}

bool CreditCardAccessManager::ShouldLogServerCardUnmaskAttemptMetrics(
    CreditCard::RecordType record_type) {
  // Full server cards exist only in a temporarily cached state and should never
  // be attempted to be unmasked.
  CHECK_NE(record_type, CreditCard::RecordType::kFullServerCard);

  return record_type == CreditCard::RecordType::kMaskedServerCard ||
         record_type == CreditCard::RecordType::kVirtualCard;
}

void CreditCardAccessManager::StartDeviceAuthenticationForFilling(
    const CreditCard* card) {
  is_authentication_in_progress_ = true;

  payments::MandatoryReauthManager* mandatory_reauth_manager =
      autofill_client()
          .GetPaymentsAutofillClient()
          ->GetOrCreatePaymentsMandatoryReauthManager();
  payments::MandatoryReauthAuthenticationMethod authentication_method =
      mandatory_reauth_manager->GetAuthenticationMethod();

  // If there is no supported auth method on the device, we should skip re-auth
  // and fill the form. Otherwise the user removing authentication on the
  // device will prevent them from using payments autofill. In the settings
  // page, we signal to the user through various means that they need to turn
  // the device's authentication on in order to use re-auth.
  if (!mandatory_reauth_manager->IsDeviceAuthenticationSupported()) {
    LogMandatoryReauthCheckoutFlowUsageEvent(
        payments::MandatoryReauthManager::GetNonInteractivePaymentMethodType(
            card->record_type()),
        authentication_method,
        autofill_metrics::MandatoryReauthAuthenticationFlowEvent::kFlowSkipped);
    OnCreditCardFetched(*card, /*card_was_fetched_from_cache=*/false);
    return;
  }

  LogMandatoryReauthCheckoutFlowUsageEvent(
      payments::MandatoryReauthManager::GetNonInteractivePaymentMethodType(
          card->record_type()),
      authentication_method,
      autofill_metrics::MandatoryReauthAuthenticationFlowEvent::kFlowStarted);
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS) || \
    BUILDFLAG(IS_CHROMEOS)
  autofill_client()
      .GetPaymentsAutofillClient()
      ->GetOrCreatePaymentsMandatoryReauthManager()
      ->AuthenticateWithMessage(
          l10n_util::GetStringUTF16(
              IDS_PAYMENTS_AUTOFILL_FILLING_MANDATORY_REAUTH),
          base::BindOnce(&CreditCardAccessManager::
                             OnDeviceAuthenticationResponseForFilling,
                         GetWeakPtr(), authentication_method, card));
#elif BUILDFLAG(IS_ANDROID)
  // TODO(crbug.com/40261690): Convert this to
  // MandatoryReauthManager::AuthenticateWithMessage() with the correct message
  // once it is supported. Currently, the message is "Verify it's you".
  autofill_client()
      .GetPaymentsAutofillClient()
      ->GetOrCreatePaymentsMandatoryReauthManager()
      ->Authenticate(base::BindOnce(
          &CreditCardAccessManager::OnDeviceAuthenticationResponseForFilling,
          GetWeakPtr(), authentication_method, card));
#else
  NOTREACHED();
#endif
}

void CreditCardAccessManager::OnDeviceAuthenticationResponseForFilling(
    payments::MandatoryReauthAuthenticationMethod authentication_method,
    const CreditCard* card,
    bool successful_auth) {
  CHECK(card);
  CreditCard::RecordType record_type = card->record_type();

  LogMandatoryReauthCheckoutFlowUsageEvent(
      payments::MandatoryReauthManager::GetNonInteractivePaymentMethodType(
          record_type),
      authentication_method,
      successful_auth
          ? autofill_metrics::MandatoryReauthAuthenticationFlowEvent::
                kFlowSucceeded
          : autofill_metrics::MandatoryReauthAuthenticationFlowEvent::
                kFlowFailed);
  if (successful_auth && !card->cvc().empty()) {
    autofill_metrics::LogCvcFilling(
        autofill_metrics::CvcFillingFlowType::kMandatoryReauth, record_type);
  }
  autofill_metrics::LogServerCardUnmaskResult(
      successful_auth ? ServerCardUnmaskResult::kAuthenticationUnmasked
                      : ServerCardUnmaskResult::kAuthenticationError,
      record_type, ServerCardUnmaskFlowType::kDeviceUnlock);

  if (successful_auth) {
    OnCreditCardFetched(*card, /*card_was_fetched_from_cache=*/false);
  }
  // `on_credit_card_fetched_callback_` makes a copy of `card` and `cvc` before
  // it asynchronously fills them into the form. Thus we can safely call
  // `Reset()` here, and we should as from this class' point of view the
  // authentication flow is complete.
  Reset();
}

void CreditCardAccessManager::OnVcn3dsAuthenticationComplete(
    payments::PaymentsWindowManager::Vcn3dsAuthenticationResponse response) {
  if (response.result ==
      payments::PaymentsWindowManager::Vcn3dsAuthenticationResult::kSuccess) {
    CHECK(response.card.has_value());
    OnCreditCardFetched(response.card.value(),
                        /*card_was_fetched_from_cache=*/false);
    autofill_metrics::LogServerCardUnmaskResult(
        ServerCardUnmaskResult::kAuthenticationUnmasked,
        PaymentsRpcCardType::kVirtualCard,
        ServerCardUnmaskFlowType::kThreeDomainSecure);
    form_event_logger().LogCardUnmaskAuthenticationPromptCompleted(
        unmask_auth_flow_type_);
  } else {
    autofill_metrics::LogServerCardUnmaskResult(
        response.result == payments::PaymentsWindowManager::
                               Vcn3dsAuthenticationResult::kAuthenticationFailed
            ? ServerCardUnmaskResult::kVirtualCardRetrievalError
            : ServerCardUnmaskResult::kFlowCancelled,
        PaymentsRpcCardType::kVirtualCard,
        ServerCardUnmaskFlowType::kThreeDomainSecure);
  }
  Reset();
}

void CreditCardAccessManager::OnCreditCardFetched(
    const CreditCard& card,
    bool card_was_fetched_from_cache) {
  auto* form_data_importer = autofill_client().GetFormDataImporter();
  CHECK(form_data_importer);
  auto& context = form_data_importer->GetPaymentsFormDataImporter()
                      .fetched_payments_data_context();
  context.fetched_card_instrument_id = card.instrument_id();
  context.card_was_fetched_from_cache = card_was_fetched_from_cache;
  if (on_credit_card_fetched_callback_) {
    std::move(on_credit_card_fetched_callback_).Run(card);
  }
  NotifyObservers(&Observer::OnCreditCardFetchSucceeded, card);
}

}  // namespace autofill
