Items & Purchases
Items are one-time purchasable things — templates, credits, digital goods, access passes. Register them once, let buyers pay through hosted Midtrans checkout, get a webhook when money lands.
Items vs subscriptions vs shop payments
| Type | Use when |
|---|---|
| Items | You have a catalog of things to sell. Register items, buyers pick one, pay the listed price (or a dynamic override). |
| Subscriptions | You have pricing tiers. Payment auto-assigns a plan claim to the user's JWT. |
| Shop payments | You manage your own catalog. Just pass an amount and order ID — no item registration needed. |
How it works
Create items via the builder API or dashboard. Set a name, price, and optional webhook URL.
Call the public purchase endpoint with the buyer's email and item ID. Get back a Midtrans checkout URL.
When payment settles, we POST an item.purchased event to your webhook URL.
Create an item
Items are created via the builder API (requires your session cookie) or directly in the dashboard under Items → Catalog.
const res = await fetch("https://astapa.com/api/platform/items", {
method: "PUT",
headers: { "Content-Type": "application/json" },
credentials: "include", // builder session cookie
body: JSON.stringify({
project_id: "your_project_id",
name: "Premium Template",
description: "A professionally designed template",
price: 50000, // IDR, stored as the default price
currency: "IDR",
webhook_url: "https://yourapp.com/webhooks/purchase", // optional
}),
});
const { item } = await res.json();
// item.id is what buyers will referencePublic endpoints (no auth)
These are called from your frontend or your buyer's browser. No credentials needed — just the client_id.
Dynamic pricing — override the price at checkout
Pass amount in the purchase request to override the item's stored price. Useful for donations, custom quotes, or variable-price products.
// Buyer is paying a custom amount for a "donation" item
const res = await fetch("https://astapa.com/api/platform/public-items/purchase", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: process.env.NEXT_PUBLIC_ASTAPA_CLIENT_ID,
item_id: "donation-item-id",
buyer_email: "donor@example.com",
amount: 150000, // buyer chose this amount — overrides item.price
metadata: { campaign: "ramadan-2026" },
}),
});
const { invoice_url } = await res.json();
window.location.href = invoice_url;amount, that's what gets charged and what shows up in the purchase record. The item's price field is only used as a fallback when amount is omitted.Webhook on settlement
When a purchase settles, we POST to the webhook URL. Priority order: item-level webhook_url → project-level webhook_url. If neither is set, no webhook fires.
{
"event": "item.purchased",
"order_id": "item-abc12345-1a2b3c4d",
"item_id": "item-uuid",
"item_name": "Premium Template",
"buyer_email": "buyer@example.com",
"buyer_name": "John Doe",
"amount": 50000,
"currency": "IDR",
"paid_at": "2026-05-06T10: 00: 00Z",
"metadata": { "ref": "campaign-42" }
}View purchases
failed, hit Retry. Or call POST /api/platform/projects/[id]/item-purchases/[purchaseId]/retry-webhook.Related