diff --git a/.changeset/calm-paws-shave.md b/.changeset/calm-paws-shave.md new file mode 100644 index 00000000..7ba76ce0 --- /dev/null +++ b/.changeset/calm-paws-shave.md @@ -0,0 +1,5 @@ +--- +"@godaddy/react": patch +--- + +fix payload amount precision for godaddy payment express checkout on shipping changes diff --git a/packages/react/src/components/checkout/payment/checkout-buttons/express/godaddy.tsx b/packages/react/src/components/checkout/payment/checkout-buttons/express/godaddy.tsx index eec97822..9f2c15e1 100644 --- a/packages/react/src/components/checkout/payment/checkout-buttons/express/godaddy.tsx +++ b/packages/react/src/components/checkout/payment/checkout-buttons/express/godaddy.tsx @@ -34,7 +34,10 @@ import { filterAndSortShippingMethods } from '@/components/checkout/shipping/uti import { useGetShippingMethodByAddress } from '@/components/checkout/shipping/utils/use-get-shipping-methods'; import { useGetTaxes } from '@/components/checkout/taxes/utils/use-get-taxes'; import { mapOrderToFormValues } from '@/components/checkout/utils/checkout-transformers'; -import { useFormatCurrency } from '@/components/checkout/utils/format-currency'; +import { + useConvertMajorToMinorUnits, + useFormatCurrency, +} from '@/components/checkout/utils/format-currency'; import { Skeleton } from '@/components/ui/skeleton'; import { useGoDaddyContext } from '@/godaddy-provider'; import { GraphQLErrorWithCodes } from '@/lib/graphql-with-errors'; @@ -47,6 +50,7 @@ import { export function ExpressCheckoutButton() { const formatCurrency = useFormatCurrency(); + const convertMajorToMinorUnits = useConvertMajorToMinorUnits(); const { session, setCheckoutErrors } = useCheckoutContext(); const { isPoyntLoaded } = useLoadPoyntCollect(); const { godaddyPaymentsConfig } = useCheckoutContext(); @@ -104,7 +108,7 @@ export function ExpressCheckoutButton() { subtotalPrice: { currencyCode: currency, // Wallet APIs provide amounts in major units (e.g., "10.50"), convert to minor units for our API - value: Number(amount) * 100 || 0, + value: convertMajorToMinorUnits(amount || '0', currency), }, }, ], @@ -927,7 +931,10 @@ export function ExpressCheckoutButton() { shipping: { currencyCode: currencyCode, // Convert wallet API amount from major to minor units for internal storage - value: Number(shippingAmount) * 100 || 0, + value: convertMajorToMinorUnits( + shippingAmount || '0', + currencyCode + ), }, })); @@ -938,7 +945,10 @@ export function ExpressCheckoutButton() { shippingAddress, { // Convert wallet API amount from major to minor units for API request - amountInMinorUnits: Number(shippingAmount) * 100 || 0, + amountInMinorUnits: convertMajorToMinorUnits( + shippingAmount || '0', + currencyCode + ), name: e.shippingMethod?.label || t.totals.shipping, } ); @@ -1036,7 +1046,12 @@ export function ExpressCheckoutButton() { ...updatedOrder, total: { label: t.payment.orderTotal, - amount: totalAmount.toString(), + amount: formatCurrency({ + amount: totalAmount, + currencyCode: currencyCode, + inputInMinorUnits: false, + returnRaw: true, + }), }, lineItems: poyntLineItems, }; @@ -1220,7 +1235,12 @@ export function ExpressCheckoutButton() { ...poyntExpressRequest, total: { label: t.payment.orderTotal, - amount: totalAmount.toString(), + amount: formatCurrency({ + amount: totalAmount, + currencyCode: currencyCode, + inputInMinorUnits: false, + returnRaw: true, + }), }, shippingMethods: methods as ShippingMethod[], lineItems: poyntLineItems, diff --git a/packages/react/src/components/checkout/utils/format-currency.ts b/packages/react/src/components/checkout/utils/format-currency.ts index 40935c29..e72ab591 100644 --- a/packages/react/src/components/checkout/utils/format-currency.ts +++ b/packages/react/src/components/checkout/utils/format-currency.ts @@ -105,6 +105,28 @@ export function formatCurrency({ } } +/** + * Converts a currency amount from major units (dollars) to minor units (cents). + * + * This is the reverse operation of formatCurrency with inputInMinorUnits=true. + * It properly handles different currency precisions: + * - 2 decimals (USD, EUR, etc.): multiply by 100 + * - 0 decimals (JPY, KRW, VND, etc.): multiply by 1 + * - 3 decimals (KWD, BHD, JOD, OMR): multiply by 1000 + * + * @param amount - The amount in major units (e.g., "10.50" or 10.50) + * @param currencyCode - ISO 4217 currency code (e.g., 'USD', 'JPY', 'KWD') + * @returns The amount in minor units (e.g., 1050 for USD, 10 for JPY, 10500 for KWD) + */ +export function convertMajorToMinorUnits( + amount: number | string, + currencyCode: string +): number { + const config = currencyConfigs[currencyCode] || { precision: 2 }; + const numAmount = typeof amount === 'string' ? Number(amount) : amount; + return Math.round(numAmount * Math.pow(10, config.precision)); +} + /** * Hook that returns a formatCurrency function using the locale from GoDaddyProvider context. * The returned function has the same signature as formatCurrency, but uses the context locale as default. @@ -119,3 +141,15 @@ export function useFormatCurrency() { }); }; } + +/** + * Hook that returns a convertMajorToMinorUnits function. + * This follows the same pattern as useFormatCurrency for consistency. + * Note: Locale is not used in currency conversion (only in display formatting), + * but we access context to maintain the same hook pattern. + */ +export function useConvertMajorToMinorUnits() { + return (amount: number | string, currencyCode: string) => { + return convertMajorToMinorUnits(amount, currencyCode); + }; +}