Midtrans-Middleware/docs/payment-link-e2e.md

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 /createtransaksi dan menerima url serta token.
  • Frontend membuka halaman Pay di route pay/:token, lalu me-resolve token via GET /api/payment-links/:token.
  • Pengguna memilih metode pembayaran (sesuai allowed_methods dan toggle runtime), kemudian frontend memanggil POST /api/payments/charge.
  • Status pembayaran dapat dicek via GET /api/payments/:orderId/status dan disinkronkan via webhook POST /api/payments/webhook.

Prasyarat & Konfigurasi

  • Backend: jalankan node server/index.cjs (default port 8000).
  • Frontend: jalankan npm run dev (contoh dev port: 5175).
  • Penyesuaian environment penting:
    • EXTERNAL_API_KEY: API Key luar untuk POST /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 halaman Pay (default: http://localhost:5174/pay). Sesuaikan ke port frontend yang aktif (misal http://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 jika EXTERNAL_API_KEY tidak 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.
  • 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).
  • 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.
  • GET /api/health, GET/POST /api/config

    • Health: cek ketersediaan key dan environment.
    • Config: baca/ubah toggles (dev-only untuk POST).
  • Token adalah base64url(JSON) dengan fields minimal: { v, order_id, nominal, expire_at, sig, customer?, allowed_methods? }.
  • sig adalah HMAC SHA-256 dari string kanonik: "order_id|nominal|expire_at" menggunakan PAYMENT_LINK_SECRET.

Langkah QA (Contoh)

  1. 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"]
  }'
  1. Resolve Token
Invoke-RestMethod -Method GET -Uri "http://localhost:8000/api/payment-links/<token>" | ConvertTo-Json -Depth 5
  1. Buka Halaman Pay
  • Buka http://localhost:5175/pay/<token> (sesuaikan PAYMENT_LINK_BASE dengan port frontend).
  • Periksa daftar metode, panel, serta batasan dari allowed_methods dan runtime toggles.
  1. 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
  1. 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).

  1. Status Check
Invoke-RestMethod -Method GET -Uri 'http://localhost:8000/api/payments/INV-PL-001/status' | ConvertTo-Json -Depth 5
  1. 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): set PAYMENT_LINK_BASE=http://localhost:5175/pay di backend agar URL link mengarah ke port yang benar.
  • 400 Bad Request untuk GoPay/QR:
    • Pastikan gopay payload memenuhi kebutuhan sandbox (mis. enable_qr, kadang perlu qr_black_white, atau callback_url).
    • Periksa toggles runtime (/api/config) dan ketersediaan Midtrans keys.
    • Beberapa merchant sandbox memiliki batasan; rujuk dokumentasi Midtrans untuk parameter terbaru.
  • UNAUTHORIZED saat createtransaksi: set header X-API-KEY sesuai EXTERNAL_API_KEY jika dikonfigurasi.
  • ORDER_ACTIVE atau ORDER_COMPLETED: backend menjaga activeOrders dan notifiedOrders untuk mencegah duplikasi; tunggu TTL atau gunakan order baru.

Integrasi Frontend

  • Route: pay/:token (lihat src/app/router.tsx).
  • Resolver: getPaymentLinkPayload(token) (lihat src/services/api.ts).
  • Toggle & Allowed Methods: PayPage menggabungkan runtimeCfg.paymentToggles dengan allowed_methods. Kunci metode: bank_transfer, credit_card, gopay, cstore, cpay.

Notifikasi ERP

  • Di settlement sukses, backend menghitung signature ERP (sha512) dan mengirim payload ke ERP_NOTIFICATION_URL jika ERP_ENABLE_NOTIF=true dan konfigurasi lengkap.

Postman Collection

  • Anda dapat mengimpor koleksi: docs/qa/payment-link.postman_collection.json untuk 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:8000
    • paymentLinkBase: http://localhost:5175/pay (sesuaikan port frontend)
    • externalApiKey: kosong atau set sesuai EXTERNAL_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. 1) Create Transaction
    • Body default: item_id, nominal, customer, allowed_methods.
    • Jika EXTERNAL_API_KEY aktif, pastikan header X-API-KEY: {{externalApiKey}} terisi.
  2. 2) Resolve Token
    • Memakai {{token}} dari langkah 1.
  3. 3) Charge - Bank Transfer (BCA) atau 3) Charge - CStore (Indomaret)
    • Memakai {{order_id}} dari langkah 1.
  4. 4) Payment Status
    • Memakai {{order_id}} untuk cek status.

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 Transaction2) Resolve Token3) Charge4) Status.
  • Pastikan environment dipilih. Anda dapat menambah delay antar request jika diperlukan.

8) Tips & Variabel Tambahan

  • Sesuaikan paymentLinkBase ke port frontend aktif (mis. 5175).
  • Jika EXTERNAL_API_KEY aktif, isi externalApiKey di environment.
  • Simpan server_key (Midtrans Server Key) di environment untuk uji webhook.
  • Tambahkan gross_amount, status_code variabel supaya mudah dikustom saat uji webhook/status.