package handler import ( "strconv" "github.com/gin-gonic/gin" "github.com/google/uuid" "apskel-pos-be/internal/appcontext" "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" ) // CustomerPinHandler serves /customer/pin and the dashboard's PIN endpoints // (docs/prd-point-coin.md F11). Request bodies hold PINs, so nothing here logs a body, // and binding errors are reported without the values sent. type CustomerPinHandler struct { pinService service.CustomerPinService } func NewCustomerPinHandler(pinService service.CustomerPinService) *CustomerPinHandler { return &CustomerPinHandler{pinService: pinService} } func (h *CustomerPinHandler) Status(c *gin.Context) { customerID, ok := customerIDFromGin(c, "CustomerPinHandler::Status") if !ok { return } util.HandleResponse(c.Writer, c.Request, h.pinService.Status(c.Request.Context(), customerID), "CustomerPinHandler::Status") } func (h *CustomerPinHandler) RequestOtp(c *gin.Context) { customerID, ok := customerIDFromGin(c, "CustomerPinHandler::RequestOtp") if !ok { return } var req contract.RequestPinOtpRequest if !bindPinRequest(c, &req, "CustomerPinHandler::RequestOtp") { return } util.HandleResponse(c.Writer, c.Request, h.pinService.RequestOtp(c.Request.Context(), customerID, &req), "CustomerPinHandler::RequestOtp") } func (h *CustomerPinHandler) CreatePin(c *gin.Context) { customerID, ok := customerIDFromGin(c, "CustomerPinHandler::CreatePin") if !ok { return } var req contract.CreateCustomerPinRequest if !bindPinRequest(c, &req, "CustomerPinHandler::CreatePin") { return } util.HandleResponse(c.Writer, c.Request, h.pinService.CreatePin(c.Request.Context(), customerID, &req, pinRequestInfo(c)), "CustomerPinHandler::CreatePin") } func (h *CustomerPinHandler) ChangePin(c *gin.Context) { customerID, ok := customerIDFromGin(c, "CustomerPinHandler::ChangePin") if !ok { return } var req contract.ChangeCustomerPinRequest if !bindPinRequest(c, &req, "CustomerPinHandler::ChangePin") { return } util.HandleResponse(c.Writer, c.Request, h.pinService.ChangePin(c.Request.Context(), customerID, &req, pinRequestInfo(c)), "CustomerPinHandler::ChangePin") } func (h *CustomerPinHandler) ResetPin(c *gin.Context) { customerID, ok := customerIDFromGin(c, "CustomerPinHandler::ResetPin") if !ok { return } var req contract.ResetCustomerPinRequest if !bindPinRequest(c, &req, "CustomerPinHandler::ResetPin") { return } util.HandleResponse(c.Writer, c.Request, h.pinService.ResetPin(c.Request.Context(), customerID, &req, pinRequestInfo(c)), "CustomerPinHandler::ResetPin") } // RemovePin is DELETE /marketing/customers/:id/pin. func (h *CustomerPinHandler) RemovePin(c *gin.Context) { customerID, ok := parseUUIDParam(c, "id", "CustomerPinHandler::RemovePin") if !ok { return } var req contract.RemoveCustomerPinRequest if !bindPinRequest(c, &req, "CustomerPinHandler::RemovePin") { return } ctx := c.Request.Context() util.HandleResponse(c.Writer, c.Request, h.pinService.RemovePin(ctx, appcontext.FromGinContext(ctx), customerID, &req, pinRequestInfo(c)), "CustomerPinHandler::RemovePin") } // ListSecurityEvents is GET /marketing/customers/:id/security-events. func (h *CustomerPinHandler) ListSecurityEvents(c *gin.Context) { customerID, ok := parseUUIDParam(c, "id", "CustomerPinHandler::ListSecurityEvents") if !ok { return } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) ctx := c.Request.Context() util.HandleResponse(c.Writer, c.Request, h.pinService.ListSecurityEvents(ctx, appcontext.FromGinContext(ctx), customerID, page, limit), "CustomerPinHandler::ListSecurityEvents") } // bindPinRequest binds a JSON body. The error it reports names what is wrong, never the // values, since those can be PINs. func bindPinRequest(c *gin.Context, req interface{}, method string) bool { if err := c.ShouldBindJSON(req); err != nil { util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "invalid request body: required fields are missing or have the wrong type"), }), method) return false } return true } // customerIDFromGin reads the customer set by CustomerAuthMiddleware. func customerIDFromGin(c *gin.Context, method string) (uuid.UUID, bool) { raw, _ := c.Get("customer_id") s, _ := raw.(string) id, err := uuid.Parse(s) if err != nil { util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ contract.NewResponseError(constants.ValidationErrorCode, constants.AuthHandlerEntity, "Customer ID not found"), }), method) return uuid.Nil, false } return id, true } func pinRequestInfo(c *gin.Context) models.CustomerPinRequestInfo { return models.CustomerPinRequestInfo{IPAddress: c.ClientIP(), UserAgent: c.Request.UserAgent()} } // IssuePaymentCode is POST /customer/wallet/payment-code. func (h *CustomerPinHandler) IssuePaymentCode(c *gin.Context) { customerID, ok := customerIDFromGin(c, "CustomerPinHandler::IssuePaymentCode") if !ok { return } var req contract.IssuePaymentCodeRequest if !bindPinRequest(c, &req, "CustomerPinHandler::IssuePaymentCode") { return } util.HandleResponse(c.Writer, c.Request, h.pinService.IssuePaymentCode(c.Request.Context(), customerID, &req, pinRequestInfo(c)), "CustomerPinHandler::IssuePaymentCode") }