2026-09-30 09:36:18 +07:00
|
|
|
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
|
2026-09-30 12:25:17 +07:00
|
|
|
// 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
|
2026-09-30 09:36:18 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type WalletAdminServiceImpl struct {
|
|
|
|
|
walletAdminProcessor *processor.WalletAdminProcessor
|
2026-09-30 12:25:17 +07:00
|
|
|
walletTraceProcessor *processor.WalletTraceProcessor
|
2026-09-30 09:36:18 +07:00
|
|
|
}
|
|
|
|
|
|
2026-09-30 12:25:17 +07:00
|
|
|
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)
|
2026-09-30 09:36:18 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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"
|
2026-09-30 12:25:17 +07:00
|
|
|
case errors.Is(err, repository.ErrWalletTransactionNotFound):
|
|
|
|
|
code, message = constants.NotFoundErrorCode, "wallet transaction not found"
|
2026-09-30 09:36:18 +07:00
|
|
|
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),
|
|
|
|
|
})
|
|
|
|
|
}
|