Shop Payments
You manage your own product catalog. You just need Astapa to handle the Midtrans checkout. Pass an amount and an order ID โ we create the session, handle the webhook, and redirect the buyer back to you.
When should I use this?
| Use shop payments when... | Use items when... |
|---|---|
| Your product catalog lives in your own database | You want Astapa to manage the catalog |
| Each order has a unique amount (e.g. cart total) | Items have a fixed price (with optional override) |
| You already have order IDs from your own system | You want Astapa to generate order IDs |
Karejo uses this
Shop payments power Karejo's store checkout. Each order has a unique amount and order ID from Karejo's own database โ Astapa just handles the Midtrans layer.
How it works
1
Your backend calls POST /api/platform/shop-payment
Pass your client credentials, the order amount, your own order ID, and buyer details.
2
You get back a checkout URL
Redirect the buyer to invoice_url. Midtrans handles the payment UI.
3
Midtrans notifies us
We verify the signature and record the payment.
4
We notify you
If you have a project-level webhook URL, we POST item.purchased to it.
5
Buyer is redirected
After payment, Midtrans sends the buyer to astapa.com/purchase/callback, which shows a status page and redirects back to your store if you passed metadata.store_slug.
Create a shop payment
Server-side only. Never call this from the browser โ your client_secret is in the request body.
create-shop-payment.tstypescript
const res = await fetch("https://astapa.com/api/platform/shop-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,
amount: 75000, // IDR integer, required
order_id: "your-internal-order-uuid", // your order ID, must be unique
buyer_email: "buyer@example.com", // required
buyer_name: "John Doe", // optional
item_name: "Nasi Goreng Spesial", // optional โ shown in checkout
metadata: { // optional โ passed to webhook + callback
store_id: "store-uuid",
store_name: "Warung Bu Sari",
store_slug: "warung-bu-sari", // enables auto-redirect after payment
product_id: "product-uuid",
},
}),
});
const { invoice_url, order_id, purchase_id } = await res.json();
// Redirect the buyer
return redirect(invoice_url);Check payment status
Public endpoint โ no auth needed. Use this to poll for status or verify before fulfilling an order.
verify-payment.tstypescript
const res = await fetch(
`https://astapa.com/api/platform/shop-payment/verify?order_id=${orderId}`
);
const { status, store_name, redirect_url } = await res.json();
// status: "pending" | "settlement" | "cancel" | "expire" | "refund"
// redirect_url: set if metadata.store_slug was providedThe payment callback page
After Midtrans payment, the buyer lands on astapa.com/purchase/callback?order_id=.... This page:
Shows a success, pending, or error state in Bahasa Indonesia
Polls every 3 seconds for up to 60 seconds if status is still pending
Auto-redirects back to your store if you passed metadata.store_slug
Pass store_slug for a seamless return flow
If you include
metadata.store_slug, the callback page builds a redirect URL back to your store automatically. The buyer sees a success message, then gets sent home.Webhook on settlement
Shop payments fire the same item.purchased event as item purchases. The payload includes your metadata.
webhook-payload.jsonjson
{
"event": "item.purchased",
"order_id": "your-internal-order-uuid",
"item_id": "__direct__",
"item_name": "Nasi Goreng Spesial",
"buyer_email": "buyer@example.com",
"buyer_name": "John Doe",
"amount": 75000,
"currency": "IDR",
"paid_at": "2026-05-06T10: 00: 00Z",
"metadata": {
"store_id": "store-uuid",
"store_name": "Warung Bu Sari",
"store_slug": "warung-bu-sari",
"product_id": "product-uuid"
}
}item_id will be __direct__
Shop payments use a sentinel item internally. The
item_id in the webhook will always be __direct__ โ use your metadata.product_id to identify what was purchased.Testing in sandbox
Use your sandbox client_id and client_secret. Midtrans sandbox is selected automatically.
| Method | Test credentials |
|---|---|
| Credit card | 4811 1111 1111 1114 ยท any future expiry ยท CVV 123 |
| GoPay / QRIS | Auto-approve in Midtrans sandbox dashboard |
Related