# 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`). ## Format Token Payment Link - 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): ```powershell $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: ```bash curl -X POST http://localhost:8000/createtransaksi \ -H 'Content-Type: application/json' \ -H 'X-API-KEY: ' \ -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"] }' ``` 2) Resolve Token ```powershell Invoke-RestMethod -Method GET -Uri "http://localhost:8000/api/payment-links/" | ConvertTo-Json -Depth 5 ``` 3) Buka Halaman Pay - Buka `http://localhost:5175/pay/` (sesuaikan `PAYMENT_LINK_BASE` dengan port frontend). - Periksa daftar metode, panel, serta batasan dari `allowed_methods` dan runtime toggles. 4) Charge Bank Transfer (BCA) ```powershell $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 ``` 5) Charge GoPay/QR (opsional) ```powershell $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). 6) Status Check ```powershell Invoke-RestMethod -Method GET -Uri 'http://localhost:8000/api/payments/INV-PL-001/status' | ConvertTo-Json -Depth 5 ``` 7) Webhook (uji manual) Untuk uji manual, kirim payload menyerupai notifikasi Midtrans dengan `signature_key` yang valid. Signature dihitung: ```js // 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: ```javascript // 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: ```javascript 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: ```javascript 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: ```javascript 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): ```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): ```javascript // 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): ```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: ```javascript 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 `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.