From 346b59307cffa9e7885dd9333dc7e5a3694de495 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Tue, 27 Jan 2026 12:34:49 +0100 Subject: [PATCH 01/12] Reset payment source when it is expired --- .../src/components/errors/Errors.tsx | 78 ++++++++--------- .../components/orders/PlaceOrderButton.tsx | 10 ++- .../payment_gateways/PaymentGateway.tsx | 5 +- .../payment_source/AdyenPayment.tsx | 9 +- .../src/components/utils/getAllErrors.tsx | 19 ++-- .../src/reducers/OrderReducer.ts | 9 +- .../src/reducers/PaymentMethodReducer.ts | 87 ++++++++++++------- .../react-components/src/typings/errors.ts | 77 ++++++++-------- 8 files changed, 163 insertions(+), 131 deletions(-) diff --git a/packages/react-components/src/components/errors/Errors.tsx b/packages/react-components/src/components/errors/Errors.tsx index aa609924d..44cd5097b 100644 --- a/packages/react-components/src/components/errors/Errors.tsx +++ b/packages/react-components/src/components/errors/Errors.tsx @@ -1,40 +1,40 @@ -import { useContext, useMemo, type JSX } from 'react'; -import Parent from '#components/utils/Parent' -import GiftCardContext from '#context/GiftCardContext' -import OrderContext from '#context/OrderContext' -import AddressContext from '#context/AddressContext' -import getAllErrors from '#components-utils/getAllErrors' -import LineItemContext from '#context/LineItemContext' -import LineItemChildrenContext from '#context/LineItemChildrenContext' -import type { CodeErrorType } from '#typings/errors' -import CustomerContext from '#context/CustomerContext' -import PaymentMethodContext from '#context/PaymentMethodContext' -import PaymentMethodChildrenContext from '#context/PaymentMethodChildrenContext' -import ShipmentContext from '#context/ShipmentContext' -import type { ChildrenFunction } from '#typings/index' -import InStockSubscriptionContext from '#context/InStockSubscriptionContext' +import { type JSX, useContext, useMemo } from "react" +import Parent from "#components/utils/Parent" +import getAllErrors from "#components-utils/getAllErrors" +import AddressContext from "#context/AddressContext" +import CustomerContext from "#context/CustomerContext" +import GiftCardContext from "#context/GiftCardContext" +import InStockSubscriptionContext from "#context/InStockSubscriptionContext" +import LineItemChildrenContext from "#context/LineItemChildrenContext" +import LineItemContext from "#context/LineItemContext" +import OrderContext from "#context/OrderContext" +import PaymentMethodChildrenContext from "#context/PaymentMethodChildrenContext" +import PaymentMethodContext from "#context/PaymentMethodContext" +import ShipmentContext from "#context/ShipmentContext" +import type { CodeErrorType } from "#typings/errors" +import type { ChildrenFunction } from "#typings/index" export type TResourceError = - | 'addresses' - | 'billing_address' - | 'gift_cards' - | 'gift_card_or_coupon_code' - | 'line_items' - | 'orders' - | 'payment_methods' - | 'prices' - | 'shipments' - | 'shipping_address' - | 'customer_address' - | 'sku_options' - | 'variant' - | 'in_stock_subscriptions' + | "addresses" + | "billing_address" + | "gift_cards" + | "gift_card_or_coupon_code" + | "line_items" + | "orders" + | "payment_methods" + | "prices" + | "shipments" + | "shipping_address" + | "customer_address" + | "sku_options" + | "variant" + | "in_stock_subscriptions" type ErrorChildrenComponentProps = ChildrenFunction< - Omit & { errors: string[] } + Omit & { errors: string[] } > export interface TErrorComponent - extends Omit { + extends Omit { /** * Resource which get the error */ @@ -68,12 +68,12 @@ export function Errors(props: Props): JSX.Element { const { errors: customerErrors } = useContext(CustomerContext) const { errors: shipmentErrors } = useContext(ShipmentContext) const { errors: inStockSubscriptionErrors } = useContext( - InStockSubscriptionContext + InStockSubscriptionContext, ) const { errors: paymentMethodErrors, currentPaymentMethodType, - currentPaymentMethodId + currentPaymentMethodId, } = useContext(PaymentMethodContext) const { lineItem } = useContext(LineItemChildrenContext) const allErrors = useMemo( @@ -87,8 +87,8 @@ export function Errors(props: Props): JSX.Element { ...(paymentMethodErrors?.filter( (v) => v.field === currentPaymentMethodType && - payment?.id === currentPaymentMethodId - ) || []) + payment?.id === currentPaymentMethodId, + ) || []), ], [ giftCardErrors, @@ -97,12 +97,12 @@ export function Errors(props: Props): JSX.Element { customerErrors, shipmentErrors, inStockSubscriptionErrors, - paymentMethodErrors - ] + paymentMethodErrors, + ], ).filter((v, k, a) => v?.code !== a[k - 1]?.code) const addressesErrors = useMemo( () => [...(addressErrors || [])], - [addressErrors] + [addressErrors], ) const msgErrors = getAllErrors({ allErrors: [...allErrors, ...addressesErrors], @@ -111,7 +111,7 @@ export function Errors(props: Props): JSX.Element { props: p, lineItem, resource, - returnHtml: !children + returnHtml: !children, }) const parentProps = { messages, resource, field, errors: msgErrors, ...p } return children ? ( diff --git a/packages/react-components/src/components/orders/PlaceOrderButton.tsx b/packages/react-components/src/components/orders/PlaceOrderButton.tsx index c453370b9..e35351d04 100644 --- a/packages/react-components/src/components/orders/PlaceOrderButton.tsx +++ b/packages/react-components/src/components/orders/PlaceOrderButton.tsx @@ -83,6 +83,7 @@ export function PlaceOrderButton(props: Props): JSX.Element { setPaymentSource, setPaymentMethodErrors, currentCustomerPaymentSourceId, + errors: paymentMethodErrors, } = useContext(PaymentMethodContext) const { order, setOrderErrors, errors } = useContext(OrderContext) const isFree = order?.total_amount_with_taxes_cents === 0 @@ -142,15 +143,18 @@ export function PlaceOrderButton(props: Props): JSX.Element { currentPaymentMethodType, order?.id, paymentSource?.id, - order?.total_amount_with_taxes_cents + order?.total_amount_with_taxes_cents, ]) useEffect(() => { - if (errors && errors.length > 0) { + if ( + (errors && errors.length > 0) || + (paymentMethodErrors && paymentMethodErrors.length > 0) + ) { setNotPermitted(true) setIsLoading(false) setForceDisable(false) } - }, [errors]) + }, [errors?.length, paymentMethodErrors?.length]) // biome-ignore lint/correctness/useExhaustiveDependencies: Need to test useEffect(() => { // PayPal redirect flow diff --git a/packages/react-components/src/components/payment_gateways/PaymentGateway.tsx b/packages/react-components/src/components/payment_gateways/PaymentGateway.tsx index 4cfad6641..502d2bbfe 100644 --- a/packages/react-components/src/components/payment_gateways/PaymentGateway.tsx +++ b/packages/react-components/src/components/payment_gateways/PaymentGateway.tsx @@ -118,6 +118,9 @@ export function PaymentGateway({ if (order?.payment_source?.id != null) { setLoading(false) } + if (!paymentSource) { + setLoading(true) + } } if (expressPayments && show) setLoading(false) if ( @@ -131,7 +134,7 @@ export function PaymentGateway({ return () => { setLoading(true) } - }, [order?.payment_method?.id, show, paymentSource]) + }, [order?.payment_method?.id, show, paymentSource?.id]) useEffect(() => { if (status === "placing") setLoading(true) diff --git a/packages/react-components/src/components/payment_source/AdyenPayment.tsx b/packages/react-components/src/components/payment_source/AdyenPayment.tsx index 564704118..360d9f31c 100644 --- a/packages/react-components/src/components/payment_source/AdyenPayment.tsx +++ b/packages/react-components/src/components/payment_source/AdyenPayment.tsx @@ -640,7 +640,14 @@ export function AdyenPayment({ const id: string = component._id if (id.search("scheme") === -1) { if (ref.current) { - if (id.search("paypal") === -1) { + /** + * For payment methods different from card, we remove the onsubmit handler + * to manage the submission via Adyen Drop-in and the place order button remains disabled + */ + if ( + id.search("paypal") === -1 && + id.search("giftcard") === -1 + ) { ref.current.onsubmit = async () => { return await handleSubmit( ref.current as unknown as FormEvent, diff --git a/packages/react-components/src/components/utils/getAllErrors.tsx b/packages/react-components/src/components/utils/getAllErrors.tsx index 7ebb450db..dd7bfe58f 100644 --- a/packages/react-components/src/components/utils/getAllErrors.tsx +++ b/packages/react-components/src/components/utils/getAllErrors.tsx @@ -1,22 +1,21 @@ -import customMessages from '#utils/customMessages' -import type { LineItem } from '@commercelayer/sdk' -import type { BaseError } from '#typings/errors' -import type { TResourceError } from '#components/errors/Errors' - -import type { JSX } from "react"; +import type { LineItem } from "@commercelayer/sdk" +import type { JSX } from "react" +import type { TResourceError } from "#components/errors/Errors" +import type { BaseError } from "#typings/errors" +import customMessages from "#utils/customMessages" export interface AllErrorsParams { allErrors: BaseError[] messages: BaseError[] field?: string - props: Omit + props: Omit lineItem?: LineItem | null resource?: TResourceError returnHtml?: boolean } export type GetAllErrors =

( - params: P + params: P, ) => Array const getAllErrors: GetAllErrors = (params) => { @@ -27,7 +26,7 @@ const getAllErrors: GetAllErrors = (params) => { props, lineItem, resource, - returnHtml = true + returnHtml = true, } = params return allErrors .map((v, k): JSX.Element | string | undefined => { @@ -39,7 +38,7 @@ const getAllErrors: GetAllErrors = (params) => { if (objMsg?.message) text = objMsg?.message.trim() const isEmpty = text.length === 0 if (field) { - if (v.resource === 'line_items') { + if (v.resource === "line_items") { if (lineItem && v.id === lineItem.id) { return isEmpty ? undefined : returnHtml ? ( diff --git a/packages/react-components/src/reducers/OrderReducer.ts b/packages/react-components/src/reducers/OrderReducer.ts index dae22282a..583e71240 100644 --- a/packages/react-components/src/reducers/OrderReducer.ts +++ b/packages/react-components/src/reducers/OrderReducer.ts @@ -271,7 +271,7 @@ export async function updateOrder({ state, }: UpdateOrderArgs): Promise<{ success: boolean - error?: unknown + error?: { errors: BaseError[] } order?: Order }> { const sdk = config != null ? getSdk(config) : undefined @@ -289,14 +289,9 @@ export async function updateOrder({ error, resource: "orders", }) + console.error("Update order", errors) if (dispatch) { setOrderErrors({ errors, dispatch }) - dispatch({ - type: "setErrors", - payload: { - errors, - }, - }) } return { success: false, error } } diff --git a/packages/react-components/src/reducers/PaymentMethodReducer.ts b/packages/react-components/src/reducers/PaymentMethodReducer.ts index a08e633f9..b9e4cf1cb 100644 --- a/packages/react-components/src/reducers/PaymentMethodReducer.ts +++ b/packages/react-components/src/reducers/PaymentMethodReducer.ts @@ -1,6 +1,22 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ + +import type { + AdyenPayment, + BraintreePayment, + CheckoutComPayment, + ExternalPayment, + KlarnaPayment, + Order, + PaymentMethod, + PaypalPayment, + StripePayment, + WireTransfer, +} from "@commercelayer/sdk" +import type { Dispatch, MutableRefObject } from "react" import type { AdyenPaymentConfig } from "#components/payment_source/AdyenPayment" import type { BraintreeConfig } from "#components/payment_source/BraintreePayment" +import type { CheckoutComConfig } from "#components/payment_source/CheckoutComPayment" +import type { ExternalPaymentConfig } from "#components/payment_source/ExternalPayment" import type { PaypalConfig } from "#components/payment_source/PaypalPayment" import type { StripeConfig } from "#components/payment_source/StripePayment" import type { WireTransferConfig } from "#components/payment_source/WireTransferPayment" @@ -9,26 +25,11 @@ import type { getOrderContext, updateOrder } from "#reducers/OrderReducer" import type { BaseError } from "#typings/errors" import baseReducer from "#utils/baseReducer" import getErrors, { setErrors } from "#utils/getErrors" +import type { ResourceKeys } from "#utils/getPaymentAttributes" import getSdk from "#utils/getSdk" -import type { - Order, - PaymentMethod, - StripePayment, - WireTransfer, - AdyenPayment, - BraintreePayment, - CheckoutComPayment, - ExternalPayment, - PaypalPayment, - KlarnaPayment, -} from "@commercelayer/sdk" -import type { Dispatch, MutableRefObject } from "react" -import type { CheckoutComConfig } from "#components/payment_source/CheckoutComPayment" -import type { ExternalPaymentConfig } from "#components/payment_source/ExternalPayment" -import { snakeToCamelCase } from "#utils/snakeToCamelCase" -import { replace } from "#utils/replace" import { pick } from "#utils/pick" -import type { ResourceKeys } from "#utils/getPaymentAttributes" +import { replace } from "#utils/replace" +import { snakeToCamelCase } from "#utils/snakeToCamelCase" export type PaymentSourceType = Order["payment_source"] @@ -224,7 +225,11 @@ export async function setPaymentMethod({ updateOrder, setOrderErrors, paymentResource, -}: TSetPaymentMethodParams): Promise<{ success: boolean; order?: Order }> { +}: TSetPaymentMethodParams): Promise<{ + success: boolean + order?: Order + error?: { errors: BaseError[] } +}> { let response: { success: boolean; order?: Order } = { success: false, } @@ -237,6 +242,16 @@ export async function setPaymentMethod({ } if (updateOrder != null) { const currentOrder = await updateOrder({ id: order.id, attributes }) + if (!currentOrder.success && currentOrder.error) { + const errors = getErrors({ + error: currentOrder.error, + resource: "orders", + field: paymentResource, + }) + console.error("Set payment method", errors) + setOrderErrors?.(errors) + return response + } response = currentOrder } dispatch({ @@ -355,7 +370,22 @@ export async function setPaymentSource({ resource: "payment_methods", field: paymentResource, }) - console.error("Set payment source:", errors) + const expiredErrors = errors.filter((v) => v?.meta?.error === "expired") + if (expiredErrors.length > 0 && order && config) { + console.error("Set payment source - expired:", expiredErrors) + destroyPaymentSource({ + paymentSourceId: order.payment_source?.id || "", + paymentResource, + dispatch, + }) + // resetPaymentSource({ + // orderId: order.id, + // paymentResource, + // config: config, + // getOrder, + // dispatch, + // }) + } if (errors != null && errors?.length > 0) { const [error] = errors if (error?.status === "401" && getOrder != null && order != null) { @@ -435,21 +465,12 @@ export const destroyPaymentSource: DestroyPaymentSource = async ({ paymentSourceId, paymentResource, dispatch, - // updateOrder, - // orderId, }) => { if (paymentSourceId && paymentResource) { - // await updateOrder({ - // id: orderId, - // attributes: { - // payment_source: {}, - // }, - // }) - if (dispatch) - dispatch({ - type: "setPaymentSource", - payload: { paymentSource: undefined }, - }) + dispatch?.({ + type: "setPaymentSource", + payload: { paymentSource: undefined }, + }) } } diff --git a/packages/react-components/src/typings/errors.ts b/packages/react-components/src/typings/errors.ts index ae5f7d46a..764b99dec 100644 --- a/packages/react-components/src/typings/errors.ts +++ b/packages/react-components/src/typings/errors.ts @@ -1,42 +1,42 @@ -import type { TResourceError } from '#components/errors/Errors' +import type { TResourceError } from "#components/errors/Errors" export type CodeErrorType = - | 'EMPTY_ERROR' - | 'FILTER_NOT_ALLOWED' - | 'FORBIDDEN' - | 'INTERNAL_SERVER_ERROR' - | 'INVALID_DATA_FORMAT' - | 'INVALID_FIELD' - | 'INVALID_FIELD_FORMAT' - | 'INVALID_FIELD_VALUE' - | 'INVALID_FILTERS_SYNTAX' - | 'INVALID_FILTER_VALUE' - | 'INVALID_INCLUDE' - | 'INVALID_LINKS_OBJECT' - | 'INVALID_PAGE_OBJECT' - | 'INVALID_PAGE_VALUE' - | 'INVALID_RESOURCE' - | 'INVALID_RESOURCE_ID' - | 'INVALID_SORT_CRITERIA' - | 'INVALID_TOKEN' - | 'KEY_NOT_INCLUDED_IN_URL' - | 'KEY_ORDER_MISMATCH' - | 'LOCKED' - | 'NOT_ACCEPTABLE' - | 'OUT_OF_STOCK' - | 'PARAM_MISSING' - | 'PARAM_NOT_ALLOWED' - | 'PAYMENT_NOT_APPROVED_FOR_EXECUTION' - | 'PAYMENT_INTENT_AUTHENTICATION_FAILURE' - | 'RECORD_NOT_FOUND' - | 'RECORD_NOT_FOUND' - | 'RELATION_EXISTS' - | 'NO_SHIPPING_METHODS' - | 'SAVE_FAILED' - | 'TYPE_MISMATCH' - | 'UNAUTHORIZED' - | 'UNSUPPORTED_MEDIA_TYPE' - | 'VALIDATION_ERROR' + | "EMPTY_ERROR" + | "FILTER_NOT_ALLOWED" + | "FORBIDDEN" + | "INTERNAL_SERVER_ERROR" + | "INVALID_DATA_FORMAT" + | "INVALID_FIELD" + | "INVALID_FIELD_FORMAT" + | "INVALID_FIELD_VALUE" + | "INVALID_FILTERS_SYNTAX" + | "INVALID_FILTER_VALUE" + | "INVALID_INCLUDE" + | "INVALID_LINKS_OBJECT" + | "INVALID_PAGE_OBJECT" + | "INVALID_PAGE_VALUE" + | "INVALID_RESOURCE" + | "INVALID_RESOURCE_ID" + | "INVALID_SORT_CRITERIA" + | "INVALID_TOKEN" + | "KEY_NOT_INCLUDED_IN_URL" + | "KEY_ORDER_MISMATCH" + | "LOCKED" + | "NOT_ACCEPTABLE" + | "OUT_OF_STOCK" + | "PARAM_MISSING" + | "PARAM_NOT_ALLOWED" + | "PAYMENT_NOT_APPROVED_FOR_EXECUTION" + | "PAYMENT_INTENT_AUTHENTICATION_FAILURE" + | "RECORD_NOT_FOUND" + | "RECORD_NOT_FOUND" + | "RELATION_EXISTS" + | "NO_SHIPPING_METHODS" + | "SAVE_FAILED" + | "TYPE_MISMATCH" + | "UNAUTHORIZED" + | "UNSUPPORTED_MEDIA_TYPE" + | "VALIDATION_ERROR" export interface BaseError { code: CodeErrorType @@ -47,6 +47,9 @@ export interface BaseError { title?: string detail?: string status?: string + meta?: { + error: string + } } export interface TAPIError { From 7a1ccd9d22bc20c51ad3c4dd4299823b60413b59 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Tue, 27 Jan 2026 17:07:50 +0100 Subject: [PATCH 02/12] v4.29.0-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 3f90ff2bc..b77956545 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.28.8", + "version": "4.29.0-beta.0", "command": { "version": { "preid": "beta" diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 89149c07b..8de4a7f77 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@commercelayer/react-components", - "version": "4.28.8", + "version": "4.29.0-beta.0", "description": "The Official Commerce Layer React Components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", From 24afa5c32b7b2e62665e8b851f1963aba1fa1809 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Fri, 30 Jan 2026 12:21:08 +0100 Subject: [PATCH 03/12] Nullify payment source if there is a new total amount --- .../src/reducers/OrderReducer.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/react-components/src/reducers/OrderReducer.ts b/packages/react-components/src/reducers/OrderReducer.ts index 583e71240..28a2d815c 100644 --- a/packages/react-components/src/reducers/OrderReducer.ts +++ b/packages/react-components/src/reducers/OrderReducer.ts @@ -165,6 +165,7 @@ export async function createOrder(params: CreateOrderParams): Promise { } persistKey && setLocalOrder && setLocalOrder(persistKey, o.id) return o.id + // biome-ignore lint/suspicious/noExplicitAny: No types information about the error } catch (error: any) { const errors = getErrors({ error, @@ -278,10 +279,25 @@ export async function updateOrder({ try { if (sdk == null) return { success: false } const resource = { ...attributes, id } + // Take note of current total amount with taxes cents (used in some cases like Adyen and the payment source is expired and needs to be updated) + const currentTotalAmountWithTaxesCents = + state?.order?.total_amount_with_taxes_cents // const order = await sdk.orders.update(resource, { include }) await sdk.orders.update(resource, { include }) // NOTE: Retrieve doesn't response with attributes updated - const order = await getApiOrder({ id, config, dispatch, state }) + let order = await getApiOrder({ id, config, dispatch, state }) + const newTotalAmountWithTaxesCents = order?.total_amount_with_taxes_cents + if ( + currentTotalAmountWithTaxesCents !== newTotalAmountWithTaxesCents && + order?.payment_source?.id + ) { + // If the total amount with taxes cents has changed, we need to update the payment source + await sdk.orders.update({ + id, + payment_source: null, + }) + order = await getApiOrder({ id, config, dispatch, state }) + } dispatch && order && dispatch({ type: "setOrder", payload: { order } }) return { success: true, order } } catch (error: any) { From 1843aef2472c9b30e51a1f85d124109e8c0b2d25 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Fri, 30 Jan 2026 12:21:33 +0100 Subject: [PATCH 04/12] v4.29.0-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 b77956545..c55a9c784 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.29.0-beta.0", + "version": "4.29.0-beta.1", "command": { "version": { "preid": "beta" diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 8de4a7f77..6056965a7 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@commercelayer/react-components", - "version": "4.29.0-beta.0", + "version": "4.29.0-beta.1", "description": "The Official Commerce Layer React Components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", From d0b9abb3a0a5755b0ae891918de23df5039cfa64 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Fri, 30 Jan 2026 18:10:05 +0100 Subject: [PATCH 05/12] Fix check amount with expire payment source --- packages/react-components/src/reducers/OrderReducer.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/react-components/src/reducers/OrderReducer.ts b/packages/react-components/src/reducers/OrderReducer.ts index 28a2d815c..1e2df7e5e 100644 --- a/packages/react-components/src/reducers/OrderReducer.ts +++ b/packages/react-components/src/reducers/OrderReducer.ts @@ -289,8 +289,14 @@ export async function updateOrder({ const newTotalAmountWithTaxesCents = order?.total_amount_with_taxes_cents if ( currentTotalAmountWithTaxesCents !== newTotalAmountWithTaxesCents && - order?.payment_source?.id + order?.payment_source?.id && + // @ts-expect-error TS2532 + order?.payment_source?.expires_at <= new Date().toISOString() ) { + console.log( + "Total amount with taxes cents changed, updating payment source...", + { old: state?.order, new: order }, + ) // If the total amount with taxes cents has changed, we need to update the payment source await sdk.orders.update({ id, @@ -300,6 +306,7 @@ export async function updateOrder({ } dispatch && order && dispatch({ type: "setOrder", payload: { order } }) return { success: true, order } + // biome-ignore lint/suspicious/noExplicitAny: No types information about the error } catch (error: any) { const errors = getErrors({ error, From 0540f61addb716b423b6a79bee9ed50b0f156a44 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Fri, 30 Jan 2026 18:10:30 +0100 Subject: [PATCH 06/12] v4.29.0-beta.2 --- 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 c55a9c784..375a5f091 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.29.0-beta.1", + "version": "4.29.0-beta.2", "command": { "version": { "preid": "beta" diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 6056965a7..674d703c6 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@commercelayer/react-components", - "version": "4.29.0-beta.1", + "version": "4.29.0-beta.2", "description": "The Official Commerce Layer React Components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", From 0def96a8ab65d9fdfb124a14f9cf27404f77f5d3 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Mon, 2 Feb 2026 13:43:16 +0100 Subject: [PATCH 07/12] Fix filter errors --- .../src/reducers/PaymentMethodReducer.ts | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/react-components/src/reducers/PaymentMethodReducer.ts b/packages/react-components/src/reducers/PaymentMethodReducer.ts index b9e4cf1cb..6ee591629 100644 --- a/packages/react-components/src/reducers/PaymentMethodReducer.ts +++ b/packages/react-components/src/reducers/PaymentMethodReducer.ts @@ -370,23 +370,16 @@ export async function setPaymentSource({ resource: "payment_methods", field: paymentResource, }) - const expiredErrors = errors.filter((v) => v?.meta?.error === "expired") - if (expiredErrors.length > 0 && order && config) { - console.error("Set payment source - expired:", expiredErrors) - destroyPaymentSource({ - paymentSourceId: order.payment_source?.id || "", - paymentResource, - dispatch, - }) - // resetPaymentSource({ - // orderId: order.id, - // paymentResource, - // config: config, - // getOrder, - // dispatch, - // }) - } if (errors != null && errors?.length > 0) { + const expiredErrors = errors.filter((v) => v?.meta?.error === "expired") + if (expiredErrors.length > 0 && order && config) { + console.error("Set payment source - expired:", expiredErrors) + destroyPaymentSource({ + paymentSourceId: order.payment_source?.id || "", + paymentResource, + dispatch, + }) + } const [error] = errors if (error?.status === "401" && getOrder != null && order != null) { const currentOrder = await getOrder(order?.id) From 18f536f9335033bc7659d11517a52119ad5632b7 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Mon, 2 Feb 2026 13:43:47 +0100 Subject: [PATCH 08/12] v4.29.0-beta.3 --- 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 375a5f091..2ff698e4d 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.29.0-beta.2", + "version": "4.29.0-beta.3", "command": { "version": { "preid": "beta" diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 674d703c6..a4a45cefa 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@commercelayer/react-components", - "version": "4.29.0-beta.2", + "version": "4.29.0-beta.3", "description": "The Official Commerce Layer React Components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", From 13db24d94fddc02f5ad4c3c256f3d3ba2be74bc0 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Mon, 2 Feb 2026 17:07:30 +0100 Subject: [PATCH 09/12] Fix place order button --- .../components/orders/PlaceOrderButton.tsx | 2 + .../payment_source/StripePayment.tsx | 63 ++++++++++--------- .../src/reducers/OrderReducer.ts | 4 -- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/packages/react-components/src/components/orders/PlaceOrderButton.tsx b/packages/react-components/src/components/orders/PlaceOrderButton.tsx index e35351d04..3f22ff082 100644 --- a/packages/react-components/src/components/orders/PlaceOrderButton.tsx +++ b/packages/react-components/src/components/orders/PlaceOrderButton.tsx @@ -153,6 +153,8 @@ export function PlaceOrderButton(props: Props): JSX.Element { setNotPermitted(true) setIsLoading(false) setForceDisable(false) + } else { + setNotPermitted(false) } }, [errors?.length, paymentMethodErrors?.length]) // biome-ignore lint/correctness/useExhaustiveDependencies: Need to test diff --git a/packages/react-components/src/components/payment_source/StripePayment.tsx b/packages/react-components/src/components/payment_source/StripePayment.tsx index 7cee18e47..4d9085336 100644 --- a/packages/react-components/src/components/payment_source/StripePayment.tsx +++ b/packages/react-components/src/components/payment_source/StripePayment.tsx @@ -67,8 +67,12 @@ function StripePaymentForm({ stripe, }: StripePaymentFormProps): JSX.Element { const ref = useRef(null) - const { currentPaymentMethodType, setPaymentMethodErrors, setPaymentRef } = - useContext(PaymentMethodContext) + const { + errors, + currentPaymentMethodType, + setPaymentMethodErrors, + setPaymentRef, + } = useContext(PaymentMethodContext) const { order, setOrderErrors } = useContext(OrderContext) const { sdkClient } = useCommerceLayer() const { setPlaceOrderStatus } = useContext(PlaceOrderContext) @@ -176,35 +180,38 @@ function StripePaymentForm({ } async function handleChange(event: StripePaymentElementChangeEvent) { - console.debug("StripePaymentElement onChange event", { event }) selectedPaymentMethodType = event.value.type + console.log("Errors", errors) // Handle change events from the PaymentElement - if ( - event.complete && - ["apple_pay", "google_pay"].includes(event.value.type) - ) { - const sdk = sdkClient() - if (sdk == null) return - if (order == null) return - const { status } = await sdk.orders.retrieve(order?.id, { - fields: ["status"], - }) - const isDraftOrder = status === "draft" - if (isDraftOrder) { - /** - * Draft order cannot be placed - */ - setOrderErrors([ - { - code: "VALIDATION_ERROR", - resource: "orders", - message: "Draft order cannot be placed", - }, - ]) - setPlaceOrderStatus?.({ - status: "disabled", + if (event.complete) { + if (errors && errors.length > 0) { + setPaymentMethodErrors([]) + } + if (["apple_pay", "google_pay"].includes(event.value.type)) { + const sdk = sdkClient() + if (sdk == null) return + if (order == null) return + // Reset payment method errors + const { status } = await sdk.orders.retrieve(order?.id, { + fields: ["status"], }) - return + const isDraftOrder = status === "draft" + if (isDraftOrder) { + /** + * Draft order cannot be placed + */ + setOrderErrors([ + { + code: "VALIDATION_ERROR", + resource: "orders", + message: "Draft order cannot be placed", + }, + ]) + setPlaceOrderStatus?.({ + status: "disabled", + }) + return + } } } } diff --git a/packages/react-components/src/reducers/OrderReducer.ts b/packages/react-components/src/reducers/OrderReducer.ts index 1e2df7e5e..13b4e562a 100644 --- a/packages/react-components/src/reducers/OrderReducer.ts +++ b/packages/react-components/src/reducers/OrderReducer.ts @@ -293,10 +293,6 @@ export async function updateOrder({ // @ts-expect-error TS2532 order?.payment_source?.expires_at <= new Date().toISOString() ) { - console.log( - "Total amount with taxes cents changed, updating payment source...", - { old: state?.order, new: order }, - ) // If the total amount with taxes cents has changed, we need to update the payment source await sdk.orders.update({ id, From 25de5d1313e195bc84fdb3de46b7586e05c0a090 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Mon, 2 Feb 2026 17:07:54 +0100 Subject: [PATCH 10/12] v4.29.0-beta.4 --- 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 2ff698e4d..87ae18b8a 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.29.0-beta.3", + "version": "4.29.0-beta.4", "command": { "version": { "preid": "beta" diff --git a/packages/react-components/package.json b/packages/react-components/package.json index a4a45cefa..0b2098326 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@commercelayer/react-components", - "version": "4.29.0-beta.3", + "version": "4.29.0-beta.4", "description": "The Official Commerce Layer React Components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js", From f00e3c0fb2ffe1c1652d6b22f53071d1e4e3577d Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Wed, 4 Feb 2026 17:48:14 +0100 Subject: [PATCH 11/12] Fix payment show card when there is a givex authorized and the user refresh the page --- .../src/components/payment_source/PaymentSource.tsx | 4 +++- .../src/components/payment_source/StripePayment.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/react-components/src/components/payment_source/PaymentSource.tsx b/packages/react-components/src/components/payment_source/PaymentSource.tsx index 9ec0d05d0..b9929a64b 100644 --- a/packages/react-components/src/components/payment_source/PaymentSource.tsx +++ b/packages/react-components/src/components/payment_source/PaymentSource.tsx @@ -77,7 +77,9 @@ export function PaymentSource(props: PaymentSourceProps): JSX.Element { errors?.length === 0 && checkPaymentSourceStatus !== "declined" ) { - setShowCard(true) + if (card.brand !== "giftcard") { + setShowCard(true) + } } if (checkPaymentSourceStatus === "declined") { setShowCard(false) diff --git a/packages/react-components/src/components/payment_source/StripePayment.tsx b/packages/react-components/src/components/payment_source/StripePayment.tsx index 4d9085336..370132d4d 100644 --- a/packages/react-components/src/components/payment_source/StripePayment.tsx +++ b/packages/react-components/src/components/payment_source/StripePayment.tsx @@ -181,9 +181,11 @@ function StripePaymentForm({ async function handleChange(event: StripePaymentElementChangeEvent) { selectedPaymentMethodType = event.value.type - console.log("Errors", errors) // Handle change events from the PaymentElement if (event.complete) { + /** + * Clear payment method errors on complete event + */ if (errors && errors.length > 0) { setPaymentMethodErrors([]) } From 704525b44d7ed597ac34322962ef4d5e4fc0d973 Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Wed, 4 Feb 2026 17:48:33 +0100 Subject: [PATCH 12/12] v4.29.0-beta.5 --- 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 87ae18b8a..3b1863926 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.29.0-beta.4", + "version": "4.29.0-beta.5", "command": { "version": { "preid": "beta" diff --git a/packages/react-components/package.json b/packages/react-components/package.json index 0b2098326..8597dfa3b 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -1,6 +1,6 @@ { "name": "@commercelayer/react-components", - "version": "4.29.0-beta.4", + "version": "4.29.0-beta.5", "description": "The Official Commerce Layer React Components", "main": "lib/cjs/index.js", "module": "lib/esm/index.js",