Your Checkout Trusts the Price the Browser Sends It
AI-built checkout flows often let the browser tell the server what the price is. Anyone with dev tools open can change that number before they pay.

Your app charges whatever number the browser sends it, not the number on your own price list.
AI coding tools build checkout flows fast. They wire up a cart, a total, and a call to Stripe or PayPal in minutes. But they often skip one check: where does the price actually come from? If the browser sends the price and the server just accepts it, anyone can open dev tools and pay one cent for a $200 item.
This happens because the AI writes the checkout form first. The form already knows the price, since it renders it on the page. So when it builds the API call, it just sends that price along with the request. It feels natural. It also means the price now lives in a variable the user controls.
Here is what that looks like in code.
// Broken: server trusts the price from the client
app.post("/api/checkout", async (req, res) => {
const { productId, price, quantity } = req.body;
const amount = price * quantity;
await stripe.paymentIntents.create({
amount: amount * 100,
currency: "usd",
});
res.json({ success: true });
});A user can open the network tab, edit the request, change price to 0.01, and resend it. The server has no way to know that number is fake. It just multiplies and charges.
The fix is simple. Never trust a price or quantity from the client. Look up the real price from your own database, using only the product ID the client sent.
// Fixed: server looks up the real price
app.post("/api/checkout", async (req, res) => {
const { productId, quantity } = req.body;
const product = await db.product.findUnique({
where: { id: productId },
});
if (!product) {
return res.status(404).json({ error: "Product not found" });
}
const safeQuantity = Math.max(1, Math.min(quantity, 50));
const amount = product.price * safeQuantity;
await stripe.paymentIntents.create({
amount: amount * 100,
currency: "usd",
});
res.json({ success: true });
});Notice the request body only sends productId and quantity now. The price never comes from the client at all. The server owns the number that gets charged, not the browser.
This same bug shows up with discount codes and shipping costs too. If your server reads a discount or shippingCost field straight from the request body, someone can zero those out the same way. Every dollar amount in a checkout flow needs to come from your own data, calculated on your own server.
Quantity needs a limit too. Without one, someone can send a quantity of -5 and get a negative charge, which some payment processors will treat as a refund straight into their account.
Check your own app right now. Open your checkout page, open the network tab in your browser, and look at what your app sends when you click pay. If you see a price, a discount, or a total sitting in that request body, your server is probably trusting a number it should be calculating itself. Fix it by moving every price lookup to the server, keyed only by product ID.
Want a second pair of eyes on your checkout flow? Get a free scan at /free-assessment.
VibeAudits audits apps built with Cursor, Lovable, Bolt, Claude Code, Replit, and other AI tools.