santimpay + direct depoist
This commit is contained in:
parent
982573d67e
commit
5815381a21
|
|
@ -3,7 +3,7 @@ FROM golang:1.24-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
# RUN go mod download
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN go build -ldflags="-s -w" -o ./bin/web ./cmd/main.go
|
RUN go build -ldflags="-s -w" -o ./bin/web ./cmd/main.go
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ import (
|
||||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/santimpay"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||||
|
|
@ -126,6 +127,7 @@ func main() {
|
||||||
walletSvc := wallet.NewService(
|
walletSvc := wallet.NewService(
|
||||||
wallet.WalletStore(store),
|
wallet.WalletStore(store),
|
||||||
wallet.TransferStore(store),
|
wallet.TransferStore(store),
|
||||||
|
wallet.DirectDepositStore(store),
|
||||||
notificatioStore,
|
notificatioStore,
|
||||||
notificationSvc,
|
notificationSvc,
|
||||||
userSvc,
|
userSvc,
|
||||||
|
|
@ -231,9 +233,14 @@ func main() {
|
||||||
arifpaySvc := arifpay.NewArifpayService(cfg, transferStore, &http.Client{
|
arifpaySvc := arifpay.NewArifpayService(cfg, transferStore, &http.Client{
|
||||||
Timeout: 30 * time.Second})
|
Timeout: 30 * time.Second})
|
||||||
|
|
||||||
|
santimpayClient := santimpay.NewSantimPayClient(cfg)
|
||||||
|
|
||||||
|
santimpaySvc := santimpay.NewSantimPayService(santimpayClient, cfg, transferStore)
|
||||||
|
|
||||||
// Initialize and start HTTP server
|
// Initialize and start HTTP server
|
||||||
app := httpserver.NewApp(
|
app := httpserver.NewApp(
|
||||||
arifpaySvc,
|
arifpaySvc,
|
||||||
|
santimpaySvc,
|
||||||
issueReportingSvc,
|
issueReportingSvc,
|
||||||
instSvc,
|
instSvc,
|
||||||
currSvc,
|
currSvc,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,21 @@
|
||||||
|
CREATE TABLE direct_deposits (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
customer_id BIGINT NOT NULL REFERENCES users(id),
|
||||||
|
wallet_id BIGINT NOT NULL REFERENCES wallets(id),
|
||||||
|
amount NUMERIC(15, 2) NOT NULL,
|
||||||
|
bank_reference TEXT NOT NULL,
|
||||||
|
sender_account TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL CHECK (status IN ('pending', 'completed', 'rejected')),
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
verified_by BIGINT REFERENCES users(id),
|
||||||
|
verification_notes TEXT,
|
||||||
|
verified_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_direct_deposits_status ON direct_deposits(status);
|
||||||
|
CREATE INDEX idx_direct_deposits_customer ON direct_deposits(customer_id);
|
||||||
|
CREATE INDEX idx_direct_deposits_reference ON direct_deposits(bank_reference);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
first_name VARCHAR(255) NOT NULL,
|
first_name VARCHAR(255) NOT NULL,
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,11 @@
|
||||||
-- Settings Initial Data
|
-- Settings Initial Data
|
||||||
INSERT INTO settings (key, value)
|
INSERT INTO settings (key, value)
|
||||||
<<<<<<< HEAD
|
|
||||||
VALUES
|
|
||||||
('max_number_of_outcomes', '30'),
|
|
||||||
=======
|
|
||||||
VALUES ('sms_provider', '30'),
|
VALUES ('sms_provider', '30'),
|
||||||
('max_number_of_outcomes', '30'),
|
('max_number_of_outcomes', '30'),
|
||||||
>>>>>>> d43b12c589d32e4b6147cfb54a3b939c476bae6f
|
|
||||||
('bet_amount_limit', '100000'),
|
('bet_amount_limit', '100000'),
|
||||||
('daily_ticket_limit', '50'),
|
('daily_ticket_limit', '50'),
|
||||||
('total_winnings_limit', '1000000'),
|
('total_winnings_limit', '1000000'),
|
||||||
('amount_for_bet_referral', '1000000'),
|
('amount_for_bet_referral', '1000000'),
|
||||||
<<<<<<< HEAD
|
|
||||||
('cashback_amount_cap', '1000')
|
|
||||||
ON CONFLICT (key)
|
|
||||||
DO UPDATE SET value = EXCLUDED.value;
|
|
||||||
=======
|
|
||||||
('cashback_amount_cap', '1000') ON CONFLICT (key) DO
|
('cashback_amount_cap', '1000') ON CONFLICT (key) DO
|
||||||
UPDATE
|
UPDATE
|
||||||
SET value = EXCLUDED.value;
|
SET value = EXCLUDED.value;
|
||||||
>>>>>>> d43b12c589d32e4b6147cfb54a3b939c476bae6f
|
|
||||||
|
|
|
||||||
30
db/query/direct_deposit.sql
Normal file
30
db/query/direct_deposit.sql
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
-- name: CreateDirectDeposit :one
|
||||||
|
INSERT INTO direct_deposits (
|
||||||
|
customer_id,
|
||||||
|
wallet_id,
|
||||||
|
amount,
|
||||||
|
bank_reference,
|
||||||
|
sender_account,
|
||||||
|
status
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6
|
||||||
|
) RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetDirectDeposit :one
|
||||||
|
SELECT * FROM direct_deposits WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: UpdateDirectDeposit :one
|
||||||
|
UPDATE direct_deposits
|
||||||
|
SET
|
||||||
|
status = $2,
|
||||||
|
verified_by = $3,
|
||||||
|
verification_notes = $4,
|
||||||
|
verified_at = $5
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetDirectDepositsByStatus :many
|
||||||
|
SELECT * FROM direct_deposits WHERE status = $1 ORDER BY created_at DESC;
|
||||||
|
|
||||||
|
-- name: GetCustomerDirectDeposits :many
|
||||||
|
SELECT * FROM direct_deposits WHERE customer_id = $1 ORDER BY created_at DESC;
|
||||||
199
gen/db/direct_deposit.sql.go
Normal file
199
gen/db/direct_deposit.sql.go
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.29.0
|
||||||
|
// source: direct_deposit.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreateDirectDeposit = `-- name: CreateDirectDeposit :one
|
||||||
|
INSERT INTO direct_deposits (
|
||||||
|
customer_id,
|
||||||
|
wallet_id,
|
||||||
|
amount,
|
||||||
|
bank_reference,
|
||||||
|
sender_account,
|
||||||
|
status
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6
|
||||||
|
) RETURNING id, customer_id, wallet_id, amount, bank_reference, sender_account, status, created_at, verified_by, verification_notes, verified_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateDirectDepositParams struct {
|
||||||
|
CustomerID int64 `json:"customer_id"`
|
||||||
|
WalletID int64 `json:"wallet_id"`
|
||||||
|
Amount pgtype.Numeric `json:"amount"`
|
||||||
|
BankReference string `json:"bank_reference"`
|
||||||
|
SenderAccount string `json:"sender_account"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateDirectDeposit(ctx context.Context, arg CreateDirectDepositParams) (DirectDeposit, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CreateDirectDeposit,
|
||||||
|
arg.CustomerID,
|
||||||
|
arg.WalletID,
|
||||||
|
arg.Amount,
|
||||||
|
arg.BankReference,
|
||||||
|
arg.SenderAccount,
|
||||||
|
arg.Status,
|
||||||
|
)
|
||||||
|
var i DirectDeposit
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CustomerID,
|
||||||
|
&i.WalletID,
|
||||||
|
&i.Amount,
|
||||||
|
&i.BankReference,
|
||||||
|
&i.SenderAccount,
|
||||||
|
&i.Status,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.VerifiedBy,
|
||||||
|
&i.VerificationNotes,
|
||||||
|
&i.VerifiedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetCustomerDirectDeposits = `-- name: GetCustomerDirectDeposits :many
|
||||||
|
SELECT id, customer_id, wallet_id, amount, bank_reference, sender_account, status, created_at, verified_by, verification_notes, verified_at FROM direct_deposits WHERE customer_id = $1 ORDER BY created_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetCustomerDirectDeposits(ctx context.Context, customerID int64) ([]DirectDeposit, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetCustomerDirectDeposits, customerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []DirectDeposit
|
||||||
|
for rows.Next() {
|
||||||
|
var i DirectDeposit
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CustomerID,
|
||||||
|
&i.WalletID,
|
||||||
|
&i.Amount,
|
||||||
|
&i.BankReference,
|
||||||
|
&i.SenderAccount,
|
||||||
|
&i.Status,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.VerifiedBy,
|
||||||
|
&i.VerificationNotes,
|
||||||
|
&i.VerifiedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetDirectDeposit = `-- name: GetDirectDeposit :one
|
||||||
|
SELECT id, customer_id, wallet_id, amount, bank_reference, sender_account, status, created_at, verified_by, verification_notes, verified_at FROM direct_deposits WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetDirectDeposit(ctx context.Context, id int64) (DirectDeposit, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetDirectDeposit, id)
|
||||||
|
var i DirectDeposit
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CustomerID,
|
||||||
|
&i.WalletID,
|
||||||
|
&i.Amount,
|
||||||
|
&i.BankReference,
|
||||||
|
&i.SenderAccount,
|
||||||
|
&i.Status,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.VerifiedBy,
|
||||||
|
&i.VerificationNotes,
|
||||||
|
&i.VerifiedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetDirectDepositsByStatus = `-- name: GetDirectDepositsByStatus :many
|
||||||
|
SELECT id, customer_id, wallet_id, amount, bank_reference, sender_account, status, created_at, verified_by, verification_notes, verified_at FROM direct_deposits WHERE status = $1 ORDER BY created_at DESC
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetDirectDepositsByStatus(ctx context.Context, status string) ([]DirectDeposit, error) {
|
||||||
|
rows, err := q.db.Query(ctx, GetDirectDepositsByStatus, status)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []DirectDeposit
|
||||||
|
for rows.Next() {
|
||||||
|
var i DirectDeposit
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CustomerID,
|
||||||
|
&i.WalletID,
|
||||||
|
&i.Amount,
|
||||||
|
&i.BankReference,
|
||||||
|
&i.SenderAccount,
|
||||||
|
&i.Status,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.VerifiedBy,
|
||||||
|
&i.VerificationNotes,
|
||||||
|
&i.VerifiedAt,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateDirectDeposit = `-- name: UpdateDirectDeposit :one
|
||||||
|
UPDATE direct_deposits
|
||||||
|
SET
|
||||||
|
status = $2,
|
||||||
|
verified_by = $3,
|
||||||
|
verification_notes = $4,
|
||||||
|
verified_at = $5
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING id, customer_id, wallet_id, amount, bank_reference, sender_account, status, created_at, verified_by, verification_notes, verified_at
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateDirectDepositParams struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
VerifiedBy pgtype.Int8 `json:"verified_by"`
|
||||||
|
VerificationNotes pgtype.Text `json:"verification_notes"`
|
||||||
|
VerifiedAt pgtype.Timestamp `json:"verified_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateDirectDeposit(ctx context.Context, arg UpdateDirectDepositParams) (DirectDeposit, error) {
|
||||||
|
row := q.db.QueryRow(ctx, UpdateDirectDeposit,
|
||||||
|
arg.ID,
|
||||||
|
arg.Status,
|
||||||
|
arg.VerifiedBy,
|
||||||
|
arg.VerificationNotes,
|
||||||
|
arg.VerifiedAt,
|
||||||
|
)
|
||||||
|
var i DirectDeposit
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.CustomerID,
|
||||||
|
&i.WalletID,
|
||||||
|
&i.Amount,
|
||||||
|
&i.BankReference,
|
||||||
|
&i.SenderAccount,
|
||||||
|
&i.Status,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.VerifiedBy,
|
||||||
|
&i.VerificationNotes,
|
||||||
|
&i.VerifiedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
@ -234,6 +234,20 @@ type CustomerWalletDetail struct {
|
||||||
PhoneNumber pgtype.Text `json:"phone_number"`
|
PhoneNumber pgtype.Text `json:"phone_number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DirectDeposit struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
CustomerID int64 `json:"customer_id"`
|
||||||
|
WalletID int64 `json:"wallet_id"`
|
||||||
|
Amount pgtype.Numeric `json:"amount"`
|
||||||
|
BankReference string `json:"bank_reference"`
|
||||||
|
SenderAccount string `json:"sender_account"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
|
VerifiedBy pgtype.Int8 `json:"verified_by"`
|
||||||
|
VerificationNotes pgtype.Text `json:"verification_notes"`
|
||||||
|
VerifiedAt pgtype.Timestamp `json:"verified_at"`
|
||||||
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
SportID pgtype.Int4 `json:"sport_id"`
|
SportID pgtype.Int4 `json:"sport_id"`
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,12 @@ type ARIFPAYConfig struct {
|
||||||
SuccessUrl string `mapstructure:"VELI_BRAND_ID"`
|
SuccessUrl string `mapstructure:"VELI_BRAND_ID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SANTIMPAYConfig struct {
|
||||||
|
SecretKey string `mapstructure:"secret_key"`
|
||||||
|
MerchantID string `mapstructure:"merchant_id"`
|
||||||
|
BaseURL string `mapstructure:"base_url"`
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
FIXER_API_KEY string
|
FIXER_API_KEY string
|
||||||
FIXER_BASE_URL string
|
FIXER_BASE_URL string
|
||||||
|
|
@ -96,6 +102,7 @@ type Config struct {
|
||||||
AleaPlay AleaPlayConfig `mapstructure:"alea_play"`
|
AleaPlay AleaPlayConfig `mapstructure:"alea_play"`
|
||||||
VeliGames VeliConfig `mapstructure:"veli_games"`
|
VeliGames VeliConfig `mapstructure:"veli_games"`
|
||||||
ARIFPAY ARIFPAYConfig `mapstructure:"arifpay_config"`
|
ARIFPAY ARIFPAYConfig `mapstructure:"arifpay_config"`
|
||||||
|
SANTIMPAY SANTIMPAYConfig `mapstructure:"santimpay_config"`
|
||||||
ResendApiKey string
|
ResendApiKey string
|
||||||
ResendSenderEmail string
|
ResendSenderEmail string
|
||||||
TwilioAccountSid string
|
TwilioAccountSid string
|
||||||
|
|
@ -204,6 +211,10 @@ func (c *Config) loadEnv() error {
|
||||||
c.ARIFPAY.NotifyUrl = os.Getenv("ARIFPAY_NOTIFY_URL")
|
c.ARIFPAY.NotifyUrl = os.Getenv("ARIFPAY_NOTIFY_URL")
|
||||||
c.ARIFPAY.SuccessUrl = os.Getenv("ARIFPAY_SUCCESS_URL")
|
c.ARIFPAY.SuccessUrl = os.Getenv("ARIFPAY_SUCCESS_URL")
|
||||||
|
|
||||||
|
c.SANTIMPAY.SecretKey = os.Getenv("SANTIMPAY_SECRET_KEY")
|
||||||
|
c.SANTIMPAY.MerchantID = os.Getenv("SANTIMPAY_MERCHANT_ID")
|
||||||
|
c.SANTIMPAY.BaseURL = os.Getenv("SANTIMPAY_Base_URL")
|
||||||
|
|
||||||
//Alea Play
|
//Alea Play
|
||||||
aleaEnabled := os.Getenv("ALEA_ENABLED")
|
aleaEnabled := os.Getenv("ALEA_ENABLED")
|
||||||
if aleaEnabled == "" {
|
if aleaEnabled == "" {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ type NotificationDeliveryStatus string
|
||||||
type DeliveryChannel string
|
type DeliveryChannel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
NotificationTypeDepositResult NotificationType = "deposit_result"
|
||||||
|
NotificationTypeDepositVerification NotificationType = "deposit_verification"
|
||||||
NotificationTypeCashOutSuccess NotificationType = "cash_out_success"
|
NotificationTypeCashOutSuccess NotificationType = "cash_out_success"
|
||||||
NotificationTypeDepositSuccess NotificationType = "deposit_success"
|
NotificationTypeDepositSuccess NotificationType = "deposit_success"
|
||||||
NotificationTypeWithdrawSuccess NotificationType = "withdraw_success"
|
NotificationTypeWithdrawSuccess NotificationType = "withdraw_success"
|
||||||
|
|
|
||||||
25
internal/domain/santimpay.go
Normal file
25
internal/domain/santimpay.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type GeneratePaymentURLInput struct {
|
||||||
|
ID string
|
||||||
|
Amount int
|
||||||
|
Reason string
|
||||||
|
PhoneNumber string
|
||||||
|
// SuccessRedirectURL string
|
||||||
|
// FailureRedirectURL string
|
||||||
|
// CancelRedirectURL string
|
||||||
|
// NotifyURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitiatePaymentPayload struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
Reason string `json:"paymentReason"`
|
||||||
|
MerchantID string `json:"merchantId"`
|
||||||
|
SignedToken string `json:"signedToken"`
|
||||||
|
SuccessRedirectURL string `json:"successRedirectUrl"`
|
||||||
|
FailureRedirectURL string `json:"failureRedirectUrl"`
|
||||||
|
NotifyURL string `json:"notifyUrl"`
|
||||||
|
CancelRedirectURL string `json:"cancelRedirectUrl"`
|
||||||
|
PhoneNumber string `json:"phoneNumber"`
|
||||||
|
}
|
||||||
|
|
@ -80,3 +80,58 @@ const (
|
||||||
BranchWalletType WalletType = "branch_wallet"
|
BranchWalletType WalletType = "branch_wallet"
|
||||||
CompanyWalletType WalletType = "company_wallet"
|
CompanyWalletType WalletType = "company_wallet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// domain/wallet.go
|
||||||
|
|
||||||
|
type DirectDepositStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DepositStatusPending DirectDepositStatus = "pending"
|
||||||
|
DepositStatusCompleted DirectDepositStatus = "completed"
|
||||||
|
DepositStatusRejected DirectDepositStatus = "rejected"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DirectDeposit struct {
|
||||||
|
ID int64
|
||||||
|
CustomerID int64
|
||||||
|
WalletID int64
|
||||||
|
Wallet Wallet // Joined data
|
||||||
|
Amount Currency
|
||||||
|
BankReference string
|
||||||
|
SenderAccount string
|
||||||
|
Status DirectDepositStatus
|
||||||
|
CreatedAt time.Time
|
||||||
|
VerifiedBy *int64 // Nullable
|
||||||
|
VerificationNotes string
|
||||||
|
VerifiedAt *time.Time // Nullable
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateDirectDeposit struct {
|
||||||
|
CustomerID int64
|
||||||
|
WalletID int64
|
||||||
|
Amount Currency
|
||||||
|
BankReference string
|
||||||
|
SenderAccount string
|
||||||
|
Status DirectDepositStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateDirectDeposit struct {
|
||||||
|
ID int64
|
||||||
|
Status DirectDepositStatus
|
||||||
|
VerifiedBy int64
|
||||||
|
VerificationNotes string
|
||||||
|
VerifiedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type DirectDepositRequest struct {
|
||||||
|
CustomerID int64 `json:"customer_id" binding:"required"`
|
||||||
|
Amount Currency `json:"amount" binding:"required,gt=0"`
|
||||||
|
BankReference string `json:"bank_reference" binding:"required"`
|
||||||
|
SenderAccount string `json:"sender_account" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyDirectDepositRequest struct {
|
||||||
|
DepositID int64 `json:"deposit_id" binding:"required"`
|
||||||
|
IsVerified bool `json:"is_verified" binding:"required"`
|
||||||
|
Notes string `json:"notes"`
|
||||||
|
}
|
||||||
|
|
|
||||||
112
internal/repository/direct_deposit.go
Normal file
112
internal/repository/direct_deposit.go
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
func convertDBDirectDeposit(deposit dbgen.DirectDeposit) domain.DirectDeposit {
|
||||||
|
return domain.DirectDeposit{
|
||||||
|
ID: deposit.ID,
|
||||||
|
CustomerID: deposit.CustomerID,
|
||||||
|
WalletID: deposit.WalletID,
|
||||||
|
Amount: domain.Currency(deposit.Amount.Int.Int64()),
|
||||||
|
BankReference: deposit.BankReference,
|
||||||
|
SenderAccount: deposit.SenderAccount,
|
||||||
|
Status: domain.DirectDepositStatus(deposit.Status),
|
||||||
|
CreatedAt: deposit.CreatedAt.Time,
|
||||||
|
VerifiedBy: convertPgInt64ToPtr(deposit.VerifiedBy),
|
||||||
|
VerificationNotes: deposit.VerificationNotes.String,
|
||||||
|
VerifiedAt: convertPgTimeToPtr(deposit.VerifiedAt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertCreateDirectDeposit(deposit domain.CreateDirectDeposit) dbgen.CreateDirectDepositParams {
|
||||||
|
return dbgen.CreateDirectDepositParams{
|
||||||
|
CustomerID: deposit.CustomerID,
|
||||||
|
WalletID: deposit.WalletID,
|
||||||
|
Amount: pgtype.Numeric{Int: big.NewInt(int64(deposit.Amount)), Valid: true},
|
||||||
|
BankReference: deposit.BankReference,
|
||||||
|
SenderAccount: deposit.SenderAccount,
|
||||||
|
Status: string(deposit.Status),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertUpdateDirectDeposit(deposit domain.UpdateDirectDeposit) dbgen.UpdateDirectDepositParams {
|
||||||
|
return dbgen.UpdateDirectDepositParams{
|
||||||
|
ID: deposit.ID,
|
||||||
|
Status: string(deposit.Status),
|
||||||
|
VerifiedBy: pgtype.Int8{Int64: deposit.VerifiedBy, Valid: true},
|
||||||
|
VerificationNotes: pgtype.Text{String: deposit.VerificationNotes, Valid: deposit.VerificationNotes != ""},
|
||||||
|
VerifiedAt: pgtype.Timestamp{Time: deposit.VerifiedAt, Valid: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertPgInt64ToPtr(i pgtype.Int8) *int64 {
|
||||||
|
if i.Valid {
|
||||||
|
return &i.Int64
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertPgTimeToPtr(t pgtype.Timestamp) *time.Time {
|
||||||
|
if t.Valid {
|
||||||
|
return &t.Time
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) CreateDirectDeposit(ctx context.Context, deposit domain.CreateDirectDeposit) (domain.DirectDeposit, error) {
|
||||||
|
newDeposit, err := s.queries.CreateDirectDeposit(ctx, convertCreateDirectDeposit(deposit))
|
||||||
|
if err != nil {
|
||||||
|
return domain.DirectDeposit{}, err
|
||||||
|
}
|
||||||
|
return convertDBDirectDeposit(newDeposit), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetDirectDeposit(ctx context.Context, id int64) (domain.DirectDeposit, error) {
|
||||||
|
deposit, err := s.queries.GetDirectDeposit(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return domain.DirectDeposit{}, err
|
||||||
|
}
|
||||||
|
return convertDBDirectDeposit(deposit), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdateDirectDeposit(ctx context.Context, deposit domain.UpdateDirectDeposit) (domain.DirectDeposit, error) {
|
||||||
|
updatedDeposit, err := s.queries.UpdateDirectDeposit(ctx, convertUpdateDirectDeposit(deposit))
|
||||||
|
if err != nil {
|
||||||
|
return domain.DirectDeposit{}, err
|
||||||
|
}
|
||||||
|
return convertDBDirectDeposit(updatedDeposit), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetDirectDepositsByStatus(ctx context.Context, status domain.DirectDepositStatus) ([]domain.DirectDeposit, error) {
|
||||||
|
deposits, err := s.queries.GetDirectDepositsByStatus(ctx, string(status))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]domain.DirectDeposit, 0, len(deposits))
|
||||||
|
for _, deposit := range deposits {
|
||||||
|
result = append(result, convertDBDirectDeposit(deposit))
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetCustomerDirectDeposits(ctx context.Context, customerID int64) ([]domain.DirectDeposit, error) {
|
||||||
|
deposits, err := s.queries.GetCustomerDirectDeposits(ctx, customerID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]domain.DirectDeposit, 0, len(deposits))
|
||||||
|
for _, deposit := range deposits {
|
||||||
|
result = append(result, convertDBDirectDeposit(deposit))
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
53
internal/services/santimpay/client.go
Normal file
53
internal/services/santimpay/client.go
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package santimpay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SantimPayClient interface {
|
||||||
|
GenerateSignedToken(amount int, reason string) (string, error)
|
||||||
|
CheckTransactionStatus(id string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type santimClient struct {
|
||||||
|
cfg *config.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSantimPayClient(cfg *config.Config) SantimPayClient {
|
||||||
|
return &santimClient{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *santimClient) GenerateSignedToken(amount int, reason string) (string, error) {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
claims := jwt.MapClaims{
|
||||||
|
"amount": amount,
|
||||||
|
"paymentReason": reason,
|
||||||
|
"merchantId": c.cfg.SANTIMPAY.MerchantID,
|
||||||
|
"generated": now,
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
|
||||||
|
privateKey, err := jwt.ParseECPrivateKeyFromPEM([]byte(c.cfg.SANTIMPAY.SecretKey))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("invalid private key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signedToken, err := token.SignedString(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("signing failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *santimClient) CheckTransactionStatus(id string) {
|
||||||
|
// optional async checker — can log or poll transaction status
|
||||||
|
fmt.Println("Checking transaction status for:", id)
|
||||||
|
}
|
||||||
92
internal/services/santimpay/service.go
Normal file
92
internal/services/santimpay/service.go
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
package santimpay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// type SantimPayService interface {
|
||||||
|
// GeneratePaymentURL(input domain.GeneratePaymentURLInput) (map[string]string, error)
|
||||||
|
// }
|
||||||
|
|
||||||
|
type SantimPayService struct {
|
||||||
|
client SantimPayClient
|
||||||
|
cfg *config.Config
|
||||||
|
transferStore wallet.TransferStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSantimPayService(client SantimPayClient, cfg *config.Config, transferStore wallet.TransferStore) *SantimPayService {
|
||||||
|
return &SantimPayService{
|
||||||
|
client: client,
|
||||||
|
cfg: cfg,
|
||||||
|
transferStore: transferStore,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SantimPayService) GeneratePaymentURL(input domain.GeneratePaymentURLInput) (map[string]string, error) {
|
||||||
|
paymentID := uuid.NewString()
|
||||||
|
|
||||||
|
token, err := s.client.GenerateSignedToken(input.Amount, input.Reason)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("token generation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := domain.InitiatePaymentPayload{
|
||||||
|
ID: paymentID,
|
||||||
|
Amount: input.Amount,
|
||||||
|
Reason: input.Reason,
|
||||||
|
MerchantID: s.cfg.SANTIMPAY.MerchantID,
|
||||||
|
SignedToken: token,
|
||||||
|
SuccessRedirectURL: s.cfg.ARIFPAY.SuccessUrl,
|
||||||
|
FailureRedirectURL: s.cfg.ARIFPAY.ErrorUrl,
|
||||||
|
NotifyURL: s.cfg.ARIFPAY.NotifyUrl,
|
||||||
|
CancelRedirectURL: s.cfg.ARIFPAY.CancelUrl,
|
||||||
|
PhoneNumber: input.PhoneNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Post(s.cfg.SANTIMPAY.BaseURL+"/initiate-payment", "application/json", bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to send HTTP request: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("non-200 status code received: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var responseBody map[string]string
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&responseBody); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save transfer
|
||||||
|
transfer := domain.CreateTransfer{
|
||||||
|
Amount: domain.Currency(input.Amount),
|
||||||
|
Verified: false,
|
||||||
|
Type: domain.DEPOSIT,
|
||||||
|
ReferenceNumber: paymentID,
|
||||||
|
Status: string(domain.PaymentStatusPending),
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.transferStore.CreateTransfer(context.Background(), transfer); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create transfer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally check transaction status in a goroutine
|
||||||
|
go s.client.CheckTransactionStatus(paymentID)
|
||||||
|
|
||||||
|
return responseBody, nil
|
||||||
|
}
|
||||||
217
internal/services/wallet/direct_deposit.go
Normal file
217
internal/services/wallet/direct_deposit.go
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
package wallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitiateDirectDeposit creates a pending deposit request
|
||||||
|
func (s *Service) InitiateDirectDeposit(
|
||||||
|
ctx context.Context,
|
||||||
|
customerID int64,
|
||||||
|
amount domain.Currency,
|
||||||
|
bankRef string, // Mobile banking transaction reference
|
||||||
|
senderAccount string, // Customer's account number
|
||||||
|
) (domain.DirectDeposit, error) {
|
||||||
|
// Get customer's betting wallet
|
||||||
|
customerWallet, err := s.GetCustomerWallet(ctx, customerID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.DirectDeposit{}, fmt.Errorf("failed to get customer wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create pending deposit record
|
||||||
|
deposit, err := s.directDepositStore.CreateDirectDeposit(ctx, domain.CreateDirectDeposit{
|
||||||
|
CustomerID: customerID,
|
||||||
|
WalletID: customerWallet.ID,
|
||||||
|
Amount: amount,
|
||||||
|
BankReference: bankRef,
|
||||||
|
SenderAccount: senderAccount,
|
||||||
|
Status: domain.DepositStatusPending,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.DirectDeposit{}, fmt.Errorf("failed to create deposit record: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify cashiers for manual verification
|
||||||
|
go s.notifyCashiersForVerification(ctx, deposit.ID, customerID, amount)
|
||||||
|
|
||||||
|
return deposit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyDirectDeposit verifies and processes the deposit
|
||||||
|
func (s *Service) VerifyDirectDeposit(
|
||||||
|
ctx context.Context,
|
||||||
|
depositID int64,
|
||||||
|
cashierID int64,
|
||||||
|
isVerified bool,
|
||||||
|
verificationNotes string,
|
||||||
|
) (domain.DirectDeposit, error) {
|
||||||
|
// Get the deposit record
|
||||||
|
deposit, err := s.directDepositStore.GetDirectDeposit(ctx, depositID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.DirectDeposit{}, fmt.Errorf("failed to get deposit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate deposit status
|
||||||
|
if deposit.Status != domain.DepositStatusPending {
|
||||||
|
return domain.DirectDeposit{}, errors.New("only pending deposits can be verified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update based on verification result
|
||||||
|
if isVerified {
|
||||||
|
// Credit the wallet
|
||||||
|
err = s.walletStore.UpdateBalance(ctx, deposit.WalletID,
|
||||||
|
deposit.Wallet.Balance+deposit.Amount)
|
||||||
|
if err != nil {
|
||||||
|
return domain.DirectDeposit{}, fmt.Errorf("failed to update wallet balance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish wallet update event
|
||||||
|
go s.publishWalletUpdate(ctx, deposit.WalletID, deposit.Wallet.UserID,
|
||||||
|
deposit.Wallet.Balance+deposit.Amount, "direct_deposit_verified")
|
||||||
|
|
||||||
|
// Update deposit status
|
||||||
|
deposit.Status = domain.DepositStatusCompleted
|
||||||
|
} else {
|
||||||
|
deposit.Status = domain.DepositStatusRejected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update deposit record
|
||||||
|
updatedDeposit, err := s.directDepositStore.UpdateDirectDeposit(ctx, domain.UpdateDirectDeposit{
|
||||||
|
ID: depositID,
|
||||||
|
Status: deposit.Status,
|
||||||
|
VerifiedBy: cashierID,
|
||||||
|
VerificationNotes: verificationNotes,
|
||||||
|
VerifiedAt: time.Now(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return domain.DirectDeposit{}, fmt.Errorf("failed to update deposit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify customer of verification result
|
||||||
|
go s.notifyCustomerVerificationResult(ctx, updatedDeposit)
|
||||||
|
|
||||||
|
return updatedDeposit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPendingDirectDeposits returns deposits needing verification
|
||||||
|
func (s *Service) GetPendingDirectDeposits(ctx context.Context) ([]domain.DirectDeposit, error) {
|
||||||
|
return s.directDepositStore.GetDirectDepositsByStatus(ctx, domain.DepositStatusPending)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
func (s *Service) notifyCashiersForVerification(ctx context.Context, depositID, customerID int64, amount domain.Currency) {
|
||||||
|
cashiers, _, err := s.userSvc.GetAllCashiers(ctx, domain.UserFilter{Role: string(domain.RoleCashier)})
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to get cashiers for notification",
|
||||||
|
"error", err,
|
||||||
|
"deposit_id", depositID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
customer, err := s.userSvc.GetUserByID(ctx, customerID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to get customer details",
|
||||||
|
"error", err,
|
||||||
|
"customer_id", customerID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cashier := range cashiers {
|
||||||
|
metadataMap := map[string]interface{}{
|
||||||
|
"deposit_id": depositID,
|
||||||
|
"customer_id": customerID,
|
||||||
|
"amount": amount.Float32(),
|
||||||
|
}
|
||||||
|
metadataJSON, err := json.Marshal(metadataMap)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to marshal notification metadata",
|
||||||
|
"error", err,
|
||||||
|
"deposit_id", depositID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
notification := &domain.Notification{
|
||||||
|
RecipientID: cashier.ID,
|
||||||
|
Type: domain.NotificationTypeDepositVerification,
|
||||||
|
Level: domain.NotificationLevelInfo,
|
||||||
|
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||||
|
Payload: domain.NotificationPayload{
|
||||||
|
Headline: "Direct Deposit Requires Verification",
|
||||||
|
Message: fmt.Sprintf("Customer %s deposited %.2f - please verify", customer.FirstName+" "+customer.LastName, amount.Float32()),
|
||||||
|
},
|
||||||
|
Metadata: metadataJSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.notificationStore.SendNotification(ctx, notification); err != nil {
|
||||||
|
s.logger.Error("failed to send verification notification",
|
||||||
|
"cashier_id", cashier.ID,
|
||||||
|
"error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) notifyCustomerVerificationResult(ctx context.Context, deposit domain.DirectDeposit) {
|
||||||
|
var (
|
||||||
|
headline string
|
||||||
|
message string
|
||||||
|
level domain.NotificationLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
if deposit.Status == domain.DepositStatusCompleted {
|
||||||
|
headline = "Deposit Verified"
|
||||||
|
message = fmt.Sprintf("Your deposit of %.2f has been credited to your wallet", deposit.Amount.Float32())
|
||||||
|
level = domain.NotificationLevelSuccess
|
||||||
|
} else {
|
||||||
|
headline = "Deposit Rejected"
|
||||||
|
message = fmt.Sprintf("Your deposit of %.2f was not verified. Reason: %s",
|
||||||
|
deposit.Amount.Float32(), deposit.VerificationNotes)
|
||||||
|
level = domain.NotificationLevelError
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataMap := map[string]interface{}{
|
||||||
|
"deposit_id": deposit.ID,
|
||||||
|
"amount": deposit.Amount.Float32(),
|
||||||
|
"status": string(deposit.Status),
|
||||||
|
}
|
||||||
|
metadataJSON, err := json.Marshal(metadataMap)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to marshal notification metadata",
|
||||||
|
"error", err,
|
||||||
|
"deposit_id", deposit.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notification := &domain.Notification{
|
||||||
|
RecipientID: deposit.CustomerID,
|
||||||
|
Type: domain.NotificationTypeDepositResult,
|
||||||
|
Level: level,
|
||||||
|
DeliveryChannel: domain.DeliveryChannelInApp,
|
||||||
|
Payload: domain.NotificationPayload{
|
||||||
|
Headline: headline,
|
||||||
|
Message: message,
|
||||||
|
},
|
||||||
|
Metadata: metadataJSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.notificationStore.SendNotification(ctx, notification); err != nil {
|
||||||
|
s.logger.Error("failed to send deposit result notification",
|
||||||
|
"customer_id", deposit.CustomerID,
|
||||||
|
"error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) publishWalletUpdate(ctx context.Context, walletID, userID int64, newBalance domain.Currency, trigger string) {
|
||||||
|
s.kafkaProducer.Publish(ctx, fmt.Sprint(walletID), event.WalletEvent{
|
||||||
|
EventType: event.WalletBalanceUpdated,
|
||||||
|
WalletID: walletID,
|
||||||
|
UserID: userID,
|
||||||
|
Balance: newBalance,
|
||||||
|
Trigger: trigger,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -45,3 +45,11 @@ type ApprovalStore interface {
|
||||||
GetApprovalsByTransfer(ctx context.Context, transferID int64) ([]domain.TransactionApproval, error)
|
GetApprovalsByTransfer(ctx context.Context, transferID int64) ([]domain.TransactionApproval, error)
|
||||||
GetPendingApprovals(ctx context.Context) ([]domain.TransferDetail, error)
|
GetPendingApprovals(ctx context.Context) ([]domain.TransferDetail, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DirectDepositStore interface {
|
||||||
|
CreateDirectDeposit(ctx context.Context, deposit domain.CreateDirectDeposit) (domain.DirectDeposit, error)
|
||||||
|
GetDirectDeposit(ctx context.Context, id int64) (domain.DirectDeposit, error)
|
||||||
|
UpdateDirectDeposit(ctx context.Context, deposit domain.UpdateDirectDeposit) (domain.DirectDeposit, error)
|
||||||
|
GetDirectDepositsByStatus(ctx context.Context, status domain.DirectDepositStatus) ([]domain.DirectDeposit, error)
|
||||||
|
GetCustomerDirectDeposits(ctx context.Context, customerID int64) ([]domain.DirectDeposit, error)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ type Service struct {
|
||||||
// approvalStore ApprovalStore
|
// approvalStore ApprovalStore
|
||||||
walletStore WalletStore
|
walletStore WalletStore
|
||||||
transferStore TransferStore
|
transferStore TransferStore
|
||||||
|
directDepositStore DirectDepositStore
|
||||||
notificationStore notificationservice.NotificationStore
|
notificationStore notificationservice.NotificationStore
|
||||||
notificationSvc *notificationservice.Service
|
notificationSvc *notificationservice.Service
|
||||||
userSvc *user.Service
|
userSvc *user.Service
|
||||||
|
|
@ -24,6 +25,7 @@ type Service struct {
|
||||||
func NewService(
|
func NewService(
|
||||||
walletStore WalletStore,
|
walletStore WalletStore,
|
||||||
transferStore TransferStore,
|
transferStore TransferStore,
|
||||||
|
directDepositStore DirectDepositStore,
|
||||||
notificationStore notificationservice.NotificationStore,
|
notificationStore notificationservice.NotificationStore,
|
||||||
notificationSvc *notificationservice.Service,
|
notificationSvc *notificationservice.Service,
|
||||||
userSvc *user.Service,
|
userSvc *user.Service,
|
||||||
|
|
@ -34,6 +36,7 @@ func NewService(
|
||||||
return &Service{
|
return &Service{
|
||||||
walletStore: walletStore,
|
walletStore: walletStore,
|
||||||
transferStore: transferStore,
|
transferStore: transferStore,
|
||||||
|
directDepositStore: directDepositStore,
|
||||||
// approvalStore: approvalStore,
|
// approvalStore: approvalStore,
|
||||||
notificationStore: notificationStore,
|
notificationStore: notificationStore,
|
||||||
notificationSvc: notificationSvc,
|
notificationSvc: notificationSvc,
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/santimpay"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||||
|
|
@ -42,6 +43,7 @@ import (
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
arifpaySvc *arifpay.ArifpayService
|
arifpaySvc *arifpay.ArifpayService
|
||||||
|
santimpaySvc *santimpay.SantimPayService
|
||||||
issueReportingSvc *issuereporting.Service
|
issueReportingSvc *issuereporting.Service
|
||||||
instSvc *institutions.Service
|
instSvc *institutions.Service
|
||||||
currSvc *currency.Service
|
currSvc *currency.Service
|
||||||
|
|
@ -79,6 +81,7 @@ type App struct {
|
||||||
|
|
||||||
func NewApp(
|
func NewApp(
|
||||||
arifpaySvc *arifpay.ArifpayService,
|
arifpaySvc *arifpay.ArifpayService,
|
||||||
|
santimpaySvc *santimpay.SantimPayService,
|
||||||
issueReportingSvc *issuereporting.Service,
|
issueReportingSvc *issuereporting.Service,
|
||||||
instSvc *institutions.Service,
|
instSvc *institutions.Service,
|
||||||
currSvc *currency.Service,
|
currSvc *currency.Service,
|
||||||
|
|
@ -126,6 +129,7 @@ func NewApp(
|
||||||
|
|
||||||
s := &App{
|
s := &App{
|
||||||
arifpaySvc: arifpaySvc,
|
arifpaySvc: arifpaySvc,
|
||||||
|
santimpaySvc: santimpaySvc,
|
||||||
issueReportingSvc: issueReportingSvc,
|
issueReportingSvc: issueReportingSvc,
|
||||||
instSvc: instSvc,
|
instSvc: instSvc,
|
||||||
currSvc: currSvc,
|
currSvc: currSvc,
|
||||||
|
|
|
||||||
124
internal/web_server/handlers/direct_deposit.go
Normal file
124
internal/web_server/handlers/direct_deposit.go
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitiateDirectDeposit godoc
|
||||||
|
// @Summary Initiate a direct deposit
|
||||||
|
// @Description Customer initiates a direct deposit from mobile banking
|
||||||
|
// @Tags Direct Deposits
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body domain.DirectDepositRequest true "Deposit details"
|
||||||
|
// @Success 201 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/direct_deposit [post]
|
||||||
|
func (h *Handler) InitiateDirectDeposit(c *fiber.Ctx) error {
|
||||||
|
var req domain.DirectDepositRequest
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Error: err.Error(),
|
||||||
|
Message: "Invalid request payload",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deposit, err := h.walletSvc.InitiateDirectDeposit(
|
||||||
|
c.Context(),
|
||||||
|
req.CustomerID,
|
||||||
|
req.Amount,
|
||||||
|
req.BankReference,
|
||||||
|
req.SenderAccount,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Error: err.Error(),
|
||||||
|
Message: "Failed to initiate direct deposit",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
||||||
|
Message: "Direct deposit initiated successfully",
|
||||||
|
Data: deposit,
|
||||||
|
Success: true,
|
||||||
|
StatusCode: fiber.StatusCreated,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyDirectDeposit godoc
|
||||||
|
// @Summary Verify a direct deposit
|
||||||
|
// @Description Cashier verifies a direct deposit transaction
|
||||||
|
// @Tags Direct Deposits
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body domain.VerifyDepositRequest true "Verification details"
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 401 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/direct_deposit/verify [post]
|
||||||
|
func (h *Handler) VerifyDirectDeposit(c *fiber.Ctx) error {
|
||||||
|
var req domain.VerifyDirectDepositRequest
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Error: err.Error(),
|
||||||
|
Message: "Invalid verification request",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cashierID := c.Locals("user_id")
|
||||||
|
if cashierID == nil {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||||
|
Error: "missing user_id in context",
|
||||||
|
Message: "Unauthorized access",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deposit, err := h.walletSvc.VerifyDirectDeposit(
|
||||||
|
c.Context(),
|
||||||
|
req.DepositID,
|
||||||
|
cashierID.(int64),
|
||||||
|
req.IsVerified,
|
||||||
|
req.Notes,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Error: err.Error(),
|
||||||
|
Message: "Failed to verify deposit",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Deposit verification processed successfully",
|
||||||
|
Data: deposit,
|
||||||
|
Success: true,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPendingDeposits godoc
|
||||||
|
// @Summary Get pending direct deposits
|
||||||
|
// @Description Get list of direct deposits needing verification
|
||||||
|
// @Tags Direct Deposits
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/direct_deposit/pending [get]
|
||||||
|
func (h *Handler) GetPendingDirectDeposits(c *fiber.Ctx) error {
|
||||||
|
deposits, err := h.walletSvc.GetPendingDirectDeposits(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Error: err.Error(),
|
||||||
|
Message: "Failed to retrieve pending deposits",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Pending deposits retrieved successfully",
|
||||||
|
Data: deposits,
|
||||||
|
Success: true,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/report"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/result"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/santimpay"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/settings"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||||
|
|
@ -37,6 +38,7 @@ import (
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
arifpaySvc *arifpay.ArifpayService
|
arifpaySvc *arifpay.ArifpayService
|
||||||
|
santimpaySvc *santimpay.SantimPayService
|
||||||
issueReportingSvc *issuereporting.Service
|
issueReportingSvc *issuereporting.Service
|
||||||
instSvc *institutions.Service
|
instSvc *institutions.Service
|
||||||
currSvc *currency.Service
|
currSvc *currency.Service
|
||||||
|
|
@ -71,6 +73,7 @@ type Handler struct {
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
arifpaySvc *arifpay.ArifpayService,
|
arifpaySvc *arifpay.ArifpayService,
|
||||||
|
santimpaySvc *santimpay.SantimPayService,
|
||||||
issueReportingSvc *issuereporting.Service,
|
issueReportingSvc *issuereporting.Service,
|
||||||
instSvc *institutions.Service,
|
instSvc *institutions.Service,
|
||||||
currSvc *currency.Service,
|
currSvc *currency.Service,
|
||||||
|
|
@ -104,6 +107,7 @@ func New(
|
||||||
) *Handler {
|
) *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
arifpaySvc: arifpaySvc,
|
arifpaySvc: arifpaySvc,
|
||||||
|
santimpaySvc: santimpaySvc,
|
||||||
issueReportingSvc: issueReportingSvc,
|
issueReportingSvc: issueReportingSvc,
|
||||||
instSvc: instSvc,
|
instSvc: instSvc,
|
||||||
currSvc: currSvc,
|
currSvc: currSvc,
|
||||||
|
|
|
||||||
43
internal/web_server/handlers/santimpay.go
Normal file
43
internal/web_server/handlers/santimpay.go
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateSantimPayPaymentHandler initializes a payment session with SantimPay.
|
||||||
|
//
|
||||||
|
// @Summary Create SantimPay Payment Session
|
||||||
|
// @Description Generates a payment URL using SantimPay and returns it to the client.
|
||||||
|
// @Tags SantimPay
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body domain.GeneratePaymentURLInput true "SantimPay payment request payload"
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/santimpay/payment [post]
|
||||||
|
func (h *Handler) CreateSantimPayPaymentHandler(c *fiber.Ctx) error {
|
||||||
|
var req domain.GeneratePaymentURLInput
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Error: err.Error(),
|
||||||
|
Message: "Failed to process your request",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentURL, err := h.santimpaySvc.GeneratePaymentURL(req)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Error: err.Error(),
|
||||||
|
Message: "Failed to initiate SantimPay payment session",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "SantimPay payment URL generated successfully",
|
||||||
|
Data: paymentURL,
|
||||||
|
Success: true,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
func (a *App) initAppRoutes() {
|
func (a *App) initAppRoutes() {
|
||||||
h := handlers.New(
|
h := handlers.New(
|
||||||
a.arifpaySvc,
|
a.arifpaySvc,
|
||||||
|
a.santimpaySvc,
|
||||||
a.issueReportingSvc,
|
a.issueReportingSvc,
|
||||||
a.instSvc,
|
a.instSvc,
|
||||||
a.currSvc,
|
a.currSvc,
|
||||||
|
|
@ -60,10 +61,18 @@ func (a *App) initAppRoutes() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
groupV1 := a.fiber.Group("/api/v1")
|
||||||
|
|
||||||
|
//Direct_deposit
|
||||||
|
groupV1.Post("/direct_deposit", a.authMiddleware, h.InitiateDirectDeposit)
|
||||||
|
groupV1.Post("/direct_deposit/verify", a.authMiddleware, h.VerifyDirectDeposit)
|
||||||
|
groupV1.Get("/direct_deposit/pending", a.authMiddleware, h.GetPendingDirectDeposits)
|
||||||
|
groupV1.Post("/auth/admin-login", h.LoginAdmin)
|
||||||
|
groupV1.Post("/auth/refresh", h.RefreshToken)
|
||||||
|
|
||||||
// Swagger
|
// Swagger
|
||||||
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
|
a.fiber.Get("/swagger/*", fiberSwagger.FiberWrapHandler())
|
||||||
|
|
||||||
groupV1 := a.fiber.Group("/api/v1")
|
|
||||||
groupV1.Get("/", func(c *fiber.Ctx) error {
|
groupV1.Get("/", func(c *fiber.Ctx) error {
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"message": "FortuneBet API V1 pre-alpha",
|
"message": "FortuneBet API V1 pre-alpha",
|
||||||
|
|
@ -108,6 +117,12 @@ func (a *App) initAppRoutes() {
|
||||||
groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler)
|
groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler)
|
||||||
groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
|
groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
|
||||||
|
|
||||||
|
//Santimpay
|
||||||
|
groupV1.Post("/santimpay/init-payment", h.CreateSantimPayPaymentHandler)
|
||||||
|
// groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.B2CTransferHandler)
|
||||||
|
// groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler)
|
||||||
|
// groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
|
||||||
|
|
||||||
// User Routes
|
// User Routes
|
||||||
groupV1.Post("/user/resetPassword", h.ResetPassword)
|
groupV1.Post("/user/resetPassword", h.ResetPassword)
|
||||||
groupV1.Post("/user/sendResetCode", h.SendResetCode)
|
groupV1.Post("/user/sendResetCode", h.SendResetCode)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user