Merge branch 'main' into ticket-bet
This commit is contained in:
commit
cfced1951c
7
.env
7
.env
|
|
@ -27,7 +27,14 @@ POPOK_BASE_URL=https://st.pokgaming.com/ #Staging
|
|||
|
||||
POPOK_CALLBACK_URL=1
|
||||
|
||||
#Muli-currency Support
|
||||
FIXER_API_KEY=3b0f1eb30d-63c875026d-sxy9pl
|
||||
BASE_CURRENCY=ETB
|
||||
FIXER_BASE_URL=https://api.apilayer.com/fixer
|
||||
|
||||
# Chapa API Configuration
|
||||
CHAPA_TRANSFER_TYPE="Payout"
|
||||
CHAPA_PAYMENT_TYPE="API"
|
||||
CHAPA_BASE_URL="https://api.chapa.co/v1"
|
||||
CHAPA_ENCRYPTION_KEY=zLdYrjnBCknMvFikmP5jBfen
|
||||
CHAPA_PUBLIC_KEY=CHAPUBK_TEST-HJR0qhQRPLTkauNy9Q8UrmskPTOR31aC
|
||||
|
|
|
|||
24
cmd/main.go
24
cmd/main.go
|
|
@ -4,6 +4,7 @@ import (
|
|||
// "context"
|
||||
|
||||
// "context"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
|
|
@ -33,6 +34,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/currency"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
|
|
@ -134,13 +136,18 @@ func main() {
|
|||
|
||||
chapaSvc := chapa.NewService(
|
||||
wallet.TransferStore(store),
|
||||
wallet.WalletStore(store),
|
||||
*walletSvc,
|
||||
user.UserStore(store),
|
||||
chapaClient,
|
||||
)
|
||||
|
||||
// Initialize reporting components
|
||||
reportRepo := repository.NewReportRepo(store)
|
||||
currRepo := repository.NewCurrencyPostgresRepository(store)
|
||||
|
||||
fixerFertcherSvc := currency.NewFixerFetcher(
|
||||
cfg.FIXER_API_KEY,
|
||||
cfg.FIXER_BASE_URL,
|
||||
)
|
||||
|
||||
reportSvc := report.NewService(
|
||||
bet.BetStore(store),
|
||||
|
|
@ -175,7 +182,17 @@ func main() {
|
|||
logger,
|
||||
5*time.Minute,
|
||||
)
|
||||
walletMonitorSvc.Start()
|
||||
|
||||
currSvc := currency.NewService(
|
||||
currRepo,
|
||||
cfg.BASE_CURRENCY,
|
||||
fixerFertcherSvc,
|
||||
)
|
||||
|
||||
exchangeWorker := currency.NewExchangeRateWorker(fixerFertcherSvc, logger, cfg)
|
||||
exchangeWorker.Start(context.Background())
|
||||
defer exchangeWorker.Stop()
|
||||
go walletMonitorSvc.Start()
|
||||
|
||||
httpserver.StartDataFetchingCrons(eventSvc, *oddsSvc, resultSvc)
|
||||
httpserver.StartTicketCrons(*ticketSvc)
|
||||
|
|
@ -183,6 +200,7 @@ func main() {
|
|||
|
||||
// Initialize and start HTTP server
|
||||
app := httpserver.NewApp(
|
||||
currSvc,
|
||||
cfg.Port,
|
||||
v,
|
||||
authSvc,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,15 @@ CREATE TABLE IF NOT EXISTS tickets (
|
|||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE exchange_rates (
|
||||
id SERIAL PRIMARY KEY,
|
||||
from_currency VARCHAR(3) NOT NULL,
|
||||
to_currency VARCHAR(3) NOT NULL,
|
||||
rate DECIMAL(19, 6) NOT NULL,
|
||||
valid_until TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (from_currency, to_currency)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS bet_outcomes (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
bet_id BIGINT NOT NULL,
|
||||
|
|
@ -123,14 +132,15 @@ CREATE TABLE IF NOT EXISTS customer_wallets (
|
|||
);
|
||||
CREATE TABLE IF NOT EXISTS wallet_transfer (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
amount BIGINT NOT NULL,
|
||||
type VARCHAR(255) NOT NULL,
|
||||
amount BIGINT,
|
||||
type VARCHAR(255),
|
||||
receiver_wallet_id BIGINT,
|
||||
sender_wallet_id BIGINT,
|
||||
cashier_id BIGINT,
|
||||
verified BOOLEAN NOT NULL DEFAULT false,
|
||||
reference_number VARCHAR(255) NOT NULL,
|
||||
payment_method VARCHAR(255) NOT NULL,
|
||||
verified BOOLEAN DEFAULT false,
|
||||
reference_number VARCHAR(255),
|
||||
status VARCHAR(255),
|
||||
payment_method VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
|
@ -298,7 +308,8 @@ ALTER TABLE bets
|
|||
ADD CONSTRAINT fk_bets_users FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
ADD CONSTRAINT fk_bets_branches FOREIGN KEY (branch_id) REFERENCES branches(id);
|
||||
ALTER TABLE wallets
|
||||
ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id);
|
||||
ADD CONSTRAINT fk_wallets_users FOREIGN KEY (user_id) REFERENCES users(id);
|
||||
ADD COLUMN currency VARCHAR(3) NOT NULL DEFAULT 'ETB';
|
||||
ALTER TABLE customer_wallets
|
||||
ADD CONSTRAINT fk_customer_wallets_customers FOREIGN KEY (customer_id) REFERENCES users(id),
|
||||
ADD CONSTRAINT fk_customer_wallets_regular_wallet FOREIGN KEY (regular_wallet_id) REFERENCES wallets(id),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
DROP TABLE IF EXISTS vitrual_games;
|
||||
|
||||
DROP TABLE IF EXISTS virtual_game_transactions;
|
||||
|
||||
DROP TABLE IF EXISTS virtual_game_sessions;
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ INSERT INTO wallet_transfer (
|
|||
cashier_id,
|
||||
verified,
|
||||
reference_number,
|
||||
status,
|
||||
payment_method
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
RETURNING *;
|
||||
-- name: GetAllTransfers :many
|
||||
SELECT *
|
||||
|
|
@ -32,3 +33,9 @@ UPDATE wallet_transfer
|
|||
SET verified = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $2;
|
||||
|
||||
-- name: UpdateTransferStatus :exec
|
||||
UPDATE wallet_transfer
|
||||
SET status = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $2;
|
||||
1094
docs/docs.go
1094
docs/docs.go
File diff suppressed because it is too large
Load Diff
1094
docs/swagger.json
1094
docs/swagger.json
File diff suppressed because it is too large
Load Diff
|
|
@ -31,39 +31,6 @@ definitions:
|
|||
user_id:
|
||||
type: string
|
||||
type: object
|
||||
domain.Bank:
|
||||
properties:
|
||||
acct_length:
|
||||
type: integer
|
||||
active:
|
||||
type: integer
|
||||
country_id:
|
||||
type: integer
|
||||
created_at:
|
||||
type: string
|
||||
currency:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
is_24hrs:
|
||||
description: nullable
|
||||
type: integer
|
||||
is_active:
|
||||
type: integer
|
||||
is_mobilemoney:
|
||||
description: nullable
|
||||
type: integer
|
||||
is_rtgs:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
slug:
|
||||
type: string
|
||||
swift:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
domain.BetOutcome:
|
||||
properties:
|
||||
away_team_name:
|
||||
|
|
@ -193,6 +160,22 @@ definitions:
|
|||
tx_ref:
|
||||
type: string
|
||||
type: object
|
||||
domain.ChapaWithdrawalRequest:
|
||||
properties:
|
||||
account_name:
|
||||
type: string
|
||||
account_number:
|
||||
type: string
|
||||
amount:
|
||||
description: string because Chapa API uses string for monetary values
|
||||
type: string
|
||||
bank_code:
|
||||
type: integer
|
||||
currency:
|
||||
type: string
|
||||
reference:
|
||||
type: string
|
||||
type: object
|
||||
domain.CreateBetOutcomeReq:
|
||||
properties:
|
||||
event_id:
|
||||
|
|
@ -228,6 +211,81 @@ definitions:
|
|||
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||
example: 1
|
||||
type: object
|
||||
domain.DashboardSummary:
|
||||
properties:
|
||||
active_admins:
|
||||
type: integer
|
||||
active_bets:
|
||||
type: integer
|
||||
active_branches:
|
||||
type: integer
|
||||
active_cashiers:
|
||||
type: integer
|
||||
active_companies:
|
||||
type: integer
|
||||
active_customers:
|
||||
type: integer
|
||||
active_games:
|
||||
type: integer
|
||||
active_managers:
|
||||
type: integer
|
||||
average_stake:
|
||||
type: integer
|
||||
branches_count:
|
||||
type: integer
|
||||
customer_count:
|
||||
type: integer
|
||||
inactive_admins:
|
||||
type: integer
|
||||
inactive_branches:
|
||||
type: integer
|
||||
inactive_cashiers:
|
||||
type: integer
|
||||
inactive_companies:
|
||||
type: integer
|
||||
inactive_customers:
|
||||
type: integer
|
||||
inactive_games:
|
||||
type: integer
|
||||
inactive_managers:
|
||||
type: integer
|
||||
profit:
|
||||
type: integer
|
||||
read_notifications:
|
||||
type: integer
|
||||
total_admins:
|
||||
type: integer
|
||||
total_bets:
|
||||
type: integer
|
||||
total_cashiers:
|
||||
type: integer
|
||||
total_companies:
|
||||
type: integer
|
||||
total_deposits:
|
||||
type: integer
|
||||
total_games:
|
||||
type: integer
|
||||
total_losses:
|
||||
type: integer
|
||||
total_managers:
|
||||
type: integer
|
||||
total_notifications:
|
||||
type: integer
|
||||
total_stakes:
|
||||
type: integer
|
||||
total_wallets:
|
||||
type: integer
|
||||
total_wins:
|
||||
type: integer
|
||||
total_withdrawals:
|
||||
type: integer
|
||||
unread_notifications:
|
||||
type: integer
|
||||
win_balance:
|
||||
type: integer
|
||||
win_rate:
|
||||
type: number
|
||||
type: object
|
||||
domain.ErrorResponse:
|
||||
properties:
|
||||
error:
|
||||
|
|
@ -235,6 +293,57 @@ definitions:
|
|||
message:
|
||||
type: string
|
||||
type: object
|
||||
domain.EventStatus:
|
||||
enum:
|
||||
- upcoming
|
||||
- in_play
|
||||
- to_be_fixed
|
||||
- ended
|
||||
- postponed
|
||||
- cancelled
|
||||
- walkover
|
||||
- interrupted
|
||||
- abandoned
|
||||
- retired
|
||||
- suspended
|
||||
- decided_by_fa
|
||||
- removed
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- STATUS_PENDING
|
||||
- STATUS_IN_PLAY
|
||||
- STATUS_TO_BE_FIXED
|
||||
- STATUS_ENDED
|
||||
- STATUS_POSTPONED
|
||||
- STATUS_CANCELLED
|
||||
- STATUS_WALKOVER
|
||||
- STATUS_INTERRUPTED
|
||||
- STATUS_ABANDONED
|
||||
- STATUS_RETIRED
|
||||
- STATUS_SUSPENDED
|
||||
- STATUS_DECIDED_BY_FA
|
||||
- STATUS_REMOVED
|
||||
domain.League:
|
||||
properties:
|
||||
bet365_id:
|
||||
example: 1121
|
||||
type: integer
|
||||
cc:
|
||||
example: uk
|
||||
type: string
|
||||
id:
|
||||
example: 1
|
||||
type: integer
|
||||
is_active:
|
||||
example: false
|
||||
type: boolean
|
||||
name:
|
||||
example: BPL
|
||||
type: string
|
||||
sport_id:
|
||||
example: 1
|
||||
type: integer
|
||||
type: object
|
||||
domain.Odd:
|
||||
properties:
|
||||
category:
|
||||
|
|
@ -404,6 +513,16 @@ definitions:
|
|||
totalRewardEarned:
|
||||
type: number
|
||||
type: object
|
||||
domain.Response:
|
||||
properties:
|
||||
data: {}
|
||||
message:
|
||||
type: string
|
||||
status_code:
|
||||
type: integer
|
||||
success:
|
||||
type: boolean
|
||||
type: object
|
||||
domain.Role:
|
||||
enum:
|
||||
- super_admin
|
||||
|
|
@ -466,48 +585,52 @@ definitions:
|
|||
type: object
|
||||
domain.UpcomingEvent:
|
||||
properties:
|
||||
awayKitImage:
|
||||
away_kit_image:
|
||||
description: Kit or image for away team (optional)
|
||||
type: string
|
||||
awayTeam:
|
||||
away_team:
|
||||
description: Away team name (can be empty/null)
|
||||
type: string
|
||||
awayTeamID:
|
||||
away_team_id:
|
||||
description: Away team ID (can be empty/null)
|
||||
type: integer
|
||||
homeKitImage:
|
||||
home_kit_image:
|
||||
description: Kit or image for home team (optional)
|
||||
type: string
|
||||
homeTeam:
|
||||
home_team:
|
||||
description: Home team name (if available)
|
||||
type: string
|
||||
homeTeamID:
|
||||
home_team_id:
|
||||
description: Home team ID
|
||||
type: integer
|
||||
id:
|
||||
description: Event ID
|
||||
type: string
|
||||
leagueCC:
|
||||
league_cc:
|
||||
description: League country code
|
||||
type: string
|
||||
leagueID:
|
||||
league_id:
|
||||
description: League ID
|
||||
type: integer
|
||||
leagueName:
|
||||
league_name:
|
||||
description: League name
|
||||
type: string
|
||||
matchName:
|
||||
match_name:
|
||||
description: Match or event name
|
||||
type: string
|
||||
source:
|
||||
description: bet api provider (bet365, betfair)
|
||||
type: string
|
||||
sportID:
|
||||
sport_id:
|
||||
description: Sport ID
|
||||
type: integer
|
||||
startTime:
|
||||
start_time:
|
||||
description: Converted from "time" field in UNIX format
|
||||
type: string
|
||||
status:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.EventStatus'
|
||||
description: Match Status for event
|
||||
type: object
|
||||
domain.VeliCallback:
|
||||
properties:
|
||||
|
|
@ -550,6 +673,8 @@ definitions:
|
|||
type: string
|
||||
id:
|
||||
type: integer
|
||||
is_active:
|
||||
type: boolean
|
||||
is_featured:
|
||||
type: boolean
|
||||
max_bet:
|
||||
|
|
@ -602,6 +727,9 @@ definitions:
|
|||
type: object
|
||||
handlers.BranchDetailRes:
|
||||
properties:
|
||||
balance:
|
||||
example: 100.5
|
||||
type: number
|
||||
branch_manager_id:
|
||||
example: 1
|
||||
type: integer
|
||||
|
|
@ -928,6 +1056,12 @@ definitions:
|
|||
properties:
|
||||
branch_id:
|
||||
type: integer
|
||||
branch_location:
|
||||
type: string
|
||||
branch_name:
|
||||
type: string
|
||||
branch_wallet:
|
||||
type: integer
|
||||
created_at:
|
||||
type: string
|
||||
email:
|
||||
|
|
@ -1045,6 +1179,13 @@ definitions:
|
|||
- otp
|
||||
- password
|
||||
type: object
|
||||
handlers.ResultRes:
|
||||
properties:
|
||||
outcomes:
|
||||
items:
|
||||
$ref: '#/definitions/domain.BetOutcome'
|
||||
type: array
|
||||
type: object
|
||||
handlers.SearchUserByNameOrPhoneReq:
|
||||
properties:
|
||||
query:
|
||||
|
|
@ -1296,9 +1437,9 @@ definitions:
|
|||
type: string
|
||||
mode:
|
||||
enum:
|
||||
- REAL
|
||||
- DEMO
|
||||
example: REAL
|
||||
- fun
|
||||
- real
|
||||
example: real
|
||||
type: string
|
||||
required:
|
||||
- currency
|
||||
|
|
@ -1395,39 +1536,6 @@ definitions:
|
|||
example: false
|
||||
type: boolean
|
||||
type: object
|
||||
report.DashboardSummary:
|
||||
properties:
|
||||
active_bets:
|
||||
type: integer
|
||||
active_branches:
|
||||
type: integer
|
||||
active_customers:
|
||||
type: integer
|
||||
average_stake:
|
||||
type: integer
|
||||
branches_count:
|
||||
type: integer
|
||||
customer_count:
|
||||
type: integer
|
||||
profit:
|
||||
type: integer
|
||||
total_bets:
|
||||
type: integer
|
||||
total_deposits:
|
||||
type: integer
|
||||
total_losses:
|
||||
type: integer
|
||||
total_stakes:
|
||||
type: integer
|
||||
total_wins:
|
||||
type: integer
|
||||
total_withdrawals:
|
||||
type: integer
|
||||
win_balance:
|
||||
type: integer
|
||||
win_rate:
|
||||
type: number
|
||||
type: object
|
||||
response.APIResponse:
|
||||
properties:
|
||||
data: {}
|
||||
|
|
@ -1646,6 +1754,25 @@ paths:
|
|||
summary: Launch an Alea Play virtual game
|
||||
tags:
|
||||
- Alea Virtual Games
|
||||
/api/v1/chapa/banks:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get list of banks supported by Chapa
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.Response'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Get supported banks
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/payments/deposit:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -1673,6 +1800,8 @@ paths:
|
|||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Initiate a deposit
|
||||
tags:
|
||||
- Chapa
|
||||
|
|
@ -1736,6 +1865,105 @@ paths:
|
|||
summary: Chapa payment webhook callback (used by Chapa)
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/payments/withdraw:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Initiates a withdrawal request to transfer funds to a bank account
|
||||
via Chapa
|
||||
parameters:
|
||||
- description: Withdrawal request details
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ChapaWithdrawalRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Chapa withdrawal process initiated successfully
|
||||
schema:
|
||||
$ref: '#/definitions/domain.Response'
|
||||
"400":
|
||||
description: Invalid request body
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"422":
|
||||
description: Unprocessable entity
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Initiate a withdrawal
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/currencies:
|
||||
get:
|
||||
description: Returns list of supported currencies
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
type: object
|
||||
summary: Get supported currencies
|
||||
tags:
|
||||
- Multi-Currency
|
||||
/api/v1/currencies/convert:
|
||||
get:
|
||||
description: Converts amount from one currency to another
|
||||
parameters:
|
||||
- description: Source currency code (e.g., USD)
|
||||
in: query
|
||||
name: from
|
||||
required: true
|
||||
type: string
|
||||
- description: Target currency code (e.g., ETB)
|
||||
in: query
|
||||
name: to
|
||||
required: true
|
||||
type: string
|
||||
- description: Amount to convert
|
||||
in: query
|
||||
name: amount
|
||||
required: true
|
||||
type: number
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
type: number
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Convert currency
|
||||
tags:
|
||||
- Multi-Currency
|
||||
/api/v1/reports/dashboard:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -1776,7 +2004,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/report.DashboardSummary'
|
||||
$ref: '#/definitions/domain.DashboardSummary'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -1997,27 +2225,6 @@ paths:
|
|||
summary: Refresh token
|
||||
tags:
|
||||
- auth
|
||||
/banks:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get list of banks supported by Chapa
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.Bank'
|
||||
type: array
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Get supported banks
|
||||
tags:
|
||||
- Chapa
|
||||
/bet:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -2458,6 +2665,35 @@ paths:
|
|||
summary: Delete the branch operation
|
||||
tags:
|
||||
- branch
|
||||
/branchCashier:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Gets branch for cahier
|
||||
parameters:
|
||||
- description: Branch ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.BranchDetailRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Gets branch for cahier
|
||||
tags:
|
||||
- branch
|
||||
/branchWallet:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -2516,6 +2752,39 @@ paths:
|
|||
summary: Get cashier by id
|
||||
tags:
|
||||
- cashier
|
||||
/cashierWallet:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get wallet for cashier
|
||||
parameters:
|
||||
- description: User ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.UserProfileRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Get wallet for cashier
|
||||
tags:
|
||||
- cashier
|
||||
/cashiers:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -2800,6 +3069,138 @@ paths:
|
|||
summary: Gets branches by company id
|
||||
tags:
|
||||
- branch
|
||||
/events:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieve all upcoming events from the database
|
||||
parameters:
|
||||
- description: Page number
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Page size
|
||||
in: query
|
||||
name: page_size
|
||||
type: integer
|
||||
- description: League ID Filter
|
||||
in: query
|
||||
name: league_id
|
||||
type: string
|
||||
- description: Sport ID Filter
|
||||
in: query
|
||||
name: sport_id
|
||||
type: string
|
||||
- description: Country Code Filter
|
||||
in: query
|
||||
name: cc
|
||||
type: string
|
||||
- description: Start Time
|
||||
in: query
|
||||
name: first_start_time
|
||||
type: string
|
||||
- description: End Time
|
||||
in: query
|
||||
name: last_start_time
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.UpcomingEvent'
|
||||
type: array
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Retrieve all upcoming events
|
||||
tags:
|
||||
- prematch
|
||||
/events/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Set the event status to removed
|
||||
parameters:
|
||||
- description: Event ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Set the event status to removed
|
||||
tags:
|
||||
- event
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieve an upcoming event by ID
|
||||
parameters:
|
||||
- description: ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.UpcomingEvent'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Retrieve an upcoming by ID
|
||||
tags:
|
||||
- prematch
|
||||
/leagues:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Gets all leagues
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.League'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Gets all leagues
|
||||
tags:
|
||||
- leagues
|
||||
/manager/{id}/branch:
|
||||
get:
|
||||
consumes:
|
||||
|
|
@ -2966,112 +3367,7 @@ paths:
|
|||
summary: Update Managers
|
||||
tags:
|
||||
- manager
|
||||
/operation:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Creates a operation
|
||||
parameters:
|
||||
- description: Creates operation
|
||||
in: body
|
||||
name: createBranchOperation
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.CreateBranchOperationReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.BranchOperationRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Create a operation
|
||||
tags:
|
||||
- branch
|
||||
/prematch/events:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieve all upcoming events from the database
|
||||
parameters:
|
||||
- description: Page number
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Page size
|
||||
in: query
|
||||
name: page_size
|
||||
type: integer
|
||||
- description: League ID Filter
|
||||
in: query
|
||||
name: league_id
|
||||
type: string
|
||||
- description: Sport ID Filter
|
||||
in: query
|
||||
name: sport_id
|
||||
type: string
|
||||
- description: Start Time
|
||||
in: query
|
||||
name: first_start_time
|
||||
type: string
|
||||
- description: End Time
|
||||
in: query
|
||||
name: last_start_time
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.UpcomingEvent'
|
||||
type: array
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Retrieve all upcoming events
|
||||
tags:
|
||||
- prematch
|
||||
/prematch/events/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieve an upcoming event by ID
|
||||
parameters:
|
||||
- description: ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.UpcomingEvent'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Retrieve an upcoming by ID
|
||||
tags:
|
||||
- prematch
|
||||
/prematch/odds:
|
||||
/odds:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
|
|
@ -3092,38 +3388,7 @@ paths:
|
|||
summary: Retrieve all prematch odds
|
||||
tags:
|
||||
- prematch
|
||||
/prematch/odds/{event_id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieve prematch odds for a specific event by event ID
|
||||
parameters:
|
||||
- description: Event ID
|
||||
in: path
|
||||
name: event_id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.Odd'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Retrieve prematch odds for an event
|
||||
tags:
|
||||
- prematch
|
||||
/prematch/odds/upcoming/{upcoming_id}:
|
||||
/odds/upcoming/{upcoming_id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
|
|
@ -3163,7 +3428,7 @@ paths:
|
|||
summary: Retrieve prematch odds by upcoming ID (FI)
|
||||
tags:
|
||||
- prematch
|
||||
/prematch/odds/upcoming/{upcoming_id}/market/{market_id}:
|
||||
/odds/upcoming/{upcoming_id}/market/{market_id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
|
|
@ -3199,6 +3464,36 @@ paths:
|
|||
summary: Retrieve raw odds by Market ID
|
||||
tags:
|
||||
- prematch
|
||||
/operation:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Creates a operation
|
||||
parameters:
|
||||
- description: Creates operation
|
||||
in: body
|
||||
name: createBranchOperation
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.CreateBranchOperationReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handlers.BranchOperationRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Create a operation
|
||||
tags:
|
||||
- branch
|
||||
/random/bet:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -3336,7 +3631,7 @@ paths:
|
|||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/domain.BetOutcome'
|
||||
$ref: '#/definitions/handlers.ResultRes'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: auth.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: bet.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: branch.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: cashier.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: company.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: copyfrom.go
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
|
||||
package dbgen
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: events.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: leagues.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
|
||||
package dbgen
|
||||
|
||||
|
|
@ -482,14 +482,15 @@ type WalletThresholdNotification struct {
|
|||
|
||||
type WalletTransfer struct {
|
||||
ID int64 `json:"id"`
|
||||
Amount int64 `json:"amount"`
|
||||
Type string `json:"type"`
|
||||
Amount pgtype.Int8 `json:"amount"`
|
||||
Type pgtype.Text `json:"type"`
|
||||
ReceiverWalletID pgtype.Int8 `json:"receiver_wallet_id"`
|
||||
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||
Verified bool `json:"verified"`
|
||||
ReferenceNumber string `json:"reference_number"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
Verified pgtype.Bool `json:"verified"`
|
||||
ReferenceNumber pgtype.Text `json:"reference_number"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamp `json:"updated_at"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: monitor.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: notification.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: odds.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: otp.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: referal.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: result.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: ticket.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: transactions.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: transfer.sql
|
||||
|
||||
package dbgen
|
||||
|
|
@ -20,21 +20,23 @@ INSERT INTO wallet_transfer (
|
|||
cashier_id,
|
||||
verified,
|
||||
reference_number,
|
||||
status,
|
||||
payment_method
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, payment_method, created_at, updated_at
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
RETURNING id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateTransferParams struct {
|
||||
Amount int64 `json:"amount"`
|
||||
Type string `json:"type"`
|
||||
Amount pgtype.Int8 `json:"amount"`
|
||||
Type pgtype.Text `json:"type"`
|
||||
ReceiverWalletID pgtype.Int8 `json:"receiver_wallet_id"`
|
||||
SenderWalletID pgtype.Int8 `json:"sender_wallet_id"`
|
||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||
Verified bool `json:"verified"`
|
||||
ReferenceNumber string `json:"reference_number"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
Verified pgtype.Bool `json:"verified"`
|
||||
ReferenceNumber pgtype.Text `json:"reference_number"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams) (WalletTransfer, error) {
|
||||
|
|
@ -46,6 +48,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
|||
arg.CashierID,
|
||||
arg.Verified,
|
||||
arg.ReferenceNumber,
|
||||
arg.Status,
|
||||
arg.PaymentMethod,
|
||||
)
|
||||
var i WalletTransfer
|
||||
|
|
@ -58,6 +61,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
|||
&i.CashierID,
|
||||
&i.Verified,
|
||||
&i.ReferenceNumber,
|
||||
&i.Status,
|
||||
&i.PaymentMethod,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
|
|
@ -66,7 +70,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
|||
}
|
||||
|
||||
const GetAllTransfers = `-- name: GetAllTransfers :many
|
||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, payment_method, created_at, updated_at
|
||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at
|
||||
FROM wallet_transfer
|
||||
`
|
||||
|
||||
|
|
@ -88,6 +92,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransfer, error)
|
|||
&i.CashierID,
|
||||
&i.Verified,
|
||||
&i.ReferenceNumber,
|
||||
&i.Status,
|
||||
&i.PaymentMethod,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
|
|
@ -103,7 +108,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransfer, error)
|
|||
}
|
||||
|
||||
const GetTransferByID = `-- name: GetTransferByID :one
|
||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, payment_method, created_at, updated_at
|
||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at
|
||||
FROM wallet_transfer
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -120,6 +125,7 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
|
|||
&i.CashierID,
|
||||
&i.Verified,
|
||||
&i.ReferenceNumber,
|
||||
&i.Status,
|
||||
&i.PaymentMethod,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
|
|
@ -128,12 +134,12 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
|
|||
}
|
||||
|
||||
const GetTransferByReference = `-- name: GetTransferByReference :one
|
||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, payment_method, created_at, updated_at
|
||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at
|
||||
FROM wallet_transfer
|
||||
WHERE reference_number = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber string) (WalletTransfer, error) {
|
||||
func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber pgtype.Text) (WalletTransfer, error) {
|
||||
row := q.db.QueryRow(ctx, GetTransferByReference, referenceNumber)
|
||||
var i WalletTransfer
|
||||
err := row.Scan(
|
||||
|
|
@ -145,6 +151,7 @@ func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber st
|
|||
&i.CashierID,
|
||||
&i.Verified,
|
||||
&i.ReferenceNumber,
|
||||
&i.Status,
|
||||
&i.PaymentMethod,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
|
|
@ -153,7 +160,7 @@ func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber st
|
|||
}
|
||||
|
||||
const GetTransfersByWallet = `-- name: GetTransfersByWallet :many
|
||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, payment_method, created_at, updated_at
|
||||
SELECT id, amount, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, status, payment_method, created_at, updated_at
|
||||
FROM wallet_transfer
|
||||
WHERE receiver_wallet_id = $1
|
||||
OR sender_wallet_id = $1
|
||||
|
|
@ -177,6 +184,7 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID pgt
|
|||
&i.CashierID,
|
||||
&i.Verified,
|
||||
&i.ReferenceNumber,
|
||||
&i.Status,
|
||||
&i.PaymentMethod,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
|
|
@ -191,6 +199,23 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID pgt
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const UpdateTransferStatus = `-- name: UpdateTransferStatus :exec
|
||||
UPDATE wallet_transfer
|
||||
SET status = $1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $2
|
||||
`
|
||||
|
||||
type UpdateTransferStatusParams struct {
|
||||
Status pgtype.Text `json:"status"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateTransferStatus(ctx context.Context, arg UpdateTransferStatusParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateTransferStatus, arg.Status, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
const UpdateTransferVerification = `-- name: UpdateTransferVerification :exec
|
||||
UPDATE wallet_transfer
|
||||
SET verified = $1,
|
||||
|
|
@ -199,8 +224,8 @@ WHERE id = $2
|
|||
`
|
||||
|
||||
type UpdateTransferVerificationParams struct {
|
||||
Verified bool `json:"verified"`
|
||||
ID int64 `json:"id"`
|
||||
Verified pgtype.Bool `json:"verified"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateTransferVerification(ctx context.Context, arg UpdateTransferVerificationParams) error {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: user.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: virtual_games.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// sqlc v1.29.0
|
||||
// source: wallet.sql
|
||||
|
||||
package dbgen
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@ type VeliGamesConfig struct {
|
|||
}
|
||||
|
||||
type Config struct {
|
||||
FIXER_API_KEY string
|
||||
FIXER_BASE_URL string
|
||||
BASE_CURRENCY domain.IntCurrency
|
||||
Port int
|
||||
DbUrl string
|
||||
RefreshExpiry int
|
||||
|
|
@ -68,6 +71,8 @@ type Config struct {
|
|||
AFRO_SMS_SENDER_NAME string
|
||||
AFRO_SMS_RECEIVER_PHONE_NUMBER string
|
||||
ADRO_SMS_HOST_URL string
|
||||
CHAPA_TRANSFER_TYPE string
|
||||
CHAPA_PAYMENT_TYPE string
|
||||
CHAPA_SECRET_KEY string
|
||||
CHAPA_PUBLIC_KEY string
|
||||
CHAPA_BASE_URL string
|
||||
|
|
@ -104,6 +109,13 @@ func (c *Config) loadEnv() error {
|
|||
|
||||
c.ReportExportPath = os.Getenv("REPORT_EXPORT_PATH")
|
||||
|
||||
c.CHAPA_TRANSFER_TYPE = os.Getenv("CHAPA_TRANSFER_TYPE")
|
||||
c.CHAPA_PAYMENT_TYPE = os.Getenv("CHAPA_PAYMENT_TYPE")
|
||||
|
||||
c.FIXER_API_KEY = os.Getenv("FIXER_API_KEY")
|
||||
c.BASE_CURRENCY = domain.IntCurrency(os.Getenv("BASE_CURRENCY"))
|
||||
c.FIXER_BASE_URL = os.Getenv("FIXER_BASE_URL")
|
||||
|
||||
portStr := os.Getenv("PORT")
|
||||
if portStr == "" {
|
||||
return ErrInvalidPort
|
||||
|
|
|
|||
|
|
@ -1,9 +1,27 @@
|
|||
package domain
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInsufficientBalance = errors.New("insufficient balance")
|
||||
ErrInvalidWithdrawalAmount = errors.New("invalid withdrawal amount")
|
||||
ErrWithdrawalNotFound = errors.New("withdrawal not found")
|
||||
)
|
||||
|
||||
type PaymentStatus string
|
||||
|
||||
type WithdrawalStatus string
|
||||
|
||||
const (
|
||||
WithdrawalStatusPending WithdrawalStatus = "pending"
|
||||
WithdrawalStatusProcessing WithdrawalStatus = "processing"
|
||||
WithdrawalStatusCompleted WithdrawalStatus = "completed"
|
||||
WithdrawalStatusFailed WithdrawalStatus = "failed"
|
||||
)
|
||||
|
||||
const (
|
||||
PaymentStatusPending PaymentStatus = "pending"
|
||||
PaymentStatusCompleted PaymentStatus = "completed"
|
||||
|
|
@ -91,3 +109,86 @@ type BankData struct {
|
|||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Currency string `json:"currency"`
|
||||
}
|
||||
|
||||
type ChapaWithdrawal struct {
|
||||
ID string
|
||||
UserID int64
|
||||
Amount Currency
|
||||
AccountNumber string
|
||||
BankCode string
|
||||
Status WithdrawalStatus
|
||||
Reference string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type ChapaWithdrawalRequest struct {
|
||||
AccountName string `json:"account_name"`
|
||||
AccountNumber string `json:"account_number"`
|
||||
Amount string `json:"amount"` // string because Chapa API uses string for monetary values
|
||||
Currency string `json:"currency"`
|
||||
Reference string `json:"reference"`
|
||||
BankCode int `json:"bank_code"`
|
||||
}
|
||||
|
||||
// type ChapaWithdrawalRequest struct {
|
||||
// AccountName string `json:"account_name"`
|
||||
// AccountNumber string `json:"account_number"`
|
||||
// Amount Currency `json:"amount"`
|
||||
// Currency string `json:"currency"`
|
||||
// BeneficiaryName string `json:"beneficiary_name"`
|
||||
// BankCode string `json:"bank_code"`
|
||||
// PhoneNumber string `json:"phone_number"`
|
||||
// }
|
||||
|
||||
type ChapaWithdrawalResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Reference string `json:"reference"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type ChapaTransactionType struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type ChapaWebHookTransfer struct {
|
||||
AccountName string `json:"account_name"`
|
||||
AccountNumber string `json:"account_number"`
|
||||
BankId string `json:"bank_id"`
|
||||
BankName string `json:"bank_name"`
|
||||
Currency string `json:"currency"`
|
||||
Amount string `json:"amount"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Reference string `json:"reference"`
|
||||
TxRef string `json:"tx_ref"`
|
||||
ChapaReference string `json:"chapa_reference"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type ChapaWebHookPayment struct {
|
||||
Event string `json:"event"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
Mobile interface{} `json:"mobile"`
|
||||
Currency string `json:"currency"`
|
||||
Amount string `json:"amount"`
|
||||
Charge string `json:"charge"`
|
||||
Status string `json:"status"`
|
||||
Mode string `json:"mode"`
|
||||
Reference string `json:"reference"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Type string `json:"type"`
|
||||
TxRef string `json:"tx_ref"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
Customization struct {
|
||||
Title interface{} `json:"title"`
|
||||
Description interface{} `json:"description"`
|
||||
Logo interface{} `json:"logo"`
|
||||
} `json:"customization"`
|
||||
Meta string `json:"meta"`
|
||||
}
|
||||
|
|
|
|||
88
internal/domain/currency.go
Normal file
88
internal/domain/currency.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IntCurrency string
|
||||
|
||||
const (
|
||||
ETB IntCurrency = "ETB" // Ethiopian Birr
|
||||
NGN IntCurrency = "NGN" // Nigerian Naira
|
||||
ZAR IntCurrency = "ZAR" // South African Rand
|
||||
EGP IntCurrency = "EGP" // Egyptian Pound
|
||||
KES IntCurrency = "KES" // Kenyan Shilling
|
||||
UGX IntCurrency = "UGX" // Ugandan Shilling
|
||||
TZS IntCurrency = "TZS" // Tanzanian Shilling
|
||||
RWF IntCurrency = "RWF" // Rwandan Franc
|
||||
BIF IntCurrency = "BIF" // Burundian Franc
|
||||
XOF IntCurrency = "XOF" // West African CFA Franc (BCEAO)
|
||||
XAF IntCurrency = "XAF" // Central African CFA Franc (BEAC)
|
||||
GHS IntCurrency = "GHS" // Ghanaian Cedi
|
||||
SDG IntCurrency = "SDG" // Sudanese Pound
|
||||
SSP IntCurrency = "SSP" // South Sudanese Pound
|
||||
DZD IntCurrency = "DZD" // Algerian Dinar
|
||||
MAD IntCurrency = "MAD" // Moroccan Dirham
|
||||
TND IntCurrency = "TND" // Tunisian Dinar
|
||||
LYD IntCurrency = "LYD" // Libyan Dinar
|
||||
MZN IntCurrency = "MZN" // Mozambican Metical
|
||||
AOA IntCurrency = "AOA" // Angolan Kwanza
|
||||
BWP IntCurrency = "BWP" // Botswana Pula
|
||||
ZMW IntCurrency = "ZMW" // Zambian Kwacha
|
||||
MWK IntCurrency = "MWK" // Malawian Kwacha
|
||||
LSL IntCurrency = "LSL" // Lesotho Loti
|
||||
NAD IntCurrency = "NAD" // Namibian Dollar
|
||||
SZL IntCurrency = "SZL" // Swazi Lilangeni
|
||||
CVE IntCurrency = "CVE" // Cape Verdean Escudo
|
||||
GMD IntCurrency = "GMD" // Gambian Dalasi
|
||||
SLL IntCurrency = "SLL" // Sierra Leonean Leone
|
||||
LRD IntCurrency = "LRD" // Liberian Dollar
|
||||
GNF IntCurrency = "GNF" // Guinean Franc
|
||||
XCD IntCurrency = "XCD" // Eastern Caribbean Dollar (used in Saint Lucia)
|
||||
MRU IntCurrency = "MRU" // Mauritanian Ouguiya
|
||||
KMF IntCurrency = "KMF" // Comorian Franc
|
||||
DJF IntCurrency = "DJF" // Djiboutian Franc
|
||||
SOS IntCurrency = "SOS" // Somali Shilling
|
||||
ERN IntCurrency = "ERN" // Eritrean Nakfa
|
||||
MGA IntCurrency = "MGA" // Malagasy Ariary
|
||||
SCR IntCurrency = "SCR" // Seychellois Rupee
|
||||
MUR IntCurrency = "MUR" // Mauritian Rupee
|
||||
|
||||
// International currencies (already listed)
|
||||
USD IntCurrency = "USD" // US Dollar
|
||||
EUR IntCurrency = "EUR" // Euro
|
||||
GBP IntCurrency = "GBP" // British Pound
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsupportedIntCurrency = errors.New("unsupported IntCurrency")
|
||||
ErrIntCurrencyConversion = errors.New("IntCurrency conversion failed")
|
||||
)
|
||||
|
||||
// IntCurrencyRate represents exchange rate between two currencies
|
||||
type IntCurrencyRate struct {
|
||||
From IntCurrency
|
||||
To IntCurrency
|
||||
Rate float64
|
||||
ValidUntil time.Time
|
||||
}
|
||||
|
||||
// Convert converts amount from one IntCurrency to another
|
||||
func (cr IntCurrencyRate) Convert(amount float64) (float64, error) {
|
||||
if time.Now().After(cr.ValidUntil) {
|
||||
return 0, fmt.Errorf("%w: rate expired", ErrIntCurrencyConversion)
|
||||
}
|
||||
return amount * cr.Rate, nil
|
||||
}
|
||||
|
||||
// ValidateIntCurrency checks if IntCurrency is supported
|
||||
func ValidateIntCurrency(c IntCurrency) error {
|
||||
switch c {
|
||||
case ETB, USD, EUR, GBP:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", ErrUnsupportedIntCurrency, c)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,14 @@ func UnProcessableEntityResponse(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
func UnExpectedErrorResponse(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(Response{
|
||||
Message: "Unexpected internal error",
|
||||
StatusCode: fiber.StatusInternalServerError,
|
||||
Success: false,
|
||||
})
|
||||
}
|
||||
|
||||
func FiberErrorResponse(c *fiber.Ctx, err error) error {
|
||||
var statusCode int
|
||||
var message string
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ type Transfer struct {
|
|||
ReceiverWalletID ValidInt64
|
||||
SenderWalletID ValidInt64
|
||||
ReferenceNumber string
|
||||
Status string
|
||||
CashierID ValidInt64
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
|
|
@ -50,6 +51,7 @@ type CreateTransfer struct {
|
|||
Amount Currency
|
||||
Verified bool
|
||||
ReferenceNumber string
|
||||
Status string
|
||||
ReceiverWalletID ValidInt64
|
||||
SenderWalletID ValidInt64
|
||||
CashierID ValidInt64
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import "time"
|
|||
type Wallet struct {
|
||||
ID int64
|
||||
Balance Currency
|
||||
Currency IntCurrency
|
||||
IsWithdraw bool
|
||||
IsBettable bool
|
||||
IsTransferable bool
|
||||
|
|
|
|||
96
internal/repository/currency.go
Normal file
96
internal/repository/currency.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
type CurrencyRepository interface {
|
||||
GetExchangeRate(ctx context.Context, from, to domain.IntCurrency) (domain.IntCurrencyRate, error)
|
||||
StoreExchangeRate(ctx context.Context, rate domain.IntCurrencyRate) error
|
||||
GetSupportedCurrencies(ctx context.Context) ([]domain.IntCurrency, error)
|
||||
}
|
||||
|
||||
type CurrencyPostgresRepository struct {
|
||||
store *Store
|
||||
}
|
||||
|
||||
func NewCurrencyPostgresRepository(store *Store) *CurrencyPostgresRepository {
|
||||
return &CurrencyPostgresRepository{store: store}
|
||||
}
|
||||
|
||||
func (r *CurrencyPostgresRepository) GetExchangeRate(ctx context.Context, from, to domain.IntCurrency) (domain.IntCurrencyRate, error) {
|
||||
const query = `
|
||||
SELECT from_currency, to_currency, rate, precision, valid_until
|
||||
FROM exchange_rates
|
||||
WHERE from_currency = $1 AND to_currency = $2 AND valid_until > NOW()
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1`
|
||||
|
||||
var rate domain.IntCurrencyRate
|
||||
err := r.store.conn.QueryRow(ctx, query, from, to).Scan(
|
||||
&rate.From,
|
||||
&rate.To,
|
||||
&rate.Rate,
|
||||
&rate.ValidUntil,
|
||||
)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return domain.IntCurrencyRate{}, fmt.Errorf("%w: no rate found for %s to %s",
|
||||
domain.ErrIntCurrencyConversion, from, to)
|
||||
}
|
||||
return domain.IntCurrencyRate{}, fmt.Errorf("failed to get exchange rate: %w", err)
|
||||
}
|
||||
|
||||
return rate, nil
|
||||
}
|
||||
|
||||
func (r *CurrencyPostgresRepository) StoreExchangeRate(ctx context.Context, rate domain.IntCurrencyRate) error {
|
||||
const query = `
|
||||
INSERT INTO exchange_rates (from_currency, to_currency, rate, precision, valid_until)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
ON CONFLICT (from_currency, to_currency)
|
||||
DO UPDATE SET
|
||||
rate = EXCLUDED.rate,
|
||||
precision = EXCLUDED.precision,
|
||||
valid_until = EXCLUDED.valid_until,
|
||||
created_at = NOW()`
|
||||
|
||||
_, err := r.store.conn.Exec(ctx, query,
|
||||
rate.From,
|
||||
rate.To,
|
||||
rate.Rate,
|
||||
rate.ValidUntil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to store exchange rate: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *CurrencyPostgresRepository) GetSupportedCurrencies(ctx context.Context) ([]domain.IntCurrency, error) {
|
||||
const query = `SELECT DISTINCT currency FROM supported_currencies ORDER BY currency`
|
||||
|
||||
var currencies []domain.IntCurrency
|
||||
rows, err := r.store.conn.Query(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get supported currencies: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var currency domain.IntCurrency
|
||||
if err := rows.Scan(¤cy); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan currency: %w", err)
|
||||
}
|
||||
currencies = append(currencies, currency)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("row iteration error: %w", err)
|
||||
}
|
||||
|
||||
return currencies, nil
|
||||
}
|
||||
|
|
@ -22,21 +22,27 @@ func convertDBTransfer(transfer dbgen.WalletTransfer) domain.Transfer {
|
|||
Value: transfer.SenderWalletID.Int64,
|
||||
Valid: transfer.SenderWalletID.Valid,
|
||||
},
|
||||
ID: transfer.ID,
|
||||
Amount: domain.Currency(transfer.Amount.Int64),
|
||||
Type: domain.TransferType(transfer.Type.String),
|
||||
Verified: transfer.Verified.Bool,
|
||||
ReceiverWalletID: transfer.ReceiverWalletID.Int64,
|
||||
SenderWalletID: transfer.SenderWalletID.Int64,
|
||||
CashierID: domain.ValidInt64{
|
||||
Value: transfer.CashierID.Int64,
|
||||
Valid: transfer.CashierID.Valid,
|
||||
},
|
||||
PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod),
|
||||
PaymentMethod: domain.PaymentMethod(transfer.PaymentMethod.String),
|
||||
}
|
||||
}
|
||||
|
||||
func convertCreateTransfer(transfer domain.CreateTransfer) dbgen.CreateTransferParams {
|
||||
return dbgen.CreateTransferParams{
|
||||
Amount: int64(transfer.Amount),
|
||||
Type: string(transfer.Type),
|
||||
Amount: pgtype.Int8{Int64: int64(transfer.Amount), Valid: true},
|
||||
Type: pgtype.Text{String: string(transfer.Type), Valid: true},
|
||||
ReceiverWalletID: pgtype.Int8{
|
||||
Int64: transfer.ReceiverWalletID.Value,
|
||||
Valid: transfer.ReceiverWalletID.Valid,
|
||||
Int64: transfer.ReceiverWalletID,
|
||||
Valid: true,
|
||||
},
|
||||
SenderWalletID: pgtype.Int8{
|
||||
Int64: transfer.SenderWalletID.Value,
|
||||
|
|
@ -46,7 +52,7 @@ func convertCreateTransfer(transfer domain.CreateTransfer) dbgen.CreateTransferP
|
|||
Int64: transfer.CashierID.Value,
|
||||
Valid: transfer.CashierID.Valid,
|
||||
},
|
||||
PaymentMethod: string(transfer.PaymentMethod),
|
||||
PaymentMethod: pgtype.Text{String: string(transfer.PaymentMethod), Valid: true},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,10 +77,7 @@ func (s *Store) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error)
|
|||
return result, nil
|
||||
}
|
||||
func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]domain.Transfer, error) {
|
||||
transfers, err := s.queries.GetTransfersByWallet(ctx, pgtype.Int8{
|
||||
Int64: walletID,
|
||||
Valid: true,
|
||||
})
|
||||
transfers, err := s.queries.GetTransfersByWallet(ctx, pgtype.Int8{Int64: walletID, Valid: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -88,7 +91,7 @@ func (s *Store) GetTransfersByWallet(ctx context.Context, walletID int64) ([]dom
|
|||
}
|
||||
|
||||
func (s *Store) GetTransferByReference(ctx context.Context, reference string) (domain.Transfer, error) {
|
||||
transfer, err := s.queries.GetTransferByReference(ctx, reference)
|
||||
transfer, err := s.queries.GetTransferByReference(ctx, pgtype.Text{String: reference, Valid: true})
|
||||
if err != nil {
|
||||
return domain.Transfer{}, nil
|
||||
}
|
||||
|
|
@ -106,7 +109,16 @@ func (s *Store) GetTransferByID(ctx context.Context, id int64) (domain.Transfer,
|
|||
func (s *Store) UpdateTransferVerification(ctx context.Context, id int64, verified bool) error {
|
||||
err := s.queries.UpdateTransferVerification(ctx, dbgen.UpdateTransferVerificationParams{
|
||||
ID: id,
|
||||
Verified: verified,
|
||||
Verified: pgtype.Bool{Bool: verified, Valid: true},
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Store) UpdateTransferStatus(ctx context.Context, id int64, status string) error {
|
||||
err := s.queries.UpdateTransferStatus(ctx, dbgen.UpdateTransferStatusParams{
|
||||
ID: id,
|
||||
Status: pgtype.Text{String: status, Valid: true},
|
||||
})
|
||||
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
|
|
@ -211,24 +212,78 @@ func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
|||
return banks, nil
|
||||
}
|
||||
|
||||
// Helper method to generate account regex based on bank type
|
||||
// func GetAccountRegex(bank domain.Bank) string {
|
||||
// if bank.IsMobileMoney != nil && bank.IsMobileMoney == 1 {
|
||||
// return `^09[0-9]{8}$` // Ethiopian mobile money pattern
|
||||
// }
|
||||
// return fmt.Sprintf(`^[0-9]{%d}$`, bank.AcctLength)
|
||||
// }
|
||||
func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawalRequest) (bool, error) {
|
||||
// base, err := url.Parse(c.baseURL)
|
||||
// if err != nil {
|
||||
// return false, fmt.Errorf("invalid base URL: %w", err)
|
||||
// }
|
||||
endpoint := c.baseURL + "/transfers"
|
||||
fmt.Printf("\n\nChapa withdrawal URL is %v\n\n", endpoint)
|
||||
|
||||
// // Helper method to generate example account number
|
||||
// func GetExampleAccount(bank domain.Bank) string {
|
||||
// if bank.IsMobileMoney != nil && *bank.IsMobileMoney {
|
||||
// return "0912345678" // Ethiopian mobile number example
|
||||
// }
|
||||
reqBody, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
// // Generate example based on length
|
||||
// example := "1"
|
||||
// for i := 1; i < bank.AcctLength; i++ {
|
||||
// example += fmt.Sprintf("%d", i%10)
|
||||
// }
|
||||
// return example
|
||||
// }
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
c.setHeaders(httpReq)
|
||||
|
||||
resp, err := c.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return false, fmt.Errorf("chapa api returned status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var response domain.ChapaWithdrawalResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
return false, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return response.Status == string(domain.WithdrawalStatusProcessing), nil
|
||||
}
|
||||
|
||||
func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.ChapaVerificationResponse, error) {
|
||||
base, err := url.Parse(c.baseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid base URL: %w", err)
|
||||
}
|
||||
endpoint := base.ResolveReference(&url.URL{Path: fmt.Sprintf("/v1/transfers/%s/verify", reference)})
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "GET", endpoint.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
c.setHeaders(httpReq)
|
||||
|
||||
resp, err := c.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("chapa api returned status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var verification domain.ChapaVerificationResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return &verification, nil
|
||||
}
|
||||
|
||||
func (c *Client) setHeaders(req *http.Request) {
|
||||
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ import (
|
|||
|
||||
type ChapaStore interface {
|
||||
InitializePayment(request domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error)
|
||||
VerifyPayment(reference string) (domain.ChapaDepositVerification, error)
|
||||
ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error)
|
||||
// VerifyPayment(reference string) (domain.ChapaDepositVerification, error)
|
||||
ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error)
|
||||
FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
||||
CreateWithdrawal(userID string, amount float64, accountNumber, bankCode string) (*domain.ChapaWithdrawal, error)
|
||||
HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebHookTransfer) error
|
||||
HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
|
|
@ -20,7 +21,7 @@ var (
|
|||
|
||||
type Service struct {
|
||||
transferStore wallet.TransferStore
|
||||
walletStore wallet.WalletStore
|
||||
walletStore wallet.Service
|
||||
userStore user.UserStore
|
||||
cfg *config.Config
|
||||
chapaClient *Client
|
||||
|
|
@ -28,7 +29,7 @@ type Service struct {
|
|||
|
||||
func NewService(
|
||||
transferStore wallet.TransferStore,
|
||||
walletStore wallet.WalletStore,
|
||||
walletStore wallet.Service,
|
||||
userStore user.UserStore,
|
||||
chapaClient *Client,
|
||||
|
||||
|
|
@ -113,47 +114,100 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
|||
|
||||
return response.CheckoutURL, nil
|
||||
}
|
||||
func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req domain.ChapaWithdrawalRequest) (*domain.Transfer, error) {
|
||||
// Parse and validate amount
|
||||
amount, err := strconv.ParseInt(req.Amount, 10, 64)
|
||||
if err != nil || amount <= 0 {
|
||||
return nil, domain.ErrInvalidWithdrawalAmount
|
||||
}
|
||||
|
||||
// VerifyDeposit handles payment verification from webhook
|
||||
func (s *Service) VerifyDeposit(ctx context.Context, reference string) error {
|
||||
// Find payment by reference
|
||||
payment, err := s.transferStore.GetTransferByReference(ctx, reference)
|
||||
// Get user details
|
||||
// user, err := s.userStore.GetUserByID(ctx, userID)
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("failed to get user: %w", err)
|
||||
// }
|
||||
|
||||
// Get user's wallet
|
||||
wallets, err := s.walletStore.GetWalletsByUser(ctx, userID)
|
||||
if err != nil {
|
||||
return ErrPaymentNotFound
|
||||
return nil, fmt.Errorf("failed to get user wallets: %w", err)
|
||||
}
|
||||
|
||||
// just making sure that the sender id is valid
|
||||
if !payment.SenderWalletID.Valid {
|
||||
return fmt.Errorf("sender wallet is invalid %v \n", payment.SenderWalletID)
|
||||
}
|
||||
|
||||
// Skip if already completed
|
||||
if payment.Verified {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify payment with Chapa
|
||||
verification, err := s.chapaClient.VerifyPayment(ctx, reference)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to verify payment: %w", err)
|
||||
}
|
||||
|
||||
// Update payment status
|
||||
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
||||
return fmt.Errorf("failed to update payment status: %w", err)
|
||||
}
|
||||
|
||||
// If payment is completed, credit user's wallet
|
||||
if verification.Status == domain.PaymentStatusCompleted {
|
||||
if err := s.walletStore.UpdateBalance(ctx, payment.SenderWalletID.Value, payment.Amount); err != nil {
|
||||
return fmt.Errorf("failed to credit user wallet: %w", err)
|
||||
var withdrawWallet domain.Wallet
|
||||
for _, wallet := range wallets {
|
||||
if wallet.IsWithdraw {
|
||||
withdrawWallet = wallet
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
if withdrawWallet.ID == 0 {
|
||||
return nil, errors.New("withdrawal wallet not found")
|
||||
}
|
||||
// Check balance
|
||||
if withdrawWallet.Balance < domain.Currency(amount) {
|
||||
return nil, domain.ErrInsufficientBalance
|
||||
}
|
||||
|
||||
// Generate unique reference
|
||||
reference := uuid.New().String()
|
||||
|
||||
createTransfer := domain.CreateTransfer{
|
||||
Amount: domain.Currency(amount),
|
||||
Type: domain.WITHDRAW,
|
||||
ReceiverWalletID: 1,
|
||||
SenderWalletID: withdrawWallet.ID,
|
||||
Status: string(domain.PaymentStatusPending),
|
||||
Verified: false,
|
||||
ReferenceNumber: reference,
|
||||
PaymentMethod: domain.TRANSFER_CHAPA,
|
||||
}
|
||||
|
||||
transfer, err := s.transferStore.CreateTransfer(ctx, createTransfer)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create transfer record: %w", err)
|
||||
}
|
||||
// Initiate transfer with Chapa
|
||||
transferReq := domain.ChapaWithdrawalRequest{
|
||||
AccountName: req.AccountName,
|
||||
AccountNumber: req.AccountNumber,
|
||||
Amount: fmt.Sprintf("%d", amount),
|
||||
Currency: req.Currency,
|
||||
Reference: reference,
|
||||
// BeneficiaryName: fmt.Sprintf("%s %s", user.FirstName, user.LastName),
|
||||
BankCode: req.BankCode,
|
||||
}
|
||||
|
||||
success, err := s.chapaClient.InitiateTransfer(ctx, transferReq)
|
||||
if err != nil || !success {
|
||||
// Update withdrawal status to failed
|
||||
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
||||
return nil, fmt.Errorf("failed to initiate transfer: %w", err)
|
||||
}
|
||||
|
||||
// Update withdrawal status to processing
|
||||
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusProcessing)); err != nil {
|
||||
return nil, fmt.Errorf("failed to update withdrawal status: %w", err)
|
||||
}
|
||||
// Deduct from wallet (or wait for webhook confirmation depending on your flow)
|
||||
newBalance := withdrawWallet.Balance - domain.Currency(amount)
|
||||
if err := s.walletStore.UpdateBalance(ctx, withdrawWallet.ID, newBalance); err != nil {
|
||||
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
|
||||
}
|
||||
|
||||
return &transfer, nil
|
||||
}
|
||||
|
||||
func (s *Service) ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
||||
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
||||
banks, err := s.chapaClient.FetchSupportedBanks(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch banks: %w", err)
|
||||
}
|
||||
return banks, nil
|
||||
}
|
||||
|
||||
func (s *Service) ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
||||
// First check if we already have a verified record
|
||||
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
||||
if err == nil && transfer.Verified {
|
||||
|
|
@ -196,10 +250,76 @@ func (s *Service) ManualVerifyPayment(ctx context.Context, txRef string) (*domai
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
||||
banks, err := s.chapaClient.FetchSupportedBanks(ctx)
|
||||
func (s *Service) HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebHookTransfer) error {
|
||||
// Find payment by reference
|
||||
payment, err := s.transferStore.GetTransferByReference(ctx, transfer.Reference)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch banks: %w", err)
|
||||
return ErrPaymentNotFound
|
||||
}
|
||||
return banks, nil
|
||||
|
||||
if payment.Verified {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify payment with Chapa
|
||||
// verification, err := s.chapaClient.VerifyPayment(ctx, transfer.Reference)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to verify payment: %w", err)
|
||||
// }
|
||||
|
||||
// Update payment status
|
||||
// verified := false
|
||||
// if transfer.Status == string(domain.PaymentStatusCompleted) {
|
||||
// verified = true
|
||||
// }
|
||||
|
||||
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
||||
return fmt.Errorf("failed to update payment status: %w", err)
|
||||
}
|
||||
|
||||
// If payment is completed, credit user's wallet
|
||||
if transfer.Status == string(domain.PaymentStatusCompleted) {
|
||||
if err := s.walletStore.AddToWallet(ctx, payment.SenderWalletID, payment.Amount); err != nil {
|
||||
return fmt.Errorf("failed to credit user wallet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) error {
|
||||
// Find payment by reference
|
||||
transfer, err := s.transferStore.GetTransferByReference(ctx, payment.Reference)
|
||||
if err != nil {
|
||||
return ErrPaymentNotFound
|
||||
}
|
||||
|
||||
if transfer.Verified {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify payment with Chapa
|
||||
// verification, err := s.chapaClient.VerifyPayment(ctx, payment.Reference)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("failed to verify payment: %w", err)
|
||||
// }
|
||||
|
||||
// Update payment status
|
||||
// verified := false
|
||||
// if transfer.Status == string(domain.PaymentStatusCompleted) {
|
||||
// verified = true
|
||||
// }
|
||||
|
||||
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||
return fmt.Errorf("failed to update payment status: %w", err)
|
||||
}
|
||||
|
||||
// If payment is completed, credit user's wallet
|
||||
if payment.Status == string(domain.PaymentStatusFailed) {
|
||||
if err := s.walletStore.AddToWallet(ctx, transfer.SenderWalletID, transfer.Amount); err != nil {
|
||||
return fmt.Errorf("failed to credit user wallet: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
69
internal/services/currency/fetcher.go
Normal file
69
internal/services/currency/fetcher.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package currency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
type FixerFetcher struct {
|
||||
apiKey string
|
||||
baseURL string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
func NewFixerFetcher(apiKey string, baseURL string) *FixerFetcher {
|
||||
return &FixerFetcher{
|
||||
apiKey: apiKey,
|
||||
baseURL: baseURL,
|
||||
httpClient: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
type fixerResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Base string `json:"base"`
|
||||
Date string `json:"date"`
|
||||
Rates map[string]float64 `json:"rates"`
|
||||
}
|
||||
|
||||
func (f *FixerFetcher) FetchLatestRates(ctx context.Context, baseCurrency domain.IntCurrency) (map[domain.IntCurrency]float64, error) {
|
||||
url := fmt.Sprintf("%s/latest?base=%s", f.baseURL, baseCurrency)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("apikey", f.apiKey)
|
||||
|
||||
resp, err := f.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch rates: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result fixerResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
if !result.Success {
|
||||
return nil, fmt.Errorf("api returned unsuccessful response")
|
||||
}
|
||||
|
||||
rates := make(map[domain.IntCurrency]float64)
|
||||
for currency, rate := range result.Rates {
|
||||
rates[domain.IntCurrency(currency)] = rate
|
||||
}
|
||||
|
||||
return rates, nil
|
||||
}
|
||||
125
internal/services/currency/service.go
Normal file
125
internal/services/currency/service.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package currency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo repository.CurrencyRepository
|
||||
baseCurrency domain.IntCurrency
|
||||
fixerFetcher *FixerFetcher
|
||||
}
|
||||
|
||||
func NewService(repo repository.CurrencyRepository, baseCurrency domain.IntCurrency, fixerFetcher *FixerFetcher) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
func (s *Service) Convert(ctx context.Context, amount float64, from, to domain.IntCurrency) (float64, error) {
|
||||
if from == to {
|
||||
return amount, nil
|
||||
}
|
||||
|
||||
rate, err := s.repo.GetExchangeRate(ctx, from, to)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return rate.Convert(amount)
|
||||
}
|
||||
|
||||
func (s *Service) GetSupportedCurrencies(ctx context.Context) ([]domain.IntCurrency, error) {
|
||||
return s.repo.GetSupportedCurrencies(ctx)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateRates(ctx context.Context) error {
|
||||
// Implement fetching from external API (e.g., Fixer, Open Exchange Rates)
|
||||
rates := map[domain.IntCurrency]map[domain.IntCurrency]float64{
|
||||
domain.ETB: {
|
||||
domain.USD: 0.018,
|
||||
domain.EUR: 0.016,
|
||||
domain.GBP: 0.014,
|
||||
},
|
||||
// Add other currencies...
|
||||
}
|
||||
|
||||
for from, toRates := range rates {
|
||||
for to, rate := range toRates {
|
||||
err := s.repo.StoreExchangeRate(ctx, domain.IntCurrencyRate{
|
||||
From: from,
|
||||
To: to,
|
||||
Rate: rate,
|
||||
ValidUntil: time.Now().Add(24 * time.Hour), // Refresh daily
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) FetchAndStoreRates(ctx context.Context) error {
|
||||
// s.logger.Info("Starting exchange rate update")
|
||||
|
||||
rates, err := s.fixerFetcher.FetchLatestRates(ctx, s.baseCurrency)
|
||||
if err != nil {
|
||||
// s.logger.Error("Failed to fetch rates", "error", err)
|
||||
return fmt.Errorf("failed to fetch rates: %w", err)
|
||||
}
|
||||
|
||||
// Convert to integer rates with precision
|
||||
const precision = 6 // 1.000000
|
||||
for currency, rate := range rates {
|
||||
if currency == s.baseCurrency {
|
||||
continue
|
||||
}
|
||||
|
||||
intRate := domain.IntCurrencyRate{
|
||||
From: s.baseCurrency,
|
||||
To: currency,
|
||||
Rate: rate * float64(pow10(precision)),
|
||||
ValidUntil: time.Now().Add(24 * time.Hour), // Rates valid for 24 hours
|
||||
}
|
||||
|
||||
if err := s.repo.StoreExchangeRate(ctx, intRate); err != nil {
|
||||
// s.logger.Error("Failed to store rate",
|
||||
// "from", s.baseCurrency,
|
||||
// "to", currency,
|
||||
// "error", err)
|
||||
continue // Try to store other rates even if one fails
|
||||
}
|
||||
|
||||
// Also store the inverse rate
|
||||
inverseRate := domain.IntCurrencyRate{
|
||||
From: currency,
|
||||
To: s.baseCurrency,
|
||||
Rate: (1 / rate) * float64(pow10(precision)),
|
||||
ValidUntil: time.Now().Add(24 * time.Hour),
|
||||
}
|
||||
|
||||
if err := s.repo.StoreExchangeRate(ctx, inverseRate); err != nil {
|
||||
// s.logger.Error("Failed to store inverse rate",
|
||||
// "from", currency,
|
||||
// "to", s.baseCurrency,
|
||||
// "error", err)
|
||||
return fmt.Errorf("Error storing exchange rates")
|
||||
}
|
||||
}
|
||||
|
||||
// s.logger.Info("Exchange rates updated successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func pow10(n int) int64 {
|
||||
result := int64(1)
|
||||
for i := 0; i < n; i++ {
|
||||
result *= 10
|
||||
}
|
||||
return result
|
||||
}
|
||||
55
internal/services/currency/worker.go
Normal file
55
internal/services/currency/worker.go
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package currency
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"github.com/go-co-op/gocron"
|
||||
)
|
||||
|
||||
type ExchangeRateWorker struct {
|
||||
fetcherService *FixerFetcher
|
||||
scheduler *gocron.Scheduler
|
||||
logger *slog.Logger
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func NewExchangeRateWorker(
|
||||
fetcherService *FixerFetcher, logger *slog.Logger, cfg *config.Config,
|
||||
) *ExchangeRateWorker {
|
||||
return &ExchangeRateWorker{
|
||||
fetcherService: fetcherService,
|
||||
scheduler: gocron.NewScheduler(time.UTC),
|
||||
logger: logger,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ExchangeRateWorker) Start(ctx context.Context) {
|
||||
_, err := w.scheduler.Every(6).Hours().Do(w.RunUpdate)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Run immediately on startup
|
||||
go w.RunUpdate()
|
||||
|
||||
w.scheduler.StartAsync()
|
||||
}
|
||||
|
||||
func (w *ExchangeRateWorker) RunUpdate() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if _, err := w.fetcherService.FetchLatestRates(ctx, w.cfg.BASE_CURRENCY); err != nil {
|
||||
fmt.Println("Exchange rate update failed", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *ExchangeRateWorker) Stop() {
|
||||
w.scheduler.Stop()
|
||||
w.logger.Info("Exchange rate worker stopped")
|
||||
}
|
||||
|
|
@ -28,4 +28,5 @@ type TransferStore interface {
|
|||
GetTransferByReference(ctx context.Context, reference string) (domain.Transfer, error)
|
||||
GetTransferByID(ctx context.Context, id int64) (domain.Transfer, error)
|
||||
UpdateTransferVerification(ctx context.Context, id int64, verified bool) error
|
||||
UpdateTransferStatus(ctx context.Context, id int64, status string) error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,10 +38,17 @@ func (s *Service) UpdateTransferVerification(ctx context.Context, id int64, veri
|
|||
return s.transferStore.UpdateTransferVerification(ctx, id, verified)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateTransferStatus(ctx context.Context, id int64, status string) error {
|
||||
return s.transferStore.UpdateTransferStatus(ctx, id, status)
|
||||
}
|
||||
|
||||
func (s *Service) UpdateTransferStatus(ctx context.Context, id int64, status string) error {
|
||||
return s.transferStore.UpdateTransferStatus(ctx, id, status)
|
||||
}
|
||||
|
||||
|
||||
func (s *Service) TransferToWallet(ctx context.Context,
|
||||
senderID int64, receiverID int64,
|
||||
|
||||
func (s *Service) TransferToWallet(ctx context.Context, senderID int64, receiverID int64,
|
||||
amount domain.Currency, paymentMethod domain.PaymentMethod,
|
||||
cashierID domain.ValidInt64) (domain.Transfer, error) {
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/currency"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
|
|
@ -34,6 +35,7 @@ import (
|
|||
)
|
||||
|
||||
type App struct {
|
||||
currSvc *currency.Service
|
||||
fiber *fiber.App
|
||||
aleaVirtualGameService alea.AleaVirtualGameService
|
||||
veliVirtualGameService veli.VeliVirtualGameService
|
||||
|
|
@ -64,6 +66,7 @@ type App struct {
|
|||
}
|
||||
|
||||
func NewApp(
|
||||
currSvc *currency.Service,
|
||||
port int, validator *customvalidator.CustomValidator,
|
||||
authSvc *authentication.Service,
|
||||
logger *slog.Logger,
|
||||
|
|
@ -104,6 +107,7 @@ func NewApp(
|
|||
}))
|
||||
|
||||
s := &App{
|
||||
currSvc: currSvc,
|
||||
fiber: app,
|
||||
port: port,
|
||||
authSvc: authSvc,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param request body domain.ChapaDepositRequestPayload true "Deposit request"
|
||||
// @Success 200 {object} domain.ChapaDepositResponse
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
|
|
@ -65,38 +66,59 @@ func (h *Handler) InitiateDeposit(c *fiber.Ctx) error {
|
|||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/chapa/payments/webhook/verify [post]
|
||||
func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
||||
// Verify webhook signature first
|
||||
// signature := c.Get("Chapa-Signature")
|
||||
// if !verifySignature(signature, c.Body()) {
|
||||
// return c.Status(fiber.StatusUnauthorized).JSON(ErrorResponse{
|
||||
// Error: "invalid signature",
|
||||
// })
|
||||
// }
|
||||
|
||||
var payload struct {
|
||||
TxRef string `json:"tx_ref"`
|
||||
Amount float64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Status string `json:"status"`
|
||||
chapaTransactionType := new(domain.ChapaTransactionType)
|
||||
|
||||
if parseTypeErr := c.BodyParser(chapaTransactionType); parseTypeErr != nil {
|
||||
return domain.UnProcessableEntityResponse(c)
|
||||
}
|
||||
|
||||
if err := c.BodyParser(&payload); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Error: err.Error(),
|
||||
switch chapaTransactionType.Type {
|
||||
case h.Cfg.CHAPA_TRANSFER_TYPE:
|
||||
chapaTransferVerificationRequest := new(domain.ChapaWebHookTransfer)
|
||||
|
||||
if err := c.BodyParser(chapaTransferVerificationRequest); err != nil {
|
||||
return domain.UnProcessableEntityResponse(c)
|
||||
}
|
||||
|
||||
err := h.chapaSvc.HandleVerifyDepositWebhook(c.Context(), *chapaTransferVerificationRequest)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to verify Chapa depposit",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
StatusCode: 200,
|
||||
Message: "Chapa deposit transaction verified successfully",
|
||||
Data: chapaTransferVerificationRequest,
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
case h.Cfg.CHAPA_PAYMENT_TYPE:
|
||||
chapaPaymentVerificationRequest := new(domain.ChapaWebHookPayment)
|
||||
if err := c.BodyParser(chapaPaymentVerificationRequest); err != nil {
|
||||
return domain.UnProcessableEntityResponse(c)
|
||||
}
|
||||
|
||||
if err := h.chapaSvc.VerifyDeposit(c.Context(), payload.TxRef); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Error: err.Error(),
|
||||
err := h.chapaSvc.HandleVerifyWithdrawWebhook(c.Context(), *chapaPaymentVerificationRequest)
|
||||
if err != nil {
|
||||
return domain.UnExpectedErrorResponse(c)
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
StatusCode: 200,
|
||||
Message: "Chapa withdrawal transaction verified successfully",
|
||||
Data: chapaPaymentVerificationRequest,
|
||||
Success: true,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
StatusCode: 200,
|
||||
Message: "payment verified successfully",
|
||||
Data: payload.TxRef,
|
||||
Success: true,
|
||||
// Return a 400 Bad Request if the type does not match any known case
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid Chapa webhook type",
|
||||
Error: "Unknown transaction type",
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +133,7 @@ func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
|||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/chapa/payments/manual/verify/{tx_ref} [get]
|
||||
func (h *Handler) ManualVerifyPayment(c *fiber.Ctx) error {
|
||||
func (h *Handler) ManualVerifyTransaction(c *fiber.Ctx) error {
|
||||
txRef := c.Params("tx_ref")
|
||||
if txRef == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
|
|
@ -120,7 +142,7 @@ func (h *Handler) ManualVerifyPayment(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
verification, err := h.chapaSvc.ManualVerifyPayment(c.Context(), txRef)
|
||||
verification, err := h.chapaSvc.ManualVerifTransaction(c.Context(), txRef)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to verify Chapa transaction",
|
||||
|
|
@ -142,9 +164,9 @@ func (h *Handler) ManualVerifyPayment(c *fiber.Ctx) error {
|
|||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} domain.Bank
|
||||
// @Success 200 {object} domain.Response
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /banks [get]
|
||||
// @Router /api/v1/chapa/banks [get]
|
||||
func (h *Handler) GetSupportedBanks(c *fiber.Ctx) error {
|
||||
banks, err := h.chapaSvc.GetSupportedBanks(c.Context())
|
||||
if err != nil {
|
||||
|
|
@ -161,3 +183,44 @@ func (h *Handler) GetSupportedBanks(c *fiber.Ctx) error {
|
|||
Data: banks,
|
||||
})
|
||||
}
|
||||
|
||||
// InitiateWithdrawal initiates a withdrawal request via Chapa payment gateway
|
||||
// @Summary Initiate a withdrawal
|
||||
// @Description Initiates a withdrawal request to transfer funds to a bank account via Chapa
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param request body domain.ChapaWithdrawalRequest true "Withdrawal request details"
|
||||
// @Success 201 {object} domain.Response "Chapa withdrawal process initiated successfully"
|
||||
// @Failure 400 {object} domain.ErrorResponse "Invalid request body"
|
||||
// @Failure 401 {object} domain.ErrorResponse "Unauthorized"
|
||||
// @Failure 422 {object} domain.ErrorResponse "Unprocessable entity"
|
||||
// @Failure 500 {object} domain.ErrorResponse "Internal server error"
|
||||
// @Router /api/v1/chapa/payments/withdraw [post]
|
||||
func (h *Handler) InitiateWithdrawal(c *fiber.Ctx) error {
|
||||
userID, ok := c.Locals("user_id").(int64)
|
||||
if !ok {
|
||||
return domain.UnProcessableEntityResponse(c)
|
||||
}
|
||||
|
||||
var req domain.ChapaWithdrawalRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return domain.UnProcessableEntityResponse(c)
|
||||
}
|
||||
|
||||
withdrawal, err := h.chapaSvc.InitiateWithdrawal(c.Context(), userID, req)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to initiate Chapa withdrawal",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
||||
Message: "Chapa withdrawal process initiated successfully",
|
||||
StatusCode: 201,
|
||||
Success: true,
|
||||
Data: withdrawal,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
57
internal/web_server/handlers/currency.go
Normal file
57
internal/web_server/handlers/currency.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// @Summary Get supported currencies
|
||||
// @Description Returns list of supported currencies
|
||||
// @Tags Multi-Currency
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.Response{data=[]domain.Currency}
|
||||
// @Router /api/v1/currencies [get]
|
||||
func (h *Handler) GetSupportedCurrencies(c *fiber.Ctx) error {
|
||||
currencies, err := h.currSvc.GetSupportedCurrencies(c.Context())
|
||||
if err != nil {
|
||||
return domain.UnExpectedErrorResponse(c)
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Success: true,
|
||||
Message: "Supported currencies retrieved successfully",
|
||||
Data: currencies,
|
||||
StatusCode: fiber.StatusOK,
|
||||
})
|
||||
}
|
||||
|
||||
// @Summary Convert currency
|
||||
// @Description Converts amount from one currency to another
|
||||
// @Tags Multi-Currency
|
||||
// @Produce json
|
||||
// @Param from query string true "Source currency code (e.g., USD)"
|
||||
// @Param to query string true "Target currency code (e.g., ETB)"
|
||||
// @Param amount query number true "Amount to convert"
|
||||
// @Success 200 {object} domain.Response{data=float64}
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/currencies/convert [get]
|
||||
func (h *Handler) ConvertCurrency(c *fiber.Ctx) error {
|
||||
from := domain.IntCurrency(c.Query("from"))
|
||||
to := domain.IntCurrency(c.Query("to"))
|
||||
amount := c.QueryFloat("amount", 0)
|
||||
// if err != nil {
|
||||
// return domain.BadRequestResponse(c)
|
||||
// }
|
||||
|
||||
converted, err := h.currSvc.Convert(c.Context(), amount, from, to)
|
||||
if err != nil {
|
||||
return domain.UnExpectedErrorResponse(c)
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Success: true,
|
||||
Message: "Currency converted successfully",
|
||||
Data: converted,
|
||||
StatusCode: fiber.StatusOK,
|
||||
})
|
||||
}
|
||||
|
|
@ -9,14 +9,15 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/chapa"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/company"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/currency"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/event"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/league"
|
||||
notificationservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/notfication"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/odds"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/recommendation"
|
||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
|
|
@ -29,6 +30,7 @@ import (
|
|||
)
|
||||
|
||||
type Handler struct {
|
||||
currSvc *currency.Service
|
||||
logger *slog.Logger
|
||||
notificationSvc *notificationservice.Service
|
||||
userSvc *user.Service
|
||||
|
|
@ -56,6 +58,7 @@ type Handler struct {
|
|||
}
|
||||
|
||||
func New(
|
||||
currSvc *currency.Service,
|
||||
logger *slog.Logger,
|
||||
notificationSvc *notificationservice.Service,
|
||||
validator *customvalidator.CustomValidator,
|
||||
|
|
@ -82,6 +85,7 @@ func New(
|
|||
cfg *config.Config,
|
||||
) *Handler {
|
||||
return &Handler{
|
||||
currSvc: currSvc,
|
||||
logger: logger,
|
||||
notificationSvc: notificationSvc,
|
||||
reportSvc: reportSvc,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
// @Param sport_id query string false "Sport ID filter"
|
||||
// @Param status query int false "Status filter (0=Pending, 1=Win, 2=Loss, 3=Half, 4=Void, 5=Error)"
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {object} report.DashboardSummary
|
||||
// @Success 200 {object} domain.DashboardSummary
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 401 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
type ResultRes struct {
|
||||
ResultData json.RawMessage `json:"result_data"`
|
||||
ResultData json.RawMessage `json:"result_data" swaggerignore:"true"`
|
||||
Outcomes []domain.BetOutcome `json:"outcomes"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
|
||||
func (a *App) initAppRoutes() {
|
||||
h := handlers.New(
|
||||
a.currSvc,
|
||||
a.logger,
|
||||
a.NotidicationStore,
|
||||
a.validator,
|
||||
|
|
@ -199,10 +200,15 @@ a.fiber.Get("/cashierWallet", a.authMiddleware, h.GetWalletForCashier)
|
|||
|
||||
//Chapa Routes
|
||||
group.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
|
||||
group.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyPayment)
|
||||
group.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyTransaction)
|
||||
group.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
|
||||
group.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
|
||||
group.Get("/chapa/banks", h.GetSupportedBanks)
|
||||
|
||||
// Currencies
|
||||
group.Get("/currencies", h.GetSupportedCurrencies)
|
||||
group.Get("/currencies/convert", h.ConvertCurrency)
|
||||
|
||||
//Report Routes
|
||||
group.Get("/reports/dashboard", h.GetDashboardReport)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user