Stripe
Use Stripe for MainSDK payments and usage metering - test and live keys, main.yaml, webhook signing secrets, local testing and swapping.
Stripe is the default provider behind pay and one of the two drivers behind meter. Your app starts a hosted checkout, a subscription or a refund through pay, and Stripe holds the cards, customers and subscriptions. Pick Stripe when you sell to customers outside India, or as the default next to Razorpay for India. Pick its meters when your pricing charges by usage and you already bill through Stripe.
Interfaces it serves
Pick Stripe in main.yaml for any of these. Your calls stay the same, and you can swap later.
payBuilding for 0.3Driver stripe StripeDefault
payments:
driver: stripeSwap to another provider
meterBuilding for 0.3Driver stripe-meters Stripe Billing meters (export through pay)From the meter spec
meter:
driver: stripe-metersBefore you start
You need a Stripe account. Stripe gives every account a test mode and a live mode, each with its own keys, objects and webhook endpoints.
- Get your API keys. In the Stripe Dashboard, open the developers area and copy the secret key for test mode. You get the live secret key once your account is activated for payments. Stripe also lets you create restricted keys with narrower permissions; use one if your team's policy calls for it.
- Create a webhook endpoint. In the webhooks section of the developers area, add an endpoint that points at the URL where your app receives
payevents, and subscribe it to the payment, refund and subscription events your app handles. Howpay.on()handlers are mounted on a route is not defined yet. Stripe shows a signing secret for each endpoint; copy it. - Repeat for live mode. Test and live endpoints are separate, and each has its own signing secret.
- For metering: set up the meters and usage-based prices you bill on in Stripe Billing.
Store the keys in your secrets manager, never in code or a committed env file.
Configure
Payments route by market under payments:; Stripe's settings live under pay.stripe.
payments: { default: stripe }
pay:
stripe:
key: secret:STRIPE_SECRET_KEY # suggested name; the secret API key
webhook: secret:STRIPE_WEBHOOK_SECRET # suggested name; the endpoint's signing secretUse test-mode values in development and stage, and live values in production, by pointing each environment's secrets at the right keys. A literal key in main.yaml fails mainsdk check with MAIN-CONFIG-0004.
For usage metering:
meter:
driver: stripe-metersThe meter interface has no spec yet, so how the driver gets its key and how metrics map to Stripe meters are not decided yet.
Use it
Start a hosted checkout. Card details stay on Stripe's page or hosted fields.
"use server";
import { auth, pay } from "mainsdk";
import { redirect } from "next/navigation";
export async function startCheckout(req: Request, orderId: string) {
await auth.require(req);
const session = await pay.checkout({
order: orderId,
amount: 4900, // integer minor units
currency: "USD", // ISO 4217
successUrl: "/orders/done",
cancelUrl: "/cart",
});
if ("url" in session) redirect(session.url);
return session; // { clientSecret } for hosted fields
}React to verified Stripe events with pay.on():
import { pay } from "mainsdk";
pay.on("payment.succeeded", async (event) => {
// runs only after the Stripe signature and timestamp are checked, once per event
});Record usage for Stripe meters:
import { meter } from "mainsdk";
meter.record("api_calls", 1, { feature: "export" });Local development and testing
- No Stripe account:
mainsdk devwith no provider key runs the local test card flow, which completes a checkout and firespayment.succeeded. - Stripe test mode: use your test-mode secret key and signing secret to try the real hosted checkout with Stripe's test cards. To receive webhooks on your machine, the Stripe CLI can forward test events to a local URL and prints a signing secret for that session.
- Tests:
createTestApp()from@mainsdk/testingrunspayon its in-memory fake, andchaos.on("pay", { outage: true })simulates a Stripe outage.
import { it } from "node:test";
import { createTestApp, expect } from "@mainsdk/testing";
it("charges once when checkout is retried", async () => {
const app = createTestApp({ main: "./main.yaml" });
// ... run your checkout action twice
expect(app.pay).toHaveCharged({ amount: 4900, currency: "USD", times: 1 });
});What MainSDK adds on top
- Checkout only on Stripe's hosted page or hosted fields; card numbers never touch your server, and card handling in your code fails
MAIN-PAY-0001. - Every Stripe webhook verified (signature and a 5-minute timestamp window) before your handler runs; unverified calls get 400 and are logged, and duplicates run your handler once.
- Idempotency keys on checkout, subscribe and refund, passed to Stripe's own idempotency, so a retry never charges or refunds twice.
- Amounts in integer minor units with an ISO 4217 currency.
See Payments for the full contract.
Limits and notes
- Stripe's availability depends on the country of your business and of your customers; check that Stripe supports both.
- Use Razorpay for UPI and India-first checkout, routed by market so one deployment serves both.
pay.raw()returns the Stripe client for featurespaydoes not wrap, such as Stripe-only billing settings.mainsdk checklists every use.- Store purchases (Play Billing, StoreKit) are not part of
pay@1.
Swap to or from it
Route a market to another provider, or change the default, in one line:
payments: { in: razorpay, default: stripe } # was { default: stripe }FAQ
Do I need a Stripe account to start building? No. mainsdk dev runs a local test card flow with no key, and tests use the in-memory fake. You need an account when you want the real hosted checkout.
Can I use Stripe and Razorpay in the same app? Yes. Route by market with payments: { in: razorpay, default: stripe }. Each request uses the provider for the user's market.
Can I build my own card form on Stripe Elements? Only with Stripe's hosted fields, which keep card data on Stripe. A card-number input that posts to your own route fails MAIN-PAY-0001.
Next
- Payments and Usage metering
- Razorpay, Paddle and Lago
- Swap a driver
Open questions
Was this page helpful?