diff --git a/src/api/order/controllers/order.ts b/src/api/order/controllers/order.ts index d0d2830..1cc7cec 100644 --- a/src/api/order/controllers/order.ts +++ b/src/api/order/controllers/order.ts @@ -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 || [])];