diff --git a/packages/react/package.json b/packages/react/package.json index 50c3e4c7..ad43477a 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,7 +1,7 @@ { "name": "@godaddy/react", "private": false, - "version": "1.0.58", + "version": "1.0.59", "type": "module", "types": "./dist/index.d.ts", "files": [ diff --git a/packages/react/src/components/checkout/__tests__/checkout-razorpay-config.test.tsx b/packages/react/src/components/checkout/__tests__/checkout-razorpay-config.test.tsx index b3d39e72..7ba0216b 100644 --- a/packages/react/src/components/checkout/__tests__/checkout-razorpay-config.test.tsx +++ b/packages/react/src/components/checkout/__tests__/checkout-razorpay-config.test.tsx @@ -11,6 +11,13 @@ const razorpayPaymentMethods = { }, }; +// Airo-style create: explicit card-only paymentMethods; discovery still fills +// paymentProviderConfiguration.razorpay (and may leave razorpay null / omitted). +const cardOnlyPaymentMethods = { + card: { processor: 'godaddy', checkoutTypes: ['standard'] }, + razorpay: null, +}; + describe('Razorpay configuration gating', () => { it('hides Razorpay when paymentProviderConfiguration.razorpay is missing', async () => { renderCheckout({ @@ -50,4 +57,34 @@ describe('Razorpay configuration gating', () => { screen.getByRole('button', { name: /razorpay/i }) ).toBeInTheDocument(); }); + + it('shows Razorpay when paymentMethods.razorpay is null but configured is true', async () => { + renderCheckout({ + sessionOverrides: { + paymentMethods: cardOnlyPaymentMethods, + paymentProviderConfiguration: { razorpay: { configured: true } }, + } as never, + }); + await waitForCheckoutReady(); + + expect( + screen.getByRole('button', { name: /razorpay/i }) + ).toBeInTheDocument(); + }); + + it('shows Razorpay when paymentMethods omits razorpay but configured is true', async () => { + renderCheckout({ + sessionOverrides: { + paymentMethods: { + card: { processor: 'godaddy', checkoutTypes: ['standard'] }, + }, + paymentProviderConfiguration: { razorpay: { configured: true } }, + } as never, + }); + await waitForCheckoutReady(); + + expect( + screen.getByRole('button', { name: /razorpay/i }) + ).toBeInTheDocument(); + }); }); diff --git a/packages/react/src/components/checkout/payment/payment-form.tsx b/packages/react/src/components/checkout/payment/payment-form.tsx index de2c4ae6..463eda85 100644 --- a/packages/react/src/components/checkout/payment/payment-form.tsx +++ b/packages/react/src/components/checkout/payment/payment-form.tsx @@ -126,13 +126,14 @@ export function PaymentForm( paymentMethod as PaymentMethodValue ); // TEMP FOR TESTING — DO NOT COMMIT: mirrors the availablePaymentMethods - // bypass below. session.paymentMethods.paypal can be null even when PayPal - // is actually configured (only paymentProviderConfiguration.paypal gets - // resolved by discovery on a session created with explicit paymentMethods - // input). Without this, useGetSelectedPaymentMethod returns null for - // PayPal, so getCheckoutButton() bails out at `if (!methodConfig) return - // null` and the "Pay now" button never renders — even though PayPal is - // selectable in the accordion thanks to the bypass below. + // bypass below. session.paymentMethods.paypal / razorpay can be null even + // when the provider is actually configured (only + // paymentProviderConfiguration gets resolved by discovery on a session + // created with explicit paymentMethods input). Without this, + // useGetSelectedPaymentMethod returns null, so getCheckoutButton() bails + // out at `if (!methodConfig) return null` and the "Pay now" button never + // renders — even though the method is selectable in the accordion thanks + // to the bypass below. const methodConfig = rawMethodConfig ?? (paymentMethod === PaymentMethodType.PAYPAL && @@ -142,7 +143,14 @@ export function PaymentForm( processor: PaymentProvider.PAYPAL, checkoutTypes: [CheckoutType.STANDARD], } - : null); + : paymentMethod === PaymentMethodType.RAZORPAY && + razorpayConfig?.configured === true + ? { + type: PaymentMethodType.RAZORPAY as PaymentMethodValue, + processor: PaymentProvider.RAZORPAY, + checkoutTypes: [CheckoutType.STANDARD], + } + : null); const { isPoyntLoaded } = useLoadPoyntCollect(); const [pazeSupported, setPazeSupported] = useState(null); @@ -280,25 +288,40 @@ export function PaymentForm( const availablePaymentMethods = React.useMemo(() => { if (!configuredPaymentMethods) return []; - return Object.keys(configuredPaymentMethods).filter(key => { + // TEMP FOR TESTING — DO NOT COMMIT: when callers send explicit + // paymentMethods (e.g. card-only), discovery still fills + // paymentProviderConfiguration but leaves paymentMethods.paypal / + // razorpay null. GraphQL may omit those null keys entirely, so ensure + // the provider key is present whenever its public config exists. + const methodKeys = new Set(Object.keys(configuredPaymentMethods)); + if (paypalConfig?.clientId?.trim()) { + methodKeys.add(PaymentMethodType.PAYPAL); + } + if (razorpayConfig?.configured === true) { + methodKeys.add(PaymentMethodType.RAZORPAY); + } + return [...methodKeys].filter(key => { const method = configuredPaymentMethods[key as PaymentMethodValue]; - // TEMP FOR TESTING — DO NOT COMMIT: session.paymentMethods.paypal can - // be null on a session created with explicit paymentMethods input - // (only paymentProviderConfiguration gets resolved by discovery in - // that case). Only treat a null paypal method as "standard" when real - // PayPal SDK config actually exists. (Razorpay's equivalent signal, - // paymentProviderConfiguration.razorpay.configured, is checked in the - // stable gating block below instead.) + // TEMP FOR TESTING — DO NOT COMMIT: session.paymentMethods.paypal / + // razorpay can be null on a session created with explicit + // paymentMethods input (only paymentProviderConfiguration gets + // resolved by discovery in that case). Treat a null method as + // "standard" when the provider's public config actually exists. const isPayPalWithRealConfig = key === PaymentMethodType.PAYPAL && !!paypalConfig?.clientId?.trim(); + const isRazorpayWithRealConfig = + key === PaymentMethodType.RAZORPAY && + razorpayConfig?.configured === true; + const hasProviderConfigBypass = + isPayPalWithRealConfig || isRazorpayWithRealConfig; const effectiveCheckoutTypes = method?.checkoutTypes ?? - (isPayPalWithRealConfig ? [CheckoutType.STANDARD] : undefined); + (hasProviderConfigBypass ? [CheckoutType.STANDARD] : undefined); const baseCheck = PAYMENT_METHOD_ICONS[key as PaymentMethodValue] && - (method || isPayPalWithRealConfig) && + (method || hasProviderConfigBypass) && Array.isArray(effectiveCheckoutTypes) && effectiveCheckoutTypes.includes(CheckoutType.STANDARD); @@ -330,10 +353,12 @@ export function PaymentForm( } // Razorpay requires checkout-api to have resolved a working merchant - // account before the button is offered. + // account before the button is offered. Mirror PayPal: allow through + // when paymentMethods.razorpay is null but + // paymentProviderConfiguration.razorpay.configured is true. if ( key === PaymentMethodType.RAZORPAY && - method?.processor === PaymentProvider.RAZORPAY + (method?.processor === PaymentProvider.RAZORPAY || !method) ) { return baseCheck && razorpayConfig?.configured === true; }