1623 lines
39 KiB
Markdown
1623 lines
39 KiB
Markdown
# EnakGame — Economy & Business Rules v1
|
||||
|
|
|
|||
|
|
| Item | Value |
|
|||
|
|
| --------------------------- | ----------------------------------------------------------------------------------- |
|
|||
|
|
| **Status** | Draft v1 |
|
|||
|
|
| **Purpose** | Source of truth untuk perencanaan dan implementasi EnakGame menggunakan Claude Code |
|
|||
|
|
| **Backend** | Go |
|
|||
|
|
| **Database** | PostgreSQL |
|
|||
|
|
| **Game Client** | Phaser |
|
|||
|
|
| **Frontend/Backoffice** | Next.js |
|
|||
|
|
| **Cache/Temporary Session** | Redis (opsional, sesuai kebutuhan implementasi) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Product Overview
|
|||
|
|
|
|||
|
|
EnakGame adalah game portal dalam ekosistem Enaklo/F&B.
|
|||
|
|
|
|||
|
|
- User membayar **Coin** untuk memainkan mini-game (entry cost), dan dapat memperoleh **Coin** sebagai virtual reward dari hasil permainan.
|
|||
|
|
- Coin dapat dikonversi/digunakan sebagai **Point**, kemudian Point **hanya** dapat ditukar ke voucher yang disediakan Enaklo.
|
|||
|
|
- Point **tidak** dapat digunakan sebagai alat pembayaran dan **tidak** dapat diuangkan.
|
|||
|
|
- EnakGame **bukan** platform gambling dan **tidak** menggunakan uang tunai sebagai hadiah dari game.
|
|||
|
|
|
|||
|
|
### Core Flow
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
User
|
|||
|
|
↓
|
|||
|
|
Game Lobby
|
|||
|
|
↓
|
|||
|
|
Start Game
|
|||
|
|
↓
|
|||
|
|
Pay Entry Cost (Coin) + Create Game Session
|
|||
|
|
↓
|
|||
|
|
Play Game
|
|||
|
|
↓
|
|||
|
|
Submit Result
|
|||
|
|
↓
|
|||
|
|
Result Validation
|
|||
|
|
↓
|
|||
|
|
Reward Engine
|
|||
|
|
↓
|
|||
|
|
Economy Guard
|
|||
|
|
↓
|
|||
|
|
Coin Wallet + Ledger
|
|||
|
|
↓
|
|||
|
|
Coin → Point
|
|||
|
|
↓
|
|||
|
|
Voucher Redemption
|
|||
|
|
↓
|
|||
|
|
Realized Voucher Cost
|
|||
|
|
↓
|
|||
|
|
Budget Controller
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Core Business Principles
|
|||
|
|
|
|||
|
|
### 2.1 Server Is Authoritative
|
|||
|
|
|
|||
|
|
Client/game (Phaser) **tidak boleh** menentukan reward final.
|
|||
|
|
|
|||
|
|
Client hanya mengirim hasil permainan yang diperlukan, misalnya:
|
|||
|
|
|
|||
|
|
- `session_id`
|
|||
|
|
- `score`
|
|||
|
|
- `outcome`
|
|||
|
|
- game-specific result data
|
|||
|
|
|
|||
|
|
Backend menghitung reward berdasarkan configuration yang aktif.
|
|||
|
|
|
|||
|
|
#### ❌ Tidak boleh
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Phaser:
|
|||
|
|
score = 800
|
|||
|
|
reward = 1000 Coin
|
|||
|
|
→ backend menerima 1000 Coin
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### ✅ Yang benar
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Phaser:
|
|||
|
|
score = 800
|
|||
|
|
|
|||
|
|
Backend:
|
|||
|
|
score 800
|
|||
|
|
→ load active reward configuration
|
|||
|
|
→ calculate reward
|
|||
|
|
→ validate limits
|
|||
|
|
→ issue approved Coin
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Currency Model
|
|||
|
|
|
|||
|
|
EnakGame memiliki dua konsep utama: **Coin** dan **Point**.
|
|||
|
|
|
|||
|
|
### 3.1 Coin
|
|||
|
|
|
|||
|
|
Coin adalah **virtual game/reward currency**.
|
|||
|
|
|
|||
|
|
Sumber utama:
|
|||
|
|
|
|||
|
|
- Game reward
|
|||
|
|
- Event reward
|
|||
|
|
- Mission reward
|
|||
|
|
- Bonus/promo yang diizinkan
|
|||
|
|
|
|||
|
|
Penggunaan:
|
|||
|
|
|
|||
|
|
- Membayar entry cost untuk memainkan game (lihat [Section 10.1](#101-game-entry-cost))
|
|||
|
|
- Dikonversi ke Point
|
|||
|
|
|
|||
|
|
Coin dapat memiliki expiration (lihat [Section 4](#4-coin-expiration)).
|
|||
|
|
|
|||
|
|
### 3.2 Point
|
|||
|
|
|
|||
|
|
Point adalah **redemption currency**.
|
|||
|
|
|
|||
|
|
Current business rule:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
1 Coin = 1 Point = Rp1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Penggunaan Point dibatasi:
|
|||
|
|
|
|||
|
|
- Point **hanya** dapat ditukar ke voucher (lihat [Section 26](#26-redemption-flow)).
|
|||
|
|
- Point **tidak** dapat digunakan sebagai alat pembayaran (misalnya membayar order).
|
|||
|
|
- Point **tidak** dapat diuangkan (cash out).
|
|||
|
|
|
|||
|
|
Nilai `Rp1` di atas adalah nilai acuan untuk perhitungan voucher dan budget, **bukan** nilai tukar ke uang tunai.
|
|||
|
|
|
|||
|
|
Konversi dan expiration mengikuti sistem existing ([prd-point-coin.md](prd-point-coin.md)), tidak didesain ulang:
|
|||
|
|
|
|||
|
|
- **Coin → Point** menggunakan fitur exchange existing (K3, F4): dilakukan manual oleh customer, satu arah, kurs di-configure di level organisasi (default 1 : 1).
|
|||
|
|
- **Point expiration** menggunakan sistem expiration existing per lot (F12).
|
|||
|
|
|
|||
|
|
> **Catatan perubahan dari sistem existing:** Berdasarkan keputusan sebelumnya ([prd-point-coin.md](prd-point-coin.md)), backend saat ini masih mengizinkan EnakPoint dipakai untuk **membayar order** (point payment, ledger `PAYMENT` / `PAYMENT_REFUND`). Aturan EnakGame di atas menggantikan keputusan tersebut, sehingga fitur point payment perlu di-update/dinonaktifkan sebelum EnakGame berjalan. Belum ada order yang dibayar dengan Point, jadi tidak ada data lama yang perlu dimigrasi. Saldo Point user tetap sebagai Point; yang berubah hanya penggunaannya (redeem voucher saja).
|
|||
|
|
|
|||
|
|
Namun Coin dan Point tetap diperlakukan sebagai **konsep/domain yang berbeda** agar sistem tidak terlalu tightly coupled.
|
|||
|
|
|
|||
|
|
Tujuannya agar business rule di masa depan dapat berubah tanpa membongkar Game/Reward Engine.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Coin Expiration
|
|||
|
|
|
|||
|
|
Sistem Coin sudah memiliki konsep expiration. Expiration harus tetap menjadi bagian dari economy.
|
|||
|
|
|
|||
|
|
Coin yang expired:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Wallet usable balance
|
|||
|
|
↓
|
|||
|
|
berkurang
|
|||
|
|
|
|||
|
|
Ledger
|
|||
|
|
↓
|
|||
|
|
mencatat expiration transaction
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **Penting:** Expiration **tidak boleh** dianggap sebagai voucher redemption.
|
|||
|
|
|
|||
|
|
Kategori transaksi harus dapat dibedakan:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Wallet usable balance
|
|||
|
|
↓
|
|||
|
|
berkurang
|
|||
|
|
|
|||
|
|
Ledger
|
|||
|
|
↓
|
|||
|
|
mencatat expiration transaction
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Implementasi detail expiration mengikuti sistem existing dan tidak perlu didesain ulang kecuali diperlukan.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Budget Model
|
|||
|
|
|
|||
|
|
### 5.1 Budget Is Shared
|
|||
|
|
|
|||
|
|
Semua game menggunakan **budget pool yang sama**. Tidak ada kewajiban setiap game memiliki budget terpisah.
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
EnakGame Global Budget
|
|||
|
|
Rp100.000.000
|
|||
|
|
│
|
|||
|
|
├── Runner
|
|||
|
|
├── Spin
|
|||
|
|
├── Memory
|
|||
|
|
└── Puzzle
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Semua reward normal game berasal dari economy/budget pool yang sama.
|
|||
|
|
|
|||
|
|
Pengecualian: **Event/Campaign** memiliki budget sendiri (lihat [Section 7](#7-budget-hierarchy)).
|
|||
|
|
|
|||
|
|
### 5.2 Budget Period
|
|||
|
|
|
|||
|
|
- Global budget menggunakan periode **bulanan** secara default.
|
|||
|
|
- Periode budget dapat di-**configure**.
|
|||
|
|
- Setiap Event/Campaign memiliki budget sendiri, terpisah dari global budget bulanan.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Budget vs Realized Cost
|
|||
|
|
|
|||
|
|
### 6.1 Budget Is Based on Actual Voucher Redemption
|
|||
|
|
|
|||
|
|
Budget Controller menggunakan **voucher yang benar-benar diredeem** sebagai dasar actual cost.
|
|||
|
|
|
|||
|
|
Coin yang baru diterbitkan **bukan** otomatis dianggap sebagai biaya voucher.
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
| Metric | Jumlah |
|
|||
|
|
| ------------------- | ---------- |
|
|||
|
|
| Coin Generated | 20.000.000 |
|
|||
|
|
| Coin Outstanding | 12.000.000 |
|
|||
|
|
| Coin Expired | 3.000.000 |
|
|||
|
|
| Coin Used/Converted | 5.000.000 |
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Actual Voucher Cost = Rp5.000.000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Karena `1 Point = Rp1` dan Point digunakan untuk redemption.
|
|||
|
|
|
|||
|
|
### 6.1.1 Hanya Point dari EnakGame yang Dihitung ke Budget
|
|||
|
|
|
|||
|
|
Satu voucher dapat dibayar dengan Point dari berbagai asal: reward game, belanja (earning order), transfer, atau adjustment.
|
|||
|
|
|
|||
|
|
Realized cost yang dihitung ke budget EnakGame (global maupun event) **hanya** bagian voucher yang dibayar dengan Point yang berasal dari **reward EnakGame**. Bagian yang dibayar dengan Point dari belanja tetap dicatat untuk reporting Finance, tetapi **tidak** mengurangi budget mana pun.
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Voucher face value = Rp10.000, point cost = 8.000
|
|||
|
|
Point dipakai = 6.000 (dari reward game) + 2.000 (dari belanja)
|
|||
|
|
|
|||
|
|
Dihitung ke budget = Rp7.500
|
|||
|
|
Tidak ke budget = Rp2.500
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.2 Important Distinction
|
|||
|
|
|
|||
|
|
Sistem harus membedakan:
|
|||
|
|
|
|||
|
|
| Konsep | Definisi |
|
|||
|
|
| ----------------- | ------------------------------------------------------------ |
|
|||
|
|
| **Issuance** | Berapa Coin yang dikeluarkan oleh Reward Engine |
|
|||
|
|
| **Outstanding** | Berapa Coin masih berada di user/economy |
|
|||
|
|
| **Redemption** | Berapa Point benar-benar digunakan untuk mendapatkan voucher |
|
|||
|
|
| **Realized Cost** | Nilai voucher yang benar-benar menjadi cost bisnis |
|
|||
|
|
|
|||
|
|
Budget Controller terutama menggunakan **realized redemption/cost**, bukan sekadar total Coin issuance.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Budget Hierarchy
|
|||
|
|
|
|||
|
|
Secara konsep:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
GLOBAL BUDGET (bulanan, configurable)
|
|||
|
|
│
|
|||
|
|
└── Regular Game Activity (reward normal semua game)
|
|||
|
|
|
|||
|
|
EVENT/CAMPAIGN BUDGET (per event, terpisah)
|
|||
|
|
│
|
|||
|
|
└── Tambahan reward dari event (multiplier + bonus)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Event dan Campaign adalah hal yang sama.** Setiap event memiliki **budget sendiri**, terpisah dari global budget.
|
|||
|
|
|
|||
|
|
Pembagian biaya saat event berjalan:
|
|||
|
|
|
|||
|
|
| Komponen reward | Dibiayai oleh |
|
|||
|
|
| ------------------------------------ | --------------- |
|
|||
|
|
| Reward normal game (base reward) | Global budget |
|
|||
|
|
| Tambahan dari event (multiplier/bonus) | Budget event |
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Normal Reward 10 Coin → Global budget
|
|||
|
|
Ramadan 2x +10 Coin → Budget event Ramadan
|
|||
|
|
──────────────────────────
|
|||
|
|
Final Reward 20 Coin
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Realized cost dari Point yang berasal dari tambahan event dihitung ke budget event tersebut, **kapan pun** Point itu ditukar ke voucher, termasuk setelah event berakhir.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. Budget Metrics
|
|||
|
|
|
|||
|
|
Minimum metric yang harus tersedia:
|
|||
|
|
|
|||
|
|
- Total Budget
|
|||
|
|
- Allocated Budget
|
|||
|
|
- Realized Cost
|
|||
|
|
- Remaining Budget
|
|||
|
|
- Budget Utilization
|
|||
|
|
- Forecasted Cost
|
|||
|
|
- Forecasted Remaining Budget
|
|||
|
|
- Budget Status
|
|||
|
|
|
|||
|
|
Contoh status:
|
|||
|
|
|
|||
|
|
- `HEALTHY`
|
|||
|
|
- `WARNING`
|
|||
|
|
- `CRITICAL`
|
|||
|
|
- `EXHAUSTED`
|
|||
|
|
|
|||
|
|
Threshold harus **configurable**.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. Game Management
|
|||
|
|
|
|||
|
|
Game Management mengelola semua game yang tersedia di EnakGame.
|
|||
|
|
|
|||
|
|
Minimum information:
|
|||
|
|
|
|||
|
|
- `id`
|
|||
|
|
- `name`
|
|||
|
|
- `slug`
|
|||
|
|
- `description`
|
|||
|
|
- `thumbnail`
|
|||
|
|
- game URL/path
|
|||
|
|
- `version`
|
|||
|
|
- `status`
|
|||
|
|
- `configuration`
|
|||
|
|
- `created_at`
|
|||
|
|
- `updated_at`
|
|||
|
|
|
|||
|
|
Game status minimal:
|
|||
|
|
|
|||
|
|
- `DRAFT`
|
|||
|
|
- `ACTIVE`
|
|||
|
|
- `INACTIVE`
|
|||
|
|
- `ARCHIVED`
|
|||
|
|
|
|||
|
|
Game harus dapat memiliki reward configuration.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 10. Game Session
|
|||
|
|
|
|||
|
|
Setiap permainan yang dapat menghasilkan reward **harus** memiliki session.
|
|||
|
|
|
|||
|
|
Flow:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
User
|
|||
|
|
↓
|
|||
|
|
Start Game
|
|||
|
|
↓
|
|||
|
|
Debit Entry Cost (Coin) + Create Game Session
|
|||
|
|
↓
|
|||
|
|
Phaser Game
|
|||
|
|
↓
|
|||
|
|
Submit Result
|
|||
|
|
↓
|
|||
|
|
Validate Session
|
|||
|
|
↓
|
|||
|
|
Calculate Reward
|
|||
|
|
↓
|
|||
|
|
Issue Reward
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Session harus dapat mencegah:
|
|||
|
|
|
|||
|
|
- duplicate completion
|
|||
|
|
- duplicate reward
|
|||
|
|
- expired session
|
|||
|
|
- invalid session
|
|||
|
|
- user mismatch
|
|||
|
|
- game mismatch
|
|||
|
|
|
|||
|
|
Recommended concept: `game_session_id` menjadi **idempotency key** untuk reward completion.
|
|||
|
|
|
|||
|
|
> **Penting:** Satu session tidak boleh menghasilkan reward berkali-kali.
|
|||
|
|
|
|||
|
|
### 10.1 Game Entry Cost
|
|||
|
|
|
|||
|
|
Semua game **wajib berbayar** menggunakan **Coin**. Tidak ada game gratis.
|
|||
|
|
|
|||
|
|
- Entry cost di-configure per game sebagai bagian dari game configuration, dengan nilai minimal **1 Coin**.
|
|||
|
|
- Entry cost dibayar dengan **Coin**, bukan Point.
|
|||
|
|
- Coin dipotong saat **Start Game**, yaitu saat session dibuat — bukan saat submit result.
|
|||
|
|
- Debit Coin dan pembuatan session terjadi dalam **satu database transaction**. Jika salah satu gagal, tidak ada Coin yang terpotong dan tidak ada session yang terbuat.
|
|||
|
|
- Jika usable balance Coin tidak cukup, session **tidak** dibuat dan user tidak dapat bermain.
|
|||
|
|
- Entry cost yang berlaku dicatat di session (snapshot), sehingga perubahan configuration tidak mengubah session yang sudah berjalan.
|
|||
|
|
- Debit dicatat di ledger sebagai `SPENT` dengan reference `game_session_id` dan `game_id`.
|
|||
|
|
- Satu session hanya boleh didebit **satu kali** (`game_session_id` menjadi idempotency key untuk debit).
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Start Game
|
|||
|
|
→ load game configuration (entry cost)
|
|||
|
|
→ check Coin usable balance
|
|||
|
|
→ [1 transaction] debit Coin (SPENT) + create session
|
|||
|
|
→ return session_id ke Phaser
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Entry Cost & Budget
|
|||
|
|
|
|||
|
|
Entry cost **tidak** mengurangi atau meng-offset perhitungan budget.
|
|||
|
|
|
|||
|
|
Budget Controller dan Budget Metrics hanya memperhitungkan **reward** (Coin yang diterbitkan) dan realized voucher cost. Coin yang dibayar untuk bermain tidak dianggap sebagai pemasukan yang menambah budget.
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Reward issued = 1.000.000 Coin
|
|||
|
|
Entry cost paid = 400.000 Coin
|
|||
|
|
|
|||
|
|
Budget memakai → 1.000.000 Coin (reward), bukan 600.000 (net)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 10.2 Entry Cost Refund
|
|||
|
|
|
|||
|
|
Entry cost dapat dikembalikan (refund) ke user untuk session yang tidak dapat diselesaikan.
|
|||
|
|
|
|||
|
|
- Refund mengembalikan **jumlah Coin yang sama** dengan entry cost yang tercatat di session.
|
|||
|
|
- Refund dicatat di ledger sebagai `REFUND` dengan reference `game_session_id` dan reference ke transaksi `SPENT` asalnya.
|
|||
|
|
- Refund **wajib idempotent**: satu session maksimal satu kali refund.
|
|||
|
|
- Session yang sudah di-refund **tidak** boleh menghasilkan reward, dan session yang sudah rewarded **tidak** boleh di-refund.
|
|||
|
|
- Refund bukan reward: tidak melewati Reward Engine, tidak dihitung sebagai Coin issued, dan tidak dihitung di budget.
|
|||
|
|
- Refund harus auditable (siapa/sistem apa yang memicu dan alasannya).
|
|||
|
|
|
|||
|
|
Kondisi refund:
|
|||
|
|
|
|||
|
|
| Kondisi | Refund |
|
|||
|
|
| -------------------------------------------------------- | -------------------------- |
|
|||
|
|
| System error (session gagal diselesaikan karena sistem) | **Ya, otomatis** oleh sistem |
|
|||
|
|
| Game di-nonaktifkan saat session sedang berjalan | **Ya, otomatis** oleh sistem |
|
|||
|
|
| Session expired/abandoned oleh user | **Tidak** |
|
|||
|
|
|
|||
|
|
- Refund dijalankan **otomatis** oleh sistem, tanpa perlu request user atau approval admin.
|
|||
|
|
- Session yang ditinggalkan user tidak di-refund, agar user tidak dapat memulai game lalu meninggalkannya saat hasilnya tidak menguntungkan.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 11. Reward Engine
|
|||
|
|
|
|||
|
|
Reward Engine adalah komponen yang menentukan berapa reward yang **secara teoritis** berhak diterima user berdasarkan result dan configuration.
|
|||
|
|
|
|||
|
|
Reward Engine **tidak** bertanggung jawab sendirian untuk memutuskan apakah reward boleh diterbitkan.
|
|||
|
|
|
|||
|
|
Flow:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Game Result
|
|||
|
|
↓
|
|||
|
|
Reward Engine
|
|||
|
|
↓
|
|||
|
|
Base Reward
|
|||
|
|
↓
|
|||
|
|
Event Modifier / Bonus
|
|||
|
|
↓
|
|||
|
|
Final Calculated Reward
|
|||
|
|
↓
|
|||
|
|
Economy Guard
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 12. Supported Reward Types
|
|||
|
|
|
|||
|
|
Reward Engine dirancang agar **extensible**.
|
|||
|
|
|
|||
|
|
Jenis reward minimum yang dapat didukung:
|
|||
|
|
|
|||
|
|
### `FIXED`
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Play completed → 5 Coin
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `SCORE_BASED`
|
|||
|
|
|
|||
|
|
| Score | Reward |
|
|||
|
|
| -------- | ------- |
|
|||
|
|
| 0–100 | 1 Coin |
|
|||
|
|
| 101–500 | 5 Coin |
|
|||
|
|
| 501–1000 | 10 Coin |
|
|||
|
|
| 1001+ | 20 Coin |
|
|||
|
|
|
|||
|
|
### `OUTCOME_BASED`
|
|||
|
|
|
|||
|
|
| Outcome | Reward |
|
|||
|
|
| --------- | ------- |
|
|||
|
|
| `PERFECT` | 20 Coin |
|
|||
|
|
| `GOOD` | 10 Coin |
|
|||
|
|
| `NORMAL` | 5 Coin |
|
|||
|
|
| `FAIL` | 0 Coin |
|
|||
|
|
|
|||
|
|
### `PROBABILITY`
|
|||
|
|
|
|||
|
|
Reward berdasarkan probability yang **dihitung server**.
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
| Probability | Reward |
|
|||
|
|
| ----------- | --------- |
|
|||
|
|
| 0.1% | 1000 Coin |
|
|||
|
|
| 1% | 100 Coin |
|
|||
|
|
| 10% | 10 Coin |
|
|||
|
|
| 88.9% | 0 Coin |
|
|||
|
|
|
|||
|
|
Probability harus tervalidasi.
|
|||
|
|
|
|||
|
|
### `MULTIPLIER`
|
|||
|
|
|
|||
|
|
Base reward dikalikan modifier.
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Base = 10 Coin
|
|||
|
|
Multiplier = 2x
|
|||
|
|
|
|||
|
|
Final = 20 Coin
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `TIERED`
|
|||
|
|
|
|||
|
|
Reward berdasarkan tier/user/game progression.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
Jenis reward dapat ditambah di masa depan tanpa mengubah seluruh engine.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 13. Reward Configuration
|
|||
|
|
|
|||
|
|
Reward configuration harus memiliki **lifecycle/version**.
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
| Config | Reward |
|
|||
|
|
| ----------------------- | ------- |
|
|||
|
|
| Runner Reward Config v1 | 10 Coin |
|
|||
|
|
| Runner Reward Config v2 | 8 Coin |
|
|||
|
|
|
|||
|
|
> **Penting:** Jangan overwrite configuration lama jika configuration tersebut sudah pernah digunakan dalam transaksi.
|
|||
|
|
|
|||
|
|
Tujuan:
|
|||
|
|
|
|||
|
|
- audit
|
|||
|
|
- debugging
|
|||
|
|
- historical accuracy
|
|||
|
|
- mengetahui rule yang berlaku saat user bermain
|
|||
|
|
|
|||
|
|
Setiap reward transaction idealnya dapat dilacak ke configuration/version yang digunakan.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 14. Normal Reward + Event Reward
|
|||
|
|
|
|||
|
|
Normal reward dan event modifier **boleh aktif bersamaan**.
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Normal Reward 10 Coin
|
|||
|
|
Ramadan Event 2x multiplier
|
|||
|
|
Mission Bonus +5 Coin
|
|||
|
|
─────────────────────────
|
|||
|
|
Final Reward 25 Coin
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Event **tidak boleh** mengubah permanent/base configuration game. Event bekerja sebagai **layer/override/modifier**.
|
|||
|
|
|
|||
|
|
Setelah event selesai:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Normal Reward 10 Coin
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 15. Event Management
|
|||
|
|
|
|||
|
|
Event digunakan untuk seasonal/campaign activity.
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
- Ramadan
|
|||
|
|
- Christmas
|
|||
|
|
- Independence Day
|
|||
|
|
- Anniversary
|
|||
|
|
- F&B campaign
|
|||
|
|
|
|||
|
|
Minimum event data:
|
|||
|
|
|
|||
|
|
- `id`
|
|||
|
|
- `name`
|
|||
|
|
- `slug`
|
|||
|
|
- `description`
|
|||
|
|
- `banner`
|
|||
|
|
- `start_at`
|
|||
|
|
- `end_at`
|
|||
|
|
- `timezone`
|
|||
|
|
- `status`
|
|||
|
|
- `priority`
|
|||
|
|
- reward configuration/modifier
|
|||
|
|
- budget sendiri (lihat [Section 7](#7-budget-hierarchy))
|
|||
|
|
- rules
|
|||
|
|
|
|||
|
|
Event dapat menentukan:
|
|||
|
|
|
|||
|
|
- participating games
|
|||
|
|
- reward multiplier
|
|||
|
|
- bonus reward
|
|||
|
|
- mission
|
|||
|
|
- daily limit
|
|||
|
|
- user limit
|
|||
|
|
- leaderboard
|
|||
|
|
- event-specific campaign rules
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 16. Multiple Event Handling
|
|||
|
|
|
|||
|
|
Karena event dapat overlap, sistem harus memiliki **priority/stacking rule**.
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Normal Reward
|
|||
|
|
+ Event A
|
|||
|
|
+ Event B
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Sistem harus memiliki aturan jelas apakah:
|
|||
|
|
|
|||
|
|
- hanya event priority tertinggi yang berlaku
|
|||
|
|
- modifier dapat ditumpuk
|
|||
|
|
- bonus dapat ditumpuk
|
|||
|
|
- ada maximum multiplier
|
|||
|
|
|
|||
|
|
Default recommendation:
|
|||
|
|
|
|||
|
|
| Komponen | Rule |
|
|||
|
|
| ------------------ | --------------------- |
|
|||
|
|
| Reward Modifier | Controlled stacking |
|
|||
|
|
| Bonus | Independently tracked |
|
|||
|
|
| Maximum Reward Cap | Always enforced |
|
|||
|
|
|
|||
|
|
> **Penting:** Final stacking rules harus ditentukan sebelum production.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 17. Economy Guard
|
|||
|
|
|
|||
|
|
Economy Guard menentukan apakah calculated reward **benar-benar boleh diterbitkan**.
|
|||
|
|
|
|||
|
|
Minimum checks:
|
|||
|
|
|
|||
|
|
- session valid
|
|||
|
|
- session belum rewarded
|
|||
|
|
- user valid
|
|||
|
|
- game valid
|
|||
|
|
- reward configuration active
|
|||
|
|
- user daily limit
|
|||
|
|
- game daily limit
|
|||
|
|
- event limit
|
|||
|
|
- global limit
|
|||
|
|
- maximum reward
|
|||
|
|
- budget/economy status
|
|||
|
|
- fraud/risk checks
|
|||
|
|
- idempotency
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Reward Engine
|
|||
|
|
→ 20 Coin
|
|||
|
|
|
|||
|
|
Economy Guard
|
|||
|
|
→ daily limit OK
|
|||
|
|
→ event limit OK
|
|||
|
|
→ duplicate NO
|
|||
|
|
→ budget status OK
|
|||
|
|
|
|||
|
|
Approved
|
|||
|
|
→ issue 20 Coin
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 18. Coin Wallet
|
|||
|
|
|
|||
|
|
Wallet menyimpan **current usable balance**.
|
|||
|
|
|
|||
|
|
Namun wallet **bukan** satu-satunya source of truth. Source of truth untuk audit adalah **ledger**.
|
|||
|
|
|
|||
|
|
| Komponen | Digunakan untuk |
|
|||
|
|
| ---------- | ---------------------------------------------------------------- |
|
|||
|
|
| **Wallet** | fast balance lookup, current balance |
|
|||
|
|
| **Ledger** | transaction history, audit, reconciliation, debugging, reporting |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 19. Coin Ledger
|
|||
|
|
|
|||
|
|
Setiap perubahan balance **harus** menghasilkan ledger transaction.
|
|||
|
|
|
|||
|
|
Minimum transaction concepts:
|
|||
|
|
|
|||
|
|
- `EARNED`
|
|||
|
|
- `BONUS`
|
|||
|
|
- `SPENT`
|
|||
|
|
- `EXPIRED`
|
|||
|
|
- `ADJUSTMENT`
|
|||
|
|
- `REVERSAL`
|
|||
|
|
- `CONVERSION`
|
|||
|
|
- `REFUND`
|
|||
|
|
|
|||
|
|
`SPENT` mencakup pembayaran entry cost game. `REFUND` adalah pengembalian entry cost (lihat [Section 10.2](#102-entry-cost-refund)).
|
|||
|
|
|
|||
|
|
Transaction harus dapat memiliki reference:
|
|||
|
|
|
|||
|
|
- `game_session_id`
|
|||
|
|
- `game_id`
|
|||
|
|
- `event_id`
|
|||
|
|
- `voucher_redemption_id`
|
|||
|
|
- `mission_id`
|
|||
|
|
- admin adjustment reference
|
|||
|
|
|
|||
|
|
Idealnya setiap transaction memiliki:
|
|||
|
|
|
|||
|
|
- `balance_before`
|
|||
|
|
- `amount`
|
|||
|
|
- `balance_after`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 20. Idempotency
|
|||
|
|
|
|||
|
|
Reward transaction **wajib idempotent**.
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
POST /game-session/complete
|
|||
|
|
session_id = ABC
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Request pertama:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Reward = 10 Coin
|
|||
|
|
Status = SUCCESS
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Request kedua dengan session yang sama:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Tidak membuat reward baru.
|
|||
|
|
Return existing reward/result.
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Database harus memiliki **unique constraint/strategy** yang menjamin satu session tidak bisa menghasilkan duplicate reward.
|
|||
|
|
|
|||
|
|
Hal yang sama berlaku untuk entry cost: satu session maksimal satu debit `SPENT` dan satu `REFUND`.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 21. Voucher Management
|
|||
|
|
|
|||
|
|
Voucher Management adalah **core module tersendiri**.
|
|||
|
|
|
|||
|
|
Voucher adalah benefit yang dapat ditukar menggunakan Point.
|
|||
|
|
|
|||
|
|
Minimum voucher data:
|
|||
|
|
|
|||
|
|
- `id`
|
|||
|
|
- `name`
|
|||
|
|
- `description`
|
|||
|
|
- `image`
|
|||
|
|
- provider/merchant
|
|||
|
|
- voucher value
|
|||
|
|
- point cost
|
|||
|
|
- stock
|
|||
|
|
- validity
|
|||
|
|
- terms
|
|||
|
|
- status
|
|||
|
|
- redemption rules
|
|||
|
|
|
|||
|
|
Sumber voucher mendukung **keduanya**:
|
|||
|
|
|
|||
|
|
- **Internal voucher codes** — kode dikelola sendiri (lihat [Section 25](#25-voucher-stock)).
|
|||
|
|
- **External API** — kode/voucher diterbitkan oleh provider eksternal saat redemption.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 22. Voucher Value vs Point Cost
|
|||
|
|
|
|||
|
|
Jangan menganggap `Voucher Value = Point Cost` selamanya.
|
|||
|
|
|
|||
|
|
Current conversion:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
1 Coin = 1 Point = Rp1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Tetapi sebuah voucher dapat memiliki:
|
|||
|
|
|
|||
|
|
| Voucher Value | Point Cost |
|
|||
|
|
| ------------- | ---------- |
|
|||
|
|
| Rp10.000 | 8.000 |
|
|||
|
|
| Rp10.000 | 12.000 |
|
|||
|
|
|
|||
|
|
Hal ini memungkinkan Product/Finance mengatur **subsidy, promotion, atau margin**.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 23. Voucher Types
|
|||
|
|
|
|||
|
|
Voucher Management harus **extensible**.
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
| Type | Contoh |
|
|||
|
|
| ----------------------- | ---------------------------------------------------------- |
|
|||
|
|
| **Fixed Value** | Rp5.000, Rp10.000, Rp20.000 |
|
|||
|
|
| **Percentage Discount** | 10%, 20%, 50% |
|
|||
|
|
| **Free Item** | Free Coffee, Free Food |
|
|||
|
|
| **Merchant Benefit** | Benefit yang hanya berlaku pada merchant/location tertentu |
|
|||
|
|
|
|||
|
|
Jenis voucher dapat ditambah sesuai kebutuhan.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 24. Voucher Inventory
|
|||
|
|
|
|||
|
|
Jika voucher menggunakan unique code, sistem harus memiliki inventory.
|
|||
|
|
|
|||
|
|
Status minimal:
|
|||
|
|
|
|||
|
|
- `AVAILABLE`
|
|||
|
|
- `RESERVED`
|
|||
|
|
- `REDEEMED`
|
|||
|
|
- `EXPIRED`
|
|||
|
|
- `CANCELLED`
|
|||
|
|
|
|||
|
|
Flow normal:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
AVAILABLE → RESERVED → REDEEMED
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Jika redemption gagal/timeout:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
RESERVED → AVAILABLE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Jika voucher expired:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
AVAILABLE → EXPIRED
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 25. Voucher Stock
|
|||
|
|
|
|||
|
|
Voucher dapat menggunakan salah satu dari:
|
|||
|
|
|
|||
|
|
### Static Stock
|
|||
|
|
|
|||
|
|
Admin memasukkan jumlah stock.
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Stock = 1.000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Code Pool
|
|||
|
|
|
|||
|
|
Admin/provider memasukkan unique voucher codes.
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
CODE-001
|
|||
|
|
CODE-002
|
|||
|
|
CODE-003
|
|||
|
|
...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
System harus mengetahui stock available secara **reliable**.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 26. Redemption Flow
|
|||
|
|
|
|||
|
|
Recommended flow:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
User
|
|||
|
|
↓
|
|||
|
|
Select Voucher
|
|||
|
|
↓
|
|||
|
|
Check Point Balance
|
|||
|
|
↓
|
|||
|
|
Check Voucher Availability
|
|||
|
|
↓
|
|||
|
|
Reserve Voucher
|
|||
|
|
↓
|
|||
|
|
Deduct Point
|
|||
|
|
↓
|
|||
|
|
Issue Voucher
|
|||
|
|
↓
|
|||
|
|
Mark Voucher Redeemed
|
|||
|
|
↓
|
|||
|
|
Create Redemption Record
|
|||
|
|
↓
|
|||
|
|
Record Realized Cost
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Transaction harus **atomic**.
|
|||
|
|
|
|||
|
|
> **Penting:** Jangan sampai Point sudah dikurangi **+** voucher gagal diberikan tanpa recovery/rollback.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 27. Redemption Idempotency
|
|||
|
|
|
|||
|
|
Redemption juga **harus idempotent**.
|
|||
|
|
|
|||
|
|
Satu redemption request tidak boleh menghasilkan:
|
|||
|
|
|
|||
|
|
- dua voucher
|
|||
|
|
- dua Point deduction
|
|||
|
|
- dua cost records
|
|||
|
|
|
|||
|
|
Gunakan idempotency/reference key.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 28. Realized Cost
|
|||
|
|
|
|||
|
|
Setiap successful voucher redemption menghasilkan **realized cost**.
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Voucher Value = Rp10.000
|
|||
|
|
Point Used = 10.000
|
|||
|
|
|
|||
|
|
Realized Cost = Rp10.000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Realized cost menggunakan **voucher face value**.
|
|||
|
|
|
|||
|
|
Sistem tetap menyimpan field berikut secara terpisah:
|
|||
|
|
|
|||
|
|
- `voucher_value` — face value, menjadi dasar realized cost dan budget
|
|||
|
|
- `point_cost` — Point yang dibayar user
|
|||
|
|
- `business_cost` — opsional, untuk reporting Finance jika berbeda dari face value; **tidak** dipakai untuk budget
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 29. Budget Controller
|
|||
|
|
|
|||
|
|
Budget Controller bertugas mengontrol reward economy berdasarkan **actual redemption dan forecast**.
|
|||
|
|
|
|||
|
|
### Input
|
|||
|
|
|
|||
|
|
Minimum input:
|
|||
|
|
|
|||
|
|
- global budget
|
|||
|
|
- realized voucher cost
|
|||
|
|
- remaining budget
|
|||
|
|
- remaining days
|
|||
|
|
- historical redemption
|
|||
|
|
- coin issuance
|
|||
|
|
- active users
|
|||
|
|
- plays
|
|||
|
|
- average reward
|
|||
|
|
- event status
|
|||
|
|
- redemption rate
|
|||
|
|
- target utilization
|
|||
|
|
|
|||
|
|
### Output
|
|||
|
|
|
|||
|
|
Budget Controller dapat menghasilkan:
|
|||
|
|
|
|||
|
|
- budget status
|
|||
|
|
- forecast cost
|
|||
|
|
- recommended reward multiplier
|
|||
|
|
- recommended reward rate
|
|||
|
|
- warning
|
|||
|
|
- automatic adjustment
|
|||
|
|
- stop reward recommendation
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 30. Redemption-Based Forecast
|
|||
|
|
|
|||
|
|
Contoh kondisi:
|
|||
|
|
|
|||
|
|
| Metric | Nilai |
|
|||
|
|
| -------------- | ------ |
|
|||
|
|
| Global Budget | Rp100M |
|
|||
|
|
| Realized Cost | Rp60M |
|
|||
|
|
| Remaining | Rp40M |
|
|||
|
|
| Remaining Days | 10 |
|
|||
|
|
|
|||
|
|
Jika current burn rate terlalu tinggi:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Forecast = Rp115M
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
System dapat menghitung adjustment.
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
| Item | Nilai |
|
|||
|
|
| ---------------------- | -------- |
|
|||
|
|
| Current Reward | 10 Coin |
|
|||
|
|
| Recommended Multiplier | 0.85x |
|
|||
|
|
| New Effective Reward | 8.5 Coin |
|
|||
|
|
|
|||
|
|
> **Penting:** Rounding rule harus ditentukan agar reward final tidak menghasilkan pecahan Coin jika Coin integer.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 31. Dynamic Rate Safety
|
|||
|
|
|
|||
|
|
Reward rate **tidak boleh** berubah secara liar setiap request.
|
|||
|
|
|
|||
|
|
Gunakan:
|
|||
|
|
|
|||
|
|
- configuration version
|
|||
|
|
- `effective_at`
|
|||
|
|
- cooldown
|
|||
|
|
- min/max multiplier
|
|||
|
|
- adjustment step
|
|||
|
|
- forecast window
|
|||
|
|
- audit log
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Current = 10 Coin
|
|||
|
|
Allowed adjustment step = 10%
|
|||
|
|
|
|||
|
|
Next possible: 9 Coin atau 11 Coin
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
❌ Jangan:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
10 → 7 → 12 → 5 → 14 (dalam waktu singkat)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Tujuannya menjaga **predictability** dan **user trust**.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 32. Budget Status
|
|||
|
|
|
|||
|
|
Recommended:
|
|||
|
|
|
|||
|
|
| Status | Arti |
|
|||
|
|
| ----------- | ------------------------------------- |
|
|||
|
|
| `HEALTHY` | Budget aman |
|
|||
|
|
| `WARNING` | Burn rate mulai tinggi |
|
|||
|
|
| `CRITICAL` | Forecast berpotensi melewati budget |
|
|||
|
|
| `EXHAUSTED` | Budget sudah mencapai limit |
|
|||
|
|
|
|||
|
|
Behavior setiap status harus **configurable**.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 33. Automatic vs Approval
|
|||
|
|
|
|||
|
|
Sistem sebaiknya mendukung dua mode:
|
|||
|
|
|
|||
|
|
### `RECOMMENDATION MODE`
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
System calculates recommendation
|
|||
|
|
↓
|
|||
|
|
Admin/Product/Finance approves
|
|||
|
|
↓
|
|||
|
|
Publish
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### `AUTOMATIC MODE`
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
System calculates
|
|||
|
|
↓
|
|||
|
|
Guardrails
|
|||
|
|
↓
|
|||
|
|
Auto publish
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Automatic mode hanya boleh berjalan dengan:
|
|||
|
|
|
|||
|
|
- min reward
|
|||
|
|
- max reward
|
|||
|
|
- min multiplier
|
|||
|
|
- max multiplier
|
|||
|
|
- maximum daily adjustment
|
|||
|
|
- budget safety threshold
|
|||
|
|
- audit log
|
|||
|
|
- rollback capability
|
|||
|
|
|
|||
|
|
> **Default recommendation untuk production awal:** `RECOMMENDATION MODE`
|
|||
|
|
>
|
|||
|
|
> Setelah economy memiliki data historis yang cukup, automatic mode dapat diaktifkan untuk rule tertentu.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 34. Budget Exhaustion
|
|||
|
|
|
|||
|
|
Jika budget exhausted, system harus memiliki policy.
|
|||
|
|
|
|||
|
|
Possible policy:
|
|||
|
|
|
|||
|
|
1. Stop rewards
|
|||
|
|
2. Reduce rewards to minimum
|
|||
|
|
3. Allow non-budget rewards only
|
|||
|
|
4. Disable affected event
|
|||
|
|
5. Continue game but no reward
|
|||
|
|
|
|||
|
|
Policy harus **configurable**.
|
|||
|
|
|
|||
|
|
Game tetap dapat dimainkan meskipun reward sementara tidak tersedia, kecuali Product menentukan game harus ikut disabled.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 35. Limits
|
|||
|
|
|
|||
|
|
Minimum limit concepts:
|
|||
|
|
|
|||
|
|
| Limit | Definisi |
|
|||
|
|
| -------------------- | ----------------------------------------------------- |
|
|||
|
|
| **User Daily Limit** | Jumlah Coin maksimal yang dapat diperoleh user per hari |
|
|||
|
|
| **Game Daily Limit** | Total reward game per hari |
|
|||
|
|
| **Event Limit** | Reward maksimal dari event |
|
|||
|
|
| **Global Limit** | Global economy protection |
|
|||
|
|
| **Session Limit** | Satu session hanya dapat rewarded sekali |
|
|||
|
|
|
|||
|
|
Semua limit harus dapat di-**configure**.
|
|||
|
|
|
|||
|
|
Jika reward melewati limit, reward **dipotong ke sisa limit** (bukan dibatalkan seluruhnya). Jika sisa limit 0, reward menjadi 0.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 36. Reporting & Analytics
|
|||
|
|
|
|||
|
|
Dashboard minimal:
|
|||
|
|
|
|||
|
|
### Game
|
|||
|
|
|
|||
|
|
- DAU
|
|||
|
|
- total plays
|
|||
|
|
- completed games
|
|||
|
|
- average score
|
|||
|
|
- total Coin issued
|
|||
|
|
- average reward
|
|||
|
|
- reward per play
|
|||
|
|
- total Coin spent for entry cost
|
|||
|
|
- total Coin refunded
|
|||
|
|
|
|||
|
|
### Economy
|
|||
|
|
|
|||
|
|
- Coin generated
|
|||
|
|
- Coin spent
|
|||
|
|
- Coin expired
|
|||
|
|
- Coin outstanding
|
|||
|
|
- Point balance
|
|||
|
|
- Point redeemed
|
|||
|
|
|
|||
|
|
### Voucher
|
|||
|
|
|
|||
|
|
- redemption count
|
|||
|
|
- voucher value
|
|||
|
|
- point spent
|
|||
|
|
- realized cost
|
|||
|
|
- stock
|
|||
|
|
- redemption rate
|
|||
|
|
|
|||
|
|
### Budget
|
|||
|
|
|
|||
|
|
- total budget
|
|||
|
|
- realized cost
|
|||
|
|
- remaining budget
|
|||
|
|
- utilization
|
|||
|
|
- forecast
|
|||
|
|
- burn rate
|
|||
|
|
|
|||
|
|
### Event
|
|||
|
|
|
|||
|
|
- participants
|
|||
|
|
- plays
|
|||
|
|
- Coin issued
|
|||
|
|
- redemption
|
|||
|
|
- event cost
|
|||
|
|
- event performance
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 37. Audit Log
|
|||
|
|
|
|||
|
|
Admin changes harus dapat **diaudit**.
|
|||
|
|
|
|||
|
|
Minimal audit:
|
|||
|
|
|
|||
|
|
- who
|
|||
|
|
- what
|
|||
|
|
- before
|
|||
|
|
- after
|
|||
|
|
- timestamp
|
|||
|
|
- reason
|
|||
|
|
- source
|
|||
|
|
|
|||
|
|
Contoh:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Who : Admin A
|
|||
|
|
Change : Reward 10 Coin → 8 Coin
|
|||
|
|
Reason : Budget optimization
|
|||
|
|
Timestamp : 2027-03-10 10:00
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Audit terutama **wajib** untuk:
|
|||
|
|
|
|||
|
|
- reward configuration
|
|||
|
|
- event configuration
|
|||
|
|
- budget
|
|||
|
|
- voucher
|
|||
|
|
- Point/Coin adjustment
|
|||
|
|
- automatic controller changes
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 38. Security & Anti-Abuse
|
|||
|
|
|
|||
|
|
Minimum protection:
|
|||
|
|
|
|||
|
|
- server-side reward calculation
|
|||
|
|
- session validation
|
|||
|
|
- idempotency
|
|||
|
|
- rate limiting
|
|||
|
|
- duplicate detection
|
|||
|
|
- suspicious score detection
|
|||
|
|
- impossible score detection
|
|||
|
|
- concurrent request protection
|
|||
|
|
- atomic wallet update
|
|||
|
|
- atomic redemption
|
|||
|
|
- audit trail
|
|||
|
|
|
|||
|
|
> **Penting:** Jangan mempercayai reward amount dari client.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 39. Recommended System Architecture
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
┌──────────────────┐
|
|||
|
|
│ Next.js Web │
|
|||
|
|
│ Game Portal │
|
|||
|
|
└────────┬─────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌──────────────────┐
|
|||
|
|
│ Go API │
|
|||
|
|
└────────┬─────────┘
|
|||
|
|
│
|
|||
|
|
┌────────────────────┼────────────────────┐
|
|||
|
|
│ │ │
|
|||
|
|
▼ ▼ ▼
|
|||
|
|
Game Management Game Session User/Economy
|
|||
|
|
│ │ │
|
|||
|
|
└────────────────────┼────────────────────┘
|
|||
|
|
▼
|
|||
|
|
Result Validator
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Reward Engine
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Economy Guard
|
|||
|
|
│
|
|||
|
|
┌──────┴──────┐
|
|||
|
|
▼ ▼
|
|||
|
|
Budget Controller Event
|
|||
|
|
│ │
|
|||
|
|
└──────┬──────┘
|
|||
|
|
▼
|
|||
|
|
Coin Wallet
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Coin Ledger
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Point Layer
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Voucher Management
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Redemption Engine
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Realized Cost
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
Budget Controller
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 40. Recommended Development Order
|
|||
|
|
|
|||
|
|
Jangan langsung membuat semua modul sekaligus.
|
|||
|
|
|
|||
|
|
### Phase 1 — Business Rules
|
|||
|
|
|
|||
|
|
Lock:
|
|||
|
|
|
|||
|
|
- Coin
|
|||
|
|
- Point
|
|||
|
|
- Voucher
|
|||
|
|
- Budget
|
|||
|
|
- Redemption
|
|||
|
|
- Reward
|
|||
|
|
- Event
|
|||
|
|
- Limits
|
|||
|
|
- Expiration
|
|||
|
|
|
|||
|
|
### Phase 2 — Domain & Database
|
|||
|
|
|
|||
|
|
Design:
|
|||
|
|
|
|||
|
|
- `games`
|
|||
|
|
- `game_sessions`
|
|||
|
|
- `reward_configs`
|
|||
|
|
- `events`
|
|||
|
|
- `event_reward_configs`
|
|||
|
|
- `wallets`
|
|||
|
|
- `coin_transactions`
|
|||
|
|
- `budgets`
|
|||
|
|
- `budget_transactions`
|
|||
|
|
- `vouchers`
|
|||
|
|
- `voucher_inventory`
|
|||
|
|
- `redemptions`
|
|||
|
|
- `audit_logs`
|
|||
|
|
|
|||
|
|
Exact schema harus ditentukan setelah business rules final.
|
|||
|
|
|
|||
|
|
### Phase 3 — Game Management
|
|||
|
|
|
|||
|
|
Build:
|
|||
|
|
|
|||
|
|
- Game CRUD
|
|||
|
|
- Game status
|
|||
|
|
- Game configuration
|
|||
|
|
- Game version
|
|||
|
|
|
|||
|
|
### Phase 4 — Game Session
|
|||
|
|
|
|||
|
|
Build:
|
|||
|
|
|
|||
|
|
- create session
|
|||
|
|
- entry cost debit (Coin) saat create session
|
|||
|
|
- entry cost refund
|
|||
|
|
- validate session
|
|||
|
|
- complete session
|
|||
|
|
- idempotency
|
|||
|
|
|
|||
|
|
### Phase 5 — Reward Engine
|
|||
|
|
|
|||
|
|
Build:
|
|||
|
|
|
|||
|
|
- fixed reward
|
|||
|
|
- score based
|
|||
|
|
- outcome based
|
|||
|
|
- probability
|
|||
|
|
- multiplier
|
|||
|
|
- tiered
|
|||
|
|
- reward configuration versioning
|
|||
|
|
|
|||
|
|
### Phase 6 — Coin Economy
|
|||
|
|
|
|||
|
|
Build:
|
|||
|
|
|
|||
|
|
- wallet
|
|||
|
|
- ledger
|
|||
|
|
- earning
|
|||
|
|
- expiration integration
|
|||
|
|
- adjustment
|
|||
|
|
- transaction history
|
|||
|
|
|
|||
|
|
### Phase 7 — Voucher Management
|
|||
|
|
|
|||
|
|
Build:
|
|||
|
|
|
|||
|
|
- voucher CRUD
|
|||
|
|
- voucher type
|
|||
|
|
- voucher value
|
|||
|
|
- point cost
|
|||
|
|
- stock
|
|||
|
|
- code pool
|
|||
|
|
- expiration
|
|||
|
|
- status
|
|||
|
|
|
|||
|
|
### Phase 8 — Redemption
|
|||
|
|
|
|||
|
|
Build:
|
|||
|
|
|
|||
|
|
- voucher reservation
|
|||
|
|
- Point deduction
|
|||
|
|
- voucher issue
|
|||
|
|
- rollback
|
|||
|
|
- idempotency
|
|||
|
|
- realized cost
|
|||
|
|
|
|||
|
|
### Phase 9 — Event Management
|
|||
|
|
|
|||
|
|
Build:
|
|||
|
|
|
|||
|
|
- event CRUD
|
|||
|
|
- participating games
|
|||
|
|
- reward modifiers
|
|||
|
|
- missions
|
|||
|
|
- event limits
|
|||
|
|
- event leaderboard
|
|||
|
|
|
|||
|
|
### Phase 10 — Budget Controller
|
|||
|
|
|
|||
|
|
Build:
|
|||
|
|
|
|||
|
|
- budget pool
|
|||
|
|
- realized cost tracking
|
|||
|
|
- burn rate
|
|||
|
|
- forecast
|
|||
|
|
- recommendation
|
|||
|
|
- automatic mode
|
|||
|
|
- guardrails
|
|||
|
|
|
|||
|
|
### Phase 11 — Analytics
|
|||
|
|
|
|||
|
|
Build:
|
|||
|
|
|
|||
|
|
- game analytics
|
|||
|
|
- economy analytics
|
|||
|
|
- voucher analytics
|
|||
|
|
- budget dashboard
|
|||
|
|
- event analytics
|
|||
|
|
|
|||
|
|
### Phase 12 — Phaser Integration
|
|||
|
|
|
|||
|
|
Integrate real games with:
|
|||
|
|
|
|||
|
|
- game session
|
|||
|
|
- result submission
|
|||
|
|
- reward result
|
|||
|
|
- leaderboard
|
|||
|
|
- missions
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 41. Important Implementation Rules for Claude Code
|
|||
|
|
|
|||
|
|
Claude Code **must** follow these principles:
|
|||
|
|
|
|||
|
|
1. Do not invent business rules that are not defined.
|
|||
|
|
2. Do not let Phaser/client determine reward amount.
|
|||
|
|
3. Do not mutate historical reward configuration that has already been used in transactions.
|
|||
|
|
4. Every reward completion must be idempotent.
|
|||
|
|
5. Every wallet mutation must have a ledger record.
|
|||
|
|
6. Voucher redemption must be atomic/recoverable.
|
|||
|
|
7. Budget actual cost is based on successful voucher redemption, according to the current business rule.
|
|||
|
|
8. All games use the same global/shared budget pool.
|
|||
|
|
9. Normal reward and event reward can be active simultaneously.
|
|||
|
|
10. Event configuration must not permanently mutate normal game reward configuration.
|
|||
|
|
11. Automatic reward adjustment must have guardrails.
|
|||
|
|
12. All important admin/economy changes must be auditable.
|
|||
|
|
13. Use database transactions for wallet, redemption, and other financial/economic mutations.
|
|||
|
|
14. Avoid premature microservices. Start with a modular Go backend unless scale requires otherwise.
|
|||
|
|
15. Prefer clear domain boundaries inside the existing backend.
|
|||
|
|
16. Entry cost is debited in Coin when the session is created, in the same transaction as the session.
|
|||
|
|
17. Entry cost and its refund never offset the budget; budget only counts reward and realized voucher cost.
|
|||
|
|
18. Point can only be redeemed for vouchers. It must never be usable as payment or cashed out.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 42. Current Decisions
|
|||
|
|
|
|||
|
|
These decisions are **confirmed for v1**:
|
|||
|
|
|
|||
|
|
| Decision | Rule |
|
|||
|
|
| ----------------------- | -------------------------------- |
|
|||
|
|
| Backend | Go |
|
|||
|
|
| Database | PostgreSQL |
|
|||
|
|
| Game engine | Phaser |
|
|||
|
|
| Temporary session | Redis optional |
|
|||
|
|
| Coin | Virtual game currency |
|
|||
|
|
| Point | Redemption currency |
|
|||
|
|
| Coin : Point | 1 : 1 |
|
|||
|
|
| Coin : Rupiah | 1 Coin = Rp1 (nilai acuan, bukan cash out) |
|
|||
|
|
| Point usage | Voucher redemption only |
|
|||
|
|
| Point as payment | Not allowed |
|
|||
|
|
| Point cash out | Not allowed |
|
|||
|
|
| Game entry cost | Paid in Coin |
|
|||
|
|
| Entry cost debit | At session creation (Start Game) |
|
|||
|
|
| Entry cost refund | Automatic, idempotent; only for system error / game deactivated mid-session |
|
|||
|
|
| Abandoned/expired session | No refund |
|
|||
|
|
| Entry cost vs budget | Not counted; budget = reward only |
|
|||
|
|
| Coin expiration | Existing system |
|
|||
|
|
| Point expiration | Existing system |
|
|||
|
|
| Coin → Point | Existing exchange (manual, configurable rate) |
|
|||
|
|
| Budget model | Shared/global pool |
|
|||
|
|
| Budget period | Monthly (default), configurable |
|
|||
|
|
| Event = Campaign | Same concept |
|
|||
|
|
| Budget counts | Only Point originating from EnakGame rewards |
|
|||
|
|
| Budget actual cost | Successful voucher redemption |
|
|||
|
|
| Realized cost basis | Voucher face value |
|
|||
|
|
| Voucher source | Internal codes + external API |
|
|||
|
|
| Free game | Not allowed (entry cost ≥ 1 Coin) |
|
|||
|
|
| Event budget | Own budget per event, separate from global; funds the event's extra reward |
|
|||
|
|
| Over limit | Reward capped to remaining limit |
|
|||
|
|
| Game reward Coin transfer | Allowed |
|
|||
|
|
| Voucher redemption PIN | Required |
|
|||
|
|
| Legacy games (spin, ferris wheel) | Removed; spin rebuilt as EnakGame game |
|
|||
|
|
| Game budget | Shared pool, not isolated per game |
|
|||
|
|
| Normal + Event reward | Can run together |
|
|||
|
|
| Reward authority | Backend |
|
|||
|
|
| Reward idempotency | Required |
|
|||
|
|
| Wallet | Required |
|
|||
|
|
| Ledger | Required |
|
|||
|
|
| Voucher Management | Core module |
|
|||
|
|
| Redemption | Atomic/idempotent |
|
|||
|
|
| Budget Controller | Required |
|
|||
|
|
| Dynamic reward | Supported |
|
|||
|
|
| Auto adjustment | Supported with guardrails |
|
|||
|
|
| Default adjustment mode | Recommendation/approval |
|
|||
|
|
| Event | Layer over normal game reward |
|
|||
|
|
| Historical configs | Must be versioned |
|
|||
|
|
| Audit | Required |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 43. Open Decisions Before Database Design
|
|||
|
|
|
|||
|
|
The following should remain **explicitly unresolved** until Product/Finance decides them:
|
|||
|
|
|
|||
|
|
1. **Reward rounding**
|
|||
|
|
- integer Coin only
|
|||
|
|
- allow fractional internal calculation but round final reward
|
|||
|
|
2. **Event stacking**
|
|||
|
|
- priority only
|
|||
|
|
- controlled stacking
|
|||
|
|
- maximum multiplier
|
|||
|
|
3. **Budget exhaustion policy** (global dan event)
|
|||
|
|
4. **Automatic Budget Controller thresholds**
|
|||
|
|
5. **Voucher reservation timeout**
|
|||
|
|
|
|||
|
|
Sudah diputuskan (lihat [Section 42](#42-current-decisions)): budget period, event/campaign budget, budget attribution, redemption cost model, Coin → Point conversion, Point expiration, voucher provider, free game, entry cost refund conditions.
|
|||
|
|
|
|||
|
|
These decisions should be finalized before production schema/API is considered final.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 44. Definition of Done for Economy v1
|
|||
|
|
|
|||
|
|
Economy v1 is considered technically ready when:
|
|||
|
|
|
|||
|
|
- [ ] Game session cannot be rewarded twice.
|
|||
|
|
- [ ] Entry cost is debited exactly once per session, atomically with session creation.
|
|||
|
|
- [ ] Session cannot start when Coin balance is insufficient.
|
|||
|
|
- [ ] Entry cost refund is idempotent, and a session cannot be both refunded and rewarded.
|
|||
|
|
- [ ] Sessions failed by system error or game deactivation are refunded automatically; abandoned/expired sessions are not.
|
|||
|
|
- [ ] Entry cost does not affect budget calculation.
|
|||
|
|
- [ ] Point cannot be used as payment or cashed out.
|
|||
|
|
- [ ] Client cannot directly choose reward amount.
|
|||
|
|
- [ ] Reward configuration is versioned.
|
|||
|
|
- [ ] Reward Engine calculates reward correctly.
|
|||
|
|
- [ ] Economy Guard enforces limits.
|
|||
|
|
- [ ] Wallet balance is consistent with ledger.
|
|||
|
|
- [ ] Coin expiration is correctly represented.
|
|||
|
|
- [ ] Voucher stock cannot go negative.
|
|||
|
|
- [ ] Redemption is atomic.
|
|||
|
|
- [ ] Redemption is idempotent.
|
|||
|
|
- [ ] Successful redemption records realized cost.
|
|||
|
|
- [ ] Budget Controller can calculate current utilization.
|
|||
|
|
- [ ] Budget Controller can forecast future cost.
|
|||
|
|
- [ ] Reward adjustment has guardrails.
|
|||
|
|
- [ ] Event reward does not corrupt normal reward configuration.
|
|||
|
|
- [ ] Admin changes are auditable.
|
|||
|
|
- [ ] Finance can reconcile voucher cost against redemption records.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 45. Guiding Principle
|
|||
|
|
|
|||
|
|
| Component | Responsibility |
|
|||
|
|
| ---------------------- | ------------------------------------ |
|
|||
|
|
| **Product/Finance** | Defines the budget |
|
|||
|
|
| **Game Management** | Defines the game |
|
|||
|
|
| **Reward Engine** | Calculates the reward |
|
|||
|
|
| **Economy Guard** | Protects the economy |
|
|||
|
|
| **Budget Controller** | Protects the budget |
|
|||
|
|
| **Voucher Management** | Defines what users can redeem |
|
|||
|
|
| **Redemption** | Records the actual business cost |
|
|||
|
|
| **Ledger** | Provides the audit trail |
|
|||
|
|
|
|||
|
|
The goal is not simply to build a collection of games.
|