Yimaru-BackEnd/internal/services/chapa/service.go

206 lines
5.9 KiB
Go

package chapa
import (
"context"
"errors"
"fmt"
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
"github.com/google/uuid"
)
var (
ErrPaymentNotFound = errors.New("payment not found")
ErrPaymentAlreadyExists = errors.New("payment with this reference already exists")
ErrInvalidPaymentAmount = errors.New("invalid payment amount")
)
type Service struct {
transferStore wallet.TransferStore
walletStore wallet.WalletStore
userStore user.UserStore
cfg *config.Config
chapaClient *Client
}
func NewService(
transferStore wallet.TransferStore,
walletStore wallet.WalletStore,
userStore user.UserStore,
chapaClient *Client,
) *Service {
return &Service{
transferStore: transferStore,
walletStore: walletStore,
userStore: userStore,
chapaClient: chapaClient,
}
}
// InitiateDeposit starts a new deposit process
func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount domain.Currency) (string, error) {
// Validate amount
if amount <= 0 {
return "", ErrInvalidPaymentAmount
}
// Get user details
user, err := s.userStore.GetUserByID(ctx, userID)
if err != nil {
return "", fmt.Errorf("failed to get user: %w", err)
}
var senderWallet domain.Wallet
// Generate unique reference
reference := uuid.New().String()
senderWallets, err := s.walletStore.GetWalletsByUser(ctx, userID)
if err != nil {
return "", fmt.Errorf("failed to get sender wallets: %w", err)
}
for _, wallet := range senderWallets {
if wallet.IsWithdraw {
senderWallet = wallet
break
}
}
// Check if payment with this reference already exists
// if transfer, err := s.transferStore.GetTransferByReference(ctx, reference); err == nil {
// return fmt.Sprintf("%v", transfer), ErrPaymentAlreadyExists
// }
// Create payment record
transfer := domain.CreateTransfer{
Amount: amount,
Type: domain.DEPOSIT,
PaymentMethod: domain.TRANSFER_CHAPA,
ReferenceNumber: reference,
// ReceiverWalletID: 1,
SenderWalletID: domain.ValidInt64{
Value: senderWallet.ID,
Valid: true,
},
Verified: false,
}
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
return "", fmt.Errorf("failed to save payment: %w", err)
}
// Initialize payment with Chapa
response, err := s.chapaClient.InitializePayment(ctx, domain.ChapaDepositRequest{
Amount: amount,
Currency: "ETB",
Email: user.Email,
FirstName: user.FirstName,
LastName: user.LastName,
TxRef: reference,
CallbackURL: "https://fortunebet.com/api/v1/payments/callback",
ReturnURL: "https://fortunebet.com/api/v1/payment-success",
})
if err != nil {
// Update payment status to failed
// _ = s.transferStore.(payment.ID, domain.PaymentStatusFailed)
return "", fmt.Errorf("failed to initialize payment: %w", err)
}
return response.CheckoutURL, nil
}
// VerifyDeposit handles payment verification from webhook
func (s *Service) VerifyDeposit(ctx context.Context, reference string) error {
// Find payment by reference
payment, err := s.transferStore.GetTransferByReference(ctx, reference)
if err != nil {
return ErrPaymentNotFound
}
// just making sure that the sender id is valid
if !payment.SenderWalletID.Valid {
return fmt.Errorf("sender wallet is invalid %v \n", payment.SenderWalletID)
}
// Skip if already completed
if payment.Verified {
return nil
}
// Verify payment with Chapa
verification, err := s.chapaClient.VerifyPayment(ctx, reference)
if err != nil {
return fmt.Errorf("failed to verify payment: %w", err)
}
// Update payment status
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
return fmt.Errorf("failed to update payment status: %w", err)
}
// If payment is completed, credit user's wallet
if verification.Status == domain.PaymentStatusCompleted {
if err := s.walletStore.UpdateBalance(ctx, payment.SenderWalletID.Value, payment.Amount); err != nil {
return fmt.Errorf("failed to credit user wallet: %w", err)
}
}
return nil
}
func (s *Service) ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
// First check if we already have a verified record
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
if err == nil && transfer.Verified {
return &domain.ChapaVerificationResponse{
Status: string(domain.PaymentStatusCompleted),
Amount: float64(transfer.Amount) / 100, // Convert from cents/kobo
Currency: "ETB",
}, nil
}
// just making sure that the sender id is valid
if !transfer.SenderWalletID.Valid {
return nil, fmt.Errorf("sender wallet id is invalid: %v \n", transfer.SenderWalletID)
}
// If not verified or not found, verify with Chapa
verification, err := s.chapaClient.VerifyPayment(ctx, txRef)
if err != nil {
return nil, fmt.Errorf("failed to verify payment: %w", err)
}
// Update our records if payment is successful
if verification.Status == domain.PaymentStatusCompleted {
err = s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true)
if err != nil {
return nil, fmt.Errorf("failed to update verification status: %w", err)
}
// Credit user's wallet
err = s.walletStore.UpdateBalance(ctx, transfer.SenderWalletID.Value, transfer.Amount)
if err != nil {
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
}
}
return &domain.ChapaVerificationResponse{
Status: string(verification.Status),
Amount: float64(verification.Amount),
Currency: verification.Currency,
}, nil
}
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
banks, err := s.chapaClient.FetchSupportedBanks(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch banks: %w", err)
}
return banks, nil
}