forked from mirrors/gecko-dev
Bug 1470199 - Add a tooltip for the CVV input. r=MattN
Differential Revision: https://phabricator.services.mozilla.com/D7473 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
6309162ed8
commit
4a3b48e09b
17 changed files with 277 additions and 46 deletions
103
browser/components/payments/res/components/csc-input.js
Normal file
103
browser/components/payments/res/components/csc-input.js
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import ObservedPropertiesMixin from "../mixins/ObservedPropertiesMixin.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <csc-input placeholder="CVV*"
|
||||||
|
default-value="123"
|
||||||
|
front-tooltip="Look on front of card for CSC"
|
||||||
|
back-tooltip="Look on back of card for CSC"></csc-input>
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class CscInput extends ObservedPropertiesMixin(HTMLElement) {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return [
|
||||||
|
"back-tooltip",
|
||||||
|
"card-type",
|
||||||
|
"default-value",
|
||||||
|
"disabled",
|
||||||
|
"front-tooltip",
|
||||||
|
"placeholder",
|
||||||
|
"value",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
constructor({
|
||||||
|
useAlwaysVisiblePlaceholder,
|
||||||
|
inputId,
|
||||||
|
} = {}) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.useAlwaysVisiblePlaceholder = useAlwaysVisiblePlaceholder;
|
||||||
|
|
||||||
|
this._input = document.createElement("input");
|
||||||
|
this._input.id = inputId || "";
|
||||||
|
this._input.setAttribute("type", "text");
|
||||||
|
this._input.autocomplete = "off";
|
||||||
|
this._input.size = 3;
|
||||||
|
this._input.required = true;
|
||||||
|
// 3 or more digits
|
||||||
|
this._input.pattern = "[0-9]{3,}";
|
||||||
|
this._input.classList.add("security-code");
|
||||||
|
if (useAlwaysVisiblePlaceholder) {
|
||||||
|
this._label = document.createElement("span");
|
||||||
|
this._label.dataset.localization = "cardCVV";
|
||||||
|
this._label.className = "label-text";
|
||||||
|
}
|
||||||
|
this._tooltip = document.createElement("span");
|
||||||
|
this._tooltip.className = "info-tooltip csc";
|
||||||
|
this._tooltip.setAttribute("tabindex", "0");
|
||||||
|
this._tooltip.setAttribute("role", "tooltip");
|
||||||
|
|
||||||
|
// The parent connectedCallback calls its render method before
|
||||||
|
// our connectedCallback can run. This causes issues for parent
|
||||||
|
// code that is looking for all the form elements. Thus, we
|
||||||
|
// append the children during the constructor to make sure they
|
||||||
|
// be part of the DOM sooner.
|
||||||
|
this.appendChild(this._input);
|
||||||
|
if (this.useAlwaysVisiblePlaceholder) {
|
||||||
|
this.appendChild(this._label);
|
||||||
|
}
|
||||||
|
this.appendChild(this._tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.value) {
|
||||||
|
// Setting the value will trigger form validation
|
||||||
|
// so only set the value if one has been provided.
|
||||||
|
this._input.value = this.value;
|
||||||
|
}
|
||||||
|
if (this.useAlwaysVisiblePlaceholder) {
|
||||||
|
this._label.textContent = this.placeholder || "";
|
||||||
|
} else {
|
||||||
|
this._input.placeholder = this.placeholder || "";
|
||||||
|
}
|
||||||
|
if (this.cardType == "amex") {
|
||||||
|
this._tooltip.setAttribute("aria-label", this.frontTooltip || "");
|
||||||
|
} else {
|
||||||
|
this._tooltip.setAttribute("aria-label", this.backTooltip || "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get value() {
|
||||||
|
return this._input.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isValid() {
|
||||||
|
return this._input.validity.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
set disabled(value) {
|
||||||
|
// This is kept out of render() since callers
|
||||||
|
// are expecting it to apply immediately.
|
||||||
|
this._input.disabled = value;
|
||||||
|
return !!value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("csc-input", CscInput);
|
||||||
|
|
@ -12,6 +12,11 @@ basic-card-form .editCreditCardForm {
|
||||||
"billingAddressGUID billingAddressGUID billingAddressGUID";
|
"billingAddressGUID billingAddressGUID billingAddressGUID";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
basic-card-form csc-input {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
basic-card-form .editCreditCardForm > accepted-cards {
|
basic-card-form .editCreditCardForm > accepted-cards {
|
||||||
grid-area: accepted;
|
grid-area: accepted;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
/* import-globals-from ../../../../../browser/extensions/formautofill/content/autofillEditForms.js*/
|
/* import-globals-from ../../../../../browser/extensions/formautofill/content/autofillEditForms.js*/
|
||||||
import AcceptedCards from "../components/accepted-cards.js";
|
import AcceptedCards from "../components/accepted-cards.js";
|
||||||
|
import CscInput from "../components/csc-input.js";
|
||||||
import LabelledCheckbox from "../components/labelled-checkbox.js";
|
import LabelledCheckbox from "../components/labelled-checkbox.js";
|
||||||
import PaymentDialog from "./payment-dialog.js";
|
import PaymentDialog from "./payment-dialog.js";
|
||||||
import PaymentRequestPage from "../components/payment-request-page.js";
|
import PaymentRequestPage from "../components/payment-request-page.js";
|
||||||
|
|
@ -36,6 +37,11 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
this.addressEditLink.href = "javascript:void(0)";
|
this.addressEditLink.href = "javascript:void(0)";
|
||||||
this.addressEditLink.addEventListener("click", this);
|
this.addressEditLink.addEventListener("click", this);
|
||||||
|
|
||||||
|
this.cscInput = new CscInput({
|
||||||
|
useAlwaysVisiblePlaceholder: true,
|
||||||
|
inputId: "cc-csc",
|
||||||
|
});
|
||||||
|
|
||||||
this.persistCheckbox = new LabelledCheckbox();
|
this.persistCheckbox = new LabelledCheckbox();
|
||||||
// The persist checkbox shouldn't be part of the record which gets saved so
|
// The persist checkbox shouldn't be part of the record which gets saved so
|
||||||
// exclude it from the form.
|
// exclude it from the form.
|
||||||
|
|
@ -107,6 +113,11 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
field.addEventListener("invalid", this);
|
field.addEventListener("invalid", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace the form-autofill cc-csc fields with our csc-input.
|
||||||
|
let cscContainer = this.form.querySelector("#cc-csc-container");
|
||||||
|
cscContainer.textContent = "";
|
||||||
|
cscContainer.appendChild(this.cscInput);
|
||||||
|
|
||||||
let fragment = document.createDocumentFragment();
|
let fragment = document.createDocumentFragment();
|
||||||
fragment.append(" ");
|
fragment.append(" ");
|
||||||
fragment.append(this.addressEditLink);
|
fragment.append(this.addressEditLink);
|
||||||
|
|
@ -148,6 +159,11 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
} else {
|
} else {
|
||||||
this.saveButton.textContent = this.dataset.nextButtonLabel;
|
this.saveButton.textContent = this.dataset.nextButtonLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.cscInput.placeholder = this.dataset.cscPlaceholder;
|
||||||
|
this.cscInput.frontTooltip = this.dataset.cscFrontInfoTooltip;
|
||||||
|
this.cscInput.backTooltip = this.dataset.cscBackInfoTooltip;
|
||||||
|
|
||||||
this.persistCheckbox.label = this.dataset.persistCheckboxLabel;
|
this.persistCheckbox.label = this.dataset.persistCheckboxLabel;
|
||||||
this.persistCheckbox.infoTooltip = this.dataset.persistCheckboxInfoTooltip;
|
this.persistCheckbox.infoTooltip = this.dataset.persistCheckboxInfoTooltip;
|
||||||
this.addressAddLink.textContent = this.dataset.addressAddLinkLabel;
|
this.addressAddLink.textContent = this.dataset.addressAddLinkLabel;
|
||||||
|
|
@ -169,7 +185,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
|
|
||||||
// The CVV fields should be hidden and disabled when editing.
|
// The CVV fields should be hidden and disabled when editing.
|
||||||
this.form.querySelector("#cc-csc-container").hidden = editing;
|
this.form.querySelector("#cc-csc-container").hidden = editing;
|
||||||
this.form.querySelector("#cc-csc").disabled = editing;
|
this.cscInput.disabled = editing;
|
||||||
|
|
||||||
// If a card is selected we want to edit it.
|
// If a card is selected we want to edit it.
|
||||||
if (editing) {
|
if (editing) {
|
||||||
|
|
@ -258,6 +274,9 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange(evt) {
|
onChange(evt) {
|
||||||
|
let ccType = this.form.querySelector("#cc-type");
|
||||||
|
this.cscInput.setAttribute("card-type", ccType.value);
|
||||||
|
|
||||||
this.updateSaveButtonState();
|
this.updateSaveButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -429,7 +448,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
|
||||||
id: "payment-summary",
|
id: "payment-summary",
|
||||||
},
|
},
|
||||||
[selectedStateKey]: guid,
|
[selectedStateKey]: guid,
|
||||||
[selectedStateKey + "SecurityCode"]: this.form.querySelector("#cc-csc").value,
|
[selectedStateKey + "SecurityCode"]: this.cscInput.value,
|
||||||
});
|
});
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
log.warn("saveRecord: error:", ex);
|
log.warn("saveRecord: error:", ex);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="46" height="27" version="1.1">
|
||||||
|
<defs>
|
||||||
|
<circle id="a" cx="10" cy="10" r="10"/>
|
||||||
|
</defs>
|
||||||
|
<g fill="none" fill-rule="evenodd" stroke="none" stroke-width="1">
|
||||||
|
<path fill="#A1C6FF" d="M37 6.2a10.046 10.046 0 0 0 -2 -0.2c-5.523 0 -10 4.477 -10 10a9.983 9.983 0 0 0 3.999 8h-27.999a1 1 0 0 1 -1 -1v-22a1 1 0 0 1 1 -1h35a1 1 0 0 1 1 1v5.2zm-18 7.8c3.314 0 6 -1.567 6 -3.5s-2.686 -3.5 -6 -3.5 -6 1.567 -6 3.5 2.686 3.5 6 3.5z"/>
|
||||||
|
<path fill="#5F5F5F" d="M2 17h9v2h-9v-2zm0 -15h33v3h-33v-3zm0 18h15v2h-15v-2zm10 -3h13v2h-13v-2z"/>
|
||||||
|
<g transform="translate(25 6)">
|
||||||
|
<mask id="b" fill="#fff">
|
||||||
|
<use xlink:href="#a"/>
|
||||||
|
</mask>
|
||||||
|
<use stroke="#FFF" stroke-width="1.5" xlink:href="#a"/>
|
||||||
|
<g mask="url(#b)">
|
||||||
|
<g transform="translate(-77 -31)">
|
||||||
|
<rect width="99.39" height="69.141" x="0" y="0" fill="#A1C6FF" fill-rule="evenodd" rx="1"/>
|
||||||
|
<path fill="#5F5F5F" fill-rule="evenodd" d="M79 46h17v6h-17z"/>
|
||||||
|
<text fill="none" font-family="sans-serif" font-size="6">
|
||||||
|
<tspan x="80" y="42" fill="#5F5F5F">1234</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,25 @@
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="45" height="27" version="1.1">
|
||||||
|
<defs>
|
||||||
|
<circle id="a" cx="10" cy="10" r="10"/>
|
||||||
|
</defs>
|
||||||
|
<g fill="none" fill-rule="evenodd" stroke="none" stroke-width="1">
|
||||||
|
<path fill="#62A0FF" d="M37 6.458a9.996 9.996 0 0 0 -3 -0.458c-3.701 0 -6.933 2.011 -8.662 5h-22.338v5h21a9.983 9.983 0 0 0 3.999 8h-26.999a1 1 0 0 1 -1 -1v-22a1 1 0 0 1 1 -1h35a1 1 0 0 1 1 1v5.458z"/>
|
||||||
|
<path fill="#5F5F5F" d="M37 6.458a9.996 9.996 0 0 0 -3 -0.458 9.97 9.97 0 0 0 -7.141 3h-26.859v-6h37v3.458z"/>
|
||||||
|
<g transform="translate(24 6)">
|
||||||
|
<mask id="b" fill="#fff">
|
||||||
|
<use xlink:href="#a"/>
|
||||||
|
</mask>
|
||||||
|
<use stroke="#FFF" stroke-width="1.5" xlink:href="#a"/>
|
||||||
|
<g mask="url(#b)">
|
||||||
|
<path fill="#62A0FF" fill-rule="evenodd" d="M-41.923 -15.615h64.476a1 1 0 0 1 1 1v44.244a1 1 0 0 1 -1 1h-64.476a1 1 0 0 1 -1 -1v-44.244a1 1 0 0 1 1 -1zm2.923 19.615v9h55v-9h-55z"/>
|
||||||
|
<path fill="#5F5F5F" fill-rule="evenodd" d="M-43 -10h66v12h-66z"/>
|
||||||
|
<text fill="none" font-family="sans-serif" font-size="6" transform="translate(-43.923 -15.615)">
|
||||||
|
<tspan x="47.676" y="26.104" fill="#5F5F5F">123</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -264,7 +264,7 @@ export default class PaymentDialog extends PaymentStateSubscriberMixin(HTMLEleme
|
||||||
(this._isPayerRequested(state.request.paymentOptions) &&
|
(this._isPayerRequested(state.request.paymentOptions) &&
|
||||||
(!this._payerAddressPicker.selectedOption ||
|
(!this._payerAddressPicker.selectedOption ||
|
||||||
this._payerAddressPicker.classList.contains(INVALID_CLASS_NAME))) ||
|
this._payerAddressPicker.classList.contains(INVALID_CLASS_NAME))) ||
|
||||||
!this._paymentMethodPicker.securityCodeInput.validity.valid ||
|
!this._paymentMethodPicker.securityCodeInput.isValid ||
|
||||||
!this._paymentMethodPicker.selectedOption ||
|
!this._paymentMethodPicker.selectedOption ||
|
||||||
this._paymentMethodPicker.classList.contains(INVALID_CLASS_NAME) ||
|
this._paymentMethodPicker.classList.contains(INVALID_CLASS_NAME) ||
|
||||||
state.changesPrevented;
|
state.changesPrevented;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import BasicCardOption from "../components/basic-card-option.js";
|
import BasicCardOption from "../components/basic-card-option.js";
|
||||||
|
import CscInput from "../components/csc-input.js";
|
||||||
import RichPicker from "./rich-picker.js";
|
import RichPicker from "./rich-picker.js";
|
||||||
import paymentRequest from "../paymentRequest.js";
|
import paymentRequest from "../paymentRequest.js";
|
||||||
/* import-globals-from ../unprivileged-fallbacks.js */
|
/* import-globals-from ../unprivileged-fallbacks.js */
|
||||||
|
|
@ -17,14 +18,11 @@ export default class PaymentMethodPicker extends RichPicker {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.dropdown.setAttribute("option-type", "basic-card-option");
|
this.dropdown.setAttribute("option-type", "basic-card-option");
|
||||||
this.securityCodeInput = document.createElement("input");
|
this.securityCodeInput = new CscInput();
|
||||||
this.securityCodeInput.autocomplete = "off";
|
this.securityCodeInput.className = "security-code-container";
|
||||||
this.securityCodeInput.placeholder = this.dataset.cvvPlaceholder;
|
this.securityCodeInput.placeholder = this.dataset.cscPlaceholder;
|
||||||
this.securityCodeInput.size = 3;
|
this.securityCodeInput.backTooltip = this.dataset.cscBackTooltip;
|
||||||
this.securityCodeInput.required = true;
|
this.securityCodeInput.frontTooltip = this.dataset.cscFrontTooltip;
|
||||||
// 3 or more digits
|
|
||||||
this.securityCodeInput.pattern = "[0-9]{3,}";
|
|
||||||
this.securityCodeInput.classList.add("security-code");
|
|
||||||
this.securityCodeInput.addEventListener("change", this);
|
this.securityCodeInput.addEventListener("change", this);
|
||||||
this.securityCodeInput.addEventListener("input", this);
|
this.securityCodeInput.addEventListener("input", this);
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +79,10 @@ export default class PaymentMethodPicker extends RichPicker {
|
||||||
this.securityCodeInput.defaultValue = securityCodeState;
|
this.securityCodeInput.defaultValue = securityCodeState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let selectedCardType = (basicCards[selectedPaymentCardGUID] &&
|
||||||
|
basicCards[selectedPaymentCardGUID]["cc-type"]) || "";
|
||||||
|
this.securityCodeInput.cardType = selectedCardType;
|
||||||
|
|
||||||
super.render(state);
|
super.render(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +125,7 @@ export default class PaymentMethodPicker extends RichPicker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onInputOrChange({target}) {
|
onInputOrChange({currentTarget}) {
|
||||||
let selectedKey = this.selectedStateKey;
|
let selectedKey = this.selectedStateKey;
|
||||||
let stateChange = {};
|
let stateChange = {};
|
||||||
|
|
||||||
|
|
@ -131,8 +133,8 @@ export default class PaymentMethodPicker extends RichPicker {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (target) {
|
switch (currentTarget) {
|
||||||
case this.dropdown.popupBox: {
|
case this.dropdown: {
|
||||||
stateChange[selectedKey] = this.dropdown.value;
|
stateChange[selectedKey] = this.dropdown.value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,16 +55,23 @@ payment-method-picker.rich-picker {
|
||||||
grid-template-columns: 20fr 1fr auto auto;
|
grid-template-columns: 20fr 1fr auto auto;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"label spacer edit add"
|
"label spacer edit add"
|
||||||
"dropdown cvv cvv cvv"
|
"dropdown csc csc csc"
|
||||||
"invalid invalid invalid invalid";
|
"invalid invalid invalid invalid";
|
||||||
}
|
}
|
||||||
|
|
||||||
payment-method-picker > input {
|
.security-code-container {
|
||||||
border: 1px solid #0C0C0D33;
|
display: flex;
|
||||||
border-inline-start: none;
|
flex-grow: 1;
|
||||||
grid-area: cvv;
|
grid-area: csc;
|
||||||
margin: 10px 0; /* Has to be same as rich-select */
|
margin: 10px 0; /* Has to be same as rich-select */
|
||||||
padding: 8px;
|
|
||||||
/* So the error outline appears above the adjacent dropdown */
|
/* So the error outline appears above the adjacent dropdown */
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rich-picker .security-code {
|
||||||
|
border: 1px solid #0C0C0D33;
|
||||||
|
/* Override the border from common.css */
|
||||||
|
border-inline-start: none !important;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,25 @@ let BASIC_CARDS_1 = {
|
||||||
"cc-family-name": "Fields",
|
"cc-family-name": "Fields",
|
||||||
"cc-type": "discover",
|
"cc-type": "discover",
|
||||||
},
|
},
|
||||||
|
"amex-card": {
|
||||||
|
methodName: "basic-card",
|
||||||
|
billingAddressGUID: "68gjdh354j",
|
||||||
|
"cc-number": "************1941",
|
||||||
|
"guid": "amex-card",
|
||||||
|
"version": 1,
|
||||||
|
"timeCreated": 1517890536491,
|
||||||
|
"timeLastModified": 1517890564518,
|
||||||
|
"timeLastUsed": 0,
|
||||||
|
"timesUsed": 0,
|
||||||
|
"cc-name": "Capt America",
|
||||||
|
"cc-given-name": "Capt",
|
||||||
|
"cc-additional-name": "",
|
||||||
|
"cc-family-name": "America",
|
||||||
|
"cc-type": "amex",
|
||||||
|
"cc-exp-month": 6,
|
||||||
|
"cc-exp-year": 2023,
|
||||||
|
"cc-exp": "2023-06",
|
||||||
|
},
|
||||||
"missing-cc-name": {
|
"missing-cc-name": {
|
||||||
methodName: "basic-card",
|
methodName: "basic-card",
|
||||||
"cc-number": "************8563",
|
"cc-number": "************8563",
|
||||||
|
|
|
||||||
|
|
@ -210,12 +210,14 @@ payment-dialog[changes-prevented][complete-status="success"] #pay {
|
||||||
content: attr(aria-label);
|
content: attr(aria-label);
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 2px 5px;
|
padding: 3px 5px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 1px solid #bebebf;
|
border: 1px solid #bebebf;
|
||||||
box-shadow: 1px 1px 3px #bebebf;
|
box-shadow: 1px 1px 3px #bebebf;
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
min-width: 188px;
|
line-height: normal;
|
||||||
|
width: 188px;
|
||||||
|
/* Center the tooltip over the (i) icon (188px / 2 - 5px (padding) - 1px (border)). */
|
||||||
left: -86px;
|
left: -86px;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
@ -226,6 +228,28 @@ payment-dialog[changes-prevented][complete-status="success"] #pay {
|
||||||
right: -86px;
|
right: -86px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.csc.info-tooltip:focus::after,
|
||||||
|
.csc.info-tooltip:hover::after {
|
||||||
|
/* Right-align the tooltip over the (i) icon (-188px - 60px (padding) - 2px (border) + 4px ((i) start padding) + 16px ((i) icon width)). */
|
||||||
|
left: -226px;
|
||||||
|
background-position: top 5px left 5px;
|
||||||
|
background-image: url(./containers/cvv-hint-image-back.svg);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
padding-inline-start: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.csc.info-tooltip[cc-type="amex"]::after {
|
||||||
|
background-image: url(./containers/cvv-hint-image-front.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.csc.info-tooltip:dir(rtl):focus::after,
|
||||||
|
.csc.info-tooltip:dir(rtl):hover::after {
|
||||||
|
left: auto;
|
||||||
|
/* Left-align the tooltip over the (i) icon (-188px - 60px (padding) - 2px (border) + 4px ((i) start padding) + 16px ((i) icon width)). */
|
||||||
|
right: -226px;
|
||||||
|
background-position: top 5px right 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.branding {
|
.branding {
|
||||||
background-image: url(chrome://branding/content/icon32.png);
|
background-image: url(chrome://branding/content/icon32.png);
|
||||||
background-size: 16px;
|
background-size: 16px;
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@
|
||||||
<!ENTITY billingAddress.editPage.title "Edit Billing Address">
|
<!ENTITY billingAddress.editPage.title "Edit Billing Address">
|
||||||
<!ENTITY basicCard.addPage.title "Add Credit Card">
|
<!ENTITY basicCard.addPage.title "Add Credit Card">
|
||||||
<!ENTITY basicCard.editPage.title "Edit Credit Card">
|
<!ENTITY basicCard.editPage.title "Edit Credit Card">
|
||||||
<!ENTITY basicCard.cvv.placeholder "CVV&fieldRequiredSymbol;">
|
<!ENTITY basicCard.csc.placeholder "CVV">
|
||||||
|
<!ENTITY basicCard.csc.back.infoTooltip "3 digit number found on the back of your credit card.">
|
||||||
|
<!ENTITY basicCard.csc.front.infoTooltip "3 digit number found on the front of your credit card.">
|
||||||
<!ENTITY payer.addPage.title "Add Payer Contact">
|
<!ENTITY payer.addPage.title "Add Payer Contact">
|
||||||
<!ENTITY payer.editPage.title "Edit Payer Contact">
|
<!ENTITY payer.editPage.title "Edit Payer Contact">
|
||||||
<!ENTITY payerLabel "Contact Information">
|
<!ENTITY payerLabel "Contact Information">
|
||||||
|
|
@ -150,7 +152,9 @@
|
||||||
<payment-method-picker selected-state-key="selectedPaymentCard"
|
<payment-method-picker selected-state-key="selectedPaymentCard"
|
||||||
data-add-link-label="&basicCard.addLink.label;"
|
data-add-link-label="&basicCard.addLink.label;"
|
||||||
data-edit-link-label="&basicCard.editLink.label;"
|
data-edit-link-label="&basicCard.editLink.label;"
|
||||||
data-cvv-placeholder="&basicCard.cvv.placeholder;"
|
data-csc-placeholder="&basicCard.csc.placeholder;"
|
||||||
|
data-csc-back-tooltip="&basicCard.csc.back.infoTooltip;"
|
||||||
|
data-csc-front-tooltip="&basicCard.csc.front.infoTooltip;"
|
||||||
data-invalid-label="&invalidOption.label;"
|
data-invalid-label="&invalidOption.label;"
|
||||||
label="&paymentMethodsLabel;">
|
label="&paymentMethodsLabel;">
|
||||||
</payment-method-picker>
|
</payment-method-picker>
|
||||||
|
|
@ -199,6 +203,9 @@
|
||||||
data-cancel-button-label="&cancelPaymentButton.label;"
|
data-cancel-button-label="&cancelPaymentButton.label;"
|
||||||
data-persist-checkbox-label="&basicCardPage.persistCheckbox.label;"
|
data-persist-checkbox-label="&basicCardPage.persistCheckbox.label;"
|
||||||
data-persist-checkbox-info-tooltip="&basicCardPage.persistCheckbox.infoTooltip;"
|
data-persist-checkbox-info-tooltip="&basicCardPage.persistCheckbox.infoTooltip;"
|
||||||
|
data-csc-placeholder="&basicCard.csc.placeholder;"
|
||||||
|
data-csc-back-info-tooltip="&basicCard.csc.back.infoTooltip;"
|
||||||
|
data-csc-front-info-tooltip="&basicCard.csc.front.infoTooltip;"
|
||||||
data-accepted-cards-label="&acceptedCards.label;"
|
data-accepted-cards-label="&acceptedCards.label;"
|
||||||
data-field-required-symbol="&fieldRequiredSymbol;"
|
data-field-required-symbol="&fieldRequiredSymbol;"
|
||||||
hidden="hidden"></basic-card-form>
|
hidden="hidden"></basic-card-form>
|
||||||
|
|
|
||||||
|
|
@ -293,7 +293,7 @@ var PaymentTestUtils = {
|
||||||
let picker = Cu.waiveXrays(content.document.querySelector("payment-method-picker"));
|
let picker = Cu.waiveXrays(content.document.querySelector("payment-method-picker"));
|
||||||
// Unwaive to access the ChromeOnly `setUserInput` API.
|
// Unwaive to access the ChromeOnly `setUserInput` API.
|
||||||
// setUserInput dispatches changes events.
|
// setUserInput dispatches changes events.
|
||||||
Cu.unwaiveXrays(picker.securityCodeInput).setUserInput(securityCode);
|
Cu.unwaiveXrays(picker.securityCodeInput).querySelector("input").setUserInput(securityCode);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ add_task(async function test_saveButton() {
|
||||||
let year = (new Date()).getFullYear().toString();
|
let year = (new Date()).getFullYear().toString();
|
||||||
fillField(form.form.querySelector("#cc-exp-year"), year);
|
fillField(form.form.querySelector("#cc-exp-year"), year);
|
||||||
fillField(form.form.querySelector("#cc-type"), "visa");
|
fillField(form.form.querySelector("#cc-type"), "visa");
|
||||||
fillField(form.form.querySelector("#cc-csc"), "123");
|
fillField(form.form.querySelector("csc-input input"), "123");
|
||||||
isnot(form.form.querySelector("#billingAddressGUID").value, address2.guid,
|
isnot(form.form.querySelector("#billingAddressGUID").value, address2.guid,
|
||||||
"Check initial billing address");
|
"Check initial billing address");
|
||||||
fillField(form.form.querySelector("#billingAddressGUID"), address2.guid);
|
fillField(form.form.querySelector("#billingAddressGUID"), address2.guid);
|
||||||
|
|
@ -222,7 +222,8 @@ add_task(async function test_requiredAttributePropagated() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let container = element.closest("label") || element.closest("div");
|
let container = element.closest("label") || element.closest("div");
|
||||||
ok(container.hasAttribute("required"), "Container should also be marked as required");
|
ok(container.hasAttribute("required"),
|
||||||
|
`Container ${container.id} should also be marked as required`);
|
||||||
}
|
}
|
||||||
// Now test that toggling the `required` attribute will affect the container.
|
// Now test that toggling the `required` attribute will affect the container.
|
||||||
let sampleRequiredElement = requiredElements[0];
|
let sampleRequiredElement = requiredElements[0];
|
||||||
|
|
@ -460,7 +461,7 @@ add_task(async function test_field_validity_updates() {
|
||||||
let ccNumber = form.form.querySelector("#cc-number");
|
let ccNumber = form.form.querySelector("#cc-number");
|
||||||
let nameInput = form.form.querySelector("#cc-name");
|
let nameInput = form.form.querySelector("#cc-name");
|
||||||
let typeInput = form.form.querySelector("#cc-type");
|
let typeInput = form.form.querySelector("#cc-type");
|
||||||
let cscInput = form.form.querySelector("#cc-csc");
|
let cscInput = form.form.querySelector("csc-input input");
|
||||||
let monthInput = form.form.querySelector("#cc-exp-month");
|
let monthInput = form.form.querySelector("#cc-exp-month");
|
||||||
let yearInput = form.form.querySelector("#cc-exp-year");
|
let yearInput = form.form.querySelector("#cc-exp-year");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ async function setup({shippingRequired, payerRequired}) {
|
||||||
|
|
||||||
// Fill the security code input so it doesn't interfere with checking the pay
|
// Fill the security code input so it doesn't interfere with checking the pay
|
||||||
// button state for dropdown changes.
|
// button state for dropdown changes.
|
||||||
el1._paymentMethodPicker.securityCodeInput.select();
|
el1._paymentMethodPicker.securityCodeInput.querySelector("input").select();
|
||||||
sendString("123");
|
sendString("123");
|
||||||
await asyncElementRendered();
|
await asyncElementRendered();
|
||||||
}
|
}
|
||||||
|
|
@ -234,14 +234,14 @@ add_task(async function test_securityCodeRequired() {
|
||||||
selectFirstItemOfPicker(picker);
|
selectFirstItemOfPicker(picker);
|
||||||
await stateChangedPromise;
|
await stateChangedPromise;
|
||||||
|
|
||||||
picker.securityCodeInput.select();
|
picker.securityCodeInput.querySelector("input").select();
|
||||||
stateChangedPromise = promiseStateChange(el1.requestStore);
|
stateChangedPromise = promiseStateChange(el1.requestStore);
|
||||||
synthesizeKey("VK_DELETE");
|
synthesizeKey("VK_DELETE");
|
||||||
await stateChangedPromise;
|
await stateChangedPromise;
|
||||||
|
|
||||||
ok(payButton.disabled, "Button is disabled when CVV is empty");
|
ok(payButton.disabled, "Button is disabled when CVV is empty");
|
||||||
|
|
||||||
picker.securityCodeInput.select();
|
picker.securityCodeInput.querySelector("input").select();
|
||||||
stateChangedPromise = promiseStateChange(el1.requestStore);
|
stateChangedPromise = promiseStateChange(el1.requestStore);
|
||||||
sendString("123");
|
sendString("123");
|
||||||
await stateChangedPromise;
|
await stateChangedPromise;
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ add_task(async function test_change_selected_card() {
|
||||||
let stateChangePromise = promiseStateChange(picker1.requestStore);
|
let stateChangePromise = promiseStateChange(picker1.requestStore);
|
||||||
|
|
||||||
// Type in the security code field
|
// Type in the security code field
|
||||||
picker1.securityCodeInput.focus();
|
picker1.securityCodeInput.querySelector("input").focus();
|
||||||
sendString("836");
|
sendString("836");
|
||||||
sendKey("Tab");
|
sendKey("Tab");
|
||||||
let state = await stateChangePromise;
|
let state = await stateChangePromise;
|
||||||
|
|
|
||||||
|
|
@ -61,15 +61,7 @@
|
||||||
<span data-localization="cardNetwork" class="label-text"/>
|
<span data-localization="cardNetwork" class="label-text"/>
|
||||||
</label>
|
</label>
|
||||||
<label id="cc-csc-container" class="container" hidden="hidden">
|
<label id="cc-csc-container" class="container" hidden="hidden">
|
||||||
<!-- Keep these attributes in-sync with securityCodeInput in payment-method-picker.js -->
|
<!-- The CSC container will get filled in by forms that need a CSC (using csc-input.js) -->
|
||||||
<input id="cc-csc"
|
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
size="3"
|
|
||||||
required="required"
|
|
||||||
pattern="[0-9]{3,}"
|
|
||||||
disabled="disabled"/>
|
|
||||||
<span data-localization="cardCVV" class="label-text"/>
|
|
||||||
</label>
|
</label>
|
||||||
<div id="billingAddressGUID-container" class="billingAddressRow container rich-picker">
|
<div id="billingAddressGUID-container" class="billingAddressRow container rich-picker">
|
||||||
<select id="billingAddressGUID" required="required">
|
<select id="billingAddressGUID" required="required">
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ form div {
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
form :-moz-any(label, div) > .label-text {
|
form :-moz-any(label, div) .label-text {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: GrayText;
|
color: GrayText;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
@ -49,8 +49,8 @@ form :-moz-any(label, div) > .label-text {
|
||||||
font-size .2s var(--animation-easing-function);
|
font-size .2s var(--animation-easing-function);
|
||||||
}
|
}
|
||||||
|
|
||||||
form :-moz-any(label, div):focus-within > .label-text,
|
form :-moz-any(label, div):focus-within .label-text,
|
||||||
form :-moz-any(label, div) > .label-text[field-populated] {
|
form :-moz-any(label, div) .label-text[field-populated] {
|
||||||
top: 0;
|
top: 0;
|
||||||
font-size: var(--in-field-label-size);
|
font-size: var(--in-field-label-size);
|
||||||
}
|
}
|
||||||
|
|
@ -65,8 +65,8 @@ form :-moz-any(input, select, textarea):focus:-moz-ui-invalid ~ .label-text {
|
||||||
color: var(--in-content-text-color);
|
color: var(--in-content-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
form div[required] > label > .label-text::after,
|
form div[required] > label .label-text::after,
|
||||||
form :-moz-any(label, div)[required] > .label-text::after {
|
form :-moz-any(label, div)[required] .label-text::after {
|
||||||
content: attr(fieldRequiredSymbol);
|
content: attr(fieldRequiredSymbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue