;
if (shippingAddress && shippingMethod) {
const selectedMethodInfo = {
- amount: (godaddyTotals.shipping.value / 100).toString(),
+ amountInMinorUnits: godaddyTotals.shipping.value,
name: shippingMethod,
};
shippingLines = convertAddressToShippingLines(
@@ -580,38 +628,67 @@ export function ExpressCheckoutButton() {
if (godaddyTotals.shipping.value > 0) {
finalLineItems.push({
label: t.totals.shipping,
- amount: (godaddyTotals.shipping.value / 100).toString(),
+ amount: formatCurrency({
+ amount: godaddyTotals.shipping.value,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
});
}
if (godaddyTotals.taxes.value > 0) {
finalLineItems.push({
label: t.totals.estimatedTaxes,
- amount: (godaddyTotals.taxes.value / 100).toString(),
+ amount: formatCurrency({
+ amount: godaddyTotals.taxes.value,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
});
}
// Add the discount line item
finalLineItems.push({
label: t.totals.discount,
- amount: (-adjustment / 100).toString(),
+ amount: formatCurrency({
+ amount: -adjustment,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
});
- // Calculate the total amount
- const totalAmount = finalLineItems
- .reduce((acc, item) => acc + Number(item.amount), 0)
- .toFixed(2);
+ // Calculate the total in minor units then format with proper currency precision
+ const totalInMinorUnits =
+ (totals?.subTotal?.value || 0) +
+ godaddyTotals.shipping.value +
+ godaddyTotals.taxes.value -
+ adjustment;
+
+ const totalAmount = formatCurrency({
+ amount: totalInMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ });
updatedOrder = {
lineItems: finalLineItems,
total: {
label: t.payment.orderTotal,
- amount: totalAmount.toString(),
+ amount: totalAmount,
},
couponCode: {
code: couponCode,
label: t.totals.discount,
- amount: (-adjustment / 100).toString(),
+ amount: formatCurrency({
+ amount: -adjustment,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
},
};
} else {
@@ -748,7 +825,7 @@ export function ExpressCheckoutButton() {
subTotal: godaddyTotals.shipping,
taxTotal: {
value: 0,
- currencyCode: 'USD',
+ currencyCode: currencyCode,
},
},
},
@@ -833,6 +910,7 @@ export function ExpressCheckoutButton() {
// Handle shipping method change
if (e.shippingMethod && shippingAddress) {
+ // Wallet API provides shipping amount in major units (e.g., "10.50")
const shippingAmount = e.shippingMethod?.amount;
poyntLineItems.push({
@@ -843,7 +921,8 @@ export function ExpressCheckoutButton() {
setGoDaddyTotals(value => ({
...value,
shipping: {
- currencyCode: 'USD',
+ currencyCode: currencyCode,
+ // Convert wallet API amount from major to minor units for internal storage
value: Number(shippingAmount) * 100 || 0,
},
}));
@@ -854,7 +933,8 @@ export function ExpressCheckoutButton() {
const shippingLines = convertAddressToShippingLines(
shippingAddress,
{
- amount: shippingAmount || '0',
+ // Convert wallet API amount from major to minor units for API request
+ amountInMinorUnits: Number(shippingAmount) * 100 || 0,
name: e.shippingMethod?.label || t.totals.shipping,
}
);
@@ -897,12 +977,20 @@ export function ExpressCheckoutButton() {
if (taxesResult?.value) {
poyntLineItems.push({
label: t.totals.estimatedTaxes,
- amount: (taxesResult.value / 100).toString(),
+ amount: formatCurrency({
+ amount: taxesResult.value,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
isPending: false,
});
setGoDaddyTotals(value => ({
...value,
- taxes: { currencyCode: 'USD', value: taxesResult.value || 0 },
+ taxes: {
+ currencyCode: currencyCode,
+ value: taxesResult.value || 0,
+ },
}));
}
} catch (_error) {
@@ -925,7 +1013,12 @@ export function ExpressCheckoutButton() {
if (priceAdjustment && appliedCouponCode) {
poyntLineItems.push({
label: t.totals.discount,
- amount: (-priceAdjustment / 100).toString(),
+ amount: formatCurrency({
+ amount: -priceAdjustment,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
isPending: false,
});
}
@@ -949,7 +1042,12 @@ export function ExpressCheckoutButton() {
updatedOrder.couponCode = {
code: appliedCouponCode,
label: t.totals.discount,
- amount: (-priceAdjustment / 100).toString(),
+ amount: formatCurrency({
+ amount: -priceAdjustment,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
};
}
@@ -978,14 +1076,14 @@ export function ExpressCheckoutButton() {
setGoDaddyTotals(value => ({
...value,
shipping: {
- currencyCode: 'USD',
- value: Number(methods?.[0]?.amount) * 100 || 0,
+ currencyCode: currencyCode,
+ value: methods?.[0]?.amountInMinorUnits || 0,
},
}));
poyntLineItems.push({
label: t.totals.shipping,
- amount: (Number(methods?.[0]?.amount) || 0).toString(),
+ amount: methods?.[0]?.amount || '0',
isPending: false,
});
@@ -999,7 +1097,7 @@ export function ExpressCheckoutButton() {
const shippingLines = convertAddressToShippingLines(
e.shippingAddress,
{
- amount: methods[0]?.amount || '0',
+ amountInMinorUnits: methods[0]?.amountInMinorUnits || 0,
name: methods[0]?.label || t.totals.shipping,
}
);
@@ -1040,7 +1138,7 @@ export function ExpressCheckoutButton() {
setGoDaddyTotals(value => ({
...value,
shipping: {
- currencyCode: 'USD',
+ currencyCode: currencyCode,
value: 0,
},
}));
@@ -1060,13 +1158,18 @@ export function ExpressCheckoutButton() {
if (taxesResult?.value) {
poyntLineItems.push({
label: t.totals.estimatedTaxes,
- amount: (taxesResult.value / 100).toString(),
+ amount: formatCurrency({
+ amount: taxesResult.value,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
isPending: false,
});
setGoDaddyTotals(value => ({
...value,
taxes: {
- currencyCode: 'USD',
+ currencyCode: currencyCode,
value: taxesResult.value || 0,
},
}));
@@ -1094,7 +1197,12 @@ export function ExpressCheckoutButton() {
if (priceAdjustment && appliedCouponCode) {
poyntLineItems.push({
label: t.totals.discount,
- amount: (-priceAdjustment / 100).toString(),
+ amount: formatCurrency({
+ amount: -priceAdjustment,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
isPending: false,
});
}
@@ -1119,7 +1227,12 @@ export function ExpressCheckoutButton() {
updatedOrder.couponCode = {
code: appliedCouponCode,
label: appliedCouponCode || 'Discount',
- amount: (-priceAdjustment / 100).toString(),
+ amount: formatCurrency({
+ amount: -priceAdjustment,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
};
} else {
updatedOrder.couponCode = {
diff --git a/packages/react/src/components/checkout/payment/payment-form.tsx b/packages/react/src/components/checkout/payment/payment-form.tsx
index 18178852..7a29e5a8 100644
--- a/packages/react/src/components/checkout/payment/payment-form.tsx
+++ b/packages/react/src/components/checkout/payment/payment-form.tsx
@@ -34,6 +34,7 @@ import {
DraftOrderTotals,
type DraftOrderTotalsProps,
} from '@/components/checkout/totals/totals';
+import { formatCurrency } from '@/components/checkout/utils/format-currency';
import {
Accordion,
AccordionContent,
@@ -447,10 +448,11 @@ export function PaymentForm(
{t.totals.orderSummary}
- {new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: props.currencyCode,
- }).format(props.total || 0)}
+ {formatCurrency({
+ amount: props.total || 0,
+ currencyCode: props.currencyCode || 'USD',
+ isInCents: true,
+ })}
@@ -459,6 +461,7 @@ export function PaymentForm(
@@ -466,6 +469,7 @@ export function PaymentForm(
sum + (line?.amount?.value || 0),
0
- ) || 0) / 100;
- const discount = (totals?.discountTotal?.value || 0) / 100;
- const total = (totals?.total?.value || 0) / 100;
+ ) || 0;
+ const discountMinorUnits = totals?.discountTotal?.value || 0;
+ const totalMinorUnits = totals?.total?.value || 0;
const countryCode = useMemo(
() => session?.shipping?.originAddress?.countryCode || 'US',
@@ -254,52 +255,62 @@ export function useBuildPaymentRequest(): {
merchantCapabilities: ['supports3DS'],
total: {
label: 'Order Total',
- amount: new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: currencyCode,
- }).format(total),
+ amount: formatCurrency({
+ amount: totals?.total?.value || 0,
+ currencyCode,
+ isInCents: true,
+ }),
type: 'final',
},
lineItems: [
...(items || []).map(lineItem => ({
label: lineItem?.name || '',
- amount: new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: currencyCode,
- }).format((lineItem?.originalPrice || 0) * (lineItem?.quantity || 0)),
+ amount: formatCurrency({
+ amount: (lineItem?.originalPrice || 0) * (lineItem?.quantity || 0),
+ currencyCode,
+ isInCents: true,
+ }),
type: 'LINE_ITEM',
status: 'FINAL',
})),
{
label: 'Subtotal',
- amount: new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: currencyCode,
- }).format(subtotal),
+ amount: formatCurrency({
+ amount: totals?.subTotal?.value || 0,
+ currencyCode,
+ isInCents: true,
+ }),
type: 'final',
},
{
label: 'Tax',
- amount: new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: currencyCode,
- }).format(tax),
+ amount: formatCurrency({
+ amount: totals?.taxTotal?.value || 0,
+ currencyCode,
+ isInCents: true,
+ }),
type: 'final',
},
{
label: 'Shipping',
- amount: new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: currencyCode,
- }).format(shipping),
+ amount: formatCurrency({
+ amount:
+ order?.shippingLines?.reduce(
+ (sum, line) => sum + (line?.amount?.value || 0),
+ 0
+ ) || 0,
+ currencyCode,
+ isInCents: true,
+ }),
type: 'final',
},
{
label: 'Discount',
- amount: new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: currencyCode,
- }).format(-1 * discount),
+ amount: formatCurrency({
+ amount: -1 * (totals?.discountTotal?.value || 0),
+ currencyCode,
+ isInCents: true,
+ }),
type: 'final',
},
].filter(item => Number.parseFloat(item.amount) !== 0),
@@ -338,10 +349,11 @@ export function useBuildPaymentRequest(): {
},
transactionInfo: {
totalPriceStatus: 'FINAL',
- totalPrice: new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: currencyCode,
- }).format(total),
+ totalPrice: formatCurrency({
+ amount: totals?.total?.value || 0,
+ currencyCode,
+ isInCents: true,
+ }),
totalPriceLabel: 'Total',
currencyCode,
displayItems: [
@@ -353,25 +365,53 @@ export function useBuildPaymentRequest(): {
})),
{
label: 'Subtotal',
- price: subtotal,
+ price: Number.parseFloat(
+ formatCurrency({
+ amount: subtotalMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ })
+ ),
type: 'LINE_ITEM',
status: 'FINAL',
},
{
label: 'Tax',
- price: tax,
+ price: Number.parseFloat(
+ formatCurrency({
+ amount: taxMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ })
+ ),
type: 'LINE_ITEM',
status: 'FINAL',
},
{
label: 'Shipping',
- price: shipping,
+ price: Number.parseFloat(
+ formatCurrency({
+ amount: shippingMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ })
+ ),
type: 'LINE_ITEM',
status: 'FINAL',
},
{
label: 'Discount',
- price: -1 * discount,
+ price: Number.parseFloat(
+ formatCurrency({
+ amount: -1 * discountMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ })
+ ),
type: 'LINE_ITEM',
status: 'FINAL',
},
@@ -380,30 +420,59 @@ export function useBuildPaymentRequest(): {
};
// Create PayPal request with proper breakdown validation
- const calculatedTotal = subtotal + tax + shipping - discount;
+ const calculatedTotalMinorUnits =
+ subtotalMinorUnits +
+ taxMinorUnits +
+ shippingMinorUnits -
+ discountMinorUnits;
const payPalRequest: PayPalRequest = {
purchase_units: [
{
amount: {
currency_code: currencyCode,
- value: calculatedTotal.toFixed(2),
+ value: formatCurrency({
+ amount: calculatedTotalMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
breakdown: {
item_total: {
currency_code: currencyCode,
- value: subtotal.toFixed(2),
+ value: formatCurrency({
+ amount: subtotalMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
},
tax_total: {
currency_code: currencyCode,
- value: tax.toFixed(2),
+ value: formatCurrency({
+ amount: taxMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
},
shipping: {
currency_code: currencyCode,
- value: shipping.toFixed(2),
+ value: formatCurrency({
+ amount: shippingMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
},
discount: {
currency_code: currencyCode,
- value: discount.toFixed(2),
+ value: formatCurrency({
+ amount: discountMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
},
},
},
@@ -411,7 +480,12 @@ export function useBuildPaymentRequest(): {
name: lineItem?.name || '',
unit_amount: {
currency_code: currencyCode,
- value: (lineItem?.originalPrice || 0).toFixed(2),
+ value: formatCurrency({
+ amount: lineItem?.originalPrice || 0,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
},
quantity: (lineItem?.quantity || 1).toString(),
})),
@@ -500,15 +574,23 @@ export function useBuildPaymentRequest(): {
const poyntExpressRequest: PoyntExpressRequest = {
total: {
label: 'Order Total',
- amount: subtotal.toString(),
+ amount: formatCurrency({
+ amount: subtotalMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
},
lineItems: [
...(items || []).map(lineItem => {
return {
label: lineItem?.name || '',
- amount: (
- (lineItem?.originalPrice || 0) * (lineItem?.quantity || 1)
- ).toString(),
+ amount: formatCurrency({
+ amount: (lineItem?.originalPrice || 0) * (lineItem?.quantity || 1),
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
};
}),
],
@@ -517,28 +599,51 @@ export function useBuildPaymentRequest(): {
const poyntStandardRequest: PoyntStandardRequest = {
total: {
label: 'Order Total',
- amount: total.toString(),
+ amount: formatCurrency({
+ amount: totalMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
},
lineItems: [
...(items || []).map(lineItem => {
return {
label: lineItem?.name || '',
- amount: (
- (lineItem?.originalPrice || 0) * (lineItem?.quantity || 1)
- ).toString(),
+ amount: formatCurrency({
+ amount: (lineItem?.originalPrice || 0) * (lineItem?.quantity || 1),
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
};
}),
{
label: 'Tax',
- amount: tax.toFixed(2),
+ amount: formatCurrency({
+ amount: taxMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
},
{
label: 'Shipping',
- amount: shipping.toFixed(2),
+ amount: formatCurrency({
+ amount: shippingMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
},
{
label: 'Discount',
- amount: (-1 * discount).toFixed(2),
+ amount: formatCurrency({
+ amount: -1 * discountMinorUnits,
+ currencyCode,
+ isInCents: true,
+ returnRaw: true,
+ }),
},
],
};
diff --git a/packages/react/src/components/checkout/shipping/shipping-method.tsx b/packages/react/src/components/checkout/shipping/shipping-method.tsx
index c9023d20..65e35a0a 100644
--- a/packages/react/src/components/checkout/shipping/shipping-method.tsx
+++ b/packages/react/src/components/checkout/shipping/shipping-method.tsx
@@ -14,6 +14,7 @@ import { ShippingMethodSkeleton } from '@/components/checkout/shipping/shipping-
import { filterAndSortShippingMethods } from '@/components/checkout/shipping/utils/filter-shipping-methods';
import { useApplyShippingMethod } from '@/components/checkout/shipping/utils/use-apply-shipping-method';
import { useDraftOrderShippingMethods } from '@/components/checkout/shipping/utils/use-draft-order-shipping-methods';
+import { formatCurrency } from '@/components/checkout/utils/format-currency';
import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { useGoDaddyContext } from '@/godaddy-provider';
@@ -259,10 +260,12 @@ export function ShippingMethodForm() {
{t.general.free}
) : (
- {new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: shippingMethods[0]?.cost?.currencyCode || 'USD',
- }).format((shippingMethods[0]?.cost?.value || 0) / 100)}
+ {formatCurrency({
+ amount: shippingMethods[0]?.cost?.value || 0,
+ currencyCode:
+ shippingMethods[0]?.cost?.currencyCode || 'USD',
+ isInCents: true,
+ })}
)}
@@ -301,10 +304,11 @@ export function ShippingMethodForm() {
{t.general.free}
) : (
- {new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: method?.cost?.currencyCode || 'USD',
- }).format((method?.cost?.value || 0) / 100)}
+ {formatCurrency({
+ amount: method?.cost?.value || 0,
+ currencyCode: method?.cost?.currencyCode || 'USD',
+ isInCents: true,
+ })}
)}
diff --git a/packages/react/src/components/checkout/tips/tips-form.tsx b/packages/react/src/components/checkout/tips/tips-form.tsx
index bfc0aeab..7451436d 100644
--- a/packages/react/src/components/checkout/tips/tips-form.tsx
+++ b/packages/react/src/components/checkout/tips/tips-form.tsx
@@ -1,6 +1,7 @@
import { useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { useCheckoutContext } from '@/components/checkout/checkout';
+import { formatCurrency } from '@/components/checkout/utils/format-currency';
import { Button } from '@/components/ui/button';
import {
FormControl,
@@ -27,15 +28,8 @@ export function TipsForm({ total, currencyCode }: TipsFormProps) {
const [showCustomTip, setShowCustomTip] = useState(false);
const calculateTipAmount = (percentage: number): number => {
- return Math.round(((total * percentage) / 100) * 100);
- };
-
- const formatCurrency = (amount: number): string => {
- // Convert from cents to dollars before formatting
- return new Intl.NumberFormat('en-US', {
- style: 'currency',
- currency: currencyCode || 'USD',
- }).format(amount / 100);
+ // total is in minor units, so calculate percentage and return in minor units
+ return Math.round((total * percentage) / 100);
};
const handlePercentageSelect = (percentage: number) => {
@@ -116,7 +110,11 @@ export function TipsForm({ total, currencyCode }: TipsFormProps) {
>
{percentage}%
- {formatCurrency(calculateTipAmount(percentage))}
+ {formatCurrency({
+ amount: calculateTipAmount(percentage),
+ currencyCode: currencyCode || 'USD',
+ isInCents: true,
+ })}
))}
@@ -168,19 +166,32 @@ export function TipsForm({ total, currencyCode }: TipsFormProps) {
{...field}
placeholder={t.tips.placeholder}
className='h-12'
- value={field.value > 0 ? field.value / 100 : ''}
+ value={
+ field.value > 0
+ ? formatCurrency({
+ amount: field.value,
+ currencyCode: currencyCode || 'USD',
+ isInCents: true,
+ returnRaw: true,
+ })
+ : ''
+ }
onChange={e => {
- // Convert dollars to cents when storing
- const tipAmount = Math.round(
- Number.parseFloat(e.target.value) * 100
- );
- field.onChange(tipAmount);
-
- // Only track when user stops typing (on blur)
+ // User inputs in major units (e.g., $10.50), convert to minor units for storage
+ const inputValue = Number.parseFloat(e.target.value);
+ if (!Number.isNaN(inputValue)) {
+ const tipAmount = Math.round(inputValue * 100);
+ field.onChange(tipAmount);
+ } else {
+ field.onChange(0);
+ }
}}
onBlur={e => {
- const tipAmount =
- Math.round(Number.parseFloat(e.target.value) * 100) || 0;
+ // User inputs in major units (e.g., $10.50), convert to minor units for storage
+ const inputValue = Number.parseFloat(e.target.value);
+ const tipAmount = !Number.isNaN(inputValue)
+ ? Math.round(inputValue * 100)
+ : 0;
// Track custom tip amount entry
track({
eventId: eventIds.enterCustomTip,
diff --git a/packages/react/src/components/checkout/totals/totals.tsx b/packages/react/src/components/checkout/totals/totals.tsx
index ca4c4751..3ae210b6 100644
--- a/packages/react/src/components/checkout/totals/totals.tsx
+++ b/packages/react/src/components/checkout/totals/totals.tsx
@@ -1,5 +1,6 @@
import { DiscountStandalone } from '@/components/checkout/discount/discount-standalone';
import { TotalLineItemSkeleton } from '@/components/checkout/totals/totals-skeleton';
+import { formatCurrency } from '@/components/checkout/utils/format-currency';
import { useGoDaddyContext } from '@/godaddy-provider';
export interface DraftOrderTotalsProps {
@@ -18,6 +19,7 @@ export interface DraftOrderTotalsProps {
enableTaxes?: boolean | null;
enableDiscounts?: boolean | null;
enableShipping?: boolean | null;
+ isInCents?: boolean;
}
function TotalLineItem({
@@ -25,11 +27,13 @@ function TotalLineItem({
description,
value,
currencyCode = 'USD',
+ isInCents = false,
}: {
title: string;
description?: string;
value: number;
currencyCode?: string;
+ isInCents?: boolean;
}) {
return (
@@ -40,10 +44,11 @@ function TotalLineItem({
) : null}
- {new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: currencyCode,
- }).format(value)}
+ {formatCurrency({
+ amount: value,
+ currencyCode,
+ isInCents,
+ })}
);
@@ -64,6 +69,7 @@ export function DraftOrderTotals({
isShippingLoading = false,
isDiscountLoading = false,
enableShipping = true,
+ isInCents = false,
}: DraftOrderTotalsProps) {
const { t } = useGoDaddyContext();
const handleDiscountsChange = (discounts: string[]) => {
@@ -82,6 +88,7 @@ export function DraftOrderTotals({
: t.totals.noItems
}
value={subtotal}
+ isInCents={isInCents}
/>
{discount > 0 ? (
isDiscountLoading ? (
@@ -91,6 +98,7 @@ export function DraftOrderTotals({
currencyCode={currencyCode}
title={t.totals.discount}
value={-discount || 0}
+ isInCents={isInCents}
/>
)
) : null}
@@ -102,6 +110,7 @@ export function DraftOrderTotals({
currencyCode={currencyCode}
title={t.totals.shipping}
value={shipping || 0}
+ isInCents={isInCents}
/>
))}
{tip ? (
@@ -109,6 +118,7 @@ export function DraftOrderTotals({
currencyCode={currencyCode}
title={t.totals.tip}
value={tip || 0}
+ isInCents={isInCents}
/>
) : null}
{enableTaxes &&
@@ -119,6 +129,7 @@ export function DraftOrderTotals({
currencyCode={currencyCode}
title={t.totals.estimatedTaxes}
value={taxes || 0}
+ isInCents={isInCents}
/>
))}
@@ -143,10 +154,11 @@ export function DraftOrderTotals({
{currencyCode}{' '}
- {new Intl.NumberFormat('en-us', {
- style: 'currency',
- currency: currencyCode,
- }).format(total)}
+ {formatCurrency({
+ amount: total,
+ currencyCode,
+ isInCents,
+ })}
diff --git a/packages/react/src/components/checkout/utils/checkout-transformers.ts b/packages/react/src/components/checkout/utils/checkout-transformers.ts
index a296fac2..5382367c 100644
--- a/packages/react/src/components/checkout/utils/checkout-transformers.ts
+++ b/packages/react/src/components/checkout/utils/checkout-transformers.ts
@@ -160,15 +160,12 @@ export function mapSkusToItemsDisplay(
image: orderItem.details?.productAssetUrl || skuDetails?.mediaUrls?.[0],
quantity: orderItem.quantity || 0,
originalPrice:
- (orderItem.totals?.subTotal?.value ?? 0) /
- (orderItem.quantity || 0) /
- 100,
+ (orderItem.totals?.subTotal?.value ?? 0) / (orderItem.quantity || 0),
price:
- ((orderItem.totals?.subTotal?.value ?? 0) +
- (orderItem.totals?.feeTotal?.value ?? 0) -
- // (orderItem.totals?.taxTotal?.value ?? 0) - // do we need taxTotal here?
- (orderItem.totals?.discountTotal?.value ?? 0)) /
- 100,
+ (orderItem.totals?.subTotal?.value ?? 0) +
+ (orderItem.totals?.feeTotal?.value ?? 0) -
+ // (orderItem.totals?.taxTotal?.value ?? 0) - // do we need taxTotal here?
+ (orderItem.totals?.discountTotal?.value ?? 0),
notes: orderItem.notes
?.filter(
note =>
diff --git a/packages/react/src/components/checkout/utils/format-currency.ts b/packages/react/src/components/checkout/utils/format-currency.ts
new file mode 100644
index 00000000..36fbf69d
--- /dev/null
+++ b/packages/react/src/components/checkout/utils/format-currency.ts
@@ -0,0 +1,96 @@
+/**
+ * Currency configuration map with symbols and decimal precision.
+ */
+export const currencyConfigs: Record<
+ string,
+ { symbol: string; precision: number; pattern?: string }
+> = {
+ AUD: { symbol: '$', precision: 2 },
+ CAD: { symbol: '$', precision: 2 },
+ HKD: { symbol: '$', precision: 2 },
+ SGD: { symbol: '$', precision: 2 },
+ NZD: { symbol: '$', precision: 2 },
+ USD: { symbol: '$', precision: 2 },
+ VND: { symbol: '₫', precision: 0 },
+ EUR: { symbol: '€', precision: 2 },
+ GBP: { symbol: '£', precision: 2 },
+ ARS: { symbol: '$', precision: 2 },
+ CLP: { symbol: '$', precision: 0 },
+ COP: { symbol: '$', precision: 2 },
+ PHP: { symbol: '₱', precision: 2 },
+ MXN: { symbol: '$', precision: 2 },
+ BRL: { symbol: 'R$', precision: 2 },
+ INR: { symbol: '₹', precision: 2 },
+ IDR: { symbol: 'Rp', precision: 2 },
+ PEN: { symbol: 'S/', precision: 2 },
+ AED: { symbol: 'د.إ', precision: 2, pattern: '#!' },
+ ILS: { symbol: '₪', precision: 2 },
+ TRY: { symbol: '₺', precision: 2 },
+ ZAR: { symbol: 'R', precision: 2 },
+ CNY: { symbol: '¥', precision: 2 },
+ JPY: { symbol: '¥', precision: 0 },
+ KRW: { symbol: '₩', precision: 0 },
+ TWD: { symbol: 'NT$', precision: 0 },
+ KWD: { symbol: 'د.ك', precision: 3 },
+ BHD: { symbol: '.د.ب', precision: 3 },
+ JOD: { symbol: 'د.ا', precision: 3 },
+ OMR: { symbol: 'ر.ع.', precision: 3 },
+};
+
+export interface FormatCurrencyOptions {
+ /** Numeric amount to format or convert */
+ amount: number;
+ /** ISO 4217 currency code (e.g. 'USD', 'VND', 'CLP') */
+ currencyCode: string;
+ /** Optional locale, defaults to 'en-US' */
+ locale?: string;
+ /**
+ * Indicates whether the input is already in cents (minor units).
+ * - true → format to currency string (default)
+ * - false → convert to minor units and return as string
+ */
+ isInCents?: boolean;
+ /**
+ * Return raw numeric value without currency symbol.
+ * - true → returns "10.00" instead of "$10.00"
+ * - false → returns full currency string (default)
+ */
+ returnRaw?: boolean;
+}
+
+/**
+ * Formats or converts a currency amount.
+ *
+ * - When `isInCents = true` (default): returns formatted string like "$123.45"
+ * - When `isInCents = false`: returns string representing minor units like "12345"
+ * - When `returnRaw = true`: returns numeric value without currency symbol like "123.45"
+ */
+export function formatCurrency({
+ amount,
+ currencyCode,
+ locale = 'en-US',
+ isInCents = true,
+ returnRaw = false,
+}: FormatCurrencyOptions): string {
+ const config = currencyConfigs[currencyCode] || {};
+
+ const { precision = 2 } = config;
+
+ if (!isInCents) {
+ // Convert major units to minor units and return as string
+ return Math.round(amount * Math.pow(10, precision)).toString();
+ }
+
+ // Format value already in minor units
+ const value = amount / Math.pow(10, precision);
+
+ const nfLocale = returnRaw ? 'en-US' : locale;
+
+ return new Intl.NumberFormat(nfLocale, {
+ style: returnRaw ? 'decimal' : 'currency',
+ currency: returnRaw ? undefined : currencyCode,
+ minimumFractionDigits: precision,
+ maximumFractionDigits: precision,
+ useGrouping: returnRaw ? false : undefined,
+ }).format(value);
+}
diff --git a/packages/react/src/server.ts b/packages/react/src/server.ts
index fc9ee3c9..92cee345 100644
--- a/packages/react/src/server.ts
+++ b/packages/react/src/server.ts
@@ -3,7 +3,7 @@
import * as GoDaddy from '@/lib/godaddy/godaddy';
import { CreateCheckoutSessionInputWithKebabCase } from '@/lib/godaddy/godaddy';
import { getEnvVar } from '@/lib/utils';
-import type { CheckoutSessionInput, CheckoutSessionOptions } from '@/types';
+import type { CheckoutSessionOptions } from '@/types';
let accessToken: string | undefined;
let accessTokenExpiresAt: number | undefined;
@@ -81,10 +81,9 @@ async function getAccessToken({
);
}
- const result = (await response.json()) as {
+ return (await response.json()) as {
access_token: string;
scope: string;
expires_in: number;
};
- return result;
}