Reapply "feat(loyalty): EnakPoint & EnakCoin" (#32)

This reverts commit 4e24f9bbb0.
This commit is contained in:
efrilm
2026-09-30 15:31:11 +07:00
parent 5e28b05770
commit c6062c4bb9
195 changed files with 20058 additions and 2177 deletions
+82
View File
@@ -0,0 +1,82 @@
package service
import (
"context"
"errors"
"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/processor"
"apskel-pos-be/internal/repository"
)
// WalletAdminService is the dashboard's access to a customer's wallet
// (docs/prd-point-coin.md F7). Every call is scoped to the caller's organization.
type WalletAdminService interface {
GetCustomerWallet(ctx context.Context, apctx *appcontext.ContextInfo, customerID uuid.UUID, query models.ListCustomerWalletTransactionsQuery) *contract.Response
AdjustCustomerWallet(ctx context.Context, apctx *appcontext.ContextInfo, customerID uuid.UUID, req *contract.AdjustCustomerWalletRequest) *contract.Response
// TraceTransaction follows a ledger row lot by lot back to where its balance came from.
TraceTransaction(ctx context.Context, apctx *appcontext.ContextInfo, transactionID uuid.UUID) *contract.Response
}
type WalletAdminServiceImpl struct {
walletAdminProcessor *processor.WalletAdminProcessor
walletTraceProcessor *processor.WalletTraceProcessor
}
func NewWalletAdminService(walletAdminProcessor *processor.WalletAdminProcessor, walletTraceProcessor *processor.WalletTraceProcessor) *WalletAdminServiceImpl {
return &WalletAdminServiceImpl{walletAdminProcessor: walletAdminProcessor, walletTraceProcessor: walletTraceProcessor}
}
func (s *WalletAdminServiceImpl) TraceTransaction(ctx context.Context, apctx *appcontext.ContextInfo, transactionID uuid.UUID) *contract.Response {
trace, err := s.walletTraceProcessor.Trace(ctx, apctx.OrganizationID, transactionID)
if err != nil {
return walletErrorResponse(err)
}
return contract.BuildSuccessResponse(trace)
}
func (s *WalletAdminServiceImpl) GetCustomerWallet(ctx context.Context, apctx *appcontext.ContextInfo, customerID uuid.UUID, query models.ListCustomerWalletTransactionsQuery) *contract.Response {
wallet, err := s.walletAdminProcessor.GetCustomerWallet(ctx, apctx.OrganizationID, customerID, query)
if err != nil {
return walletErrorResponse(err)
}
return contract.BuildSuccessResponse(wallet)
}
func (s *WalletAdminServiceImpl) AdjustCustomerWallet(ctx context.Context, apctx *appcontext.ContextInfo, customerID uuid.UUID, req *contract.AdjustCustomerWalletRequest) *contract.Response {
result, err := s.walletAdminProcessor.Adjust(ctx, apctx.OrganizationID, apctx.UserID, customerID, models.WalletAdjustment{
Currency: req.Currency,
Amount: req.Amount,
Reason: req.Reason,
IdempotencyKey: req.IdempotencyKey,
})
if err != nil {
return walletErrorResponse(err)
}
return contract.BuildSuccessResponse(result)
}
// walletErrorResponse tells the dashboard's mistakes apart from server failures.
func walletErrorResponse(err error) *contract.Response {
code, message := constants.InternalServerErrorCode, err.Error()
switch {
case errors.Is(err, repository.ErrWalletNotFound):
code, message = constants.NotFoundErrorCode, "customer not found"
case errors.Is(err, repository.ErrWalletTransactionNotFound):
code, message = constants.NotFoundErrorCode, "wallet transaction not found"
case errors.Is(err, repository.ErrWalletInsufficientBalance):
code, message = constants.ValidationErrorCode, "the adjustment would take more than the customer can spend"
case errors.Is(err, processor.ErrInvalidWalletQuery),
errors.Is(err, processor.ErrWalletInvalidEntry),
errors.Is(err, processor.ErrWalletIdempotencyConflict):
code = constants.ValidationErrorCode
}
return contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(code, constants.WalletServiceEntity, message),
})
}