EnakGame phases 1-8 of docs/tasks-enakgame.md (EG-101 to EG-803), built on the
existing EnakPoint/EnakCoin wallet (docs/rfc-enakgame.md).
Foundation (phase 1)
- Migrations 000103-000106: games extended with organization, slug, status,
entry cost and result rules, old games archived (not deleted); budgets,
versioned reward configs, sessions and session rewards; the ledger types
GAME_SPEND_REFUND, GAME_REWARD and REWARD_REDEEM_REFUND; audit_logs.
- AuditLogger writes in the caller's transaction only.
- enakgame.limit.user_daily and global_daily organization settings.
Games and sessions (phases 2-4)
- Admin /marketing/enakgame: games, reward config versions (immutable but for
status, one ACTIVE per game), budgets with non-overlapping global periods and
a daily job opening the next month.
- Customer /customer/enakgame: start (Idempotency-Key, entry cost and config
frozen on the session), complete (result validation, reward engine, max_reward
cap, daily limits via game_reward_counters, one GAME_REWARD per budget),
automatic refunds for system errors and deactivated games, and a session job.
- Reward engine: FIXED, SCORE_BASED, OUTCOME_BASED, PROBABILITY (crypto/rand),
rounded down.
Vouchers and budgets (phases 5-6)
- Migration 000108 and 000107: vouchers, codes, redemptions, cost attribution;
Economy Guard counters.
- STATIC and CODE_POOL redemption in one transaction with the REDEEM PIN action;
realized cost traced through the lots to the budget that paid the reward.
- Budget metrics: realized cost, forecast, exposure and status. Migrations
000109-000110 add the wallet_lots indexes they need, built CONCURRENTLY.
Events (phase 7)
- Migration 000111: game events, each with its own EVENT budget. Event extras
stack per PRD §16 defaults, with event and per-customer limits.
External vouchers (phase 8)
- VoucherProvider contract, two-step PENDING redemption and a recovery job,
tested with a fake provider. No provider adapter is registered yet, so
EXTERNAL vouchers stay out of the catalog.
Not yet decided before release: reward rounding, event stacking, budget
exhaustion policy and thresholds (RFC §19.2). Migrations 000103-000111 have
not been run on any shared database.
Also fixes a leftover PAYMENT filter in a wallet test and a data race in a
test PIN fake.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Adds POST /customer/wallet/payment-code (docs/prd-point-coin.md F9, K8,
PC-304). The customer approves with their PIN on their own phone and gets a
6-digit code, as digits and as a QR payload (enakpoint:<code>) for the app
to render, valid for two minutes. The PIN is never typed at the cashier.
Codes are drawn from crypto/rand and stored in Redis with SET NX and a TTL,
bound to the customer; a new code retires the previous one. Redeeming is a
single Lua step that uses the code up only if it belongs to the order's
customer, so it stays one-time under a race, and a cashier scanning it
against the wrong order does not burn it for its owner, which a plain
GETDEL would. Expired, used, unknown and other customers' codes are all
refused alike.
Tests run against miniredis, added as a test dependency.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>