Stripe charges 100x less: amount must be in cents
Your product costs $17.99, but the Stripe Checkout page shows $0.18, or the API rejects the request with an "Invalid integer" error. Stripe does not take prices in dollars. It takes an integer number of the smallest currency unit: cents for USD and EUR.
The cause
The amount field on PaymentIntents and the unit_amount field on Prices and Checkout line items are integers. 1799 means $17.99. Passing 17.99 fails because it is not an integer; passing 18 (a rounded dollar value) charges 18 cents, which is 100 times less than you meant.
const price = 17.99;
await stripe.paymentIntents.create({
amount: price, // rejected: not an integer
currency: 'usd',
});
await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{
quantity: 1,
price_data: {
currency: 'usd',
unit_amount: Math.round(price), // 18 cents, not $17.99
product_data: { name: 'Pro plan' },
},
}],
success_url: `${origin}/success`,
});The fix
Multiply by 100 and round. The rounding is not optional: floating point math gives 17.99 * 100 === 1798.9999999999998, which Stripe rejects, and truncating with Math.floor or | 0 would charge one cent too little.
function toCents(price: number): number {
return Math.round(price * 100);
}
await stripe.paymentIntents.create({
amount: toCents(17.99), // 1799
currency: 'usd',
});A better long-term fix is to store prices in cents everywhere (database column price_cents integer) and only divide by 100 when you display them. Then there is no conversion to forget. Never take the amount from the browser either: look the price up on the server by product id, or use a Stripe Price id.
const label = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(priceCents / 100); // "$17.99"Zero-decimal currencies
"Multiply by 100" is only right for currencies with two decimal places. Some currencies have no minor unit, so the amount is the whole number: amount: 500 in jpy is 500 yen, not 5 yen. Examples include JPY, KRW, VND and CLP. A few currencies use three decimals (such as BHD, KWD and OMR). Stripe's currency documentation has the full list and some special cases, so check it before you add a new currency.
const ZERO_DECIMAL = new Set(['jpy', 'krw', 'vnd', 'clp' /* see Stripe docs for the full list */]);
function toStripeAmount(price: number, currency: string): number {
return ZERO_DECIMAL.has(currency.toLowerCase())
? Math.round(price)
: Math.round(price * 100);
}Verify it
toCents(17.99) returns 1799 and toCents(0.29) returns 29.How Zelsis catches this
In files that use Stripe, Zelsis flags amount and unit_amount set to a decimal literal or to a variable holding one, and suggests the integer-cents fix.
- BUG-10Stripe amount or unit_amount given in dollars instead of cents
More logic bugs from real code: bugs we found in popular open-source repos.
Check your own repo
Public repositories scan free. Paste a GitHub URL on the dashboard and every finding comes with the file, the line and a fix.
Scan a repository free