Files
apskel-pos-backend/internal/service/customer_wallet_service.go
T
2026-09-30 15:31:44 +07:00

92 lines
3.7 KiB
Go

package service
import (
"context"
"errors"
"github.com/google/uuid"
"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"
)
// CustomerWalletService moves balance on the customer's own request: exchanging
// EnakCoin into EnakPoint and sending either to another customer
// (docs/prd-point-coin.md F4, F5).
type CustomerWalletService interface {
PreviewExchange(ctx context.Context, customerID uuid.UUID, coins int64) *contract.Response
Exchange(ctx context.Context, customerID uuid.UUID, req *contract.ExchangeCoinsRequest, idempotencyKey string, info models.CustomerPinRequestInfo) *contract.Response
TransferRecipient(ctx context.Context, customerID uuid.UUID, phoneNumber string) *contract.Response
Transfer(ctx context.Context, customerID uuid.UUID, req *contract.TransferWalletRequest, idempotencyKey string, info models.CustomerPinRequestInfo) *contract.Response
}
type CustomerWalletServiceImpl struct {
exchanges *processor.WalletExchangeProcessor
transfers *processor.WalletTransferProcessor
}
func NewCustomerWalletService(exchanges *processor.WalletExchangeProcessor, transfers *processor.WalletTransferProcessor) *CustomerWalletServiceImpl {
return &CustomerWalletServiceImpl{exchanges: exchanges, transfers: transfers}
}
func (s *CustomerWalletServiceImpl) TransferRecipient(ctx context.Context, customerID uuid.UUID, phoneNumber string) *contract.Response {
recipient, err := s.transfers.Recipient(ctx, customerID, phoneNumber)
if err != nil {
return walletMoveErrorResponse(err)
}
return contract.BuildSuccessResponse(recipient)
}
func (s *CustomerWalletServiceImpl) Transfer(ctx context.Context, customerID uuid.UUID, req *contract.TransferWalletRequest, idempotencyKey string, info models.CustomerPinRequestInfo) *contract.Response {
result, err := s.transfers.Transfer(ctx, customerID, models.WalletTransfer{
Currency: req.Currency,
Amount: req.Amount,
RecipientPhone: req.RecipientPhone,
}, req.Pin, idempotencyKey, info)
if err != nil {
return walletMoveErrorResponse(err)
}
return contract.BuildSuccessResponse(result)
}
func (s *CustomerWalletServiceImpl) PreviewExchange(ctx context.Context, customerID uuid.UUID, coins int64) *contract.Response {
preview, err := s.exchanges.Preview(ctx, customerID, coins)
if err != nil {
return walletMoveErrorResponse(err)
}
return contract.BuildSuccessResponse(preview)
}
func (s *CustomerWalletServiceImpl) Exchange(ctx context.Context, customerID uuid.UUID, req *contract.ExchangeCoinsRequest, idempotencyKey string, info models.CustomerPinRequestInfo) *contract.Response {
result, err := s.exchanges.Exchange(ctx, customerID, req.Coins, req.Pin, idempotencyKey, info)
if err != nil {
return walletMoveErrorResponse(err)
}
return contract.BuildSuccessResponse(result)
}
// walletMoveErrorResponse keeps the PIN codes the apps act on, and tells a refused
// request apart from a server failure.
func walletMoveErrorResponse(err error) *contract.Response {
var pinErr *processor.PinError
if errors.As(err, &pinErr) {
return PinErrorResponse(err)
}
code := constants.InternalServerErrorCode
switch {
case errors.Is(err, repository.ErrWalletNotFound),
errors.Is(err, processor.ErrWalletRecipientNotFound):
code = constants.NotFoundErrorCode
case errors.Is(err, processor.ErrWalletMoveRejected),
errors.Is(err, processor.ErrWalletIdempotencyConflict),
errors.Is(err, processor.ErrWalletInvalidEntry):
code = constants.ValidationErrorCode
}
return contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(code, constants.WalletServiceEntity, err.Error()),
})
}