Checkout: forward only the checkout fields, show clear errors, fix the contact form #16

Open
libretech wants to merge 6 commits from fix/checkout-errors into main
4 changed files with 45 additions and 5 deletions
Showing only changes of commit 6f434c21ba - Show all commits
+4 -2
View File
@@ -9,6 +9,7 @@ import type {
PaymentMethod,
ApiResponse
} from "~/types";
import type { AlreadyCaptured } from "~/utils/captureResponse";
/**
* Shop API composable.
@@ -116,8 +117,9 @@ export function useShopApi() {
});
},
async capturePayment(uuid: string, paypalOrderId: string): Promise<Order> {
return await $fetch<Order>(`/api/orders/${uuid}/capture/${paypalOrderId}`, {
/** The paid order, or { success: true, alreadyCaptured: true } when an earlier capture of this PayPal order went through. */
async capturePayment(uuid: string, paypalOrderId: string): Promise<Order | AlreadyCaptured> {
return await $fetch<Order | AlreadyCaptured>(`/api/orders/${uuid}/capture/${paypalOrderId}`, {
method: "POST"
});
},
+8 -3
View File
@@ -191,6 +191,7 @@
import { numberFormatter } from "~/utils/numberFormatter";
import { trackEvent } from "~/utils/trackEvent";
import { checkoutErrorMessageFor } from "~/utils/checkoutError";
import { isAlreadyCaptured } from "~/utils/captureResponse";
import type { Order } from "~/types";
const config = useRuntimeConfig();
@@ -312,10 +313,14 @@ async function initializePayPalButtons() {
onApprove: async (data) => {
try {
// Capture payment server-side (CMS captures via PayPal server SDK and updates order)
const capturedOrder = await shopApi.capturePayment(orderData.value.uuid, data.orderID!);
const captured = await shopApi.capturePayment(orderData.value.uuid, data.orderID!);
if (capturedOrder.paymentAuthorised) {
orderData.value = capturedOrder;
if (isAlreadyCaptured(captured)) {
// An earlier capture of this PayPal order went through: reload the paid order, keeping the shown one if that fails.
orderData.value = await shopApi.getOrder(orderData.value.uuid).catch(() => orderData.value);
await handleSuccessfulPayment();
} else if (captured.paymentAuthorised) {
orderData.value = captured;
await handleSuccessfulPayment();
} else {
console.error("Payment capture did not result in authorisation");
+21
View File
@@ -0,0 +1,21 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { isAlreadyCaptured } from "../../utils/captureResponse.ts";
test("recognises the CMS's answer to a repeated capture", () => {
assert.equal(isAlreadyCaptured({ success: true, alreadyCaptured: true }), true);
});
test("takes the paid order of a first capture for no repeated capture", () => {
const order = { uuid: "11111111-2222-4333-8444-555555555555", total: 22.85, paymentAuthorised: true, email: "erika@example.org" };
assert.equal(isAlreadyCaptured(order), false);
assert.equal(isAlreadyCaptured({ ...order, alreadyCaptured: false }), false);
});
test("takes nothing but true for both fields as a repeated capture", () => {
assert.equal(isAlreadyCaptured({ alreadyCaptured: true }), false);
assert.equal(isAlreadyCaptured({ success: true, alreadyCaptured: "true" }), false);
assert.equal(isAlreadyCaptured({ success: false, alreadyCaptured: true }), false);
for (const response of [undefined, null, "alreadyCaptured", 1, [true]]) assert.equal(isAlreadyCaptured(response), false);
});
+12
View File
@@ -0,0 +1,12 @@
// The answer of POST /api/orders/:uuid/capture/:paypalOrderId: the paid order, or, when the order is already paid with this
// PayPal order (a repeated capture), { success: true, alreadyCaptured: true } (libreshop/cms src/api/order/controllers/order.ts).
// Pure: no Nuxt or Vue imports, tested in tests/unit/captureResponse.test.ts.
/** The CMS's answer to a repeated capture: the payment went through before, and the order is paid. */
export type AlreadyCaptured = { success: true; alreadyCaptured: true };
export const isAlreadyCaptured = (response: unknown): response is AlreadyCaptured =>
typeof response === "object" &&
response !== null &&
(response as Record<string, unknown>).success === true &&
(response as Record<string, unknown>).alreadyCaptured === true;