Files
apskel-pos-backend/internal/validator/wallet_validator.go
2026-09-30 15:31:44 +07:00

67 lines
2.3 KiB
Go

package validator
import (
"errors"
"strings"
"unicode/utf8"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
)
// walletAdjustmentReasonLimit is the size of wallet_transactions.reason.
const walletAdjustmentReasonLimit = 255
// walletCashOutWords are reasons that describe turning balance into money. EnakPoint
// and EnakCoin can never be cashed out, and an adjustment must not be used to record
// that it happened (docs/prd-point-coin.md K7). Words like "tunai" are left out on
// purpose: "paid in cash but the points never arrived" is a legitimate reason.
var walletCashOutWords = []string{"pencairan", "cairkan", "cash out", "cashout", "withdraw"}
type WalletValidator interface {
ValidateAdjustRequest(req *contract.AdjustCustomerWalletRequest) (error, string)
}
type WalletValidatorImpl struct{}
func NewWalletValidator() *WalletValidatorImpl {
return &WalletValidatorImpl{}
}
func (v *WalletValidatorImpl) ValidateAdjustRequest(req *contract.AdjustCustomerWalletRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
req.Currency = strings.ToUpper(strings.TrimSpace(req.Currency))
if req.Currency == "" {
return errors.New("currency is required"), constants.MissingFieldErrorCode
}
if !constants.IsValidWalletCurrency(req.Currency) {
return errors.New("currency must be POINT or COIN"), constants.MalformedFieldErrorCode
}
if req.Amount == 0 {
return errors.New("amount must not be zero: use a positive amount to add and a negative one to take away"), constants.ValidationErrorCode
}
req.Reason = strings.TrimSpace(req.Reason)
if req.Reason == "" {
return errors.New("reason is required"), constants.MissingFieldErrorCode
}
if utf8.RuneCountInString(req.Reason) > walletAdjustmentReasonLimit {
return errors.New("reason must be at most 255 characters"), constants.MalformedFieldErrorCode
}
lower := strings.ToLower(req.Reason)
for _, word := range walletCashOutWords {
if strings.Contains(lower, word) {
return errors.New("EnakPoint and EnakCoin cannot be cashed out, so an adjustment cannot be for a cash-out"), constants.ValidationErrorCode
}
}
if len(req.IdempotencyKey) > 50 {
return errors.New("idempotency_key must be at most 50 characters"), constants.MalformedFieldErrorCode
}
return nil, ""
}