diff --git a/.changeset/fair-eyes-smile.md b/.changeset/fair-eyes-smile.md new file mode 100644 index 00000000..a3e299d1 --- /dev/null +++ b/.changeset/fair-eyes-smile.md @@ -0,0 +1,5 @@ +--- +"@godaddy/react": patch +--- + +Add standard google pay and apple pay payment methods using GDP diff --git a/examples/nextjs/app/globals.css b/examples/nextjs/app/globals.css index a2dc41ec..f1d8c73c 100644 --- a/examples/nextjs/app/globals.css +++ b/examples/nextjs/app/globals.css @@ -1,26 +1 @@ @import "tailwindcss"; - -:root { - --background: #ffffff; - --foreground: #171717; -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} diff --git a/packages/react/src/components/checkout/payment/checkout-buttons/applePay/godaddy.tsx b/packages/react/src/components/checkout/payment/checkout-buttons/applePay/godaddy.tsx new file mode 100644 index 00000000..9976c342 --- /dev/null +++ b/packages/react/src/components/checkout/payment/checkout-buttons/applePay/godaddy.tsx @@ -0,0 +1,263 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useFormContext } from 'react-hook-form'; +import { useCheckoutContext } from '@/components/checkout/checkout'; +import { useDraftOrderTotals } from '@/components/checkout/order/use-draft-order'; +import type { + TokenizeJs, + WalletError, +} from '@/components/checkout/payment/types'; +import { useBuildPaymentRequest } from '@/components/checkout/payment/utils/use-build-payment-request'; +import { + PaymentProvider, + useConfirmCheckout, +} from '@/components/checkout/payment/utils/use-confirm-checkout'; +import { useIsPaymentDisabled } from '@/components/checkout/payment/utils/use-is-payment-disabled'; +import { useLoadPoyntCollect } from '@/components/checkout/payment/utils/use-load-poynt-collect'; +import { Skeleton } from '@/components/ui/skeleton'; +import { useGoDaddyContext } from '@/godaddy-provider'; +import { GraphQLErrorWithCodes } from '@/lib/graphql-with-errors'; +import { eventIds } from '@/tracking/events'; +import { TrackingEventType, track } from '@/tracking/track'; +import { PaymentMethodType } from '@/types'; + +export function GoDaddyApplePayCheckoutButton() { + const { session, setCheckoutErrors, isConfirmingCheckout } = + useCheckoutContext(); + const isPaymentDisabled = useIsPaymentDisabled(); + const form = useFormContext(); + const { isPoyntLoaded } = useLoadPoyntCollect(); + const { godaddyPaymentsConfig } = useCheckoutContext(); + const { t } = useGoDaddyContext(); + const [isCollectLoading, setIsCollectLoading] = useState(true); + const [error, setError] = useState(''); + const { data: totals } = useDraftOrderTotals(); + const { poyntStandardRequest } = useBuildPaymentRequest(); + + const currencyCode = totals?.total?.currencyCode || 'USD'; + const countryCode = session?.shipping?.originAddress?.countryCode || 'US'; + + const confirmCheckout = useConfirmCheckout(); + const collect = useRef(null); + const hasMounted = useRef(false); + + const isDisabled = isConfirmingCheckout || isPaymentDisabled; + + // Use a ref so the SDK's stale onClick closure always calls the latest handler + const handleApplePayClickRef = useRef<() => Promise>( + async () => undefined + ); + + const handleApplePayClick = useCallback(async () => { + if (!poyntStandardRequest || isDisabled) return; + + const valid = await form.trigger(); + if (!valid) { + const firstError = Object.keys(form.formState.errors)[0]; + if (firstError) { + form.setFocus(firstError); + } + return; + } + + setCheckoutErrors(undefined); + + collect?.current?.startApplePaySession(poyntStandardRequest); + + track({ + eventId: eventIds.applePayClick, + type: TrackingEventType.CLICK, + properties: { + paymentType: PaymentMethodType.APPLE_PAY, + }, + }); + }, [poyntStandardRequest, setCheckoutErrors, form, isDisabled]); + + // Keep ref in sync so the SDK's stale onClick closure always calls the latest handler + handleApplePayClickRef.current = handleApplePayClick; + + // Initialize the TokenizeJs instance when the component mounts + useEffect(() => { + if ( + !collect.current && + godaddyPaymentsConfig && + (godaddyPaymentsConfig?.businessId || session?.businessId) && + isCollectLoading && + isPoyntLoaded && + !hasMounted.current + ) { + collect.current = new (window as any).TokenizeJs( + { + businessId: godaddyPaymentsConfig?.businessId || session?.businessId, + storeId: session?.storeId, + channelId: session?.channelId, + applicationId: godaddyPaymentsConfig?.appId, + }, + { + country: countryCode, + currency: currencyCode, + merchantName: session?.storeName || '', + requireEmail: false, + requireShippingAddress: false, + supportCouponCode: false, + } + ); + } + }, [ + godaddyPaymentsConfig, + countryCode, + currencyCode, + session?.businessId, + session?.storeId, + session?.channelId, + isPoyntLoaded, + isCollectLoading, + ]); + + // Mount the TokenizeJs instance + useEffect(() => { + if ( + !isPoyntLoaded || + !godaddyPaymentsConfig || + (!godaddyPaymentsConfig?.businessId && !session?.businessId) || + !isCollectLoading || + !collect.current || + hasMounted.current + ) + return; + + collect.current?.supportWalletPayments().then(supports => { + if (!hasMounted.current && supports.applePay) { + hasMounted.current = true; + + collect?.current?.mount('apple-pay-element', document, { + paymentMethods: ['apple_pay'], + buttonsContainerOptions: { + className: 'gap-1 !flex-col sm:!flex-row place-items-center', + }, + buttonOptions: { + type: 'plain', + margin: '0', + height: '50px', + width: '100%', + justifyContent: 'flex-start', + onClick: () => handleApplePayClickRef.current(), + }, + }); + + setIsCollectLoading(false); + + track({ + eventId: eventIds.applePayImpression, + type: TrackingEventType.IMPRESSION, + properties: { + provider: 'poynt', + }, + }); + } + }); + }, [ + isPoyntLoaded, + godaddyPaymentsConfig, + isCollectLoading, + session?.businessId, + ]); + + // Set up event listeners for TokenizeJs + useEffect(() => { + if (!collect.current || !isPoyntLoaded) return; + + collect.current.on('close_wallet', () => { + setError(''); + }); + + collect.current.on('payment_authorized', async event => { + const nonce = event?.nonce; + + if (nonce) { + const checkoutBody = { + paymentToken: nonce, + paymentType: PaymentMethodType.CREDIT_CARD, + paymentProvider: PaymentProvider.POYNT, + }; + + try { + await confirmCheckout.mutateAsync(checkoutBody); + event.complete(); + } catch (err: unknown) { + if (err instanceof GraphQLErrorWithCodes) { + const walletError: WalletError = { + code: 'invalid_payment_data', + message: + t.apiErrors?.[err.codes[0] as keyof typeof t.apiErrors] || + t.errors.errorProcessingPayment, + }; + + setCheckoutErrors(err.codes); + + track({ + eventId: eventIds.checkoutError, + type: TrackingEventType.EVENT, + properties: { + paymentType: event.source, + provider: 'poynt', + errorCodes: err.codes.join(','), + }, + }); + + event.complete({ error: walletError }); + } else { + track({ + eventId: eventIds.checkoutError, + type: TrackingEventType.EVENT, + properties: { + paymentType: event.source, + provider: 'poynt', + errorType: 'generic', + }, + }); + + const walletError: WalletError = { + code: 'invalid_payment_data', + message: t.errors.errorProcessingPayment, + }; + event.complete({ error: walletError }); + } + } + } else { + track({ + eventId: eventIds.checkoutError, + type: TrackingEventType.EVENT, + properties: { + paymentType: event.source, + provider: 'poynt', + errorCodes: 'no_nonce', + }, + }); + const walletError: WalletError = { + code: 'invalid_payment_data', + message: t.errors.errorProcessingPayment, + }; + event.complete({ error: walletError }); + } + }); + + collect.current.on('error', event => { + setError(event?.data?.error?.message || t.errors.errorProcessingPayment); + }); + }, [isPoyntLoaded, confirmCheckout.mutateAsync, t, setCheckoutErrors]); + + return ( + <> +
+ {isCollectLoading ? ( +
+ +
+ ) : null} + {error ?

{error}

: null} + + ); +} diff --git a/packages/react/src/components/checkout/payment/checkout-buttons/googlePay/godaddy.tsx b/packages/react/src/components/checkout/payment/checkout-buttons/googlePay/godaddy.tsx index 9b4b0973..950b71d5 100644 --- a/packages/react/src/components/checkout/payment/checkout-buttons/googlePay/godaddy.tsx +++ b/packages/react/src/components/checkout/payment/checkout-buttons/googlePay/godaddy.tsx @@ -1,3 +1,263 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useFormContext } from 'react-hook-form'; +import { useCheckoutContext } from '@/components/checkout/checkout'; +import { useDraftOrderTotals } from '@/components/checkout/order/use-draft-order'; +import type { + TokenizeJs, + WalletError, +} from '@/components/checkout/payment/types'; +import { useBuildPaymentRequest } from '@/components/checkout/payment/utils/use-build-payment-request'; +import { + PaymentProvider, + useConfirmCheckout, +} from '@/components/checkout/payment/utils/use-confirm-checkout'; +import { useIsPaymentDisabled } from '@/components/checkout/payment/utils/use-is-payment-disabled'; +import { useLoadPoyntCollect } from '@/components/checkout/payment/utils/use-load-poynt-collect'; +import { Skeleton } from '@/components/ui/skeleton'; +import { useGoDaddyContext } from '@/godaddy-provider'; +import { GraphQLErrorWithCodes } from '@/lib/graphql-with-errors'; +import { eventIds } from '@/tracking/events'; +import { TrackingEventType, track } from '@/tracking/track'; +import { PaymentMethodType } from '@/types'; + export function GoDaddyGooglePayCheckoutButton() { - return
not implemented
; + const { session, setCheckoutErrors, isConfirmingCheckout } = + useCheckoutContext(); + const isPaymentDisabled = useIsPaymentDisabled(); + const form = useFormContext(); + const { isPoyntLoaded } = useLoadPoyntCollect(); + const { godaddyPaymentsConfig } = useCheckoutContext(); + const { t } = useGoDaddyContext(); + const [isCollectLoading, setIsCollectLoading] = useState(true); + const [error, setError] = useState(''); + const { data: totals } = useDraftOrderTotals(); + const { poyntStandardRequest } = useBuildPaymentRequest(); + + const currencyCode = totals?.total?.currencyCode || 'USD'; + const countryCode = session?.shipping?.originAddress?.countryCode || 'US'; + + const confirmCheckout = useConfirmCheckout(); + const collect = useRef(null); + const hasMounted = useRef(false); + + const isDisabled = isConfirmingCheckout || isPaymentDisabled; + + // Use a ref so the SDK's stale onClick closure always calls the latest handler + const handleGooglePayClickRef = useRef<() => Promise>( + async () => undefined + ); + + const handleGooglePayClick = useCallback(async () => { + if (!poyntStandardRequest || isDisabled) return; + + const valid = await form.trigger(); + if (!valid) { + const firstError = Object.keys(form.formState.errors)[0]; + if (firstError) { + form.setFocus(firstError); + } + return; + } + + setCheckoutErrors(undefined); + + collect?.current?.startGooglePaySession(poyntStandardRequest); + + track({ + eventId: eventIds.googlePayClick, + type: TrackingEventType.CLICK, + properties: { + paymentType: PaymentMethodType.GOOGLE_PAY, + }, + }); + }, [poyntStandardRequest, setCheckoutErrors, form, isDisabled]); + + // Keep ref in sync so the SDK's stale onClick closure always calls the latest handler + handleGooglePayClickRef.current = handleGooglePayClick; + + // Initialize the TokenizeJs instance when the component mounts + useEffect(() => { + if ( + !collect.current && + godaddyPaymentsConfig && + (godaddyPaymentsConfig?.businessId || session?.businessId) && + isCollectLoading && + isPoyntLoaded && + !hasMounted.current + ) { + collect.current = new (window as any).TokenizeJs( + { + businessId: godaddyPaymentsConfig?.businessId || session?.businessId, + storeId: session?.storeId, + channelId: session?.channelId, + applicationId: godaddyPaymentsConfig?.appId, + }, + { + country: countryCode, + currency: currencyCode, + merchantName: session?.storeName || '', + requireEmail: false, + requireShippingAddress: false, + supportCouponCode: false, + } + ); + } + }, [ + godaddyPaymentsConfig, + countryCode, + currencyCode, + session?.businessId, + session?.storeId, + session?.channelId, + isPoyntLoaded, + isCollectLoading, + ]); + + // Mount the TokenizeJs instance + useEffect(() => { + if ( + !isPoyntLoaded || + !godaddyPaymentsConfig || + (!godaddyPaymentsConfig?.businessId && !session?.businessId) || + !isCollectLoading || + !collect.current || + hasMounted.current + ) + return; + + collect.current?.supportWalletPayments().then(supports => { + if (!hasMounted.current && supports.googlePay) { + hasMounted.current = true; + + collect?.current?.mount('google-pay-element', document, { + paymentMethods: ['google_pay'], + buttonsContainerOptions: { + className: 'gap-1 !flex-col sm:!flex-row place-items-center', + }, + buttonOptions: { + type: 'plain', + margin: '0', + height: '50px', + width: '100%', + justifyContent: 'flex-start', + onClick: () => handleGooglePayClickRef.current(), + }, + }); + + setIsCollectLoading(false); + + track({ + eventId: eventIds.googlePayImpression, + type: TrackingEventType.IMPRESSION, + properties: { + provider: 'poynt', + }, + }); + } + }); + }, [ + isPoyntLoaded, + godaddyPaymentsConfig, + isCollectLoading, + session?.businessId, + ]); + + // Set up event listeners for TokenizeJs + useEffect(() => { + if (!collect.current || !isPoyntLoaded) return; + + collect.current.on('close_wallet', () => { + setError(''); + }); + + collect.current.on('payment_authorized', async event => { + const nonce = event?.nonce; + + if (nonce) { + const checkoutBody = { + paymentToken: nonce, + paymentType: PaymentMethodType.CREDIT_CARD, + paymentProvider: PaymentProvider.POYNT, + }; + + try { + await confirmCheckout.mutateAsync(checkoutBody); + event.complete(); + } catch (err: unknown) { + if (err instanceof GraphQLErrorWithCodes) { + const walletError: WalletError = { + code: 'invalid_payment_data', + message: + t.apiErrors?.[err.codes[0] as keyof typeof t.apiErrors] || + t.errors.errorProcessingPayment, + }; + + setCheckoutErrors(err.codes); + + track({ + eventId: eventIds.checkoutError, + type: TrackingEventType.EVENT, + properties: { + paymentType: event.source, + provider: 'poynt', + errorCodes: err.codes.join(','), + }, + }); + + event.complete({ error: walletError }); + } else { + track({ + eventId: eventIds.checkoutError, + type: TrackingEventType.EVENT, + properties: { + paymentType: event.source, + provider: 'poynt', + errorType: 'generic', + }, + }); + + const walletError: WalletError = { + code: 'invalid_payment_data', + message: t.errors.errorProcessingPayment, + }; + event.complete({ error: walletError }); + } + } + } else { + track({ + eventId: eventIds.checkoutError, + type: TrackingEventType.EVENT, + properties: { + paymentType: event.source, + provider: 'poynt', + errorCodes: 'no_nonce', + }, + }); + const walletError: WalletError = { + code: 'invalid_payment_data', + message: t.errors.errorProcessingPayment, + }; + event.complete({ error: walletError }); + } + }); + + collect.current.on('error', event => { + setError(event?.data?.error?.message || t.errors.errorProcessingPayment); + }); + }, [isPoyntLoaded, confirmCheckout.mutateAsync, t, setCheckoutErrors]); + + return ( + <> +
+ {isCollectLoading ? ( +
+ +
+ ) : null} + {error ?

{error}

: null} + + ); } diff --git a/packages/react/src/components/checkout/payment/checkout-buttons/paze/godaddy.tsx b/packages/react/src/components/checkout/payment/checkout-buttons/paze/godaddy.tsx index 850a3e76..15d16fc3 100644 --- a/packages/react/src/components/checkout/payment/checkout-buttons/paze/godaddy.tsx +++ b/packages/react/src/components/checkout/payment/checkout-buttons/paze/godaddy.tsx @@ -11,6 +11,7 @@ import { PaymentProvider, useConfirmCheckout, } from '@/components/checkout/payment/utils/use-confirm-checkout'; +import { useIsPaymentDisabled } from '@/components/checkout/payment/utils/use-is-payment-disabled'; import { useLoadPoyntCollect } from '@/components/checkout/payment/utils/use-load-poynt-collect'; import { Skeleton } from '@/components/ui/skeleton'; import { useGoDaddyContext } from '@/godaddy-provider'; @@ -20,7 +21,9 @@ import { TrackingEventType, track } from '@/tracking/track'; import { PaymentMethodType } from '@/types'; export function PazeCheckoutButton() { - const { session, setCheckoutErrors } = useCheckoutContext(); + const { session, setCheckoutErrors, isConfirmingCheckout } = + useCheckoutContext(); + const isPaymentDisabled = useIsPaymentDisabled(); const form = useFormContext(); const { isPoyntLoaded } = useLoadPoyntCollect(); const { godaddyPaymentsConfig } = useCheckoutContext(); @@ -37,8 +40,13 @@ export function PazeCheckoutButton() { const collect = useRef(null); const hasMounted = useRef(false); + const isDisabled = isConfirmingCheckout || isPaymentDisabled; + + // Use a ref so the SDK's stale onClick closure always calls the latest handler + const handlePazeClickRef = useRef<() => Promise>(async () => undefined); + const handlePazeClick = useCallback(async () => { - if (!poyntStandardRequest) return; + if (!poyntStandardRequest || isDisabled) return; const valid = await form.trigger(); if (!valid) { @@ -61,39 +69,10 @@ export function PazeCheckoutButton() { paymentType: PaymentMethodType.PAZE, }, }); - }, [poyntStandardRequest, setCheckoutErrors, form]); - - const mountPazeElement = useCallback(() => { - if (!hasMounted.current && collect?.current) { - hasMounted.current = true; - // console.log("[poynt collect] Mounting paze-pay-element"); - collect?.current.mount('paze-pay-element', document, { - paymentMethods: ['paze'], - buttonsContainerOptions: { - className: 'gap-1 !flex-col sm:!flex-row place-items-center', - }, - - buttonOptions: { - type: 'plain', - margin: '0', - height: '50px', - width: '100%', - justifyContent: 'flex-start', - onClick: handlePazeClick, - }, - }); + }, [poyntStandardRequest, setCheckoutErrors, form, isDisabled]); - setIsCollectLoading(false); - - track({ - eventId: eventIds.pazePayImpression, - type: TrackingEventType.IMPRESSION, - properties: { - provider: 'poynt', - }, - }); - } - }, [handlePazeClick]); + // Keep ref in sync so the SDK's stale onClick closure always calls the latest handler + handlePazeClickRef.current = handlePazeClick; // Initialize the TokenizeJs instance when the component mounts useEffect(() => { @@ -105,7 +84,6 @@ export function PazeCheckoutButton() { isPoyntLoaded && !hasMounted.current ) { - // console.log("[poynt collect] Initializing TokenizeJs instance"); collect.current = new (window as any).TokenizeJs( { businessId: godaddyPaymentsConfig?.businessId || session?.businessId, @@ -148,14 +126,39 @@ export function PazeCheckoutButton() { collect.current?.supportWalletPayments().then(supports => { if (!hasMounted.current && supports.paze) { - mountPazeElement(); + hasMounted.current = true; + + collect?.current?.mount('paze-pay-element', document, { + paymentMethods: ['paze'], + buttonsContainerOptions: { + className: 'gap-1 !flex-col sm:!flex-row place-items-center', + }, + buttonOptions: { + type: 'plain', + margin: '0', + height: '50px', + width: '100%', + justifyContent: 'flex-start', + onClick: () => handlePazeClickRef.current(), + }, + }); + + setIsCollectLoading(false); + + track({ + eventId: eventIds.pazePayImpression, + type: TrackingEventType.IMPRESSION, + properties: { + provider: 'poynt', + }, + }); } }); }, [ isPoyntLoaded, godaddyPaymentsConfig, isCollectLoading, - mountPazeElement, + session?.businessId, ]); // Set up event listeners for TokenizeJs @@ -172,7 +175,7 @@ export function PazeCheckoutButton() { if (nonce) { const checkoutBody = { paymentToken: nonce, - paymentType: event?.source, + paymentType: PaymentMethodType.CREDIT_CARD, paymentProvider: PaymentProvider.POYNT, }; @@ -191,7 +194,7 @@ export function PazeCheckoutButton() { setCheckoutErrors(err.codes); track({ - eventId: eventIds.expressCheckoutError, + eventId: eventIds.checkoutError, type: TrackingEventType.EVENT, properties: { paymentType: event.source, @@ -203,7 +206,7 @@ export function PazeCheckoutButton() { event.complete({ error: walletError }); } else { track({ - eventId: eventIds.expressCheckoutError, + eventId: eventIds.checkoutError, type: TrackingEventType.EVENT, properties: { paymentType: event.source, @@ -221,7 +224,7 @@ export function PazeCheckoutButton() { } } else { track({ - eventId: eventIds.expressCheckoutError, + eventId: eventIds.checkoutError, type: TrackingEventType.EVENT, properties: { paymentType: event.source, @@ -244,7 +247,10 @@ export function PazeCheckoutButton() { return ( <> -
+
{isCollectLoading ? (
diff --git a/packages/react/src/components/checkout/payment/lazy-payment-loader.tsx b/packages/react/src/components/checkout/payment/lazy-payment-loader.tsx index 6edadc0c..3087222d 100644 --- a/packages/react/src/components/checkout/payment/lazy-payment-loader.tsx +++ b/packages/react/src/components/checkout/payment/lazy-payment-loader.tsx @@ -108,6 +108,13 @@ const LazyComponents = { default: module.GoDaddyGooglePayCheckoutButton, })) ), + GoDaddyApplePayCheckoutButton: lazy(() => + import( + '@/components/checkout/payment/checkout-buttons/applePay/godaddy' + ).then(module => ({ + default: module.GoDaddyApplePayCheckoutButton, + })) + ), OfflinePaymentCheckoutButton: lazy(() => import( '@/components/checkout/payment/checkout-buttons/offline/default' @@ -180,6 +187,11 @@ type PaymentComponentRegistry = { button: PaymentComponentKey; }; }; + [PaymentMethodType.APPLE_PAY]?: { + [PaymentProvider.GODADDY]: { + button: PaymentComponentKey; + }; + }; [PaymentMethodType.OFFLINE]?: { [PaymentProvider.OFFLINE]: { button: PaymentComponentKey; @@ -239,6 +251,11 @@ export const lazyPaymentComponentRegistry: PaymentComponentRegistry = { button: 'GoDaddyGooglePayCheckoutButton', }, }, + [PaymentMethodType.APPLE_PAY]: { + [PaymentProvider.GODADDY]: { + button: 'GoDaddyApplePayCheckoutButton', + }, + }, [PaymentMethodType.OFFLINE]: { [PaymentProvider.OFFLINE]: { button: 'OfflinePaymentCheckoutButton', diff --git a/packages/react/src/components/checkout/payment/payment-form.tsx b/packages/react/src/components/checkout/payment/payment-form.tsx index 23fa3ca4..609bbfe0 100644 --- a/packages/react/src/components/checkout/payment/payment-form.tsx +++ b/packages/react/src/components/checkout/payment/payment-form.tsx @@ -97,6 +97,12 @@ export function PaymentForm( const { isPoyntLoaded } = useLoadPoyntCollect(); const [pazeSupported, setPazeSupported] = useState(null); + const [applePaySupported, setApplePaySupported] = useState( + null + ); + const [googlePaySupported, setGooglePaySupported] = useState( + null + ); const collect = useRef(null); const currencyCode = props.currencyCode || 'USD'; @@ -156,7 +162,13 @@ export function PaymentForm( [t] ); - // Initialize TokenizeJs for Paze with GoDaddy processor + // Check if any GoDaddy wallet payment method is configured + const hasGoDaddyWalletPayment = + session?.paymentMethods?.paze?.processor === PaymentProvider.GODADDY || + session?.paymentMethods?.applePay?.processor === PaymentProvider.GODADDY || + session?.paymentMethods?.googlePay?.processor === PaymentProvider.GODADDY; + + // Initialize TokenizeJs for GoDaddy wallet payments (Paze, Apple Pay, Google Pay) useLayoutEffect(() => { if ( !collect.current && @@ -165,7 +177,7 @@ export function PaymentForm( isPoyntLoaded && countryCode && currencyCode && - session?.paymentMethods?.paze?.processor === PaymentProvider.GODADDY + hasGoDaddyWalletPayment ) { collect.current = new (window as any).TokenizeJs( { @@ -182,18 +194,16 @@ export function PaymentForm( ); collect.current?.supportWalletPayments().then(supports => { - if (supports.paze) { - setPazeSupported(true); - } else { - setPazeSupported(false); - } + setPazeSupported(supports.paze ?? false); + setApplePaySupported(supports.applePay ?? false); + setGooglePaySupported(supports.googlePay ?? false); }); } }, [ godaddyPaymentsConfig, countryCode, currencyCode, - session?.paymentMethods?.paze?.processor, + hasGoDaddyWalletPayment, session?.storeName, session?.businessId, session?.storeId, @@ -206,28 +216,37 @@ export function PaymentForm( return Object.keys(session.paymentMethods).filter(key => { const method = session.paymentMethods?.[key as PaymentMethodValue]; - // Special handling for Paze with GoDaddy processor + const baseCheck = + PAYMENT_METHOD_ICONS[key as PaymentMethodValue] && + method && + Array.isArray(method.checkoutTypes) && + method.checkoutTypes.includes(CheckoutType.STANDARD); + + // Special handling for GoDaddy wallet payments — only show when device supports them if ( key === PaymentMethodType.PAZE && method?.processor === PaymentProvider.GODADDY ) { - return ( - PAYMENT_METHOD_ICONS[key as PaymentMethodValue] && - method && - Array.isArray(method.checkoutTypes) && - method.checkoutTypes.includes(CheckoutType.STANDARD) && - pazeSupported === true - ); + return baseCheck && pazeSupported === true; } - return ( - PAYMENT_METHOD_ICONS[key as PaymentMethodValue] && - method && - Array.isArray(method.checkoutTypes) && - method.checkoutTypes.includes(CheckoutType.STANDARD) - ); + if ( + key === PaymentMethodType.APPLE_PAY && + method?.processor === PaymentProvider.GODADDY + ) { + return baseCheck && applePaySupported === true; + } + + if ( + key === PaymentMethodType.GOOGLE_PAY && + method?.processor === PaymentProvider.GODADDY + ) { + return baseCheck && googlePaySupported === true; + } + + return baseCheck; }); - }, [session, pazeSupported]); + }, [session, pazeSupported, applePaySupported, googlePaySupported]); const isBillingAddressRequired = session?.enableBillingAddressCollection && diff --git a/packages/react/src/tracking/events.ts b/packages/react/src/tracking/events.ts index d453b005..eafe52df 100644 --- a/packages/react/src/tracking/events.ts +++ b/packages/react/src/tracking/events.ts @@ -27,6 +27,10 @@ export const eventIds = { pazePayImpression: 'paze_pay.impression', pazePayClick: 'paze_pay.click', pazePayCompleted: 'paze_pay_completed.event', + applePayImpression: 'apple_pay.impression', + applePayClick: 'apple_pay.click', + googlePayImpression: 'google_pay.impression', + googlePayClick: 'google_pay.click', expressCheckoutError: 'express_checkout_error.event', // Express checkout coupon events expressApplyCouponEvent: 'express_checkout_apply_coupon.event',