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