Files
api-wipay/QRIS_SPEC_IMPLEMENTATION.md

6.7 KiB

QRIS Dynamic Payment Implementation - Sesuai Spec

Implementasi Sesuai Task Specification

1. Environment Variables

QRIS_API_KEY=139139250221910
QRIS_MID=126670220
QRIS_NMID=ID1025466699168
QRIS_BASE_URL=https://qris.interactive.co.id/restapi/qris
QRIS_EXPIRED_MINUTES=30

2. API Generate Invoice

Endpoint: GET {QRIS_BASE_URL}/qris/show_qris.php

Parameters:

  • do: "create-invoice"
  • apikey: "{QRIS_API_KEY}"
  • mID: "{QRIS_MID}"
  • cliTrxNumber: "{invoice_code}" (no_trx)
  • cliTrxAmount: "{total_amount}"
  • useTip: "no"

Implementation: QrisHelper::createInvoice()

Validasi:

  • Minimum amount: Rp 100
  • Maximum untuk QRIS: Rp 70.000

3. Invoice Handling

Store to Database:

  • invoice_codeno_trx
  • qris_invoiceidqris_invoiceid
  • qris_contentqris_qr_code
  • qris_request_dateqris_request_date
  • amountjumlah_tagihan + biaya_admin
  • status: unpaidqris_status = 'unpaid'
  • created_attanggal_request

UI Display:

  • QR Code content tersedia di response
  • NMID tersedia di response
  • Expired countdown: 30 menit

4. Expired Policy

Expired After: 30 menit (dari qris_request_date)

Behavior:

  • Mark status: expired
  • Disable scan: User tidak bisa check status lagi

Implementation:

  • Check di cekStatusQris() sebelum check status
  • Update qris_status = 'expired' jika expired

5. API Check Status

Endpoint: GET {QRIS_BASE_URL}/qris/checkpaid_qris.php

Parameters:

  • do: "checkStatus"
  • apikey: "{QRIS_API_KEY}"
  • mID: "{QRIS_MID}"
  • invid: "{qris_invoiceid}"
  • trxvalue: "{total_amount}"
  • trxdate: "YYYY-MM-DD" (dari qris_request_date)

Implementation: QrisHelper::checkStatus()

Response Handling:

  • status: success → Check qris_status
  • qris_status: paid → Auto approve
  • qris_status: unpaid → Continue checking

6. Status Check Policy

Request Timeout: 15 seconds

Retry Rule:

  • Max attempts: 3
  • Retry interval: 15 seconds
  • Total max duration: 45 seconds (3 x 15)

Implementation: QrisHelper::checkStatusWithRetry()

Logic:

  1. Lakukan request checkStatus
  2. Jika qris_status == 'paid' → Return immediately
  3. Jika qris_status == 'unpaid' → Wait 15 seconds, retry
  4. Hentikan jika attempts >= 3

Allowed Triggers:

  • User click check payment button (/timo/cek_status_qris)
  • Controlled backend process (future: cronjob)

Forbidden Patterns:

  • Continuous polling
  • Interval under 15 seconds
  • Auto check on page refresh
  • Infinite loop check

7. Fallback If Still Unpaid

After 3 Attempts:

  • Show upload payment proof form (show_upload_proof: true)
  • Show contact CS (show_contact_cs: true)
  • Store customer phone number (via user data)
  • Mark transaction status: pending_verification

Response Format:

{
  "status": 200,
  "pesan": "Silahkan upload bukti pembayaran atau hubungi customer service",
  "data": {
    "status": "pending_verification",
    "check_count": 3,
    "message": "Pembayaran belum terdeteksi setelah 3x pengecekan...",
    "show_upload_proof": true,
    "show_contact_cs": true
  }
}

8. Database Schema

File: database/qris_migration.sql

Fields:

  • qris_qr_code TEXT
  • qris_invoiceid VARCHAR(100)
  • qris_nmid VARCHAR(100)
  • qris_request_date DATETIME
  • qris_expired_at DATETIME
  • qris_check_count INT DEFAULT 0
  • qris_last_check_at DATETIME
  • qris_status ENUM('unpaid', 'paid', 'expired')
  • qris_payment_method VARCHAR(50)
  • qris_payment_customer_name VARCHAR(255)
  • qris_paid_at DATETIME

Indexes:

  • idx_qris_invoiceid
  • idx_qris_status
  • idx_qris_expired_at

9. Auto Approve Flow

Setelah QRIS Paid:

  1. Update qris_status = 'paid'
  2. Store payment method & customer name
  3. Call TIMO API: payment/{token}
  4. Update status_bayar = 'DIBAYAR'
  5. Set tanggal_bayar & jumlah_bayar
  6. Send WhatsApp notification ke user

10. Webhook (Future)

Architecture Ready:

  • Endpoint planned: /api/webhook/qris
  • Whitelist domain: https://timo.wipay.id
  • Future use: Auto update status, validate signature, prevent double payment

📋 Endpoints

POST /timo/request_pembayaran

Support QRIS:

{
  "token": "...",
  "no_sl": "...",
  "payment_method": "qris"
}

Response (QRIS):

{
  "status": 200,
  "data": {
    "no_trx": "#TIMO...",
    "qris_qr_code": "000201010212...",
    "qris_invoiceid": 123456,
    "qris_nmid": "ID1025466699168",
    "qris_request_date": "2025-01-15 14:00:00",
    "qris_expired_at": "2025-01-15 14:30:00",
    "qris_status": "unpaid"
  }
}

POST /timo/cek_status_qris

Check Status dengan Retry:

{
  "token": "...",
  "no_sl": "..."
}

Response (Paid):

{
  "status": 200,
  "pesan": "Pembayaran berhasil",
  "data": {
    "status": "paid",
    "payment_method": "gopay",
    "customer_name": "John Doe"
  }
}

Response (Unpaid - Max Attempts):

{
  "status": 200,
  "pesan": "Silahkan upload bukti pembayaran...",
  "data": {
    "status": "pending_verification",
    "check_count": 3,
    "show_upload_proof": true,
    "show_contact_cs": true
  }
}

Compliance dengan Constraints

  1. API LIVE/PRODUCTION - Menggunakan credentials production
  2. Real Payment - Scan QRIS akan memotong saldo e-wallet
  3. No Auto Refund - Tidak ada refund otomatis
  4. No Aggressive Polling - Max 3 attempts dengan interval 15 detik

🎯 Deliverables

  • QRIS Invoice Generator Service (QrisHelper::createInvoice())
  • QR Code Renderer (content tersedia di response)
  • Transaction Persistence Layer (database fields)
  • Manual Status Checker (cekStatusQris() dengan retry)
  • Webhook-ready Architecture (endpoint planned)

📝 Testing Checklist

  • Test generate invoice untuk amount < 70rb
  • Test generate invoice untuk amount > 70rb (should fail)
  • Test check status (unpaid) - max 3 attempts
  • Test check status (paid) - auto approve
  • Test expired QRIS (after 30 minutes)
  • Test WhatsApp notification setelah paid
  • Test fallback setelah 3 attempts failed

🔄 Flow Diagram

User Request QRIS (< 70rb)
  ↓
Generate Invoice via API
  ↓
Store: qris_content, qris_invoiceid, qris_request_date
  ↓
Display QR Code (30 menit countdown)
  ↓
User Scan & Pay
  ↓
User Click "Check Status"
  ↓
Check Status (max 3x, interval 15s)
  ↓
If Paid → Auto Approve → WhatsApp
If Unpaid (after 3x) → Show Upload Proof Form