From c4c5f0219a3fc87ec9fcfebb74cff537b85947a3 Mon Sep 17 00:00:00 2001 From: Catherine Shing Date: Tue, 30 Jun 2026 23:02:29 -0700 Subject: [PATCH 1/9] add button cursor-pointer --- packages/react/src/components/ui/button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/components/ui/button.tsx b/packages/react/src/components/ui/button.tsx index 25d70383..ae374c7a 100644 --- a/packages/react/src/components/ui/button.tsx +++ b/packages/react/src/components/ui/button.tsx @@ -6,7 +6,7 @@ import { useCheckoutContext } from '@/components/checkout/checkout'; import { cn } from '@/lib/utils'; const buttonVariants = cva( - 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', { variants: { variant: { From 17853222375216311ace1dabded310c41e3cf847 Mon Sep 17 00:00:00 2001 From: Catherine Shing Date: Tue, 30 Jun 2026 23:03:01 -0700 Subject: [PATCH 2/9] improve tip btn styling, fix custom tip input --- .../components/checkout/tips/tips-form.tsx | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/react/src/components/checkout/tips/tips-form.tsx b/packages/react/src/components/checkout/tips/tips-form.tsx index e9523110..5ba3873f 100644 --- a/packages/react/src/components/checkout/tips/tips-form.tsx +++ b/packages/react/src/components/checkout/tips/tips-form.tsx @@ -104,17 +104,19 @@ export function TipsForm({ total, currencyCode }: TipsFormProps) { - {showCustomTip && ( + {showCustomTip ? ( - )} + ) : null} ); } @@ -278,6 +283,10 @@ function CustomTipInput({ }); }; + // Ref to avoid `form` (unstable reference) in the dependency array. + const formRef = useRef(form); + formRef.current = form; + // When the debounced value settles and the input is still focused, // sync to form state and format the display — the same effect as blur // but triggered by 1.5s of inactivity. This keeps the order summary @@ -285,11 +294,11 @@ function CustomTipInput({ useEffect(() => { if (!isFocused.current || debouncedLocal === null) return; const tipAmount = convertMajorToMinorUnits(debouncedLocal ?? '', code); - form.setValue('tipAmount', tipAmount); + formRef.current.setValue('tipAmount', tipAmount); // Clear local state so the display derives from the formatted form // value (e.g. "10.5" → "10.50"), same as the blur handler. setLocalValue(null); - }, [debouncedLocal, code, form]); + }, [debouncedLocal, code]); const symbolEl = ( Date: Tue, 30 Jun 2026 23:03:29 -0700 Subject: [PATCH 3/9] enable tips in nextjs example --- examples/nextjs/app/page.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/nextjs/app/page.tsx b/examples/nextjs/app/page.tsx index 12ff2de4..88778aaa 100644 --- a/examples/nextjs/app/page.tsx +++ b/examples/nextjs/app/page.tsx @@ -21,6 +21,7 @@ export default async function Home() { enableTaxCollection: true, enableNotesCollection: true, enablePromotionCodes: true, + enableTips: true, shipping: { fulfillmentLocationId: 'default-location', originAddress: { From cfed7a4106202731884a23de842466c118e794ae Mon Sep 17 00:00:00 2001 From: Catherine Shing Date: Tue, 30 Jun 2026 23:03:59 -0700 Subject: [PATCH 4/9] fix tipPercentage schema --- packages/react/src/components/checkout/checkout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/components/checkout/checkout.tsx b/packages/react/src/components/checkout/checkout.tsx index 143ea113..1dd496cb 100644 --- a/packages/react/src/components/checkout/checkout.tsx +++ b/packages/react/src/components/checkout/checkout.tsx @@ -186,7 +186,7 @@ export const baseCheckoutSchema = z.object({ pickupLeadTime: z.number().nullish(), pickupTimezone: z.string().nullish(), tipAmount: z.number().optional(), - tipPercentage: z.number().optional(), + tipPercentage: z.number().nullish(), paymentMethod: z.string().min(1, 'Select a payment method'), stripePaymentIntent: z.string().optional(), stripePaymentIntentId: z.string().optional(), From 0d559e53ef3393e7524d24a50b599f2fb8690831 Mon Sep 17 00:00:00 2001 From: Catherine Shing Date: Tue, 30 Jun 2026 23:04:29 -0700 Subject: [PATCH 5/9] pass tipAmount in confirmCheckout --- .../payment/utils/use-confirm-checkout.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/react/src/components/checkout/payment/utils/use-confirm-checkout.ts b/packages/react/src/components/checkout/payment/utils/use-confirm-checkout.ts index 998fb3f9..0e72d58f 100644 --- a/packages/react/src/components/checkout/payment/utils/use-confirm-checkout.ts +++ b/packages/react/src/components/checkout/payment/utils/use-confirm-checkout.ts @@ -169,6 +169,12 @@ export function useConfirmCheckout() { defaultTimezone: session?.defaultOperatingHours?.timeZone, }) : {}; + const tipAmount = form.getValues('tipAmount'); + const payload = { + ...confirmCheckoutInput, + ...pickUpData, + tipAmount, + } // keep for debugging // console.log({ @@ -195,18 +201,12 @@ export function useConfirmCheckout() { const data = jwt ? await confirmCheckout( - { - ...confirmCheckoutInput, - ...(isPickup ? pickUpData : {}), - }, + payload, { accessToken: jwt, sessionId: session?.id || '' }, apiHost ) : await confirmCheckout( - { - ...confirmCheckoutInput, - ...(isPickup ? pickUpData : {}), - }, + payload, session, apiHost ); From ac650af48231818382d1d95498735f7b5a94b0ef Mon Sep 17 00:00:00 2001 From: Catherine Shing Date: Tue, 30 Jun 2026 23:05:16 -0700 Subject: [PATCH 6/9] add tipAmount definition --- packages/react/src/lib/godaddy/checkout-env.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react/src/lib/godaddy/checkout-env.ts b/packages/react/src/lib/godaddy/checkout-env.ts index 4e7d5c1c..7d5ac970 100644 --- a/packages/react/src/lib/godaddy/checkout-env.ts +++ b/packages/react/src/lib/godaddy/checkout-env.ts @@ -7804,6 +7804,13 @@ const introspection = { name: 'MoneyInput', }, }, + { + name: 'tipAmount', + type: { + kind: 'SCALAR', + name: 'Int', + }, + }, ], isOneOf: false, }, From 813e2a3247e52ddb7de3fe0a15363bb59b73b17c Mon Sep 17 00:00:00 2001 From: Catherine Shing Date: Tue, 30 Jun 2026 23:05:27 -0700 Subject: [PATCH 7/9] formatting --- packages/react/src/lib/godaddy/checkout-mutations.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/lib/godaddy/checkout-mutations.ts b/packages/react/src/lib/godaddy/checkout-mutations.ts index b5af9be9..8b94ffbb 100644 --- a/packages/react/src/lib/godaddy/checkout-mutations.ts +++ b/packages/react/src/lib/godaddy/checkout-mutations.ts @@ -392,10 +392,10 @@ export const ApplyCheckoutSessionDiscountMutation = graphql(` export const ConfirmCheckoutSessionMutation = graphql(` mutation ConfirmCheckoutSession($input: MutationConfirmCheckoutSessionInput!, $sessionId: String!) { - confirmCheckoutSession(input: $input, sessionId: $sessionId) { - status - } + confirmCheckoutSession(input: $input, sessionId: $sessionId) { + status } + } `); export const ApplyCheckoutSessionShippingMethodMutation = graphql(` From 3878b05a31785866de9d281ad7b1bff613bc1048 Mon Sep 17 00:00:00 2001 From: Catherine Shing Date: Tue, 30 Jun 2026 23:05:49 -0700 Subject: [PATCH 8/9] add tests --- .../checkout/__tests__/checkout-tips.test.tsx | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/packages/react/src/components/checkout/__tests__/checkout-tips.test.tsx b/packages/react/src/components/checkout/__tests__/checkout-tips.test.tsx index d634fea9..3af41864 100644 --- a/packages/react/src/components/checkout/__tests__/checkout-tips.test.tsx +++ b/packages/react/src/components/checkout/__tests__/checkout-tips.test.tsx @@ -8,6 +8,7 @@ import { waitForCheckoutReady, waitForOperation, } from './checkout-test-env'; +import { getLastConfirmInput } from './checkout-test-fixtures'; vi.mock('@/tracking/track', async importOriginal => { const actual = await importOriginal(); @@ -346,4 +347,119 @@ describe('Checkout tips', () => { expect(screen.queryByPlaceholderText('0')).not.toBeInTheDocument(); }); }); + + it('includes tipAmount in the ConfirmCheckoutSession mutation payload', async () => { + const { user } = renderCheckout({ + sessionOverrides: { + enableTips: true, + enableShipping: false, + enableLocalPickup: false, + enableTaxCollection: false, + paymentMethods: { + card: { + processor: 'godaddy', + checkoutTypes: ['standard'], + }, + }, + }, + draftOrderOverrides: { + totals: { + subTotal: { value: 2500, currencyCode: 'USD' }, + discountTotal: { value: 0, currencyCode: 'USD' }, + shippingTotal: { value: 0, currencyCode: 'USD' }, + taxTotal: { value: 0, currencyCode: 'USD' }, + feeTotal: { value: 0, currencyCode: 'USD' }, + total: { value: 2500, currencyCode: 'USD' }, + }, + }, + }); + await waitForCheckoutReady(); + clearOperations(); + + await user.click(await screen.findByRole('button', { name: /20%/ })); + await waitFor(() => { + expect(screen.getAllByText('$5.00').length).toBeGreaterThan(0); + }); + + await user.click(await screen.findByRole('button', { name: /pay now/i })); + await waitForOperation('ConfirmCheckoutSession'); + + expect(getLastConfirmInput()).toMatchObject({ + tipAmount: 500, + }); + }); + + it('includes a custom tipAmount when entering a custom tip before confirming', async () => { + const { user } = renderCheckout({ + sessionOverrides: { + enableTips: true, + enableShipping: false, + enableLocalPickup: false, + enableTaxCollection: false, + paymentMethods: { + card: { + processor: 'godaddy', + checkoutTypes: ['standard'], + }, + }, + }, + draftOrderOverrides: { + totals: { + subTotal: { value: 2500, currencyCode: 'USD' }, + discountTotal: { value: 0, currencyCode: 'USD' }, + shippingTotal: { value: 0, currencyCode: 'USD' }, + taxTotal: { value: 0, currencyCode: 'USD' }, + feeTotal: { value: 0, currencyCode: 'USD' }, + total: { value: 2500, currencyCode: 'USD' }, + }, + }, + }); + await waitForCheckoutReady(); + clearOperations(); + + await user.click( + await screen.findByRole('button', { name: /custom amount/i }) + ); + const input = await screen.findByPlaceholderText('0.00'); + await user.click(input); + await user.type(input, '7.50'); + await user.tab(); + + await waitFor(() => { + expect(screen.getAllByText('$7.50').length).toBeGreaterThan(0); + }); + + await user.click(await screen.findByRole('button', { name: /pay now/i })); + await waitForOperation('ConfirmCheckoutSession'); + + expect(getLastConfirmInput()).toMatchObject({ + tipAmount: 750, + }); + }); + + it('sends tipAmount as 0 when no tip is selected', async () => { + const { user } = renderCheckout({ + sessionOverrides: { + enableTips: true, + enableShipping: false, + enableLocalPickup: false, + enableTaxCollection: false, + paymentMethods: { + card: { + processor: 'godaddy', + checkoutTypes: ['standard'], + }, + }, + }, + }); + await waitForCheckoutReady(); + clearOperations(); + + await user.click(await screen.findByRole('button', { name: /pay now/i })); + await waitForOperation('ConfirmCheckoutSession'); + + expect(getLastConfirmInput()).toMatchObject({ + tipAmount: 0, + }); + }); }); From d1d3bb30bc8fd7d95d7f4a50c0e2b98730b5a0e7 Mon Sep 17 00:00:00 2001 From: Catherine Shing Date: Tue, 30 Jun 2026 23:23:33 -0700 Subject: [PATCH 9/9] changeset --- .changeset/fruity-dots-jog.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fruity-dots-jog.md diff --git a/.changeset/fruity-dots-jog.md b/.changeset/fruity-dots-jog.md new file mode 100644 index 00000000..e9a4fbd5 --- /dev/null +++ b/.changeset/fruity-dots-jog.md @@ -0,0 +1,5 @@ +--- +"@godaddy/react": patch +--- + +Support tips in unified checkout