<https://simulator.sandbox.midtrans.com/openapi/va/index?bank=bri>
create table public.orders (
id uuid not null default extensions.uuid_generate_v4 (),
user_id uuid null,
order_id text null,
amount integer null,
status text null default 'pending'::text,
snap_token text null,
created_at timestamp without time zone null default now(),
updated_at timestamp with time zone null default now(),
constraint orders_pkey primary key (id),
constraint orders_order_id_key unique (order_id)
) TABLESPACE pg_default;
<https://ufkkmgqkznzchxynvmfn.supabase.co/functions/v1/new-create-payment>
import { serve } from "<https://deno.land/std/http/server.ts>";
serve(async (req) => {
// ✅ Handle preflight request
if (req.method === "OPTIONS") {
return new Response("ok", {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
"Access-Control-Allow-Methods": "POST, OPTIONS",
},
});
}
const { order_id, amount } = await req.json();
const serverKey = Deno.env.get("MIDTRANS_SERVER_KEY");
const response = await fetch("<https://app.sandbox.midtrans.com/snap/v1/transactions>", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Basic " + btoa(serverKey + ":")
},
body: JSON.stringify({
transaction_details: {
order_id,
gross_amount: amount
}
})
});
const data = await response.json();
return new Response(JSON.stringify(data), {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*", // ✅ penting
}
});
});

link :
<https://ufkkmgqkznzchxynvmfn.supabase.co/functions/v1/midtrans-webhook>
import { serve } from "<https://deno.land/std/http/server.ts>";
serve(async (req) => {
try {
if (req.method !== "POST") {
return new Response("Method Not Allowed", { status: 405 });
}
const body = await req.json();
const {
order_id,
status_code,
gross_amount,
signature_key,
transaction_status,
fraud_status
} = body;
// ================================
// 1️⃣ VERIFY SIGNATURE (WAJIB)
// ================================
const serverKey = Deno.env.get("MIDTRANS_SERVER_KEY");
const dataToHash =
order_id + status_code + gross_amount + serverKey;
const hashBuffer = await crypto.subtle.digest(
"SHA-512",
new TextEncoder().encode(dataToHash)
);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const computedSignature = hashArray
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
if (computedSignature !== signature_key) {
console.error("❌ Invalid signature");
return new Response("Invalid signature", { status: 403 });
}
// ================================
// 2️⃣ MAP STATUS
// ================================
let newStatus = "pending";
switch (transaction_status) {
case "capture":
if (fraud_status === "challenge") {
newStatus = "challenge";
} else if (fraud_status === "accept") {
newStatus = "paid";
}
break;
case "settlement":
newStatus = "paid";
break;
case "pending":
newStatus = "pending";
break;
case "deny":
case "cancel":
newStatus = "failed";
break;
case "expire":
newStatus = "expired";
break;
case "refund":
case "chargeback":
newStatus = "refunded";
break;
}
// ================================
// 3️⃣ UPDATE DATABASE (SERVICE ROLE)
// ================================
const serviceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
const updateRes = await fetch(
`https://ufkkmgqkznzchxynvmfn.supabase.co/rest/v1/orders?order_id=eq.${order_id}`,
{
method: "PATCH",
headers: {
apikey: serviceRoleKey!,
Authorization: `Bearer ${serviceRoleKey}`,
"Content-Type": "application/json",
Prefer: "return=representation",
},
body: JSON.stringify({
status: newStatus,
updated_at: new Date().toISOString(),
}),
}
);
if (!updateRes.ok) {
const errText = await updateRes.text();
console.error("❌ DB Update Failed:", errText);
return new Response("Database update failed", { status: 500 });
}
console.log(`✅ Order ${order_id} updated to ${newStatus}`);
return new Response("OK", { status: 200 });
} catch (err) {
console.error("❌ Webhook error:", err);
return new Response("Internal Server Error", { status: 500 });
}
});
Pertanyaan bagus sekali 👌 ini sudah level arsitektur production.
Jawaban singkatnya: