Arifpay webhook fixes

This commit is contained in:
Yared Yemane 2025-08-17 12:13:29 +03:00
parent 31117d9641
commit f1a4f5e6f9
18 changed files with 910 additions and 228 deletions

View File

@ -153,7 +153,7 @@ func main() {
virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger) virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger)
aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger) aleaService := alea.NewAleaPlayService(vitualGameRepo, *walletSvc, cfg, logger)
veliCLient := veli.NewClient(cfg, walletSvc) veliCLient := veli.NewClient(cfg, walletSvc)
veliVirtualGameService := veli.New(veliCLient) veliVirtualGameService := veli.New(veliCLient, walletSvc)
recommendationSvc := recommendation.NewService(recommendationRepo) recommendationSvc := recommendation.NewService(recommendationRepo)
chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY) chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY)
@ -234,7 +234,7 @@ func main() {
transferStore := wallet.TransferStore(store) transferStore := wallet.TransferStore(store)
// walletStore := wallet.WalletStore(store) // walletStore := wallet.WalletStore(store)
arifpaySvc := arifpay.NewArifpayService(cfg, transferStore, &http.Client{ arifpaySvc := arifpay.NewArifpayService(cfg, transferStore, walletSvc, &http.Client{
Timeout: 30 * time.Second}) Timeout: 30 * time.Second})
santimpayClient := santimpay.NewSantimPayClient(cfg) santimpayClient := santimpay.NewSantimPayClient(cfg)

View File

@ -190,6 +190,7 @@ CREATE TABLE IF NOT EXISTS wallet_transfer (
cashier_id BIGINT, cashier_id BIGINT,
verified BOOLEAN DEFAULT false, verified BOOLEAN DEFAULT false,
reference_number VARCHAR(255) NOT NULL, reference_number VARCHAR(255) NOT NULL,
session_id VARCHAR(255),
status VARCHAR(255), status VARCHAR(255),
payment_method VARCHAR(255), payment_method VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

View File

@ -8,10 +8,11 @@ INSERT INTO wallet_transfer (
cashier_id, cashier_id,
verified, verified,
reference_number, reference_number,
session_id,
status, status,
payment_method payment_method
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING *; RETURNING *;
-- name: GetAllTransfers :many -- name: GetAllTransfers :many
SELECT * SELECT *

View File

@ -713,6 +713,7 @@ type WalletTransfer struct {
CashierID pgtype.Int8 `json:"cashier_id"` CashierID pgtype.Int8 `json:"cashier_id"`
Verified pgtype.Bool `json:"verified"` Verified pgtype.Bool `json:"verified"`
ReferenceNumber string `json:"reference_number"` ReferenceNumber string `json:"reference_number"`
SessionID pgtype.Text `json:"session_id"`
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
PaymentMethod pgtype.Text `json:"payment_method"` PaymentMethod pgtype.Text `json:"payment_method"`
CreatedAt pgtype.Timestamp `json:"created_at"` CreatedAt pgtype.Timestamp `json:"created_at"`
@ -729,6 +730,7 @@ type WalletTransferDetail struct {
CashierID pgtype.Int8 `json:"cashier_id"` CashierID pgtype.Int8 `json:"cashier_id"`
Verified pgtype.Bool `json:"verified"` Verified pgtype.Bool `json:"verified"`
ReferenceNumber string `json:"reference_number"` ReferenceNumber string `json:"reference_number"`
SessionID pgtype.Text `json:"session_id"`
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
PaymentMethod pgtype.Text `json:"payment_method"` PaymentMethod pgtype.Text `json:"payment_method"`
CreatedAt pgtype.Timestamp `json:"created_at"` CreatedAt pgtype.Timestamp `json:"created_at"`

View File

@ -21,11 +21,12 @@ INSERT INTO wallet_transfer (
cashier_id, cashier_id,
verified, verified,
reference_number, reference_number,
session_id,
status, status,
payment_method payment_method
) )
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at RETURNING id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at
` `
type CreateTransferParams struct { type CreateTransferParams struct {
@ -37,6 +38,7 @@ type CreateTransferParams struct {
CashierID pgtype.Int8 `json:"cashier_id"` CashierID pgtype.Int8 `json:"cashier_id"`
Verified pgtype.Bool `json:"verified"` Verified pgtype.Bool `json:"verified"`
ReferenceNumber string `json:"reference_number"` ReferenceNumber string `json:"reference_number"`
SessionID pgtype.Text `json:"session_id"`
Status pgtype.Text `json:"status"` Status pgtype.Text `json:"status"`
PaymentMethod pgtype.Text `json:"payment_method"` PaymentMethod pgtype.Text `json:"payment_method"`
} }
@ -51,6 +53,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
arg.CashierID, arg.CashierID,
arg.Verified, arg.Verified,
arg.ReferenceNumber, arg.ReferenceNumber,
arg.SessionID,
arg.Status, arg.Status,
arg.PaymentMethod, arg.PaymentMethod,
) )
@ -65,6 +68,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
&i.CashierID, &i.CashierID,
&i.Verified, &i.Verified,
&i.ReferenceNumber, &i.ReferenceNumber,
&i.SessionID,
&i.Status, &i.Status,
&i.PaymentMethod, &i.PaymentMethod,
&i.CreatedAt, &i.CreatedAt,
@ -74,7 +78,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
} }
const GetAllTransfers = `-- name: GetAllTransfers :many const GetAllTransfers = `-- name: GetAllTransfers :many
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at, first_name, last_name, phone_number SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
FROM wallet_transfer_details FROM wallet_transfer_details
` `
@ -97,6 +101,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransferDetail,
&i.CashierID, &i.CashierID,
&i.Verified, &i.Verified,
&i.ReferenceNumber, &i.ReferenceNumber,
&i.SessionID,
&i.Status, &i.Status,
&i.PaymentMethod, &i.PaymentMethod,
&i.CreatedAt, &i.CreatedAt,
@ -116,7 +121,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransferDetail,
} }
const GetTransferByID = `-- name: GetTransferByID :one const GetTransferByID = `-- name: GetTransferByID :one
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at, first_name, last_name, phone_number SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
FROM wallet_transfer_details FROM wallet_transfer_details
WHERE id = $1 WHERE id = $1
` `
@ -134,6 +139,7 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
&i.CashierID, &i.CashierID,
&i.Verified, &i.Verified,
&i.ReferenceNumber, &i.ReferenceNumber,
&i.SessionID,
&i.Status, &i.Status,
&i.PaymentMethod, &i.PaymentMethod,
&i.CreatedAt, &i.CreatedAt,
@ -146,7 +152,7 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
} }
const GetTransferByReference = `-- name: GetTransferByReference :one const GetTransferByReference = `-- name: GetTransferByReference :one
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at, first_name, last_name, phone_number SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
FROM wallet_transfer_details FROM wallet_transfer_details
WHERE reference_number = $1 WHERE reference_number = $1
` `
@ -164,6 +170,7 @@ func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber st
&i.CashierID, &i.CashierID,
&i.Verified, &i.Verified,
&i.ReferenceNumber, &i.ReferenceNumber,
&i.SessionID,
&i.Status, &i.Status,
&i.PaymentMethod, &i.PaymentMethod,
&i.CreatedAt, &i.CreatedAt,
@ -176,7 +183,7 @@ func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber st
} }
const GetTransfersByWallet = `-- name: GetTransfersByWallet :many const GetTransfersByWallet = `-- name: GetTransfersByWallet :many
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at, first_name, last_name, phone_number SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
FROM wallet_transfer_details FROM wallet_transfer_details
WHERE receiver_wallet_id = $1 WHERE receiver_wallet_id = $1
OR sender_wallet_id = $1 OR sender_wallet_id = $1
@ -201,6 +208,7 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID pgt
&i.CashierID, &i.CashierID,
&i.Verified, &i.Verified,
&i.ReferenceNumber, &i.ReferenceNumber,
&i.SessionID,
&i.Status, &i.Status,
&i.PaymentMethod, &i.PaymentMethod,
&i.CreatedAt, &i.CreatedAt,

2
go.mod
View File

@ -94,6 +94,6 @@ require (
github.com/segmentio/kafka-go v0.4.48 // direct github.com/segmentio/kafka-go v0.4.48 // direct
) )
require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f // require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f
// require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f // direct // require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f // direct

View File

@ -6,6 +6,7 @@ import (
"log/slog" "log/slog"
"os" "os"
"strconv" "strconv"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger" customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger"
@ -59,11 +60,25 @@ type VeliConfig struct {
} }
type ARIFPAYConfig struct { type ARIFPAYConfig struct {
APIKey string `mapstructure:"ARIFPAYAPI_KEY"` APIKey string `mapstructure:"ARIFPAY_API_KEY"`
CancelUrl string `mapstructure:"ARIFPAY_BASE_URL"` BaseURL string `mapstructure:"ARIFPAY_BASE_URL"`
ErrorUrl string `mapstructure:"ARIFPAY_SECRET_KEY"`
NotifyUrl string `mapstructure:"ARIFPAY_OPERATOR_ID"` // Default URLs
SuccessUrl string `mapstructure:"ARIFPAY_BRAND_ID"` CancelUrl string `mapstructure:"cancelUrl"`
SuccessUrl string `mapstructure:"successUrl"`
ErrorUrl string `mapstructure:"errorUrl"`
B2CNotifyUrl string `mapstructure:"notifyUrl"`
C2BNotifyUrl string `mapstructure:"notifyUrl"`
// Default Payment Configs
PaymentMethods []string `mapstructure:"paymentMethods"`
ExpireDate string `mapstructure:"expireDate"`
ItemName string `mapstructure:"name"`
Quantity int `mapstructure:"quantity"`
Description string `mapstructure:"description"`
BeneficiaryAccountNumber string `mapstructure:"accountNumber"`
Bank string `mapstructure:"bank"`
Lang string `mapstructure:"amount"`
} }
type SANTIMPAYConfig struct { type SANTIMPAYConfig struct {
@ -224,8 +239,18 @@ func (c *Config) loadEnv() error {
c.ARIFPAY.APIKey = os.Getenv("ARIFPAY_API_KEY") c.ARIFPAY.APIKey = os.Getenv("ARIFPAY_API_KEY")
c.ARIFPAY.CancelUrl = os.Getenv("ARIFPAY_CANCEL_URL") c.ARIFPAY.CancelUrl = os.Getenv("ARIFPAY_CANCEL_URL")
c.ARIFPAY.ErrorUrl = os.Getenv("ARIFPAY_ERROR_URL") c.ARIFPAY.ErrorUrl = os.Getenv("ARIFPAY_ERROR_URL")
c.ARIFPAY.NotifyUrl = os.Getenv("ARIFPAY_NOTIFY_URL") c.ARIFPAY.C2BNotifyUrl = os.Getenv("ARIFPAY_C2B_NOTIFY_URL")
c.ARIFPAY.B2CNotifyUrl = os.Getenv("ARIFPAY_B2C_NOTIFY_URL")
c.ARIFPAY.SuccessUrl = os.Getenv("ARIFPAY_SUCCESS_URL") c.ARIFPAY.SuccessUrl = os.Getenv("ARIFPAY_SUCCESS_URL")
c.ARIFPAY.BaseURL = os.Getenv("ARIFPAY_BASE_URL")
c.ARIFPAY.Bank = os.Getenv("ARIFPAY_BANK")
c.ARIFPAY.BeneficiaryAccountNumber = os.Getenv("ARIFPAY_BENEFICIARY_ACCOUNT_NUMBER")
c.ARIFPAY.Description = os.Getenv("ARIFPAY_DESCRIPTION")
c.ARIFPAY.ExpireDate = time.Now().Add(time.Hour * 3).Format("2006-01-02")
c.ARIFPAY.ItemName = os.Getenv("ARIFPAY_ITEM_NAME")
c.ARIFPAY.Lang = "EN"
c.ARIFPAY.Quantity = 1
c.ARIFPAY.PaymentMethods = []string{"TELEBIRR", "AWAASH", "AWAASH_WALLET", "PSS", "CBE", "AMOLE", "BOA", "KACHA", "TELEBIRR", "HELLOCASH", "MPESSA"}
c.SANTIMPAY.SecretKey = os.Getenv("SANTIMPAY_SECRET_KEY") c.SANTIMPAY.SecretKey = os.Getenv("SANTIMPAY_SECRET_KEY")
c.SANTIMPAY.MerchantID = os.Getenv("SANTIMPAY_MERCHANT_ID") c.SANTIMPAY.MerchantID = os.Getenv("SANTIMPAY_MERCHANT_ID")

View File

@ -1,37 +1,39 @@
package domain package domain
import "time" type CheckoutSessionRequest struct {
CancelURL string `json:"cancelUrl"`
Phone string `json:"phone"`
Email string `json:"email"`
Nonce string `json:"nonce"`
SuccessURL string `json:"successUrl"`
ErrorURL string `json:"errorUrl"`
NotifyURL string `json:"notifyUrl"`
PaymentMethods []string `json:"paymentMethods"`
ExpireDate string `json:"expireDate"` // could also use time.Time if you parse it
type Item struct { Items []struct {
Name string `json:"name"` Name string `json:"name"`
Quantity int `json:"quantity"` Quantity int `json:"quantity"`
Price float64 `json:"price"` Price float64 `json:"price"`
Description string `json:"description"` Description string `json:"description"`
Image string `json:"image"` } `json:"items"`
Beneficiaries []struct {
AccountNumber string `json:"accountNumber"`
Bank string `json:"bank"`
Amount float64 `json:"amount"`
} `json:"beneficiaries"`
Lang string `json:"lang"`
} }
type Beneficiary struct { type CheckoutSessionClientRequest struct {
AccountNumber string `json:"accountNumber"` Amount float64 `json:"amount" binding:"required"`
Bank string `json:"bank"` CustomerEmail string `json:"customerEmail" binding:"required"`
Amount float64 `json:"amount"` CustomerPhone string `json:"customerPhone" binding:"required"`
} }
type CreateCheckoutSessionRequest struct { type CancelCheckoutSessionResponse struct {
CancelUrl string `json:"cancelUrl"`
Phone string `json:"phone"`
Email string `json:"email"`
Nonce string `json:"nonce"`
ErrorUrl string `json:"errorUrl"`
NotifyUrl string `json:"notifyUrl"`
SuccessUrl string `json:"successUrl"`
PaymentMethods []string `json:"paymentMethods"`
ExpireDate time.Time `json:"expireDate"`
Items []Item `json:"items"`
Beneficiaries []Beneficiary `json:"beneficiaries"`
Lang string `json:"lang"`
}
type ArifPayCheckoutResponse struct {
Error bool `json:"error"` Error bool `json:"error"`
Msg string `json:"msg"` Msg string `json:"msg"`
Data struct { Data struct {
@ -42,12 +44,34 @@ type ArifPayCheckoutResponse struct {
} `json:"data"` } `json:"data"`
} }
type ArifPayB2CRequest struct { type WebhookRequest struct {
SessionID string `json:"Sessionid"` UUID string `json:"uuid"`
PhoneNumber string `json:"Phonenumber"` Nonce string `json:"nonce"`
Phone string `json:"phone"`
PaymentMethod string `json:"paymentMethod"`
TotalAmount int64 `json:"totalAmount"`
TransactionStatus string `json:"transactionStatus"`
Transaction struct {
TransactionID string `json:"transactionId"`
TransactionStatus string `json:"transactionStatus"`
} `json:"transaction"`
NotificationURL string `json:"notificationUrl"`
SessionID string `json:"sessionId"`
} }
type ArifpayVerifyByTransactionIDRequest struct { type ArifpayB2CRequest struct{
TransactionID string `json:"transactionId"` PhoneNumber string `json:"Phonenumber"`
PaymentType int `json:"paymentType"` Amount float64 `json:"amount" binding:"required"`
CustomerEmail string `json:"customerEmail" binding:"required"`
CustomerPhone string `json:"customerPhone" binding:"required"`
}
type ArifpayVerifyByTransactionIDRequest struct{
TransactionId string `json:"transactionId"`
PaymentType int `json:"paymentType"`
}
type ARIFPAYPaymentMethod struct {
ID int
Name string
} }

View File

@ -66,6 +66,7 @@ type Transfer struct {
ReceiverWalletID ValidInt64 `json:"receiver_wallet_id"` ReceiverWalletID ValidInt64 `json:"receiver_wallet_id"`
SenderWalletID ValidInt64 `json:"sender_wallet_id"` SenderWalletID ValidInt64 `json:"sender_wallet_id"`
ReferenceNumber string `json:"reference_number"` // <-- needed ReferenceNumber string `json:"reference_number"` // <-- needed
SessionID string `json:"session_id"`
Status string `json:"status"` Status string `json:"status"`
DepositorID ValidInt64 `json:"depositor_id"` DepositorID ValidInt64 `json:"depositor_id"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
@ -81,6 +82,7 @@ type TransferDetail struct {
ReceiverWalletID ValidInt64 `json:"receiver_wallet_id"` ReceiverWalletID ValidInt64 `json:"receiver_wallet_id"`
SenderWalletID ValidInt64 `json:"sender_wallet_id"` SenderWalletID ValidInt64 `json:"sender_wallet_id"`
ReferenceNumber string `json:"reference_number"` // <-- needed ReferenceNumber string `json:"reference_number"` // <-- needed
SessionID string `json:"session_id"`
Status string `json:"status"` Status string `json:"status"`
DepositorID ValidInt64 `json:"depositor_id"` DepositorID ValidInt64 `json:"depositor_id"`
DepositorFirstName string `json:"depositor_first_name"` DepositorFirstName string `json:"depositor_first_name"`
@ -99,6 +101,7 @@ type CreateTransfer struct {
ReceiverWalletID ValidInt64 `json:"receiver_wallet_id"` ReceiverWalletID ValidInt64 `json:"receiver_wallet_id"`
SenderWalletID ValidInt64 `json:"sender_wallet_id"` SenderWalletID ValidInt64 `json:"sender_wallet_id"`
ReferenceNumber string `json:"reference_number"` // <-- needed ReferenceNumber string `json:"reference_number"` // <-- needed
SessionID string `json:"session_id"`
Status string `json:"status"` Status string `json:"status"`
CashierID ValidInt64 `json:"cashier_id"` CashierID ValidInt64 `json:"cashier_id"`
} }

View File

@ -32,6 +32,7 @@ func convertDBTransferDetail(transfer dbgen.WalletTransferDetail) domain.Transfe
DepositorPhoneNumber: transfer.PhoneNumber.String, DepositorPhoneNumber: transfer.PhoneNumber.String,
PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod.String), PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod.String),
ReferenceNumber: transfer.ReferenceNumber, ReferenceNumber: transfer.ReferenceNumber,
SessionID: transfer.SessionID.String,
Status: transfer.Status.String, Status: transfer.Status.String,
CreatedAt: transfer.CreatedAt.Time, CreatedAt: transfer.CreatedAt.Time,
UpdatedAt: transfer.UpdatedAt.Time, UpdatedAt: transfer.UpdatedAt.Time,
@ -58,6 +59,7 @@ func convertDBTransfer(transfer dbgen.WalletTransfer) domain.Transfer {
}, },
PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod.String), PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod.String),
ReferenceNumber: transfer.ReferenceNumber, ReferenceNumber: transfer.ReferenceNumber,
SessionID: transfer.SessionID.String,
Status: transfer.Status.String, Status: transfer.Status.String,
CreatedAt: transfer.CreatedAt.Time, CreatedAt: transfer.CreatedAt.Time,
UpdatedAt: transfer.UpdatedAt.Time, UpdatedAt: transfer.UpdatedAt.Time,
@ -82,6 +84,10 @@ func convertCreateTransfer(transfer domain.CreateTransfer) dbgen.CreateTransferP
Valid: transfer.CashierID.Valid, Valid: transfer.CashierID.Valid,
}, },
ReferenceNumber: string(transfer.ReferenceNumber), ReferenceNumber: string(transfer.ReferenceNumber),
SessionID: pgtype.Text{
String: transfer.SessionID,
Valid: true,
},
PaymentMethod: pgtype.Text{String: string(transfer.PaymentMethod), Valid: true}, PaymentMethod: pgtype.Text{String: string(transfer.PaymentMethod), Valid: true},
Verified: pgtype.Bool{ Verified: pgtype.Bool{

View File

@ -4,12 +4,11 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"time"
"github.com/AnaniyaBelew/ArifpayGoPlugin"
"github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/config"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
@ -19,174 +18,567 @@ import (
type ArifpayService struct { type ArifpayService struct {
cfg *config.Config cfg *config.Config
transferStore wallet.TransferStore transferStore wallet.TransferStore
walletSvc *wallet.Service
httpClient *http.Client httpClient *http.Client
} }
func NewArifpayService(cfg *config.Config, transferStore wallet.TransferStore, httpClient *http.Client) *ArifpayService { func NewArifpayService(cfg *config.Config, transferStore wallet.TransferStore, walletSvc *wallet.Service, httpClient *http.Client) *ArifpayService {
return &ArifpayService{ return &ArifpayService{
cfg: cfg, cfg: cfg,
transferStore: transferStore, transferStore: transferStore,
walletSvc: walletSvc,
httpClient: httpClient, httpClient: httpClient,
} }
} }
func (s *ArifpayService) CreateCheckoutSession(req domain.CreateCheckoutSessionRequest) (string, error) { func (s *ArifpayService) CreateCheckoutSession(req domain.CheckoutSessionClientRequest, isDeposit bool) (map[string]any, error) {
// Create SDK-compatible payload // Generate unique nonce
paymentPayload := ArifpayGoPlugin.PaymentRequest{ nonce := uuid.NewString()
CancelUrl: s.cfg.ARIFPAY.CancelUrl,
Phone: req.Phone, var NotifyURL string
Email: req.Email,
Nonce: req.Nonce, if isDeposit{
ErrorUrl: s.cfg.ARIFPAY.ErrorUrl, NotifyURL = s.cfg.ARIFPAY.C2BNotifyUrl
NotifyUrl: s.cfg.ARIFPAY.NotifyUrl, }else{
SuccessUrl: s.cfg.ARIFPAY.SuccessUrl, NotifyURL = s.cfg.ARIFPAY.B2CNotifyUrl
PaymentMethods: req.PaymentMethods,
Lang: req.Lang,
} }
// Convert items // Construct full checkout request
for _, item := range req.Items { checkoutReq := domain.CheckoutSessionRequest{
paymentPayload.Items = append(paymentPayload.Items, domain.Item{ CancelURL: s.cfg.ARIFPAY.CancelUrl,
Name: item.Name, Phone: req.CustomerPhone, // must be in format 2519...
Quantity: item.Quantity, Email: req.CustomerEmail,
Price: item.Price, Nonce: nonce,
Description: item.Description, SuccessURL: s.cfg.ARIFPAY.SuccessUrl,
Image: item.Image, ErrorURL: s.cfg.ARIFPAY.ErrorUrl,
}) NotifyURL: NotifyURL,
PaymentMethods: s.cfg.ARIFPAY.PaymentMethods,
ExpireDate: s.cfg.ARIFPAY.ExpireDate,
Items: []struct {
Name string `json:"name"`
Quantity int `json:"quantity"`
Price float64 `json:"price"`
Description string `json:"description"`
}{
{
Name: s.cfg.ARIFPAY.ItemName,
Quantity: s.cfg.ARIFPAY.Quantity,
Price: req.Amount,
Description: s.cfg.ARIFPAY.Description,
},
},
Beneficiaries: []struct {
AccountNumber string `json:"accountNumber"`
Bank string `json:"bank"`
Amount float64 `json:"amount"`
}{
{
AccountNumber: s.cfg.ARIFPAY.BeneficiaryAccountNumber,
Bank: s.cfg.ARIFPAY.Bank,
Amount: req.Amount,
},
},
Lang: s.cfg.ARIFPAY.Lang,
} }
// Convert beneficiaries // Marshal to JSON
for _, b := range req.Beneficiaries { payload, err := json.Marshal(checkoutReq)
paymentPayload.Beneficiaries = append(paymentPayload.Beneficiaries, domain.Beneficiary{
AccountNumber: b.AccountNumber,
Bank: b.Bank,
Amount: b.Amount,
})
}
// Instantiate payment client
expireDate := time.Now().AddDate(2, 0, 0) // 2 months from now
paymentClient := ArifpayGoPlugin.NewPayment(s.cfg.ARIFPAY.APIKey, expireDate)
// Create checkout session
response, err := paymentClient.MakePayment(paymentPayload)
if err != nil { if err != nil {
return "", err return nil, fmt.Errorf("failed to marshal checkout request: %w", err)
} }
transfer := domain.CreateTransfer{ // Send request to Arifpay API
Amount: domain.Currency(req.Beneficiaries[0].Amount), url := fmt.Sprintf("%s/api/checkout/session", s.cfg.ARIFPAY.BaseURL)
Verified: false, httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
Type: domain.DEPOSIT,
ReferenceNumber: uuid.NewString(),
Status: string(domain.PaymentStatusPending),
}
if _, err := s.transferStore.CreateTransfer(context.Background(), transfer); err != nil {
return "", err
}
return response, nil
}
func (s *ArifpayService) B2CTransfer(ctx context.Context, req domain.ArifPayB2CRequest, endpoint string) (*map[string]interface{}, error) {
// endpoint := c.baseURL + "/api/Telebirr/b2c/transfer"
payloadBytes, err := json.Marshal(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to marshal request payload: %w", err) return nil, err
} }
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(payloadBytes))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Accept", "application/json")
httpReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey) httpReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
resp, err := s.httpClient.Do(httpReq) resp, err := s.httpClient.Do(httpReq)
if err != nil { if err != nil {
return nil, fmt.Errorf("request to Telebirr B2C failed: %w", err) return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body) // Read response
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Telebirr API returned status %d: %s", resp.StatusCode, string(body)) return nil, fmt.Errorf("failed to create checkout session: %s", string(body))
} }
var response map[string]interface{} // Optionally unmarshal response to struct
if err := json.Unmarshal(body, &response); err != nil { var result map[string]interface{}
return nil, fmt.Errorf("failed to parse response body: %w", err) if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("invalid response from Arifpay: %w", err)
} }
return &response, nil data := result["data"].(map[string]interface{})
// paymentURL := data["paymentUrl"].(string)
// Store transfer in DB
transfer := domain.CreateTransfer{
Amount: domain.Currency(req.Amount),
Verified: false,
Type: domain.DEPOSIT,
ReferenceNumber: nonce,
SessionID: fmt.Sprintf("%v", data["sessionId"]),
Status: string(domain.PaymentStatusPending),
}
if _, err := s.transferStore.CreateTransfer(context.Background(), transfer); err != nil {
return nil, err
}
return data, nil
} }
func (s *ArifpayService) VerifyByTransactionID(transactionID string, paymentType int) ([]byte, error) { func (s *ArifpayService) CancelCheckoutSession(ctx context.Context, sessionID string) (*domain.CancelCheckoutSessionResponse, error) {
url := "https://gateway.arifpay.org/api/checkout/getSessionByTransactionId" // Build the cancel URL
url := fmt.Sprintf("%s/api/sandbox/checkout/session/%s", s.cfg.ARIFPAY.BaseURL, sessionID)
var payload domain.ArifpayVerifyByTransactionIDRequest // Create the request
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
bodyBytes, err := json.Marshal(payload)
if err != nil {
return nil, fmt.Errorf("failed to marshal request body: %w", err)
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err) return nil, fmt.Errorf("failed to create request: %w", err)
} }
// Add headers
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey) req.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
// Execute request
resp, err := s.httpClient.Do(req) resp, err := s.httpClient.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("request failed: %w", err) return nil, fmt.Errorf("failed to execute cancel request: %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body) // Read response body
body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err) return nil, fmt.Errorf("failed to read cancel response: %w", err)
} }
// Handle non-200 status codes
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("non-200 response from Arifpay: %s", string(respBody)) return nil, fmt.Errorf("cancel request failed: status=%d, body=%s", resp.StatusCode, string(body))
} }
return respBody, nil // Decode into response struct
var cancelResp domain.CancelCheckoutSessionResponse
if err := json.Unmarshal(body, &cancelResp); err != nil {
return nil, fmt.Errorf("failed to unmarshal cancel response: %w", err)
}
return &cancelResp, nil
} }
func (s *ArifpayService) VerifyBySessionID(sessionID string) ([]byte, error) { func (s *ArifpayService) HandleWebhook(ctx context.Context, req domain.WebhookRequest, userId int64, isDepost bool) error {
url := "https://gateway.arifpay.org/api/ms/transaction/status/" + sessionID // 1. Get transfer by SessionID
transfer, err := s.transferStore.GetTransferByReference(ctx, req.Transaction.TransactionID)
if err != nil {
return err
}
// Create GET request without body wallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
req, err := http.NewRequest("GET", url, nil) if err != nil {
return err
}
if transfer.Verified {
return errors.New("transfer already verified")
}
// 2. Update transfer status
newStatus := req.Transaction.TransactionStatus
// if req.Transaction.TransactionStatus != "" {
// newStatus = req.Transaction.TransactionStatus
// }
err = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, newStatus)
if err != nil {
return err
}
err = s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true)
if err != nil {
return err
}
// 3. If SUCCESS -> update customer wallet balance
if (newStatus == "SUCCESS" && isDepost) || (newStatus == "FAILED" && !isDepost) {
_, err = s.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.TotalAmount), domain.ValidInt64{}, transfer.PaymentMethod, domain.PaymentDetails{
ReferenceNumber: domain.ValidString{
Value: req.Transaction.TransactionID,
Valid: true,
},
BankNumber: domain.ValidString{
Value: "",
Valid: false,
},
}, "")
if err != nil {
return err
}
}
return nil
}
func (s *ArifpayService) ExecuteTelebirrB2CTransfer(ctx context.Context, req domain.ArifpayB2CRequest, userId int64) error {
// Step 1: Create Session
referenceNum := uuid.NewString()
sessionReq := domain.CheckoutSessionClientRequest{
Amount: req.Amount,
CustomerEmail: req.CustomerEmail,
CustomerPhone: req.CustomerPhone,
}
sessionResp, err := s.CreateCheckoutSession(sessionReq, false)
if err != nil {
return fmt.Errorf("failed to create session: %w", err)
}
// Step 2: Execute Transfer
transferURL := fmt.Sprintf("%s/api/Telebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
reqBody := map[string]any{
"Sessionid": sessionResp["sessionId"],
"Phonenumber": req.PhoneNumber,
}
payload, err := json.Marshal(reqBody)
if err != nil {
return fmt.Errorf("failed to marshal transfer request: %w", err)
}
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
if err != nil {
return fmt.Errorf("failed to build transfer request: %w", err)
}
transferReq.Header.Set("Content-Type", "application/json")
transferReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
transferResp, err := s.httpClient.Do(transferReq)
if err != nil {
return fmt.Errorf("failed to execute transfer request: %w", err)
}
defer transferResp.Body.Close()
if transferResp.StatusCode >= 300 {
body, _ := io.ReadAll(transferResp.Body)
return fmt.Errorf("transfer failed with status %d: %s", transferResp.StatusCode, string(body))
}
// Step 3: Store transfer in DB
transfer := domain.CreateTransfer{
Amount: domain.Currency(req.Amount),
Verified: false,
Type: domain.WITHDRAW, // B2C = payout
ReferenceNumber: referenceNum,
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
Status: string(domain.PaymentStatusPending),
PaymentMethod: domain.TRANSFER_ARIFPAY,
}
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
return fmt.Errorf("failed to store transfer: %w", err)
}
// Step 4: Deduct from wallet
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
if err != nil {
return fmt.Errorf("failed to get user wallets: %w", err)
}
if len(userWallets) == 0 {
return fmt.Errorf("no wallet found for user %d", userId)
}
_, err = s.walletSvc.DeductFromWallet(
ctx,
userWallets[0].ID,
domain.Currency(req.Amount),
domain.ValidInt64{},
domain.TRANSFER_ARIFPAY,
"",
)
if err != nil {
return fmt.Errorf("failed to deduct from wallet: %w", err)
}
return nil
}
func (s *ArifpayService) ExecuteCBEB2CTransfer(ctx context.Context, req domain.ArifpayB2CRequest, userId int64) error {
// Step 1: Create Session
referenceNum := uuid.NewString()
sessionReq := domain.CheckoutSessionClientRequest{
Amount: req.Amount,
CustomerEmail: req.CustomerEmail,
CustomerPhone: req.CustomerPhone,
}
sessionResp, err := s.CreateCheckoutSession(sessionReq, false)
if err != nil {
return fmt.Errorf("cbebirr: failed to create session: %w", err)
}
// Step 2: Execute Transfer
transferURL := fmt.Sprintf("%s/api/Cbebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
reqBody := map[string]any{
"Sessionid": sessionResp["sessionId"],
"Phonenumber": req.PhoneNumber,
}
payload, err := json.Marshal(reqBody)
if err != nil {
return fmt.Errorf("cbebirr: failed to marshal transfer request: %w", err)
}
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
if err != nil {
return fmt.Errorf("cbebirr: failed to build transfer request: %w", err)
}
transferReq.Header.Set("Content-Type", "application/json")
transferReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
transferResp, err := s.httpClient.Do(transferReq)
if err != nil {
return fmt.Errorf("cbebirr: failed to execute transfer request: %w", err)
}
defer transferResp.Body.Close()
if transferResp.StatusCode >= 300 {
body, _ := io.ReadAll(transferResp.Body)
return fmt.Errorf("cbebirr: transfer failed with status %d: %s", transferResp.StatusCode, string(body))
}
// Step 3: Store transfer in DB
transfer := domain.CreateTransfer{
Amount: domain.Currency(req.Amount),
Verified: false,
Type: domain.WITHDRAW, // B2C = payout
ReferenceNumber: referenceNum,
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
Status: string(domain.PaymentStatusPending),
PaymentMethod: domain.TRANSFER_ARIFPAY,
}
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
return fmt.Errorf("cbebirr: failed to store transfer: %w", err)
}
// Step 4: Deduct from user wallet
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
if err != nil {
return fmt.Errorf("cbebirr: failed to get user wallets: %w", err)
}
if len(userWallets) == 0 {
return fmt.Errorf("cbebirr: no wallet found for user %d", userId)
}
_, err = s.walletSvc.DeductFromWallet(
ctx,
userWallets[0].ID,
domain.Currency(req.Amount),
domain.ValidInt64{},
domain.TRANSFER_ARIFPAY,
"",
)
if err != nil {
return fmt.Errorf("cbebirr: failed to deduct from wallet: %w", err)
}
return nil
}
func (s *ArifpayService) ExecuteMPesaB2CTransfer(ctx context.Context, req domain.ArifpayB2CRequest, userId int64) error {
// Step 1: Create Session
referenceNum := uuid.NewString()
sessionReq := domain.CheckoutSessionClientRequest{
Amount: req.Amount,
CustomerEmail: req.CustomerEmail,
CustomerPhone: req.CustomerPhone,
}
sessionResp, err := s.CreateCheckoutSession(sessionReq, false)
if err != nil {
return fmt.Errorf("Mpesa: failed to create session: %w", err)
}
// Step 2: Execute Transfer
transferURL := fmt.Sprintf("%s/api/Mpesa/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
reqBody := map[string]any{
"Sessionid": sessionResp["sessionId"],
"Phonenumber": req.PhoneNumber,
}
payload, err := json.Marshal(reqBody)
if err != nil {
return fmt.Errorf("Mpesa: failed to marshal transfer request: %w", err)
}
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
if err != nil {
return fmt.Errorf("Mpesa: failed to build transfer request: %w", err)
}
transferReq.Header.Set("Content-Type", "application/json")
transferReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
transferResp, err := s.httpClient.Do(transferReq)
if err != nil {
return fmt.Errorf("Mpesa: failed to execute transfer request: %w", err)
}
defer transferResp.Body.Close()
if transferResp.StatusCode >= 300 {
body, _ := io.ReadAll(transferResp.Body)
return fmt.Errorf("Mpesa: transfer failed with status %d: %s", transferResp.StatusCode, string(body))
}
// Step 3: Store transfer in DB
transfer := domain.CreateTransfer{
Amount: domain.Currency(req.Amount),
Verified: false,
Type: domain.WITHDRAW, // B2C = payout
ReferenceNumber: referenceNum,
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
Status: string(domain.PaymentStatusPending),
PaymentMethod: domain.TRANSFER_ARIFPAY,
}
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
return fmt.Errorf("Mpesa: failed to store transfer: %w", err)
}
// Step 4: Deduct from user wallet
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
if err != nil {
return fmt.Errorf("Mpesa: failed to get user wallets: %w", err)
}
if len(userWallets) == 0 {
return fmt.Errorf("Mpesa: no wallet found for user %d", userId)
}
_, err = s.walletSvc.DeductFromWallet(
ctx,
userWallets[0].ID,
domain.Currency(req.Amount),
domain.ValidInt64{},
domain.TRANSFER_ARIFPAY,
"",
)
if err != nil {
return fmt.Errorf("Mpesa: failed to deduct from wallet: %w", err)
}
return nil
}
func (s *ArifpayService) VerifyTransactionByTransactionID(ctx context.Context, req domain.ArifpayVerifyByTransactionIDRequest) (*domain.WebhookRequest, error) {
endpoint := fmt.Sprintf("%s/api/checkout/getSessionByTransactionId", s.cfg.ARIFPAY.BaseURL)
// Marshal request payload
bodyBytes, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
// Build HTTP request
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(bodyBytes))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err) return nil, fmt.Errorf("failed to create request: %w", err)
} }
req.Header.Set("Content-Type", "application/json") httpReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
req.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
resp, err := s.httpClient.Do(req) // Execute request
resp, err := s.httpClient.Do(httpReq)
if err != nil { if err != nil {
return nil, fmt.Errorf("request failed: %w", err) return nil, fmt.Errorf("failed to call verify transaction API: %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body) // Read response body
respBytes, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err) return nil, fmt.Errorf("failed to read response: %w", err)
} }
// Handle non-200 responses
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("non-200 response from Arifpay: %s", string(respBody)) return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(respBytes))
} }
return respBody, nil // Decode into domain response
var result domain.WebhookRequest
if err := json.Unmarshal(respBytes, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
func (s *ArifpayService) VerifyTransactionBySessionID(ctx context.Context, sessionID string) (*domain.WebhookRequest, error) {
endpoint := fmt.Sprintf("%s/api/ms/transaction/status/%s", s.cfg.ARIFPAY.BaseURL, sessionID)
// Create HTTP GET request
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
// Set required headers
httpReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
// Execute request
resp, err := s.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("failed to call verify transaction API: %w", err)
}
defer resp.Body.Close()
// Read response body
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
// Handle non-200 responses
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(respBytes))
}
// Decode into domain response
var result domain.WebhookRequest
if err := json.Unmarshal(respBytes, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result, nil
}
func (s *ArifpayService) GetPaymentMethodsMapping() []domain.ARIFPAYPaymentMethod {
return []domain.ARIFPAYPaymentMethod{
{ID: 1, Name: "ACCOUNT"},
{ID: 2, Name: "NONYMOUS_ACCOUNT"},
{ID: 3, Name: "ANONYMOUS_CARD"},
{ID: 4, Name: "TELEBIRR"},
{ID: 5, Name: "AWASH"},
{ID: 6, Name: "AWASH_WALLET"},
{ID: 7, Name: "PSS"},
{ID: 8, Name: "CBE"},
{ID: 9, Name: "AMOLE"},
{ID: 10, Name: "BOA"},
{ID: 11, Name: "KACHA"},
{ID: 12, Name: "ETHSWITCH"},
{ID: 13, Name: "TELEBIRR_USSD"},
{ID: 14, Name: "HELLOCASH"},
{ID: 15, Name: "MPESSA"},
}
} }

View File

@ -47,7 +47,7 @@ func (s *SantimPayService) GeneratePaymentURL(input domain.GeneratePaymentURLInp
SignedToken: token, SignedToken: token,
SuccessRedirectURL: s.cfg.ARIFPAY.SuccessUrl, SuccessRedirectURL: s.cfg.ARIFPAY.SuccessUrl,
FailureRedirectURL: s.cfg.ARIFPAY.ErrorUrl, FailureRedirectURL: s.cfg.ARIFPAY.ErrorUrl,
NotifyURL: s.cfg.ARIFPAY.NotifyUrl, NotifyURL: s.cfg.ARIFPAY.B2CNotifyUrl,
CancelRedirectURL: s.cfg.ARIFPAY.CancelUrl, CancelRedirectURL: s.cfg.ARIFPAY.CancelUrl,
PhoneNumber: input.PhoneNumber, PhoneNumber: input.PhoneNumber,
} }

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
) )
var ( var (
@ -19,11 +20,13 @@ var (
type service struct { type service struct {
client *Client client *Client
walletSvc *wallet.Service
} }
func New(client *Client) VeliVirtualGameService { func New(client *Client, walletSvc *wallet.Service) VeliVirtualGameService {
return &service{ return &service{
client: client, client: client,
walletSvc: walletSvc,
} }
} }
@ -87,20 +90,48 @@ func (s *service) StartDemoGame(ctx context.Context, req domain.DemoGameRequest)
} }
func (s *service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*domain.BalanceResponse, error) { func (s *service) GetBalance(ctx context.Context, req domain.BalanceRequest) (*domain.BalanceResponse, error) {
sigParams := map[string]string{ // Retrieve player's real balance from wallet service
"sessionId": req.SessionID, playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
"providerId": req.ProviderID, if err != nil {
"playerId": req.PlayerID, return nil, fmt.Errorf("invalid PlayerID: %w", err)
"currency": req.Currency,
"brandId": req.BrandID,
} }
if req.GameID != "" { playerWallets, err := s.walletSvc.GetWalletsByUser(ctx, playerIDInt64)
sigParams["gameId"] = req.GameID if err != nil {
return nil, fmt.Errorf("failed to get real balance: %w", err)
} }
var res domain.BalanceResponse realBalance := playerWallets[0].Balance
err := s.client.post(ctx, "/balance", req, sigParams, &res)
return &res, err // Retrieve bonus balance if applicable
var bonusBalance float64
if len(playerWallets) > 1 {
bonusBalance = float64(playerWallets[1].Balance)
} else {
bonusBalance = 0
}
// Build the response
res := &domain.BalanceResponse{
Real: struct {
Currency string `json:"currency"`
Amount float64 `json:"amount"`
}{
Currency: string(playerWallets[0].Currency),
Amount: float64(realBalance),
},
}
if bonusBalance > 0 {
res.Bonus = &struct {
Currency string `json:"currency"`
Amount float64 `json:"amount"`
}{
Currency: req.Currency,
Amount: bonusBalance,
}
}
return res, nil
} }
func (s *service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domain.BetResponse, error) { func (s *service) ProcessBet(ctx context.Context, req domain.BetRequest) (*domain.BetResponse, error) {

View File

@ -1,8 +1,6 @@
package handlers package handlers
import ( import (
"encoding/json"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@ -14,13 +12,13 @@ import (
// @Tags Arifpay // @Tags Arifpay
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param request body domain.CreateCheckoutSessionRequest true "Checkout session request payload" // @Param request body domain.CheckoutSessionClientRequest true "Checkout session request payload"
// @Success 200 {object} domain.Response // @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse // @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/checkout [post] // @Router /api/v1/arifpay/checkout [post]
func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error { func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
var req domain.CreateCheckoutSessionRequest var req domain.CheckoutSessionClientRequest
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: err.Error(), Error: err.Error(),
@ -28,7 +26,7 @@ func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
}) })
} }
paymentURL, err := h.arifpaySvc.CreateCheckoutSession(req) data, err := h.arifpaySvc.CreateCheckoutSession(req, true)
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Error: err.Error(), Error: err.Error(),
@ -38,62 +36,138 @@ func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(domain.Response{ return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Checkout session created successfully", Message: "Checkout session created successfully",
Data: paymentURL, Data: data,
Success: true, Success: true,
StatusCode: fiber.StatusOK, StatusCode: fiber.StatusOK,
}) })
} }
// B2CTransferHandler handles Arifpay B2C transfers based on the transfer_mode. // CancelCheckoutSessionHandler cancels an existing Arifpay checkout session.
// //
// @Summary Initiate B2C Transfer // @Summary Cancel Arifpay Checkout Session
// @Description Initiates a B2C transfer via Telebirr, CBE, or MPESA through Arifpay // @Description Cancels a payment session using Arifpay before completion.
// @Tags Arifpay // @Tags Arifpay
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param transfer_mode query string true "Transfer mode (Telebirr, CBE, MPESA)" // @Param sessionId path string true "Checkout session ID"
// @Param request body domain.ArifPayB2CRequest true "Transfer request payload" // @Success 200 {object} domain.Response
// @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse
// @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse
// @Failure 502 {object} domain.ErrorResponse // @Router /api/v1/arifpay/checkout/{sessionId}/cancel [post]
// @Router /api/v1/arifpay/b2c/transfer [post] func (h *Handler) CancelCheckoutSessionHandler(c *fiber.Ctx) error {
func (h *Handler) B2CTransferHandler(c *fiber.Ctx) error { sessionID := c.Params("sessionId")
transferMode := c.Query("transfer_mode") if sessionID == "" {
var endpoint string
switch transferMode {
case "Telebirr":
endpoint = "https://telebirr-b2c.arifpay.net/api/Telebirr/b2c/transfer"
case "CBE":
endpoint = "https://cbe-b2c.arifpay.net/api/Cbebirr/b2c/transfer"
case "MPESA":
endpoint = "https://mpesa-b2c.arifpay.net/api/Mpesa/b2c/transfer"
default:
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: "invalid transfer_mode. Allowed values: Telebirr, CBE, MPESA", Error: "missing session ID",
Message: "Failed to process your request", Message: "Session ID is required",
}) })
} }
var req domain.ArifPayB2CRequest data, err := h.arifpaySvc.CancelCheckoutSession(c.Context(), sessionID)
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to process your request",
})
}
resp, err := h.arifpaySvc.B2CTransfer(c.Context(), req, endpoint)
if err != nil { if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Error: err.Error(), Error: err.Error(),
Message: "Failed to process your request", Message: "Failed to cancel checkout session",
}) })
} }
return c.Status(fiber.StatusOK).JSON(domain.Response{ return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Transfer initiated successfully", Message: "Checkout session canceled successfully",
Data: resp, Data: data,
Success: true,
StatusCode: fiber.StatusOK,
})
}
// HandleWebhook processes Arifpay webhook notifications.
//
// @Summary Handle Arifpay C2B Webhook
// @Description Handles webhook notifications from Arifpay for C2B transfers and updates transfer + wallet status.
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param request body domain.WebhookRequest true "Arifpay webhook payload"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/c2b-webhook [post]
func (h *Handler) HandleArifpayC2BWebhook(c *fiber.Ctx) error {
var req domain.WebhookRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Invalid webhook payload",
})
}
// 🚨 Decide how to get userId:
// If you get it from auth context/middleware, extract it here.
// For now, let's assume userId comes from your auth claims:
userId, ok := c.Locals("user_id").(int64)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
Error: "missing user id",
Message: "Unauthorized",
})
}
err := h.arifpaySvc.HandleWebhook(c.Context(), req, userId, true)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to process webhook",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Webhook processed successfully",
Success: true,
StatusCode: fiber.StatusOK,
})
}
// HandleWebhook processes Arifpay webhook notifications.
//
// @Summary Handle Arifpay B2C Webhook
// @Description Handles webhook notifications from Arifpay for B2C transfers and updates transfer + wallet status.
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param request body domain.WebhookRequest true "Arifpay webhook payload"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/b2c-webhook [post]
func (h *Handler) HandleArifpayB2CWebhook(c *fiber.Ctx) error {
var req domain.WebhookRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Invalid webhook payload",
})
}
// 🚨 Decide how to get userId:
// If you get it from auth context/middleware, extract it here.
// For now, let's assume userId comes from your auth claims:
userId, ok := c.Locals("user_id").(int64)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
Error: "missing user id",
Message: "Unauthorized",
})
}
err := h.arifpaySvc.HandleWebhook(c.Context(), req, userId, false)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to process webhook",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Webhook processed successfully",
Success: true, Success: true,
StatusCode: fiber.StatusOK, StatusCode: fiber.StatusOK,
}) })
@ -119,14 +193,7 @@ func (h *Handler) ArifpayVerifyByTransactionIDHandler(c *fiber.Ctx) error {
}) })
} }
if req.TransactionID == "" || req.PaymentType == 0 { resp, err := h.arifpaySvc.VerifyTransactionByTransactionID(c.Context(), req)
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: "missing transactionId or paymentType",
Message: "transactionId and paymentType are required fields",
})
}
resp, err := h.arifpaySvc.VerifyByTransactionID(req.TransactionID, req.PaymentType)
if err != nil { if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Error: err.Error(), Error: err.Error(),
@ -136,7 +203,7 @@ func (h *Handler) ArifpayVerifyByTransactionIDHandler(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(domain.Response{ return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Transaction verified successfully", Message: "Transaction verified successfully",
Data: json.RawMessage(resp), Data: resp,
Success: true, Success: true,
StatusCode: fiber.StatusOK, StatusCode: fiber.StatusOK,
}) })
@ -162,18 +229,104 @@ func (h *Handler) ArifpayVerifyBySessionIDHandler(c *fiber.Ctx) error {
}) })
} }
resp, err := h.arifpaySvc.VerifyBySessionID(sessionID) resp, err := h.arifpaySvc.VerifyTransactionBySessionID(c.Context(), sessionID)
if err != nil { if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Error: err.Error(), Error: err.Error(),
Message: "Failed to verify session", Message: "Failed to verify transaction",
}) })
} }
return c.Status(fiber.StatusOK).JSON(domain.Response{ return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Session verified successfully", Message: "Transaction verified successfully",
Data: json.RawMessage(resp), Data: resp,
Success: true, Success: true,
StatusCode: fiber.StatusOK, StatusCode: fiber.StatusOK,
}) })
} }
// ExecuteTransfer handles B2C transfers via Telebirr, CBE, or MPESA.
//
// @Summary Execute B2C Transfer
// @Description Initiates a B2C transfer using Telebirr, CBE, or MPESA depending on the "type" query parameter
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param type query string true "Transfer type (telebirr, cbe, mpesa)"
// @Param request body domain.ArifpayB2CRequest true "Transfer request payload"
// @Success 200 {object} map[string]string "message: transfer executed successfully"
// @Failure 400 {object} map[string]string "error: invalid request or unsupported transfer type"
// @Failure 500 {object} map[string]string "error: internal server error"
// @Router /api/v1/arifpay/b2c/transfer [post]
func (h *Handler) ExecuteArifpayB2CTransfer(c *fiber.Ctx) error {
transferType := c.Query("type")
if transferType == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to process your withdrawal request",
Error: "missing query parameter: type (telebirr, cbe, mpesa)",
})
}
userId, ok := c.Locals("user_id").(int64)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
Error: "missing user id",
Message: "Unauthorized",
})
}
var req domain.ArifpayB2CRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to process your withdrawal request",
Error: "invalid request body",
})
}
var err error
switch transferType {
case "telebirr":
err = h.arifpaySvc.ExecuteTelebirrB2CTransfer(c.Context(), req, userId)
case "cbe":
err = h.arifpaySvc.ExecuteCBEB2CTransfer(c.Context(), req, userId)
case "mpesa":
err = h.arifpaySvc.ExecuteMPesaB2CTransfer(c.Context(), req, userId)
default:
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to process your withdrawal request",
Error: "unsupported transfer type, must be one of: telebirr, cbe, mpesa",
})
}
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to process your withdrawal request",
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Withdrawal process initiated successfully",
Success: true,
StatusCode: fiber.StatusOK,
})
}
// GetPaymentMethodsHandler returns the list of all Arifpay payment methods
//
// @Summary List Arifpay Payment Methods
// @Description Returns all payment method IDs and names for Arifpay
// @Tags Arifpay
// @Produce json
// @Success 200 {object} []domain.ARIFPAYPaymentMethod
// @Router /api/v1/arifpay/payment-methods [get]
func (h *Handler) GetArifpayPaymentMethodsHandler(c *fiber.Ctx) error {
methods := h.arifpaySvc.GetPaymentMethodsMapping()
return c.Status(fiber.StatusOK).JSON(domain.Response{
Success: true,
Message: "Arifpay payment methods fetched successfully",
Data: methods,
StatusCode: fiber.StatusOK,
})
}

View File

@ -175,14 +175,22 @@ func (h *Handler) StartDemoGame(c *fiber.Ctx) error {
func (h *Handler) GetBalance(c *fiber.Ctx) error { func (h *Handler) GetBalance(c *fiber.Ctx) error {
var req domain.BalanceRequest var req domain.BalanceRequest
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") // return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid request body",
Error: err.Error(),
})
} }
// Optionally verify signature here... // Optionally verify signature here...
balance, err := h.veliVirtualGameSvc.GetBalance(c.Context(), req) balance, err := h.veliVirtualGameSvc.GetBalance(c.Context(), req)
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to retrieve balance",
Error: err.Error(),
})
} }
return c.JSON(balance) return c.JSON(balance)
@ -191,7 +199,11 @@ func (h *Handler) GetBalance(c *fiber.Ctx) error {
func (h *Handler) PlaceBet(c *fiber.Ctx) error { func (h *Handler) PlaceBet(c *fiber.Ctx) error {
var req domain.BetRequest var req domain.BetRequest
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") // return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid request body",
Error: err.Error(),
})
} }
// Signature check optional here // Signature check optional here
@ -201,7 +213,11 @@ func (h *Handler) PlaceBet(c *fiber.Ctx) error {
if errors.Is(err, veli.ErrDuplicateTransaction) { if errors.Is(err, veli.ErrDuplicateTransaction) {
return fiber.NewError(fiber.StatusConflict, "DUPLICATE_TRANSACTION") return fiber.NewError(fiber.StatusConflict, "DUPLICATE_TRANSACTION")
} }
return fiber.NewError(fiber.StatusBadRequest, err.Error()) return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to process bet",
Error: err.Error(),
})
// return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
return c.JSON(res) return c.JSON(res)
@ -210,7 +226,11 @@ func (h *Handler) PlaceBet(c *fiber.Ctx) error {
func (h *Handler) RegisterWin(c *fiber.Ctx) error { func (h *Handler) RegisterWin(c *fiber.Ctx) error {
var req domain.WinRequest var req domain.WinRequest
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") // return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid request body",
Error: err.Error(),
})
} }
res, err := h.veliVirtualGameSvc.ProcessWin(c.Context(), req) res, err := h.veliVirtualGameSvc.ProcessWin(c.Context(), req)
@ -218,7 +238,11 @@ func (h *Handler) RegisterWin(c *fiber.Ctx) error {
if errors.Is(err, veli.ErrDuplicateTransaction) { if errors.Is(err, veli.ErrDuplicateTransaction) {
return fiber.NewError(fiber.StatusConflict, "DUPLICATE_TRANSACTION") return fiber.NewError(fiber.StatusConflict, "DUPLICATE_TRANSACTION")
} }
return fiber.NewError(fiber.StatusInternalServerError, err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to process win",
Error: err.Error(),
})
} }
return c.JSON(res) return c.JSON(res)
@ -227,7 +251,11 @@ func (h *Handler) RegisterWin(c *fiber.Ctx) error {
func (h *Handler) CancelTransaction(c *fiber.Ctx) error { func (h *Handler) CancelTransaction(c *fiber.Ctx) error {
var req domain.CancelRequest var req domain.CancelRequest
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") // return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid request body",
Error: err.Error(),
})
} }
res, err := h.veliVirtualGameSvc.ProcessCancel(c.Context(), req) res, err := h.veliVirtualGameSvc.ProcessCancel(c.Context(), req)
@ -235,7 +263,11 @@ func (h *Handler) CancelTransaction(c *fiber.Ctx) error {
if errors.Is(err, veli.ErrDuplicateTransaction) { if errors.Is(err, veli.ErrDuplicateTransaction) {
return fiber.NewError(fiber.StatusConflict, "DUPLICATE_TRANSACTION") return fiber.NewError(fiber.StatusConflict, "DUPLICATE_TRANSACTION")
} }
return fiber.NewError(fiber.StatusInternalServerError, err.Error()) // return fiber.NewError(fiber.StatusInternalServerError, err.Error())
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Failed to process cancel",
Error: err.Error(),
})
} }
return c.JSON(res) return c.JSON(res)

View File

@ -114,9 +114,13 @@ func (a *App) initAppRoutes() {
//Arifpay //Arifpay
groupV1.Post("/arifpay/checkout", a.authMiddleware, h.CreateCheckoutSessionHandler) groupV1.Post("/arifpay/checkout", a.authMiddleware, h.CreateCheckoutSessionHandler)
groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.B2CTransferHandler) groupV1.Post("/arifpay/checkout/cancel/:session_id", a.authMiddleware, h.CancelCheckoutSessionHandler)
groupV1.Post("/api/v1/arifpay/c2b-webhook", a.authMiddleware, h.HandleArifpayC2BWebhook)
groupV1.Post("/api/v1/arifpay/b2c-webhook", a.authMiddleware, h.HandleArifpayB2CWebhook)
groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.ExecuteArifpayB2CTransfer)
groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler) groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler)
groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler) groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
groupV1.Get("/arifpay/payment-methods", a.authMiddleware, h.GetArifpayPaymentMethodsHandler)
//Telebirr //Telebirr
groupV1.Post("/telebirr/init-payment", a.authMiddleware, h.CreateTelebirrPaymentHandler) groupV1.Post("/telebirr/init-payment", a.authMiddleware, h.CreateTelebirrPaymentHandler)
@ -281,7 +285,7 @@ func (a *App) initAppRoutes() {
//Veli Virtual Game Routes //Veli Virtual Game Routes
groupV1.Post("/veli/providers", h.GetProviders) groupV1.Post("/veli/providers", h.GetProviders)
groupV1.Post("/veli/games-list", h.GetGamesByProvider) groupV1.Post("/veli/games-list", h.GetGamesByProvider)
groupV1.Post("/veli/start-game", a.authMiddleware, h.StartGame) groupV1.Post("/veli/start-game", h.StartGame)
groupV1.Post("/veli/start-demo-game", h.StartDemoGame) groupV1.Post("/veli/start-demo-game", h.StartDemoGame)
a.fiber.Post("/balance", h.GetBalance) a.fiber.Post("/balance", h.GetBalance)
groupV1.Post("/veli/gaming-activity", h.GetGamingActivity) groupV1.Post("/veli/gaming-activity", h.GetGamingActivity)