13 KiB
Panduan QA End-to-End: Payment Link
Dokumen ini menjelaskan alur end-to-end Payment Link yang tersedia di proyek, mencakup konfigurasi, endpoint backend, format token, langkah QA dengan contoh request, hingga troubleshooting dan integrasi frontend.
Ringkasan Alur
- ERP/eksternal membuat Payment Link via
POST /createtransaksidan menerimaurlsertatoken. - Frontend membuka halaman
Paydi routepay/:token, lalu me-resolve token viaGET /api/payment-links/:token. - Pengguna memilih metode pembayaran (sesuai
allowed_methodsdan toggle runtime), kemudian frontend memanggilPOST /api/payments/charge. - Status pembayaran dapat dicek via
GET /api/payments/:orderId/statusdan disinkronkan via webhookPOST /api/payments/webhook.
Prasyarat & Konfigurasi
- Backend: jalankan
node server/index.cjs(default port8000). - Frontend: jalankan
npm run dev(contoh dev port:5175). - Penyesuaian environment penting:
EXTERNAL_API_KEY: API Key luar untukPOST /createtransaksi. Jika tidak diset, di dev akan diizinkan tanpa key.PAYMENT_LINK_SECRET: secret untuk penandatanganan token Payment Link (HMAC SHA-256). Default dev:dev-secret.PAYMENT_LINK_TTL_MINUTES: waktu kedaluwarsa token (default:30).PAYMENT_LINK_BASE: base URL untuk halamanPay(default:http://localhost:5174/pay). Sesuaikan ke port frontend yang aktif (misalhttp://localhost:5175/pay).PORT: port backend (default:8000).ERP_NOTIFICATION_URL,ERP_CLIENT_ID,ERP_MERCANT_ID,ERP_ENABLE_NOTIF: konfigurasi notifikasi ERP saat settlement.- Midtrans keys: Server Key dan Client Key harus tersedia untuk charge/status.
- Frontend env:
VITE_API_BASE_URL(contoh:http://localhost:8000/api),VITE_MIDTRANS_CLIENT_KEY.
Endpoint Backend
-
POST /createtransaksi- Header:
X-API-KEY(opsional di dev jikaEXTERNAL_API_KEYtidak diset). - Body:
{ item_id | order_id, nominal, customer?, allowed_methods? } - Respon:
{ url, token, order_id, nominal, expire_at } - Error:
UNAUTHORIZED,BAD_REQUEST,ORDER_COMPLETED,ORDER_ACTIVE,CREATE_ERROR.
- Header:
-
GET /api/payment-links/:token- Respon:
{ order_id, nominal, customer?, expire_at?, allowed_methods? } - Error:
410 TOKEN_EXPIRED,400 INVALID_*(di dev ada fallback payload jika token invalid).
- Respon:
-
POST /api/payments/charge- Body: payload Midtrans (contoh di bawah). Diblokir jika method dimatikan oleh runtime toggles.
- Error:
PAYMENT_TYPE_DISABLED,CHARGE_ERROR.
-
GET /api/payments/:orderId/status- Respon: pass-through dari Midtrans (transaction status, VA, dll.).
-
POST /api/payments/webhook- Verifikasi signature:
sha512(orderId + statusCode + grossAmount + serverKey). - Pada sukses (settlement atau capture+accept untuk kartu), backend kirim notifikasi ke ERP (jika diaktifkan) dan menandai order sebagai completed.
- Verifikasi signature:
-
GET /api/health,GET/POST /api/config- Health: cek ketersediaan key dan environment.
- Config: baca/ubah toggles (dev-only untuk
POST).
Format Token Payment Link
- Token adalah
base64url(JSON)dengan fields minimal:{ v, order_id, nominal, expire_at, sig, customer?, allowed_methods? }. sigadalah HMAC SHA-256 dari string kanonik:"order_id|nominal|expire_at"menggunakanPAYMENT_LINK_SECRET.
Langkah QA (Contoh)
- Buat Payment Link
PowerShell (Windows):
$body = @{
item_id = 'INV-PL-001';
nominal = 150000;
customer = @{ name='QA Tester'; phone='081234567890'; email='qa@example.com' };
allowed_methods = @('bank_transfer','cstore','gopay','credit_card')
} | ConvertTo-Json -Depth 5;
Invoke-RestMethod -Method POST -Uri 'http://localhost:8000/createtransaksi' -ContentType 'application/json' -Body $body | ConvertTo-Json -Depth 5
curl:
curl -X POST http://localhost:8000/createtransaksi \
-H 'Content-Type: application/json' \
-H 'X-API-KEY: <jika-diperlukan>' \
-d '{
"item_id":"INV-PL-001",
"nominal":150000,
"customer": {"name":"QA Tester","phone":"081234567890","email":"qa@example.com"},
"allowed_methods":["bank_transfer","cstore","gopay","credit_card"]
}'
- Resolve Token
Invoke-RestMethod -Method GET -Uri "http://localhost:8000/api/payment-links/<token>" | ConvertTo-Json -Depth 5
- Buka Halaman Pay
- Buka
http://localhost:5175/pay/<token>(sesuaikanPAYMENT_LINK_BASEdengan port frontend). - Periksa daftar metode, panel, serta batasan dari
allowed_methodsdan runtime toggles.
- Charge Bank Transfer (BCA)
$bt = @{
payment_type = 'bank_transfer';
transaction_details = @{ order_id = 'INV-PL-001'; gross_amount = 150000 };
bank_transfer = @{ bank = 'bca' }
} | ConvertTo-Json -Depth 5;
Invoke-RestMethod -Method POST -Uri 'http://localhost:8000/api/payments/charge' -ContentType 'application/json' -Body $bt | ConvertTo-Json -Depth 5
- Charge GoPay/QR (opsional)
$qr = @{
payment_type = 'gopay';
transaction_details = @{ order_id = 'INV-PL-001'; gross_amount = 150000 };
gopay = @{ enable_qr = $true }
} | ConvertTo-Json -Depth 5;
Invoke-RestMethod -Method POST -Uri 'http://localhost:8000/api/payments/charge' -ContentType 'application/json' -Body $qr | ConvertTo-Json -Depth 5
Catatan: Bila 400 Bad Request, cek konfigurasi akun sandbox dan parameter GoPay/QRIS (lihat bagian troubleshooting).
- Status Check
Invoke-RestMethod -Method GET -Uri 'http://localhost:8000/api/payments/INV-PL-001/status' | ConvertTo-Json -Depth 5
- Webhook (uji manual)
Untuk uji manual, kirim payload menyerupai notifikasi Midtrans dengan signature_key yang valid. Signature dihitung:
// Node.js contoh perhitungan signature
const crypto = require('crypto')
function computeMidtransSignature(orderId, statusCode, grossAmount, secretKey) {
const raw = String(orderId) + String(statusCode) + String(grossAmount) + String(secretKey)
return crypto.createHash('sha512').update(raw).digest('hex')
}
Kemudian POST ke http://localhost:8000/api/payments/webhook dengan body berisi fields Midtrans (order_id, status_code, gross_amount, signature_key, dll.).
Troubleshooting
- Frontend tidak sesuai
PAYMENT_LINK_BASE(5174 vs 5175): setPAYMENT_LINK_BASE=http://localhost:5175/paydi backend agar URL link mengarah ke port yang benar. 400 Bad Requestuntuk GoPay/QR:- Pastikan
gopaypayload memenuhi kebutuhan sandbox (mis.enable_qr, kadang perluqr_black_white, ataucallback_url). - Periksa toggles runtime (
/api/config) dan ketersediaan Midtrans keys. - Beberapa merchant sandbox memiliki batasan; rujuk dokumentasi Midtrans untuk parameter terbaru.
- Pastikan
UNAUTHORIZEDsaatcreatetransaksi: set headerX-API-KEYsesuaiEXTERNAL_API_KEYjika dikonfigurasi.ORDER_ACTIVEatauORDER_COMPLETED: backend menjagaactiveOrdersdannotifiedOrdersuntuk mencegah duplikasi; tunggu TTL atau gunakan order baru.
Integrasi Frontend
- Route:
pay/:token(lihatsrc/app/router.tsx). - Resolver:
getPaymentLinkPayload(token)(lihatsrc/services/api.ts). - Toggle & Allowed Methods:
PayPagemenggabungkanruntimeCfg.paymentTogglesdenganallowed_methods. Kunci metode:bank_transfer,credit_card,gopay,cstore,cpay.
Notifikasi ERP
- Di settlement sukses, backend menghitung signature ERP (
sha512) dan mengirim payload keERP_NOTIFICATION_URLjikaERP_ENABLE_NOTIF=truedan konfigurasi lengkap.
Postman Collection
- Anda dapat mengimpor koleksi:
docs/qa/payment-link.postman_collection.jsonuntuk mencoba endpoint di atas.
Pengujian via Postman (Langkah Lengkap)
1) Import & Setup Environment
- Import koleksi:
docs/qa/payment-link.postman_collection.json. - Buat Environment (mis.
Midtrans-CIFO-Local) dengan variabel:baseUrl:http://localhost:8000paymentLinkBase:http://localhost:5175/pay(sesuaikan port frontend)externalApiKey: kosong atau set sesuaiEXTERNAL_API_KEY(jika diaktifkan)token: kosong (akan diisi dari respon create)order_id: kosong (akan diisi dari respon create)
Catatan: Koleksi memakai variabel ini pada URL/body. Pastikan environment terpilih saat menjalankan request.
2) Jalankan Koleksi (Urutan Dasar)
1) Create Transaction- Body default:
item_id,nominal,customer,allowed_methods. - Jika
EXTERNAL_API_KEYaktif, pastikan headerX-API-KEY: {{externalApiKey}}terisi.
- Body default:
2) Resolve Token- Memakai
{{token}}dari langkah 1.
- Memakai
3) Charge - Bank Transfer (BCA)atau3) Charge - CStore (Indomaret)- Memakai
{{order_id}}dari langkah 1.
- Memakai
4) Payment Status- Memakai
{{order_id}}untuk cek status.
- Memakai
3) Skrip Test untuk Otomatis Mengisi Variabel
Tambahkan skrip berikut pada tab Tests di request 1) Create Transaction agar token, order_id, dan paymentLinkUrl otomatis tersimpan ke variabel koleksi:
// Tests: 1) Create Transaction
pm.test('Create returns token and url', function () {
pm.response.to.have.status(200);
const json = pm.response.json();
pm.expect(json).to.have.property('token');
pm.expect(json).to.have.property('url');
pm.expect(json).to.have.property('order_id');
});
const res = pm.response.json();
pm.collectionVariables.set('token', res.token);
pm.collectionVariables.set('order_id', res.order_id);
pm.collectionVariables.set('paymentLinkUrl', res.url);
Tambahkan skrip sederhana di 2) Resolve Token untuk validasi payload:
pm.test('Resolve returns payload fields', function () {
pm.response.to.have.status(200);
const json = pm.response.json();
pm.expect(json).to.have.property('order_id');
pm.expect(json).to.have.property('nominal');
});
Di 3) Charge - Bank Transfer (BCA), tambahkan assert dasar:
pm.test('Charge is created (pending)', function () {
pm.response.to.have.status(200);
const json = pm.response.json();
pm.expect(json).to.have.property('status_code');
pm.expect(json.status_code).to.eql('201');
pm.expect(json.transaction_status).to.eql('pending');
});
Di 4) Payment Status, periksa status:
pm.test('Status returns transaction info', function () {
pm.response.to.have.status(200);
const json = pm.response.json();
pm.expect(json).to.have.property('order_id');
pm.expect(json).to.have.property('transaction_status');
});
4) Menambah Request GoPay/QR (Opsional)
Jika Anda ingin menguji GoPay/QR, tambahkan request baru di koleksi:
- Method:
POST - URL:
{{baseUrl}}/api/payments/charge - Body (raw JSON):
{
"payment_type": "gopay",
"transaction_details": { "order_id": "{{order_id}}", "gross_amount": 150000 },
"gopay": { "enable_qr": true }
}
Catatan: Bila menghasilkan 400 Bad Request, cek kembali konfigurasi sandbox merchant dan parameter tambahan yang dibutuhkan (misal qr_black_white, callback_url).
5) Membuka Halaman Pay dari Postman
Setelah 1) Create Transaction, variabel paymentLinkUrl tersimpan. Anda bisa klik tombol Open in Browser (ikon tautan) pada Postman untuk membuka {{paymentLinkUrl}} langsung di browser.
6) Uji Webhook dengan Pre-request Script
Tambahkan request baru Webhook (Manual) dengan URL {{baseUrl}}/api/payments/webhook. Isi body dengan fields Midtrans. Untuk menghitung signature_key otomatis, gunakan Pre-request Script berikut (CryptoJS tersedia di sandbox Postman):
// Pre-request: Webhook signature
const orderId = pm.collectionVariables.get('order_id');
const statusCode = pm.collectionVariables.get('status_code') || '200';
const grossAmount = pm.collectionVariables.get('gross_amount') || '150000';
const serverKey = pm.collectionVariables.get('server_key'); // set di environment
if (!serverKey) {
console.warn('server_key is not set in environment');
}
const raw = String(orderId) + String(statusCode) + String(grossAmount) + String(serverKey);
const sig = CryptoJS.SHA512(raw).toString(CryptoJS.enc.Hex);
pm.collectionVariables.set('signature_key', sig);
Body contoh (raw JSON):
{
"order_id": "{{order_id}}",
"status_code": "{{status_code}}",
"gross_amount": "{{gross_amount}}",
"signature_key": "{{signature_key}}",
"transaction_status": "settlement"
}
Tambahkan test sederhana untuk memverifikasi respons webhook:
pm.test('Webhook acknowledged', function () {
pm.response.to.have.status(200);
const json = pm.response.json();
pm.expect(json).to.have.property('ok');
pm.expect(json.ok).to.eql(true);
});
7) Postman Runner (Otomasi)
- Gunakan Runner untuk menjalankan berurutan:
1) Create Transaction→2) Resolve Token→3) Charge→4) Status. - Pastikan environment dipilih. Anda dapat menambah delay antar request jika diperlukan.
8) Tips & Variabel Tambahan
- Sesuaikan
paymentLinkBaseke port frontend aktif (mis.5175). - Jika
EXTERNAL_API_KEYaktif, isiexternalApiKeydi environment. - Simpan
server_key(Midtrans Server Key) di environment untuk uji webhook. - Tambahkan
gross_amount,status_codevariabel supaya mudah dikustom saat uji webhook/status.