chapa minor fixes
This commit is contained in:
parent
1e39d75239
commit
46d70d7c8c
|
|
@ -253,6 +253,7 @@ CREATE TABLE IF NOT EXISTS wallet_transfer (
|
||||||
cashier_id BIGINT,
|
cashier_id BIGINT,
|
||||||
verified BOOLEAN DEFAULT false,
|
verified BOOLEAN DEFAULT false,
|
||||||
reference_number VARCHAR(255) NOT NULL,
|
reference_number VARCHAR(255) NOT NULL,
|
||||||
|
ext_reference_number VARCHAR(255),
|
||||||
session_id VARCHAR(255),
|
session_id VARCHAR(255),
|
||||||
status VARCHAR(255),
|
status VARCHAR(255),
|
||||||
payment_method VARCHAR(255),
|
payment_method VARCHAR(255),
|
||||||
|
|
|
||||||
|
|
@ -448,6 +448,46 @@ SELECT *
|
||||||
FROM enetpulse_preodds_bettingoffers
|
FROM enetpulse_preodds_bettingoffers
|
||||||
ORDER BY created_at DESC;
|
ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: GetAllEnetpulsePreoddsWithBettingOffers :many
|
||||||
|
SELECT
|
||||||
|
p.id AS preodds_db_id,
|
||||||
|
p.preodds_id,
|
||||||
|
p.event_fk,
|
||||||
|
p.outcome_type_fk,
|
||||||
|
p.outcome_scope_fk,
|
||||||
|
p.outcome_subtype_fk,
|
||||||
|
p.event_participant_number,
|
||||||
|
p.iparam,
|
||||||
|
p.iparam2,
|
||||||
|
p.dparam,
|
||||||
|
p.dparam2,
|
||||||
|
p.sparam,
|
||||||
|
p.updates_count AS preodds_updates_count,
|
||||||
|
p.last_updated_at AS preodds_last_updated_at,
|
||||||
|
p.created_at AS preodds_created_at,
|
||||||
|
p.updated_at AS preodds_updated_at,
|
||||||
|
|
||||||
|
-- Betting offer fields
|
||||||
|
bo.id AS bettingoffer_db_id,
|
||||||
|
bo.bettingoffer_id,
|
||||||
|
bo.preodds_fk, -- ✅ ensure alias matches struct field
|
||||||
|
bo.bettingoffer_status_fk,
|
||||||
|
bo.odds_provider_fk,
|
||||||
|
bo.odds,
|
||||||
|
bo.odds_old,
|
||||||
|
bo.active,
|
||||||
|
bo.coupon_key,
|
||||||
|
bo.updates_count AS bettingoffer_updates_count,
|
||||||
|
bo.last_updated_at AS bettingoffer_last_updated_at,
|
||||||
|
bo.created_at AS bettingoffer_created_at,
|
||||||
|
bo.updated_at AS bettingoffer_updated_at
|
||||||
|
|
||||||
|
FROM enetpulse_preodds p
|
||||||
|
LEFT JOIN enetpulse_preodds_bettingoffers bo
|
||||||
|
ON bo.preodds_fk = p.preodds_id
|
||||||
|
ORDER BY p.created_at DESC, bo.created_at DESC;
|
||||||
|
|
||||||
|
|
||||||
-- name: GetFixturesWithPreodds :many
|
-- name: GetFixturesWithPreodds :many
|
||||||
SELECT
|
SELECT
|
||||||
f.fixture_id AS id,
|
f.fixture_id AS id,
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,12 @@ INSERT INTO wallet_transfer (
|
||||||
cashier_id,
|
cashier_id,
|
||||||
verified,
|
verified,
|
||||||
reference_number,
|
reference_number,
|
||||||
|
ext_reference_number,
|
||||||
session_id,
|
session_id,
|
||||||
status,
|
status,
|
||||||
payment_method
|
payment_method
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
-- name: GetAllTransfers :many
|
-- name: GetAllTransfers :many
|
||||||
SELECT *
|
SELECT *
|
||||||
|
|
|
||||||
|
|
@ -1081,6 +1081,128 @@ func (q *Queries) GetAllEnetpulsePreoddsBettingOffers(ctx context.Context) ([]En
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetAllEnetpulsePreoddsWithBettingOffers = `-- name: GetAllEnetpulsePreoddsWithBettingOffers :many
|
||||||
|
SELECT
|
||||||
|
p.id AS preodds_db_id,
|
||||||
|
p.preodds_id,
|
||||||
|
p.event_fk,
|
||||||
|
p.outcome_type_fk,
|
||||||
|
p.outcome_scope_fk,
|
||||||
|
p.outcome_subtype_fk,
|
||||||
|
p.event_participant_number,
|
||||||
|
p.iparam,
|
||||||
|
p.iparam2,
|
||||||
|
p.dparam,
|
||||||
|
p.dparam2,
|
||||||
|
p.sparam,
|
||||||
|
p.updates_count AS preodds_updates_count,
|
||||||
|
p.last_updated_at AS preodds_last_updated_at,
|
||||||
|
p.created_at AS preodds_created_at,
|
||||||
|
p.updated_at AS preodds_updated_at,
|
||||||
|
|
||||||
|
-- Betting offer fields
|
||||||
|
bo.id AS bettingoffer_db_id,
|
||||||
|
bo.bettingoffer_id,
|
||||||
|
bo.preodds_fk, -- ✅ ensure alias matches struct field
|
||||||
|
bo.bettingoffer_status_fk,
|
||||||
|
bo.odds_provider_fk,
|
||||||
|
bo.odds,
|
||||||
|
bo.odds_old,
|
||||||
|
bo.active,
|
||||||
|
bo.coupon_key,
|
||||||
|
bo.updates_count AS bettingoffer_updates_count,
|
||||||
|
bo.last_updated_at AS bettingoffer_last_updated_at,
|
||||||
|
bo.created_at AS bettingoffer_created_at,
|
||||||
|
bo.updated_at AS bettingoffer_updated_at
|
||||||
|
|
||||||
|
FROM enetpulse_preodds p
|
||||||
|
LEFT JOIN enetpulse_preodds_bettingoffers bo
|
||||||
|
ON bo.preodds_fk = p.preodds_id
|
||||||
|
ORDER BY p.created_at DESC, bo.created_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetAllEnetpulsePreoddsWithBettingOffersRow struct {
|
||||||
|
PreoddsDbID int64 `json:"preodds_db_id"`
|
||||||
|
PreoddsID string `json:"preodds_id"`
|
||||||
|
EventFk int64 `json:"event_fk"`
|
||||||
|
OutcomeTypeFk pgtype.Int4 `json:"outcome_type_fk"`
|
||||||
|
OutcomeScopeFk pgtype.Int4 `json:"outcome_scope_fk"`
|
||||||
|
OutcomeSubtypeFk pgtype.Int4 `json:"outcome_subtype_fk"`
|
||||||
|
EventParticipantNumber pgtype.Int4 `json:"event_participant_number"`
|
||||||
|
Iparam pgtype.Text `json:"iparam"`
|
||||||
|
Iparam2 pgtype.Text `json:"iparam2"`
|
||||||
|
Dparam pgtype.Text `json:"dparam"`
|
||||||
|
Dparam2 pgtype.Text `json:"dparam2"`
|
||||||
|
Sparam pgtype.Text `json:"sparam"`
|
||||||
|
PreoddsUpdatesCount pgtype.Int4 `json:"preodds_updates_count"`
|
||||||
|
PreoddsLastUpdatedAt pgtype.Timestamptz `json:"preodds_last_updated_at"`
|
||||||
|
PreoddsCreatedAt pgtype.Timestamptz `json:"preodds_created_at"`
|
||||||
|
PreoddsUpdatedAt pgtype.Timestamptz `json:"preodds_updated_at"`
|
||||||
|
BettingofferDbID pgtype.Int8 `json:"bettingoffer_db_id"`
|
||||||
|
BettingofferID pgtype.Text `json:"bettingoffer_id"`
|
||||||
|
PreoddsFk pgtype.Text `json:"preodds_fk"`
|
||||||
|
BettingofferStatusFk pgtype.Int4 `json:"bettingoffer_status_fk"`
|
||||||
|
OddsProviderFk pgtype.Int4 `json:"odds_provider_fk"`
|
||||||
|
Odds pgtype.Numeric `json:"odds"`
|
||||||
|
OddsOld pgtype.Numeric `json:"odds_old"`
|
||||||
|
Active pgtype.Bool `json:"active"`
|
||||||
|
CouponKey pgtype.Text `json:"coupon_key"`
|
||||||
|
BettingofferUpdatesCount pgtype.Int4 `json:"bettingoffer_updates_count"`
|
||||||
|
BettingofferLastUpdatedAt pgtype.Timestamptz `json:"bettingoffer_last_updated_at"`
|
||||||
|
BettingofferCreatedAt pgtype.Timestamptz `json:"bettingoffer_created_at"`
|
||||||
|
BettingofferUpdatedAt pgtype.Timestamptz `json:"bettingoffer_updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetAllEnetpulsePreoddsWithBettingOffers(ctx context.Context) ([]GetAllEnetpulsePreoddsWithBettingOffersRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetAllEnetpulsePreoddsWithBettingOffers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetAllEnetpulsePreoddsWithBettingOffersRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetAllEnetpulsePreoddsWithBettingOffersRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.PreoddsDbID,
|
||||||
|
&i.PreoddsID,
|
||||||
|
&i.EventFk,
|
||||||
|
&i.OutcomeTypeFk,
|
||||||
|
&i.OutcomeScopeFk,
|
||||||
|
&i.OutcomeSubtypeFk,
|
||||||
|
&i.EventParticipantNumber,
|
||||||
|
&i.Iparam,
|
||||||
|
&i.Iparam2,
|
||||||
|
&i.Dparam,
|
||||||
|
&i.Dparam2,
|
||||||
|
&i.Sparam,
|
||||||
|
&i.PreoddsUpdatesCount,
|
||||||
|
&i.PreoddsLastUpdatedAt,
|
||||||
|
&i.PreoddsCreatedAt,
|
||||||
|
&i.PreoddsUpdatedAt,
|
||||||
|
&i.BettingofferDbID,
|
||||||
|
&i.BettingofferID,
|
||||||
|
&i.PreoddsFk,
|
||||||
|
&i.BettingofferStatusFk,
|
||||||
|
&i.OddsProviderFk,
|
||||||
|
&i.Odds,
|
||||||
|
&i.OddsOld,
|
||||||
|
&i.Active,
|
||||||
|
&i.CouponKey,
|
||||||
|
&i.BettingofferUpdatesCount,
|
||||||
|
&i.BettingofferLastUpdatedAt,
|
||||||
|
&i.BettingofferCreatedAt,
|
||||||
|
&i.BettingofferUpdatedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const GetAllEnetpulseResults = `-- name: GetAllEnetpulseResults :many
|
const GetAllEnetpulseResults = `-- name: GetAllEnetpulseResults :many
|
||||||
SELECT id, result_id, name, sport_fk, tournament_fk, tournament_template_fk, tournament_name, tournament_template_name, sport_name, start_date, status_type, status_desc_fk, round_type_fk, updates_count, last_updated_at, round, live, venue_name, livestats_plus, livestats_type, commentary, lineup_confirmed, verified, spectators, game_started, first_half_ended, second_half_started, second_half_ended, game_ended, created_at, updated_at
|
SELECT id, result_id, name, sport_fk, tournament_fk, tournament_template_fk, tournament_name, tournament_template_name, sport_name, start_date, status_type, status_desc_fk, round_type_fk, updates_count, last_updated_at, round, live, venue_name, livestats_plus, livestats_type, commentary, lineup_confirmed, verified, spectators, game_started, first_half_ended, second_half_started, second_half_ended, game_ended, created_at, updated_at
|
||||||
FROM enetpulse_results
|
FROM enetpulse_results
|
||||||
|
|
|
||||||
|
|
@ -1147,6 +1147,7 @@ type WalletTransfer struct {
|
||||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||||
Verified pgtype.Bool `json:"verified"`
|
Verified pgtype.Bool `json:"verified"`
|
||||||
ReferenceNumber string `json:"reference_number"`
|
ReferenceNumber string `json:"reference_number"`
|
||||||
|
ExtReferenceNumber pgtype.Text `json:"ext_reference_number"`
|
||||||
SessionID pgtype.Text `json:"session_id"`
|
SessionID pgtype.Text `json:"session_id"`
|
||||||
Status pgtype.Text `json:"status"`
|
Status pgtype.Text `json:"status"`
|
||||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||||
|
|
@ -1164,6 +1165,7 @@ type WalletTransferDetail struct {
|
||||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||||
Verified pgtype.Bool `json:"verified"`
|
Verified pgtype.Bool `json:"verified"`
|
||||||
ReferenceNumber string `json:"reference_number"`
|
ReferenceNumber string `json:"reference_number"`
|
||||||
|
ExtReferenceNumber pgtype.Text `json:"ext_reference_number"`
|
||||||
SessionID pgtype.Text `json:"session_id"`
|
SessionID pgtype.Text `json:"session_id"`
|
||||||
Status pgtype.Text `json:"status"`
|
Status pgtype.Text `json:"status"`
|
||||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,13 @@ INSERT INTO wallet_transfer (
|
||||||
cashier_id,
|
cashier_id,
|
||||||
verified,
|
verified,
|
||||||
reference_number,
|
reference_number,
|
||||||
|
ext_reference_number,
|
||||||
session_id,
|
session_id,
|
||||||
status,
|
status,
|
||||||
payment_method
|
payment_method
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||||
RETURNING id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at
|
RETURNING id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, ext_reference_number, session_id, status, payment_method, created_at, updated_at
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateTransferParams struct {
|
type CreateTransferParams struct {
|
||||||
|
|
@ -38,6 +39,7 @@ type CreateTransferParams struct {
|
||||||
CashierID pgtype.Int8 `json:"cashier_id"`
|
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||||
Verified pgtype.Bool `json:"verified"`
|
Verified pgtype.Bool `json:"verified"`
|
||||||
ReferenceNumber string `json:"reference_number"`
|
ReferenceNumber string `json:"reference_number"`
|
||||||
|
ExtReferenceNumber pgtype.Text `json:"ext_reference_number"`
|
||||||
SessionID pgtype.Text `json:"session_id"`
|
SessionID pgtype.Text `json:"session_id"`
|
||||||
Status pgtype.Text `json:"status"`
|
Status pgtype.Text `json:"status"`
|
||||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||||
|
|
@ -53,6 +55,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
||||||
arg.CashierID,
|
arg.CashierID,
|
||||||
arg.Verified,
|
arg.Verified,
|
||||||
arg.ReferenceNumber,
|
arg.ReferenceNumber,
|
||||||
|
arg.ExtReferenceNumber,
|
||||||
arg.SessionID,
|
arg.SessionID,
|
||||||
arg.Status,
|
arg.Status,
|
||||||
arg.PaymentMethod,
|
arg.PaymentMethod,
|
||||||
|
|
@ -68,6 +71,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.ExtReferenceNumber,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
|
|
@ -78,7 +82,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetAllTransfers = `-- name: GetAllTransfers :many
|
const GetAllTransfers = `-- name: GetAllTransfers :many
|
||||||
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, ext_reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
||||||
FROM wallet_transfer_details
|
FROM wallet_transfer_details
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -101,6 +105,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransferDetail,
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.ExtReferenceNumber,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
|
|
@ -121,7 +126,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransferDetail,
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetTransferByID = `-- name: GetTransferByID :one
|
const GetTransferByID = `-- name: GetTransferByID :one
|
||||||
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, ext_reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
||||||
FROM wallet_transfer_details
|
FROM wallet_transfer_details
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
@ -139,6 +144,7 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.ExtReferenceNumber,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
|
|
@ -152,7 +158,7 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetTransferByReference = `-- name: GetTransferByReference :one
|
const GetTransferByReference = `-- name: GetTransferByReference :one
|
||||||
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, ext_reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
||||||
FROM wallet_transfer_details
|
FROM wallet_transfer_details
|
||||||
WHERE reference_number = $1
|
WHERE reference_number = $1
|
||||||
`
|
`
|
||||||
|
|
@ -170,6 +176,7 @@ func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber st
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.ExtReferenceNumber,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
|
|
@ -217,7 +224,7 @@ func (q *Queries) GetTransferStats(ctx context.Context, senderWalletID pgtype.In
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetTransfersByWallet = `-- name: GetTransfersByWallet :many
|
const GetTransfersByWallet = `-- name: GetTransfersByWallet :many
|
||||||
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
SELECT id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, ext_reference_number, session_id, status, payment_method, created_at, updated_at, first_name, last_name, phone_number
|
||||||
FROM wallet_transfer_details
|
FROM wallet_transfer_details
|
||||||
WHERE receiver_wallet_id = $1
|
WHERE receiver_wallet_id = $1
|
||||||
OR sender_wallet_id = $1
|
OR sender_wallet_id = $1
|
||||||
|
|
@ -242,6 +249,7 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID pgt
|
||||||
&i.CashierID,
|
&i.CashierID,
|
||||||
&i.Verified,
|
&i.Verified,
|
||||||
&i.ReferenceNumber,
|
&i.ReferenceNumber,
|
||||||
|
&i.ExtReferenceNumber,
|
||||||
&i.SessionID,
|
&i.SessionID,
|
||||||
&i.Status,
|
&i.Status,
|
||||||
&i.PaymentMethod,
|
&i.PaymentMethod,
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,7 @@ type Config struct {
|
||||||
CHAPA_ENCRYPTION_KEY string
|
CHAPA_ENCRYPTION_KEY string
|
||||||
CHAPA_CALLBACK_URL string
|
CHAPA_CALLBACK_URL string
|
||||||
CHAPA_RETURN_URL string
|
CHAPA_RETURN_URL string
|
||||||
|
CHAPA_RECEIPT_URL string
|
||||||
Bet365Token string
|
Bet365Token string
|
||||||
EnetPulseConfig EnetPulseConfig
|
EnetPulseConfig EnetPulseConfig
|
||||||
PopOK domain.PopOKConfig
|
PopOK domain.PopOKConfig
|
||||||
|
|
@ -262,6 +263,7 @@ func (c *Config) loadEnv() error {
|
||||||
c.CHAPA_PUBLIC_KEY = os.Getenv("CHAPA_PUBLIC_KEY")
|
c.CHAPA_PUBLIC_KEY = os.Getenv("CHAPA_PUBLIC_KEY")
|
||||||
c.CHAPA_ENCRYPTION_KEY = os.Getenv("CHAPA_ENCRYPTION_KEY")
|
c.CHAPA_ENCRYPTION_KEY = os.Getenv("CHAPA_ENCRYPTION_KEY")
|
||||||
c.CHAPA_BASE_URL = os.Getenv("CHAPA_BASE_URL")
|
c.CHAPA_BASE_URL = os.Getenv("CHAPA_BASE_URL")
|
||||||
|
c.CHAPA_RECEIPT_URL = os.Getenv("CHAPA_RECEIPT_URL")
|
||||||
if c.CHAPA_BASE_URL == "" {
|
if c.CHAPA_BASE_URL == "" {
|
||||||
c.CHAPA_BASE_URL = "https://api.chapa.co/v1"
|
c.CHAPA_BASE_URL = "https://api.chapa.co/v1"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ const (
|
||||||
PaymentStatusFailed PaymentStatus = "failed"
|
PaymentStatusFailed PaymentStatus = "failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChapaDepositRequest struct {
|
type ChapaInitDepositRequest struct {
|
||||||
Amount Currency `json:"amount"`
|
Amount Currency `json:"amount"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
|
@ -42,6 +42,8 @@ type ChapaDepositRequest struct {
|
||||||
TxRef string `json:"tx_ref"`
|
TxRef string `json:"tx_ref"`
|
||||||
CallbackURL string `json:"callback_url"`
|
CallbackURL string `json:"callback_url"`
|
||||||
ReturnURL string `json:"return_url"`
|
ReturnURL string `json:"return_url"`
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
|
// PhoneNumber string `json:"phone_number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaDepositRequestPayload struct {
|
type ChapaDepositRequestPayload struct {
|
||||||
|
|
@ -49,9 +51,15 @@ type ChapaDepositRequestPayload struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaWebhookPayload struct {
|
type ChapaWebhookPayload struct {
|
||||||
TxRef string `json:"tx_ref"`
|
TxRef string `json:"trx_ref"`
|
||||||
Amount Currency `json:"amount"`
|
Amount Currency `json:"amount"`
|
||||||
Currency string `json:"currency"`
|
// Currency string `json:"currency"`
|
||||||
|
Status PaymentStatus `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaPaymentWebhookRequest struct {
|
||||||
|
TxRef string `json:"trx_ref"`
|
||||||
|
RefId string `json:"ref_id"`
|
||||||
Status PaymentStatus `json:"status"`
|
Status PaymentStatus `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,10 +77,91 @@ type ChapaDepositVerification struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaVerificationResponse struct {
|
type ChapaVerificationResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Amount float64 `json:"amount"`
|
Data struct {
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Charge float64 `json:"charge"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Reference string `json:"reference"`
|
||||||
TxRef string `json:"tx_ref"`
|
TxRef string `json:"tx_ref"`
|
||||||
|
Customization struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Logo interface{} `json:"logo"`
|
||||||
|
} `json:"customization"`
|
||||||
|
Meta interface{} `json:"meta"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaAllTransactionsResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
Transactions []struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
RefID string `json:"ref_id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Charge string `json:"charge"`
|
||||||
|
TransID *string `json:"trans_id"`
|
||||||
|
PaymentMethod string `json:"payment_method"`
|
||||||
|
Customer struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Email *string `json:"email"`
|
||||||
|
FirstName *string `json:"first_name"`
|
||||||
|
LastName *string `json:"last_name"`
|
||||||
|
Mobile *string `json:"mobile"`
|
||||||
|
} `json:"customer"`
|
||||||
|
} `json:"transactions"`
|
||||||
|
Pagination struct {
|
||||||
|
PerPage int `json:"per_page"`
|
||||||
|
CurrentPage int `json:"current_page"`
|
||||||
|
FirstPageURL string `json:"first_page_url"`
|
||||||
|
NextPageURL *string `json:"next_page_url"`
|
||||||
|
PrevPageURL *string `json:"prev_page_url"`
|
||||||
|
} `json:"pagination"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaTransactionEvent struct {
|
||||||
|
Item int64 `json:"item"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaTransaction struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
RefID string `json:"ref_id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Charge string `json:"charge"`
|
||||||
|
TransID *string `json:"trans_id"`
|
||||||
|
PaymentMethod string `json:"payment_method"`
|
||||||
|
Customer ChapaCustomer `json:"customer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaCustomer struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Email *string `json:"email"`
|
||||||
|
FirstName *string `json:"first_name"`
|
||||||
|
LastName *string `json:"last_name"`
|
||||||
|
Mobile *string `json:"mobile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// type Bank struct {
|
// type Bank struct {
|
||||||
|
|
@ -221,3 +310,13 @@ type SwapResponse struct {
|
||||||
CreatedAt string `json:"created_at"`
|
CreatedAt string `json:"created_at"`
|
||||||
UpdatedAt string `json:"updated_at"`
|
UpdatedAt string `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChapaCancelResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -703,6 +703,7 @@ type EnetpulsePreodds struct {
|
||||||
LastUpdatedAt time.Time
|
LastUpdatedAt time.Time
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
|
BettingOffers []EnetpulsePreoddsBettingOffer
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnetpulseResultParticipant struct {
|
type EnetpulseResultParticipant struct {
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,71 @@ func (s *Store) GetAllEnetpulsePreoddsBettingOffers(ctx context.Context) ([]doma
|
||||||
return offers, nil
|
return offers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetAllEnetpulsePreoddsWithBettingOffers(ctx context.Context) ([]domain.EnetpulsePreodds, error) {
|
||||||
|
rows, err := s.queries.GetAllEnetpulsePreoddsWithBettingOffers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch preodds with betting offers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map for grouping betting offers under each Preodd
|
||||||
|
preoddsMap := make(map[string]*domain.EnetpulsePreodds)
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
pid := row.PreoddsID
|
||||||
|
preodd, exists := preoddsMap[pid]
|
||||||
|
if !exists {
|
||||||
|
// Create the base Preodd entry
|
||||||
|
preodd = &domain.EnetpulsePreodds{
|
||||||
|
ID: row.PreoddsDbID,
|
||||||
|
PreoddsID: row.PreoddsID,
|
||||||
|
EventFK: row.EventFk,
|
||||||
|
OutcomeTypeFK: row.OutcomeTypeFk.Int32,
|
||||||
|
OutcomeScopeFK: row.OutcomeScopeFk.Int32,
|
||||||
|
OutcomeSubtypeFK: row.OutcomeSubtypeFk.Int32,
|
||||||
|
EventParticipantNumber: row.EventParticipantNumber.Int32,
|
||||||
|
IParam: row.Iparam.String,
|
||||||
|
IParam2: row.Iparam2.String,
|
||||||
|
DParam: row.Dparam.String,
|
||||||
|
DParam2: row.Dparam2.String,
|
||||||
|
SParam: row.Sparam.String,
|
||||||
|
UpdatesCount: row.PreoddsUpdatesCount.Int32,
|
||||||
|
LastUpdatedAt: row.PreoddsLastUpdatedAt.Time,
|
||||||
|
CreatedAt: row.PreoddsCreatedAt.Time,
|
||||||
|
UpdatedAt: row.PreoddsUpdatedAt.Time,
|
||||||
|
BettingOffers: []domain.EnetpulsePreoddsBettingOffer{},
|
||||||
|
}
|
||||||
|
preoddsMap[pid] = preodd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append BettingOffer only if exists
|
||||||
|
if row.BettingofferID.Valid && row.BettingofferID.String != "" {
|
||||||
|
offer := domain.EnetpulsePreoddsBettingOffer{
|
||||||
|
ID: row.BettingofferDbID.Int64,
|
||||||
|
BettingOfferID: row.BettingofferID.String,
|
||||||
|
BettingOfferStatusFK: row.BettingofferStatusFk.Int32,
|
||||||
|
OddsProviderFK: row.OddsProviderFk.Int32,
|
||||||
|
Odds: float64(row.Odds.Exp),
|
||||||
|
OddsOld: float64(row.OddsOld.Exp),
|
||||||
|
Active: fmt.Sprintf("%v", row.Active),
|
||||||
|
CouponKey: row.CouponKey.String,
|
||||||
|
UpdatesCount: int(row.BettingofferUpdatesCount.Int32),
|
||||||
|
LastUpdatedAt: row.BettingofferLastUpdatedAt.Time,
|
||||||
|
CreatedAt: row.BettingofferCreatedAt.Time,
|
||||||
|
UpdatedAt: row.BettingofferUpdatedAt.Time,
|
||||||
|
}
|
||||||
|
preodd.BettingOffers = append(preodd.BettingOffers, offer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert map to slice
|
||||||
|
result := make([]domain.EnetpulsePreodds, 0, len(preoddsMap))
|
||||||
|
for _, p := range preoddsMap {
|
||||||
|
result = append(result, *p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) GetFixturesWithPreodds(ctx context.Context) ([]domain.EnetpulseFixtureWithPreodds, error) {
|
func (s *Store) GetFixturesWithPreodds(ctx context.Context) ([]domain.EnetpulseFixtureWithPreodds, error) {
|
||||||
dbRows, err := s.queries.GetFixturesWithPreodds(ctx)
|
dbRows, err := s.queries.GetFixturesWithPreodds(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -893,6 +893,7 @@ func (s *Service) GetBetOutcomeByBetID(ctx context.Context, UserID int64) ([]dom
|
||||||
func (s *Service) GetBetOutcomeViewByEventID(ctx context.Context, eventID int64, filter domain.BetOutcomeViewFilter) ([]domain.BetOutcomeViewRes, int64, error) {
|
func (s *Service) GetBetOutcomeViewByEventID(ctx context.Context, eventID int64, filter domain.BetOutcomeViewFilter) ([]domain.BetOutcomeViewRes, int64, error) {
|
||||||
return s.betStore.GetBetOutcomeViewByEventID(ctx, eventID, filter)
|
return s.betStore.GetBetOutcomeViewByEventID(ctx, eventID, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) {
|
func (s *Service) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) {
|
||||||
return s.betStore.GetBetOutcomeByEventID(ctx, eventID, is_filtered)
|
return s.betStore.GetBetOutcomeByEventID(ctx, eventID, is_filtered)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,16 +29,17 @@ func NewClient(baseURL, secretKey string) *Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error) {
|
func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaInitDepositRequest) (domain.ChapaDepositResponse, error) {
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"amount": fmt.Sprintf("%.2f", float64(req.Amount)),
|
"amount": fmt.Sprintf("%.2f", float64(req.Amount)),
|
||||||
"currency": req.Currency,
|
"currency": req.Currency,
|
||||||
// "email": req.Email,
|
"email": req.Email,
|
||||||
"first_name": req.FirstName,
|
"first_name": req.FirstName,
|
||||||
"last_name": req.LastName,
|
"last_name": req.LastName,
|
||||||
"tx_ref": req.TxRef,
|
"tx_ref": req.TxRef,
|
||||||
"callback_url": req.CallbackURL,
|
"callback_url": req.CallbackURL,
|
||||||
"return_url": req.ReturnURL,
|
"return_url": req.ReturnURL,
|
||||||
|
"phone_number": req.PhoneNumber,
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n\nChapa Payload: %+v\n\n", payload)
|
fmt.Printf("\n\nChapa Payload: %+v\n\n", payload)
|
||||||
|
|
@ -69,9 +70,9 @@ func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaDepositR
|
||||||
return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body)) // <-- Log it
|
return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body)) // <-- Log it
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
// if resp.StatusCode != http.StatusOK {
|
||||||
return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
// return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
}
|
// }
|
||||||
|
|
||||||
var response struct {
|
var response struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
|
|
@ -133,12 +134,13 @@ func (c *Client) VerifyPayment(ctx context.Context, reference string) (domain.Ch
|
||||||
func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
||||||
url := fmt.Sprintf("%s/transaction/verify/%s", c.baseURL, txRef)
|
url := fmt.Sprintf("%s/transaction/verify/%s", c.baseURL, txRef)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -147,32 +149,24 @@ func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
var response struct {
|
var verification domain.ChapaVerificationResponse
|
||||||
Status string `json:"status"`
|
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
||||||
Amount float64 `json:"amount"`
|
|
||||||
Currency string `json:"currency"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var status domain.PaymentStatus
|
// Normalize payment status for internal use
|
||||||
switch response.Status {
|
// switch strings.ToLower(verification.Data.Status) {
|
||||||
case "success":
|
// case "success":
|
||||||
status = domain.PaymentStatusCompleted
|
// verification.Status = string(domain.PaymentStatusCompleted)
|
||||||
default:
|
// default:
|
||||||
status = domain.PaymentStatusFailed
|
// verification.Status = string(domain.PaymentStatusFailed)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return &domain.ChapaVerificationResponse{
|
return &verification, nil
|
||||||
Status: string(status),
|
|
||||||
Amount: response.Amount,
|
|
||||||
Currency: response.Currency,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ManualVerifyTransfer(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
func (c *Client) ManualVerifyTransfer(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
||||||
|
|
@ -215,11 +209,74 @@ func (c *Client) ManualVerifyTransfer(ctx context.Context, txRef string) (*domai
|
||||||
|
|
||||||
return &domain.ChapaVerificationResponse{
|
return &domain.ChapaVerificationResponse{
|
||||||
Status: string(status),
|
Status: string(status),
|
||||||
Amount: response.Amount,
|
// Amount: response.Amount,
|
||||||
Currency: response.Currency,
|
// Currency: response.Currency,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetAllTransactions(ctx context.Context) (domain.ChapaAllTransactionsResponse, error) {
|
||||||
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/transactions", nil)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaAllTransactionsResponse{}, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaAllTransactionsResponse{}, fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return domain.ChapaAllTransactionsResponse{}, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var response domain.ChapaAllTransactionsResponse
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||||
|
return domain.ChapaAllTransactionsResponse{}, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetTransactionEvents(ctx context.Context, refId string) ([]domain.ChapaTransactionEvent, error) {
|
||||||
|
url := fmt.Sprintf("%s/transaction/events/%s", c.baseURL, refId)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := c.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var response struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data []domain.ChapaTransactionEvent `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/banks", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/banks", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -336,6 +393,62 @@ func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.
|
||||||
return &verification, nil
|
return &verification, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) CancelTransaction(ctx context.Context, txRef string) (domain.ChapaCancelResponse, error) {
|
||||||
|
// Construct URL for the cancel transaction endpoint
|
||||||
|
url := fmt.Sprintf("%s/transaction/cancel/%s", c.baseURL, txRef)
|
||||||
|
|
||||||
|
// Create HTTP request with context
|
||||||
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPut, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set authorization header
|
||||||
|
httpReq.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Execute the HTTP request
|
||||||
|
resp, err := c.httpClient.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Handle non-OK responses
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode successful response
|
||||||
|
var response struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return mapped domain response
|
||||||
|
return domain.ChapaCancelResponse{
|
||||||
|
Message: response.Message,
|
||||||
|
Status: response.Status,
|
||||||
|
TxRef: response.Data.TxRef,
|
||||||
|
Amount: response.Data.Amount,
|
||||||
|
Currency: response.Data.Currency,
|
||||||
|
CreatedAt: response.Data.CreatedAt,
|
||||||
|
UpdatedAt: response.Data.UpdatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) setHeaders(req *http.Request) {
|
func (c *Client) setHeaders(req *http.Request) {
|
||||||
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
// }
|
// }
|
||||||
|
|
||||||
type ChapaStore interface {
|
type ChapaStore interface {
|
||||||
InitializePayment(request domain.ChapaDepositRequest) (domain.ChapaDepositResponse, error)
|
InitializePayment(request domain.ChapaInitDepositRequest) (domain.ChapaDepositResponse, error)
|
||||||
ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error)
|
ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error)
|
||||||
FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
||||||
CreateWithdrawal(userID string, amount float64, accountNumber, bankCode string) (*domain.ChapaWithdrawal, error)
|
CreateWithdrawal(userID string, amount float64, accountNumber, bankCode string) (*domain.ChapaWithdrawal, error)
|
||||||
|
|
|
||||||
|
|
@ -56,22 +56,22 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
return "", fmt.Errorf("failed to get user: %w", err)
|
return "", fmt.Errorf("failed to get user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var senderWallet domain.Wallet
|
// var senderWallet domain.Wallet
|
||||||
|
|
||||||
// Generate unique reference
|
// Generate unique reference
|
||||||
// reference := uuid.New().String()
|
// reference := uuid.New().String()
|
||||||
reference := fmt.Sprintf("chapa-deposit-%d-%s", userID, uuid.New().String())
|
reference := fmt.Sprintf("chapa-deposit-%d-%s", userID, uuid.New().String())
|
||||||
|
|
||||||
senderWallets, err := s.walletStore.GetWalletsByUser(ctx, userID)
|
senderWallet, err := s.walletStore.GetCustomerWallet(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get sender wallets: %w", err)
|
return "", fmt.Errorf("failed to get sender wallet: %w", err)
|
||||||
}
|
|
||||||
for _, wallet := range senderWallets {
|
|
||||||
if wallet.IsTransferable {
|
|
||||||
senderWallet = wallet
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// for _, wallet := range senderWallets {
|
||||||
|
// if wallet.IsTransferable {
|
||||||
|
// senderWallet = wallet
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Check if payment with this reference already exists
|
// Check if payment with this reference already exists
|
||||||
// if transfer, err := s.transferStore.GetTransferByReference(ctx, reference); err == nil {
|
// if transfer, err := s.transferStore.GetTransferByReference(ctx, reference); err == nil {
|
||||||
|
|
@ -92,9 +92,16 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
Verified: false,
|
Verified: false,
|
||||||
|
Status: string(domain.STATUS_PENDING),
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := domain.ChapaDepositRequest{
|
userPhoneNum := user.PhoneNumber[len(user.PhoneNumber)-9:]
|
||||||
|
|
||||||
|
if len(user.PhoneNumber) >= 9 {
|
||||||
|
userPhoneNum = "0" + userPhoneNum
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := domain.ChapaInitDepositRequest{
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
Currency: "ETB",
|
Currency: "ETB",
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
|
|
@ -103,6 +110,7 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
TxRef: reference,
|
TxRef: reference,
|
||||||
CallbackURL: s.cfg.CHAPA_CALLBACK_URL,
|
CallbackURL: s.cfg.CHAPA_CALLBACK_URL,
|
||||||
ReturnURL: s.cfg.CHAPA_RETURN_URL,
|
ReturnURL: s.cfg.CHAPA_RETURN_URL,
|
||||||
|
PhoneNumber: userPhoneNum,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize payment with Chapa
|
// Initialize payment with Chapa
|
||||||
|
|
@ -127,6 +135,157 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
return response.CheckoutURL, nil
|
return response.CheckoutURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) ProcessVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaPaymentWebhookRequest) error {
|
||||||
|
// Find payment by reference
|
||||||
|
payment, err := s.transferStore.GetTransferByReference(ctx, transfer.TxRef)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ErrPaymentNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
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 payment is completed, credit user's wallet
|
||||||
|
if transfer.Status == domain.PaymentStatusSuccessful {
|
||||||
|
|
||||||
|
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
||||||
|
return fmt.Errorf("failed to update is payment verified value: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.transferStore.UpdateTransferStatus(ctx, payment.ID, string(domain.DepositStatusCompleted)); err != nil {
|
||||||
|
return fmt.Errorf("failed to update payment status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.walletStore.AddToWallet(ctx, payment.SenderWalletID.Value, payment.Amount, domain.ValidInt64{}, domain.TRANSFER_CHAPA, domain.PaymentDetails{
|
||||||
|
ReferenceNumber: domain.ValidString{
|
||||||
|
Value: transfer.TxRef,
|
||||||
|
},
|
||||||
|
}, fmt.Sprintf("Added %v to wallet using Chapa", payment.Amount)); err != nil {
|
||||||
|
return fmt.Errorf("failed to credit user wallet: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CancelDeposit(ctx context.Context, userID int64, txRef string) (domain.ChapaCancelResponse, error) {
|
||||||
|
// Validate input
|
||||||
|
if txRef == "" {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("transaction reference is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve user to verify ownership / context (optional but good practice)
|
||||||
|
user, err := s.userStore.GetUserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("failed to get user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\nAttempting to cancel Chapa transaction: %s for user %s (%d)\n\n", txRef, user.Email, userID)
|
||||||
|
|
||||||
|
// Call Chapa API to cancel transaction
|
||||||
|
cancelResp, err := s.chapaClient.CancelTransaction(ctx, txRef)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ChapaCancelResponse{}, fmt.Errorf("failed to cancel transaction via Chapa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update transfer/payment status locally
|
||||||
|
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
||||||
|
if err != nil {
|
||||||
|
// Log but do not block cancellation if remote succeeded
|
||||||
|
fmt.Printf("Warning: unable to find local transfer for txRef %s: %v\n", txRef, err)
|
||||||
|
} else {
|
||||||
|
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.STATUS_CANCELLED)); err != nil {
|
||||||
|
fmt.Printf("Warning: failed to update transfer status for txRef %s: %v\n", txRef, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, false); err != nil {
|
||||||
|
fmt.Printf("Warning: failed to update transfer status for txRef %s: %v\n", txRef, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\nChapa cancellation response: %+v\n\n", cancelResp)
|
||||||
|
|
||||||
|
return cancelResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) FetchAllTransactions(ctx context.Context) ([]domain.ChapaTransaction, error) {
|
||||||
|
// Call Chapa API to get all transactions
|
||||||
|
resp, err := s.chapaClient.GetAllTransactions(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch transactions from Chapa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Status != "success" {
|
||||||
|
return nil, fmt.Errorf("chapa API returned non-success status: %s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions := make([]domain.ChapaTransaction, 0, len(resp.Data.Transactions))
|
||||||
|
|
||||||
|
// Map API transactions to domain transactions
|
||||||
|
for _, t := range resp.Data.Transactions {
|
||||||
|
tx := domain.ChapaTransaction{
|
||||||
|
Status: t.Status,
|
||||||
|
RefID: t.RefID,
|
||||||
|
Type: t.Type,
|
||||||
|
CreatedAt: t.CreatedAt,
|
||||||
|
Currency: t.Currency,
|
||||||
|
Amount: t.Amount,
|
||||||
|
Charge: t.Charge,
|
||||||
|
TransID: t.TransID,
|
||||||
|
PaymentMethod: t.PaymentMethod,
|
||||||
|
Customer: domain.ChapaCustomer{
|
||||||
|
ID: t.Customer.ID,
|
||||||
|
Email: t.Customer.Email,
|
||||||
|
FirstName: t.Customer.FirstName,
|
||||||
|
LastName: t.Customer.LastName,
|
||||||
|
Mobile: t.Customer.Mobile,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
transactions = append(transactions, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) FetchTransactionEvents(ctx context.Context, refID string) ([]domain.ChapaTransactionEvent, error) {
|
||||||
|
if refID == "" {
|
||||||
|
return nil, fmt.Errorf("transaction reference ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Chapa client to fetch transaction events
|
||||||
|
events, err := s.chapaClient.GetTransactionEvents(ctx, refID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch transaction events from Chapa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Transform or filter events if needed
|
||||||
|
transformedEvents := make([]domain.ChapaTransactionEvent, 0, len(events))
|
||||||
|
for _, e := range events {
|
||||||
|
transformedEvents = append(transformedEvents, domain.ChapaTransactionEvent{
|
||||||
|
Item: e.Item,
|
||||||
|
Message: e.Message,
|
||||||
|
Type: e.Type,
|
||||||
|
CreatedAt: e.CreatedAt,
|
||||||
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedEvents, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req domain.ChapaWithdrawalRequest) (*domain.Transfer, error) {
|
func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req domain.ChapaWithdrawalRequest) (*domain.Transfer, error) {
|
||||||
// Parse and validate amount
|
// Parse and validate amount
|
||||||
amount, err := strconv.ParseInt(req.Amount, 10, 64)
|
amount, err := strconv.ParseInt(req.Amount, 10, 64)
|
||||||
|
|
@ -213,124 +372,7 @@ func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req doma
|
||||||
return &transfer, nil
|
return &transfer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
func (s *Service) ProcessVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) 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) ManuallyVerify(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
|
||||||
// Lookup transfer by reference
|
|
||||||
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("transfer not found for reference %s: %w", txRef, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if transfer.Verified {
|
|
||||||
return &domain.ChapaVerificationResponse{
|
|
||||||
Status: string(domain.PaymentStatusCompleted),
|
|
||||||
Amount: float64(transfer.Amount) / 100,
|
|
||||||
Currency: "ETB",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate sender wallet
|
|
||||||
if !transfer.SenderWalletID.Valid {
|
|
||||||
return nil, fmt.Errorf("invalid sender wallet ID: %v", transfer.SenderWalletID)
|
|
||||||
}
|
|
||||||
|
|
||||||
var verification *domain.ChapaVerificationResponse
|
|
||||||
|
|
||||||
// Decide verification method based on type
|
|
||||||
switch strings.ToLower(string(transfer.Type)) {
|
|
||||||
case "deposit":
|
|
||||||
// Use Chapa Payment Verification
|
|
||||||
verification, err = s.chapaClient.ManualVerifyPayment(ctx, txRef)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to verify deposit with Chapa: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if verification.Status == string(domain.PaymentStatusSuccessful) {
|
|
||||||
// Mark verified
|
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to mark deposit transfer as verified: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Credit wallet
|
|
||||||
_, err := s.walletStore.AddToWallet(ctx, transfer.SenderWalletID.Value,
|
|
||||||
transfer.Amount, domain.ValidInt64{}, domain.TRANSFER_CHAPA, domain.PaymentDetails{},
|
|
||||||
fmt.Sprintf("Added %v to wallet using Chapa", transfer.Amount.Float32()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "withdraw":
|
|
||||||
// Use Chapa Transfer Verification
|
|
||||||
verification, err = s.chapaClient.ManualVerifyTransfer(ctx, txRef)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to verify withdrawal with Chapa: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if verification.Status == string(domain.PaymentStatusSuccessful) {
|
|
||||||
// Mark verified (withdraw doesn't affect balance)
|
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to mark withdrawal transfer as verified: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported transfer type: %s", transfer.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return verification, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 domain.ErrPaymentNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
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 payment is completed, credit user's wallet
|
|
||||||
if transfer.Status == string(domain.PaymentStatusSuccessful) {
|
|
||||||
|
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
|
||||||
return fmt.Errorf("failed to update payment status: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := s.walletStore.AddToWallet(ctx, payment.SenderWalletID.Value, payment.Amount, domain.ValidInt64{}, domain.TRANSFER_CHAPA, domain.PaymentDetails{
|
|
||||||
ReferenceNumber: domain.ValidString{
|
|
||||||
Value: transfer.Reference,
|
|
||||||
},
|
|
||||||
}, fmt.Sprintf("Added %v to wallet using Chapa", 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
|
// Find payment by reference
|
||||||
transfer, err := s.transferStore.GetTransferByReference(ctx, payment.Reference)
|
transfer, err := s.transferStore.GetTransferByReference(ctx, payment.Reference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -369,15 +411,111 @@ func (s *Service) HandleVerifyWithdrawWebhook(ctx context.Context, payment domai
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetPaymentReceiptURL(ctx context.Context, chapaRef string) (string, error) {
|
func (s *Service) GetPaymentReceiptURL(refId string) (string, error) {
|
||||||
if chapaRef == "" {
|
if refId == "" {
|
||||||
return "", fmt.Errorf("chapa reference ID is required")
|
return "", fmt.Errorf("reference ID cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
receiptURL := fmt.Sprintf("https://chapa.link/payment-receipt/%s", chapaRef)
|
receiptURL := s.cfg.CHAPA_RECEIPT_URL + refId
|
||||||
return receiptURL, nil
|
return receiptURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
||||||
|
banks, err := s.chapaClient.FetchSupportedBanks(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch banks: %w", err)
|
||||||
|
}
|
||||||
|
return banks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
||||||
|
// Lookup transfer by reference
|
||||||
|
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("transfer not found for reference %s: %w", txRef, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already verified, just return a completed response
|
||||||
|
if transfer.Verified {
|
||||||
|
return &domain.ChapaVerificationResponse{}, errors.New("transfer already verified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate sender wallet
|
||||||
|
if !transfer.SenderWalletID.Valid {
|
||||||
|
return nil, fmt.Errorf("invalid sender wallet ID: %v", transfer.SenderWalletID)
|
||||||
|
}
|
||||||
|
|
||||||
|
var verification *domain.ChapaVerificationResponse
|
||||||
|
|
||||||
|
switch strings.ToLower(string(transfer.Type)) {
|
||||||
|
case string(domain.DEPOSIT):
|
||||||
|
// Verify Chapa payment
|
||||||
|
verification, err = s.chapaClient.ManualVerifyPayment(ctx, txRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to verify deposit with Chapa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(verification.Data.Status) == "success" ||
|
||||||
|
verification.Status == string(domain.PaymentStatusCompleted) {
|
||||||
|
|
||||||
|
// Credit wallet
|
||||||
|
_, err := s.walletStore.AddToWallet(ctx,
|
||||||
|
transfer.SenderWalletID.Value,
|
||||||
|
transfer.Amount,
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_CHAPA,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
fmt.Sprintf("Added %.2f ETB to wallet using Chapa", transfer.Amount.Float32()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to credit wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark verified in DB
|
||||||
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to mark deposit transfer as verified: %w", err)
|
||||||
|
}
|
||||||
|
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.DepositStatusCompleted)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update deposit transfer status: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case string(domain.WITHDRAW):
|
||||||
|
// Verify Chapa transfer
|
||||||
|
verification, err = s.chapaClient.ManualVerifyTransfer(ctx, txRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to verify withdrawal with Chapa: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(verification.Data.Status) == "success" ||
|
||||||
|
verification.Status == string(domain.PaymentStatusCompleted) {
|
||||||
|
|
||||||
|
// Deduct wallet
|
||||||
|
_, err := s.walletStore.DeductFromWallet(ctx,
|
||||||
|
transfer.SenderWalletID.Value,
|
||||||
|
transfer.Amount,
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_CHAPA,
|
||||||
|
fmt.Sprintf("Deducted %.2f ETB from wallet using Chapa", transfer.Amount.Float32()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to debit wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark verified in DB
|
||||||
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to mark withdraw transfer as verified: %w", err)
|
||||||
|
}
|
||||||
|
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusCompleted)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update withdraw transfer status: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported transfer type: %s", transfer.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return verification, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) {
|
func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.chapa.co/v1/transfers", nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.chapa.co/v1/transfers", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,5 @@ type EnetPulseService interface {
|
||||||
FetchTournamentParticipants(ctx context.Context, tournamentID string) error
|
FetchTournamentParticipants(ctx context.Context, tournamentID string) error
|
||||||
FetchPreMatchOdds(ctx context.Context, params domain.PreMatchOddsRequest) (*domain.PreMatchOddsResponse, error)
|
FetchPreMatchOdds(ctx context.Context, params domain.PreMatchOddsRequest) (*domain.PreMatchOddsResponse, error)
|
||||||
FetchCountryFlag(ctx context.Context, countryFK int64) (*domain.ImageResponse, error)
|
FetchCountryFlag(ctx context.Context, countryFK int64) (*domain.ImageResponse, error)
|
||||||
|
GetAllPreoddsWithBettingOffers(ctx context.Context) ([]domain.EnetpulsePreodds, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -872,6 +872,7 @@ func (s *Service) FetchAndStorePreodds(ctx context.Context) error {
|
||||||
for _, fixture := range fixtures {
|
for _, fixture := range fixtures {
|
||||||
// 4️⃣ Loop through each outcome type
|
// 4️⃣ Loop through each outcome type
|
||||||
for _, outcome := range outcomeTypes {
|
for _, outcome := range outcomeTypes {
|
||||||
|
|
||||||
url := fmt.Sprintf(
|
url := fmt.Sprintf(
|
||||||
"http://eapi.enetpulse.com/preodds/event/?objectFK=%s&odds_providerFK=%s&outcome_typeFK=%s&username=%s&token=%s",
|
"http://eapi.enetpulse.com/preodds/event/?objectFK=%s&odds_providerFK=%s&outcome_typeFK=%s&username=%s&token=%s",
|
||||||
fixture.FixtureID,
|
fixture.FixtureID,
|
||||||
|
|
@ -896,6 +897,7 @@ func (s *Service) FetchAndStorePreodds(ctx context.Context) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Struct adjusted exactly to match JSON structure
|
||||||
var preoddsResp struct {
|
var preoddsResp struct {
|
||||||
Preodds map[string]struct {
|
Preodds map[string]struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
|
@ -910,17 +912,18 @@ func (s *Service) FetchAndStorePreodds(ctx context.Context) error {
|
||||||
Sparam string `json:"sparam"`
|
Sparam string `json:"sparam"`
|
||||||
N string `json:"n"`
|
N string `json:"n"`
|
||||||
UT string `json:"ut"`
|
UT string `json:"ut"`
|
||||||
BettingOffers []struct {
|
|
||||||
|
PreoddsBettingOffers map[string]struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
BettingOfferStatusFK int32 `json:"bettingoffer_status_fk"`
|
BettingOfferStatusFK string `json:"bettingoffer_statusFK"`
|
||||||
OddsProviderFK int32 `json:"odds_provider_fk"`
|
OddsProviderFK string `json:"odds_providerFK"`
|
||||||
Odds float64 `json:"odds"`
|
Odds string `json:"odds"`
|
||||||
OddsOld float64 `json:"odds_old"`
|
OddsOld string `json:"odds_old"`
|
||||||
Active string `json:"active"`
|
Active string `json:"active"`
|
||||||
CouponKey string `json:"coupon_key"`
|
CouponKey string `json:"couponKey"`
|
||||||
N string `json:"n"`
|
N string `json:"n"`
|
||||||
UT string `json:"ut"`
|
UT string `json:"ut"`
|
||||||
} `json:"bettingoffers"`
|
} `json:"preodds_bettingoffers"`
|
||||||
} `json:"preodds"`
|
} `json:"preodds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -929,65 +932,53 @@ func (s *Service) FetchAndStorePreodds(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range preoddsResp.Preodds {
|
for _, p := range preoddsResp.Preodds {
|
||||||
updatesCount := 0
|
// Convert numeric/string fields safely
|
||||||
if p.N != "" {
|
updatesCount, _ := strconv.Atoi(defaultIfEmpty(p.N, "0"))
|
||||||
if n, err := strconv.Atoi(p.N); err == nil {
|
eventParticipantNumber, _ := strconv.Atoi(defaultIfEmpty(p.EventParticipantNumber, "0"))
|
||||||
updatesCount = n
|
lastUpdatedAt := parseTimeOrNow(p.UT)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastUpdatedAt, _ := time.Parse(time.RFC3339, p.UT)
|
|
||||||
|
|
||||||
eventParticipantNumber := int32(0)
|
|
||||||
if p.EventParticipantNumber != "" {
|
|
||||||
if epn, err := strconv.Atoi(p.EventParticipantNumber); err == nil {
|
|
||||||
eventParticipantNumber = int32(epn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createPreodds := domain.CreateEnetpulsePreodds{
|
createPreodds := domain.CreateEnetpulsePreodds{
|
||||||
PreoddsID: p.ID,
|
PreoddsID: p.ID,
|
||||||
EventFK: fixture.FixtureID,
|
EventFK: fixture.FixtureID,
|
||||||
OutcomeTypeFK: outcome.OutcomeTypeID,
|
OutcomeTypeFK: p.OutcomeTypeFK,
|
||||||
OutcomeScopeFK: string(p.OutcomeScopeFK),
|
OutcomeScopeFK: p.OutcomeScopeFK,
|
||||||
OutcomeSubtypeFK: string(p.OutcomeSubtypeFK),
|
OutcomeSubtypeFK: p.OutcomeSubtypeFK,
|
||||||
EventParticipantNumber: int(eventParticipantNumber),
|
EventParticipantNumber: eventParticipantNumber,
|
||||||
IParam: p.Iparam,
|
IParam: p.Iparam,
|
||||||
IParam2: p.Iparam2,
|
IParam2: p.Iparam2,
|
||||||
DParam: p.Dparam,
|
DParam: p.Dparam,
|
||||||
DParam2: p.Dparam2,
|
DParam2: p.Dparam2,
|
||||||
SParam: p.Sparam,
|
SParam: p.Sparam,
|
||||||
UpdatesCount: int(updatesCount),
|
UpdatesCount: updatesCount,
|
||||||
LastUpdatedAt: lastUpdatedAt,
|
LastUpdatedAt: lastUpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n\nPreodds are:%v\n\n", createPreodds)
|
// Store preodds in DB
|
||||||
|
_, err := s.store.CreateEnetpulsePreodds(ctx, createPreodds)
|
||||||
storedPreodds, err := s.store.CreateEnetpulsePreodds(ctx, createPreodds)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, o := range p.BettingOffers {
|
// 5️⃣ Loop through betting offers map
|
||||||
bettingUpdates := 0
|
for _, o := range p.PreoddsBettingOffers {
|
||||||
if o.N != "" {
|
bettingUpdates, _ := strconv.Atoi(defaultIfEmpty(o.N, "0"))
|
||||||
if n, err := strconv.Atoi(o.N); err == nil {
|
bettingLastUpdatedAt := parseTimeOrNow(o.UT)
|
||||||
bettingUpdates = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bettingLastUpdatedAt, _ := time.Parse(time.RFC3339, o.UT)
|
odds, _ := strconv.ParseFloat(defaultIfEmpty(o.Odds, "0"), 64)
|
||||||
|
oddsOld, _ := strconv.ParseFloat(defaultIfEmpty(o.OddsOld, "0"), 64)
|
||||||
|
bettingOfferStatusFK, _ := strconv.Atoi(defaultIfEmpty(o.BettingOfferStatusFK, "0"))
|
||||||
|
oddsProviderFK, _ := strconv.Atoi(defaultIfEmpty(o.OddsProviderFK, "0"))
|
||||||
|
|
||||||
createOffer := domain.CreateEnetpulsePreoddsBettingOffer{
|
createOffer := domain.CreateEnetpulsePreoddsBettingOffer{
|
||||||
BettingOfferID: o.ID,
|
BettingOfferID: o.ID,
|
||||||
PreoddsFK: storedPreodds.PreoddsID,
|
PreoddsFK: createPreodds.PreoddsID,
|
||||||
BettingOfferStatusFK: o.BettingOfferStatusFK,
|
BettingOfferStatusFK: int32(bettingOfferStatusFK),
|
||||||
OddsProviderFK: o.OddsProviderFK,
|
OddsProviderFK: int32(oddsProviderFK),
|
||||||
Odds: o.Odds,
|
Odds: odds,
|
||||||
OddsOld: o.OddsOld,
|
OddsOld: oddsOld,
|
||||||
Active: o.Active,
|
Active: o.Active,
|
||||||
CouponKey: o.CouponKey,
|
CouponKey: o.CouponKey,
|
||||||
UpdatesCount: int(bettingUpdates),
|
UpdatesCount: bettingUpdates,
|
||||||
LastUpdatedAt: bettingLastUpdatedAt,
|
LastUpdatedAt: bettingLastUpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1000,6 +991,23 @@ func (s *Service) FetchAndStorePreodds(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility helpers
|
||||||
|
func defaultIfEmpty(val, def string) string {
|
||||||
|
if val == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTimeOrNow(t string) time.Time {
|
||||||
|
parsed, err := time.Parse(time.RFC3339, t)
|
||||||
|
if err != nil {
|
||||||
|
return time.Now().UTC()
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// helper function to parse string to int32 safely
|
// helper function to parse string to int32 safely
|
||||||
func ParseStringToInt32(s string) int32 {
|
func ParseStringToInt32(s string) int32 {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
|
|
@ -1117,6 +1125,15 @@ func (s *Service) GetAllBettingOffers(ctx context.Context) ([]domain.EnetpulsePr
|
||||||
return offers, nil
|
return offers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAllPreoddsWithBettingOffers(ctx context.Context) ([]domain.EnetpulsePreodds, error) {
|
||||||
|
preodds, err := s.store.GetAllEnetpulsePreoddsWithBettingOffers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch preodds with betting offers from DB: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return preodds, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) GetFixturesWithPreodds(ctx context.Context) ([]domain.EnetpulseFixtureWithPreodds, error) {
|
func (s *Service) GetFixturesWithPreodds(ctx context.Context) ([]domain.EnetpulseFixtureWithPreodds, error) {
|
||||||
// 1️⃣ Fetch fixtures and their associated preodds from the repository
|
// 1️⃣ Fetch fixtures and their associated preodds from the repository
|
||||||
fixtures, err := s.store.GetFixturesWithPreodds(ctx)
|
fixtures, err := s.store.GetFixturesWithPreodds(ctx)
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ func (h *Handler) InitiateDeposit(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
amount := domain.Currency(req.Amount * 100)
|
amount := domain.Currency(req.Amount)
|
||||||
|
|
||||||
fmt.Println("We are here init Chapa payment")
|
fmt.Println("We are here init Chapa payment")
|
||||||
|
|
||||||
|
|
@ -51,40 +51,6 @@ func (h *Handler) InitiateDeposit(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// get static wallet of user
|
|
||||||
// wallet, err := h.walletSvc.GetCustomerWallet(c.Context(), userID)
|
|
||||||
// if err != nil {
|
|
||||||
// return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
||||||
// Error: err.Error(),
|
|
||||||
// Message: "Failed to initiate Chapa deposit",
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var multiplier float32 = 1
|
|
||||||
// bonusMultiplier, err := h.bonusSvc.GetBonusMultiplier(c.Context())
|
|
||||||
// if err == nil {
|
|
||||||
// multiplier = bonusMultiplier[0].Multiplier
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var balanceCap int64 = 0
|
|
||||||
// bonusBalanceCap, err := h.bonusSvc.GetBonusBalanceCap(c.Context())
|
|
||||||
// if err == nil {
|
|
||||||
// balanceCap = bonusBalanceCap[0].BalanceCap
|
|
||||||
// }
|
|
||||||
|
|
||||||
// capedBalanceAmount := domain.Currency((math.Min(req.Amount, float64(balanceCap)) * float64(multiplier)) * 100)
|
|
||||||
|
|
||||||
// _, err = h.walletSvc.AddToWallet(c.Context(), wallet.StaticID, capedBalanceAmount, domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{},
|
|
||||||
// fmt.Sprintf("Added %v to static wallet because of deposit bonus using multiplier %v", capedBalanceAmount, multiplier),
|
|
||||||
// )
|
|
||||||
// if err != nil {
|
|
||||||
// h.logger.Error("Failed to add bonus to static wallet", "walletID", wallet.StaticID, "user id", userID, "error", err)
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if err := h.bonusSvc.ProcessWelcomeBonus(c.Context(), domain.ToCurrency(float32(req.Amount)), 0, userID); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
Message: "Chapa deposit process initiated successfully",
|
Message: "Chapa deposit process initiated successfully",
|
||||||
Data: checkoutURL,
|
Data: checkoutURL,
|
||||||
|
|
@ -114,16 +80,16 @@ func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
||||||
|
|
||||||
switch chapaTransactionType.Type {
|
switch chapaTransactionType.Type {
|
||||||
case h.Cfg.CHAPA_PAYMENT_TYPE:
|
case h.Cfg.CHAPA_PAYMENT_TYPE:
|
||||||
chapaTransferVerificationRequest := new(domain.ChapaWebHookTransfer)
|
chapaTransferVerificationRequest := new(domain.ChapaPaymentWebhookRequest)
|
||||||
|
|
||||||
if err := c.BodyParser(chapaTransferVerificationRequest); err != nil {
|
if err := c.BodyParser(chapaTransferVerificationRequest); err != nil {
|
||||||
return domain.UnProcessableEntityResponse(c)
|
return domain.UnProcessableEntityResponse(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.chapaSvc.HandleVerifyDepositWebhook(c.Context(), *chapaTransferVerificationRequest)
|
err := h.chapaSvc.ProcessVerifyDepositWebhook(c.Context(), *chapaTransferVerificationRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Message: "Failed to verify Chapa depposit",
|
Message: "Failed to verify Chapa deposit",
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -140,7 +106,7 @@ func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
||||||
return domain.UnProcessableEntityResponse(c)
|
return domain.UnProcessableEntityResponse(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.chapaSvc.HandleVerifyWithdrawWebhook(c.Context(), *chapaPaymentVerificationRequest)
|
err := h.chapaSvc.ProcessVerifyWithdrawWebhook(c.Context(), *chapaPaymentVerificationRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.UnExpectedErrorResponse(c)
|
return domain.UnExpectedErrorResponse(c)
|
||||||
}
|
}
|
||||||
|
|
@ -161,6 +127,120 @@ func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CancelDeposit godoc
|
||||||
|
// @Summary Cancel a Chapa deposit transaction
|
||||||
|
// @Description Cancels an active Chapa transaction using its transaction reference
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param tx_ref path string true "Transaction Reference"
|
||||||
|
// @Success 200 {object} domain.ChapaCancelResponse
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 404 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/chapa/transaction/cancel/{tx_ref} [put]
|
||||||
|
func (h *Handler) CancelDeposit(c *fiber.Ctx) error {
|
||||||
|
// Get user ID from context (set by your auth middleware)
|
||||||
|
userID, ok := c.Locals("user_id").(int64)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Error: "invalid user ID",
|
||||||
|
Message: "User ID is required to cancel a deposit",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract tx_ref from URL path
|
||||||
|
txRef := c.Params("tx_ref")
|
||||||
|
if txRef == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Error: "missing transaction reference",
|
||||||
|
Message: "Transaction reference is required in the path",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n\nReceived request to cancel Chapa transaction: %s (User ID: %d)\n\n", txRef, userID)
|
||||||
|
|
||||||
|
// Call the service layer to cancel deposit
|
||||||
|
cancelResp, err := h.chapaSvc.CancelDeposit(c.Context(), userID, txRef)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Error: err.Error(),
|
||||||
|
Message: "Failed to cancel Chapa deposit",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Chapa transaction cancelled successfully",
|
||||||
|
Data: cancelResp,
|
||||||
|
StatusCode: 200,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchAllTransactions godoc
|
||||||
|
// @Summary Get all Chapa transactions
|
||||||
|
// @Description Retrieves all transactions from Chapa payment gateway
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Success 200 {array} domain.ChapaTransaction
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/chapa/transactions [get]
|
||||||
|
func (h *Handler) FetchAllTransactions(c *fiber.Ctx) error {
|
||||||
|
transactions, err := h.chapaSvc.FetchAllTransactions(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Error: err.Error(),
|
||||||
|
Message: "Failed to fetch Chapa transactions",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Chapa transactions retrieved successfully",
|
||||||
|
Data: transactions,
|
||||||
|
StatusCode: 200,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionEvents godoc
|
||||||
|
// @Summary Fetch transaction events
|
||||||
|
// @Description Retrieve the timeline of events for a specific Chapa transaction
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param ref_id path string true "Transaction Reference"
|
||||||
|
// @Success 200 {array} domain.ChapaTransactionEvent
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/chapa/transaction/events/{ref_id} [get]
|
||||||
|
func (h *Handler) GetTransactionEvents(c *fiber.Ctx) error {
|
||||||
|
refID := c.Params("ref_id")
|
||||||
|
if refID == "" {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch transaction events",
|
||||||
|
Error: "Transaction reference is required",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
events, err := h.chapaSvc.FetchTransactionEvents(c.Context(), refID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch transaction events",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Transaction events fetched successfully",
|
||||||
|
Data: events,
|
||||||
|
StatusCode: 200,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyPayment godoc
|
// VerifyPayment godoc
|
||||||
// @Summary Verify a payment manually
|
// @Summary Verify a payment manually
|
||||||
// @Description Manually verify a payment using Chapa's API
|
// @Description Manually verify a payment using Chapa's API
|
||||||
|
|
@ -171,7 +251,7 @@ func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
||||||
// @Success 200 {object} domain.ChapaVerificationResponse
|
// @Success 200 {object} domain.ChapaVerificationResponse
|
||||||
// @Failure 400 {object} domain.ErrorResponse
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/chapa/payments/manual/verify/{tx_ref} [get]
|
// @Router /api/v1/chapa/transaction/manual/verify/{tx_ref} [get]
|
||||||
func (h *Handler) ManualVerifyTransaction(c *fiber.Ctx) error {
|
func (h *Handler) ManualVerifyTransaction(c *fiber.Ctx) error {
|
||||||
txRef := c.Params("tx_ref")
|
txRef := c.Params("tx_ref")
|
||||||
if txRef == "" {
|
if txRef == "" {
|
||||||
|
|
@ -189,11 +269,11 @@ func (h *Handler) ManualVerifyTransaction(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.ChapaVerificationResponse{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
Status: string(verification.Status),
|
Message: "Chapa transaction verified successfully",
|
||||||
Amount: verification.Amount,
|
Data: verification,
|
||||||
Currency: verification.Currency,
|
StatusCode: 200,
|
||||||
TxRef: txRef,
|
Success: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,7 +364,7 @@ func (h *Handler) GetPaymentReceipt(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
receiptURL, err := h.chapaSvc.GetPaymentReceiptURL(c.Context(), chapaRef)
|
receiptURL, err := h.chapaSvc.GetPaymentReceiptURL(chapaRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Message: "Failed to get Chapa payment receipt",
|
Message: "Failed to get Chapa payment receipt",
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,62 @@ func (h *Handler) GetAllPreodds(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAllBettingOffers godoc
|
||||||
|
// @Summary Get all betting offers
|
||||||
|
// @Description Fetches all EnetPulse preodds betting offers stored in the database
|
||||||
|
// @Tags EnetPulse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulsePreoddsBettingOffer}
|
||||||
|
// @Failure 502 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/enetpulse/betting-offers [get]
|
||||||
|
func (h *Handler) GetAllBettingOffers(c *fiber.Ctx) error {
|
||||||
|
// Call service
|
||||||
|
offers, err := h.enetPulseSvc.GetAllBettingOffers(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("GetAllBettingOffers error:", err)
|
||||||
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch EnetPulse betting offers",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "EnetPulse betting offers fetched successfully",
|
||||||
|
Data: offers,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllPreoddsWithBettingOffers godoc
|
||||||
|
// @Summary Get all preodds with betting offers
|
||||||
|
// @Description Fetches all EnetPulse pre-match odds along with their associated betting offers stored in the database
|
||||||
|
// @Tags EnetPulse
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response{data=[]domain.EnetpulsePreodds}
|
||||||
|
// @Failure 502 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/enetpulse/preodds-with-offers [get]
|
||||||
|
func (h *Handler) GetAllPreoddsWithBettingOffers(c *fiber.Ctx) error {
|
||||||
|
// Call service
|
||||||
|
preodds, err := h.enetPulseSvc.GetAllPreoddsWithBettingOffers(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("GetAllPreoddsWithBettingOffers error:", err)
|
||||||
|
return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to fetch EnetPulse preodds with betting offers",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "EnetPulse preodds with betting offers fetched successfully",
|
||||||
|
Data: preodds,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// GetFixturesWithPreodds godoc
|
// GetFixturesWithPreodds godoc
|
||||||
// @Summary Get fixtures with preodds
|
// @Summary Get fixtures with preodds
|
||||||
// @Description Fetches all EnetPulse fixtures along with their associated pre-match odds
|
// @Description Fetches all EnetPulse fixtures along with their associated pre-match odds
|
||||||
|
|
|
||||||
|
|
@ -292,7 +292,8 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Get("/tournament_stages", h.GetAllTournamentStages)
|
groupV1.Get("/tournament_stages", h.GetAllTournamentStages)
|
||||||
groupV1.Get("/fixtures", h.GetFixturesByDate)
|
groupV1.Get("/fixtures", h.GetFixturesByDate)
|
||||||
groupV1.Get("/results", h.GetAllResults)
|
groupV1.Get("/results", h.GetAllResults)
|
||||||
groupV1.Get("/preodds", h.GetAllPreodds)
|
groupV1.Get("/preodds", h.GetAllPreoddsWithBettingOffers)
|
||||||
|
groupV1.Get("/bettingoffers", h.GetAllBettingOffers)
|
||||||
groupV1.Get("/fixtures/preodds", h.GetFixturesWithPreodds)
|
groupV1.Get("/fixtures/preodds", h.GetFixturesWithPreodds)
|
||||||
|
|
||||||
// Leagues
|
// Leagues
|
||||||
|
|
@ -380,7 +381,10 @@ func (a *App) initAppRoutes() {
|
||||||
|
|
||||||
//Chapa Routes
|
//Chapa Routes
|
||||||
groupV1.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
|
groupV1.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
|
||||||
groupV1.Get("/chapa/payments/manual/verify/:tx_ref", h.ManualVerifyTransaction)
|
groupV1.Get("/chapa/transaction/manual/verify/:tx_ref", h.ManualVerifyTransaction)
|
||||||
|
groupV1.Put("/chapa/transaction/cancel/:tx_ref", a.authMiddleware, h.CancelDeposit)
|
||||||
|
groupV1.Get("/chapa/transactions", h.FetchAllTransactions)
|
||||||
|
groupV1.Get("/chapa/transaction/events/:ref_id", h.GetTransactionEvents)
|
||||||
groupV1.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
|
groupV1.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
|
||||||
groupV1.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
|
groupV1.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
|
||||||
groupV1.Get("/chapa/banks", h.GetSupportedBanks)
|
groupV1.Get("/chapa/banks", h.GetSupportedBanks)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user