Files
api-wipay/QRIS_SPEC_IMPLEMENTATION.md

278 lines
6.7 KiB
Markdown

# QRIS Dynamic Payment Implementation - Sesuai Spec
## ✅ Implementasi Sesuai Task Specification
### 1. Environment Variables ✅
```ini
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_code``no_trx`
-`qris_invoiceid``qris_invoiceid`
-`qris_content``qris_qr_code`
-`qris_request_date``qris_request_date`
-`amount``jumlah_tagihan + biaya_admin`
-`status: unpaid``qris_status = 'unpaid'`
-`created_at``tanggal_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:**
```json
{
"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:**
```json
{
"token": "...",
"no_sl": "...",
"payment_method": "qris"
}
```
**Response (QRIS):**
```json
{
"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:**
```json
{
"token": "...",
"no_sl": "..."
}
```
**Response (Paid):**
```json
{
"status": 200,
"pesan": "Pembayaran berhasil",
"data": {
"status": "paid",
"payment_method": "gopay",
"customer_name": "John Doe"
}
}
```
**Response (Unpaid - Max Attempts):**
```json
{
"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
```