Bug 1489968 - Implement BasicCardErrors for PaymentRequest. r=edenchuang,baku

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Marcos Cáceres 2018-09-27 08:03:14 +00:00
parent 495837c33c
commit ce4c75065c
8 changed files with 278 additions and 2 deletions

View File

@ -343,6 +343,19 @@ BasicCardService::DecodeBasicCardData(const nsAString& aData,
return NS_OK;
}
bool
BasicCardService::IsValidBasicCardErrors(JSContext* aCx,
JSObject* aData)
{
if (!aData) {
return true;
}
JS::RootedValue data(aCx, JS::ObjectValue(*aData));
BasicCardErrors bcError;
return !bcError.Init(aCx, data);
}
#ifdef PaymentBasicCardMacros
#undef PaymentBasicCardMacros
#undef EncodeBasicCardProperty

View File

@ -24,9 +24,9 @@ public:
bool IsBasicCardPayment(const nsAString& aSupportedMethods);
bool IsValidBasicCardRequest(JSContext* aCx, JSObject* aData, nsAString& aErrorMsg);
bool IsValidBasicCardErrors(JSContext* aCx, JSObject* aData);
bool IsValidExpiryMonth(const nsAString& aExpiryMonth);
bool IsValidExpiryYear(const nsAString& aExpiryYear);
/*
To let BasicCardResponse using the same data type with non-BasicCard response
in IPC transferring, following two methods is used to Encode/Decode the raw

View File

@ -248,6 +248,18 @@ PaymentResponse::Retry(JSContext* aCx,
return promise.forget();
}
// Depending on the PMI, try to do IDL type conversion
// (e.g., basic-card expects at BasicCardErrors dictionary)
nsAutoString errorMsg;
rv = ConvertPaymentMethodErrors(aCx, aErrors, errorMsg);
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_ASSERT(!errorMsg.IsEmpty());
ErrorResult error;
error.ThrowTypeError<MSG_NOT_DICTIONARY>(errorMsg);
promise->MaybeReject(error);
return promise.forget();
}
MOZ_ASSERT(mRequest);
rv = mRequest->RetryPayment(aCx, aErrors);
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -298,6 +310,27 @@ PaymentResponse::RejectRetry(nsresult aRejectReason)
mRetryPromise = nullptr;
}
nsresult
PaymentResponse::ConvertPaymentMethodErrors(
JSContext* aCx,
const PaymentValidationErrors& aErrors,
nsAString& errorMsg) const
{
MOZ_ASSERT(aCx);
if (!aErrors.mPaymentMethod.WasPassed()) {
return NS_OK;
}
RefPtr<BasicCardService> service = BasicCardService::GetService();
MOZ_ASSERT(service);
if (service->IsBasicCardPayment(mMethodName)) {
if (service->IsValidBasicCardErrors(aCx, aErrors.mPaymentMethod.Value())) {
errorMsg.Assign(NS_LITERAL_STRING("paymentMethod"));
return NS_ERROR_TYPE_ERR;
}
}
return NS_OK;
}
nsresult
PaymentResponse::ValidatePaymentValidationErrors(
const PaymentValidationErrors& aErrors)

View File

@ -89,7 +89,12 @@ public:
protected:
~PaymentResponse();
nsresult ValidatePaymentValidationErrors(const PaymentValidationErrors& aErrors);
nsresult ValidatePaymentValidationErrors(
const PaymentValidationErrors& aErrors);
nsresult ConvertPaymentMethodErrors(JSContext* aCx,
const PaymentValidationErrors& aErrors,
nsAString& aErrorMsg) const;
nsresult DispatchUpdateEvent(const nsAString& aType);

View File

@ -0,0 +1,129 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const paymentSrv = Cc[
"@mozilla.org/dom/payments/payment-request-service;1"
].getService(Ci.nsIPaymentRequestService);
const defaultCard = {
cardholderName: "",
cardNumber: "4111111111111111",
expiryMonth: "",
expiryYear: "",
cardSecurityCode: "",
billingAddress: null,
};
function makeBillingAddress() {
const billingAddress = Cc[
"@mozilla.org/dom/payments/payment-address;1"
].createInstance(Ci.nsIPaymentAddress);
const addressLine = Cc["@mozilla.org/array;1"].createInstance(
Ci.nsIMutableArray
);
const address = Cc["@mozilla.org/supports-string;1"].createInstance(
Ci.nsISupportsString
);
address.data = "Easton Ave";
addressLine.appendElement(address);
const addressArgs = [
"USA", // country
addressLine, // address line
"CA", // region
"San Bruno", // city
"", // dependent locality
"94066", // postal code
"123456", // sorting code
"", // organization
"Bill A. Pacheco", // recipient
"+14344413879", // phone
];
billingAddress.init(...addressArgs);
return billingAddress;
}
function makeBasicCardResponse(details) {
const basicCardResponseData = Cc[
"@mozilla.org/dom/payments/basiccard-response-data;1"
].createInstance(Ci.nsIBasicCardResponseData);
const {
cardholderName,
cardNumber,
expiryMonth,
expiryYear,
cardSecurityCode,
billingAddress,
} = details;
const address =
billingAddress !== undefined ? billingAddress : makeBillingAddress();
basicCardResponseData.initData(
cardholderName,
cardNumber,
expiryMonth,
expiryYear,
cardSecurityCode,
address
);
return basicCardResponseData;
}
const TestingUIService = {
showPayment(requestId, details = { ...defaultCard }) {
const showResponse = Cc[
"@mozilla.org/dom/payments/payment-show-action-response;1"
].createInstance(Ci.nsIPaymentShowActionResponse);
showResponse.init(
requestId,
Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED,
"basic-card", // payment method
makeBasicCardResponse(details),
"Person name",
"Person email",
"Person phone"
);
paymentSrv.respondPayment(
showResponse.QueryInterface(Ci.nsIPaymentActionResponse)
);
},
// Handles response.retry({ paymentMethod }):
updatePayment(requestId) {
// Let's echo what was sent in by the error...
const request = paymentSrv.getPaymentRequestById(requestId);
this.showPayment(requestId, request.paymentDetails.paymentMethod);
},
// Handles response.complete()
completePayment(requestId) {
const request = paymentSrv.getPaymentRequestById(requestId);
const completeResponse = Cc[
"@mozilla.org/dom/payments/payment-complete-action-response;1"
].createInstance(Ci.nsIPaymentCompleteActionResponse);
completeResponse.init(
requestId,
Ci.nsIPaymentActionResponse.COMPLETE_SUCCEEDED
);
paymentSrv.respondPayment(
completeResponse.QueryInterface(Ci.nsIPaymentActionResponse)
);
},
get QueryInterface() {
return ChromeUtils.generateQI([Ci.nsIPaymentUIService]);
},
};
paymentSrv.setTestingUIService(
TestingUIService.QueryInterface(Ci.nsIPaymentUIService)
);
addMessageListener("teardown", () => {
paymentSrv.setTestingUIService(null);
sendAsyncMessage("teardown-complete");
});

View File

@ -9,6 +9,7 @@ support-files =
echo_payment_request.html
BasiccardChromeScript.js
Bug1478740ChromeScript.js
BasicCardErrorsChromeScript.js
Bug1490698ChromeScript.js
ClosePaymentChromeScript.js
ConstructorChromeScript.js
@ -26,6 +27,7 @@ support-files =
[test_abortPayment.html]
run-if = nightly_build # Bug 1390018: Depends on the Nightly-only UI service
[test_basiccard.html]
[test_basiccarderrors.html]
[test_block_none10s.html]
skip-if = e10s # Bug 1408250: Don't expose PaymentRequest Constructor in non-e10s
[test_bug1478740.html]

View File

@ -0,0 +1,85 @@
<!doctype html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1489968
-->
<meta charset="utf-8">
<title>Test for Bug 1489968</title>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="./DefaultData.js"></script>
<script>
SimpleTest.waitForExplicitFinish();
const gUrl = SimpleTest.getTestFileURL("BasicCardErrorsChromeScript.js");
const gScript = SpecialPowers.loadChromeScript(gUrl);
function sendOnce(message) {
return data => {
return new Promise(resolve => {
const doneMsg = `${message}-complete`;
gScript.addMessageListener(doneMsg, function listener() {
gScript.removeMessageListener(doneMsg, listener);
resolve();
});
gScript.sendAsyncMessage(message, data);
});
};
}
const sendTearDown = sendOnce("teardown");
async function teardown() {
await sendTearDown();
gScript.destroy();
SimpleTest.finish();
}
async function testBasicCardErrors() {
const handler = SpecialPowers.getDOMWindowUtils(window).setHandlingUserInput(
true
);
const request = new PaymentRequest(
[{ supportedMethods: "basic-card" }],
defaultDetails
);
const response = await request.show();
// Smoke test the initial state
is(response.details.cardNumber, "4111111111111111", "Expected cardNumber to initially be 4111111111111111");
// We send these up and have the chrome script echo them back to us.
const expected = {
cardholderName: "PASS",
cardNumber: "3566002020360505",
cardSecurityCode: "666",
expiryMonth: "02",
expiryYear: "2020",
};
await response.retry({ paymentMethod: expected });
// the values of the response would have been updated with the expected
for (const [member, expectedValue] of Object.entries(expected)) {
const actual = response.details[member];
is(
actual,
expectedValue,
`Expected member ${member} to be "${expectedValue}, but got "${actual}"`
);
}
await response.complete("success");
handler.destruct();
}
async function runTests() {
try {
await testBasicCardErrors();
} catch (err) {
ok(false, `Unexpected error: ${err} ${err.stack}.`);
} finally {
await teardown();
}
}
window.addEventListener("load", () => {
const prefs = [["dom.payments.request.enabled", true]];
SpecialPowers.pushPrefEnv({ set: prefs }, runTests);
});
</script>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1489968">Mozilla Bug 1489968</a>

View File

@ -25,3 +25,12 @@ dictionary BasicCardResponse {
DOMString cardSecurityCode;
PaymentAddress? billingAddress;
};
dictionary BasicCardErrors {
DOMString cardNumber;
DOMString cardholderName;
DOMString cardSecurityCode;
DOMString expiryMonth;
DOMString expiryYear;
AddressErrors billingAddress;
};