EnakPoint can only be redeemed for vouchers now: it can no longer pay for orders and is never cashed out (docs/enakgame-prd.md §3.2, EG-001, EG-002). No order was ever paid with EnakPoint, so there is no data to move. Removed: - POST /customer/wallet/payment-code, POST /customer/orders/:id/pay-with-points and GET /orders/:id/point-payment/preview, with their processors, repositories, services, handlers and tests. - The point payment method type: paying, splitting and refunding with it, the outlet filter on the method list, and the system-method guard. - points and payment_code on CreatePayment; points_used and point_value on payments; accepts_point_payment on the customer outlets. - The outlet point_payment settings. A PUT that still sends them is rejected as an unknown field. - The EnakPoint split in the payment method analytics. - PAYMENT and PAYMENT_REFUND from the wallet type rules. Tests that used them as a generic EnakPoint debit use REWARD_REDEEM. - The EnakPoint-paid part from the earning basis, which is subtotal − discount again. Migration 000102 drops the trigger, the point methods and their index, the payments columns, and the outlet settings, and restores the method type CHECK without point. payments.payment_method_id is ON DELETE RESTRICT, so it fails rather than lose a payment made with EnakPoint. The integration docs list the removed endpoints and fields, and the EnakPoint & EnakCoin PRD and tasks note what is superseded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
381 lines
18 KiB
Markdown
381 lines
18 KiB
Markdown
# API EnakPoint & EnakCoin
|
||
|
||
30 Sep 2026
|
||
|
||
Semua endpoint EnakPoint (`POINT`, hanya untuk ditukar ke voucher) dan EnakCoin (`COIN`, untuk game dan ditukar ke EnakPoint) ada di bawah base URL `/api/v1`, memakai satu format response, dan semua jumlah berupa bilangan bulat.
|
||
|
||
> **Perubahan 7 Okt 2026:** bayar order dengan EnakPoint sudah dihapus, karena EnakPoint sekarang hanya bisa ditukar ke voucher: tidak bisa dipakai sebagai alat bayar dan tidak bisa dicairkan ([`enakgame-prd.md`](./enakgame-prd.md) §3.2). Endpoint dan field yang ikut dihapus ada di Referensi → Endpoint dan field yang dihapus.
|
||
|
||
## Konvensi umum
|
||
|
||
| Klien | Autentikasi | Prefix |
|
||
| --- | --- | --- |
|
||
| Customer app / self-order | `Authorization: Bearer <token customer>` | `/api/v1/customer` |
|
||
| POS | Token user (kasir/manager) | `/api/v1` |
|
||
| Dashboard | Token user, role Admin atau Manager | `/api/v1/marketing`, `/api/v1/outlets` |
|
||
|
||
**Format response.** Sukses: `{"success": true, "data": {…}, "errors": null}`. Gagal: `{"success": false, "data": null, "errors": [{"code": "304", "entity": "wallet_service", "cause": "…"}]}`. Tampilkan `cause` sebagai alasan penolakan.
|
||
|
||
| `code` | HTTP | Arti |
|
||
| --- | --- | --- |
|
||
| `303`, `310` | 400 | Body atau parameter tidak lengkap / salah format |
|
||
| `304` | 400 | Ditolak aturan bisnis (saldo kurang, di luar batas, dst.) |
|
||
| `404` | 404 | Tidak ditemukan, juga untuk data milik customer atau organisasi lain |
|
||
| `429` | 429 | OTP diminta ulang terlalu cepat |
|
||
| `PIN_NOT_SET` | 403 | Customer belum membuat PIN |
|
||
| `PIN_INVALID` | 400 | PIN salah |
|
||
| `PIN_LOCKED` | 423 | PIN terkunci 30 menit setelah 5 kali salah |
|
||
| `TRANSFER_BLOCKED` | 403 | Transfer ditahan 24 jam setelah reset PIN |
|
||
| `900` | 500 | Kesalahan server |
|
||
|
||
**Error PIN** membawa `data` yang tidak `null`: `{"code": "PIN_INVALID", "remaining_attempts": 3}`, `{"code": "PIN_LOCKED", "locked_until": "…"}`, atau `{"code": "TRANSFER_BLOCKED", "transfer_blocked_until": "…"}`. Endpoint yang menerima `pin` bisa mengembalikan salah satunya. PIN selalu dikirim sebagai string 6 digit.
|
||
|
||
**Idempotency.** Exchange dan transfer wajib header `Idempotency-Key` (maks. 50 karakter, `X-Idempotency-Key` juga diterima): satu key per percobaan, dan key yang sama dipakai ulang saat retry. Retry mengembalikan hasil pertama dengan `replayed: true`.
|
||
|
||
**Waktu.** Tanggal kedaluwarsa dan filter tanggal memakai WIB. Saldo berlaku sampai 23:59:59 WIB pada tanggal kedaluwarsanya.
|
||
|
||
## Customer app: saldo & riwayat
|
||
|
||
| Method | Path | Keterangan |
|
||
| --- | --- | --- |
|
||
| GET | `/customer/wallet` | Saldo, nilai rupiah, kedaluwarsa terdekat, 5 mutasi terakhir |
|
||
| GET | `/customer/wallet/transactions` | Riwayat mutasi, dengan pagination dan filter |
|
||
| GET | `/customer/wallet/expiring` | Saldo yang akan kedaluwarsa, per currency dan tanggal |
|
||
| PUT | `/customer/devices` | Daftarkan token FCM device |
|
||
| DELETE | `/customer/devices/:device_id` | Hapus device saat logout |
|
||
| GET | `/customer/outlets` | Outlet aktif di organisasi customer, dengan `earns_points`, `earns_coins` |
|
||
| GET | `/customer/orders` | Riwayat order customer (`page`, `limit`), dengan `points_earned` / `coins_earned` |
|
||
| GET | `/customer/orders/:id` | Detail order: item, pembayaran, EnakPoint/EnakCoin yang didapat; order customer lain → `404` |
|
||
|
||
Registrasi (`POST /customer-auth/register/start`) menerima `organization_id` opsional: bila tidak dikirim dan hanya ada satu organisasi, customer masuk ke organisasi itu. Contoh request dan response lengkap untuk outlet dan order ada di [`mobile-customer-enakpoint.md`](./mobile-customer-enakpoint.md) §4.4–§4.5.
|
||
|
||
### GET /customer/wallet
|
||
|
||
```json
|
||
{
|
||
"point_balance": 12500,
|
||
"coin_balance": 8,
|
||
"point_value": 1,
|
||
"point_discount_value": 12500,
|
||
"nearest_expiring": {
|
||
"point": { "amount": 150, "date": "2026-12-31" },
|
||
"coin": null
|
||
},
|
||
"recent_transactions": [ "… sama seperti item riwayat …" ]
|
||
}
|
||
```
|
||
|
||
- `point_balance` / `coin_balance` = saldo yang bisa dipakai sekarang.
|
||
- `point_discount_value` = `point_balance × point_value`; tampilkan sebagai "setara potongan Rp …", bukan saldo uang.
|
||
- `nearest_expiring.point` / `.coin` bernilai `null` bila tidak ada yang akan kedaluwarsa.
|
||
|
||
### GET /customer/wallet/transactions
|
||
|
||
| Query | Tipe | Keterangan |
|
||
| --- | --- | --- |
|
||
| `page` | int | Default 1 |
|
||
| `limit` | int | 1–100, default 20 |
|
||
| `currency` | `POINT` \| `COIN` | Opsional |
|
||
| `type` | string | Satu tipe atau beberapa dipisah koma, mis. `EARN,TRANSFER_IN` |
|
||
| `from`, `to` | `YYYY-MM-DD` | Tanggal WIB, inklusif |
|
||
|
||
```json
|
||
{
|
||
"data": [
|
||
{
|
||
"id": "…",
|
||
"currency": "POINT",
|
||
"type": "EARN",
|
||
"amount": 875,
|
||
"balance_after": 12500,
|
||
"description": "Belanja #ORD-0123 di Outlet Kemang",
|
||
"source": { "type": "ORDER", "id": "…" },
|
||
"outlet_id": "…",
|
||
"group_id": null,
|
||
"expires_at": "2026-12-31T23:59:59+07:00",
|
||
"lots": [{ "amount": 875, "remaining": 875, "expires_at": "2026-12-31T23:59:59+07:00" }],
|
||
"created_at": "2026-09-30T12:01:00Z"
|
||
}
|
||
],
|
||
"pagination": { "page": 1, "limit": 20, "total_count": 42, "total_pages": 3 }
|
||
}
|
||
```
|
||
|
||
`amount` bertanda (+ menambah, − mengurangi). Penambahan membawa `source`, pengurangan membawa `destination`, keduanya `{ type, id }`. Dua baris exchange atau transfer berbagi `group_id`. Daftar tipe ada di bagian Referensi.
|
||
|
||
### GET /customer/wallet/expiring
|
||
|
||
```json
|
||
{
|
||
"point": [
|
||
{ "amount": 150, "date": "2026-10-31" },
|
||
{ "amount": 200, "date": "2026-12-31" }
|
||
],
|
||
"coin": []
|
||
}
|
||
```
|
||
|
||
Terurut dari tanggal terdekat. Daftar kosong berarti tidak ada yang akan kedaluwarsa.
|
||
|
||
### PUT /customer/devices
|
||
|
||
```json
|
||
{ "device_id": "a1b2c3", "fcm_token": "…", "platform": "android", "app_version": "2.4.0" }
|
||
```
|
||
|
||
Panggil setelah login dan setiap kali FCM memberi token baru. `device_id` dan `fcm_token` wajib; `platform` = `android` | `ios` | `web`. Satu token hanya milik satu customer: customer lain yang mendaftarkan token yang sama mengambil alih HP itu. Response: `{ "device_id": "a1b2c3" }`.
|
||
|
||
## Customer app: PIN
|
||
|
||
PIN 6 digit wajib untuk exchange dan transfer; minta customer membuatnya saat pertama kali melakukan aksi itu.
|
||
|
||
| Method | Path | Body | Response |
|
||
| --- | --- | --- | --- |
|
||
| GET | `/customer/pin/status` | – | `{ "has_pin", "locked_until", "transfer_blocked_until" }` |
|
||
| POST | `/customer/pin/otp` | `{ "purpose": "pin_setup" }` atau `"pin_reset"` | `{ "purpose", "otp_token", "expires_at" }` |
|
||
| POST | `/customer/pin` | `{ "otp_token", "otp_code", "pin", "confirm_pin" }` | Status PIN |
|
||
| PUT | `/customer/pin` | `{ "old_pin", "pin", "confirm_pin" }` | Status PIN |
|
||
| POST | `/customer/pin/reset` | `{ "otp_token", "otp_code", "pin", "confirm_pin" }` | Status PIN |
|
||
|
||
1. **Buat PIN:** minta OTP dengan `purpose: "pin_setup"` (dikirim lewat WhatsApp), lalu `POST /customer/pin` dengan `otp_token` dari response OTP dan kode yang diterima customer.
|
||
2. **Lupa PIN:** minta OTP dengan `purpose: "pin_reset"`, lalu `POST /customer/pin/reset`. Reset membuka kunci PIN, tapi transfer keluar ditahan 24 jam; exchange tetap bisa.
|
||
3. **Ganti PIN:** `PUT /customer/pin` dengan PIN lama.
|
||
|
||
PIN baru ditolak `304` bila bukan 6 digit, konfirmasinya beda, semua digit sama (`111111`), berurutan (`123456`, `654321`), atau sama dengan tanggal lahir (`DDMMYY` / `YYMMDD`). OTP yang diminta terlalu cepat dijawab `429`. Penanganan `PIN_INVALID`, `PIN_LOCKED`, dan `TRANSFER_BLOCKED` ada di Konvensi umum.
|
||
|
||
## Customer app: exchange, transfer, game
|
||
|
||
| Method | Path | PIN | Idempotency-Key |
|
||
| --- | --- | --- | --- |
|
||
| GET | `/customer/wallet/exchange/preview?coins=` | – | – |
|
||
| POST | `/customer/wallet/exchange` | Ya | Wajib |
|
||
| GET | `/customer/wallet/transfer/recipient?phone=` | – | – |
|
||
| POST | `/customer/wallet/transfer` | Ya | Wajib |
|
||
| POST | `/customer/spin` | – | – |
|
||
|
||
### GET /customer/wallet/exchange/preview?coins=30
|
||
|
||
```json
|
||
{ "coin_amount": 10, "point_amount": 3, "coin_balance": 35, "coins": 30, "points": 9, "valid": true }
|
||
```
|
||
|
||
Kurs: `coin_amount` EnakCoin = `point_amount` EnakPoint (default 1 : 1). Bila `valid: false`, tampilkan `reason`.
|
||
|
||
### POST /customer/wallet/exchange
|
||
|
||
Body `{ "coins": 30, "pin": "482913" }`. Response:
|
||
|
||
```json
|
||
{
|
||
"group_id": "…",
|
||
"coins": 30,
|
||
"points": 9,
|
||
"coin_amount": 10,
|
||
"point_amount": 3,
|
||
"lots": [{ "amount": 9, "expires_at": "2026-12-31T23:59:59+07:00" }],
|
||
"coin_balance": 5,
|
||
"point_balance": 9,
|
||
"replayed": false
|
||
}
|
||
```
|
||
|
||
`coins` harus kelipatan `coin_amount`; jumlah yang salah ditolak `304` sebelum PIN dicek. Exchange tidak bisa dibatalkan. EnakPoint hasil tukar tidak bisa hidup lebih lama dari EnakCoin asalnya (lihat `lots`).
|
||
|
||
### GET /customer/wallet/transfer/recipient?phone=081234561234
|
||
|
||
```json
|
||
{ "name": "Bu*** Sa***", "phone_number": "08**-****-1234" }
|
||
```
|
||
|
||
Nomor di luar organisasi atau tidak terdaftar → `404`. Diri sendiri, customer walk-in, atau nonaktif → `304`.
|
||
|
||
### POST /customer/wallet/transfer
|
||
|
||
Body `{ "currency": "POINT", "amount": 120, "recipient_phone": "081234561234", "pin": "482913" }`. Response:
|
||
|
||
```json
|
||
{
|
||
"group_id": "…",
|
||
"currency": "POINT",
|
||
"amount": 120,
|
||
"recipient": { "name": "Bu*** Sa***", "phone_number": "08**-****-1234" },
|
||
"lots": [
|
||
{ "amount": 100, "expires_at": "2026-12-31T23:59:59+07:00" },
|
||
{ "amount": 20, "expires_at": null }
|
||
],
|
||
"balance": 30,
|
||
"replayed": false
|
||
}
|
||
```
|
||
|
||
`currency` = `POINT` atau `COIN`. Batas organisasi (transfer aktif, minimal, maksimal per transaksi, batas harian per currency yang reset tengah malam WIB) ditolak `304` sebelum PIN dicek. Transfer final. Saldo membawa tanggal kedaluwarsa aslinya ke penerima (`lots`), dan penerima mendapat push `WALLET_TRANSFER_IN`.
|
||
|
||
### POST /customer/spin
|
||
|
||
Body `{ "spin_id": "<id game>" }`. Memotong EnakCoin sebesar `metadata.coin_cost` game itu (default 1).
|
||
|
||
```json
|
||
{
|
||
"game_play": { "id": "…", "game_id": "…", "coins_used": 1, "created_at": "…" },
|
||
"prize_won": { "id": "…", "name": "Voucher 10rb" },
|
||
"coins_remaining": 7
|
||
}
|
||
```
|
||
|
||
EnakCoin kurang, game nonaktif, atau hadiah baru saja habis → `304`, tidak ada EnakCoin yang terpotong.
|
||
|
||
## POS: earning, void, dan refund
|
||
|
||
EnakPoint bukan payment method: tidak ada lagi tipe `point`, dan `POST /payments` memakai `amount` seperti pembayaran lain. Response order membawa `points_earned` dan `coins_earned` untuk struk. Saat order di-void atau direfund, EnakPoint dan EnakCoin yang didapat dari order itu ikut ditarik (`EARN_REVERSAL`); bila saldo sudah terpakai, ditarik sebanyak yang ada dan refund tetap jalan.
|
||
|
||
## Dashboard
|
||
|
||
Semua endpoint dashboard butuh role Admin atau Manager, dan semuanya dibatasi ke organisasi user yang login. Rincian layar ada di [`backoffice-enakpoint.md`](./backoffice-enakpoint.md).
|
||
|
||
| Method | Path | Keterangan |
|
||
| --- | --- | --- |
|
||
| GET, PUT | `/outlets/:outlet_id/loyalty-settings` | Earning EnakPoint dan EnakCoin per outlet |
|
||
| GET, PUT | `/marketing/loyalty-settings` | Nilai EnakPoint, kurs, transfer, kedaluwarsa (`?dry_run=true` untuk preview) |
|
||
| GET | `/marketing/loyalty-settings/history` | Riwayat perubahan setting (`page`, `limit`, `outlet_id`) |
|
||
| GET | `/marketing/customers/:id/wallet` | Saldo, lot aktif, riwayat dengan nama asli |
|
||
| POST | `/marketing/customers/:id/wallet/adjust` | Koreksi saldo manual |
|
||
| GET | `/marketing/wallet-transactions/:id/trace` | Telusuri asal saldo per butir |
|
||
| DELETE | `/marketing/customers/:id/pin` | Hapus PIN customer |
|
||
| GET | `/marketing/customers/:id/security-events` | Log keamanan PIN (`page`, `limit`) |
|
||
|
||
Pada kedua `PUT` setting, field yang tidak dikirim tetap memakai nilai sekarang; field yang tidak dikenal ditolak.
|
||
|
||
### /outlets/:outlet_id/loyalty-settings
|
||
|
||
```json
|
||
{
|
||
"point": { "enabled": true, "earn_mode": "PER_AMOUNT", "earn_per_amount": 100, "earn_value": 1, "earn_percent": 1, "min_order_amount": 0, "max_per_order": null },
|
||
"coin": { "enabled": true, "earn_mode": "PER_AMOUNT", "earn_per_amount": 25000, "earn_value": 1, "earn_percent": 1, "min_order_amount": 0, "max_per_order": null }
|
||
}
|
||
```
|
||
|
||
Response menambahkan `outlet_id`, `point_value`, `point_cashback_percent` (default di atas = 1%), dan `changes` pada PUT. `earn_mode` adalah `PER_AMOUNT` (setiap `earn_per_amount` rupiah mendapat `earn_value`) atau `PERCENTAGE` (`earn_percent` persen dari basis). Validasi: `earn_per_amount > 0`, `earn_value ≥ 0`, `earn_percent` 0–100 dengan maks. 2 angka desimal. Objek `point_payment` sudah dihapus; `PUT` yang masih mengirimnya ditolak `310` (field tidak dikenal).
|
||
|
||
### /marketing/loyalty-settings
|
||
|
||
```json
|
||
{
|
||
"point_value": 1,
|
||
"exchange": { "coin_amount": 1, "point_amount": 1 },
|
||
"transfer": { "enabled": true, "min_amount": 1, "max_per_transaction": null, "daily_limit": null },
|
||
"point_expiry": {
|
||
"enabled": false,
|
||
"mode": "FIXED_DATE",
|
||
"fixed_dates": ["12-31"],
|
||
"grace_months": 3,
|
||
"period": 12,
|
||
"unit": "MONTH",
|
||
"end_of_month": false,
|
||
"reminder_days": 7
|
||
},
|
||
"coin_expiry": { "…": "sama dengan point_expiry" }
|
||
}
|
||
```
|
||
|
||
| Field kedaluwarsa | Dipakai mode | Nilai |
|
||
| --- | --- | --- |
|
||
| `mode` | – | `FIXED_DATE` (hangus di tanggal tetap tiap tahun) atau `ROLLING` (umur sejak didapat) |
|
||
| `fixed_dates` | `FIXED_DATE` | `MM-DD`, boleh lebih dari satu; `02-29` ditolak |
|
||
| `grace_months` | `FIXED_DATE` | 0–24; saldo yang didapat kurang dari ini sebelum tanggal hangus ikut ke tanggal berikutnya |
|
||
| `period`, `unit` | `ROLLING` | ≥ 1, `DAY` atau `MONTH` |
|
||
| `end_of_month` | `ROLLING` | Dibulatkan ke akhir bulan |
|
||
| `reminder_days` | keduanya | Hari sebelum hangus untuk pengingat; 0 = tanpa pengingat |
|
||
|
||
Response menambahkan:
|
||
|
||
- `impact`: saldo beredar dan nilai rupiahnya sebelum/sesudah perubahan `point_value` atau kurs.
|
||
- `expiry_preview`: `{ "point", "coin" }`, kapan saldo yang didapat sekarang kedaluwarsa (`null` = tidak).
|
||
- `expiry_activations`: bila perubahan ini menyalakan kedaluwarsa pertama kali, `[{ "currency", "lots", "amount", "expires_at" }]` saldo lama yang ikut diberi tanggal.
|
||
- `changes` dan `dry_run`.
|
||
|
||
### POST /marketing/customers/:id/wallet/adjust
|
||
|
||
```json
|
||
{ "currency": "POINT", "amount": -500, "reason": "Komplain #45", "idempotency_key": "adj-45" }
|
||
```
|
||
|
||
`amount` bertanda dan tidak boleh 0; `reason` wajib. Pengurangan yang melebihi saldo ditolak `304`. Response: `{ "transaction", "spendable_point_balance", "spendable_coin_balance", "replayed" }`.
|
||
|
||
### GET /marketing/wallet-transactions/:id/trace
|
||
|
||
```json
|
||
{
|
||
"transaction": { "id": "…", "customer": { "id": "…", "name": "Budi Santoso" }, "type": "TRANSFER_OUT", "amount": -30, "…": "…" },
|
||
"lots": [
|
||
{
|
||
"amount": 30,
|
||
"chain": [
|
||
{ "lot": { "id": "…", "expires_at": "…" }, "source": { "type": "TRANSFER_IN", "customer": { "name": "Budi Santoso" } } },
|
||
{ "lot": { "id": "…", "origin_lot_id": null }, "source": { "type": "EARN", "reference_type": "ORDER", "description": "Belanja #ORD-1", "customer": { "name": "Anita" } } }
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Pengurangan menampilkan lot yang dipakai; penambahan menampilkan lot yang dibuat. Tiap `chain` mundur lewat transfer atau exchange sampai lot pertama dari `EARN`, `ADJUSTMENT`, atau `MIGRATION`.
|
||
|
||
### PIN customer
|
||
|
||
`DELETE /marketing/customers/:id/pin` dengan `{ "reason": "…" }` memaksa customer membuat PIN baru lewat OTP; admin tidak bisa membuat, mengganti, atau melihat PIN. `security-events` mengembalikan `PIN_SET`, `PIN_CHANGED`, `PIN_RESET`, `PIN_FAILED`, `PIN_LOCKED`, `PIN_REMOVED_BY_ADMIN` beserta waktu, IP, dan perangkat.
|
||
|
||
## Referensi
|
||
|
||
### Tipe mutasi (`type`)
|
||
|
||
| `type` | Arah | Arti | `source` / `destination` |
|
||
| --- | --- | --- | --- |
|
||
| `EARN` | + | Didapat dari order lunas | `ORDER` |
|
||
| `EARN_REVERSAL` | − | Ditarik karena order di-void/refund | `ORDER` |
|
||
| `EXCHANGE_OUT` | − | EnakCoin ditukar | `WALLET_TX` (baris `EXCHANGE_IN`) |
|
||
| `EXCHANGE_IN` | + | EnakPoint hasil tukar | `WALLET_TX` (baris `EXCHANGE_OUT`) |
|
||
| `TRANSFER_OUT` | − | Dikirim ke customer lain | `WALLET_TX` (baris `TRANSFER_IN`) |
|
||
| `TRANSFER_IN` | + | Diterima dari customer lain | `WALLET_TX` (baris `TRANSFER_OUT`) |
|
||
| `GAME_SPEND` | − | Main game (EnakCoin saja) | `GAME_PLAY` |
|
||
| `EXPIRE` | − | Hangus karena kedaluwarsa | `LOT` |
|
||
| `ADJUSTMENT` | + / − | Koreksi admin | `USER` |
|
||
| `MIGRATION` | + | Saldo dari sistem lama | `LEGACY_POINTS` / `LEGACY_TOKENS` |
|
||
|
||
### Notifikasi push (FCM)
|
||
|
||
Semua nilai `data` berupa string. Push hanya sampai ke device yang terdaftar lewat `PUT /customer/devices`.
|
||
|
||
| `data.type` | Kapan | Isi `data` lainnya |
|
||
| --- | --- | --- |
|
||
| `WALLET_TRANSFER_IN` | Menerima transfer | `transaction_id`, `group_id`, `currency`, `amount` |
|
||
| `WALLET_EXPIRING` | `reminder_days` hari sebelum hangus, sekali per tanggal | `currency`, `amount`, `expiry_date` |
|
||
| `WALLET_EXPIRED` | Saldo baru saja hangus | `currency`, `amount` |
|
||
| `PIN_LOCKED` | PIN terkunci setelah 5 kali salah | `locked_until` (RFC3339, UTC) |
|
||
|
||
### Endpoint dan field deprecated
|
||
|
||
Masih jalan dan membaca wallet, tapi akan dihapus setelah semua versi aplikasi pindah. Semua yang bernama token (`/customer/tokens`, `total_tokens`, `tokens_history`, `token_used`, `tokens_remaining`, campaign `TOKENS`) sudah dihapus; pakai `coin_balance`, `coins_used`, `coins_remaining`, dan `COINS`.
|
||
|
||
| Lama | Pengganti |
|
||
| --- | --- |
|
||
| `GET /customer/points` | `GET /customer/wallet` → `point_balance` |
|
||
| `total_points`, `points_history`, `last_updated` di `/customer/wallet` | `point_balance`, `recent_transactions` |
|
||
|
||
### Endpoint dan field yang dihapus
|
||
|
||
Bayar dengan EnakPoint dihapus pada 7 Okt 2026 karena EnakPoint sekarang hanya untuk voucher ([`enakgame-prd.md`](./enakgame-prd.md) §3.2). Tidak ada penggantinya; jangan dipanggil lagi.
|
||
|
||
| Dihapus | Catatan |
|
||
| --- | --- |
|
||
| `POST /customer/wallet/payment-code` | Kode bayar untuk kasir |
|
||
| `POST /customer/orders/:id/pay-with-points` | Bayar order dari app / self-order |
|
||
| `GET /orders/:id/point-payment/preview` | Batas pembayaran EnakPoint di POS |
|
||
| Payment method tipe `point`; field `points` dan `payment_code` di `POST /payments` | `amount` kembali wajib seperti pembayaran lain |
|
||
| `points_used`, `point_value` di response pembayaran dan di `payments` pada `GET /customer/orders/:id` | – |
|
||
| `accepts_point_payment` di `GET /customer/outlets` | – |
|
||
| `point_payment` (`accept_payment`, `min_payment_points`, `max_payment_percent`) di `/outlets/:outlet_id/loyalty-settings` | `PUT` yang masih mengirimnya ditolak `310` |
|
||
| `summary.point_amount`, `summary.points_used`, `summary.total_with_points`, serta `points_used` dan `counts_as_cash_in` per baris di analytics payment method | `summary.total_amount` kembali total semua method; persentase dihitung dari total itu |
|
||
| Tipe mutasi `PAYMENT` dan `PAYMENT_REFUND` | Tidak ditulis lagi |
|
||
|
||
Panduan alur lengkap per tim ada di [`integration-enakpoint.md`](./integration-enakpoint.md).
|