5 min report fix + arifpay integration

This commit is contained in:
Yared Yemane 2025-07-23 14:02:52 +03:00
parent ae56d253c2
commit d8de92e7d6
26 changed files with 3096 additions and 2975 deletions

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"log" "log"
"log/slog" "log/slog"
"net/http"
"os" "os"
"time" "time"
@ -28,6 +29,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
// "github.com/SamuelTariku/FortuneBet-Backend/internal/router" // "github.com/SamuelTariku/FortuneBet-Backend/internal/router"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/arifpay"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bonus" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bonus"
@ -38,6 +40,7 @@ import (
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/institutions" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/institutions"
issuereporting "github.com/SamuelTariku/FortuneBet-Backend/internal/services/issue_reporting" issuereporting "github.com/SamuelTariku/FortuneBet-Backend/internal/services/issue_reporting"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/kafka"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
@ -119,6 +122,7 @@ func main() {
// userStore, // userStore,
notificationSvc, notificationSvc,
logger, logger,
kafka.NewProducer([]string{"localhost:9092"}, "wallet-events"),
) )
branchSvc := branch.NewService(store) branchSvc := branch.NewService(store)
@ -209,32 +213,18 @@ func main() {
httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc) httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc)
httpserver.StartTicketCrons(*ticketSvc) httpserver.StartTicketCrons(*ticketSvc)
// Fetch companies and branches for live wallet metrics update
// ctx := context.Background()
// companies := []domain.GetCompany{
// {ID: 1, Name: "Company A", WalletBalance: 1000.0},
// }
// branches := []domain.BranchWallet{
// {ID: 10, Name: "Branch Z", CompanyID: 1, Balance: 500.0},
// }
// notificationSvc.UpdateLiveWalletMetrics(ctx, companies, branches)
// if err != nil {
// log.Println("Failed to update live metrics:", err)
// } else {
// log.Println("Live metrics broadcasted successfully")
// }
issueReportingRepo := repository.NewReportedIssueRepository(store) issueReportingRepo := repository.NewReportedIssueRepository(store)
issueReportingSvc := issuereporting.New(issueReportingRepo) issueReportingSvc := issuereporting.New(issueReportingRepo)
// go httpserver.SetupReportCronJob(reportWorker) transferStore := wallet.TransferStore(store)
arifpaySvc := arifpay.NewArifpayService(cfg, transferStore, &http.Client{
Timeout: 30 * time.Second})
// Initialize and start HTTP server // Initialize and start HTTP server
app := httpserver.NewApp( app := httpserver.NewApp(
arifpaySvc,
issueReportingSvc, issueReportingSvc,
instSvc, instSvc,
currSvc, currSvc,

View File

@ -1,11 +1,11 @@
-- Settings Initial Data -- Settings Initial Data
INSERT INTO settings (key, value) INSERT INTO settings (key, value)
VALUES ('max_number_of_outcomes', '30'), VALUES
('max_number_of_outcomes', '30'),
('bet_amount_limit', '100000'), ('bet_amount_limit', '100000'),
('daily_ticket_limit', '50'), ('daily_ticket_limit', '50'),
('total_winnings_limit', '1000000'), ('total_winnings_limit', '1000000'),
('amount_for_bet_referral', '1000000') ('amount_for_bet_referral', '1000000'),
('cashback_amount_cap', '1000') ('cashback_amount_cap', '1000')
ON CONFLICT (key) DO ON CONFLICT (key)
UPDATE DO UPDATE SET value = EXCLUDED.value;
SET value = EXCLUDED.value;

View File

@ -21,7 +21,7 @@ services:
mongo: mongo:
container_name: fortunebet-mongo container_name: fortunebet-mongo
image: mongo:7.0 image: mongo:7.0.11
restart: always restart: always
ports: ports:
- "27017:27017" - "27017:27017"
@ -89,8 +89,8 @@ services:
networks: networks:
- app - app
command: ["/app/bin/web"] command: ["/app/bin/web"]
volumes: # volumes:
- "C:/Users/User/Desktop:/host-desktop" # - "C:/Users/User/Desktop:/host-desktop"
test: test:
build: build:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

29
go.mod
View File

@ -16,15 +16,15 @@ require (
// github.com/stretchr/testify v1.10.0 // github.com/stretchr/testify v1.10.0
github.com/swaggo/fiber-swagger v1.3.0 github.com/swaggo/fiber-swagger v1.3.0
github.com/swaggo/swag v1.16.4 github.com/swaggo/swag v1.16.4
github.com/valyala/fasthttp v1.59.0 github.com/valyala/fasthttp v1.63.0
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.39.0
) )
require ( require (
// github.com/davecgh/go-spew v1.1.1 // indirect // github.com/davecgh/go-spew v1.1.1 // indirect
// github.com/pmezard/go-difflib v1.0.0 // indirect // github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.2.0 // indirect
// github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
@ -53,11 +53,11 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
go.mongodb.org/mongo-driver v1.17.3 go.mongodb.org/mongo-driver v1.17.3
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/net v0.38.0 // indirect golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.12.0 // indirect golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.31.0 // indirect golang.org/x/tools v0.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
@ -77,10 +77,8 @@ require (
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
) )
require ( // github.com/go-resty/resty/v2 v2.16.5
// github.com/go-resty/resty/v2 v2.16.5 require github.com/twilio/twilio-go v1.26.3
github.com/twilio/twilio-go v1.26.3
)
require ( require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
@ -90,3 +88,10 @@ require (
github.com/redis/go-redis/v9 v9.10.0 // direct github.com/redis/go-redis/v9 v9.10.0 // direct
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
) )
require (
github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/segmentio/kafka-go v0.4.48 // direct
)
require github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f // direct

67
go.sum
View File

@ -1,3 +1,5 @@
github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f h1:UOp9At84RG8OT2Nw2TQidYWaoW1rAfTqChOJLdhYcm8=
github.com/AnaniyaBelew/ArifpayGoPlugin v0.0.0-20231127130208-54b9bc51118f/go.mod h1:N2NQ6ad3i+oLQU+MlPci2f7mx6Mtg+wUcvzCv77KCl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
@ -7,9 +9,13 @@ github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaW
github.com/amanuelabay/afrosms-go v1.0.6 h1:/B9upckMqzr5/de7dbXPqIfmJm4utbQq0QUQePxmXtk= github.com/amanuelabay/afrosms-go v1.0.6 h1:/B9upckMqzr5/de7dbXPqIfmJm4utbQq0QUQePxmXtk=
github.com/amanuelabay/afrosms-go v1.0.6/go.mod h1:5mzzZtWSCDdvQsA0OyYf5CtbdGpl9lXyrcpl8/DyBj0= github.com/amanuelabay/afrosms-go v1.0.6/go.mod h1:5mzzZtWSCDdvQsA0OyYf5CtbdGpl9lXyrcpl8/DyBj0=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
@ -54,8 +60,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY= github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
@ -87,6 +91,7 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
@ -124,6 +129,8 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -143,6 +150,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/segmentio/kafka-go v0.4.48 h1:9jyu9CWK4W5W+SroCe8EffbrRZVqAOkuaLd/ApID4Vs=
github.com/segmentio/kafka-go v0.4.48/go.mod h1:HjF6XbOKh0Pjlkr5GVZxt6CsjjwnmhVOfURM5KMd8qg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@ -174,8 +183,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.35.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/fasthttp v1.35.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/fasthttp v1.36.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/fasthttp v1.36.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI= github.com/valyala/fasthttp v1.63.0 h1:DisIL8OjB7ul2d7cBaMRcKTQDYnrGy56R4FCiuDP0Ns=
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU= github.com/valyala/fasthttp v1.63.0/go.mod h1:REc4IeW+cAEyLrRPa5A81MIjvz0QE1laoTX2EaPHKJM=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@ -206,12 +215,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -222,13 +233,17 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -242,28 +257,36 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -58,6 +58,14 @@ type VeliConfig struct {
Enabled bool `mapstructure:"Enabled"` Enabled bool `mapstructure:"Enabled"`
} }
type ARIFPAYConfig struct {
APIKey string `mapstructure:"VELI_API_KEY"`
CancelUrl string `mapstructure:"VELI_BASE_URL"`
ErrorUrl string `mapstructure:"VELI_SECRET_KEY"`
NotifyUrl string `mapstructure:"VELI_OPERATOR_ID"`
SuccessUrl string `mapstructure:"VELI_BRAND_ID"`
}
type Config struct { type Config struct {
FIXER_API_KEY string FIXER_API_KEY string
FIXER_BASE_URL string FIXER_BASE_URL string
@ -87,6 +95,7 @@ type Config struct {
PopOK domain.PopOKConfig PopOK domain.PopOKConfig
AleaPlay AleaPlayConfig `mapstructure:"alea_play"` AleaPlay AleaPlayConfig `mapstructure:"alea_play"`
VeliGames VeliConfig `mapstructure:"veli_games"` VeliGames VeliConfig `mapstructure:"veli_games"`
ARIFPAY ARIFPAYConfig `mapstructure:"arifpay_config"`
ResendApiKey string ResendApiKey string
ResendSenderEmail string ResendSenderEmail string
TwilioAccountSid string TwilioAccountSid string
@ -189,6 +198,12 @@ func (c *Config) loadEnv() error {
c.CHAPA_CALLBACK_URL = os.Getenv("CHAPA_CALLBACK_URL") c.CHAPA_CALLBACK_URL = os.Getenv("CHAPA_CALLBACK_URL")
c.CHAPA_RETURN_URL = os.Getenv("CHAPA_RETURN_URL") c.CHAPA_RETURN_URL = os.Getenv("CHAPA_RETURN_URL")
c.ARIFPAY.APIKey = os.Getenv("ARIFPAY_API_KEY")
c.ARIFPAY.CancelUrl = os.Getenv("ARIFPAY_CANCEL_URL")
c.ARIFPAY.ErrorUrl = os.Getenv("ARIFPAY_ERROR_URL")
c.ARIFPAY.NotifyUrl = os.Getenv("ARIFPAY_NOTIFY_URL")
c.ARIFPAY.SuccessUrl = os.Getenv("ARIFPAY_SUCCESS_URL")
//Alea Play //Alea Play
aleaEnabled := os.Getenv("ALEA_ENABLED") aleaEnabled := os.Getenv("ALEA_ENABLED")
if aleaEnabled == "" { if aleaEnabled == "" {

View File

@ -0,0 +1,53 @@
package domain
import "time"
type Item struct {
Name string `json:"name"`
Quantity int `json:"quantity"`
Price float64 `json:"price"`
Description string `json:"description"`
Image string `json:"image"`
}
type Beneficiary struct {
AccountNumber string `json:"accountNumber"`
Bank string `json:"bank"`
Amount float64 `json:"amount"`
}
type CreateCheckoutSessionRequest 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"`
Msg string `json:"msg"`
Data struct {
SessionID string `json:"sessionId"`
PaymentURL string `json:"paymentUrl"`
CancelURL string `json:"cancelUrl"`
TotalAmount float64 `json:"totalAmount"`
} `json:"data"`
}
type ArifPayB2CRequest struct {
SessionID string `json:"Sessionid"`
PhoneNumber string `json:"Phonenumber"`
}
type ArifpayVerifyByTransactionIDRequest struct {
TransactionID string `json:"transactionId"`
PaymentType int `json:"paymentType"`
}

View File

@ -0,0 +1,18 @@
package event
import "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
type WalletEventType string
const (
WalletBalanceUpdated WalletEventType = "wallet.balance.updated"
WalletLowAlert WalletEventType = "wallet.alert.low_balance"
)
type WalletEvent struct {
EventType WalletEventType `json:"event_type"`
WalletID int64 `json:"wallet_id"`
UserID int64 `json:"user_id"`
Balance domain.Currency `json:"balance"`
Trigger string `json:"trigger"` // e.g. "AddToWallet", "DeductFromWallet"
}

View File

@ -0,0 +1,192 @@
package arifpay
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/AnaniyaBelew/ArifpayGoPlugin"
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
"github.com/google/uuid"
)
type ArifpayService struct {
cfg *config.Config
transferStore wallet.TransferStore
httpClient *http.Client
}
func NewArifpayService(cfg *config.Config, transferStore wallet.TransferStore, httpClient *http.Client) *ArifpayService {
return &ArifpayService{
cfg: cfg,
transferStore: transferStore,
httpClient: httpClient,
}
}
func (s *ArifpayService) CreateCheckoutSession(req domain.CreateCheckoutSessionRequest) (string, error) {
// Create SDK-compatible payload
paymentPayload := ArifpayGoPlugin.PaymentRequest{
CancelUrl: s.cfg.ARIFPAY.CancelUrl,
Phone: req.Phone,
Email: req.Email,
Nonce: req.Nonce,
ErrorUrl: s.cfg.ARIFPAY.ErrorUrl,
NotifyUrl: s.cfg.ARIFPAY.NotifyUrl,
SuccessUrl: s.cfg.ARIFPAY.SuccessUrl,
PaymentMethods: req.PaymentMethods,
Lang: req.Lang,
}
// Convert items
for _, item := range req.Items {
paymentPayload.Items = append(paymentPayload.Items, domain.Item{
Name: item.Name,
Quantity: item.Quantity,
Price: item.Price,
Description: item.Description,
Image: item.Image,
})
}
// Convert beneficiaries
for _, b := range req.Beneficiaries {
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 {
return "", err
}
transfer := domain.CreateTransfer{
Amount: domain.Currency(req.Beneficiaries[0].Amount),
Verified: false,
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 {
return nil, fmt.Errorf("failed to marshal request payload: %w", 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("Accept", "application/json")
httpReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
resp, err := s.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("request to Telebirr B2C failed: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Telebirr API returned status %d: %s", resp.StatusCode, string(body))
}
var response map[string]interface{}
if err := json.Unmarshal(body, &response); err != nil {
return nil, fmt.Errorf("failed to parse response body: %w", err)
}
return &response, nil
}
func (s *ArifpayService) VerifyByTransactionID(transactionID string, paymentType int) ([]byte, error) {
url := "https://gateway.arifpay.org/api/checkout/getSessionByTransactionId"
var payload domain.ArifpayVerifyByTransactionIDRequest
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 {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
resp, err := s.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("non-200 response from Arifpay: %s", string(respBody))
}
return respBody, nil
}
func (s *ArifpayService) VerifyBySessionID(sessionID string) ([]byte, error) {
url := "https://gateway.arifpay.org/api/ms/transaction/status/" + sessionID
// Create GET request without body
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
resp, err := s.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("non-200 response from Arifpay: %s", string(respBody))
}
return respBody, nil
}

View File

@ -0,0 +1,80 @@
package kafka
import (
"context"
"encoding/json"
"log"
"github.com/SamuelTariku/FortuneBet-Backend/internal/event"
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/ws"
"github.com/segmentio/kafka-go"
)
type WalletConsumer struct {
reader *kafka.Reader
hub *ws.NotificationHub
topic string
groupID string
brokers []string
}
func NewWalletConsumer(brokers []string, topic, groupID string, hub *ws.NotificationHub) *WalletConsumer {
return &WalletConsumer{
brokers: brokers,
topic: topic,
groupID: groupID,
hub: hub,
reader: kafka.NewReader(kafka.ReaderConfig{
Brokers: brokers,
GroupID: groupID,
Topic: topic,
}),
}
}
func (c *WalletConsumer) Start(ctx context.Context) {
go func() {
for {
m, err := c.reader.ReadMessage(ctx)
if err != nil {
log.Printf("Error reading wallet Kafka message: %v", err)
continue
}
var evt event.WalletEvent
if err := json.Unmarshal(m.Value, &evt); err != nil {
log.Printf("Failed to unmarshal wallet event: %v", err)
continue
}
payload := map[string]interface{}{
"type": evt.EventType,
"wallet_id": evt.WalletID,
"user_id": evt.UserID,
"balance": evt.Balance,
"trigger": evt.Trigger,
"recipient_id": evt.UserID,
}
// Broadcast to appropriate WebSocket clients
c.hub.Broadcast <- payload
}
}()
}
// func (c *WalletConsumer) Shutdown() error {
// return c.reader.Close()
// }
// func (h *ws.NotificationHub) Shutdown() {
// close(h.Register)
// close(h.Unregister)
// close(h.Broadcast)
// h.mu.Lock()
// defer h.mu.Unlock()
// for client := range h.Clients {
// client.Conn.Close()
// delete(h.Clients, client)
// }
// }

View File

@ -0,0 +1,36 @@
package kafka
import (
"context"
"encoding/json"
"time"
"github.com/segmentio/kafka-go"
)
type Producer struct {
writer *kafka.Writer
}
func NewProducer(brokers []string, topic string) *Producer {
return &Producer{
writer: &kafka.Writer{
Addr: kafka.TCP(brokers...),
Topic: topic,
Balancer: &kafka.LeastBytes{},
},
}
}
func (p *Producer) Publish(ctx context.Context, key string, event any) error {
msgBytes, err := json.Marshal(event)
if err != nil {
return err
}
return p.writer.WriteMessages(ctx, kafka.Message{
Key: []byte(key),
Value: msgBytes,
Time: time.Now(),
})
}

View File

@ -394,7 +394,7 @@ func (s *Service) GetLiveMetrics(ctx context.Context) (domain.LiveMetric, error)
return metric, nil return metric, nil
} }
func (s *Service) UpdateLiveWalletMetricForWallet(ctx context.Context, wallet domain.Wallet) { func (s *Service) UpdateLiveMetricForWallet(ctx context.Context, wallet domain.Wallet) {
var ( var (
payload domain.LiveWalletMetrics payload domain.LiveWalletMetrics
event map[string]interface{} event map[string]interface{}

View File

@ -779,164 +779,6 @@ func getTimeRange(period string) (time.Time, time.Time) {
} }
} }
// func (s *Service) GetCompanyPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CompanyPerformance, error) {
// // Get company bet activity
// companyBets, err := s.betStore.GetCompanyBetActivity(ctx, filter)
// if err != nil {
// s.logger.Error("failed to get company bet activity", "error", err)
// return nil, err
// }
// // Get company details
// companyDetails, err := s.branchStore.GetCompanyDetails(ctx, filter)
// if err != nil {
// s.logger.Error("failed to get company details", "error", err)
// return nil, err
// }
// // Get company branches
// companyBranches, err := s.branchStore.GetCompanyBranchCounts(ctx, filter)
// if err != nil {
// s.logger.Error("failed to get company branch counts", "error", err)
// return nil, err
// }
// // Combine data into performance report
// var performances []domain.CompanyPerformance
// for _, bet := range companyBets {
// performance := domain.CompanyPerformance{
// CompanyID: bet.CompanyID,
// TotalBets: bet.TotalBets,
// TotalStakes: bet.TotalStakes,
// TotalWins: bet.TotalWins,
// TotalPayouts: bet.TotalPayouts,
// Profit: bet.TotalStakes - bet.TotalPayouts,
// }
// // Add company details
// if details, ok := companyDetails[bet.CompanyID]; ok {
// performance.CompanyName = details.Name
// performance.ContactEmail = details.ContactEmail
// }
// // Add branch counts
// if branches, ok := companyBranches[bet.CompanyID]; ok {
// performance.TotalBranches = branches.Total
// performance.ActiveBranches = branches.Active
// }
// // Calculate metrics
// if bet.TotalBets > 0 {
// performance.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100
// performance.AverageStake = bet.TotalStakes / domain.Currency(bet.TotalBets)
// }
// performances = append(performances, performance)
// }
// // Sort by profit (descending)
// sort.Slice(performances, func(i, j int) bool {
// return performances[i].Profit > performances[j].Profit
// })
// return performances, nil
// }
// GetCashierPerformance returns cashier performance report
// func (s *Service) GetCashierPerformance(ctx context.Context, filter domain.ReportFilter) ([]domain.CashierPerformance, error) {
// // Get cashier bet activity
// cashierBets, err := s.betStore.GetCashierBetActivity(ctx, filter)
// if err != nil {
// s.logger.Error("failed to get cashier bet activity", "error", err)
// return nil, err
// }
// // Get cashier details
// cashierDetails, err := s.userStore.GetCashierDetails(ctx, filter)
// if err != nil {
// s.logger.Error("failed to get cashier details", "error", err)
// return nil, err
// }
// // Get cashier transactions
// cashierTransactions, err := s.transactionStore.GetCashierTransactionTotals(ctx, filter)
// if err != nil {
// s.logger.Error("failed to get cashier transactions", "error", err)
// return nil, err
// }
// // Combine data into performance report
// var performances []domain.CashierPerformance
// for _, bet := range cashierBets {
// performance := domain.CashierPerformance{
// CashierID: bet.CashierID,
// TotalBets: bet.TotalBets,
// TotalStakes: bet.TotalStakes,
// TotalWins: bet.TotalWins,
// TotalPayouts: bet.TotalPayouts,
// Profit: bet.TotalStakes - bet.TotalPayouts,
// }
// // Add cashier details
// if details, ok := cashierDetails[bet.CashierID]; ok {
// performance.CashierName = details.Name
// performance.BranchID = details.BranchID
// performance.BranchName = details.BranchName
// }
// // Add transactions
// if transactions, ok := cashierTransactions[bet.CashierID]; ok {
// performance.Deposits = transactions.Deposits
// performance.Withdrawals = transactions.Withdrawals
// }
// // Calculate metrics
// if bet.TotalBets > 0 {
// performance.WinRate = float64(bet.TotalWins) / float64(bet.TotalBets) * 100
// performance.AverageStake = bet.TotalStakes / domain.Currency(bet.TotalBets)
// }
// performances = append(performances, performance)
// }
// // Sort by total stakes (descending)
// sort.Slice(performances, func(i, j int) bool {
// return performances[i].TotalStakes > performances[j].TotalStakes
// })
// return performances, nil
// }
// GetNotificationReport returns notification statistics report
// func (s *Service) GetNotificationReport(ctx context.Context, filter domain.ReportFilter) (domain.NotificationReport, error) {
// // Get notification counts by type
// countsByType, err := s.notificationStore.GetNotificationCountsByType(ctx, filter)
// if err != nil {
// s.logger.Error("failed to get notification counts by type", "error", err)
// return domain.NotificationReport{}, err
// }
// // Get notification delivery stats
// deliveryStats, err := s.notificationStore.GetNotificationDeliveryStats(ctx, filter)
// if err != nil {
// s.logger.Error("failed to get notification delivery stats", "error", err)
// return domain.NotificationReport{}, err
// }
// // Get most active notification recipients
// activeRecipients, err := s.notificationStore.GetMostActiveNotificationRecipients(ctx, filter)
// if err != nil {
// s.logger.Error("failed to get active notification recipients", "error", err)
// return domain.NotificationReport{}, err
// }
// return domain.NotificationReport{
// CountsByType: countsByType,
// DeliveryStats: deliveryStats,
// ActiveRecipients: activeRecipients,
// }, nil
// }
// Helper functions // Helper functions
func validateTimeRange(filter domain.ReportFilter) error { func validateTimeRange(filter domain.ReportFilter) error {
if filter.StartTime.Valid && filter.EndTime.Valid { if filter.StartTime.Valid && filter.EndTime.Valid {

View File

@ -141,7 +141,8 @@ func (s *Service) GetBranchByRole(ctx context.Context, branchID *int64, role dom
// var branchID int64 // var branchID int64
// var companyID int64 // var companyID int64
if role == domain.RoleAdmin || role == domain.RoleBranchManager || role == domain.RoleSuperAdmin { switch role {
case domain.RoleAdmin, domain.RoleBranchManager, domain.RoleSuperAdmin:
if branchID == nil { if branchID == nil {
// h.logger.Error("CashoutReq Branch ID is required for this user role") // h.logger.Error("CashoutReq Branch ID is required for this user role")
return nil, nil, ErrBranchRequiredForRole return nil, nil, ErrBranchRequiredForRole
@ -166,14 +167,14 @@ func (s *Service) GetBranchByRole(ctx context.Context, branchID *int64, role dom
} }
return &branch.ID, &branch.CompanyID, nil return &branch.ID, &branch.CompanyID, nil
} else if role == domain.RoleCashier { case domain.RoleCashier:
branch, err := s.branchSvc.GetBranchByCashier(ctx, userID) branch, err := s.branchSvc.GetBranchByCashier(ctx, userID)
if err != nil { if err != nil {
// h.logger.Error("CashoutReq failed, branch id invalid") // h.logger.Error("CashoutReq failed, branch id invalid")
return nil, nil, ErrInvalidBranchID return nil, nil, ErrInvalidBranchID
} }
return &branch.ID, &branch.CompanyID, nil return &branch.ID, &branch.CompanyID, nil
} else { default:
return nil, nil, ErrCustomerRoleNotAuthorized return nil, nil, ErrCustomerRoleNotAuthorized
} }
} }

View File

@ -49,9 +49,9 @@ func (s *Service) CreateShopDeposit(ctx context.Context, userID int64, role doma
return domain.ShopDeposit{}, err return domain.ShopDeposit{}, err
} }
if err != nil { // if err != nil {
return domain.ShopDeposit{}, err // return domain.ShopDeposit{}, err
} // }
newTransaction, err := s.CreateShopTransaction(ctx, domain.CreateShopTransaction{ newTransaction, err := s.CreateShopTransaction(ctx, domain.CreateShopTransaction{
Amount: domain.Currency(req.Amount), Amount: domain.Currency(req.Amount),

View File

@ -3,6 +3,7 @@ package wallet
import ( import (
"log/slog" "log/slog"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/kafka"
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication" notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
) )
@ -13,10 +14,10 @@ type Service struct {
notificationStore notificationservice.NotificationStore notificationStore notificationservice.NotificationStore
notificationSvc *notificationservice.Service notificationSvc *notificationservice.Service
logger *slog.Logger logger *slog.Logger
// userStore user.UserStore kafkaProducer *kafka.Producer
} }
func NewService(walletStore WalletStore, transferStore TransferStore, notificationStore notificationservice.NotificationStore, notificationSvc *notificationservice.Service, logger *slog.Logger) *Service { func NewService(walletStore WalletStore, transferStore TransferStore, notificationStore notificationservice.NotificationStore, notificationSvc *notificationservice.Service, logger *slog.Logger, kafkaProducer *kafka.Producer) *Service {
return &Service{ return &Service{
walletStore: walletStore, walletStore: walletStore,
transferStore: transferStore, transferStore: transferStore,
@ -24,7 +25,6 @@ func NewService(walletStore WalletStore, transferStore TransferStore, notificati
notificationStore: notificationStore, notificationStore: notificationStore,
notificationSvc: notificationSvc, notificationSvc: notificationSvc,
logger: logger, logger: logger,
// userStore: userStore, kafkaProducer: kafkaProducer,
// userStore users
} }
} }

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/SamuelTariku/FortuneBet-Backend/internal/event"
) )
var ( var (
@ -76,12 +77,21 @@ func (s *Service) UpdateBalance(ctx context.Context, id int64, balance domain.Cu
return err return err
} }
wallet, err := s.GetWalletByID(ctx, id) wallet, err := s.walletStore.GetWalletByID(ctx, id)
if err != nil { if err != nil {
return err return err
} }
go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) go func() {
s.kafkaProducer.Publish(ctx, fmt.Sprint(wallet.ID), event.WalletEvent{
EventType: event.WalletBalanceUpdated,
WalletID: wallet.ID,
UserID: wallet.UserID,
Balance: balance,
Trigger: "UpdateBalance",
})
}()
return nil return nil
} }
@ -97,7 +107,15 @@ func (s *Service) AddToWallet(
return domain.Transfer{}, err return domain.Transfer{}, err
} }
// go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) go func() {
s.kafkaProducer.Publish(ctx, fmt.Sprint(wallet.ID), event.WalletEvent{
EventType: event.WalletBalanceUpdated,
WalletID: wallet.ID,
UserID: wallet.UserID,
Balance: wallet.Balance + amount,
Trigger: "AddToWallet",
})
}()
// Log the transfer here for reference // Log the transfer here for reference
newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{
@ -137,7 +155,15 @@ func (s *Service) DeductFromWallet(ctx context.Context, id int64, amount domain.
return domain.Transfer{}, nil return domain.Transfer{}, nil
} }
// go s.notificationSvc.UpdateLiveWalletMetricForWallet(ctx, wallet) go func() {
s.kafkaProducer.Publish(ctx, fmt.Sprint(wallet.ID), event.WalletEvent{
EventType: event.WalletBalanceUpdated,
WalletID: wallet.ID,
UserID: wallet.UserID,
Balance: wallet.Balance - amount,
Trigger: "DeductFromWallet",
})
}()
// Log the transfer here for reference // Log the transfer here for reference
newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{ newTransfer, err := s.transferStore.CreateTransfer(ctx, domain.CreateTransfer{

View File

@ -5,6 +5,7 @@ import (
"log/slog" "log/slog"
"github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/config"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/arifpay"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bonus" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bonus"
@ -40,6 +41,7 @@ import (
) )
type App struct { type App struct {
arifpaySvc *arifpay.ArifpayService
issueReportingSvc *issuereporting.Service issueReportingSvc *issuereporting.Service
instSvc *institutions.Service instSvc *institutions.Service
currSvc *currency.Service currSvc *currency.Service
@ -76,6 +78,7 @@ type App struct {
} }
func NewApp( func NewApp(
arifpaySvc *arifpay.ArifpayService,
issueReportingSvc *issuereporting.Service, issueReportingSvc *issuereporting.Service,
instSvc *institutions.Service, instSvc *institutions.Service,
currSvc *currency.Service, currSvc *currency.Service,
@ -122,6 +125,7 @@ func NewApp(
})) }))
s := &App{ s := &App{
arifpaySvc: arifpaySvc,
issueReportingSvc: issueReportingSvc, issueReportingSvc: issueReportingSvc,
instSvc: instSvc, instSvc: instSvc,
currSvc: currSvc, currSvc: currSvc,

View File

@ -114,10 +114,10 @@ func SetupReportCronJobs(ctx context.Context, reportService *report.Service) {
spec string spec string
period string period string
}{ }{
{ // {
spec: "*/300 * * * * *", // Every 5 minutes (300 seconds) // spec: "*/300 * * * * *", // Every 5 minutes (300 seconds)
period: "5min", // period: "5min",
}, // },
{ {
spec: "0 0 0 * * *", // Daily at midnight spec: "0 0 0 * * *", // Daily at midnight
period: "daily", period: "daily",

View File

@ -0,0 +1,179 @@
package handlers
import (
"encoding/json"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/gofiber/fiber/v2"
)
// CreateCheckoutSessionHandler initializes a checkout session with Arifpay.
//
// @Summary Create Arifpay Checkout Session
// @Description Creates a payment session using Arifpay and returns a redirect URL.
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param request body domain.CreateCheckoutSessionRequest true "Checkout session request payload"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/checkout [post]
func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
var req domain.CreateCheckoutSessionRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to process your request",
})
}
paymentURL, err := h.arifpaySvc.CreateCheckoutSession(req)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to process your request",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Checkout session created successfully",
Data: paymentURL,
Success: true,
StatusCode: fiber.StatusOK,
})
}
// B2CTransferHandler handles Arifpay B2C transfers based on the transfer_mode.
//
// @Summary Initiate B2C Transfer
// @Description Initiates a B2C transfer via Telebirr, CBE, or MPESA through Arifpay
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param transfer_mode query string true "Transfer mode (Telebirr, CBE, MPESA)"
// @Param request body domain.ArifPayB2CRequest true "Transfer request payload"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 502 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/b2c/transfer [post]
func (h *Handler) B2CTransferHandler(c *fiber.Ctx) error {
transferMode := c.Query("transfer_mode")
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{
Error: "invalid transfer_mode. Allowed values: Telebirr, CBE, MPESA",
Message: "Failed to process your request",
})
}
var req domain.ArifPayB2CRequest
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 {
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to process your request",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Transfer initiated successfully",
Data: resp,
Success: true,
StatusCode: fiber.StatusOK,
})
}
// ArifpayVerifyByTransactionIDHandler godoc
// @Summary Verify Arifpay Transaction
// @Description Verifies a transaction using transaction ID and payment type
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param request body domain.ArifpayVerifyByTransactionIDRequest true "Transaction verification payload"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 502 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/transaction-id/verify-transaction [post]
func (h *Handler) ArifpayVerifyByTransactionIDHandler(c *fiber.Ctx) error {
var req domain.ArifpayVerifyByTransactionIDRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to parse request body",
})
}
if req.TransactionID == "" || req.PaymentType == 0 {
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 {
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to verify transaction",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Transaction verified successfully",
Data: json.RawMessage(resp),
Success: true,
StatusCode: fiber.StatusOK,
})
}
// ArifpayVerifyBySessionIDHandler godoc
// @Summary Verify Arifpay Transaction by Session ID
// @Description Verifies an Arifpay transaction using a session ID
// @Tags Arifpay
// @Accept json
// @Produce json
// @Param session_id query string true "Arifpay Session ID"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 502 {object} domain.ErrorResponse
// @Router /api/v1/arifpay/session-id/verify-transaction/{session_id} [get]
func (h *Handler) ArifpayVerifyBySessionIDHandler(c *fiber.Ctx) error {
sessionID := c.Query("session_id")
if sessionID == "" {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Error: "missing session_id",
Message: "session_id query parameter is required",
})
}
resp, err := h.arifpaySvc.VerifyBySessionID(sessionID)
if err != nil {
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
Error: err.Error(),
Message: "Failed to verify session",
})
}
return c.Status(fiber.StatusOK).JSON(domain.Response{
Message: "Session verified successfully",
Data: json.RawMessage(resp),
Success: true,
StatusCode: fiber.StatusOK,
})
}

View File

@ -4,6 +4,7 @@ import (
"log/slog" "log/slog"
"github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/config"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/arifpay"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bet"
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/bonus" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/bonus"
@ -35,6 +36,7 @@ import (
) )
type Handler struct { type Handler struct {
arifpaySvc *arifpay.ArifpayService
issueReportingSvc *issuereporting.Service issueReportingSvc *issuereporting.Service
instSvc *institutions.Service instSvc *institutions.Service
currSvc *currency.Service currSvc *currency.Service
@ -68,6 +70,7 @@ type Handler struct {
} }
func New( func New(
arifpaySvc *arifpay.ArifpayService,
issueReportingSvc *issuereporting.Service, issueReportingSvc *issuereporting.Service,
instSvc *institutions.Service, instSvc *institutions.Service,
currSvc *currency.Service, currSvc *currency.Service,
@ -100,6 +103,7 @@ func New(
mongoLoggerSvc *zap.Logger, mongoLoggerSvc *zap.Logger,
) *Handler { ) *Handler {
return &Handler{ return &Handler{
arifpaySvc: arifpaySvc,
issueReportingSvc: issueReportingSvc, issueReportingSvc: issueReportingSvc,
instSvc: instSvc, instSvc: instSvc,
currSvc: currSvc, currSvc: currSvc,

View File

@ -20,6 +20,7 @@ import (
func (a *App) initAppRoutes() { func (a *App) initAppRoutes() {
h := handlers.New( h := handlers.New(
a.arifpaySvc,
a.issueReportingSvc, a.issueReportingSvc,
a.instSvc, a.instSvc,
a.currSvc, a.currSvc,
@ -58,6 +59,7 @@ func (a *App) initAppRoutes() {
"version": "1.0dev10", "version": "1.0dev10",
}) })
}) })
// Swagger // Swagger
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler()) a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
@ -68,6 +70,7 @@ func (a *App) initAppRoutes() {
"version": "1.0dev10", "version": "1.0dev10",
}) })
}) })
// Auth Routes // Auth Routes
groupV1.Post("/auth/login", h.LoginCustomer) groupV1.Post("/auth/login", h.LoginCustomer)
groupV1.Post("/auth/refresh", h.RefreshToken) groupV1.Post("/auth/refresh", h.RefreshToken)
@ -98,6 +101,12 @@ func (a *App) initAppRoutes() {
return c.SendString("Test endpoint") return c.SendString("Test endpoint")
}) })
//Arifpay
groupV1.Post("/arifpay/checkout", a.authMiddleware, h.CreateCheckoutSessionHandler)
groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.B2CTransferHandler)
groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler)
groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
// User Routes // User Routes
groupV1.Post("/user/resetPassword", h.ResetPassword) groupV1.Post("/user/resetPassword", h.ResetPassword)
groupV1.Post("/user/sendResetCode", h.SendResetCode) groupV1.Post("/user/sendResetCode", h.SendResetCode)
@ -225,11 +234,11 @@ func (a *App) initAppRoutes() {
groupV1.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet) groupV1.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
//Chapa Routes //Chapa Routes
a.fiber.Post("/chapa/payments/webhook/verify", h.WebhookCallback) groupV1.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
a.fiber.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyTransaction) groupV1.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyTransaction)
a.fiber.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit) groupV1.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
a.fiber.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal) groupV1.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
a.fiber.Get("/chapa/banks", h.GetSupportedBanks) groupV1.Get("/chapa/banks", h.GetSupportedBanks)
// Currencies // Currencies
groupV1.Get("/currencies", h.GetSupportedCurrencies) groupV1.Get("/currencies", h.GetSupportedCurrencies)
@ -240,26 +249,6 @@ func (a *App) initAppRoutes() {
groupV1.Get("/report-files/download/:filename", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportFile) groupV1.Get("/report-files/download/:filename", a.authMiddleware, a.OnlyAdminAndAbove, h.DownloadReportFile)
groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles) groupV1.Get("/report-files/list", a.authMiddleware, a.OnlyAdminAndAbove, h.ListReportFiles)
//Wallet Monitor Service
// group.Get("/debug/wallet-monitor/status", func(c *fiber.Ctx) error {
// return c.JSON(fiber.Map{
// "running": monitor.IsRunning(),
// "last_check": walletMonitorSvc.LastCheckTime(),
// })
// })
// group.Post("/debug/wallet-monitor/trigger", func(c *fiber.Ctx) error {
// walletMonitorSvc.ForceCheck()
// return c.SendStatus(fiber.StatusOK)
// })
// group.Post("/chapa/payments/initialize", h.InitializePayment)
// group.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction)
// group.Post("/chapa/payments/callback", h.ReceiveWebhook)
// group.Get("/chapa/banks", h.GetBanks)
// group.Post("/chapa/transfers", h.CreateTransfer)
// group.Get("/chapa/transfers/verify/:transfer_ref", h.VerifyTransfer)
//Alea Play Virtual Game Routes //Alea Play Virtual Game Routes
groupV1.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame) groupV1.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)
groupV1.Post("/webhooks/alea-play", a.authMiddleware, h.HandleAleaCallback) groupV1.Post("/webhooks/alea-play", a.authMiddleware, h.HandleAleaCallback)
@ -270,14 +259,10 @@ func (a *App) initAppRoutes() {
groupV1.Post("/veli/start-game", a.authMiddleware, h.StartGame) groupV1.Post("/veli/start-game", a.authMiddleware, h.StartGame)
groupV1.Post("/veli/start-demo-game", a.authMiddleware, h.StartDemoGame) groupV1.Post("/veli/start-demo-game", a.authMiddleware, h.StartDemoGame)
a.fiber.Post("/balance", h.GetBalance) a.fiber.Post("/balance", h.GetBalance)
// a.fiber.Post("/bet", h.PlaceBet)
// a.fiber.Post("/win", h.RegisterWin)
// a.fiber.Post("/cancel", h.CancelTransaction)
groupV1.Post("/veli/gaming-activity", h.GetGamingActivity) groupV1.Post("/veli/gaming-activity", h.GetGamingActivity)
//mongoDB logs //mongoDB logs
ctx := context.Background() groupV1.Get("/logs", a.authMiddleware, a.SuperAdminOnly, handlers.GetLogsHandler(context.Background()))
groupV1.Get("/logs", a.authMiddleware, a.SuperAdminOnly, handlers.GetLogsHandler(ctx))
// Recommendation Routes // Recommendation Routes
// group.Get("/virtual-games/recommendations/:userID", h.GetRecommendations) // group.Get("/virtual-games/recommendations/:userID", h.GetRecommendations)

View File

@ -2,9 +2,14 @@ package ws
import ( import (
"net/http" "net/http"
"strconv"
"sync" "sync"
"github.com/SamuelTariku/FortuneBet-Backend/internal/event"
"github.com/gofiber/fiber/v2"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttpadaptor"
) )
type Client struct { type Client struct {
@ -36,7 +41,6 @@ func (h *NotificationHub) Run() {
h.mu.Lock() h.mu.Lock()
h.Clients[client] = true h.Clients[client] = true
h.mu.Unlock() h.mu.Unlock()
// log.Printf("Client registered: %d", client.RecipientID)
case client := <-h.Unregister: case client := <-h.Unregister:
h.mu.Lock() h.mu.Lock()
if _, ok := h.Clients[client]; ok { if _, ok := h.Clients[client]; ok {
@ -44,7 +48,6 @@ func (h *NotificationHub) Run() {
client.Conn.Close() client.Conn.Close()
} }
h.mu.Unlock() h.mu.Unlock()
// log.Printf("Client unregistered: %d", client.RecipientID)
case message := <-h.Broadcast: case message := <-h.Broadcast:
h.mu.Lock() h.mu.Lock()
for client := range h.Clients { for client := range h.Clients {
@ -70,3 +73,102 @@ var Upgrader = websocket.Upgrader{
return true return true
}, },
} }
type responseWriterWrapper struct {
ctx *fasthttp.RequestCtx
header http.Header
wrote bool
}
func newResponseWriterWrapper(ctx *fasthttp.RequestCtx) *responseWriterWrapper {
return &responseWriterWrapper{
ctx: ctx,
header: make(http.Header),
}
}
func (rw *responseWriterWrapper) Header() http.Header {
return rw.header
}
func (rw *responseWriterWrapper) Write(b []byte) (int, error) {
rw.writeHeaderIfNeeded(http.StatusOK)
return rw.ctx.Write(b)
}
func (rw *responseWriterWrapper) WriteHeader(statusCode int) {
if rw.wrote {
return
}
rw.writeHeaderIfNeeded(statusCode)
}
func (rw *responseWriterWrapper) writeHeaderIfNeeded(statusCode int) {
if rw.wrote {
return
}
rw.wrote = true
// Copy headers from rw.header to fasthttp.Response.Header
for k, vv := range rw.header {
for _, v := range vv {
rw.ctx.Response.Header.Add(k, v)
}
}
rw.ctx.SetStatusCode(statusCode)
}
// ✅ WebSocketHandler integrates Gorilla WebSocket with Fiber
func (h *NotificationHub) WebSocketHandler() fiber.Handler {
return func(c *fiber.Ctx) error {
userIDStr := c.Params("user_id")
userID, err := strconv.ParseInt(userIDStr, 10, 64)
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString("Invalid user ID")
}
// Use your custom responseWriterWrapper here
rw := newResponseWriterWrapper(c.Context())
stdReq := new(http.Request)
if err := fasthttpadaptor.ConvertRequest(c.Context(), stdReq, true); err != nil {
return err
}
conn, err := Upgrader.Upgrade(rw, stdReq, nil)
if err != nil {
return err
}
client := &Client{
Conn: conn,
RecipientID: userID,
}
h.Register <- client
defer func() {
h.Unregister <- client
}()
for {
if _, _, err := conn.ReadMessage(); err != nil {
break
}
}
return nil
}
}
func (h *NotificationHub) BroadcastWalletUpdate(userID int64, event event.WalletEvent) {
payload := map[string]interface{}{
"type": event.EventType,
"wallet_id": event.WalletID,
"user_id": event.UserID,
"balance": event.Balance,
"trigger": event.Trigger,
"recipient_id": userID,
}
h.Broadcast <- payload
}