Checkout: only customer fields on order updates, verified PayPal capture, safe logging #6
@@ -1,9 +1,15 @@
|
||||
import { factories } from "@strapi/strapi";
|
||||
import { sanitize } from "@strapi/utils";
|
||||
import type { ID } from "@strapi/database/dist/types";
|
||||
import { Order } from "../../../../types";
|
||||
import { paypalApi } from "../../../services/PayPalApi";
|
||||
import { orderDefaultParams } from "../services/order";
|
||||
import { calculateTotalProductPrice } from "../../product/services/product";
|
||||
import { parseCustomerUpdate } from "../../../checkout/customer-update";
|
||||
import type { CustomerUpdate } from "../../../checkout/customer-update";
|
||||
import { isLockedForCustomer } from "../../../checkout/order-state";
|
||||
|
||||
const ORDER_LOCKED = "Order can no longer be changed";
|
||||
|
||||
/**
|
||||
* The order controller is a funnel for the client to interact with the order service.
|
||||
@@ -96,31 +102,42 @@ export default factories.createCoreController("api::order.order", ({ strapi }) =
|
||||
return await sanitize.contentAPI.output(order, strapi.getModel("api::order.order"));
|
||||
},
|
||||
|
||||
/**
|
||||
* The customer's checkout steps: contact details, terms, addresses and delivery method. Only these fields are written;
|
||||
* payment state, totals, numbers and documents are written by the payment flow and the server alone.
|
||||
*/
|
||||
updateByUuid: async (ctx) => {
|
||||
const { uuid } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||
const order = await strapi.db.query("api::order.order").findOne({ where: { uuid } });
|
||||
if (!order) return ctx.notFound("Order not found");
|
||||
if (isLockedForCustomer(order)) return ctx.conflict(ORDER_LOCKED);
|
||||
|
||||
const sanitizedBody = await sanitize.contentAPI.input(ctx.request.body, strapi.getModel("api::order.order"));
|
||||
const update = parseCustomerUpdate(ctx.request.body, new Date());
|
||||
if (update.ok === false) {
|
||||
strapi.log.warn("app:w:order-controller: Rejected order update", { order: order.id, fields: update.fields });
|
||||
return ctx.badRequest("Invalid order update", { errors: update.errors });
|
||||
}
|
||||
const data: CustomerUpdate & { customer?: ID } = { ...update.data };
|
||||
|
||||
if (data.delivery !== undefined) {
|
||||
const delivery = await strapi.db.query("api::delivery.delivery").findOne({ where: { id: data.delivery }, select: ["id"] });
|
||||
if (!delivery) {
|
||||
strapi.log.warn("app:w:order-controller: Rejected order update", { order: order.id, fields: ["data.delivery"] });
|
||||
return ctx.badRequest("Invalid order update", { errors: ["data.delivery: no delivery method has this id"] });
|
||||
}
|
||||
}
|
||||
|
||||
// Handle customer creation/update if structured addresses are provided
|
||||
// @ts-expect-error
|
||||
if (sanitizedBody?.data?.invoiceAddressStructured) {
|
||||
if (data.invoiceAddressStructured) {
|
||||
const address = data.invoiceAddressStructured;
|
||||
const customerAddress =
|
||||
// @ts-expect-error
|
||||
sanitizedBody.data.invoiceAddress ||
|
||||
// @ts-expect-error
|
||||
`${sanitizedBody.data.invoiceAddressStructured.givenName} ${sanitizedBody.data.invoiceAddressStructured.familyName}\n${sanitizedBody.data.invoiceAddressStructured.streetAddress}\n${sanitizedBody.data.invoiceAddressStructured.postalCode} ${sanitizedBody.data.invoiceAddressStructured.addressLevel2}`;
|
||||
// @ts-expect-error
|
||||
sanitizedBody.data.customer = await strapi
|
||||
.service("api::customer.customer")
|
||||
// @ts-expect-error
|
||||
.findOrCreateCustomer(customerAddress, sanitizedBody.data.invoiceAddressStructured);
|
||||
data.invoiceAddress ||
|
||||
`${address.givenName} ${address.familyName}\n${address.streetAddress}\n${address.postalCode} ${address.addressLevel2}`;
|
||||
data.customer = await strapi.service("api::customer.customer").findOrCreateCustomer(customerAddress, address);
|
||||
}
|
||||
|
||||
const orderUnsafe = await strapi.service("api::order.order").update(order.id, {
|
||||
// @ts-expect-error
|
||||
data: sanitizedBody?.data,
|
||||
data,
|
||||
...orderDefaultParams
|
||||
});
|
||||
|
||||
@@ -131,6 +148,7 @@ export default factories.createCoreController("api::order.order", ({ strapi }) =
|
||||
const { uuid, productId } = await sanitize.contentAPI.query(ctx.params, strapi.getModel("api::order.order"));
|
||||
const order = (await strapi.service("api::order.order").findOneByUuid(uuid)) as Order;
|
||||
if (!order) return ctx.notFound("Order not found");
|
||||
if (isLockedForCustomer(order)) return ctx.conflict(ORDER_LOCKED);
|
||||
|
||||
const product = parseInt(productId as string);
|
||||
const count = parseInt(ctx.query.count as string) || 1;
|
||||
@@ -162,6 +180,7 @@ export default factories.createCoreController("api::order.order", ({ strapi }) =
|
||||
const product = parseInt(productId as string);
|
||||
|
||||
if (!order) return ctx.notFound("Order not found");
|
||||
if (isLockedForCustomer(order)) return ctx.conflict(ORDER_LOCKED);
|
||||
strapi.log.info("app:v:order-controller: - Removing product from cart", { totalBefore: order.total });
|
||||
|
||||
const updatedCart = [...(order.cart || [])];
|
||||
|
||||
Reference in new issue
Block a user