Vouchers are what EnakPoint is redeemed for, wherever it came from, so they are not part of EnakGame. Their only link to it is the budget attribution, which does not change. - Admin: /marketing/enakgame/vouchers... -> /marketing/vouchers... - Customer: /customer/enakgame/vouchers -> /customer/vouchers, /customer/enakgame/vouchers/:id/redeem -> /customer/vouchers/:id/redeem, /customer/enakgame/redemptions -> /customer/vouchers/redemptions Roles, handlers and logic stay the same. No client calls these endpoints yet. RFC §7.4 and §11 updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
149 lines
5.0 KiB
Go
149 lines
5.0 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"apskel-pos-be/internal/constants"
|
|
"apskel-pos-be/internal/contract"
|
|
"apskel-pos-be/internal/models"
|
|
"apskel-pos-be/internal/service"
|
|
"apskel-pos-be/internal/util"
|
|
)
|
|
|
|
// EnakGameCustomerHandler serves /customer/enakgame (docs/rfc-enakgame.md §11).
|
|
type EnakGameCustomerHandler struct {
|
|
service service.EnakGameCustomerService
|
|
}
|
|
|
|
func NewEnakGameCustomerHandler(s service.EnakGameCustomerService) *EnakGameCustomerHandler {
|
|
return &EnakGameCustomerHandler{service: s}
|
|
}
|
|
|
|
// ListGames is GET /customer/enakgame/games.
|
|
func (h *EnakGameCustomerHandler) ListGames(c *gin.Context) {
|
|
const method = "EnakGameCustomerHandler::ListGames"
|
|
customerID, ok := customerIDFromGin(c, method)
|
|
if !ok {
|
|
return
|
|
}
|
|
util.HandleResponse(c.Writer, c.Request, h.service.ListGames(c.Request.Context(), customerID), method)
|
|
}
|
|
|
|
// StartSession is POST /customer/enakgame/sessions. It requires Idempotency-Key.
|
|
func (h *EnakGameCustomerHandler) StartSession(c *gin.Context) {
|
|
const method = "EnakGameCustomerHandler::StartSession"
|
|
customerID, ok := customerIDFromGin(c, method)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req contract.StartGameSessionRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
|
contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "game_id is required"),
|
|
}), method)
|
|
return
|
|
}
|
|
util.HandleResponse(c.Writer, c.Request, h.service.StartSession(c.Request.Context(), customerID, &req, idempotencyKey(c)), method)
|
|
}
|
|
|
|
// ListSessions is GET /customer/enakgame/sessions?page=&limit=.
|
|
func (h *EnakGameCustomerHandler) ListSessions(c *gin.Context) {
|
|
const method = "EnakGameCustomerHandler::ListSessions"
|
|
customerID, ok := customerIDFromGin(c, method)
|
|
if !ok {
|
|
return
|
|
}
|
|
page, _ := strconv.Atoi(c.Query("page"))
|
|
limit, _ := strconv.Atoi(c.Query("limit"))
|
|
util.HandleResponse(c.Writer, c.Request, h.service.ListSessions(c.Request.Context(), customerID, page, limit), method)
|
|
}
|
|
|
|
// GetSession is GET /customer/enakgame/sessions/:id.
|
|
func (h *EnakGameCustomerHandler) GetSession(c *gin.Context) {
|
|
const method = "EnakGameCustomerHandler::GetSession"
|
|
customerID, ok := customerIDFromGin(c, method)
|
|
if !ok {
|
|
return
|
|
}
|
|
id, ok := pathID(c, "id", method)
|
|
if !ok {
|
|
return
|
|
}
|
|
util.HandleResponse(c.Writer, c.Request, h.service.GetSession(c.Request.Context(), customerID, id), method)
|
|
}
|
|
|
|
// CompleteSession is POST /customer/enakgame/sessions/:id/complete. The body is read
|
|
// leniently on purpose: a reward amount, or anything else the backend does not take
|
|
// from the client, is ignored rather than refused (P1). An empty body is a result
|
|
// with nothing in it.
|
|
func (h *EnakGameCustomerHandler) CompleteSession(c *gin.Context) {
|
|
const method = "EnakGameCustomerHandler::CompleteSession"
|
|
customerID, ok := customerIDFromGin(c, method)
|
|
if !ok {
|
|
return
|
|
}
|
|
id, ok := pathID(c, "id", method)
|
|
if !ok {
|
|
return
|
|
}
|
|
body, ok := rawBody(c, method)
|
|
if !ok {
|
|
return
|
|
}
|
|
var in models.GameSessionCompleteInput
|
|
if len(bytes.TrimSpace(body)) > 0 {
|
|
if err := json.Unmarshal(body, &in); err != nil {
|
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
|
contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "score must be a whole number and outcome a text"),
|
|
}), method)
|
|
return
|
|
}
|
|
}
|
|
util.HandleResponse(c.Writer, c.Request, h.service.CompleteSession(c.Request.Context(), customerID, id, in), method)
|
|
}
|
|
|
|
// ListVouchers is GET /customer/vouchers.
|
|
func (h *EnakGameCustomerHandler) ListVouchers(c *gin.Context) {
|
|
const method = "EnakGameCustomerHandler::ListVouchers"
|
|
customerID, ok := customerIDFromGin(c, method)
|
|
if !ok {
|
|
return
|
|
}
|
|
util.HandleResponse(c.Writer, c.Request, h.service.ListVouchers(c.Request.Context(), customerID), method)
|
|
}
|
|
|
|
// RedeemVoucher is POST /customer/vouchers/:id/redeem. It requires the PIN
|
|
// and Idempotency-Key; the body holds the PIN, so it is never logged.
|
|
func (h *EnakGameCustomerHandler) RedeemVoucher(c *gin.Context) {
|
|
const method = "EnakGameCustomerHandler::RedeemVoucher"
|
|
customerID, ok := customerIDFromGin(c, method)
|
|
if !ok {
|
|
return
|
|
}
|
|
id, ok := pathID(c, "id", method)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req contract.RedeemVoucherRequest
|
|
if !bindPinRequest(c, &req, method) {
|
|
return
|
|
}
|
|
util.HandleResponse(c.Writer, c.Request, h.service.RedeemVoucher(c.Request.Context(), customerID, id, &req, idempotencyKey(c), pinRequestInfo(c)), method)
|
|
}
|
|
|
|
// ListRedemptions is GET /customer/vouchers/redemptions?page=&limit=.
|
|
func (h *EnakGameCustomerHandler) ListRedemptions(c *gin.Context) {
|
|
const method = "EnakGameCustomerHandler::ListRedemptions"
|
|
customerID, ok := customerIDFromGin(c, method)
|
|
if !ok {
|
|
return
|
|
}
|
|
page, _ := strconv.Atoi(c.Query("page"))
|
|
limit, _ := strconv.Atoi(c.Query("limit"))
|
|
util.HandleResponse(c.Writer, c.Request, h.service.ListRedemptions(c.Request.Context(), customerID, page, limit), method)
|
|
}
|