- ✅
- ✅ - ✅ - ✅
This commit is contained in:
@@ -42,6 +42,11 @@ export const MIDTRANS = {
|
||||
isProduction()
|
||||
? "https://app.midtrans.com/snap/snap.js"
|
||||
: "https://app.sandbox.midtrans.com/snap/snap.js",
|
||||
/** Core API base — dipakai untuk GET /v2/{order_id}/status (rekonsiliasi). */
|
||||
coreApiBase: () =>
|
||||
isProduction()
|
||||
? "https://api.midtrans.com/v2"
|
||||
: "https://api.sandbox.midtrans.com/v2",
|
||||
};
|
||||
|
||||
function requireServerKey(): string {
|
||||
@@ -70,6 +75,9 @@ interface SnapTransactionPayload {
|
||||
itemName: string;
|
||||
/// Berapa detik sampai expire. Default Midtrans 24 jam, kita pakai itu kalau undefined.
|
||||
expirySeconds?: number;
|
||||
/// URL absolut untuk redirect user setelah selesai bayar (success / pending / error).
|
||||
/// Tanpa ini, Midtrans pakai default `example.com`.
|
||||
finishUrl?: string;
|
||||
}
|
||||
|
||||
export interface SnapTransactionResult {
|
||||
@@ -110,6 +118,10 @@ export async function createSnapTransaction(
|
||||
};
|
||||
}
|
||||
|
||||
if (payload.finishUrl) {
|
||||
body.callbacks = { finish: payload.finishUrl };
|
||||
}
|
||||
|
||||
const res = await fetch(`${MIDTRANS.snapApiBase()}/transactions`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -137,6 +149,67 @@ export async function createSnapTransaction(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Bentuk minimal response dari Midtrans Core API GET /v2/{order_id}/status.
|
||||
* Sub-set field yang kita pakai untuk rekonsiliasi (sama dengan field webhook).
|
||||
* https://docs.midtrans.com/reference/get-transaction-status
|
||||
*/
|
||||
export interface MidtransTransactionStatus {
|
||||
order_id: string;
|
||||
status_code: string;
|
||||
transaction_status: string;
|
||||
gross_amount: string;
|
||||
transaction_id?: string;
|
||||
payment_type?: string;
|
||||
fraud_status?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch status transaksi langsung dari Midtrans untuk rekonsiliasi server-side.
|
||||
* Dipakai saat kita tidak bisa mengandalkan webhook (mis. dev di localhost,
|
||||
* atau webhook tertunda). Auth pakai server key — response sudah terpercaya
|
||||
* karena datang dari Midtrans atas request kita, jadi tidak perlu verifikasi
|
||||
* signature.
|
||||
*
|
||||
* Return null kalau Midtrans tidak menemukan order (404).
|
||||
*/
|
||||
export async function fetchMidtransTransactionStatus(
|
||||
orderId: string
|
||||
): Promise<MidtransTransactionStatus | null> {
|
||||
const res = await fetch(
|
||||
`${MIDTRANS.coreApiBase()}/${encodeURIComponent(orderId)}/status`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: basicAuthHeader(),
|
||||
},
|
||||
cache: "no-store",
|
||||
}
|
||||
);
|
||||
|
||||
if (res.status === 404) return null;
|
||||
|
||||
const json = (await res.json().catch(() => null)) as
|
||||
| (Partial<MidtransTransactionStatus> & { status_message?: string })
|
||||
| null;
|
||||
|
||||
if (!res.ok || !json?.order_id || !json.transaction_status) {
|
||||
const reason = json?.status_message ?? `HTTP ${res.status}`;
|
||||
throw new Error(`Midtrans status fetch gagal: ${reason}`);
|
||||
}
|
||||
|
||||
return {
|
||||
order_id: json.order_id,
|
||||
status_code: json.status_code ?? String(res.status),
|
||||
transaction_status: json.transaction_status,
|
||||
gross_amount: json.gross_amount ?? "0",
|
||||
transaction_id: json.transaction_id,
|
||||
payment_type: json.payment_type,
|
||||
fraud_status: json.fraud_status ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifikasi signature webhook Midtrans.
|
||||
* Formula: SHA512(order_id + status_code + gross_amount + serverKey).
|
||||
|
||||
Reference in New Issue
Block a user