Revert "feat(loyalty): EnakPoint & EnakCoin" (#32)
This reverts merge commit645da30, returning main tof0ff59f. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
645da3048e
commit
4e24f9bbb0
@@ -43,7 +43,7 @@ func (v *CampaignValidatorImpl) ValidateCreateCampaignRequest(req *contract.Crea
|
||||
|
||||
// Validate campaign type
|
||||
if !v.isValidCampaignType(req.Type) {
|
||||
return errors.New("invalid campaign type. Valid types are: REWARD, POINTS, COINS, MIXED"), constants.ValidationErrorCode
|
||||
return errors.New("invalid campaign type. Valid types are: REWARD, POINTS, TOKENS, MIXED"), constants.ValidationErrorCode
|
||||
}
|
||||
|
||||
// Validate date range
|
||||
@@ -79,7 +79,7 @@ func (v *CampaignValidatorImpl) ValidateUpdateCampaignRequest(req *contract.Upda
|
||||
|
||||
// Validate campaign type
|
||||
if !v.isValidCampaignType(req.Type) {
|
||||
return errors.New("invalid campaign type. Valid types are: REWARD, POINTS, COINS, MIXED"), constants.ValidationErrorCode
|
||||
return errors.New("invalid campaign type. Valid types are: REWARD, POINTS, TOKENS, MIXED"), constants.ValidationErrorCode
|
||||
}
|
||||
|
||||
// Validate date range
|
||||
@@ -113,7 +113,7 @@ func (v *CampaignValidatorImpl) ValidateListCampaignsRequest(req *contract.ListC
|
||||
|
||||
// Validate campaign type filter if provided
|
||||
if req.Type != "" && !v.isValidCampaignType(req.Type) {
|
||||
return errors.New("invalid campaign type filter. Valid types are: REWARD, POINTS, COINS, MIXED"), constants.ValidationErrorCode
|
||||
return errors.New("invalid campaign type filter. Valid types are: REWARD, POINTS, TOKENS, MIXED"), constants.ValidationErrorCode
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
@@ -223,7 +223,7 @@ func (v *CampaignValidatorImpl) ValidateListCampaignRulesRequest(req *contract.L
|
||||
|
||||
// Validate reward type filter if provided
|
||||
if req.RewardType != "" && !v.isValidRewardType(req.RewardType) {
|
||||
return errors.New("invalid reward type filter. Valid types are: POINTS, COINS, REWARD"), constants.ValidationErrorCode
|
||||
return errors.New("invalid reward type filter. Valid types are: POINTS, TOKENS, REWARD"), constants.ValidationErrorCode
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
@@ -254,7 +254,7 @@ func (v *CampaignValidatorImpl) ValidateDeleteCampaignRuleRequest(req *contract.
|
||||
}
|
||||
|
||||
func (v *CampaignValidatorImpl) isValidCampaignType(campaignType string) bool {
|
||||
validTypes := []string{"REWARD", "POINTS", "COINS", "TOKENS", "MIXED"}
|
||||
validTypes := []string{"REWARD", "POINTS", "TOKENS", "MIXED"}
|
||||
return contains(validTypes, campaignType)
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ func (v *CampaignValidatorImpl) isValidRuleType(ruleType string) bool {
|
||||
}
|
||||
|
||||
func (v *CampaignValidatorImpl) isValidRewardType(rewardType string) bool {
|
||||
validTypes := []string{"POINTS", "COINS", "TOKENS", "REWARD"}
|
||||
validTypes := []string{"POINTS", "TOKENS", "REWARD"}
|
||||
return contains(validTypes, rewardType)
|
||||
}
|
||||
|
||||
@@ -293,11 +293,11 @@ func (v *CampaignValidatorImpl) validateCampaignRule(rule *contract.CampaignRule
|
||||
|
||||
// Validate reward type
|
||||
if !v.isValidRewardType(rule.RewardType) {
|
||||
return errors.New("invalid reward type in rule " + string(rune(ruleNumber)) + ". Valid types are: POINTS, COINS, REWARD")
|
||||
return errors.New("invalid reward type in rule " + string(rune(ruleNumber)) + ". Valid types are: POINTS, TOKENS, REWARD")
|
||||
}
|
||||
|
||||
// Validate reward value based on reward type
|
||||
if rule.RewardType == "POINTS" || rule.RewardType == "COINS" || rule.RewardType == "TOKENS" {
|
||||
if rule.RewardType == "POINTS" || rule.RewardType == "TOKENS" {
|
||||
if rule.RewardValue == nil || *rule.RewardValue <= 0 {
|
||||
return errors.New("reward value must be positive for " + rule.RewardType + " type in rule " + string(rune(ruleNumber)))
|
||||
}
|
||||
|
||||
@@ -9,6 +9,20 @@ import (
|
||||
)
|
||||
|
||||
type GamificationValidator interface {
|
||||
// Customer Points
|
||||
ValidateCreateCustomerPointsRequest(req *contract.CreateCustomerPointsRequest) (error, string)
|
||||
ValidateUpdateCustomerPointsRequest(req *contract.UpdateCustomerPointsRequest) (error, string)
|
||||
ValidateListCustomerPointsRequest(req *contract.ListCustomerPointsRequest) (error, string)
|
||||
ValidateAddCustomerPointsRequest(req *contract.AddCustomerPointsRequest) (error, string)
|
||||
ValidateDeductCustomerPointsRequest(req *contract.DeductCustomerPointsRequest) (error, string)
|
||||
|
||||
// Customer Tokens
|
||||
ValidateCreateCustomerTokensRequest(req *contract.CreateCustomerTokensRequest) (error, string)
|
||||
ValidateUpdateCustomerTokensRequest(req *contract.UpdateCustomerTokensRequest) (error, string)
|
||||
ValidateListCustomerTokensRequest(req *contract.ListCustomerTokensRequest) (error, string)
|
||||
ValidateAddCustomerTokensRequest(req *contract.AddCustomerTokensRequest) (error, string)
|
||||
ValidateDeductCustomerTokensRequest(req *contract.DeductCustomerTokensRequest) (error, string)
|
||||
|
||||
// Tiers
|
||||
ValidateCreateTierRequest(req *contract.CreateTierRequest) (error, string)
|
||||
ValidateUpdateTierRequest(req *contract.UpdateTierRequest) (error, string)
|
||||
@@ -46,6 +60,152 @@ func NewGamificationValidator() *GamificationValidatorImpl {
|
||||
}
|
||||
}
|
||||
|
||||
// Customer Points Validators
|
||||
func (v *GamificationValidatorImpl) ValidateCreateCustomerPointsRequest(req *contract.CreateCustomerPointsRequest) (error, string) {
|
||||
if err := v.validate.Struct(req); err != nil {
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.Balance < 0 {
|
||||
return errors.New("balance cannot be negative"), "INVALID_BALANCE"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (v *GamificationValidatorImpl) ValidateUpdateCustomerPointsRequest(req *contract.UpdateCustomerPointsRequest) (error, string) {
|
||||
if err := v.validate.Struct(req); err != nil {
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.Balance < 0 {
|
||||
return errors.New("balance cannot be negative"), "INVALID_BALANCE"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (v *GamificationValidatorImpl) ValidateListCustomerPointsRequest(req *contract.ListCustomerPointsRequest) (error, string) {
|
||||
if err := v.validate.Struct(req); err != nil {
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.Page <= 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.Limit <= 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
if req.Limit > 100 {
|
||||
req.Limit = 100
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (v *GamificationValidatorImpl) ValidateAddCustomerPointsRequest(req *contract.AddCustomerPointsRequest) (error, string) {
|
||||
if err := v.validate.Struct(req); err != nil {
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.Points <= 0 {
|
||||
return errors.New("points must be greater than 0"), "INVALID_POINTS"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (v *GamificationValidatorImpl) ValidateDeductCustomerPointsRequest(req *contract.DeductCustomerPointsRequest) (error, string) {
|
||||
if err := v.validate.Struct(req); err != nil {
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.Points <= 0 {
|
||||
return errors.New("points must be greater than 0"), "INVALID_POINTS"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// Customer Tokens Validators
|
||||
func (v *GamificationValidatorImpl) ValidateCreateCustomerTokensRequest(req *contract.CreateCustomerTokensRequest) (error, string) {
|
||||
if err := v.validate.Struct(req); err != nil {
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.Balance < 0 {
|
||||
return errors.New("balance cannot be negative"), "INVALID_BALANCE"
|
||||
}
|
||||
|
||||
validTokenTypes := []string{"SPIN", "RAFFLE", "MINIGAME"}
|
||||
if !contains(validTokenTypes, req.TokenType) {
|
||||
return errors.New("invalid token type"), "INVALID_TOKEN_TYPE"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (v *GamificationValidatorImpl) ValidateUpdateCustomerTokensRequest(req *contract.UpdateCustomerTokensRequest) (error, string) {
|
||||
if err := v.validate.Struct(req); err != nil {
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.Balance < 0 {
|
||||
return errors.New("balance cannot be negative"), "INVALID_BALANCE"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (v *GamificationValidatorImpl) ValidateListCustomerTokensRequest(req *contract.ListCustomerTokensRequest) (error, string) {
|
||||
if err := v.validate.Struct(req); err != nil {
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.Page <= 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.Limit <= 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
if req.Limit > 100 {
|
||||
req.Limit = 100
|
||||
}
|
||||
|
||||
if req.TokenType != "" {
|
||||
validTokenTypes := []string{"SPIN", "RAFFLE", "MINIGAME"}
|
||||
if !contains(validTokenTypes, req.TokenType) {
|
||||
return errors.New("invalid token type"), "INVALID_TOKEN_TYPE"
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (v *GamificationValidatorImpl) ValidateAddCustomerTokensRequest(req *contract.AddCustomerTokensRequest) (error, string) {
|
||||
if err := v.validate.Struct(req); err != nil {
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.Tokens <= 0 {
|
||||
return errors.New("tokens must be greater than 0"), "INVALID_TOKENS"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (v *GamificationValidatorImpl) ValidateDeductCustomerTokensRequest(req *contract.DeductCustomerTokensRequest) (error, string) {
|
||||
if err := v.validate.Struct(req); err != nil {
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.Tokens <= 0 {
|
||||
return errors.New("tokens must be greater than 0"), "INVALID_TOKENS"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// Tier Validators
|
||||
func (v *GamificationValidatorImpl) ValidateCreateTierRequest(req *contract.CreateTierRequest) (error, string) {
|
||||
if err := v.validate.Struct(req); err != nil {
|
||||
@@ -276,8 +436,8 @@ func (v *GamificationValidatorImpl) ValidateCreateGamePlayRequest(req *contract.
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.CoinsUsed < 0 {
|
||||
return errors.New("coins used cannot be negative"), "INVALID_COINS_USED"
|
||||
if req.TokenUsed < 0 {
|
||||
return errors.New("token used cannot be negative"), "INVALID_TOKEN_USED"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
@@ -306,6 +466,10 @@ func (v *GamificationValidatorImpl) ValidatePlayGameRequest(req *contract.PlayGa
|
||||
return err, "VALIDATION_ERROR"
|
||||
}
|
||||
|
||||
if req.TokenUsed < 0 {
|
||||
return errors.New("token used cannot be negative"), "INVALID_TOKEN_USED"
|
||||
}
|
||||
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,6 @@ func (v *PaymentMethodValidatorImpl) isValidPaymentMethodType(paymentMethodType
|
||||
string(constants.PaymentMethodTypeDigitalWallet),
|
||||
string(constants.PaymentMethodTypeQR),
|
||||
string(constants.PaymentMethodTypeEDC),
|
||||
string(constants.PaymentMethodTypePoint),
|
||||
}
|
||||
|
||||
for _, validType := range validTypes {
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
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, ""
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
)
|
||||
|
||||
func TestWalletValidator_AdjustRequest(t *testing.T) {
|
||||
v := NewWalletValidator()
|
||||
|
||||
ok := &contract.AdjustCustomerWalletRequest{Currency: " point ", Amount: -25, Reason: " komplain #45 "}
|
||||
err, _ := v.ValidateAdjustRequest(ok)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, constants.WalletCurrencyPoint, ok.Currency, "currency is normalised")
|
||||
assert.Equal(t, "komplain #45", ok.Reason, "reason is trimmed")
|
||||
|
||||
legit := &contract.AdjustCustomerWalletRequest{Currency: "COIN", Amount: 5, Reason: "Bayar tunai tapi poin tidak masuk"}
|
||||
err, _ = v.ValidateAdjustRequest(legit)
|
||||
assert.NoError(t, err, "mentioning a cash payment is not a cash-out")
|
||||
|
||||
for name, req := range map[string]*contract.AdjustCustomerWalletRequest{
|
||||
"nil": nil,
|
||||
"no currency": {Amount: 5, Reason: "x"},
|
||||
"unknown currency": {Currency: "GOLD", Amount: 5, Reason: "x"},
|
||||
"zero amount": {Currency: "POINT", Amount: 0, Reason: "x"},
|
||||
"no reason": {Currency: "POINT", Amount: 5, Reason: " "},
|
||||
"reason too long": {Currency: "POINT", Amount: 5, Reason: strings.Repeat("a", 256)},
|
||||
"cash-out reason": {Currency: "POINT", Amount: -500, Reason: "Pencairan saldo ke rekening"},
|
||||
"cash-out, English": {Currency: "POINT", Amount: -500, Reason: "customer asked to cash out"},
|
||||
"long key": {Currency: "POINT", Amount: 5, Reason: "x", IdempotencyKey: strings.Repeat("k", 51)},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err, code := v.ValidateAdjustRequest(req)
|
||||
assert.Error(t, err)
|
||||
assert.NotEmpty(t, code)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user