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), }) }