Bug 1490805 - Add a required CSC/CVV field to the add card page. r=jaws

Depends on D6882

Differential Revision: https://phabricator.services.mozilla.com/D6883

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Matthew Noorenberghe 2018-09-27 17:26:24 +00:00
Родитель b3b9e414bc
Коммит 05d5fb0939
12 изменённых файлов: 65 добавлений и 10 удалений

Просмотреть файл

@ -128,7 +128,7 @@ var paymentDialogWrapper = {
let addressData = this.temporaryStore.addresses.get(guid) ||
await formAutofillStorage.addresses.get(guid);
if (!addressData) {
throw new Error(`Shipping address not found: ${guid}`);
throw new Error(`Address not found: ${guid}`);
}
let address = this.createPaymentAddress({

Просмотреть файл

@ -313,6 +313,7 @@ export default class AddressForm extends PaymentStateSubscriberMixin(PaymentRequ
if (page.onboardingWizard && !Object.keys(savedBasicCards).length) {
successStateChange = {
"basic-card-page": {
selectedStateKey: "selectedPaymentCard",
// Preserve field values as the user may have already edited the card
// page and went back to the address page to make a correction.
preserveFieldValues: true,

Просмотреть файл

@ -169,6 +169,10 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
this.form.querySelector("#cc-number").disabled = editing;
// The CVV fields should be hidden and disabled when editing.
this.form.querySelector("#cc-csc-container").hidden = editing;
this.form.querySelector("#cc-csc").disabled = editing;
// If a card is selected we want to edit it.
if (editing) {
this.pageTitleHeading.textContent = this.dataset.editBasicCardTitle;
@ -284,6 +288,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
preserveFieldValues: true,
guid: basicCardPage.guid,
persistCheckboxValue: this.persistCheckbox.checked,
selectedStateKey: basicCardPage.selectedStateKey,
},
};
let billingAddressGUID = this.form.querySelector("#billingAddressGUID");
@ -400,7 +405,7 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
record.isTemporary = true;
}
for (let editableFieldName of ["cc-name", "cc-exp-month", "cc-exp-year"]) {
for (let editableFieldName of ["cc-name", "cc-exp-month", "cc-exp-year", "cc-type"]) {
record[editableFieldName] = record[editableFieldName] || "";
}
@ -410,14 +415,23 @@ export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRe
record["cc-number"] = record["cc-number"] || "";
}
// Never save the CSC in storage. Storage will throw and not save the record
// if it is passed.
delete record["cc-csc"];
try {
let {guid} = await paymentRequest.updateAutofillRecord("creditCards", record,
basicCardPage.guid);
let {selectedStateKey} = currentState["basic-card-page"];
if (!selectedStateKey) {
throw new Error(`state["basic-card-page"].selectedStateKey is required`);
}
this.requestStore.setState({
page: {
id: "payment-summary",
},
selectedPaymentCard: guid,
[selectedStateKey]: guid,
[selectedStateKey + "SecurityCode"]: this.form.querySelector("#cc-csc").value,
});
} catch (ex) {
log.warn("saveRecord: error:", ex);

Просмотреть файл

@ -75,6 +75,11 @@ export default class PaymentMethodPicker extends RichPicker {
`does not exist in the payment method picker`);
}
let securityCodeState = state[this.selectedStateKey + "SecurityCode"];
if (securityCodeState && securityCodeState != this.securityCodeInput.value) {
this.securityCodeInput.defaultValue = securityCodeState;
}
super.render(state);
}
@ -143,7 +148,9 @@ export default class PaymentMethodPicker extends RichPicker {
page: {
id: "basic-card-page",
},
"basic-card-page": {},
"basic-card-page": {
selectedStateKey: this.selectedStateKey,
},
};
switch (target) {

Просмотреть файл

@ -17,6 +17,7 @@ export let requestStore = new PaymentsStore({
"basic-card-page": {
guid: null,
// preserveFieldValues: true,
selectedStateKey: null,
},
"address-page": {
guid: null,

Просмотреть файл

@ -60,7 +60,7 @@
<!ENTITY basicCardPage.addButton.label "Add">
<!ENTITY basicCardPage.nextButton.label "Next">
<!ENTITY basicCardPage.updateButton.label "Update">
<!ENTITY basicCardPage.persistCheckbox.label "Save credit card to &brandShortName; (Security code will not be saved)">
<!ENTITY basicCardPage.persistCheckbox.label "Save credit card to &brandShortName; (CVV will not be saved)">
<!ENTITY basicCardPage.persistCheckbox.infoTooltip "&brandShortName; can securely store your credit card information to use in forms like this, so you dont have to enter it every time.">
<!ENTITY addressPage.error.genericSave "There was an error saving the address.">
<!ENTITY addressPage.cancelButton.label "Cancel">

Просмотреть файл

@ -61,7 +61,10 @@ async function add_link(aOptions = {}) {
if (aOptions.hasOwnProperty("setCardPersistCheckedValue")) {
cardOptions.setPersistCheckedValue = aOptions.setCardPersistCheckedValue;
}
await fillInCardForm(frame, PTU.BasicCards.JaneMasterCard, cardOptions);
await fillInCardForm(frame, {
["cc-csc"]: 123,
...PTU.BasicCards.JaneMasterCard,
}, cardOptions);
await verifyCardNetwork(frame, cardOptions);
await verifyPersistCheckbox(frame, cardOptions);
@ -650,7 +653,10 @@ add_task(async function test_private_card_adding() {
"Check card page state");
});
await fillInCardForm(frame, PTU.BasicCards.JohnDoe);
await fillInCardForm(frame, {
["cc-csc"]: "999",
...PTU.BasicCards.JohnDoe,
});
await spawnPaymentDialogTask(frame, async function() {
let {

Просмотреть файл

@ -91,7 +91,10 @@ add_task(async function test_onboarding_wizard_without_saved_addresses_and_saved
}, "Shipping address is selected as the billing address");
});
await fillInCardForm(frame, PTU.BasicCards.JohnDoe);
await fillInCardForm(frame, {
["cc-csc"]: "123",
...PTU.BasicCards.JohnDoe,
});
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.clickPrimaryButton);
@ -362,7 +365,10 @@ add_task(async function test_onboarding_wizard_with_requestShipping_turned_off()
}, "Billing Address is correctly shown");
});
await fillInCardForm(frame, PTU.BasicCards.JohnDoe);
await fillInCardForm(frame, {
["cc-csc"]: "123",
...PTU.BasicCards.JohnDoe,
});
await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.clickPrimaryButton);

Просмотреть файл

@ -138,6 +138,7 @@ add_task(async function test_saveButton() {
let year = (new Date()).getFullYear().toString();
fillField(form.form.querySelector("#cc-exp-year"), year);
fillField(form.form.querySelector("#cc-type"), "visa");
fillField(form.form.querySelector("#cc-csc"), "123");
isnot(form.form.querySelector("#billingAddressGUID").value, address2.guid,
"Check initial billing address");
fillField(form.form.querySelector("#billingAddressGUID"), address2.guid);
@ -203,7 +204,7 @@ add_task(async function test_requiredAttributePropagated() {
await asyncElementRendered();
let requiredElements = [...form.form.elements].filter(e => e.required && !e.disabled);
is(requiredElements.length, 6, "Number of required elements");
is(requiredElements.length, 7, "Number of required elements");
for (let element of requiredElements) {
if (element.id == "billingAddressGUID") {
// The billing address has a different layout.
@ -445,6 +446,7 @@ add_task(async function test_field_validity_updates() {
let ccNumber = form.form.querySelector("#cc-number");
let nameInput = form.form.querySelector("#cc-name");
let typeInput = form.form.querySelector("#cc-type");
let cscInput = form.form.querySelector("#cc-csc");
let monthInput = form.form.querySelector("#cc-exp-month");
let yearInput = form.form.querySelector("#cc-exp-year");
@ -460,6 +462,7 @@ add_task(async function test_field_validity_updates() {
let year = (new Date()).getFullYear().toString();
fillField(yearInput, year);
fillField(typeInput, "visa");
fillField(cscInput, "456");
ok(ccNumber.checkValidity(), "cc-number field is valid with good input");
ok(nameInput.checkValidity(), "cc-name field is valid with a value");
ok(monthInput.checkValidity(), "cc-exp-month field is valid with a value");

Просмотреть файл

@ -60,6 +60,17 @@
</select>
<span data-localization="cardNetwork" class="label-text"/>
</label>
<label id="cc-csc-container" class="container" hidden="hidden">
<!-- Keep these attributes in-sync with securityCodeInput in payment-method-picker.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>
<div id="billingAddressGUID-container" class="billingAddressRow container rich-picker">
<select id="billingAddressGUID" required="required">
</select>

Просмотреть файл

@ -185,6 +185,8 @@ cardExpiresMonth = Exp. Month
cardExpiresYear = Exp. Year
billingAddress = Billing Address
cardNetwork = Card Type
# LOCALIZATION NOTE (cardCVV): Credit card security code https://en.wikipedia.org/wiki/Card_security_code
cardCVV = CVV
# LOCALIZATION NOTE: (cardNetwork.*): These are brand names and should only be translated when a locale-specific name for that brand is in common use
cardNetwork.amex = American Express

Просмотреть файл

@ -44,6 +44,10 @@
grid-area: cc-type;
}
#cc-csc-container {
grid-area: cc-csc;
}
#billingAddressGUID-container {
grid-area: billingAddressGUID;
}