blob: da67cc35bd94a673f868e120d93a9c95c3e68a1c [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/cart/fetch_discount_worker.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/cart/cart_discount_fetcher.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/commerce/core/proto/coupon_db_content.pb.h"
#include "components/search/ntp_features.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/variations/variations.mojom.h"
#include "components/variations/variations_client.h"
#include "components/variations/variations_features.h"
#include "components/variations/variations_ids_provider.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/gaia/gaia_constants.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "third_party/re2/src/re2/re2.h"
namespace {
const char kOauthName[] = "rbd";
const char kOauthScopes[] = "https://www.googleapis.com/auth/chromememex";
const char kEmptyToken[] = "";
} // namespace
CartDiscountServiceDelegate::CartDiscountServiceDelegate(
CartService* cart_service)
: cart_service_(cart_service) {}
CartDiscountServiceDelegate::~CartDiscountServiceDelegate() = default;
void CartDiscountServiceDelegate::LoadAllCarts(CartDB::LoadCallback callback) {
cart_service_->LoadAllActiveCarts(std::move(callback));
}
void CartDiscountServiceDelegate::UpdateCart(
const std::string& cart_url,
const cart_db::ChromeCartContentProto new_proto,
const bool is_tester) {
cart_service_->UpdateDiscounts(GURL(cart_url), std::move(new_proto),
is_tester);
}
void CartDiscountServiceDelegate::RecordFetchTimestamp() {
cart_service_->RecordFetchTimestamp();
}
void CartDiscountServiceDelegate::UpdateFreeListingCoupons(
const CouponService::CouponsMap& map) {
cart_service_->UpdateFreeListingCoupons(map);
}
FetchDiscountWorker::FetchDiscountWorker(
scoped_refptr<network::SharedURLLoaderFactory>
browserProcessURLLoaderFactory,
std::unique_ptr<CartDiscountFetcherFactory> fetcher_factory,
std::unique_ptr<CartDiscountServiceDelegate> cart_discount_service_delegate,
signin::IdentityManager* const identity_manager,
variations::VariationsClient* const chrome_variations_client)
: browserProcessURLLoaderFactory_(browserProcessURLLoaderFactory),
fetcher_factory_(std::move(fetcher_factory)),
cart_discount_service_delegate_(
std::move(cart_discount_service_delegate)),
identity_manager_(identity_manager),
chrome_variations_client_(chrome_variations_client) {
backend_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::BEST_EFFORT});
}
FetchDiscountWorker::~FetchDiscountWorker() = default;
void FetchDiscountWorker::Start(base::TimeDelta delay) {
content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
->PostDelayedTask(FROM_HERE,
base::BindOnce(&FetchDiscountWorker::PrepareToFetch,
weak_ptr_factory_.GetWeakPtr()),
delay);
}
void FetchDiscountWorker::PrepareToFetch() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
if (identity_manager_ &&
identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
FetchOauthToken();
} else {
LoadAllActiveCarts(/*is_oauth_fetch*/ false, kEmptyToken);
}
}
void FetchDiscountWorker::FetchOauthToken() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK(!access_token_fetcher_);
signin::AccessTokenFetcher::TokenCallback token_callback = base::BindOnce(
&FetchDiscountWorker::OnAuthTokenFetched, weak_ptr_factory_.GetWeakPtr());
access_token_fetcher_ =
std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
kOauthName, identity_manager_, signin::ScopeSet{kOauthScopes},
std::move(token_callback),
signin::PrimaryAccountAccessTokenFetcher::Mode::kImmediate);
}
void FetchDiscountWorker::OnAuthTokenFetched(
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
access_token_fetcher_.reset();
if (error.state() != GoogleServiceAuthError::NONE) {
VLOG(3) << "There was an authentication error: " << error.state();
return;
}
LoadAllActiveCarts(/*is_oauth_fetch*/ true, access_token_info.token);
}
void FetchDiscountWorker::LoadAllActiveCarts(
const bool is_oauth_fetch,
const std::string access_token_str) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
auto cart_loaded_callback = base::BindOnce(
&FetchDiscountWorker::ReadyToFetch, weak_ptr_factory_.GetWeakPtr(),
is_oauth_fetch, std::move(access_token_str));
cart_discount_service_delegate_->LoadAllCarts(
std::move(cart_loaded_callback));
}
void FetchDiscountWorker::ReadyToFetch(
const bool is_oauth_fetch,
const std::string access_token_str,
bool success,
std::vector<CartDB::KeyAndValue> proto_pairs) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
auto pending_factory = browserProcessURLLoaderFactory_->Clone();
auto fetcher = fetcher_factory_->createFetcher();
auto done_fetching_callback =
base::BindOnce(&FetchDiscountWorker::AfterDiscountFetched,
weak_ptr_factory_.GetWeakPtr());
cart_discount_service_delegate_->RecordFetchTimestamp();
// If there is no eligible merchant cart, don't fetch immediately; instead,
// post another delayed fetch.
bool has_partner_merchant = false;
bool has_potential_merchant = false;
for (auto pair : proto_pairs) {
auto cart_url = pair.second.merchant_cart_url();
bool is_partner_merchant = commerce::IsPartnerMerchant(GURL(cart_url));
bool is_potential_merchant =
base::FeatureList::IsEnabled(commerce::kMerchantWidePromotion) &&
!commerce::IsNoDiscountMerchant(GURL(cart_url));
has_partner_merchant |= is_partner_merchant;
has_potential_merchant |= is_potential_merchant;
}
bool allow_to_fetch = base::GetFieldTrialParamByFeatureAsBool(
commerce::kMerchantWidePromotion,
commerce::kReadyToFetchMerchantWidePromotionParam, true);
if (has_partner_merchant || (has_potential_merchant && allow_to_fetch)) {
backend_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&FetchInBackground, std::move(pending_factory), std::move(fetcher),
std::move(done_fetching_callback), std::move(proto_pairs),
is_oauth_fetch, std::move(access_token_str),
g_browser_process->GetApplicationLocale(), GetVariationsHeaders()));
} else {
Start(commerce::GetDiscountFetchDelay());
}
}
std::string FetchDiscountWorker::GetVariationsHeaders() {
if (!chrome_variations_client_) {
return "";
}
variations::mojom::VariationsHeadersPtr variations_headers =
chrome_variations_client_->GetVariationsHeaders();
if (variations_headers.is_null()) {
return "";
}
return variations_headers->headers_map.at(
variations::mojom::GoogleWebVisibility::FIRST_PARTY);
}
void FetchDiscountWorker::FetchInBackground(
std::unique_ptr<network::PendingSharedURLLoaderFactory> pending_factory,
std::unique_ptr<CartDiscountFetcher> fetcher,
AfterFetchingCallback after_fetching_callback,
std::vector<CartDB::KeyAndValue> proto_pairs,
const bool is_oauth_fetch,
const std::string access_token_str,
const std::string fetch_for_locale,
const std::string variation_headers) {
DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
auto done_fetching_callback = base::BindOnce(
&DoneFetchingInBackground, std::move(after_fetching_callback));
fetcher->Fetch(std::move(pending_factory), std::move(done_fetching_callback),
std::move(proto_pairs), is_oauth_fetch,
std::move(access_token_str), std::move(fetch_for_locale),
std::move(variation_headers));
}
// TODO(meiliang): Follow up to use BindPostTask.
void FetchDiscountWorker::DoneFetchingInBackground(
AfterFetchingCallback after_fetching_callback,
CartDiscountFetcher::CartDiscountMap discounts,
bool is_tester) {
DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
->PostTask(FROM_HERE,
base::BindOnce(
[](AfterFetchingCallback callback,
CartDiscountFetcher::CartDiscountMap map, bool tester) {
std::move(callback).Run(std::move(map), tester);
},
std::move(after_fetching_callback), std::move(discounts),
is_tester));
}
void FetchDiscountWorker::AfterDiscountFetched(
CartDiscountFetcher::CartDiscountMap discounts,
bool is_tester) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
auto update_discount_callback = base::BindOnce(
&FetchDiscountWorker::OnUpdatingDiscounts, weak_ptr_factory_.GetWeakPtr(),
std::move(discounts), is_tester);
cart_discount_service_delegate_->LoadAllCarts(
std::move(update_discount_callback));
}
void FetchDiscountWorker::OnUpdatingDiscounts(
CartDiscountFetcher::CartDiscountMap discounts,
bool is_tester,
bool success,
std::vector<CartDB::KeyAndValue> proto_pairs) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
if (!success || !proto_pairs.size()) {
return;
}
double current_timestamp = base::Time::Now().ToDoubleT();
base::flat_map<GURL,
std::vector<std::unique_ptr<autofill::AutofillOfferData>>>
coupon_map;
for (CartDB::KeyAndValue& key_and_value : proto_pairs) {
cart_db::ChromeCartContentProto cart_proto = key_and_value.second;
std::string cart_url_str = cart_proto.merchant_cart_url();
GURL cart_url_origin = GURL(cart_url_str).DeprecatedGetOriginAsURL();
cart_db::ChromeCartDiscountProto* cart_discount_proto =
cart_proto.mutable_discount_info();
cart_discount_proto->set_last_fetched_timestamp(current_timestamp);
if (!discounts.count(cart_url_str)) {
cart_discount_proto->clear_discount_text();
cart_discount_proto->clear_rule_discount_info();
cart_discount_proto->clear_has_coupons();
cart_discount_service_delegate_->UpdateCart(
cart_url_str, std::move(cart_proto), is_tester);
continue;
}
const MerchantIdAndDiscounts& merchant_discounts =
discounts.at(cart_url_str);
std::string merchant_id = merchant_discounts.merchant_id;
cart_discount_proto->set_merchant_id(merchant_id);
const std::vector<cart_db::RuleDiscountInfoProto>& discount_infos =
merchant_discounts.rule_discounts;
cart_discount_proto->set_discount_text(
merchant_discounts.highest_discount_string);
*cart_discount_proto->mutable_rule_discount_info() = {
discount_infos.begin(), discount_infos.end()};
cart_discount_proto->set_has_coupons(merchant_discounts.has_coupons);
cart_discount_service_delegate_->UpdateCart(
cart_url_str, std::move(cart_proto), is_tester);
if (commerce::IsCouponWithCodeEnabled()) {
for (const coupon_db::FreeListingCouponInfoProto& coupon_info :
merchant_discounts.coupon_discounts) {
int64_t offer_id = coupon_info.coupon_id();
base::Time expiry = base::Time::FromDoubleT(coupon_info.expiry_time());
std::vector<GURL> merchant_origins;
merchant_origins.emplace_back(cart_url_origin);
GURL offer_details_url = GURL();
autofill::DisplayStrings display_strings;
display_strings.value_prop_text = coupon_info.coupon_description();
std::string promo_code = coupon_info.coupon_code();
auto offer = std::make_unique<autofill::AutofillOfferData>(
autofill::AutofillOfferData::FreeListingCouponOffer(
offer_id, expiry, merchant_origins, offer_details_url,
display_strings, promo_code));
coupon_map[cart_url_origin].emplace_back(std::move(offer));
}
}
}
if (commerce::IsCouponWithCodeEnabled()) {
cart_discount_service_delegate_->UpdateFreeListingCoupons(coupon_map);
}
if (commerce::IsCartDiscountFeatureEnabled()) {
// Continue to work.
Start(commerce::GetDiscountFetchDelay());
}
}