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>
139 lines
4.9 KiB
Go
139 lines
4.9 KiB
Go
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()}
|
|
}
|