Skip to content

CKO show payment error #637

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion packages/react-components/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Props, "children"> {
/**
Expand Down Expand Up @@ -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,
Expand All @@ -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(() => {
Expand Down Expand Up @@ -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) {
Expand All @@ -313,15 +344,55 @@ export function PlaceOrderButton(props: Props): JSX.Element {
): Promise<void> => {
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,
order: order,
})
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)
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
3 changes: 2 additions & 1 deletion packages/react-components/src/reducers/OrderReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type GetOrderParams = Partial<{
id: string
persistKey: string
state: OrderState
options: QueryParamsRetrieve
}>

export type GetOrder = (params: GetOrderParams) => Promise<undefined | Order>
Expand Down Expand Up @@ -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
}
Expand Down
27 changes: 1 addition & 26 deletions packages/react-components/src/reducers/PlaceOrderReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface PlaceOrderOptions {
redirectResult?: string
}
checkoutCom?: {
session_id: string
session_id: string | undefined
}
stripe?: {
/**
Expand Down Expand Up @@ -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,
Expand Down
Loading