diff --git a/lerna.json b/lerna.json index d3f5151c..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.2", + "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 adfb1859..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.2", + "version": "4.23.3-beta.1", "description": "The Official Commerce Layer React Components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", diff --git a/packages/react-components/src/components/orders/PlaceOrderButton.tsx b/packages/react-components/src/components/orders/PlaceOrderButton.tsx index 36efc4f5..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(() => { @@ -299,9 +301,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) { @@ -313,8 +344,23 @@ export function PlaceOrderButton(props: Props): JSX.Element { ): Promise => { e?.preventDefault() e?.stopPropagation() - const isAlreadyPlaced = order?.status === "placed" + 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 + */ setPlaceOrderStatus?.({ status: "placing" }) onClick?.({ placed: true, @@ -322,6 +368,31 @@ 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", + }, + ], + }) + setOrderErrors([ + { + code: "VALIDATION_ERROR", + resource: "orders", + message: "Draft order cannot be placed", + }, + ]) + return + } setIsLoading(true) let isValid = true setForceDisable(true) @@ -360,6 +431,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..6e3a76d7 100644 --- a/packages/react-components/src/components/payment_source/CheckoutComPayment.tsx +++ b/packages/react-components/src/components/payment_source/CheckoutComPayment.tsx @@ -190,11 +190,31 @@ 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") - 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 } 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,