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,
|
||||
verified BOOLEAN DEFAULT false,
|
||||
reference_number VARCHAR(255) NOT NULL,
|
||||
ext_reference_number VARCHAR(255),
|
||||
session_id VARCHAR(255),
|
||||
status VARCHAR(255),
|
||||
payment_method VARCHAR(255),
|
||||
|
|
|
|||
|
|
@ -448,6 +448,46 @@ SELECT *
|
|||
FROM enetpulse_preodds_bettingoffers
|
||||
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
|
||||
SELECT
|
||||
f.fixture_id AS id,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ INSERT INTO wallet_transfer (
|
|||
cashier_id,
|
||||
verified,
|
||||
reference_number,
|
||||
ext_reference_number,
|
||||
session_id,
|
||||
status,
|
||||
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 *;
|
||||
-- name: GetAllTransfers :many
|
||||
SELECT *
|
||||
|
|
|
|||
|
|
@ -1081,6 +1081,128 @@ func (q *Queries) GetAllEnetpulsePreoddsBettingOffers(ctx context.Context) ([]En
|
|||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1147,6 +1147,7 @@ type WalletTransfer struct {
|
|||
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||
Verified pgtype.Bool `json:"verified"`
|
||||
ReferenceNumber string `json:"reference_number"`
|
||||
ExtReferenceNumber pgtype.Text `json:"ext_reference_number"`
|
||||
SessionID pgtype.Text `json:"session_id"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||
|
|
@ -1164,6 +1165,7 @@ type WalletTransferDetail struct {
|
|||
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||
Verified pgtype.Bool `json:"verified"`
|
||||
ReferenceNumber string `json:"reference_number"`
|
||||
ExtReferenceNumber pgtype.Text `json:"ext_reference_number"`
|
||||
SessionID pgtype.Text `json:"session_id"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||
|
|
|
|||
|
|
@ -21,12 +21,13 @@ INSERT INTO wallet_transfer (
|
|||
cashier_id,
|
||||
verified,
|
||||
reference_number,
|
||||
ext_reference_number,
|
||||
session_id,
|
||||
status,
|
||||
payment_method
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING id, amount, message, type, receiver_wallet_id, sender_wallet_id, cashier_id, verified, reference_number, session_id, status, payment_method, created_at, updated_at
|
||||
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, ext_reference_number, session_id, status, payment_method, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateTransferParams struct {
|
||||
|
|
@ -38,6 +39,7 @@ type CreateTransferParams struct {
|
|||
CashierID pgtype.Int8 `json:"cashier_id"`
|
||||
Verified pgtype.Bool `json:"verified"`
|
||||
ReferenceNumber string `json:"reference_number"`
|
||||
ExtReferenceNumber pgtype.Text `json:"ext_reference_number"`
|
||||
SessionID pgtype.Text `json:"session_id"`
|
||||
Status pgtype.Text `json:"status"`
|
||||
PaymentMethod pgtype.Text `json:"payment_method"`
|
||||
|
|
@ -53,6 +55,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
|||
arg.CashierID,
|
||||
arg.Verified,
|
||||
arg.ReferenceNumber,
|
||||
arg.ExtReferenceNumber,
|
||||
arg.SessionID,
|
||||
arg.Status,
|
||||
arg.PaymentMethod,
|
||||
|
|
@ -68,6 +71,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
|||
&i.CashierID,
|
||||
&i.Verified,
|
||||
&i.ReferenceNumber,
|
||||
&i.ExtReferenceNumber,
|
||||
&i.SessionID,
|
||||
&i.Status,
|
||||
&i.PaymentMethod,
|
||||
|
|
@ -78,7 +82,7 @@ func (q *Queries) CreateTransfer(ctx context.Context, arg CreateTransferParams)
|
|||
}
|
||||
|
||||
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
|
||||
`
|
||||
|
||||
|
|
@ -101,6 +105,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransferDetail,
|
|||
&i.CashierID,
|
||||
&i.Verified,
|
||||
&i.ReferenceNumber,
|
||||
&i.ExtReferenceNumber,
|
||||
&i.SessionID,
|
||||
&i.Status,
|
||||
&i.PaymentMethod,
|
||||
|
|
@ -121,7 +126,7 @@ func (q *Queries) GetAllTransfers(ctx context.Context) ([]WalletTransferDetail,
|
|||
}
|
||||
|
||||
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
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -139,6 +144,7 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
|
|||
&i.CashierID,
|
||||
&i.Verified,
|
||||
&i.ReferenceNumber,
|
||||
&i.ExtReferenceNumber,
|
||||
&i.SessionID,
|
||||
&i.Status,
|
||||
&i.PaymentMethod,
|
||||
|
|
@ -152,7 +158,7 @@ func (q *Queries) GetTransferByID(ctx context.Context, id int64) (WalletTransfer
|
|||
}
|
||||
|
||||
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
|
||||
WHERE reference_number = $1
|
||||
`
|
||||
|
|
@ -170,6 +176,7 @@ func (q *Queries) GetTransferByReference(ctx context.Context, referenceNumber st
|
|||
&i.CashierID,
|
||||
&i.Verified,
|
||||
&i.ReferenceNumber,
|
||||
&i.ExtReferenceNumber,
|
||||
&i.SessionID,
|
||||
&i.Status,
|
||||
&i.PaymentMethod,
|
||||
|
|
@ -217,7 +224,7 @@ func (q *Queries) GetTransferStats(ctx context.Context, senderWalletID pgtype.In
|
|||
}
|
||||
|
||||
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
|
||||
WHERE receiver_wallet_id = $1
|
||||
OR sender_wallet_id = $1
|
||||
|
|
@ -242,6 +249,7 @@ func (q *Queries) GetTransfersByWallet(ctx context.Context, receiverWalletID pgt
|
|||
&i.CashierID,
|
||||
&i.Verified,
|
||||
&i.ReferenceNumber,
|
||||
&i.ExtReferenceNumber,
|
||||
&i.SessionID,
|
||||
&i.Status,
|
||||
&i.PaymentMethod,
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ type Config struct {
|
|||
CHAPA_ENCRYPTION_KEY string
|
||||
CHAPA_CALLBACK_URL string
|
||||
CHAPA_RETURN_URL string
|
||||
CHAPA_RECEIPT_URL string
|
||||
Bet365Token string
|
||||
EnetPulseConfig EnetPulseConfig
|
||||
PopOK domain.PopOKConfig
|
||||
|
|
@ -262,6 +263,7 @@ func (c *Config) loadEnv() error {
|
|||
c.CHAPA_PUBLIC_KEY = os.Getenv("CHAPA_PUBLIC_KEY")
|
||||
c.CHAPA_ENCRYPTION_KEY = os.Getenv("CHAPA_ENCRYPTION_KEY")
|
||||
c.CHAPA_BASE_URL = os.Getenv("CHAPA_BASE_URL")
|
||||
c.CHAPA_RECEIPT_URL = os.Getenv("CHAPA_RECEIPT_URL")
|
||||
if c.CHAPA_BASE_URL == "" {
|
||||
c.CHAPA_BASE_URL = "https://api.chapa.co/v1"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const (
|
|||
PaymentStatusFailed PaymentStatus = "failed"
|
||||
)
|
||||
|
||||
type ChapaDepositRequest struct {
|
||||
type ChapaInitDepositRequest struct {
|
||||
Amount Currency `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Email string `json:"email"`
|
||||
|
|
@ -42,6 +42,8 @@ type ChapaDepositRequest struct {
|
|||
TxRef string `json:"tx_ref"`
|
||||
CallbackURL string `json:"callback_url"`
|
||||
ReturnURL string `json:"return_url"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
// PhoneNumber string `json:"phone_number"`
|
||||
}
|
||||
|
||||
type ChapaDepositRequestPayload struct {
|
||||
|
|
@ -49,9 +51,15 @@ type ChapaDepositRequestPayload struct {
|
|||
}
|
||||
|
||||
type ChapaWebhookPayload struct {
|
||||
TxRef string `json:"tx_ref"`
|
||||
TxRef string `json:"trx_ref"`
|
||||
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"`
|
||||
}
|
||||
|
||||
|
|
@ -69,10 +77,91 @@ type ChapaDepositVerification struct {
|
|||
}
|
||||
|
||||
type ChapaVerificationResponse struct {
|
||||
Message string `json:"message"`
|
||||
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"`
|
||||
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"`
|
||||
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 {
|
||||
|
|
@ -221,3 +310,13 @@ type SwapResponse struct {
|
|||
CreatedAt string `json:"created_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
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
BettingOffers []EnetpulsePreoddsBettingOffer
|
||||
}
|
||||
|
||||
type EnetpulseResultParticipant struct {
|
||||
|
|
|
|||
|
|
@ -302,6 +302,71 @@ func (s *Store) GetAllEnetpulsePreoddsBettingOffers(ctx context.Context) ([]doma
|
|||
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) {
|
||||
dbRows, err := s.queries.GetFixturesWithPreodds(ctx)
|
||||
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) {
|
||||
return s.betStore.GetBetOutcomeViewByEventID(ctx, eventID, filter)
|
||||
}
|
||||
|
||||
func (s *Service) GetBetOutcomeByEventID(ctx context.Context, eventID int64, is_filtered bool) ([]domain.BetOutcome, error) {
|
||||
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{}{
|
||||
"amount": fmt.Sprintf("%.2f", float64(req.Amount)),
|
||||
"currency": req.Currency,
|
||||
// "email": req.Email,
|
||||
"email": req.Email,
|
||||
"first_name": req.FirstName,
|
||||
"last_name": req.LastName,
|
||||
"tx_ref": req.TxRef,
|
||||
"callback_url": req.CallbackURL,
|
||||
"return_url": req.ReturnURL,
|
||||
"phone_number": req.PhoneNumber,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
// if resp.StatusCode != http.StatusOK {
|
||||
// return domain.ChapaDepositResponse{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
// }
|
||||
|
||||
var response struct {
|
||||
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) {
|
||||
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 {
|
||||
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 {
|
||||
|
|
@ -147,32 +149,24 @@ func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain
|
|||
defer resp.Body.Close()
|
||||
|
||||
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 {
|
||||
Status string `json:"status"`
|
||||
Amount float64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
var verification domain.ChapaVerificationResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
var status domain.PaymentStatus
|
||||
switch response.Status {
|
||||
case "success":
|
||||
status = domain.PaymentStatusCompleted
|
||||
default:
|
||||
status = domain.PaymentStatusFailed
|
||||
}
|
||||
// Normalize payment status for internal use
|
||||
// switch strings.ToLower(verification.Data.Status) {
|
||||
// case "success":
|
||||
// verification.Status = string(domain.PaymentStatusCompleted)
|
||||
// default:
|
||||
// verification.Status = string(domain.PaymentStatusFailed)
|
||||
// }
|
||||
|
||||
return &domain.ChapaVerificationResponse{
|
||||
Status: string(status),
|
||||
Amount: response.Amount,
|
||||
Currency: response.Currency,
|
||||
}, nil
|
||||
return &verification, nil
|
||||
}
|
||||
|
||||
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{
|
||||
Status: string(status),
|
||||
Amount: response.Amount,
|
||||
Currency: response.Currency,
|
||||
// Amount: response.Amount,
|
||||
// Currency: response.Currency,
|
||||
}, 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) {
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/banks", nil)
|
||||
if err != nil {
|
||||
|
|
@ -336,6 +393,62 @@ func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.
|
|||
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) {
|
||||
req.Header.Set("Authorization", "Bearer "+c.secretKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
// }
|
||||
|
||||
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)
|
||||
FetchSupportedBanks(ctx context.Context) ([]domain.Bank, 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)
|
||||
}
|
||||
|
||||
var senderWallet domain.Wallet
|
||||
// var senderWallet domain.Wallet
|
||||
|
||||
// Generate unique reference
|
||||
// reference := 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 {
|
||||
return "", fmt.Errorf("failed to get sender wallets: %w", err)
|
||||
}
|
||||
for _, wallet := range senderWallets {
|
||||
if wallet.IsTransferable {
|
||||
senderWallet = wallet
|
||||
break
|
||||
}
|
||||
return "", fmt.Errorf("failed to get sender wallet: %w", err)
|
||||
}
|
||||
// for _, wallet := range senderWallets {
|
||||
// if wallet.IsTransferable {
|
||||
// senderWallet = wallet
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// Check if payment with this reference already exists
|
||||
// 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,
|
||||
},
|
||||
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,
|
||||
Currency: "ETB",
|
||||
Email: user.Email,
|
||||
|
|
@ -103,6 +110,7 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
|||
TxRef: reference,
|
||||
CallbackURL: s.cfg.CHAPA_CALLBACK_URL,
|
||||
ReturnURL: s.cfg.CHAPA_RETURN_URL,
|
||||
PhoneNumber: userPhoneNum,
|
||||
}
|
||||
|
||||
// Initialize payment with Chapa
|
||||
|
|
@ -127,6 +135,157 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
|||
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) {
|
||||
// Parse and validate amount
|
||||
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
|
||||
}
|
||||
|
||||
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 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 {
|
||||
func (s *Service) ProcessVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) error {
|
||||
// Find payment by reference
|
||||
transfer, err := s.transferStore.GetTransferByReference(ctx, payment.Reference)
|
||||
if err != nil {
|
||||
|
|
@ -369,15 +411,111 @@ func (s *Service) HandleVerifyWithdrawWebhook(ctx context.Context, payment domai
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetPaymentReceiptURL(ctx context.Context, chapaRef string) (string, error) {
|
||||
if chapaRef == "" {
|
||||
return "", fmt.Errorf("chapa reference ID is required")
|
||||
func (s *Service) GetPaymentReceiptURL(refId string) (string, error) {
|
||||
if refId == "" {
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.chapa.co/v1/transfers", nil)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -14,4 +14,5 @@ type EnetPulseService interface {
|
|||
FetchTournamentParticipants(ctx context.Context, tournamentID string) error
|
||||
FetchPreMatchOdds(ctx context.Context, params domain.PreMatchOddsRequest) (*domain.PreMatchOddsResponse, 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 {
|
||||
// 4️⃣ Loop through each outcome type
|
||||
for _, outcome := range outcomeTypes {
|
||||
|
||||
url := fmt.Sprintf(
|
||||
"http://eapi.enetpulse.com/preodds/event/?objectFK=%s&odds_providerFK=%s&outcome_typeFK=%s&username=%s&token=%s",
|
||||
fixture.FixtureID,
|
||||
|
|
@ -896,6 +897,7 @@ func (s *Service) FetchAndStorePreodds(ctx context.Context) error {
|
|||
continue
|
||||
}
|
||||
|
||||
// Struct adjusted exactly to match JSON structure
|
||||
var preoddsResp struct {
|
||||
Preodds map[string]struct {
|
||||
ID string `json:"id"`
|
||||
|
|
@ -910,17 +912,18 @@ func (s *Service) FetchAndStorePreodds(ctx context.Context) error {
|
|||
Sparam string `json:"sparam"`
|
||||
N string `json:"n"`
|
||||
UT string `json:"ut"`
|
||||
BettingOffers []struct {
|
||||
|
||||
PreoddsBettingOffers map[string]struct {
|
||||
ID string `json:"id"`
|
||||
BettingOfferStatusFK int32 `json:"bettingoffer_status_fk"`
|
||||
OddsProviderFK int32 `json:"odds_provider_fk"`
|
||||
Odds float64 `json:"odds"`
|
||||
OddsOld float64 `json:"odds_old"`
|
||||
BettingOfferStatusFK string `json:"bettingoffer_statusFK"`
|
||||
OddsProviderFK string `json:"odds_providerFK"`
|
||||
Odds string `json:"odds"`
|
||||
OddsOld string `json:"odds_old"`
|
||||
Active string `json:"active"`
|
||||
CouponKey string `json:"coupon_key"`
|
||||
CouponKey string `json:"couponKey"`
|
||||
N string `json:"n"`
|
||||
UT string `json:"ut"`
|
||||
} `json:"bettingoffers"`
|
||||
} `json:"preodds_bettingoffers"`
|
||||
} `json:"preodds"`
|
||||
}
|
||||
|
||||
|
|
@ -929,65 +932,53 @@ func (s *Service) FetchAndStorePreodds(ctx context.Context) error {
|
|||
}
|
||||
|
||||
for _, p := range preoddsResp.Preodds {
|
||||
updatesCount := 0
|
||||
if p.N != "" {
|
||||
if n, err := strconv.Atoi(p.N); err == nil {
|
||||
updatesCount = n
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
// Convert numeric/string fields safely
|
||||
updatesCount, _ := strconv.Atoi(defaultIfEmpty(p.N, "0"))
|
||||
eventParticipantNumber, _ := strconv.Atoi(defaultIfEmpty(p.EventParticipantNumber, "0"))
|
||||
lastUpdatedAt := parseTimeOrNow(p.UT)
|
||||
|
||||
createPreodds := domain.CreateEnetpulsePreodds{
|
||||
PreoddsID: p.ID,
|
||||
EventFK: fixture.FixtureID,
|
||||
OutcomeTypeFK: outcome.OutcomeTypeID,
|
||||
OutcomeScopeFK: string(p.OutcomeScopeFK),
|
||||
OutcomeSubtypeFK: string(p.OutcomeSubtypeFK),
|
||||
EventParticipantNumber: int(eventParticipantNumber),
|
||||
OutcomeTypeFK: p.OutcomeTypeFK,
|
||||
OutcomeScopeFK: p.OutcomeScopeFK,
|
||||
OutcomeSubtypeFK: p.OutcomeSubtypeFK,
|
||||
EventParticipantNumber: eventParticipantNumber,
|
||||
IParam: p.Iparam,
|
||||
IParam2: p.Iparam2,
|
||||
DParam: p.Dparam,
|
||||
DParam2: p.Dparam2,
|
||||
SParam: p.Sparam,
|
||||
UpdatesCount: int(updatesCount),
|
||||
UpdatesCount: updatesCount,
|
||||
LastUpdatedAt: lastUpdatedAt,
|
||||
}
|
||||
|
||||
fmt.Printf("\n\nPreodds are:%v\n\n", createPreodds)
|
||||
|
||||
storedPreodds, err := s.store.CreateEnetpulsePreodds(ctx, createPreodds)
|
||||
// Store preodds in DB
|
||||
_, err := s.store.CreateEnetpulsePreodds(ctx, createPreodds)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, o := range p.BettingOffers {
|
||||
bettingUpdates := 0
|
||||
if o.N != "" {
|
||||
if n, err := strconv.Atoi(o.N); err == nil {
|
||||
bettingUpdates = n
|
||||
}
|
||||
}
|
||||
// 5️⃣ Loop through betting offers map
|
||||
for _, o := range p.PreoddsBettingOffers {
|
||||
bettingUpdates, _ := strconv.Atoi(defaultIfEmpty(o.N, "0"))
|
||||
bettingLastUpdatedAt := parseTimeOrNow(o.UT)
|
||||
|
||||
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{
|
||||
BettingOfferID: o.ID,
|
||||
PreoddsFK: storedPreodds.PreoddsID,
|
||||
BettingOfferStatusFK: o.BettingOfferStatusFK,
|
||||
OddsProviderFK: o.OddsProviderFK,
|
||||
Odds: o.Odds,
|
||||
OddsOld: o.OddsOld,
|
||||
PreoddsFK: createPreodds.PreoddsID,
|
||||
BettingOfferStatusFK: int32(bettingOfferStatusFK),
|
||||
OddsProviderFK: int32(oddsProviderFK),
|
||||
Odds: odds,
|
||||
OddsOld: oddsOld,
|
||||
Active: o.Active,
|
||||
CouponKey: o.CouponKey,
|
||||
UpdatesCount: int(bettingUpdates),
|
||||
UpdatesCount: bettingUpdates,
|
||||
LastUpdatedAt: bettingLastUpdatedAt,
|
||||
}
|
||||
|
||||
|
|
@ -1000,6 +991,23 @@ func (s *Service) FetchAndStorePreodds(ctx context.Context) error {
|
|||
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
|
||||
func ParseStringToInt32(s string) int32 {
|
||||
if s == "" {
|
||||
|
|
@ -1117,6 +1125,15 @@ func (s *Service) GetAllBettingOffers(ctx context.Context) ([]domain.EnetpulsePr
|
|||
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) {
|
||||
// 1️⃣ Fetch fixtures and their associated preodds from the repository
|
||||
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")
|
||||
|
||||
|
|
@ -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{
|
||||
Message: "Chapa deposit process initiated successfully",
|
||||
Data: checkoutURL,
|
||||
|
|
@ -114,16 +80,16 @@ func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
|||
|
||||
switch chapaTransactionType.Type {
|
||||
case h.Cfg.CHAPA_PAYMENT_TYPE:
|
||||
chapaTransferVerificationRequest := new(domain.ChapaWebHookTransfer)
|
||||
chapaTransferVerificationRequest := new(domain.ChapaPaymentWebhookRequest)
|
||||
|
||||
if err := c.BodyParser(chapaTransferVerificationRequest); err != nil {
|
||||
return domain.UnProcessableEntityResponse(c)
|
||||
}
|
||||
|
||||
err := h.chapaSvc.HandleVerifyDepositWebhook(c.Context(), *chapaTransferVerificationRequest)
|
||||
err := h.chapaSvc.ProcessVerifyDepositWebhook(c.Context(), *chapaTransferVerificationRequest)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to verify Chapa depposit",
|
||||
Message: "Failed to verify Chapa deposit",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
|
@ -140,7 +106,7 @@ func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
|||
return domain.UnProcessableEntityResponse(c)
|
||||
}
|
||||
|
||||
err := h.chapaSvc.HandleVerifyWithdrawWebhook(c.Context(), *chapaPaymentVerificationRequest)
|
||||
err := h.chapaSvc.ProcessVerifyWithdrawWebhook(c.Context(), *chapaPaymentVerificationRequest)
|
||||
if err != nil {
|
||||
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
|
||||
// @Summary Verify a payment manually
|
||||
// @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
|
||||
// @Failure 400 {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 {
|
||||
txRef := c.Params("tx_ref")
|
||||
if txRef == "" {
|
||||
|
|
@ -189,11 +269,11 @@ func (h *Handler) ManualVerifyTransaction(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.ChapaVerificationResponse{
|
||||
Status: string(verification.Status),
|
||||
Amount: verification.Amount,
|
||||
Currency: verification.Currency,
|
||||
TxRef: txRef,
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "Chapa transaction verified successfully",
|
||||
Data: verification,
|
||||
StatusCode: 200,
|
||||
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 {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
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
|
||||
// @Summary Get fixtures with preodds
|
||||
// @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("/fixtures", h.GetFixturesByDate)
|
||||
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)
|
||||
|
||||
// Leagues
|
||||
|
|
@ -380,7 +381,10 @@ func (a *App) initAppRoutes() {
|
|||
|
||||
//Chapa Routes
|
||||
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/withdraw", a.authMiddleware, h.InitiateWithdrawal)
|
||||
groupV1.Get("/chapa/banks", h.GetSupportedBanks)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user