Take payments without the headaches.

Three ways to take money, what each one really costs, and the two checks that stop fake payments and customer-chosen prices.

An ornate vintage cash register
Photo by GG on Unsplashdithered by Cyborb

The simplest safe way to add payments to your app is to let a payment provider host the checkout page. Start with a Stripe Payment Link or Stripe Checkout, or with a merchant of record such as Paddle if you want someone else to handle sales tax. Then unlock paid features only when a verified webhook says the payment went through.

That way card numbers never touch your server. You also avoid the two classic mistakes in AI-written payment code: trusting a price sent by the browser, and trusting a webhook nobody verified.

The short version
  • Payment Links need no code. Hosted Checkout needs one server route and a webhook. Custom payment forms are rarely worth it early on.
  • As of September 2026, Stripe charges US businesses 2.9% plus 30 cents per domestic card payment. Merchants of record charge more but take on sales tax and VAT.
  • The server decides the price. The browser only says which plan the customer picked.
  • Unlock access from a verified webhook, never from the success page alone.
  • Rehearse everything in test mode with Stripe’s test cards and CLI before real money moves.

Three ways to take payments

Payment LinkHosted CheckoutCustom payment form
Code neededNoneOne server route and a webhookThe most: your page plus Stripe’s embedded components
Where customers payA Stripe-hosted pageA Stripe-hosted pageInside your own page
How access unlocksBy hand, or a webhookA webhookA webhook
Best forPre-orders and first salesMost SaaS subscriptionsCustom flows once you have volume

A Payment Link is the fastest test of whether anyone will pay. Create a product in the Stripe dashboard, copy the link and put it on your landing page. There is no extra fee: Payment Links and Checkout are included in standard processing.

Once payments must attach to user accounts, move to hosted Checkout. Your server creates a checkout session, Stripe hosts the payment page, and a webhook tells you when money arrived. Custom forms give you full control of the look, but they add code to write, test and maintain.

Do you need a merchant of record?

This matters because sales tax, VAT and GST on software often depend on where the buyer lives. Sell to customers in many countries and the paperwork can outgrow the product. Stripe Tax can calculate and collect tax for 0.5% per transaction on its no-code plan, but your business remains the seller.

Good at
  • Tax registration, collection, filing and payment handled for you
  • Fraud screening and chargebacks handled for you
  • One provider answers customers’ billing questions
Watch out for
  • Higher fees than Stripe alone
  • Built for digital products; Stripe’s version excludes physical goods and services like consulting
  • Less control over the checkout experience

A simple rule: if you sell mostly in your own country, Stripe alone is fine. If you sell software worldwide from day one and work alone, a merchant of record is usually worth its fee.

How much does it cost to take payments?

List prices as of September 2026, from each provider’s own pricing page. Stripe’s rates are for a business based in the US; other countries have their own:

ProviderWho is the sellerFee per paymentWorth knowing
StripeYou2.9% + 30 cents (US cards)+1.5% for international cards; subscriptions add 0.7% for Billing
Stripe Managed PaymentsStripeStripe’s fees + 3.5%Digital products only, for businesses in supported countries, after an eligibility review
PaddlePaddle5% + 50 centsTax, fraud and chargebacks included; custom pricing below $10
Lemon SqueezyLemon Squeezy5% + 50 cents, plus small extra fees on some paymentsIts January 2026 update says the goal is an easy move to Stripe Managed Payments
PolarPolar5% + 50 cents on the free plan+1.5% for international cards; lower rates on paid plans from $20 a month

Percentages hide the real cost at small prices, so here is one $20 monthly subscription paid to a US business with a US card:

SetupFee per paymentSales tax
Stripe with Billing$1.02Your job
Stripe with Billing and Stripe Tax$1.12Calculated for you, still your filing
Paddle$1.50Handled for you

Disputes cost extra with Stripe: $15 for each one you receive, and another $15 if you respond to it by hand, which you get back if you win.

How money flows through your app

  1. Create your prices in the dashboard

    Set up products and prices in test mode first. Copy each price ID into an environment variable.

  2. Create a checkout session on the server

    When a signed-in user clicks Upgrade, your server creates a Checkout Session with the right price and the user’s ID, then sends them to Stripe’s page. Take that ID from the login session, as our guide to adding login explains.

  3. Let the customer pay on Stripe’s page

    Stripe collects the card, handles bank authentication and redirects the customer back to your success page.

  4. Unlock access from the webhook

    Stripe sends a checkout.session.completed event to your server. Verify it, check that its payment_status is not unpaid, then turn on the paid plan for that user, exactly once. Bank debits can finish checkout before the money arrives, and Stripe sends checkout.session.async_payment_succeeded when it does.

  5. Keep listening after the sale

    Renewals, failed payments and cancellations arrive as events too, such as invoice.payment_failed and customer.subscription.deleted. Update access when they do.

Stripe is blunt about step four. You cannot rely on the success page alone, because customers are not guaranteed to reach it. Someone can pay and lose their connection before it loads, and anyone can type its address.

Mistake one: letting the browser set the price

Ask an agent for a checkout button and it may send the price from the page to your server. Anyone can change that request in their browser’s developer tools. Edit it, and the vulnerable version asks Stripe to charge whatever amount the visitor typed.

Diff
- const { amount } = req.body; // the browser picks the price
- line_items: [{ price_data: { currency: "usd", unit_amount: amount, product_data: { name: "Pro" } }, quantity: 1 }],
+ const price = PRICES[req.body.plan]; // the server picks the price
+ line_items: [{ price, quantity: 1 }],

Here, PRICES maps plan names to the price IDs from your dashboard, and an unknown plan name gets an error. The same rule covers quantities, discounts and trial lengths. The browser asks. The server decides.

Mistake two: trusting a webhook you did not verify

Your webhook address is public. Without a signature check, anyone who finds it can post a fake “payment succeeded” event and unlock your product for free. Stripe signs every event, and your server must check that signature before acting. This handler uses Express 5.2 and the stripe library 22.6:

server.js
import express from "express";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const app = express();

// Stripe signs the exact bytes it sends, so this route needs the raw body.
// Keep it above any app.use(express.json()) line.
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.body,
      req.headers["stripe-signature"],
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    console.log(`Rejected: ${err.message}`);
    return res.sendStatus(400);
  }

  if (event.type === "checkout.session.completed") {
    const session = event.data.object;
    // Bank debits can complete checkout before the money arrives.
    if (session.payment_status !== "unpaid") {
      // Unlock access here. Stripe may send the same event twice,
      // so skip sessions you have already fulfilled.
      console.log(`Paid: ${session.id}`);
    }
  }
  res.sendStatus(200);
});

app.listen(4242, () => console.log("Listening on port 4242"));

Sending it one properly signed test event and two fakes gives this:

Text
Signed by Stripe: HTTP 200
Tampered amount: HTTP 400
No signature: HTTP 400

The raw body comment matters. Put app.use(express.json()) above this route and even genuine events fail with “Webhook payload must be provided as a string or a Buffer.” That error is when an agent may “fix” things by deleting the check. Since August 2026 it could also reach for constructEventWithoutVerification, a helper meant for events you already verified elsewhere. In a public webhook route, either one is a red flag.

Two more rules from Stripe’s webhook guide. Return a 200 quickly, because Stripe retries failed deliveries for up to three days in live mode. And store the IDs you have processed, since the same event can arrive twice and events can arrive out of order. Our security checklist for AI-generated code covers the checks around this.

Test mode before real money

Stripe’s sandboxes let you run the whole flow without moving money. Use any future expiry date and any three-digit CVC with these test cards:

  • 4242 4242 4242 4242 succeeds.

  • 4000 0000 0000 0002 is declined.

  • 4000 0025 0000 3155 asks for extra authentication.

To send events to the server on your laptop, use the Stripe CLI. These commands need your own Stripe account, so run stripe login first:

Terminal
# Forward test events to your local server
stripe listen --forward-to localhost:4242/webhook

# In a second terminal, fire a test payment event
stripe trigger checkout.session.completed

stripe listen prints a signing secret that starts with whsec_. Use it as STRIPE_WEBHOOK_SECRET while you test locally.

Before you take real money0 of 7

FAQ

Should I use Stripe or Paddle?

Use Stripe if you want the lowest fees and the most control, and you sell mostly at home or can handle sales tax. Use Paddle or another merchant of record if you sell digital products worldwide and want tax, fraud and disputes off your desk.

Is it safe to put Stripe keys in my app?

Only the publishable key, which starts with pk_, belongs in the browser. The secret key and the webhook secret must stay on the server in environment variables. Our guide to keeping API keys safe covers the rest.

What happens when a customer disputes a payment?

The customer’s bank takes the money back while it reviews the case, and you can respond with evidence. As of September 2026, Stripe charges a US business $15 for each dispute, plus $15 to respond by hand, which comes back if you win. Merchants of record such as Paddle handle chargebacks as part of their fee.

Can an AI agent build my payment integration?

Yes, with test keys only. Then check the two mistakes above yourself: the server must set the price, and the webhook must verify the signature.

Next, see how payments fit into building a SaaS from idea to first paying user, or choose a database for your orders and users.

Sources
  1. Pricing and fees, Stripe, September 2026
  2. Managed Payments, Stripe Docs, September 2026
  3. Managed Payments eligibility, Stripe Docs, September 2026
  4. Pricing, Paddle, September 2026
  5. Pricing, Lemon Squeezy, September 2026
  6. 2026 update: Lemon Squeezy and Stripe Managed Payments, Lemon Squeezy, January 2026
  7. Fees, Polar, September 2026
  8. Receive Stripe events in your webhook endpoint, Stripe Docs, September 2026
  9. Fulfill orders, Stripe Docs, September 2026
  10. Testing, Stripe Docs, September 2026
  11. stripe-node v22.5.0 release notes, Stripe, August 2026
cyborb.ai

Stop reading about it. Build it.

Describe what you want in plain words. Cyborb plans the work, writes and runs the code, makes the assets, and puts the result online.

Download Cyborb

Free to start. No card required.