diff --git a/packages/react-components/src/components/orders/PlaceOrderButton.tsx b/packages/react-components/src/components/orders/PlaceOrderButton.tsx index 51e9d32a..8fa678e0 100644 --- a/packages/react-components/src/components/orders/PlaceOrderButton.tsx +++ b/packages/react-components/src/components/orders/PlaceOrderButton.tsx @@ -5,8 +5,8 @@ import { useRef, useState, type MouseEvent, - type JSX, -} from 'react'; + type JSX +} from 'react' import Parent from '../utils/Parent' import { type ChildrenFunction } from '#typings/index' import PlaceOrderContext from '#context/PlaceOrderContext' @@ -67,7 +67,8 @@ export function PlaceOrderButton(props: Props): JSX.Element { options, paymentType, setButtonRef, - setPlaceOrderStatus + setPlaceOrderStatus, + status } = useContext(PlaceOrderContext) const [notPermitted, setNotPermitted] = useState(true) const [forceDisable, setForceDisable] = useState(disabled) @@ -251,7 +252,8 @@ export function PlaceOrderButton(props: Props): JSX.Element { // @ts-expect-error no type ref?.current?.disabled === false && currentCustomerPaymentSourceId == null && - autoPlaceOrder + autoPlaceOrder && + status === 'standby' ) { // NOTE: This is a workaround for the case when the user reloads the page after selecting a customer payment source if ( @@ -344,11 +346,19 @@ export function PlaceOrderButton(props: Props): JSX.Element { paymentSource: checkPaymentSource, currentCustomerPaymentSourceId })) - setForceDisable(false) - onClick && placed && onClick(placed) - setIsLoading(false) - if (setPlaceOrderStatus != null) { - setPlaceOrderStatus({ status: 'standby' }) + if (placed && setPlaceOrderStatus != null) { + if (placed.placed) { + setPlaceOrderStatus({ status: 'placing' }) + onClick && placed && onClick(placed) + } else { + setForceDisable(false) + onClick && placed && onClick(placed) + setIsLoading(false) + setPlaceOrderStatus({ status: 'standby' }) + } + } else { + setForceDisable(false) + setIsLoading(false) } } const disabledButton = disabled !== undefined ? disabled : notPermitted diff --git a/packages/react-components/src/components/payment_gateways/AdyenGateway.tsx b/packages/react-components/src/components/payment_gateways/AdyenGateway.tsx index 414b76c7..70cfec21 100644 --- a/packages/react-components/src/components/payment_gateways/AdyenGateway.tsx +++ b/packages/react-components/src/components/payment_gateways/AdyenGateway.tsx @@ -8,7 +8,7 @@ import PaymentSourceContext from '#context/PaymentSourceContext' import { type PaymentResource } from '#reducers/PaymentMethodReducer' import { type StripeElementLocale } from '@stripe/stripe-js' import isEmpty from 'lodash/isEmpty' -import { useContext, type JSX } from 'react'; +import { useContext, type JSX } from 'react' import AdyenPayment from '#components/payment_source/AdyenPayment' import PaymentCardsTemplate from '../utils/PaymentCardsTemplate' import { jwt } from '#utils/jwt' @@ -38,8 +38,6 @@ export function AdyenGateway(props: Props): JSX.Element | null { useContext(PaymentMethodContext) const paymentResource: PaymentResource = 'adyen_payments' const locale = order?.language_code as StripeElementLocale - console.log('AdyenGateway order', order, order?.status) - console.log('AdyenGateway order can place? --- ', order && canPlaceOrder(order)) if (!readonly && payment?.id !== currentPaymentMethodId) return null // @ts-expect-error no type const clientKey = paymentSource?.public_key diff --git a/packages/react-components/src/components/payment_methods/PaymentMethod.tsx b/packages/react-components/src/components/payment_methods/PaymentMethod.tsx index 8cc049e6..fd646514 100644 --- a/packages/react-components/src/components/payment_methods/PaymentMethod.tsx +++ b/packages/react-components/src/components/payment_methods/PaymentMethod.tsx @@ -1,4 +1,10 @@ -import { useState, useEffect, type MouseEvent, useContext, type JSX } from 'react'; +import { + useState, + useEffect, + type MouseEvent, + useContext, + type JSX +} from 'react' import PaymentMethodContext from '#context/PaymentMethodContext' import PaymentMethodChildrenContext from '#context/PaymentMethodChildrenContext' import type { LoaderType } from '#typings' @@ -19,6 +25,7 @@ import { import { isEmpty } from '#utils/isEmpty' import { getAvailableExpressPayments } from '#utils/expressPaymentHelper' import PlaceOrderContext from '#context/PlaceOrderContext' +import { sortPaymentMethods } from '#utils/payment-methods/sortPaymentMethods' export interface PaymentMethodOnClickParams { payment?: PaymentMethodType | Record @@ -48,6 +55,10 @@ type Props = { * Enable express payment. Other payment methods will be disabled. */ expressPayments?: boolean + /** + * Sort payment methods by an array of strings + */ + sortBy?: Array } & Omit & ( | { @@ -72,6 +83,7 @@ export function PaymentMethod({ expressPayments, hide, onClick, + sortBy, ...p }: Props): JSX.Element { const [loading, setLoading] = useState(true) @@ -200,7 +212,13 @@ export function PaymentMethod({ setPaymentSelected('') } }, [paymentMethods, currentPaymentMethodId]) - const components = paymentMethods + + const sortedPaymentMethods = + paymentMethods != null && sortBy != null + ? sortPaymentMethods(paymentMethods, sortBy) + : paymentMethods + + const components = sortedPaymentMethods ?.filter((payment) => { if (Array.isArray(hide)) { const source = payment?.payment_source_type as PaymentResource diff --git a/packages/react-components/src/components/payment_methods/PaymentMethodsContainer.tsx b/packages/react-components/src/components/payment_methods/PaymentMethodsContainer.tsx index 904599a5..bc0495cf 100644 --- a/packages/react-components/src/components/payment_methods/PaymentMethodsContainer.tsx +++ b/packages/react-components/src/components/payment_methods/PaymentMethodsContainer.tsx @@ -2,7 +2,14 @@ import PaymentMethodContext, { defaultPaymentMethodContext } from '#context/PaymentMethodContext' -import { type ReactNode, useContext, useEffect, useReducer, useMemo, type JSX } from 'react'; +import { + type ReactNode, + useContext, + useEffect, + useReducer, + useMemo, + type JSX +} from 'react' import paymentMethodReducer, { paymentMethodInitialState, getPaymentMethods, diff --git a/packages/react-components/src/components/payment_source/AdyenPayment.tsx b/packages/react-components/src/components/payment_source/AdyenPayment.tsx index cba32c74..0c490ab6 100644 --- a/packages/react-components/src/components/payment_source/AdyenPayment.tsx +++ b/packages/react-components/src/components/payment_source/AdyenPayment.tsx @@ -1,9 +1,27 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -import { FormEvent, useContext, useEffect, useRef, useState, type JSX } from 'react'; +import { + type FormEvent, + useContext, + useEffect, + useRef, + useState, + type JSX +} from 'react' import PaymentMethodContext from '#context/PaymentMethodContext' import { type PaymentSourceProps } from './PaymentSource' import { setCustomerOrderParam } from '#utils/localStorage' -import { AdditionalDetailsData, AdyenCheckout, CheckoutAdvancedFlowResponse, CoreConfiguration, Dropin, DropinConfiguration, OnChangeData, SubmitData, UIElement, UIElementProps } from '@adyen/adyen-web/auto'; +import { + type AdditionalDetailsData, + AdyenCheckout, + type CheckoutAdvancedFlowResponse, + type CoreConfiguration, + Dropin, + type DropinConfiguration, + type OnChangeData, + type SubmitData, + type UIElement, + type UIElementProps +} from '@adyen/adyen-web/auto' import Parent from '#components/utils/Parent' import browserInfo, { cleanUrlBy } from '#utils/browserInfo' import PlaceOrderContext from '#context/PlaceOrderContext' @@ -12,37 +30,37 @@ import { getPublicIP } from '#utils/getPublicIp' import CustomerContext from '#context/CustomerContext' interface StyleDefinitions { - background?: string; - caretColor?: string; - color?: string; - display?: string; - font?: string; - fontFamily?: string; - fontSize?: string; - fontSizeAdjust?: string; - fontSmoothing?: string; - fontStretch?: string; - fontStyle?: string; - fontVariant?: string; - fontVariantAlternates?: string; - fontVariantCaps?: string; - fontVariantEastAsian?: string; - fontVariantLigatures?: string; - fontVariantNumeric?: string; - fontWeight?: string; - letterSpacing?: string; - lineHeight?: string; - mozOsxFontSmoothing?: string; - mozTransition?: string; - outline?: string; - opacity?: string; - padding?: string; - textAlign?: string; - textShadow?: string; - transition?: string; - webkitFontSmoothing?: string; - webkitTransition?: string; - wordSpacing?: string; + background?: string + caretColor?: string + color?: string + display?: string + font?: string + fontFamily?: string + fontSize?: string + fontSizeAdjust?: string + fontSmoothing?: string + fontStretch?: string + fontStyle?: string + fontVariant?: string + fontVariantAlternates?: string + fontVariantCaps?: string + fontVariantEastAsian?: string + fontVariantLigatures?: string + fontVariantNumeric?: string + fontWeight?: string + letterSpacing?: string + lineHeight?: string + mozOsxFontSmoothing?: string + mozTransition?: string + outline?: string + opacity?: string + padding?: string + textAlign?: string + textShadow?: string + transition?: string + webkitFontSmoothing?: string + webkitTransition?: string + wordSpacing?: string } interface Styles { @@ -90,24 +108,24 @@ export interface AdyenPaymentConfig { /** * Optional CSS class name for the card container. */ - cardContainerClassName?: string; + cardContainerClassName?: string /** * Optional CSS class name for the 3D Secure container. * @deprecated */ - threeDSecureContainerClassName?: string; + threeDSecureContainerClassName?: string /** * Callback function to be called when an order is placed. * @param response - An object containing the placement status. */ - placeOrderCallback?: (response: { placed: boolean }) => void; + placeOrderCallback?: (response: { placed: boolean }) => void /** * Optional styles for the payment methods. */ - styles?: PaymentMethodsStyle; + styles?: PaymentMethodsStyle /** * Configuration options for the payment methods. @@ -120,9 +138,9 @@ export interface AdyenPaymentConfig { * @returns A promise that resolves to a boolean indicating whether the stored payment method was disabled. */ onDisableStoredPaymentMethod?: (props: { - recurringDetailReference: string; - shopperReference: string | undefined; - }) => Promise; + recurringDetailReference: string + shopperReference: string | undefined + }) => Promise } interface Props { @@ -147,7 +165,9 @@ export function AdyenPayment({ ...config } const [loadAdyen, setLoadAdyen] = useState(false) - const [checkout, setCheckout] = useState | undefined>() + const [checkout, setCheckout] = useState< + UIElement | undefined + >() const { setPaymentSource, paymentSource, @@ -160,11 +180,11 @@ export function AdyenPayment({ const { placeOrderButtonRef, setPlaceOrder } = useContext(PlaceOrderContext) const { customers } = useContext(CustomerContext) const ref = useRef(null) - const dropinRef = useRef(null); - + const dropinRef = useRef(null) + const handleSubmit = async ( - e: FormEvent, - ): Promise => { + e: FormEvent + ): Promise => { const savePaymentSourceToCustomerWallet: string = // @ts-expect-error no type e?.elements?.save_payment_source_to_customer_wallet?.checked @@ -173,16 +193,12 @@ export function AdyenPayment({ '_save_payment_source_to_customer_wallet', savePaymentSourceToCustomerWallet ) - console.log('Adyen handleSubmit', dropinRef.current) if (dropinRef.current) { - console.log('Fire adyen submit') dropinRef.current.submit() } return false } - const handleChange = async ( - state: OnChangeData - ): Promise => { + const handleChange = async (state: OnChangeData): Promise => { if (state.isValid) { if (ref.current) { ref.current.onsubmit = async () => { @@ -308,13 +324,12 @@ export function AdyenPayment({ // @ts-expect-error no type const resultCode = res?.payment_response?.resultCode if (action != null) { - console.log('Request 3DSecure action') return { resultCode, action } } - + // @ts-expect-error no type const issuerType = res?.payment_instrument?.issuer_type if (['Authorised', 'Pending', 'Received'].includes(resultCode)) { @@ -397,12 +412,12 @@ export function AdyenPayment({ // @ts-expect-error no type paymentMethods: paymentSource?.payment_methods?.paymentMethods ? // @ts-expect-error no type - paymentSource?.payment_methods.paymentMethods + paymentSource?.payment_methods.paymentMethods : [], // @ts-expect-error no type storedPaymentMethods: paymentSource?.payment_methods?.storedPaymentMethods ? // @ts-expect-error no type - paymentSource?.payment_methods.storedPaymentMethods + paymentSource?.payment_methods.storedPaymentMethods : [] } if (paymentMethodsResponse.paymentMethods.length === 0) { @@ -425,46 +440,46 @@ export function AdyenPayment({ countryCode: order?.country_code || '', paymentMethodsResponse, showPayButton: false, - onAdditionalDetails: async (state, element, actions) => { - const { resultCode } = await handleOnAdditionalDetails(state, element) - if (['Cancelled', 'Refused'].includes(resultCode)) { - actions.reject() - } else { - console.log('Adyen onSubmit res', resultCode) - console.log('Payment with 3DS successful') - actions.resolve({ - resultCode - }) + onAdditionalDetails: (state, element, actions) => { + const onAdditionalDetails = async (): Promise => { + const { resultCode } = await handleOnAdditionalDetails(state, element) + if (['Cancelled', 'Refused'].includes(resultCode)) { + actions.reject() + } else { + actions.resolve({ + resultCode + }) + } } + void onAdditionalDetails() }, onChange: (state) => { void handleChange(state) }, - onSubmit: async (state, element, actions) => { - const { resultCode, action } = await onSubmit(state, element) - if (['Cancelled', 'Refused'].includes(resultCode)) { - actions.reject() - } else if (action != null) { - console.log('Adyen 3DS request', resultCode) - console.log('action', action) - dropinRef.current?.handleAction(action) - // actions.resolve({ - // resultCode, - // action - // }) - } else { - console.log('Adyen onSubmit res', resultCode) - console.log('Payment successful') - actions.resolve({ - resultCode - }) + onSubmit: (state, element, actions) => { + const handleSubmit = async (): Promise => { + const { resultCode, action } = await onSubmit(state, element) + if (['Cancelled', 'Refused'].includes(resultCode)) { + actions.reject() + } else if (action != null) { + dropinRef.current?.handleAction(action) + // actions.resolve({ + // resultCode, + // action + // }) + } else { + actions.resolve({ + resultCode + }) + } } + void handleSubmit() } } satisfies CoreConfiguration if (!ref && clientKey) setCustomerOrderParam('_save_payment_source_to_customer_wallet', 'false') if (clientKey && !loadAdyen && window && !checkout) { - const initializeAdyen = async () => { + const initializeAdyen = async (): Promise => { const checkout = await AdyenCheckout(options) const dropin = new Dropin(checkout, { disableFinalAnimation: true, @@ -492,17 +507,21 @@ export function AdyenPayment({ onDisableStoredPaymentMethod({ recurringDetailReference, shopperReference - }).then((response) => { - if (response) { - setPaymentSource({ - paymentResource: 'adyen_payments', - order, - attributes: {} - }) - } else { - console.error('onDisableStoredPaymentMethod error') - } }) + .then((response) => { + if (response) { + void setPaymentSource({ + paymentResource: 'adyen_payments', + order, + attributes: {} + }) + } else { + console.error('onDisableStoredPaymentMethod error') + } + }) + .catch((error) => { + console.error('onDisableStoredPaymentMethod error', error) + }) } }, onSelect: (component) => { @@ -511,8 +530,7 @@ export function AdyenPayment({ if (ref.current) { if (id.search('paypal') === -1) { ref.current.onsubmit = async () => { - return await handleSubmit( - ref.current as any) + return await handleSubmit(ref.current as any) } } else { ref.current.onsubmit = null @@ -524,7 +542,6 @@ export function AdyenPayment({ }).mount('#adyen-dropin') if (dropin && checkout) { dropinRef.current = dropin - console.log('Adyen dropin mounted') setCheckout(dropin) setLoadAdyen(true) } diff --git a/packages/react-components/src/reducers/OrderReducer.ts b/packages/react-components/src/reducers/OrderReducer.ts index 432ee7de..6e9c9ac2 100644 --- a/packages/react-components/src/reducers/OrderReducer.ts +++ b/packages/react-components/src/reducers/OrderReducer.ts @@ -521,7 +521,7 @@ export async function addToCart( }) const redirectUrl = checkoutUrl ? `${checkoutUrl}/${params}` - : organizationConfig?.links?.checkout ?? href + : (organizationConfig?.links?.checkout ?? href) location.href = redirectUrl } else if (openMiniCart) { publish('open-cart') diff --git a/packages/react-components/src/reducers/PlaceOrderReducer.ts b/packages/react-components/src/reducers/PlaceOrderReducer.ts index 201e06f3..e6b15edc 100644 --- a/packages/react-components/src/reducers/PlaceOrderReducer.ts +++ b/packages/react-components/src/reducers/PlaceOrderReducer.ts @@ -189,13 +189,11 @@ export async function setPlaceOrder({ const { options, paymentType } = state try { // Prevent extra place order - const lastOrderStatus = await sdk.orders.retrieve(order.id, { - fields: { orders: ['status'] } - }) + const lastOrderStatus = await sdk.orders.retrieve(order.id) if (lastOrderStatus.status === 'placed') { return { placed: true, - order + order: lastOrderStatus } } if ( diff --git a/packages/react-components/src/utils/payment-methods/sortPaymentMethods.ts b/packages/react-components/src/utils/payment-methods/sortPaymentMethods.ts new file mode 100644 index 00000000..c4edeea0 --- /dev/null +++ b/packages/react-components/src/utils/payment-methods/sortPaymentMethods.ts @@ -0,0 +1,15 @@ +import { type PaymentMethod } from '@commercelayer/sdk' + +export function sortPaymentMethods( + methods: PaymentMethod[], + labels: Array +): PaymentMethod[] { + return methods.sort((a, b) => { + const indexA = labels.indexOf(a.payment_source_type) + const indexB = labels.indexOf(b.payment_source_type) + if (indexA === -1 && indexB === -1) return 0 + if (indexA === -1) return 1 + if (indexB === -1) return -1 + return indexA - indexB + }) +}