2026-09-30 11:20:29 +07:00
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 ()}
}
2026-09-30 11:37:20 +07:00
// 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" )
}