Menu docs

Fitur & Build Notes · · 3 menit baca

Fitur e-commerce: alur checkout Midtrans Snap yang benar

Alur pembayaran Midtrans dari ujung ke ujung: buat transaksi Snap di server (Server Key aman), popup snap.pay di browser, dan webhook dengan verifikasi signature SHA-512 sebagai satu-satunya sumber kebenaran.

MidtransPaymentsWebhooksSecurity
Contoh liveDirender langsung oleh komponennya — bukan gambar.

Ringkasan pesanan

Kopi Gayo Premium 250g × 2Rp 170.000
Ongkir — JNE REGRp 18.000
TotalRp 188.000

Metode pembayaran

VA BCAVA MandiriQRISGoPayKartu Kredit

Popup Snap terbuka di sini · status final ditentukan webhook

Midtrans Snap adalah cara tercepat menerima pembayaran lokal (VA semua bank, QRIS, GoPay, OVO, kartu) di toko online Indonesia — inilah gateway yang kupasang di NexaMart dan build klien. Alurnya tiga langkah: server membuat transaksi, browser membuka popup Snap, dan webhook mengonfirmasi pembayaran. Klik Lihat preview untuk melihat UI ringkasan checkout-nya.

Arsitektur (dan aturan emasnya)

Browser ──POST /checkout──▶ Server ──(SERVER KEY)──▶ Midtrans ──▶ snap token
Browser ◀── token ─────────┘
Browser ──snap.pay(token)──▶ popup pembayaran Midtrans
Midtrans ──webhook──▶ Server: verifikasi signature ──▶ tandai pesanan LUNAS

Aturan emas #1: Server Key tidak boleh menyentuh browser. Aturan emas #2: status “lunas” hanya boleh ditulis oleh webhook yang terverifikasi — bukan oleh callback JavaScript (user bisa menutup popup, koneksi bisa putus, dan callback bisa dipalsukan).

1. Server: buat transaksi Snap

// POST /checkout — berjalan di server (contoh: Cloudflare Workers / Node)
const AUTH = "Basic " + btoa(env.MIDTRANS_SERVER_KEY + ":");   // perhatikan ":" di akhir

const resp = await fetch("https://app.sandbox.midtrans.com/snap/v1/transactions", {
  method: "POST",
  headers: { Authorization: AUTH, "Content-Type": "application/json" },
  body: JSON.stringify({
    transaction_details: {
      order_id: `ORDER-${crypto.randomUUID()}`,   // unik — Midtrans menolak duplikat
      gross_amount: 170000,                        // total dalam Rupiah, tanpa desimal
    },
    item_details: [
      { id: "kopi-gayo-250", name: "Kopi Gayo 250g", price: 85000, quantity: 2 },
    ],
    customer_details: { first_name: "Budi", email: "budi@mail.com", phone: "0812..." },
  }),
});

const { token } = await resp.json();
return c.json({ token });   // hanya token yang boleh ke browser

Ganti app.sandbox.midtrans.comapp.midtrans.com saat production.

2. Browser: buka popup Snap

<!-- sandbox; production: https://app.midtrans.com/snap/snap.js -->
<script src="https://app.sandbox.midtrans.com/snap/snap.js"
        data-client-key="SB-Mid-client-xxx"></script>

<script>
  async function bayar() {
    const { token } = await fetch("/checkout", { method: "POST" }).then(r => r.json());
    window.snap.pay(token, {
      onSuccess:  () => location.href = "/pesanan/terima-kasih",
      onPending:  () => location.href = "/pesanan/menunggu",   // VA: user bayar belakangan
      onClose:    () => {/* user menutup popup — biarkan, jangan tandai gagal */},
    });
  }
</script>

Callback ini hanya untuk navigasi UX — bukan sumber kebenaran status.

3. Webhook: satu-satunya sumber kebenaran

Midtrans mengirim POST ke URL notifikasi-mu. Verifikasi tanda tangannya sebelum percaya:

// POST /webhook/midtrans
const n = await c.req.json();

// signature_key = SHA512(order_id + status_code + gross_amount + ServerKey)
const raw = n.order_id + n.status_code + n.gross_amount + env.MIDTRANS_SERVER_KEY;
const hash = [...new Uint8Array(
  await crypto.subtle.digest("SHA-512", new TextEncoder().encode(raw)),
)].map((b) => b.toString(16).padStart(2, "0")).join("");

if (hash !== n.signature_key) return c.text("invalid signature", 403);

const lunas = n.transaction_status === "settlement" ||
  (n.transaction_status === "capture" && n.fraud_status === "accept");

if (lunas) await tandaiLunas(n.order_id);   // idempotent — webhook bisa terkirim ulang
return c.text("ok");

Buat handler-nya idempotent: Midtrans bisa mengirim notifikasi yang sama lebih dari sekali.

Pelajaran

  • Uji penuh di sandbox (kartu & VA simulator Midtrans tersedia) sebelum menyentuh mode production.
  • order_id adalah kunci penghubung semua sistem — format konsisten, simpan di database sejak sebelum popup terbuka.
  • Status pesanan punya lebih dari dua nilai: pending (VA belum dibayar) adalah status normal, bukan error — desain UI-mu untuk itu.

Mau fitur seperti ini dibangun untuk bisnismu?