chromium/components/facilitated_payments/core/browser/facilitated_payments_manager.h

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

#ifndef COMPONENTS_FACILITATED_PAYMENTS_CORE_BROWSER_FACILITATED_PAYMENTS_MANAGER_H_
#define COMPONENTS_FACILITATED_PAYMENTS_CORE_BROWSER_FACILITATED_PAYMENTS_MANAGER_H_

#include <cstring>
#include <memory>
#include <vector>

#include "base/functional/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/types/expected.h"
#include "components/autofill/core/browser/payments/payments_autofill_client.h"
#include "components/facilitated_payments/core/browser/facilitated_payments_api_client.h"
#include "components/facilitated_payments/core/browser/facilitated_payments_driver.h"
#include "components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_request_details.h"
#include "components/facilitated_payments/core/browser/network_api/facilitated_payments_initiate_payment_response_details.h"
#include "components/facilitated_payments/core/metrics/facilitated_payments_metrics.h"
#include "components/facilitated_payments/core/mojom/facilitated_payments_agent.mojom.h"
#include "components/optimization_guide/core/optimization_guide_decider.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"

class GURL;

namespace payments::facilitated {

class FacilitatedPaymentsClient;
class FacilitatedPaymentsDriver;

// A cross-platform interface that manages the flow of payments for non-form
// based form-of-payments between the browser and the Payments platform. It is
// owned by `FacilitatedPaymentsDriver`.
class FacilitatedPaymentsManager {
 public:
  FacilitatedPaymentsManager(
      FacilitatedPaymentsDriver* driver,
      FacilitatedPaymentsClient* client,
      FacilitatedPaymentsApiClientCreator api_client_creator,
      optimization_guide::OptimizationGuideDecider* optimization_guide_decider);
  FacilitatedPaymentsManager(const FacilitatedPaymentsManager&) = delete;
  FacilitatedPaymentsManager& operator=(const FacilitatedPaymentsManager&) =
      delete;
  virtual ~FacilitatedPaymentsManager();

  // Resets `this` to initial state. Cancels any alive async callbacks.
  void Reset();

  // Initiates the PIX payments flow on the browser. There are 2 steps involved:
  // 1. Query the allowlist to check if PIX code detection should be run on the
  // page. It is possible that the infrastructure that supports querying the
  // allowlist is not ready when the page loads. In this case, we query again
  // after `kOptimizationGuideDeciderWaitTime`, and repeat
  // `kMaxAttemptsForAllowlistCheck` times. If the infrastructure is still not
  // ready, we do not run PIX code detection. `attempt_number` is an internal
  // counter for the number of attempts at querying.
  // 2. Trigger PIX code detection on the page after `kPageLoadWaitTime`. The
  // delay allows async content to load on the page. It also prevents PIX code
  // detection negatively impacting page load performance.
  void DelayedCheckAllowlistAndTriggerPixCodeDetection(
      const GURL& url,
      ukm::SourceId ukm_source_id,
      int attempt_number = 1);

  // Checks whether the `render_frame_host_url` is allowlisted and validates the
  // `pix_code` before trigger the Pix payments flow. Note: If the Pix payment
  // flow has already been triggered by the other code detection methods like
  // DOM search then this method is a no-op.
  void OnPixCodeCopiedToClipboard(const GURL& render_frame_host_url,
                                  const std::string& pix_code,
                                  ukm::SourceId ukm_source_id);

 private:
  // Defined here so they can be accessed by the tests.
  static constexpr base::TimeDelta kOptimizationGuideDeciderWaitTime =
      base::Seconds(0.5);
  static constexpr int kMaxAttemptsForAllowlistCheck = 6;
  static constexpr base::TimeDelta kPageLoadWaitTime = base::Seconds(1);
  static constexpr base::TimeDelta kRetriggerPixCodeDetectionWaitTime =
      base::Seconds(3);
  static constexpr int kMaxAttemptsForPixCodeDetection = 15;

  friend class FacilitatedPaymentsManagerTest;
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           RegisterPixAllowlist);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      DOMSearch_CheckAllowlistResultUnknown_PixCodeDetectionNotTriggered);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      DOMSearch_CheckAllowlistResultShortDelay_UrlInAllowlist_PixCodeDetectionTriggered);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      DOMSearch_CheckAllowlistResultShortDelay_UrlNotInAllowlist_PixCodeDetectionNotTriggered);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      DOMSearch_CheckAllowlistResultLongDelay_UrlInAllowlist_PixCodeDetectionNotTriggered);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           NoPixCode_PixCodeNotFoundLoggedAfterMaxAttempts);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           NoPixCode_PixCodeNotFoundLoggedAfterMaxAttempts);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest, NoPixCode_NoUkm);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTestWhenPixCodeExists,
      LongPageLoadDelay_PixCodeNotFoundLoggedAfterMaxAttempts);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTestWhenPixCodeExists,
      LongPageLoadDelay_PixCodeNotFoundLoggedAfterMaxAttempts);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTestWhenPixCodeExists,
                           Ukm);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           NoPixPaymentPromptWhenApiClientNotAvailable);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           ShowsPixPaymentPromptWhenApiClientAvailable);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           ShowsPixPaymentPrompt_HistogramLogged);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      ApiClientNotAvailable_RiskDataNotLoaded_DoesNotTriggerLoadRiskData);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      ApiClientAvailable_RiskDataNotLoaded_TriggersLoadRiskData);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           RiskDataNotEmpty_HistogramsLogged);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           RiskDataEmpty_HistogramsLogged);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      ApiClientAvailable_RiskDataLoaded_DoesNotTriggerLoadRiskData);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           PaymentNotOfferedReason_RiskDataEmpty);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      DoesNotRetrieveClientTokenIfPixPaymentPromptRejected);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           RetrievesClientTokenIfPixPaymentPromptAccepted);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           GetClientTokenHistogram_ClientTokenNotEmpty);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           GetClientTokenHistogram_ClientTokenEmpty);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           PixPaymentPromptAccepted_ProgressSceenShown);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           PixPaymentPromptRejected_ProgressSceenNotShown);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           OnGetClientToken_ClientTokenEmpty_ErrorScreenShown);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      TriggerPixDetectionOnDomContentLoadedExpDisabled_Ukm);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           TriggerPixDetectionOnDomContentLoadedExpEnabled_Ukm);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           ResettingPreventsPayment);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           CopyTrigger_UrlInAllowlist_PixValidationTriggered);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      CopyTrigger_UrlNotInAllowlist_PixValidationNotTriggered);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      CopyTriggerHappenedBeforeDOMSearch_ApiClientIsAvailableCalledOnlyOnce);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      DOMSearchHappenedBeforeCopyTrigger_ApiClientIsAvailableCalledOnlyOnce);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      ValidPixCodeDetectionResult_HasPixAccounts_ApiClientTriggered);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      ValidPixCodeDetectionResult_InvalidPixCodeString_ApiClientNotTriggered);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      InvalidPixCodeDetectionResultDoesNotTriggerApiClient);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           PixPrefTurnedOff_NoApiClientTriggered);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           NoPixAccounts_NoApiClientTriggered);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           NoPaymentsDataManager_NoApiClientTriggered);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           ValidPixDetectionResultToPixPaymentPromptShown);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           ApiClientTriggeredAfterPixCodeValidation);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           PaymentNotOfferedReason_CodeValidatorReturnsFalse);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           PixCodeValidationFailed_NoApiClientTriggered);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           PaymentNotOfferedReason_CodeValidatorFailed);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           ApiAvailabilityHistogram);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      PixCodeValidatorTerminatedUnexpectedly_NoApiClientTriggered);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           PaymentNotOfferedReason_ApiNotAvailable);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           SendInitiatePaymentRequest);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      OnInitiatePaymentResponseReceived_FailureResponse_ErrorScreenShown);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      OnInitiatePaymentResponseReceived_NoActionToken_ErrorScreenShown);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      OnInitiatePaymentResponseReceived_NoCoreAccountInfo_ErrorScreenShown);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      OnInitiatePaymentResponseReceived_LoggedOutProfile_ErrorScreenShown);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      OnInitiatePaymentResponseReceived_InvokePurchaseActionTriggered);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           OnPurchaseActionPositiveResult_UiPromptDismissed);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           OnPurchaseActionNegativeResult_UiPromptDismissed);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           InvokePurchaseActionCompleted_HistogramLogged);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           OnInitiatePaymentResponseReceived_HistogramLogged);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           TransactionSuccess_HistogramLogged);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      TransactionAbandonedAfterInvokePurchaseAction_HistogramLogged);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      TransactionFailedAfterInvokePurchaseAction_HistogramLogged);
  FRIEND_TEST_ALL_PREFIXES(
      FacilitatedPaymentsManagerTest,
      FOPSelectorNotShown_TransactionResultHistogramNotLogged);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           ApiClientInitializedLazily);
  FRIEND_TEST_ALL_PREFIXES(FacilitatedPaymentsManagerTest,
                           HandlesFailureToLazilyInitializeApiClient);

  // Register optimization guide deciders for PIX. It is an allowlist of URLs
  // where we attempt PIX code detection.
  void RegisterPixAllowlist() const;

  // Queries the allowlist for the `url`. The result could be:
  // 1. In the allowlist
  // 2. Not in the allowlist
  // 3. Infra for querying is not ready
  optimization_guide::OptimizationGuideDecision GetAllowlistCheckResult(
      const GURL& url) const;

  // Calls `TriggerPixCodeDetection` after `delay`.
  void DelayedTriggerPixCodeDetection(base::TimeDelta delay);

  // Asks the renderer to scan the document for a PIX code. The call is made via
  // the `driver_`.
  void TriggerPixCodeDetection();

  // Callback to be called after attempting PIX code detection. `result`
  // represents the result of the document scan.
  void ProcessPixCodeDetectionResult(mojom::PixCodeDetectionResult result,
                                     const std::string& pix_code);

  // Called by the utility process after validation of the `pix_code`. If the
  // utility processes has disconnected (e.g., due to a crash in the validation
  // code), then `is_pix_code_valid` contains an error string instead of the
  // boolean validation result.
  void OnPixCodeValidated(std::string pix_code,
                          base::expected<bool, std::string> is_pix_code_valid);

  // Lazily initializes an API client and returns a pointer to it. Returns a
  // pointer to the existing API client, if one is already initialized. The
  // FacilitatedPaymentManager owns this API client. This method can return
  // `nullptr` if the API client fails to initialize, e.g., if the
  // `RenderFrameHost` has been destroyed.
  FacilitatedPaymentsApiClient* GetApiClient();

  // Starts `pix_code_detection_latency_measuring_timestamp_`.
  void StartPixCodeDetectionLatencyTimer();

  int64_t GetPixCodeDetectionLatencyInMillis() const;

  // Called after checking whether the facilitated payment API is available. If
  // the API is not available, the user should not be prompted to make a
  // payment.
  void OnApiAvailabilityReceived(bool is_api_available);

  // Invoked when risk data is fetched. The call to fetch the risk data was made
  // at `start_time`.
  void OnRiskDataLoaded(base::TimeTicks start_time,
                        const std::string& risk_data);

  // Called after showing the PIX the payment prompt.
  void OnPixPaymentPromptResult(bool is_prompt_accepted,
                                int64_t selected_instrument_id);

  // Called after retrieving the client token from the facilitated payment API.
  // If not empty, the client token can be used for initiating payment.
  void OnGetClientToken(std::vector<uint8_t> client_token);

  // Makes a payment request to the Payments server after the user has selected
  // the account for making the payment.
  void SendInitiatePaymentRequest();

  // Called after receiving the `result` of the initiate payment call. The
  // `response_details` contains the action token used for payment.
  void OnInitiatePaymentResponseReceived(
      autofill::payments::PaymentsAutofillClient::PaymentsRpcResult result,
      std::unique_ptr<FacilitatedPaymentsInitiatePaymentResponseDetails>
          response_details);

  // Called after receiving the `result` of invoking the purchase manager for
  // payment.
  void OnPurchaseActionResult(
      FacilitatedPaymentsApiClient::PurchaseActionResult result);

  // Calling `Reset` has no effect in tests. Adding this method to specifically
  // test `Resets` in tests.
  void ResetForTesting();

  // Owner.
  const raw_ref<FacilitatedPaymentsDriver> driver_;

  // Indirect owner.
  const raw_ref<FacilitatedPaymentsClient> client_;

  // The creator of the facilitated payment API client.
  FacilitatedPaymentsApiClientCreator api_client_creator_;

  // The client for the facilitated payment API.
  std::unique_ptr<FacilitatedPaymentsApiClient> api_client_;

  // The optimization guide decider to help determine whether the current main
  // frame URL is eligible for facilitated payments.
  raw_ptr<optimization_guide::OptimizationGuideDecider>
      optimization_guide_decider_ = nullptr;

  ukm::SourceId ukm_source_id_;

  // Counter for the number of attempts at PIX code detection.
  int pix_code_detection_attempt_count_ = 0;

  // Scheduler. Used for check allowlist retries, PIX code detection retries,
  // page load wait, etc.
  base::OneShotTimer pix_code_detection_triggering_timer_;

  // Measures the time taken to scan the document for the PIX code.
  base::TimeTicks pix_code_detection_latency_measuring_timestamp_;

  // Measures the time taken to check the availability of the facilitated
  // payments API client.
  base::TimeTicks api_availability_check_start_time_;

  // Measures the time take to load the client token from the facilitated
  // payments API client.
  base::TimeTicks get_client_token_loading_start_time_;

  // Measures the time take to complete the call to the InitiatePayment backend
  // api.
  base::TimeTicks initiate_payment_network_start_time_;

  // Measures the time take to complete the purchase action.
  base::TimeTicks purchase_action_start_time_;

  // Stores the time when the FOP selector was shown to the user. This is used
  // to calculate the entire transaction latency.
  base::TimeTicks fop_selector_shown_time_;

  // Contains the details required for the `InitiatePayment` request to be sent
  // to the Payments server. Its ownership is transferred to
  // `FacilitatedPaymentsInitiatePaymentRequest` in
  // `SendInitiatePaymentRequest`. `Reset` destroys the existing instance, and
  // creates a new instance.
  std::unique_ptr<FacilitatedPaymentsInitiatePaymentRequestDetails>
      initiate_payment_request_details_;

  // Flag to help determine whether the payflow has been triggered since the
  // last page load. It protects against multiple triggers to initiate the
  // payflow like Pix code detection, copy button click, and copy button
  // double-click.
  bool has_payflow_started_ = false;

  // Informs whether this instance was created in a test.
  bool is_test_ = false;

  // Utility process validator for PIX code strings.
  data_decoder::DataDecoder utility_process_validator_;

  // The source of the trigger for the facilitated payments form of payment(FOP)
  // selector to show up. It is used for logging purposes. It is set whenever a
  // trigger occurs and reset if the FOP selector is not shown for some reason.
  TriggerSource trigger_source_ = TriggerSource::kUnknown;

  base::WeakPtrFactory<FacilitatedPaymentsManager> weak_ptr_factory_{this};
};

}  // namespace payments::facilitated

#endif  // COMPONENTS_FACILITATED_PAYMENTS_CORE_BROWSER_FACILITATED_PAYMENTS_MANAGER_H_