Payments
One API call creates a Midtrans checkout session. The user pays. We verify the webhook, assign the plan claim to the user, and POST a payment.settled event to your server. Free plans skip checkout entirely โ the claim is assigned instantly. You don't touch Midtrans directly.
What payment methods are supported?
All the ones your Indonesian users actually use:
How it works
Create a payment
Call this from your backend โ never from the browser. The client_secret must stay server-side.
const res = await fetch("https://astapa.com/api/platform/payment", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: process.env.ASTAPA_CLIENT_ID,
client_secret: process.env.ASTAPA_CLIENT_SECRET,
end_user_id: "user-uuid", // from your JWT's sub claim
plan_id: "plan-uuid", // from your plan registry
customer_email: "user@example.com",
customer_name: "John Doe", // optional
metadata: { ref: "upgrade-flow" }, // optional, passed to webhook
}),
});
const { invoice_url, order_id, payment_id } = await res.json();
// Redirect the user to complete payment
return redirect(invoice_url);Payment statuses
| Status | What it means |
|---|---|
pending | Checkout created, waiting for the user to pay |
settlement | Payment confirmed โ plan claim updated, webhook fired |
capture | Credit card captured (treat same as settlement) |
deny | Payment denied by the provider |
cancel | User cancelled |
expire | Payment window expired (usually 24h) |
refund | Full or partial refund processed |
Get notified when payments settle
Set a webhook URL on your project (Settings tab in the dashboard, or PATCH /api/platform/projects/[id] with webhook_url). We'll POST to it after every settled payment โ subscriptions and item purchases.
{
"event": "payment.settled",
"order_id": "proj-abc123-1a2b3c4d",
"plan_id": "plan-uuid",
"plan_key": "pro",
"plan_display_name": "Pro Plan",
"plan_period": "monthly",
"end_user_id": "user-uuid",
"amount": 29000,
"currency": "IDR",
"paid_at": "2026-05-06T10: 00: 00Z",
"metadata": { "ref": "upgrade-flow" }
}X-Astapa-Event: payment.settled. Use it to route events in a single handler.export async function POST(req: Request) {
const event = req.headers.get("X-Astapa-Event");
const body = await req.json();
if (event === "payment.settled") {
// Plan claim is already updated on the user โ this is just your chance
// to do anything else: send a receipt, provision resources, etc.
await sendReceiptEmail(body.end_user_id, body.amount);
}
return new Response("ok");
}Want the full webhook story? See the Webhooks page.
View payment history
Testing in sandbox
Use your sandbox credentials and Midtrans routes to their test environment automatically. No real money moves.
| Method | Test credentials |
|---|---|
| Credit card | 4811 1111 1111 1114 ยท any future expiry ยท CVV 123 |
| GoPay / QRIS | Auto-approve in Midtrans sandbox dashboard |
Related