fix(checkout): reject an order PayPal cannot take with a typed error
buildPayPalOrderRequest split the text delivery address at fixed offsets and called toFixed on the delivery price, and the items that were computed but never sent read the product of every cart line. A missing or two-line delivery address, a delivery method whose price is null, such as a pickup, and a cart line whose product was deleted each threw a TypeError, which the checkout answered 502 (D1 to D4). The request is now built in whole cents, with the shipping address from the first one entered of the structured delivery address, the structured invoice address and the text delivery address: - fullName is givenName and familyName, addressLine1 streetAddress, adminArea2 addressLevel2, countryCode the country upper-cased or DE; a structured address that is entered but incomplete is rejected instead of replaced by another address; - a text address needs three lines: the name, the street, and "<postal code> <city>" split at the first whitespace; lines between the name and the street become addressLine2; - a delivery price that is null counts as 0; - itemTotal is the net of the subtotal, round(subtotal cents * 100 / 119), taxTotal the order's VAT. The breakdown must add up to the total to the cent, as PayPal requires, so totals computed before a price changed are rejected here instead of by PayPal. The items are gone. With items, PayPal requires item_total and tax_total to be the sums of the amounts rounded per unit, which miss the net and the VAT of the subtotal that the invoice states: 3 x 12.50 gives 31.50 and 6.00, the subtotal 31.51 and 5.99. An empty cart, a missing total or VAT, totals that do not add up and a missing or unusable address throw CheckoutNotReadyError, which the checkout answers 400 "Order is not ready for checkout" with the reason. A deleted product throws ProductUnavailableError, answered 409 "A product in the cart is no longer available". Both are thrown before PayPal is asked and are logged as warnings; other errors stay 502. Tests: D1 to D4 are regular tests. The test of an order without a total expects CheckoutNotReadyError instead of a plain Error, and the item test is replaced by one that shows why no items are sent. The order of paypal-verify.test.ts gains the cart line the builder now requires, and its amount test builds the request for every total up to 3000.00 instead of assuming toFixed(2). Refs libretech/mp#71 Refs libretech/mp#67
This commit is contained in:
5 files changed
+434
-163
No files matched your search
@@ -23,6 +23,9 @@ const order = (overrides = {}) => ({
|
||||
VAT: 2.86,
|
||||
delivery: { id: 1, name: "DHL", price: 4.95 },
|
||||
invoiceNumber: "R-12-4711",
|
||||
cart: [
|
||||
{ id: 1, count: 1, product: { id: 101, name: "Notizbuch A5 Punktraster", cover: { price: 14.9 }, pages: { price: 3 }, ruling: { price: 0 } } }
|
||||
],
|
||||
deliveryAddress: "Erika Mustermann\nMusterstraße 1\n70190 Stuttgart",
|
||||
...overrides
|
||||
});
|
||||
@@ -80,9 +83,12 @@ test("turns the order total into integer cents, also where its float is a hair b
|
||||
test("the amount the checkout sends PayPal is the amount the capture accepts, for every total up to 3000.00", () => {
|
||||
const mismatches: number[] = [];
|
||||
for (let cents = 1; cents <= 300000; cents++) {
|
||||
// The order service rounds every total to the cent; an order without a delivery method has its subtotal as its total.
|
||||
const total = cents / 100;
|
||||
// buildPayPalOrderRequest sends order.total.toFixed(2); the order service rounds every total to the cent
|
||||
if (parseCents(total.toFixed(2)) !== cents || orderTotalCents({ total }) !== cents) mismatches.push(total);
|
||||
const VAT = Math.round((total / 1.19) * 0.19 * 100) / 100;
|
||||
const request = buildPayPalOrderRequest("https://shop.example/checkout/3", order({ subtotal: total, total, VAT, delivery: null }));
|
||||
const { amount } = request.purchaseUnits[0];
|
||||
if (parseCents(amount.value) !== cents || orderTotalCents({ total }) !== cents) mismatches.push(total);
|
||||
}
|
||||
assert.equal(mismatches.length, 0, `e.g. ${mismatches.slice(0, 5).join(", ")}`);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user