From 62593b330ce07fcd3ddf103cea6c68bc56b8b8c7 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Wed, 2 Sep 2026 11:32:00 -0500 Subject: [PATCH 1/5] Add VAT Included line item for inclusive taxes --- packages/localizations/src/deDe.ts | 1 + packages/localizations/src/enAu.ts | 1 + packages/localizations/src/enIe.ts | 1 + packages/localizations/src/enUs.ts | 1 + packages/localizations/src/esAr.ts | 1 + packages/localizations/src/esCl.ts | 1 + packages/localizations/src/esCo.ts | 1 + packages/localizations/src/esEs.ts | 1 + packages/localizations/src/esMx.ts | 1 + packages/localizations/src/esPe.ts | 1 + packages/localizations/src/esUs.ts | 1 + packages/localizations/src/frCa.ts | 1 + packages/localizations/src/frFr.ts | 1 + packages/localizations/src/idId.ts | 1 + packages/localizations/src/itIt.ts | 1 + packages/localizations/src/ptBr.ts | 1 + packages/localizations/src/qaPs.ts | 1 + packages/localizations/src/trTr.ts | 1 + packages/localizations/src/viVn.ts | 1 + packages/localizations/src/zhCn.ts | 1 + packages/localizations/src/zhSg.ts | 1 + .../checkout-totals-summary.test.tsx | 41 ++ .../checkout/form/checkout-form.tsx | 9 +- .../checkout/order/use-draft-order.ts | 9 + .../checkout/payment/payment-form.tsx | 1 + .../src/components/checkout/totals/totals.tsx | 14 + .../utils/get-included-tax-total.test.ts | 20 + .../totals/utils/get-included-tax-total.ts | 20 + .../react/src/components/storefront/cart.tsx | 3 + .../react/src/lib/godaddy/checkout-env.ts | 502 ++++++++++++++++++ .../react/src/lib/godaddy/checkout-queries.ts | 11 + 31 files changed, 650 insertions(+), 1 deletion(-) create mode 100644 packages/react/src/components/checkout/totals/utils/get-included-tax-total.test.ts create mode 100644 packages/react/src/components/checkout/totals/utils/get-included-tax-total.ts diff --git a/packages/localizations/src/deDe.ts b/packages/localizations/src/deDe.ts index 1842a654..cc199580 100644 --- a/packages/localizations/src/deDe.ts +++ b/packages/localizations/src/deDe.ts @@ -168,6 +168,7 @@ export const deDe = { shipping: 'Versand', tip: 'Trinkgeld', estimatedTaxes: 'Geschätzte Steuern', + vatIncluded: 'MwSt. enthalten', fees: 'Gebühren', totalDue: 'Gesamtbetrag', orderSummary: 'Bestellübersicht', diff --git a/packages/localizations/src/enAu.ts b/packages/localizations/src/enAu.ts index 7c8eb760..97370621 100644 --- a/packages/localizations/src/enAu.ts +++ b/packages/localizations/src/enAu.ts @@ -168,6 +168,7 @@ export const enAu = { shipping: 'Shipping', tip: 'Tip', estimatedTaxes: 'Estimated GST', + vatIncluded: 'GST Included', fees: 'Fees', totalDue: 'Total Due', orderSummary: 'Order Summary', diff --git a/packages/localizations/src/enIe.ts b/packages/localizations/src/enIe.ts index e87a00a5..7dcaabd9 100644 --- a/packages/localizations/src/enIe.ts +++ b/packages/localizations/src/enIe.ts @@ -168,6 +168,7 @@ export const enIe = { shipping: 'Shipping', tip: 'Tip', estimatedTaxes: 'Estimated VAT', + vatIncluded: 'VAT Included', fees: 'Fees', totalDue: 'Total Due', orderSummary: 'Order Summary', diff --git a/packages/localizations/src/enUs.ts b/packages/localizations/src/enUs.ts index ffef4e36..de6df949 100644 --- a/packages/localizations/src/enUs.ts +++ b/packages/localizations/src/enUs.ts @@ -168,6 +168,7 @@ export const enUs = { shipping: 'Shipping', tip: 'Tip', estimatedTaxes: 'Estimated taxes', + vatIncluded: 'VAT Included', fees: 'Fees', totalDue: 'Total Due', orderSummary: 'Order Summary', diff --git a/packages/localizations/src/esAr.ts b/packages/localizations/src/esAr.ts index cf8a2e19..3a559776 100644 --- a/packages/localizations/src/esAr.ts +++ b/packages/localizations/src/esAr.ts @@ -169,6 +169,7 @@ export const esAr = { shipping: 'Envío', tip: 'Propina', estimatedTaxes: 'Impuestos estimados', + vatIncluded: 'IVA incluido', fees: 'Cargos', totalDue: 'Total a Pagar', orderSummary: 'Resumen del Pedido', diff --git a/packages/localizations/src/esCl.ts b/packages/localizations/src/esCl.ts index 72b4ccc5..fbb8b92e 100644 --- a/packages/localizations/src/esCl.ts +++ b/packages/localizations/src/esCl.ts @@ -169,6 +169,7 @@ export const esCl = { shipping: 'Envío', tip: 'Propina', estimatedTaxes: 'Impuestos estimados', + vatIncluded: 'IVA incluido', fees: 'Cargos', totalDue: 'Total a Pagar', orderSummary: 'Resumen del Pedido', diff --git a/packages/localizations/src/esCo.ts b/packages/localizations/src/esCo.ts index e78cdae5..a8a35eee 100644 --- a/packages/localizations/src/esCo.ts +++ b/packages/localizations/src/esCo.ts @@ -169,6 +169,7 @@ export const esCo = { shipping: 'Envío', tip: 'Propina', estimatedTaxes: 'Impuestos estimados', + vatIncluded: 'IVA incluido', fees: 'Cargos', totalDue: 'Total a pagar', orderSummary: 'Resumen del pedido', diff --git a/packages/localizations/src/esEs.ts b/packages/localizations/src/esEs.ts index 34f1005d..cbfc1945 100644 --- a/packages/localizations/src/esEs.ts +++ b/packages/localizations/src/esEs.ts @@ -169,6 +169,7 @@ export const esEs = { shipping: 'Envío', tip: 'Propina', estimatedTaxes: 'Impuestos estimados', + vatIncluded: 'IVA incluido', fees: 'Cargos', totalDue: 'Total a pagar', orderSummary: 'Resumen del pedido', diff --git a/packages/localizations/src/esMx.ts b/packages/localizations/src/esMx.ts index 94447505..c08c9818 100644 --- a/packages/localizations/src/esMx.ts +++ b/packages/localizations/src/esMx.ts @@ -169,6 +169,7 @@ export const esMx = { shipping: 'Envío', tip: 'Propina', estimatedTaxes: 'Impuestos estimados', + vatIncluded: 'IVA incluido', fees: 'Cargos', totalDue: 'Total a Pagar', orderSummary: 'Resumen del Pedido', diff --git a/packages/localizations/src/esPe.ts b/packages/localizations/src/esPe.ts index b5145772..6eed5991 100644 --- a/packages/localizations/src/esPe.ts +++ b/packages/localizations/src/esPe.ts @@ -169,6 +169,7 @@ export const esPe = { shipping: 'Envío', tip: 'Propina', estimatedTaxes: 'Impuestos estimados', + vatIncluded: 'IVA incluido', fees: 'Cargos', totalDue: 'Total a Pagar', orderSummary: 'Resumen del Pedido', diff --git a/packages/localizations/src/esUs.ts b/packages/localizations/src/esUs.ts index 4d0089e1..b8d7aec1 100644 --- a/packages/localizations/src/esUs.ts +++ b/packages/localizations/src/esUs.ts @@ -169,6 +169,7 @@ export const esUs = { shipping: 'Envío', tip: 'Propina', estimatedTaxes: 'Impuestos estimados', + vatIncluded: 'IVA incluido', fees: 'Cargos', totalDue: 'Total a Pagar', orderSummary: 'Resumen del Pedido', diff --git a/packages/localizations/src/frCa.ts b/packages/localizations/src/frCa.ts index 99c4888b..84654f2f 100644 --- a/packages/localizations/src/frCa.ts +++ b/packages/localizations/src/frCa.ts @@ -169,6 +169,7 @@ export const frCa = { shipping: 'Expédition', tip: 'Pourboire', estimatedTaxes: 'Taxes estimées', + vatIncluded: 'TVA incluse', fees: 'Frais', totalDue: 'Total à payer', orderSummary: 'Résumé de la commande', diff --git a/packages/localizations/src/frFr.ts b/packages/localizations/src/frFr.ts index c8345087..b8de1945 100644 --- a/packages/localizations/src/frFr.ts +++ b/packages/localizations/src/frFr.ts @@ -169,6 +169,7 @@ export const frFr = { shipping: 'Expédition', tip: 'Pourboire', estimatedTaxes: 'Taxes estimées', + vatIncluded: 'TVA incluse', fees: 'Frais', totalDue: 'Total à payer', orderSummary: 'Récapitulatif de la commande', diff --git a/packages/localizations/src/idId.ts b/packages/localizations/src/idId.ts index 41c794e4..9ea34471 100644 --- a/packages/localizations/src/idId.ts +++ b/packages/localizations/src/idId.ts @@ -168,6 +168,7 @@ export const idId = { shipping: 'Pengiriman', tip: 'Tip', estimatedTaxes: 'Perkiraan pajak', + vatIncluded: 'Termasuk PPN', fees: 'Biaya', totalDue: 'Total Pembayaran', orderSummary: 'Ringkasan Pesanan', diff --git a/packages/localizations/src/itIt.ts b/packages/localizations/src/itIt.ts index 89525d46..bb725624 100644 --- a/packages/localizations/src/itIt.ts +++ b/packages/localizations/src/itIt.ts @@ -169,6 +169,7 @@ export const itIt = { shipping: 'Spedizione', tip: 'Mancia', estimatedTaxes: 'Tasse stimate', + vatIncluded: 'IVA inclusa', fees: 'Commissioni', totalDue: 'Totale Dovuto', orderSummary: 'Riepilogo Ordine', diff --git a/packages/localizations/src/ptBr.ts b/packages/localizations/src/ptBr.ts index ae23da6e..5ae47d65 100644 --- a/packages/localizations/src/ptBr.ts +++ b/packages/localizations/src/ptBr.ts @@ -168,6 +168,7 @@ export const ptBr = { shipping: 'Envio', tip: 'Gorjeta', estimatedTaxes: 'Impostos estimados', + vatIncluded: 'IVA incluído', fees: 'Taxas', totalDue: 'Total a Pagar', orderSummary: 'Resumo do Pedido', diff --git a/packages/localizations/src/qaPs.ts b/packages/localizations/src/qaPs.ts index 3f72740e..bb297138 100644 --- a/packages/localizations/src/qaPs.ts +++ b/packages/localizations/src/qaPs.ts @@ -169,6 +169,7 @@ export const qaPs = { shipping: '[Šhîþþîñg Çöšţš]', tip: '[Ţîþ Âmöüñţ]', estimatedTaxes: '[Ëšţîmâţëd ţâxëš çâlçülâţîöñ]', + vatIncluded: '[VÂŢ Îñçlüdëd]', fees: '[Fëëš Çhârgëd]', totalDue: '[Tötâl Âmöüñţ Düë]', orderSummary: '[Ördër Šümmârÿ Dëţâîlš]', diff --git a/packages/localizations/src/trTr.ts b/packages/localizations/src/trTr.ts index 7854783b..ea629bf7 100644 --- a/packages/localizations/src/trTr.ts +++ b/packages/localizations/src/trTr.ts @@ -168,6 +168,7 @@ export const trTr = { shipping: 'Kargo', tip: 'Bahşiş', estimatedTaxes: 'Tahmini vergiler', + vatIncluded: 'KDV dahil', fees: 'Ücretler', totalDue: 'Ödenecek Toplam', orderSummary: 'Sipariş Özeti', diff --git a/packages/localizations/src/viVn.ts b/packages/localizations/src/viVn.ts index d829fe58..f28cad41 100644 --- a/packages/localizations/src/viVn.ts +++ b/packages/localizations/src/viVn.ts @@ -168,6 +168,7 @@ export const viVn = { shipping: 'Vận chuyển', tip: 'Tip', estimatedTaxes: 'Thuế ước tính', + vatIncluded: 'Đã bao gồm VAT', fees: 'Phí', totalDue: 'Tổng cộng', orderSummary: 'Tóm tắt đơn hàng', diff --git a/packages/localizations/src/zhCn.ts b/packages/localizations/src/zhCn.ts index 229f9bb8..107d724c 100644 --- a/packages/localizations/src/zhCn.ts +++ b/packages/localizations/src/zhCn.ts @@ -163,6 +163,7 @@ export const zhCn = { shipping: '配送费', tip: '小费', estimatedTaxes: '预估税费', + vatIncluded: '已含增值税', fees: '费用', totalDue: '应付总计', orderSummary: '订单摘要', diff --git a/packages/localizations/src/zhSg.ts b/packages/localizations/src/zhSg.ts index d380a7a1..fb489e1e 100644 --- a/packages/localizations/src/zhSg.ts +++ b/packages/localizations/src/zhSg.ts @@ -163,6 +163,7 @@ export const zhSg = { shipping: '运费', tip: '小费', estimatedTaxes: '预估税费', + vatIncluded: '已含消费税', fees: '费用', totalDue: '应付总额', orderSummary: '订单摘要', diff --git a/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx b/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx index 4be61e02..e24d80bd 100644 --- a/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx +++ b/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx @@ -66,6 +66,47 @@ describe('Checkout totals and order summary UI', () => { expect(document.body).toHaveTextContent(/estimated taxes/i); expect(document.body).toHaveTextContent(/fees/i); expect(document.body).toHaveTextContent(/total due/i); + expect(document.body).not.toHaveTextContent(/vat included/i); + }); + + it('renders the total of included tax constituents separately from additive taxes', async () => { + renderCheckout({ + draftOrderOverrides: { + totals: totals({ + taxTotal: { value: 456, currencyCode: 'USD' }, + }), + taxes: [ + { + id: 'included-tax-1', + name: 'VAT', + included: true, + exempted: false, + ratePercentage: '2.5', + amount: { value: 100, currencyCode: 'USD' }, + }, + { + id: 'additive-tax', + name: 'Sales tax', + included: false, + exempted: false, + ratePercentage: '5', + amount: { value: 456, currencyCode: 'USD' }, + }, + { + id: 'included-tax-2', + name: 'VAT surcharge', + included: true, + exempted: false, + ratePercentage: '0.5', + amount: { value: 23, currencyCode: 'USD' }, + }, + ], + }, + }); + await waitForCheckoutReady(); + + expect(document.body).toHaveTextContent(/vat included/i); + expect(screen.getAllByText('$1.23').length).toBeGreaterThan(0); }); it('renders loading skeleton rows for in-flight discount, shipping, tax, and fee mutations', async () => { diff --git a/packages/react/src/components/checkout/form/checkout-form.tsx b/packages/react/src/components/checkout/form/checkout-form.tsx index bcecac7f..2af5d70b 100644 --- a/packages/react/src/components/checkout/form/checkout-form.tsx +++ b/packages/react/src/components/checkout/form/checkout-form.tsx @@ -25,7 +25,10 @@ import { import { NotesForm } from '@/components/checkout/notes/notes-form'; import { DraftOrderSyncProvider } from '@/components/checkout/order/draft-order-sync-provider'; import { isFreeOrderTotal } from '@/components/checkout/order/is-free-order'; -import { useDraftOrderTotals } from '@/components/checkout/order/use-draft-order'; +import { + useDraftOrderIncludedTaxTotal, + useDraftOrderTotals, +} from '@/components/checkout/order/use-draft-order'; import { BillingPolicyTransitionController } from '@/components/checkout/payment/billing-policy-transition-controller'; import { PaymentForm } from '@/components/checkout/payment/payment-form'; import { @@ -200,6 +203,7 @@ export function CheckoutForm({ }, [dirtyFields, form, formValues, isCheckoutBusy]); const draftOrderTotalsQuery = useDraftOrderTotals(); + const { data: vatIncluded = 0 } = useDraftOrderIncludedTaxTotal(); const { data: totals, isLoading: totalsLoading } = draftOrderTotalsQuery; validationContextRef.current.totals = totals; @@ -551,6 +555,7 @@ export function CheckoutForm({ currencyCode={currencyCode} tip={tipTotal} taxes={taxTotal} + vatIncluded={vatIncluded} fees={feeTotal} isTaxLoading={isUpdatingTaxes} isFeeLoading={isUpdatingFees} @@ -622,6 +627,7 @@ export function CheckoutForm({ currencyCode={currencyCode} tip={tipTotal} taxes={taxTotal} + vatIncluded={vatIncluded} fees={feeTotal} isTaxLoading={isUpdatingTaxes} isFeeLoading={isUpdatingFees} @@ -656,6 +662,7 @@ export function CheckoutForm({ currencyCode={currencyCode} tip={tipTotal} taxes={taxTotal} + vatIncluded={vatIncluded} fees={feeTotal} isTaxLoading={isUpdatingTaxes} isFeeLoading={isUpdatingFees} diff --git a/packages/react/src/components/checkout/order/use-draft-order.ts b/packages/react/src/components/checkout/order/use-draft-order.ts index 8e97ada8..5c28ae2f 100644 --- a/packages/react/src/components/checkout/order/use-draft-order.ts +++ b/packages/react/src/components/checkout/order/use-draft-order.ts @@ -1,5 +1,6 @@ import { type UseQueryResult, useQuery } from '@tanstack/react-query'; import { useCheckoutContext } from '@/components/checkout/checkout'; +import { getIncludedTaxTotal } from '@/components/checkout/totals/utils/get-included-tax-total'; import { checkoutQueryKeys } from '@/components/checkout/utils/query-keys'; import { useGoDaddyContext } from '@/godaddy-provider'; import { getDraftOrder } from '@/lib/godaddy/godaddy'; @@ -63,6 +64,14 @@ export function useDraftOrderTotals() { ); } +export function useDraftOrderIncludedTaxTotal() { + return useDraftOrder( + data => + getIncludedTaxTotal(data.checkoutSession?.draftOrder?.taxes ?? null), + 'draft-order' + ); +} + export function useDraftOrderShipping() { return useDraftOrder( data => data.checkoutSession?.draftOrder?.shippingLines?.[0] ?? null, diff --git a/packages/react/src/components/checkout/payment/payment-form.tsx b/packages/react/src/components/checkout/payment/payment-form.tsx index a38dc70d..047de38a 100644 --- a/packages/react/src/components/checkout/payment/payment-form.tsx +++ b/packages/react/src/components/checkout/payment/payment-form.tsx @@ -596,6 +596,7 @@ export function PaymentForm( currencyCode={props.currencyCode} tip={props.tip} taxes={props.taxes} + vatIncluded={props.vatIncluded} fees={props.fees} isTaxLoading={props.isTaxLoading} isFeeLoading={props.isFeeLoading} diff --git a/packages/react/src/components/checkout/totals/totals.tsx b/packages/react/src/components/checkout/totals/totals.tsx index ffee47e2..fc3bd790 100644 --- a/packages/react/src/components/checkout/totals/totals.tsx +++ b/packages/react/src/components/checkout/totals/totals.tsx @@ -17,6 +17,7 @@ export interface DraftOrderTotalsProps { total?: number; tip?: number; taxes?: number; + vatIncluded?: number; isTaxLoading?: boolean; fees?: number; isFeeLoading?: boolean; @@ -72,6 +73,7 @@ export function DraftOrderTotals({ total = 0, tip = 0, taxes = 0, + vatIncluded = 0, fees = 0, enableDiscounts = false, enableTaxes = false, @@ -155,6 +157,18 @@ export function DraftOrderTotals({ inputInMinorUnits={inputInMinorUnits} /> ))} + {vatIncluded > 0 ? ( + isTaxLoading ? ( + + ) : ( + + ) + ) : null} {enableFees && (isFeeLoading ? ( diff --git a/packages/react/src/components/checkout/totals/utils/get-included-tax-total.test.ts b/packages/react/src/components/checkout/totals/utils/get-included-tax-total.test.ts new file mode 100644 index 00000000..a7733740 --- /dev/null +++ b/packages/react/src/components/checkout/totals/utils/get-included-tax-total.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest'; +import { getIncludedTaxTotal } from './get-included-tax-total'; + +describe('getIncludedTaxTotal', () => { + it('totals only included tax constituents', () => { + expect( + getIncludedTaxTotal([ + { included: true, additional: false, amount: { value: 125 } }, + { included: false, additional: true, amount: { value: 300 } }, + { included: true, additional: null, amount: { value: 75 } }, + { included: true, additional: true, amount: { value: 500 } }, + ]) + ).toBe(200); + }); + + it('returns zero when included taxes have no positive amount', () => { + expect(getIncludedTaxTotal()).toBe(0); + expect(getIncludedTaxTotal([{ included: true, amount: null }])).toBe(0); + }); +}); diff --git a/packages/react/src/components/checkout/totals/utils/get-included-tax-total.ts b/packages/react/src/components/checkout/totals/utils/get-included-tax-total.ts new file mode 100644 index 00000000..4f0d7acb --- /dev/null +++ b/packages/react/src/components/checkout/totals/utils/get-included-tax-total.ts @@ -0,0 +1,20 @@ +interface TaxAmount { + included?: boolean | null; + additional?: boolean | null; + amount?: { + value?: number | null; + } | null; +} + +export function getIncludedTaxTotal( + taxes?: readonly TaxAmount[] | null +): number { + return ( + taxes?.reduce( + (total, tax) => + total + + (tax.included && tax.additional !== true ? tax.amount?.value || 0 : 0), + 0 + ) ?? 0 + ); +} diff --git a/packages/react/src/components/storefront/cart.tsx b/packages/react/src/components/storefront/cart.tsx index a5021d53..41b3d997 100644 --- a/packages/react/src/components/storefront/cart.tsx +++ b/packages/react/src/components/storefront/cart.tsx @@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Loader2, ShoppingCart } from 'lucide-react'; import type { Product } from '@/components/checkout/line-items/line-items'; +import { getIncludedTaxTotal } from '@/components/checkout/totals/utils/get-included-tax-total'; import { CartLineItems } from '@/components/storefront/cart-line-items'; import { CartTotals } from '@/components/storefront/cart-totals'; import { Button } from '@/components/ui/button'; @@ -110,6 +111,7 @@ export function Cart({ const subtotal = order?.totals?.subTotal?.value || 0; const shipping = order?.totals?.shippingTotal?.value || 0; const taxes = order?.totals?.taxTotal?.value || 0; + const vatIncluded = getIncludedTaxTotal(order?.taxes); const discount = order?.totals?.discountTotal?.value || 0; const total = order?.totals?.total?.value || 0; @@ -122,6 +124,7 @@ export function Cart({ total, tip: 0, taxes, + vatIncluded, enableDiscounts: false, enableTaxes: true, isTaxLoading: false, diff --git a/packages/react/src/lib/godaddy/checkout-env.ts b/packages/react/src/lib/godaddy/checkout-env.ts index c9ab79e9..65038eb1 100644 --- a/packages/react/src/lib/godaddy/checkout-env.ts +++ b/packages/react/src/lib/godaddy/checkout-env.ts @@ -2139,6 +2139,15 @@ const introspection = { "args": [], "isDeprecated": false }, + { + "name": "tips", + "type": { + "kind": "OBJECT", + "name": "CheckoutSessionTips" + }, + "args": [], + "isDeprecated": false + }, { "name": "token", "type": { @@ -2970,6 +2979,80 @@ const introspection = { ], "isOneOf": false }, + { + "kind": "OBJECT", + "name": "CheckoutSessionFee", + "fields": [ + { + "name": "amount", + "type": { + "kind": "SCALAR", + "name": "Int" + }, + "args": [], + "isDeprecated": false + }, + { + "name": "feeProgramId", + "type": { + "kind": "SCALAR", + "name": "String" + }, + "args": [], + "isDeprecated": false + }, + { + "name": "feeProgramType", + "type": { + "kind": "SCALAR", + "name": "String" + }, + "args": [], + "isDeprecated": false + }, + { + "name": "feeType", + "type": { + "kind": "SCALAR", + "name": "String" + }, + "args": [], + "isDeprecated": false + }, + { + "name": "signature", + "type": { + "kind": "SCALAR", + "name": "String" + }, + "args": [], + "isDeprecated": false + } + ], + "interfaces": [] + }, + { + "kind": "OBJECT", + "name": "CheckoutSessionFeesResult", + "fields": [ + { + "name": "fees", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "OBJECT", + "name": "CheckoutSessionFee" + } + } + }, + "args": [], + "isDeprecated": false + } + ], + "interfaces": [] + }, { "kind": "OBJECT", "name": "CheckoutSessionFreeShippingRule", @@ -3999,6 +4082,242 @@ const introspection = { ], "isOneOf": false }, + { + "kind": "OBJECT", + "name": "CheckoutSessionTips", + "fields": [ + { + "name": "default", + "type": { + "kind": "OBJECT", + "name": "CheckoutSessionTipsDefault" + }, + "args": [], + "isDeprecated": false + }, + { + "name": "thresholds", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "OBJECT", + "name": "CheckoutSessionTipsThreshold" + } + } + }, + "args": [], + "isDeprecated": false + } + ], + "interfaces": [] + }, + { + "kind": "OBJECT", + "name": "CheckoutSessionTipsDefault", + "fields": [ + { + "name": "amounts", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Int" + } + } + }, + "args": [], + "isDeprecated": false + }, + { + "name": "percentages", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Int" + } + } + }, + "args": [], + "isDeprecated": false + } + ], + "interfaces": [] + }, + { + "kind": "INPUT_OBJECT", + "name": "CheckoutSessionTipsDefaultInput", + "inputFields": [ + { + "name": "amounts", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Int" + } + } + } + }, + { + "name": "percentages", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Int" + } + } + } + } + ], + "isOneOf": false + }, + { + "kind": "INPUT_OBJECT", + "name": "CheckoutSessionTipsInput", + "inputFields": [ + { + "name": "default", + "type": { + "kind": "INPUT_OBJECT", + "name": "CheckoutSessionTipsDefaultInput" + } + }, + { + "name": "thresholds", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CheckoutSessionTipsThresholdInput" + } + } + } + } + ], + "isOneOf": false + }, + { + "kind": "OBJECT", + "name": "CheckoutSessionTipsThreshold", + "fields": [ + { + "name": "amounts", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Int" + } + } + }, + "args": [], + "isDeprecated": false + }, + { + "name": "maxSubtotal", + "type": { + "kind": "SCALAR", + "name": "Int" + }, + "args": [], + "isDeprecated": false + }, + { + "name": "minSubtotal", + "type": { + "kind": "SCALAR", + "name": "Int" + }, + "args": [], + "isDeprecated": false + }, + { + "name": "percentages", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Int" + } + } + }, + "args": [], + "isDeprecated": false + } + ], + "interfaces": [] + }, + { + "kind": "INPUT_OBJECT", + "name": "CheckoutSessionTipsThresholdInput", + "inputFields": [ + { + "name": "amounts", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Int" + } + } + } + }, + { + "name": "maxSubtotal", + "type": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Int" + } + } + }, + { + "name": "minSubtotal", + "type": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Int" + } + } + }, + { + "name": "percentages", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Int" + } + } + } + } + ], + "isOneOf": false + }, { "kind": "OBJECT", "name": "CheckoutSessionTotalTaxAmount", @@ -6334,6 +6653,46 @@ const introspection = { ], "isOneOf": false }, + { + "kind": "ENUM", + "name": "FeeProgramType", + "enumValues": [ + { + "name": "CASH_DISCOUNT", + "isDeprecated": false + }, + { + "name": "CONVENIENCE_FEE", + "isDeprecated": false + }, + { + "name": "SERVICE_FEE", + "isDeprecated": false + }, + { + "name": "SURCHARGE", + "isDeprecated": false + } + ] + }, + { + "kind": "ENUM", + "name": "FeeType", + "enumValues": [ + { + "name": "FIXED", + "isDeprecated": false + }, + { + "name": "HYBRID", + "isDeprecated": false + }, + { + "name": "PERCENTAGE", + "isDeprecated": false + } + ] + }, { "kind": "SCALAR", "name": "Float" @@ -6408,6 +6767,24 @@ const introspection = { } ] }, + { + "kind": "ENUM", + "name": "FundingSourceType", + "enumValues": [ + { + "name": "CREDIT", + "isDeprecated": false + }, + { + "name": "DEBIT", + "isDeprecated": false + }, + { + "name": "PREPAID", + "isDeprecated": false + } + ] + }, { "kind": "OBJECT", "name": "GeoCoordinates", @@ -7397,6 +7774,26 @@ const introspection = { ], "isDeprecated": false }, + { + "name": "calculateCheckoutSessionFees", + "type": { + "kind": "OBJECT", + "name": "CheckoutSessionFeesResult" + }, + "args": [ + { + "name": "fundingSourceType", + "type": { + "kind": "NON_NULL", + "ofType": { + "kind": "ENUM", + "name": "FundingSourceType" + } + } + } + ], + "isDeprecated": false + }, { "name": "calculateCheckoutSessionTaxes", "type": { @@ -7680,6 +8077,19 @@ const introspection = { "kind": "INPUT_OBJECT", "name": "MutationAuthorizeCheckoutSessionInput", "inputFields": [ + { + "name": "fees", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "INPUT_OBJECT", + "name": "TransactionFeeInput" + } + } + } + }, { "name": "paymentProvider", "type": { @@ -7706,6 +8116,13 @@ const introspection = { "name": "String" } } + }, + { + "name": "tipAmount", + "type": { + "kind": "SCALAR", + "name": "Int" + } } ], "isOneOf": false @@ -7735,6 +8152,19 @@ const introspection = { "name": "CalculatedTaxesInput" } }, + { + "name": "fees", + "type": { + "kind": "LIST", + "ofType": { + "kind": "NON_NULL", + "ofType": { + "kind": "INPUT_OBJECT", + "name": "TransactionFeeInput" + } + } + } + }, { "name": "fulfillmentEndAt", "type": { @@ -7819,6 +8249,13 @@ const introspection = { "kind": "INPUT_OBJECT", "name": "MoneyInput" } + }, + { + "name": "tipAmount", + "type": { + "kind": "SCALAR", + "name": "Int" + } } ], "isOneOf": false @@ -8091,6 +8528,13 @@ const introspection = { "name": "CheckoutSessionTaxesOptionsInput" } }, + { + "name": "tips", + "type": { + "kind": "INPUT_OBJECT", + "name": "CheckoutSessionTipsInput" + } + }, { "name": "url", "type": { @@ -8523,6 +8967,13 @@ const introspection = { "name": "String" } }, + { + "name": "tips", + "type": { + "kind": "INPUT_OBJECT", + "name": "CheckoutSessionTipsInput" + } + }, { "name": "url", "type": { @@ -10844,6 +11295,57 @@ const introspection = { ], "interfaces": [] }, + { + "kind": "INPUT_OBJECT", + "name": "TransactionFeeInput", + "inputFields": [ + { + "name": "amount", + "type": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "Int" + } + } + }, + { + "name": "feeProgramType", + "type": { + "kind": "NON_NULL", + "ofType": { + "kind": "ENUM", + "name": "FeeProgramType" + } + } + }, + { + "name": "feeType", + "type": { + "kind": "NON_NULL", + "ofType": { + "kind": "ENUM", + "name": "FeeType" + } + } + }, + { + "name": "required", + "type": { + "kind": "SCALAR", + "name": "Boolean" + } + }, + { + "name": "signature", + "type": { + "kind": "SCALAR", + "name": "String" + } + } + ], + "isOneOf": false + }, { "kind": "OBJECT", "name": "TransactionFundingSource", diff --git a/packages/react/src/lib/godaddy/checkout-queries.ts b/packages/react/src/lib/godaddy/checkout-queries.ts index 4e3e6185..c84001d5 100644 --- a/packages/react/src/lib/godaddy/checkout-queries.ts +++ b/packages/react/src/lib/godaddy/checkout-queries.ts @@ -483,6 +483,17 @@ export const DraftOrderQuery = graphql(` name ratePercentage } + taxes { + amount { + currencyCode + value + } + exempted + id + included + name + ratePercentage + } totals { discountTotal { currencyCode From 45c1e0abff5aad9050b4b1ef5aa0c2be8c6d7a04 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Wed, 2 Sep 2026 11:35:43 -0500 Subject: [PATCH 2/5] add changeset --- .changeset/wet-plants-float.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/wet-plants-float.md diff --git a/.changeset/wet-plants-float.md b/.changeset/wet-plants-float.md new file mode 100644 index 00000000..e6427a9b --- /dev/null +++ b/.changeset/wet-plants-float.md @@ -0,0 +1,6 @@ +--- +"@godaddy/localizations": patch +"@godaddy/react": patch +--- + +Add line item for inclusive taxes during checkout From d0e428de7b5b96f03a1e4a70a871750bd7d59c14 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Wed, 2 Sep 2026 11:49:26 -0500 Subject: [PATCH 3/5] adjust translation casing --- packages/localizations/src/enAu.ts | 2 +- packages/localizations/src/enIe.ts | 2 +- packages/localizations/src/enUs.ts | 2 +- packages/localizations/src/qaPs.ts | 2 +- packages/react/src/lib/godaddy/orders-storefront-queries.ts | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/localizations/src/enAu.ts b/packages/localizations/src/enAu.ts index 97370621..ca64650d 100644 --- a/packages/localizations/src/enAu.ts +++ b/packages/localizations/src/enAu.ts @@ -168,7 +168,7 @@ export const enAu = { shipping: 'Shipping', tip: 'Tip', estimatedTaxes: 'Estimated GST', - vatIncluded: 'GST Included', + vatIncluded: 'GST included', fees: 'Fees', totalDue: 'Total Due', orderSummary: 'Order Summary', diff --git a/packages/localizations/src/enIe.ts b/packages/localizations/src/enIe.ts index 7dcaabd9..dc90eb97 100644 --- a/packages/localizations/src/enIe.ts +++ b/packages/localizations/src/enIe.ts @@ -168,7 +168,7 @@ export const enIe = { shipping: 'Shipping', tip: 'Tip', estimatedTaxes: 'Estimated VAT', - vatIncluded: 'VAT Included', + vatIncluded: 'VAT included', fees: 'Fees', totalDue: 'Total Due', orderSummary: 'Order Summary', diff --git a/packages/localizations/src/enUs.ts b/packages/localizations/src/enUs.ts index de6df949..4797fbde 100644 --- a/packages/localizations/src/enUs.ts +++ b/packages/localizations/src/enUs.ts @@ -168,7 +168,7 @@ export const enUs = { shipping: 'Shipping', tip: 'Tip', estimatedTaxes: 'Estimated taxes', - vatIncluded: 'VAT Included', + vatIncluded: 'VAT included', fees: 'Fees', totalDue: 'Total Due', orderSummary: 'Order Summary', diff --git a/packages/localizations/src/qaPs.ts b/packages/localizations/src/qaPs.ts index bb297138..6b769778 100644 --- a/packages/localizations/src/qaPs.ts +++ b/packages/localizations/src/qaPs.ts @@ -169,7 +169,7 @@ export const qaPs = { shipping: '[Šhîþþîñg Çöšţš]', tip: '[Ţîþ Âmöüñţ]', estimatedTaxes: '[Ëšţîmâţëd ţâxëš çâlçülâţîöñ]', - vatIncluded: '[VÂŢ Îñçlüdëd]', + vatIncluded: '[VÂŢ îñçlüdëd]', fees: '[Fëëš Çhârgëd]', totalDue: '[Tötâl Âmöüñţ Düë]', orderSummary: '[Ördër Šümmârÿ Dëţâîlš]', diff --git a/packages/react/src/lib/godaddy/orders-storefront-queries.ts b/packages/react/src/lib/godaddy/orders-storefront-queries.ts index 4a4dcf4c..1d48729a 100644 --- a/packages/react/src/lib/godaddy/orders-storefront-queries.ts +++ b/packages/react/src/lib/godaddy/orders-storefront-queries.ts @@ -136,6 +136,7 @@ export const GetCartOrderQuery = graphql(` } ratePercentage included + additional exempted } shipping { From 6b0368c577ecd9056f9cb8b988b30886b76f017c Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Wed, 9 Sep 2026 22:33:22 -0500 Subject: [PATCH 4/5] feedback adjustments --- .../checkout-layout-appearance.test.tsx | 1 + .../checkout-totals-summary.test.tsx | 184 ++++++- .../checkout/form/checkout-form.tsx | 23 +- .../checkout/order/use-draft-order.ts | 3 +- .../checkout/order/use-update-taxes.ts | 36 +- .../checkout/payment/payment-form.tsx | 3 +- .../src/components/checkout/target/types.ts | 1 + .../src/components/checkout/totals/totals.tsx | 11 +- .../utils/get-included-tax-total.test.ts | 8 +- .../totals/utils/get-included-tax-total.ts | 4 +- .../react/src/components/storefront/cart.tsx | 4 +- .../react/src/lib/godaddy/checkout-env.ts | 502 ------------------ .../lib/godaddy/orders-storefront-queries.ts | 1 - 13 files changed, 218 insertions(+), 563 deletions(-) diff --git a/packages/react/src/components/checkout/__tests__/checkout-layout-appearance.test.tsx b/packages/react/src/components/checkout/__tests__/checkout-layout-appearance.test.tsx index d41a18ea..f516ccf6 100644 --- a/packages/react/src/components/checkout/__tests__/checkout-layout-appearance.test.tsx +++ b/packages/react/src/components/checkout/__tests__/checkout-layout-appearance.test.tsx @@ -42,6 +42,7 @@ const targetSlots = [ 'checkout.summary.totals.shipping.before', 'checkout.summary.totals.tip.before', 'checkout.summary.totals.taxes.before', + 'checkout.summary.totals.included-taxes.before', 'checkout.summary.totals.fees.before', 'checkout.summary.totals.after', 'checkout.summary.totals.total-due.before', diff --git a/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx b/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx index e24d80bd..114d9a30 100644 --- a/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx +++ b/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx @@ -1,7 +1,10 @@ import { act, screen, waitFor } from '@testing-library/react'; -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; +import { useUpdateTaxes } from '@/components/checkout/order/use-update-taxes'; import { checkoutMutationKeys } from '@/components/checkout/utils/query-keys'; +import { getDraftOrder, updateDraftOrderTaxes } from '@/lib/godaddy/godaddy'; import { + buildDraftOrder, buildLineItem, renderCheckout, setFeeTotal, @@ -106,9 +109,186 @@ describe('Checkout totals and order summary UI', () => { await waitForCheckoutReady(); expect(document.body).toHaveTextContent(/vat included/i); - expect(screen.getAllByText('$1.23').length).toBeGreaterThan(0); + for (const label of screen.getAllByText(/vat included/i)) { + expect(label.closest('.flex.justify-between')).toHaveTextContent('$1.23'); + } }); + it('hides included taxes when tax display is disabled', async () => { + renderCheckout({ + sessionOverrides: { enableTaxCollection: false }, + draftOrderOverrides: { + totals: totals({ taxTotal: { value: 0, currencyCode: 'USD' } }), + taxes: [ + { included: true, amount: { value: 123, currencyCode: 'USD' } }, + ], + }, + }); + await waitForCheckoutReady(); + + expect(document.body).not.toHaveTextContent( + /estimated taxes|vat included/i + ); + }); + + it.each([ + { included: false, amount: 123 }, + { included: true, amount: 0 }, + ])( + 'hides the included-tax row for $included / $amount', + async ({ included, amount }) => { + renderCheckout({ + draftOrderOverrides: { + taxes: [{ included, amount: { value: amount, currencyCode: 'USD' } }], + }, + }); + await waitForCheckoutReady(); + + expect(document.body).not.toHaveTextContent(/vat included/i); + } + ); + + it.each([ + 'applyDiscount', + 'applyShippingMethod', + 'removeShippingMethod', + ] as const)( + 'hides the previous included-tax amount during %s', + async mutation => { + const { queryClient, session } = renderCheckout({ + draftOrderOverrides: { + taxes: [ + { included: true, amount: { value: 123, currencyCode: 'USD' } }, + ], + }, + }); + await waitForCheckoutReady(); + await waitFor(() => expect(queryClient.isMutating()).toBe(0)); + + let finishMutation!: () => void; + const execution = queryClient + .getMutationCache() + .build(queryClient, { + mutationKey: checkoutMutationKeys[mutation](session.id), + mutationFn: () => + new Promise(resolve => { + finishMutation = resolve; + }), + }) + .execute(undefined); + + await waitFor(() => { + for (const label of screen.getAllByText(/vat included/i)) { + const row = label.closest('.flex.justify-between'); + expect(row?.querySelector('.animate-pulse')).toBeInTheDocument(); + expect(row).not.toHaveTextContent('$1.23'); + } + }); + + await act(async () => { + finishMutation(); + await execution; + }); + await waitFor(() => { + for (const label of screen.getAllByText(/vat included/i)) { + expect(label.closest('.flex.justify-between')).toHaveTextContent( + '$1.23' + ); + } + }); + } + ); + + it.each([50, 0])( + 'keeps tax loading until a refetch returns included taxes of %i', + async amount => { + function UpdateTaxesButton() { + const mutation = useUpdateTaxes(); + return ( + + ); + } + + const { queryClient, session, draftOrder, user } = renderCheckout({ + draftOrderOverrides: { + taxes: [ + { included: true, amount: { value: 123, currencyCode: 'USD' } }, + ], + }, + checkoutProps: { + targets: { 'checkout.form.before': () => }, + }, + }); + await waitForCheckoutReady(); + await waitFor(() => { + expect(queryClient.isMutating()).toBe(0); + expect(queryClient.isFetching()).toBe(0); + }); + + let finishRefetch!: () => void; + const refetchGate = new Promise(resolve => { + finishRefetch = resolve; + }); + vi.mocked(updateDraftOrderTaxes).mockResolvedValueOnce({ + calculateCheckoutSessionTaxes: { + totalTaxAmount: { value: 0, currencyCode: 'USD' }, + }, + }); + vi.mocked(getDraftOrder).mockImplementationOnce(async () => { + await refetchGate; + return { + checkoutSession: { + ...session, + draftOrder: buildDraftOrder({ + ...draftOrder, + taxes: + amount > 0 + ? [ + { + included: true, + amount: { value: amount, currencyCode: 'USD' }, + }, + ] + : [], + totals: totals({ taxTotal: { value: 0, currencyCode: 'USD' } }), + }), + }, + }; + }); + + await user.click( + screen.getByRole('button', { name: 'Recalculate taxes' }) + ); + await waitFor(() => expect(queryClient.isFetching()).toBeGreaterThan(0)); + expect( + queryClient.isMutating({ + mutationKey: checkoutMutationKeys.updateDraftOrderTaxes(session.id), + }) + ).toBe(1); + for (const label of screen.getAllByText(/vat included/i)) { + const row = label.closest('.flex.justify-between'); + expect(row?.querySelector('.animate-pulse')).toBeInTheDocument(); + expect(row).not.toHaveTextContent('$1.23'); + } + + await act(async () => { + finishRefetch(); + }); + await waitFor(() => expect(queryClient.isMutating()).toBe(0)); + if (amount > 0) { + for (const label of screen.getAllByText(/vat included/i)) { + const row = label.closest('.flex.justify-between'); + expect(row).toHaveTextContent('$0.50'); + expect(row?.querySelector('.animate-pulse')).not.toBeInTheDocument(); + } + } else { + expect(document.body).not.toHaveTextContent(/vat included/i); + } + } + ); + it('renders loading skeleton rows for in-flight discount, shipping, tax, and fee mutations', async () => { const { queryClient, session } = renderCheckout({ sessionOverrides: { diff --git a/packages/react/src/components/checkout/form/checkout-form.tsx b/packages/react/src/components/checkout/form/checkout-form.tsx index 2af5d70b..937a72ec 100644 --- a/packages/react/src/components/checkout/form/checkout-form.tsx +++ b/packages/react/src/components/checkout/form/checkout-form.tsx @@ -157,6 +157,11 @@ export function CheckoutForm({ const tipAmount = form.watch('tipAmount'); const isPickup = deliveryMethod === DeliveryMethods.PICKUP; const isShipping = deliveryMethod === DeliveryMethods.SHIP; + const isRemovingShipping = + useIsMutating({ + mutationKey: checkoutMutationKeys.removeShippingMethod(session?.id), + }) > 0; + const isUpdatingShipping = useIsMutating({ mutationKey: checkoutMutationKeys.applyShippingMethod(session?.id), @@ -203,7 +208,7 @@ export function CheckoutForm({ }, [dirtyFields, form, formValues, isCheckoutBusy]); const draftOrderTotalsQuery = useDraftOrderTotals(); - const { data: vatIncluded = 0 } = useDraftOrderIncludedTaxTotal(); + const { data: includedTaxTotal = 0 } = useDraftOrderIncludedTaxTotal(); const { data: totals, isLoading: totalsLoading } = draftOrderTotalsQuery; validationContextRef.current.totals = totals; @@ -555,11 +560,13 @@ export function CheckoutForm({ currencyCode={currencyCode} tip={tipTotal} taxes={taxTotal} - vatIncluded={vatIncluded} + includedTaxTotal={includedTaxTotal} fees={feeTotal} isTaxLoading={isUpdatingTaxes} isFeeLoading={isUpdatingFees} - isShippingLoading={isUpdatingShipping} + isShippingLoading={ + isUpdatingShipping || isRemovingShipping + } isDiscountLoading={isDiscountApplying} subtotal={subtotal} discount={orderDiscount} @@ -627,11 +634,13 @@ export function CheckoutForm({ currencyCode={currencyCode} tip={tipTotal} taxes={taxTotal} - vatIncluded={vatIncluded} + includedTaxTotal={includedTaxTotal} fees={feeTotal} isTaxLoading={isUpdatingTaxes} isFeeLoading={isUpdatingFees} - isShippingLoading={isUpdatingShipping} + isShippingLoading={ + isUpdatingShipping || isRemovingShipping + } subtotal={subtotal} discount={orderDiscount} isDiscountLoading={isDiscountApplying} @@ -662,11 +671,11 @@ export function CheckoutForm({ currencyCode={currencyCode} tip={tipTotal} taxes={taxTotal} - vatIncluded={vatIncluded} + includedTaxTotal={includedTaxTotal} fees={feeTotal} isTaxLoading={isUpdatingTaxes} isFeeLoading={isUpdatingFees} - isShippingLoading={isUpdatingShipping} + isShippingLoading={isUpdatingShipping || isRemovingShipping} subtotal={subtotal} discount={orderDiscount} isDiscountLoading={isDiscountApplying} diff --git a/packages/react/src/components/checkout/order/use-draft-order.ts b/packages/react/src/components/checkout/order/use-draft-order.ts index 5c28ae2f..4d849216 100644 --- a/packages/react/src/components/checkout/order/use-draft-order.ts +++ b/packages/react/src/components/checkout/order/use-draft-order.ts @@ -66,8 +66,7 @@ export function useDraftOrderTotals() { export function useDraftOrderIncludedTaxTotal() { return useDraftOrder( - data => - getIncludedTaxTotal(data.checkoutSession?.draftOrder?.taxes ?? null), + data => getIncludedTaxTotal(data.checkoutSession?.draftOrder?.taxes), 'draft-order' ); } diff --git a/packages/react/src/components/checkout/order/use-update-taxes.ts b/packages/react/src/components/checkout/order/use-update-taxes.ts index 6c5e83ec..d80760d0 100644 --- a/packages/react/src/components/checkout/order/use-update-taxes.ts +++ b/packages/react/src/components/checkout/order/use-update-taxes.ts @@ -1,12 +1,10 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import type { ResultOf } from 'gql.tada'; import { useCheckoutContext } from '@/components/checkout/checkout'; import { checkoutMutationKeys, checkoutQueryKeys, } from '@/components/checkout/utils/query-keys'; import { useGoDaddyContext } from '@/godaddy-provider'; -import type { DraftOrderQuery } from '@/lib/godaddy/checkout-queries.ts'; import { updateDraftOrderTaxes } from '@/lib/godaddy/godaddy'; export function useUpdateTaxes() { @@ -35,40 +33,10 @@ export function useUpdateTaxes() { : await updateDraftOrderTaxes(session, destination, apiHost); return data; }, - onSuccess: data => { - if (!session) return; - - // Extract shippingTotal from mutation response - const taxesTotal = data?.calculateCheckoutSessionTaxes?.totalTaxAmount; - - // Update the cached draft-order query (includes totals) - if (taxesTotal) { - queryClient.setQueryData( - checkoutQueryKeys.draftOrder(session.id), - (old: ResultOf | undefined) => { - if (!old) return old; - return { - ...old, - checkoutSession: { - ...old.checkoutSession, - draftOrder: { - ...old?.checkoutSession?.draftOrder, - totals: { - ...old?.checkoutSession?.draftOrder?.totals, - taxesTotal: { - ...taxesTotal, - }, - }, - }, - }, - }; - } - ); - } - }, onSettled: () => { if (!session) return; - queryClient.invalidateQueries({ + // Keep the mutation pending until totals and tax constituents refresh together. + return queryClient.invalidateQueries({ queryKey: checkoutQueryKeys.draftOrder(session.id), }); }, diff --git a/packages/react/src/components/checkout/payment/payment-form.tsx b/packages/react/src/components/checkout/payment/payment-form.tsx index 047de38a..0d772963 100644 --- a/packages/react/src/components/checkout/payment/payment-form.tsx +++ b/packages/react/src/components/checkout/payment/payment-form.tsx @@ -596,11 +596,12 @@ export function PaymentForm( currencyCode={props.currencyCode} tip={props.tip} taxes={props.taxes} - vatIncluded={props.vatIncluded} + includedTaxTotal={props.includedTaxTotal} fees={props.fees} isTaxLoading={props.isTaxLoading} isFeeLoading={props.isFeeLoading} isShippingLoading={props.isShippingLoading} + isDiscountLoading={props.isDiscountLoading} subtotal={props.subtotal} discount={props.discount} shipping={props.shipping} diff --git a/packages/react/src/components/checkout/target/types.ts b/packages/react/src/components/checkout/target/types.ts index 2476e96e..15d8ab58 100644 --- a/packages/react/src/components/checkout/target/types.ts +++ b/packages/react/src/components/checkout/target/types.ts @@ -30,6 +30,7 @@ export const checkoutTargetIds = [ 'checkout.summary.totals.shipping.before', 'checkout.summary.totals.tip.before', 'checkout.summary.totals.taxes.before', + 'checkout.summary.totals.included-taxes.before', 'checkout.summary.totals.fees.before', 'checkout.summary.totals.total-due.before', 'checkout.summary.totals.total-due.after', diff --git a/packages/react/src/components/checkout/totals/totals.tsx b/packages/react/src/components/checkout/totals/totals.tsx index fc3bd790..d7525783 100644 --- a/packages/react/src/components/checkout/totals/totals.tsx +++ b/packages/react/src/components/checkout/totals/totals.tsx @@ -17,7 +17,7 @@ export interface DraftOrderTotalsProps { total?: number; tip?: number; taxes?: number; - vatIncluded?: number; + includedTaxTotal?: number; isTaxLoading?: boolean; fees?: number; isFeeLoading?: boolean; @@ -73,7 +73,7 @@ export function DraftOrderTotals({ total = 0, tip = 0, taxes = 0, - vatIncluded = 0, + includedTaxTotal = 0, fees = 0, enableDiscounts = false, enableTaxes = false, @@ -157,14 +157,15 @@ export function DraftOrderTotals({ inputInMinorUnits={inputInMinorUnits} /> ))} - {vatIncluded > 0 ? ( - isTaxLoading ? ( + + {enableTaxes && includedTaxTotal > 0 ? ( + isTaxLoading || isShippingLoading || isDiscountLoading ? ( ) : ( ) diff --git a/packages/react/src/components/checkout/totals/utils/get-included-tax-total.test.ts b/packages/react/src/components/checkout/totals/utils/get-included-tax-total.test.ts index a7733740..e639cfc5 100644 --- a/packages/react/src/components/checkout/totals/utils/get-included-tax-total.test.ts +++ b/packages/react/src/components/checkout/totals/utils/get-included-tax-total.test.ts @@ -5,10 +5,10 @@ describe('getIncludedTaxTotal', () => { it('totals only included tax constituents', () => { expect( getIncludedTaxTotal([ - { included: true, additional: false, amount: { value: 125 } }, - { included: false, additional: true, amount: { value: 300 } }, - { included: true, additional: null, amount: { value: 75 } }, - { included: true, additional: true, amount: { value: 500 } }, + { included: true, amount: { value: 125 } }, + { included: false, amount: { value: 300 } }, + { included: true, amount: { value: 75 } }, + { included: null, amount: { value: 500 } }, ]) ).toBe(200); }); diff --git a/packages/react/src/components/checkout/totals/utils/get-included-tax-total.ts b/packages/react/src/components/checkout/totals/utils/get-included-tax-total.ts index 4f0d7acb..004734ea 100644 --- a/packages/react/src/components/checkout/totals/utils/get-included-tax-total.ts +++ b/packages/react/src/components/checkout/totals/utils/get-included-tax-total.ts @@ -1,6 +1,5 @@ interface TaxAmount { included?: boolean | null; - additional?: boolean | null; amount?: { value?: number | null; } | null; @@ -12,8 +11,7 @@ export function getIncludedTaxTotal( return ( taxes?.reduce( (total, tax) => - total + - (tax.included && tax.additional !== true ? tax.amount?.value || 0 : 0), + total + (tax.included === true ? tax.amount?.value || 0 : 0), 0 ) ?? 0 ); diff --git a/packages/react/src/components/storefront/cart.tsx b/packages/react/src/components/storefront/cart.tsx index 41b3d997..f2da7943 100644 --- a/packages/react/src/components/storefront/cart.tsx +++ b/packages/react/src/components/storefront/cart.tsx @@ -111,7 +111,7 @@ export function Cart({ const subtotal = order?.totals?.subTotal?.value || 0; const shipping = order?.totals?.shippingTotal?.value || 0; const taxes = order?.totals?.taxTotal?.value || 0; - const vatIncluded = getIncludedTaxTotal(order?.taxes); + const includedTaxTotal = getIncludedTaxTotal(order?.taxes); const discount = order?.totals?.discountTotal?.value || 0; const total = order?.totals?.total?.value || 0; @@ -124,7 +124,7 @@ export function Cart({ total, tip: 0, taxes, - vatIncluded, + includedTaxTotal, enableDiscounts: false, enableTaxes: true, isTaxLoading: false, diff --git a/packages/react/src/lib/godaddy/checkout-env.ts b/packages/react/src/lib/godaddy/checkout-env.ts index 65038eb1..c9ab79e9 100644 --- a/packages/react/src/lib/godaddy/checkout-env.ts +++ b/packages/react/src/lib/godaddy/checkout-env.ts @@ -2139,15 +2139,6 @@ const introspection = { "args": [], "isDeprecated": false }, - { - "name": "tips", - "type": { - "kind": "OBJECT", - "name": "CheckoutSessionTips" - }, - "args": [], - "isDeprecated": false - }, { "name": "token", "type": { @@ -2979,80 +2970,6 @@ const introspection = { ], "isOneOf": false }, - { - "kind": "OBJECT", - "name": "CheckoutSessionFee", - "fields": [ - { - "name": "amount", - "type": { - "kind": "SCALAR", - "name": "Int" - }, - "args": [], - "isDeprecated": false - }, - { - "name": "feeProgramId", - "type": { - "kind": "SCALAR", - "name": "String" - }, - "args": [], - "isDeprecated": false - }, - { - "name": "feeProgramType", - "type": { - "kind": "SCALAR", - "name": "String" - }, - "args": [], - "isDeprecated": false - }, - { - "name": "feeType", - "type": { - "kind": "SCALAR", - "name": "String" - }, - "args": [], - "isDeprecated": false - }, - { - "name": "signature", - "type": { - "kind": "SCALAR", - "name": "String" - }, - "args": [], - "isDeprecated": false - } - ], - "interfaces": [] - }, - { - "kind": "OBJECT", - "name": "CheckoutSessionFeesResult", - "fields": [ - { - "name": "fees", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "CheckoutSessionFee" - } - } - }, - "args": [], - "isDeprecated": false - } - ], - "interfaces": [] - }, { "kind": "OBJECT", "name": "CheckoutSessionFreeShippingRule", @@ -4082,242 +3999,6 @@ const introspection = { ], "isOneOf": false }, - { - "kind": "OBJECT", - "name": "CheckoutSessionTips", - "fields": [ - { - "name": "default", - "type": { - "kind": "OBJECT", - "name": "CheckoutSessionTipsDefault" - }, - "args": [], - "isDeprecated": false - }, - { - "name": "thresholds", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "CheckoutSessionTipsThreshold" - } - } - }, - "args": [], - "isDeprecated": false - } - ], - "interfaces": [] - }, - { - "kind": "OBJECT", - "name": "CheckoutSessionTipsDefault", - "fields": [ - { - "name": "amounts", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Int" - } - } - }, - "args": [], - "isDeprecated": false - }, - { - "name": "percentages", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Int" - } - } - }, - "args": [], - "isDeprecated": false - } - ], - "interfaces": [] - }, - { - "kind": "INPUT_OBJECT", - "name": "CheckoutSessionTipsDefaultInput", - "inputFields": [ - { - "name": "amounts", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Int" - } - } - } - }, - { - "name": "percentages", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Int" - } - } - } - } - ], - "isOneOf": false - }, - { - "kind": "INPUT_OBJECT", - "name": "CheckoutSessionTipsInput", - "inputFields": [ - { - "name": "default", - "type": { - "kind": "INPUT_OBJECT", - "name": "CheckoutSessionTipsDefaultInput" - } - }, - { - "name": "thresholds", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "INPUT_OBJECT", - "name": "CheckoutSessionTipsThresholdInput" - } - } - } - } - ], - "isOneOf": false - }, - { - "kind": "OBJECT", - "name": "CheckoutSessionTipsThreshold", - "fields": [ - { - "name": "amounts", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Int" - } - } - }, - "args": [], - "isDeprecated": false - }, - { - "name": "maxSubtotal", - "type": { - "kind": "SCALAR", - "name": "Int" - }, - "args": [], - "isDeprecated": false - }, - { - "name": "minSubtotal", - "type": { - "kind": "SCALAR", - "name": "Int" - }, - "args": [], - "isDeprecated": false - }, - { - "name": "percentages", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Int" - } - } - }, - "args": [], - "isDeprecated": false - } - ], - "interfaces": [] - }, - { - "kind": "INPUT_OBJECT", - "name": "CheckoutSessionTipsThresholdInput", - "inputFields": [ - { - "name": "amounts", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Int" - } - } - } - }, - { - "name": "maxSubtotal", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Int" - } - } - }, - { - "name": "minSubtotal", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Int" - } - } - }, - { - "name": "percentages", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Int" - } - } - } - } - ], - "isOneOf": false - }, { "kind": "OBJECT", "name": "CheckoutSessionTotalTaxAmount", @@ -6653,46 +6334,6 @@ const introspection = { ], "isOneOf": false }, - { - "kind": "ENUM", - "name": "FeeProgramType", - "enumValues": [ - { - "name": "CASH_DISCOUNT", - "isDeprecated": false - }, - { - "name": "CONVENIENCE_FEE", - "isDeprecated": false - }, - { - "name": "SERVICE_FEE", - "isDeprecated": false - }, - { - "name": "SURCHARGE", - "isDeprecated": false - } - ] - }, - { - "kind": "ENUM", - "name": "FeeType", - "enumValues": [ - { - "name": "FIXED", - "isDeprecated": false - }, - { - "name": "HYBRID", - "isDeprecated": false - }, - { - "name": "PERCENTAGE", - "isDeprecated": false - } - ] - }, { "kind": "SCALAR", "name": "Float" @@ -6767,24 +6408,6 @@ const introspection = { } ] }, - { - "kind": "ENUM", - "name": "FundingSourceType", - "enumValues": [ - { - "name": "CREDIT", - "isDeprecated": false - }, - { - "name": "DEBIT", - "isDeprecated": false - }, - { - "name": "PREPAID", - "isDeprecated": false - } - ] - }, { "kind": "OBJECT", "name": "GeoCoordinates", @@ -7774,26 +7397,6 @@ const introspection = { ], "isDeprecated": false }, - { - "name": "calculateCheckoutSessionFees", - "type": { - "kind": "OBJECT", - "name": "CheckoutSessionFeesResult" - }, - "args": [ - { - "name": "fundingSourceType", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "ENUM", - "name": "FundingSourceType" - } - } - } - ], - "isDeprecated": false - }, { "name": "calculateCheckoutSessionTaxes", "type": { @@ -8077,19 +7680,6 @@ const introspection = { "kind": "INPUT_OBJECT", "name": "MutationAuthorizeCheckoutSessionInput", "inputFields": [ - { - "name": "fees", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "INPUT_OBJECT", - "name": "TransactionFeeInput" - } - } - } - }, { "name": "paymentProvider", "type": { @@ -8116,13 +7706,6 @@ const introspection = { "name": "String" } } - }, - { - "name": "tipAmount", - "type": { - "kind": "SCALAR", - "name": "Int" - } } ], "isOneOf": false @@ -8152,19 +7735,6 @@ const introspection = { "name": "CalculatedTaxesInput" } }, - { - "name": "fees", - "type": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "INPUT_OBJECT", - "name": "TransactionFeeInput" - } - } - } - }, { "name": "fulfillmentEndAt", "type": { @@ -8249,13 +7819,6 @@ const introspection = { "kind": "INPUT_OBJECT", "name": "MoneyInput" } - }, - { - "name": "tipAmount", - "type": { - "kind": "SCALAR", - "name": "Int" - } } ], "isOneOf": false @@ -8528,13 +8091,6 @@ const introspection = { "name": "CheckoutSessionTaxesOptionsInput" } }, - { - "name": "tips", - "type": { - "kind": "INPUT_OBJECT", - "name": "CheckoutSessionTipsInput" - } - }, { "name": "url", "type": { @@ -8967,13 +8523,6 @@ const introspection = { "name": "String" } }, - { - "name": "tips", - "type": { - "kind": "INPUT_OBJECT", - "name": "CheckoutSessionTipsInput" - } - }, { "name": "url", "type": { @@ -11295,57 +10844,6 @@ const introspection = { ], "interfaces": [] }, - { - "kind": "INPUT_OBJECT", - "name": "TransactionFeeInput", - "inputFields": [ - { - "name": "amount", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Int" - } - } - }, - { - "name": "feeProgramType", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "ENUM", - "name": "FeeProgramType" - } - } - }, - { - "name": "feeType", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "ENUM", - "name": "FeeType" - } - } - }, - { - "name": "required", - "type": { - "kind": "SCALAR", - "name": "Boolean" - } - }, - { - "name": "signature", - "type": { - "kind": "SCALAR", - "name": "String" - } - } - ], - "isOneOf": false - }, { "kind": "OBJECT", "name": "TransactionFundingSource", diff --git a/packages/react/src/lib/godaddy/orders-storefront-queries.ts b/packages/react/src/lib/godaddy/orders-storefront-queries.ts index 1d48729a..4a4dcf4c 100644 --- a/packages/react/src/lib/godaddy/orders-storefront-queries.ts +++ b/packages/react/src/lib/godaddy/orders-storefront-queries.ts @@ -136,7 +136,6 @@ export const GetCartOrderQuery = graphql(` } ratePercentage included - additional exempted } shipping { From fa40597fba5607a71a9194a3a5284882af4a55f8 Mon Sep 17 00:00:00 2001 From: Phil Bennett Date: Tue, 15 Sep 2026 09:58:40 -0500 Subject: [PATCH 5/5] feedback updates --- .../__tests__/checkout-shipping.test.tsx | 96 ++++++++++++++++++- .../checkout-totals-summary.test.tsx | 46 +++------ .../checkout/form/checkout-form.tsx | 15 +-- .../checkout/order/use-update-taxes.ts | 4 +- .../checkout/shipping/shipping-method.tsx | 15 +-- .../src/components/checkout/totals/totals.tsx | 11 ++- .../react/src/lib/godaddy/checkout-queries.ts | 5 - 7 files changed, 120 insertions(+), 72 deletions(-) diff --git a/packages/react/src/components/checkout/__tests__/checkout-shipping.test.tsx b/packages/react/src/components/checkout/__tests__/checkout-shipping.test.tsx index 87061b35..aca516b9 100644 --- a/packages/react/src/components/checkout/__tests__/checkout-shipping.test.tsx +++ b/packages/react/src/components/checkout/__tests__/checkout-shipping.test.tsx @@ -1,17 +1,19 @@ -import { fireEvent, screen, waitFor } from '@testing-library/react'; +import { act, fireEvent, screen, waitFor } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; import { checkoutQueryKeys } from '@/components/checkout/utils/query-keys'; import * as godaddyApi from '@/lib/godaddy/godaddy'; -import { CheckoutType, PaymentProvider } from '@/types'; +import { CheckoutType, type DraftOrderSession, PaymentProvider } from '@/types'; import { advanceCheckoutDebounce, buildDraftOrder, + buildLineItem, buildShippingAddress, clearOperations, flushPromises, getOperations, renderCheckout, setApiError, + setCurrentDraftOrder, typeIntoNamedField, waitForCheckoutReady, waitForOperation, @@ -467,6 +469,96 @@ describe('Checkout shipping behavior', () => { }); }); + it.each([ + { + path: 'tax recalculation', + enableTaxCollection: true, + enablePromotionCodes: false, + }, + { + path: 'discount recalculation', + enableTaxCollection: true, + enablePromotionCodes: true, + }, + { + path: 'direct refresh', + enableTaxCollection: false, + enablePromotionCodes: false, + }, + ])( + 'refreshes shipping fulfillment once through $path', + async ({ enableTaxCollection, enablePromotionCodes }) => { + const { queryClient, session, draftOrder } = renderCheckout({ + sessionOverrides: { enableTaxCollection, enablePromotionCodes }, + draftOrderOverrides: { + shippingLines: [ + { + requestedService: 'free-shipping', + requestedProvider: 'unknown', + name: 'Free', + amount: { value: 0, currencyCode: 'USD' }, + discounts: [], + }, + ], + discounts: enablePromotionCodes + ? [ + { + code: 'SAVE', + name: 'SAVE', + amount: { value: 100, currencyCode: 'USD' }, + }, + ] + : [], + }, + }); + await waitForCheckoutReady(); + await waitFor(() => { + expect(queryClient.isMutating()).toBe(0); + expect(queryClient.isFetching()).toBe(0); + }); + clearOperations(); + + const orderWithNewItem = buildDraftOrder({ + ...draftOrder, + lineItems: [ + buildLineItem({ id: 'existing-item', fulfillmentMode: 'SHIP' }), + buildLineItem({ id: 'new-item', fulfillmentMode: 'NONE' }), + ], + }); + setCurrentDraftOrder(orderWithNewItem); + await act(async () => { + queryClient.setQueryData(checkoutQueryKeys.draftOrder(session.id), { + checkoutSession: { ...session, draftOrder: orderWithNewItem }, + }); + }); + await waitForOperation('ApplyCheckoutSessionShippingMethod'); + await waitFor(() => { + expect(queryClient.isMutating()).toBe(0); + expect(queryClient.isFetching()).toBe(0); + const refreshed = queryClient.getQueryData( + checkoutQueryKeys.draftOrder(session.id) + ); + expect(refreshed?.checkoutSession?.draftOrder?.lineItems).toEqual([ + expect.objectContaining({ + id: 'existing-item', + fulfillmentMode: 'SHIP', + }), + expect.objectContaining({ id: 'new-item', fulfillmentMode: 'SHIP' }), + ]); + }); + expect(getOperations('ApplyCheckoutSessionShippingMethod')).toHaveLength( + 1 + ); + expect(getOperations('DraftOrder')).toHaveLength(1); + expect(getOperations('CalculateCheckoutSessionTaxes')).toHaveLength( + enableTaxCollection ? 1 : 0 + ); + expect(getOperations('ApplyCheckoutSessionDiscount')).toHaveLength( + enablePromotionCodes ? 1 : 0 + ); + } + ); + it('keeps the current user-selected shipping method despite stale backend shipping line during refetch', async () => { const { user, queryClient, session } = renderCheckout({ draftOrderOverrides: { diff --git a/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx b/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx index 114d9a30..ea876b62 100644 --- a/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx +++ b/packages/react/src/components/checkout/__tests__/checkout-totals-summary.test.tsx @@ -80,28 +80,16 @@ describe('Checkout totals and order summary UI', () => { }), taxes: [ { - id: 'included-tax-1', - name: 'VAT', included: true, - exempted: false, - ratePercentage: '2.5', - amount: { value: 100, currencyCode: 'USD' }, + amount: { value: 100 }, }, { - id: 'additive-tax', - name: 'Sales tax', included: false, - exempted: false, - ratePercentage: '5', - amount: { value: 456, currencyCode: 'USD' }, + amount: { value: 456 }, }, { - id: 'included-tax-2', - name: 'VAT surcharge', included: true, - exempted: false, - ratePercentage: '0.5', - amount: { value: 23, currencyCode: 'USD' }, + amount: { value: 23 }, }, ], }, @@ -119,9 +107,7 @@ describe('Checkout totals and order summary UI', () => { sessionOverrides: { enableTaxCollection: false }, draftOrderOverrides: { totals: totals({ taxTotal: { value: 0, currencyCode: 'USD' } }), - taxes: [ - { included: true, amount: { value: 123, currencyCode: 'USD' } }, - ], + taxes: [{ included: true, amount: { value: 123 } }], }, }); await waitForCheckoutReady(); @@ -139,7 +125,7 @@ describe('Checkout totals and order summary UI', () => { async ({ included, amount }) => { renderCheckout({ draftOrderOverrides: { - taxes: [{ included, amount: { value: amount, currencyCode: 'USD' } }], + taxes: [{ included, amount: { value: amount } }], }, }); await waitForCheckoutReady(); @@ -148,18 +134,12 @@ describe('Checkout totals and order summary UI', () => { } ); - it.each([ - 'applyDiscount', - 'applyShippingMethod', - 'removeShippingMethod', - ] as const)( - 'hides the previous included-tax amount during %s', + it.each(['applyDiscount', 'applyShippingMethod'] as const)( + 'shows both tax rows as loading during %s', async mutation => { const { queryClient, session } = renderCheckout({ draftOrderOverrides: { - taxes: [ - { included: true, amount: { value: 123, currencyCode: 'USD' } }, - ], + taxes: [{ included: true, amount: { value: 123 } }], }, }); await waitForCheckoutReady(); @@ -178,7 +158,9 @@ describe('Checkout totals and order summary UI', () => { .execute(undefined); await waitFor(() => { - for (const label of screen.getAllByText(/vat included/i)) { + for (const label of screen.getAllByText( + /vat included|estimated taxes/i + )) { const row = label.closest('.flex.justify-between'); expect(row?.querySelector('.animate-pulse')).toBeInTheDocument(); expect(row).not.toHaveTextContent('$1.23'); @@ -213,9 +195,7 @@ describe('Checkout totals and order summary UI', () => { const { queryClient, session, draftOrder, user } = renderCheckout({ draftOrderOverrides: { - taxes: [ - { included: true, amount: { value: 123, currencyCode: 'USD' } }, - ], + taxes: [{ included: true, amount: { value: 123 } }], }, checkoutProps: { targets: { 'checkout.form.before': () => }, @@ -248,7 +228,7 @@ describe('Checkout totals and order summary UI', () => { ? [ { included: true, - amount: { value: amount, currencyCode: 'USD' }, + amount: { value: amount }, }, ] : [], diff --git a/packages/react/src/components/checkout/form/checkout-form.tsx b/packages/react/src/components/checkout/form/checkout-form.tsx index 937a72ec..1803bf21 100644 --- a/packages/react/src/components/checkout/form/checkout-form.tsx +++ b/packages/react/src/components/checkout/form/checkout-form.tsx @@ -157,11 +157,6 @@ export function CheckoutForm({ const tipAmount = form.watch('tipAmount'); const isPickup = deliveryMethod === DeliveryMethods.PICKUP; const isShipping = deliveryMethod === DeliveryMethods.SHIP; - const isRemovingShipping = - useIsMutating({ - mutationKey: checkoutMutationKeys.removeShippingMethod(session?.id), - }) > 0; - const isUpdatingShipping = useIsMutating({ mutationKey: checkoutMutationKeys.applyShippingMethod(session?.id), @@ -564,9 +559,7 @@ export function CheckoutForm({ fees={feeTotal} isTaxLoading={isUpdatingTaxes} isFeeLoading={isUpdatingFees} - isShippingLoading={ - isUpdatingShipping || isRemovingShipping - } + isShippingLoading={isUpdatingShipping} isDiscountLoading={isDiscountApplying} subtotal={subtotal} discount={orderDiscount} @@ -638,9 +631,7 @@ export function CheckoutForm({ fees={feeTotal} isTaxLoading={isUpdatingTaxes} isFeeLoading={isUpdatingFees} - isShippingLoading={ - isUpdatingShipping || isRemovingShipping - } + isShippingLoading={isUpdatingShipping} subtotal={subtotal} discount={orderDiscount} isDiscountLoading={isDiscountApplying} @@ -675,7 +666,7 @@ export function CheckoutForm({ fees={feeTotal} isTaxLoading={isUpdatingTaxes} isFeeLoading={isUpdatingFees} - isShippingLoading={isUpdatingShipping || isRemovingShipping} + isShippingLoading={isUpdatingShipping} subtotal={subtotal} discount={orderDiscount} isDiscountLoading={isDiscountApplying} diff --git a/packages/react/src/components/checkout/order/use-update-taxes.ts b/packages/react/src/components/checkout/order/use-update-taxes.ts index d80760d0..b8086c2c 100644 --- a/packages/react/src/components/checkout/order/use-update-taxes.ts +++ b/packages/react/src/components/checkout/order/use-update-taxes.ts @@ -35,7 +35,9 @@ export function useUpdateTaxes() { }, onSettled: () => { if (!session) return; - // Keep the mutation pending until totals and tax constituents refresh together. + // Keep totals and tax constituents in sync before mutation callers continue. + // This also waits for refetch retries and delays rejection of a failed tax + // mutation until the refetch settles. return queryClient.invalidateQueries({ queryKey: checkoutQueryKeys.draftOrder(session.id), }); diff --git a/packages/react/src/components/checkout/shipping/shipping-method.tsx b/packages/react/src/components/checkout/shipping/shipping-method.tsx index d14bcab2..4a8a8172 100644 --- a/packages/react/src/components/checkout/shipping/shipping-method.tsx +++ b/packages/react/src/components/checkout/shipping/shipping-method.tsx @@ -1,4 +1,3 @@ -import { useQueryClient } from '@tanstack/react-query'; import { useEffect, useRef } from 'react'; import { useFormContext } from 'react-hook-form'; import { useCheckoutContext } from '@/components/checkout/checkout'; @@ -20,7 +19,6 @@ import { import { useApplyShippingMethod } from '@/components/checkout/shipping/utils/use-apply-shipping-method'; import { useDraftOrderShippingMethods } from '@/components/checkout/shipping/utils/use-draft-order-shipping-methods'; import { useFormatCurrency } from '@/components/checkout/utils/format-currency'; -import { checkoutQueryKeys } from '@/components/checkout/utils/query-keys'; import { Label } from '@/components/ui/label'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { useGoDaddyContext } from '@/godaddy-provider'; @@ -54,7 +52,6 @@ export function ShippingMethodForm() { const { t } = useGoDaddyContext(); const { session, isConfirmingCheckout } = useCheckoutContext(); const updateTaxes = useUpdateTaxes(); - const queryClient = useQueryClient(); const isPaymentDisabled = useIsPaymentDisabled(); const { data: shippingMethodsData, isLoading: isShippingMethodsLoading } = @@ -201,15 +198,7 @@ export function ShippingMethodForm() { }; } - applyShippingMethod.mutate(buildShippingPayload(methodToApply), { - onSuccess: () => { - if (!isFulfillmentSync || !session?.id) return; - - queryClient.invalidateQueries({ - queryKey: checkoutQueryKeys.draftOrder(session.id), - }); - }, - }); + applyShippingMethod.mutate(buildShippingPayload(methodToApply)); } else if (session?.enableTaxCollection) { updateTaxes.mutate(undefined); } @@ -236,8 +225,6 @@ export function ShippingMethodForm() { applyShippingMethod, updateTaxes.mutate, session?.enableTaxCollection, - queryClient, - session?.id, isPickup, isDraftOrderLoading, hasLineItemsMissingShippingFulfillment, diff --git a/packages/react/src/components/checkout/totals/totals.tsx b/packages/react/src/components/checkout/totals/totals.tsx index d7525783..5a8e939b 100644 --- a/packages/react/src/components/checkout/totals/totals.tsx +++ b/packages/react/src/components/checkout/totals/totals.tsx @@ -87,6 +87,7 @@ export function DraftOrderTotals({ }: DraftOrderTotalsProps) { const { t } = useGoDaddyContext(); const formatCurrency = useFormatCurrency(); + const isTaxesLoading = isTaxLoading || isShippingLoading || isDiscountLoading; const handleDiscountsChange = (discounts: string[]) => { // Discount changes are handled by the DiscountStandalone component }; @@ -147,7 +148,7 @@ export function DraftOrderTotals({ ) : null} {enableTaxes && - (isTaxLoading ? ( + (isTaxesLoading ? ( ) : ( ))} - {enableTaxes && includedTaxTotal > 0 ? ( - isTaxLoading || isShippingLoading || isDiscountLoading ? ( + {enableTaxes && + includedTaxTotal > 0 && + (isTaxesLoading ? ( ) : ( - ) - ) : null} + ))} {enableFees && (isFeeLoading ? ( diff --git a/packages/react/src/lib/godaddy/checkout-queries.ts b/packages/react/src/lib/godaddy/checkout-queries.ts index c84001d5..c0cec19e 100644 --- a/packages/react/src/lib/godaddy/checkout-queries.ts +++ b/packages/react/src/lib/godaddy/checkout-queries.ts @@ -485,14 +485,9 @@ export const DraftOrderQuery = graphql(` } taxes { amount { - currencyCode value } - exempted - id included - name - ratePercentage } totals { discountTotal {