Promo codes¶
Purpose¶
The Promocodes screen creates, lists, and deactivates promotional codes. Codes can be one-off (single-use), batch-generated by template, or first-deposit-bonus codes (which get a separate game-whitelist sub-config — covered in deposit-bonuses.md). Players redeem via POST /promo/<code> (player-facing).
Audience¶
Marketing (primary), customer support (issue replacement codes), risk (audit promo abuse).
Path in admin-fe¶
| Screen | URL | Page |
|---|---|---|
| Promocodes list | /promocodes |
ebit-admin-fe/src/app/(dashboard)/promocodes/page.tsx |
| Promo detail | /promocodes/[id] |
ebit-admin-fe/src/app/(dashboard)/promocodes/[id]/page.tsx |
| Deposit-bonus subset (first-deposit) | /deposit-bonuses |
covered in deposit-bonuses.md |
Backing API endpoints¶
| Endpoint | Source |
|---|---|
GET /admin/promo (list) |
apps/api/src/promo/controllers/admin-promo.controller.ts:46 |
GET /admin/promo/:id |
apps/api/src/promo/controllers/admin-promo.controller.ts:74 |
PUT /admin/promo (create) |
apps/api/src/promo/controllers/admin-promo.controller.ts:147 |
PATCH /admin/promo/:id (update) |
apps/api/src/promo/controllers/admin-promo.controller.ts:157 |
DELETE /admin/promo/:id (deactivate; restorable) |
apps/api/src/promo/controllers/admin-promo.controller.ts:168 |
PUT /admin/promo/many (batch by template) |
apps/api/src/promo/controllers/admin-promo.controller.ts:81 |
GET /admin/user-promo (list claims) |
apps/api/src/promo/controllers/admin-promo.controller.ts:56 |
DELETE /admin/user-promo/:id (cancel a claim) |
apps/api/src/promo/controllers/admin-promo.controller.ts:66 |
Frontend wiring: ebit-admin-fe/src/queries/promocodes/.
Key actions¶
| Action | Required permission | API call | DB tables touched | Audit-logged? |
|---|---|---|---|---|
| List codes | promo.code.view |
GET /admin/promo |
PromoCode |
yes |
| View one code | promo.code.view |
GET /admin/promo/:id |
PromoCode, UserPromoCode (claims) |
yes |
| Create code | promo.code.edit |
PUT /admin/promo |
PromoCode |
yes |
| Update code | promo.code.edit |
PATCH /admin/promo/:id |
PromoCode |
yes |
| Deactivate code (restorable) | promo.code.edit |
DELETE /admin/promo/:id |
PromoCode.deactivatedAt |
yes |
| Batch-create from template | promo.code.edit |
PUT /admin/promo/many |
PromoCode (n inserts) |
yes |
| List claims | promo.code.view |
GET /admin/user-promo |
UserPromoCode |
yes |
| Cancel a claim (refund / void) | promo.code.edit |
DELETE /admin/user-promo/:id |
UserPromoCode, Transaction |
yes |
Filters and views¶
- Status — active / deactivated / expired / fully claimed.
- Type —
cash,bonus,free-spins,first-deposit-match. - Date range —
createdAtorexpiresAt. - Search — by code (case-insensitive substring).
- Claims drill-in — paginated list of users who used this code.
Common workflows¶
- Single-use makegood code for a player. CS opens
/promocodes, clicks Create. Sets code = "MAKEGOOD-2025-1234" (unique), value = 50 USD, expires in 7 days, single-use. Saves. Tells player to redeem via player site. - Campaign batch. Marketing wants 5,000 unique codes for a partner campaign. Click Batch Create, configure template (
PARTNER-XXXXXX), count, value, expiry. Backend writes 5k rows. Admin downloads the list (CSV via player UI or DB). - Promo code abuse audit. Risk filters
/admin/user-promobydateFrom=last 24h. Sorts byuserIdto find one user redeeming many. Cross-check with bets-history.md for matching bonus-arbitrage wagering pattern. - Cancel a fraudulent claim. Risk opens the claim row in
/admin/user-promo, clicks Cancel. Backend voids the claim, refunds platform side via reverseTransaction. Logs inAdminActionLog. - Deactivate a leaked code. A code got posted to a public Telegram group. Marketing finds the code, clicks Deactivate. Backend sets
PromoCode.deactivatedAt. New claims rejected. Existing claims preserved (still valid).
Edge cases / gotchas¶
- Codes are case-insensitive on lookup. Backend stores lowercase; UI shows uppercase.
- Deactivate is soft. A
deactivatedAt-set code can be restored byPATCH /admin/promo/:id { deactivatedAt: null }. - Single-use is enforced via
UserPromoCodeunique on(promoCodeId, userId)— but batch codes typically havemaxClaims=1per code with multiple codes. The two patterns are easy to confuse. - First-deposit-bonus codes carry a game-whitelist. Separate endpoints (
/admin/promo/deposit/:id/games). See deposit-bonuses.md. - No expiry sweeper in the admin panel. Expired codes are filtered out at redeem time (
promo-validator.service.ts). They remain in the DB. - Cancelling a claim does not revoke the player's bets placed using the bonus. To clawback bets, you must refund manually via user-profile.md deduct.
PUT /admin/promo/manywith > 10k entries can timeout. Frontend chunks into batches; backend doesn't paginate the create itself.- Bots are excluded from claims by
User.isBot=truecheck inpromo-validator.service.ts.
Cross-links¶
- First-deposit subset: deposit-bonuses.md
- Per-user claimed codes: user-profile.md → Promo tab
- Player-side redeem flow:
apps/api/src/promo/controllers/promo.controller.ts - Audit: admin-logs.md
- Permission keys:
libs/auth/src/permissions/const.ts:208-215 - Customer comms (announce campaign):
handover/customer-comms/
Sequence — creating a single-use makegood code¶
sequenceDiagram
actor cs as CS Operator
participant admin-fe
participant api
participant pg as Postgres
cs->>admin-fe: open /promocodes, click Create, fill {code, value, expiresAt, maxClaims=1}
admin-fe->>api: PUT /admin/promo { ... }
api->>api: PermissionGuard('promo.code.edit')
api->>pg: INSERT PromoCode (code=lower, value, expiresAt, maxClaims=1)
api->>pg: INSERT AdminActionLog
api-->>admin-fe: PromoCodeDto
admin-fe-->>cs: code added to list, copy-to-clipboard helper
Note over cs: shares code with player via Zendesk
Note over admin-fe,pg: player redeems via POST /promo/<code> (player-facing)