blob: 94a4d6f62b5393adc20b2e676637fd8b4da98c7a [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview 'settings-payments-section' is the section containing saved
* credit cards for use in autofill and payments APIs.
*/
import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.m.js';
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
import 'chrome://resources/cr_elements/shared_style_css.m.js';
import 'chrome://resources/cr_elements/shared_vars_css.m.js';
import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
import '../settings_shared_css.js';
import '../controls/settings_toggle_button.js';
import '../prefs/prefs.js';
import './credit_card_edit_dialog.js';
import './passwords_shared_css.js';
import './payments_list.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js';
import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {loadTimeData} from '../i18n_setup.js';
import {MetricsBrowserProxyImpl, PrivacyElementInteractions} from '../metrics_browser_proxy.js';
import {AutofillManager} from './autofill_section.js';
/** @typedef {chrome.autofillPrivate.CreditCardEntry} */
let CreditCardEntry;
/**
* Interface for all callbacks to the payments autofill API.
* @interface
*/
export class PaymentsManager {
/**
* Add an observer to the list of personal data.
* @param {function(!Array<!AutofillManager.AddressEntry>,
* !Array<!CreditCardEntry>):void} listener
*/
setPersonalDataManagerListener(listener) {}
/**
* Remove an observer from the list of personal data.
* @param {function(!Array<!AutofillManager.AddressEntry>,
* !Array<!CreditCardEntry>):void} listener
*/
removePersonalDataManagerListener(listener) {}
/**
* Request the list of credit cards.
* @param {function(!Array<!CreditCardEntry>):void}
* callback
*/
getCreditCardList(callback) {}
/** @param {string} guid The GUID of the credit card to remove. */
removeCreditCard(guid) {}
/**
* @param {string} guid The GUID to credit card to remove from the cache.
*/
clearCachedCreditCard(guid) {}
/**
* Saves the given credit card.
* @param {!CreditCardEntry} creditCard
*/
saveCreditCard(creditCard) {}
/**
* Migrate the local credit cards.
*/
migrateCreditCards() {}
/**
* Logs that the server cards edit link was clicked.
*/
logServerCardLinkClicked() {}
/**
* Enables FIDO authentication for card unmasking.
*/
setCreditCardFIDOAuthEnabledState(enabled) {}
/**
* Requests the list of UPI IDs from personal data.
* @param {function(!Array<!string>):void} callback
*/
getUpiIdList(callback) {}
}
/**
* Implementation that accesses the private API.
* @implements {PaymentsManager}
*/
export class PaymentsManagerImpl {
/** @override */
setPersonalDataManagerListener(listener) {
chrome.autofillPrivate.onPersonalDataChanged.addListener(listener);
}
/** @override */
removePersonalDataManagerListener(listener) {
chrome.autofillPrivate.onPersonalDataChanged.removeListener(listener);
}
/** @override */
getCreditCardList(callback) {
chrome.autofillPrivate.getCreditCardList(callback);
}
/** @override */
removeCreditCard(guid) {
chrome.autofillPrivate.removeEntry(assert(guid));
}
/** @override */
clearCachedCreditCard(guid) {
chrome.autofillPrivate.maskCreditCard(assert(guid));
}
/** @override */
saveCreditCard(creditCard) {
chrome.autofillPrivate.saveCreditCard(creditCard);
}
/** @override */
migrateCreditCards() {
chrome.autofillPrivate.migrateCreditCards();
}
/** @override */
logServerCardLinkClicked() {
chrome.autofillPrivate.logServerCardLinkClicked();
}
/** @override */
setCreditCardFIDOAuthEnabledState(enabled) {
chrome.autofillPrivate.setCreditCardFIDOAuthEnabledState(enabled);
}
/** @override */
getUpiIdList(callback) {
chrome.autofillPrivate.getUpiIdList(callback);
}
}
addSingletonGetter(PaymentsManagerImpl);
Polymer({
is: 'settings-payments-section',
_template: html`{__html_template__}`,
behaviors: [
WebUIListenerBehavior,
I18nBehavior,
],
properties: {
/**
* An array of all saved credit cards.
* @type {!Array<!CreditCardEntry>}
*/
creditCards: {
type: Array,
value: () => [],
},
/**
* An array of all saved UPI IDs.
* @type {!Array<!string>}
*/
upiIds: {
type: Array,
value: () => [],
},
/**
* Set to true if user can be verified through FIDO authentication.
* @private
*/
userIsFidoVerifiable_: {
type: Boolean,
value() {
return loadTimeData.getBoolean(
'fidoAuthenticationAvailableForAutofill');
},
},
/**
* The model for any credit card related action menus or dialogs.
* @private {?chrome.autofillPrivate.CreditCardEntry}
*/
activeCreditCard: Object,
/** @private */
showCreditCardDialog_: Boolean,
/** @private */
migratableCreditCardsInfo_: String,
/**
* Whether migration local card on settings page is enabled.
* @private
*/
migrationEnabled_: {
type: Boolean,
value() {
return loadTimeData.getBoolean('migrationEnabled');
},
readOnly: true,
},
},
listeners: {
'save-credit-card': 'saveCreditCard_',
'dots-card-menu-click': 'onCreditCardDotsMenuTap_',
'remote-card-menu-click': 'onRemoteEditCreditCardTap_',
},
/**
* The element to return focus to, when the currently active dialog is
* closed.
* @private {?HTMLElement}
*/
activeDialogAnchor_: null,
/**
* @type {PaymentsManager}
* @private
*/
PaymentsManager_: null,
/**
* @type {?function(!Array<!AutofillManager.AddressEntry>,
* !Array<!CreditCardEntry>)}
* @private
*/
setPersonalDataListener_: null,
/** @override */
attached() {
// Create listener function.
/** @type {function(!Array<!CreditCardEntry>)} */
const setCreditCardsListener = cardList => {
this.creditCards = cardList;
};
// Update |userIsFidoVerifiable_| based on the availability of a platform
// authenticator.
if (window.PublicKeyCredential) {
window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
.then(r => {
this.userIsFidoVerifiable_ = this.userIsFidoVerifiable_ && r;
});
}
/**
* @type {function(!Array<!AutofillManager.AddressEntry>,
* !Array<!CreditCardEntry>)}
*/
const setPersonalDataListener = (addressList, cardList) => {
this.creditCards = cardList;
};
/** @type {function(!Array<!string>)} */
const setUpiIdsListener = upiIdList => {
this.upiIds = upiIdList;
};
// Remember the bound reference in order to detach.
this.setPersonalDataListener_ = setPersonalDataListener;
// Set the managers. These can be overridden by tests.
this.paymentsManager_ = PaymentsManagerImpl.getInstance();
// Request initial data.
this.paymentsManager_.getCreditCardList(setCreditCardsListener);
this.paymentsManager_.getUpiIdList(setUpiIdsListener);
// Listen for changes.
this.paymentsManager_.setPersonalDataManagerListener(
setPersonalDataListener);
// Record that the user opened the payments settings.
chrome.metricsPrivate.recordUserAction('AutofillCreditCardsViewed');
},
/** @override */
detached() {
this.paymentsManager_.removePersonalDataManagerListener(
/**
@type {function(!Array<!AutofillManager.AddressEntry>,
!Array<!CreditCardEntry>)}
*/
(this.setPersonalDataListener_));
},
/**
* Opens the credit card action menu.
* @param {!CustomEvent<{creditCard:
* !chrome.autofillPrivate.CreditCardEntry, anchorElement:
* !HTMLElement}>} e
* @private
*/
onCreditCardDotsMenuTap_(e) {
// Copy item so dialog won't update model on cancel.
this.activeCreditCard = e.detail.creditCard;
/** @type {!CrActionMenuElement} */ (this.$.creditCardSharedMenu)
.showAt(e.detail.anchorElement);
this.activeDialogAnchor_ = e.detail.anchorElement;
},
/**
* Handles tapping on the "Add credit card" button.
* @param {!Event} e
* @private
*/
onAddCreditCardTap_(e) {
e.preventDefault();
const date = new Date(); // Default to current month/year.
const expirationMonth = date.getMonth() + 1; // Months are 0 based.
this.activeCreditCard = {
expirationMonth: expirationMonth.toString(),
expirationYear: date.getFullYear().toString(),
};
this.showCreditCardDialog_ = true;
this.activeDialogAnchor_ =
/** @type {HTMLElement} */ (this.$.addCreditCard);
},
/** @private */
onCreditCardDialogClose_() {
this.showCreditCardDialog_ = false;
focusWithoutInk(assert(this.activeDialogAnchor_));
this.activeDialogAnchor_ = null;
this.activeCreditCard = null;
},
/**
* Handles tapping on the "Edit" credit card button.
* @param {!Event} e The polymer event.
* @private
*/
onMenuEditCreditCardTap_(e) {
e.preventDefault();
if (this.activeCreditCard.metadata.isLocal) {
this.showCreditCardDialog_ = true;
} else {
this.onRemoteEditCreditCardTap_();
}
this.$.creditCardSharedMenu.close();
},
/** @private */
onRemoteEditCreditCardTap_() {
this.paymentsManager_.logServerCardLinkClicked();
window.open(loadTimeData.getString('manageCreditCardsUrl'));
},
/**
* Handles tapping on the "Remove" credit card button.
* @private
*/
onMenuRemoveCreditCardTap_() {
this.paymentsManager_.removeCreditCard(
/** @type {string} */ (this.activeCreditCard.guid));
this.$.creditCardSharedMenu.close();
this.activeCreditCard = null;
},
/**
* Handles tapping on the "Clear copy" button for cached credit cards.
* @private
*/
onMenuClearCreditCardTap_() {
this.paymentsManager_.clearCachedCreditCard(
/** @type {string} */ (this.activeCreditCard.guid));
this.$.creditCardSharedMenu.close();
this.activeCreditCard = null;
},
/**
* Handles clicking on the "Migrate" button for migrate local credit
* cards.
* @private
*/
onMigrateCreditCardsClick_() {
this.paymentsManager_.migrateCreditCards();
},
/**
* Records changes made to the "Allow sites to check if you have payment
* methods saved" setting to a histogram.
* @private
*/
onCanMakePaymentChange_() {
MetricsBrowserProxyImpl.getInstance().recordSettingsPageHistogram(
PrivacyElementInteractions.PAYMENT_METHOD);
},
/**
* Listens for the save-credit-card event, and calls the private API.
* @param {!Event} event
* @private
*/
saveCreditCard_(event) {
this.paymentsManager_.saveCreditCard(event.detail);
},
/**
* @param {boolean} creditCardEnabled
* @return {boolean} Whether or not the user is verifiable through FIDO
* authentication.
* @private
*/
shouldShowFidoToggle_(creditCardEnabled, userIsFidoVerifiable) {
return creditCardEnabled && userIsFidoVerifiable;
},
/**
* Listens for the enable-authentication event, and calls the private API.
* @private
*/
setFIDOAuthenticationEnabledState_() {
this.paymentsManager_.setCreditCardFIDOAuthEnabledState(
this.$$('#autofillCreditCardFIDOAuthToggle').checked);
},
/**
* @param {!Array<!CreditCardEntry>} creditCards
* @param {boolean} creditCardEnabled
* @return {boolean} Whether to show the migration button.
* @private
*/
checkIfMigratable_(creditCards, creditCardEnabled) {
// If migration prerequisites are not met, return false.
if (!this.migrationEnabled_) {
return false;
}
// If credit card enabled pref is false, return false.
if (!creditCardEnabled) {
return false;
}
const numberOfMigratableCreditCard =
creditCards.filter(card => card.metadata.isMigratable).length;
// Check whether exist at least one local valid card for migration.
if (numberOfMigratableCreditCard === 0) {
return false;
}
// Update the display text depends on the number of migratable credit
// cards.
this.migratableCreditCardsInfo_ = numberOfMigratableCreditCard === 1 ?
this.i18n('migratableCardsInfoSingle') :
this.i18n('migratableCardsInfoMultiple');
return true;
},
});