From 7940948f532597c4ca50cbfc0fe921bd5f8b3c8b Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Thu, 22 May 2025 19:13:06 +0200 Subject: [PATCH 1/4] Fix show CKO payment error --- .../components/orders/PlaceOrderButton.tsx | 70 ++++++++++++++++++- .../payment_source/CheckoutComPayment.tsx | 18 ++++- .../src/reducers/PlaceOrderReducer.ts | 27 +------ 3 files changed, 86 insertions(+), 29 deletions(-) diff --git a/packages/react-components/src/components/orders/PlaceOrderButton.tsx b/packages/react-components/src/components/orders/PlaceOrderButton.tsx index 36efc4f5..b9c7b644 100644 --- a/packages/react-components/src/components/orders/PlaceOrderButton.tsx +++ b/packages/react-components/src/components/orders/PlaceOrderButton.tsx @@ -299,9 +299,38 @@ export function PlaceOrderButton(props: Props): JSX.Element { ["draft", "pending"].includes(order?.status) && autoPlaceOrder ) { - handleClick() + // @ts-expect-error no type + const paymentResponse = order?.payment_source?.payment_response + const paymentStatus = paymentResponse?.status?.toLowerCase() + if (paymentStatus === "pending") { + setPaymentSource({ + paymentSourceId: paymentSource?.id, + paymentResource: "checkout_com_payments", + attributes: { + _details: 1, + }, + }).then((res) => { + // @ts-expect-error no type + const paymentStatus: string = res?.payment_response?.status + if (paymentStatus.toLowerCase() === "authorized") { + handleClick() + } else { + if (options?.checkoutCom) { + options.checkoutCom.session_id = undefined + } + setPaymentMethodErrors([ + { + code: "PAYMENT_INTENT_AUTHENTICATION_FAILURE", + resource: "payment_methods", + field: currentPaymentMethodType, + message: paymentStatus, + }, + ]) + } + }) + } } - }, [options?.checkoutCom, paymentType]) + }, [options?.checkoutCom, paymentType, order?.payment_source?.id]) // biome-ignore lint/correctness/useExhaustiveDependencies: Need to test useEffect(() => { if (ref?.current != null && setButtonRef != null) { @@ -314,7 +343,11 @@ export function PlaceOrderButton(props: Props): JSX.Element { e?.preventDefault() e?.stopPropagation() const isAlreadyPlaced = order?.status === "placed" + const isDraftOrder = order?.status === "draft" if (isAlreadyPlaced) { + /** + * Order already placed + */ setPlaceOrderStatus?.({ status: "placing" }) onClick?.({ placed: true, @@ -322,6 +355,24 @@ export function PlaceOrderButton(props: Props): JSX.Element { }) return } + if (isDraftOrder) { + /** + * Draft order cannot be placed + */ + setPlaceOrderStatus?.({ status: "standby" }) + onClick?.({ + placed: false, + order: order, + errors: [ + { + code: "VALIDATION_ERROR", + resource: "orders", + message: "Draft order cannot be placed", + }, + ], + }) + return + } setIsLoading(true) let isValid = true setForceDisable(true) @@ -360,6 +411,21 @@ export function PlaceOrderButton(props: Props): JSX.Element { ) { isValid = true } + } else if ( + currentPaymentMethodRef?.current?.onsubmit && + options?.checkoutCom?.session_id && + // @ts-expect-error no type + checkPaymentSource?.payment_response?.status?.toLowerCase() === "declined" + ) { + /** + * Permit to place order with declined payment using Checkout.com + */ + isValid = (await currentPaymentMethodRef.current?.onsubmit({ + // @ts-expect-error no type + paymentSource: checkPaymentSource, + setPlaceOrder, + onclickCallback: onClick, + })) as boolean } else if (card?.brand) { isValid = true } diff --git a/packages/react-components/src/components/payment_source/CheckoutComPayment.tsx b/packages/react-components/src/components/payment_source/CheckoutComPayment.tsx index 928df0f6..5e62d759 100644 --- a/packages/react-components/src/components/payment_source/CheckoutComPayment.tsx +++ b/packages/react-components/src/components/payment_source/CheckoutComPayment.tsx @@ -190,7 +190,23 @@ export function CheckoutComPayment({ } }, onError: (component, error) => { - console.error("onError", error, "Component", component.type) + console.error("onError", { error }, "Component", component.type) + }, + onPaymentCompleted: async (component, paymentResponse) => { + console.log("onPaymentCompleted -----", { + paymentResponse, + component, + ps, + }) + const paymentSource = await setPaymentSource({ + paymentSourceId: ps.id, + paymentResource: "checkout_com_payments", + attributes: { + token: paymentResponse.id, + _authorize: true, + }, + }) + console.log("paymentSource", { paymentSource }) }, } satisfies CheckoutWebComponent) const flowComponent = checkout.create("flow") diff --git a/packages/react-components/src/reducers/PlaceOrderReducer.ts b/packages/react-components/src/reducers/PlaceOrderReducer.ts index b57f0499..7ca66323 100644 --- a/packages/react-components/src/reducers/PlaceOrderReducer.ts +++ b/packages/react-components/src/reducers/PlaceOrderReducer.ts @@ -32,7 +32,7 @@ export interface PlaceOrderOptions { redirectResult?: string } checkoutCom?: { - session_id: string + session_id: string | undefined } stripe?: { /** @@ -206,31 +206,6 @@ export async function setPlaceOrder({ paypal_payer_id: options?.paypalPayerId, }) } - if ( - paymentType === "checkout_com_payments" && - paymentSource && - options?.checkoutCom?.session_id - ) { - const payment = await sdk[paymentType].update({ - id: paymentSource.id, - _details: true, - }) - // @ts-expect-error no type - if (payment?.payment_response?.status !== "Authorized") { - // @ts-expect-error no type - const [action] = payment?.payment_response?.actions || [""] - const errors: BaseError[] = [ - { - code: "PAYMENT_NOT_APPROVED_FOR_EXECUTION", - message: action?.response_summary, - resource: "orders", - field: "checkout_com_payments", - }, - ] - // eslint-disable-next-line @typescript-eslint/no-throw-literal - throw { errors } - } - } const updateAttributes: OrderUpdate = { id: order.id, _place: true, From c2ab2fbc8479f1333863c7dfb97630df7d78cba1 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Thu, 22 May 2025 19:17:23 +0200 Subject: [PATCH 2/4] v4.23.3-beta.0 --- lerna.json | 2 +- packages/react-components/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lerna.json b/lerna.json index d3f5151c..6ba4cf0c 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useNx": false, "npmClient": "pnpm", - "version": "4.23.2", + "version": "4.23.3-beta.0", "command": { "version": { "preid": "beta" diff --git a/packages/react-components/package.json b/packages/react-components/package.json index adfb1859..6445d592 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@commercelayer/react-components", - "version": "4.23.2", + "version": "4.23.3-beta.0", "description": "The Official Commerce Layer React Components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", From f0cb7a8c401fb3af36c19996635abc07b73acaa9 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Fri, 23 May 2025 19:02:43 +0200 Subject: [PATCH 3/4] Fix place draft order --- .../components/orders/PlaceOrderButton.tsx | 26 ++++++++++-- .../payment_source/CheckoutComPayment.tsx | 40 ++++++++++--------- .../src/reducers/OrderReducer.ts | 3 +- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/packages/react-components/src/components/orders/PlaceOrderButton.tsx b/packages/react-components/src/components/orders/PlaceOrderButton.tsx index b9c7b644..66df44ee 100644 --- a/packages/react-components/src/components/orders/PlaceOrderButton.tsx +++ b/packages/react-components/src/components/orders/PlaceOrderButton.tsx @@ -17,6 +17,7 @@ import getCardDetails from "#utils/getCardDetails" import type { BaseError } from "#typings/errors" import type { Order } from "@commercelayer/sdk" import { checkPaymentIntent } from "#utils/stripe/retrievePaymentIntent" +import useCommerceLayer from "#hooks/useCommerceLayer" interface ChildrenProps extends Omit { /** @@ -73,6 +74,7 @@ export function PlaceOrderButton(props: Props): JSX.Element { const [notPermitted, setNotPermitted] = useState(true) const [forceDisable, setForceDisable] = useState(disabled) const [isLoading, setIsLoading] = useState(false) + const { sdkClient } = useCommerceLayer() const { currentPaymentMethodRef, loading, @@ -82,7 +84,7 @@ export function PlaceOrderButton(props: Props): JSX.Element { setPaymentMethodErrors, currentCustomerPaymentSourceId, } = useContext(PaymentMethodContext) - const { order } = useContext(OrderContext) + const { order, setOrderErrors } = useContext(OrderContext) const isFree = order?.total_amount_with_taxes_cents === 0 // biome-ignore lint/correctness/useExhaustiveDependencies: Need to test useEffect(() => { @@ -342,8 +344,19 @@ export function PlaceOrderButton(props: Props): JSX.Element { ): Promise => { e?.preventDefault() e?.stopPropagation() - const isAlreadyPlaced = order?.status === "placed" - const isDraftOrder = order?.status === "draft" + const sdk = sdkClient() + if (sdk == null) return + if (order == null) return + /** + * Check if the order is already placed or in draft status to avoid placing it again + * and to prevent placing a draft order + * @see https://docs.commercelayer.io/core/how-tos/placing-orders/checkout/placing-the-order + */ + const { status } = await sdk.orders.retrieve(order?.id, { + fields: ["status"], + }) + const isAlreadyPlaced = status === "placed" + const isDraftOrder = status === "draft" if (isAlreadyPlaced) { /** * Order already placed @@ -371,6 +384,13 @@ export function PlaceOrderButton(props: Props): JSX.Element { }, ], }) + setOrderErrors([ + { + code: "VALIDATION_ERROR", + resource: "orders", + message: "Draft order cannot be placed", + }, + ]) return } setIsLoading(true) diff --git a/packages/react-components/src/components/payment_source/CheckoutComPayment.tsx b/packages/react-components/src/components/payment_source/CheckoutComPayment.tsx index 5e62d759..6e3a76d7 100644 --- a/packages/react-components/src/components/payment_source/CheckoutComPayment.tsx +++ b/packages/react-components/src/components/payment_source/CheckoutComPayment.tsx @@ -192,25 +192,29 @@ export function CheckoutComPayment({ onError: (component, error) => { console.error("onError", { error }, "Component", component.type) }, - onPaymentCompleted: async (component, paymentResponse) => { - console.log("onPaymentCompleted -----", { - paymentResponse, - component, - ps, - }) - const paymentSource = await setPaymentSource({ - paymentSourceId: ps.id, - paymentResource: "checkout_com_payments", - attributes: { - token: paymentResponse.id, - _authorize: true, - }, - }) - console.log("paymentSource", { paymentSource }) - }, + // onPaymentCompleted: async (component, paymentResponse) => { + // console.log("onPaymentCompleted -----", { + // paymentResponse, + // component, + // ps, + // }) + // const paymentSource = await setPaymentSource({ + // paymentSourceId: ps.id, + // paymentResource: "checkout_com_payments", + // attributes: { + // token: paymentResponse.id, + // _authorize: true, + // }, + // }) + // console.log("paymentSource", { paymentSource }) + // }, } satisfies CheckoutWebComponent) - const flowComponent = checkout.create("flow") - flowComponent.mount(document.getElementById("flow-container")) + // const flowComponent = checkout.create("flow") + const flowComponent = checkout.create("card") + if (await flowComponent.isAvailable()) { + flowComponent.mount(document.getElementById("flow-container")) + } + // flowComponent.mount(document.getElementById("flow-container")) } loadFlow() } diff --git a/packages/react-components/src/reducers/OrderReducer.ts b/packages/react-components/src/reducers/OrderReducer.ts index c1f68821..f8eb3e41 100644 --- a/packages/react-components/src/reducers/OrderReducer.ts +++ b/packages/react-components/src/reducers/OrderReducer.ts @@ -37,6 +37,7 @@ export type GetOrderParams = Partial<{ id: string persistKey: string state: OrderState + options: QueryParamsRetrieve }> export type GetOrder = (params: GetOrderParams) => Promise @@ -192,11 +193,11 @@ export const getApiOrder: GetOrder = async ( persistKey, deleteLocalOrder, state, + options = {}, } = params const sdk = config != null ? getSdk(config) : undefined try { if (sdk == null) return undefined - const options: QueryParamsRetrieve = {} if (state?.include && state.include.length > 0) { options.include = state.include } From cd3a1984f65bdf63118f75e2f6e4868a5ad7bd85 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Fri, 23 May 2025 19:03:17 +0200 Subject: [PATCH 4/4] v4.23.3-beta.1 --- lerna.json | 2 +- packages/react-components/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lerna.json b/lerna.json index 6ba4cf0c..896d7f9e 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useNx": false, "npmClient": "pnpm", - "version": "4.23.3-beta.0", + "version": "4.23.3-beta.1", "command": { "version": { "preid": "beta" diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 6445d592..a6e0cd6f 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@commercelayer/react-components", - "version": "4.23.3-beta.0", + "version": "4.23.3-beta.1", "description": "The Official Commerce Layer React Components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js",