feat: popok itegratio
This commit is contained in:
parent
8670fba6a4
commit
02e6f6ee6f
|
|
@ -125,6 +125,7 @@
|
|||
│ ├── bet_handler.go
|
||||
│ ├── handlers.go
|
||||
│ ├── notification_handler.go
|
||||
│ ├── referal_handlers.go
|
||||
│ ├── ticket_handler.go
|
||||
│ ├── transaction_handler.go
|
||||
│ ├── user.go
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server"
|
||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||
|
|
@ -65,14 +66,16 @@ func main() {
|
|||
|
||||
notificationRepo := repository.NewNotificationRepository(store)
|
||||
referalRepo := repository.NewReferralRepository(store)
|
||||
vitualGameRepo := repository.NewVirtualGameRepository(store)
|
||||
|
||||
notificationSvc := notificationservice.New(notificationRepo, logger, cfg)
|
||||
referalSvc := referralservice.New(referalRepo, *walletSvc, store, cfg, logger)
|
||||
virtualGameSvc := virtualgameservice.New(vitualGameRepo, *walletSvc, store, cfg, logger)
|
||||
|
||||
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
|
||||
JwtAccessKey: cfg.JwtKey,
|
||||
JwtAccessExpiry: cfg.AccessExpiry,
|
||||
}, userSvc, ticketSvc, betSvc, walletSvc, transactionSvc, notificationSvc, referalSvc)
|
||||
}, userSvc, ticketSvc, betSvc, walletSvc, transactionSvc, notificationSvc, referalSvc, virtualGameSvc)
|
||||
logger.Info("Starting server", "port", cfg.Port)
|
||||
|
||||
if err := app.Run(); err != nil {
|
||||
|
|
|
|||
3
db/migrations/000004_virtual_game_Sessios.down.sql
Normal file
3
db/migrations/000004_virtual_game_Sessios.down.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
DROP TABLE IF EXISTS virtual_game_transactions;
|
||||
|
||||
DROP TABLE IF EXISTS virtual_game_sessions;
|
||||
29
db/migrations/000004_virtual_game_Sessios.up.sql
Normal file
29
db/migrations/000004_virtual_game_Sessios.up.sql
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
CREATE TABLE virtual_game_sessions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||
game_id VARCHAR(50) NOT NULL,
|
||||
session_token VARCHAR(255) NOT NULL UNIQUE,
|
||||
currency VARCHAR(3) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE', -- ACTIVE, COMPLETED, FAILED
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE virtual_game_transactions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
session_id BIGINT NOT NULL REFERENCES virtual_game_sessions(id),
|
||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||
wallet_id BIGINT NOT NULL REFERENCES wallets(id),
|
||||
transaction_type VARCHAR(20) NOT NULL,
|
||||
amount BIGINT NOT NULL,
|
||||
currency VARCHAR(3) NOT NULL,
|
||||
external_transaction_id VARCHAR(100) NOT NULL UNIQUE, -- PopOK transaction ID
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'PENDING', -- PENDING, COMPLETED, FAILED
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_virtual_game_sessions_user_id ON virtual_game_sessions(user_id);
|
||||
CREATE INDEX idx_virtual_game_transactions_session_id ON virtual_game_transactions(session_id);
|
||||
CREATE INDEX idx_virtual_game_transactions_user_id ON virtual_game_transactions(user_id);
|
||||
33
db/query/virtual_games.sql
Normal file
33
db/query/virtual_games.sql
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
-- name: CreateVirtualGameSession :one
|
||||
INSERT INTO virtual_game_sessions (
|
||||
user_id, game_id, session_token, currency, status, expires_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6
|
||||
) RETURNING id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at;
|
||||
|
||||
-- name: GetVirtualGameSessionByToken :one
|
||||
SELECT id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at
|
||||
FROM virtual_game_sessions
|
||||
WHERE session_token = $1;
|
||||
|
||||
-- name: UpdateVirtualGameSessionStatus :exec
|
||||
UPDATE virtual_game_sessions
|
||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: CreateVirtualGameTransaction :one
|
||||
INSERT INTO virtual_game_transactions (
|
||||
session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8
|
||||
) RETURNING id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at;
|
||||
|
||||
-- name: GetVirtualGameTransactionByExternalID :one
|
||||
SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||
FROM virtual_game_transactions
|
||||
WHERE external_transaction_id = $1;
|
||||
|
||||
-- name: UpdateVirtualGameTransactionStatus :exec
|
||||
UPDATE virtual_game_transactions
|
||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1;
|
||||
|
|
@ -189,6 +189,32 @@ type User struct {
|
|||
ReferredBy pgtype.Text
|
||||
}
|
||||
|
||||
type VirtualGameSession struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
GameID string
|
||||
SessionToken string
|
||||
Currency string
|
||||
Status string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
ExpiresAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type VirtualGameTransaction struct {
|
||||
ID int64
|
||||
SessionID int64
|
||||
UserID int64
|
||||
WalletID int64
|
||||
TransactionType string
|
||||
Amount int64
|
||||
Currency string
|
||||
ExternalTransactionID string
|
||||
Status string
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type Wallet struct {
|
||||
ID int64
|
||||
Balance int64
|
||||
|
|
|
|||
|
|
@ -184,8 +184,8 @@ WHERE referrer_id = $1
|
|||
type GetReferralStatsRow struct {
|
||||
TotalReferrals int64
|
||||
CompletedReferrals int64
|
||||
TotalRewardEarned float64
|
||||
PendingRewards float64
|
||||
TotalRewardEarned interface{}
|
||||
PendingRewards interface{}
|
||||
}
|
||||
|
||||
func (q *Queries) GetReferralStats(ctx context.Context, referrerID string) (GetReferralStatsRow, error) {
|
||||
|
|
|
|||
180
gen/db/virtual_games.sql.go
Normal file
180
gen/db/virtual_games.sql.go
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// source: virtual_games.sql
|
||||
|
||||
package dbgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const CreateVirtualGameSession = `-- name: CreateVirtualGameSession :one
|
||||
INSERT INTO virtual_game_sessions (
|
||||
user_id, game_id, session_token, currency, status, expires_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6
|
||||
) RETURNING id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at
|
||||
`
|
||||
|
||||
type CreateVirtualGameSessionParams struct {
|
||||
UserID int64
|
||||
GameID string
|
||||
SessionToken string
|
||||
Currency string
|
||||
Status string
|
||||
ExpiresAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
func (q *Queries) CreateVirtualGameSession(ctx context.Context, arg CreateVirtualGameSessionParams) (VirtualGameSession, error) {
|
||||
row := q.db.QueryRow(ctx, CreateVirtualGameSession,
|
||||
arg.UserID,
|
||||
arg.GameID,
|
||||
arg.SessionToken,
|
||||
arg.Currency,
|
||||
arg.Status,
|
||||
arg.ExpiresAt,
|
||||
)
|
||||
var i VirtualGameSession
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.GameID,
|
||||
&i.SessionToken,
|
||||
&i.Currency,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const CreateVirtualGameTransaction = `-- name: CreateVirtualGameTransaction :one
|
||||
INSERT INTO virtual_game_transactions (
|
||||
session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8
|
||||
) RETURNING id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateVirtualGameTransactionParams struct {
|
||||
SessionID int64
|
||||
UserID int64
|
||||
WalletID int64
|
||||
TransactionType string
|
||||
Amount int64
|
||||
Currency string
|
||||
ExternalTransactionID string
|
||||
Status string
|
||||
}
|
||||
|
||||
func (q *Queries) CreateVirtualGameTransaction(ctx context.Context, arg CreateVirtualGameTransactionParams) (VirtualGameTransaction, error) {
|
||||
row := q.db.QueryRow(ctx, CreateVirtualGameTransaction,
|
||||
arg.SessionID,
|
||||
arg.UserID,
|
||||
arg.WalletID,
|
||||
arg.TransactionType,
|
||||
arg.Amount,
|
||||
arg.Currency,
|
||||
arg.ExternalTransactionID,
|
||||
arg.Status,
|
||||
)
|
||||
var i VirtualGameTransaction
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.SessionID,
|
||||
&i.UserID,
|
||||
&i.WalletID,
|
||||
&i.TransactionType,
|
||||
&i.Amount,
|
||||
&i.Currency,
|
||||
&i.ExternalTransactionID,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetVirtualGameSessionByToken = `-- name: GetVirtualGameSessionByToken :one
|
||||
SELECT id, user_id, game_id, session_token, currency, status, created_at, updated_at, expires_at
|
||||
FROM virtual_game_sessions
|
||||
WHERE session_token = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetVirtualGameSessionByToken(ctx context.Context, sessionToken string) (VirtualGameSession, error) {
|
||||
row := q.db.QueryRow(ctx, GetVirtualGameSessionByToken, sessionToken)
|
||||
var i VirtualGameSession
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.GameID,
|
||||
&i.SessionToken,
|
||||
&i.Currency,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetVirtualGameTransactionByExternalID = `-- name: GetVirtualGameTransactionByExternalID :one
|
||||
SELECT id, session_id, user_id, wallet_id, transaction_type, amount, currency, external_transaction_id, status, created_at, updated_at
|
||||
FROM virtual_game_transactions
|
||||
WHERE external_transaction_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetVirtualGameTransactionByExternalID(ctx context.Context, externalTransactionID string) (VirtualGameTransaction, error) {
|
||||
row := q.db.QueryRow(ctx, GetVirtualGameTransactionByExternalID, externalTransactionID)
|
||||
var i VirtualGameTransaction
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.SessionID,
|
||||
&i.UserID,
|
||||
&i.WalletID,
|
||||
&i.TransactionType,
|
||||
&i.Amount,
|
||||
&i.Currency,
|
||||
&i.ExternalTransactionID,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const UpdateVirtualGameSessionStatus = `-- name: UpdateVirtualGameSessionStatus :exec
|
||||
UPDATE virtual_game_sessions
|
||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateVirtualGameSessionStatusParams struct {
|
||||
ID int64
|
||||
Status string
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateVirtualGameSessionStatus(ctx context.Context, arg UpdateVirtualGameSessionStatusParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateVirtualGameSessionStatus, arg.ID, arg.Status)
|
||||
return err
|
||||
}
|
||||
|
||||
const UpdateVirtualGameTransactionStatus = `-- name: UpdateVirtualGameTransactionStatus :exec
|
||||
UPDATE virtual_game_transactions
|
||||
SET status = $2, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateVirtualGameTransactionStatusParams struct {
|
||||
ID int64
|
||||
Status string
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateVirtualGameTransactionStatus(ctx context.Context, arg UpdateVirtualGameTransactionStatusParams) error {
|
||||
_, err := q.db.Exec(ctx, UpdateVirtualGameTransactionStatus, arg.ID, arg.Status)
|
||||
return err
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
|
@ -20,6 +21,10 @@ var (
|
|||
ErrInvalidLevel = errors.New("invalid log level")
|
||||
ErrInvalidEnv = errors.New("env not set or invalid")
|
||||
ErrInvalidSMSAPIKey = errors.New("SMS API key is invalid")
|
||||
ErrInvalidPopOKClientID = errors.New("PopOK client ID is invalid")
|
||||
ErrInvalidPopOKSecretKey = errors.New("PopOK secret key is invalid")
|
||||
ErrInvalidPopOKBaseURL = errors.New("PopOK base URL is invalid")
|
||||
ErrInvalidPopOKCallbackURL = errors.New("PopOK callback URL is invalid")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
|
@ -34,6 +39,7 @@ type Config struct {
|
|||
AFRO_SMS_SENDER_NAME string
|
||||
AFRO_SMS_RECEIVER_PHONE_NUMBER string
|
||||
ADRO_SMS_HOST_URL string
|
||||
PopOK domain.PopOKConfig
|
||||
}
|
||||
|
||||
func NewConfig() (*Config, error) {
|
||||
|
|
@ -125,6 +131,31 @@ func (c *Config) loadEnv() error {
|
|||
if c.ADRO_SMS_HOST_URL == "" {
|
||||
c.ADRO_SMS_HOST_URL = "https://api.afrosms.com"
|
||||
}
|
||||
popOKClientID := os.Getenv("POPOK_CLIENT_ID")
|
||||
if popOKClientID == "" {
|
||||
return ErrInvalidPopOKClientID
|
||||
}
|
||||
|
||||
popOKSecretKey := os.Getenv("POPOK_SECRET_KEY")
|
||||
if popOKSecretKey == "" {
|
||||
return ErrInvalidPopOKSecretKey
|
||||
}
|
||||
|
||||
popOKBaseURL := os.Getenv("POPOK_BASE_URL")
|
||||
if popOKBaseURL == "" {
|
||||
return ErrInvalidPopOKBaseURL
|
||||
}
|
||||
|
||||
popOKCallbackURL := os.Getenv("POPOK_CALLBACK_URL")
|
||||
if popOKCallbackURL == "" {
|
||||
return ErrInvalidPopOKCallbackURL
|
||||
}
|
||||
|
||||
c.PopOK = domain.PopOKConfig{
|
||||
ClientID: popOKClientID,
|
||||
SecretKey: popOKSecretKey,
|
||||
BaseURL: popOKBaseURL,
|
||||
CallbackURL: popOKCallbackURL,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
55
internal/domain/virtual_game.go
Normal file
55
internal/domain/virtual_game.go
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type VirtualGameSession struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
GameID string `json:"game_id"`
|
||||
SessionToken string `json:"session_token"`
|
||||
Currency string `json:"currency"`
|
||||
Status string `json:"status"` // ACTIVE, COMPLETED, FAILED
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
type VirtualGameTransaction struct {
|
||||
ID int64 `json:"id"`
|
||||
SessionID int64 `json:"session_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
WalletID int64 `json:"wallet_id"`
|
||||
TransactionType string `json:"transaction_type"` // BET, WIN, REFUND, JACKPOT_WIN
|
||||
Amount int64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
ExternalTransactionID string `json:"external_transaction_id"`
|
||||
Status string `json:"status"` // PENDING, COMPLETED, FAILED
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CreateVirtualGameSession struct {
|
||||
UserID int64
|
||||
GameID string
|
||||
Currency string
|
||||
Mode string // REAL, DEMO
|
||||
}
|
||||
|
||||
type PopOKConfig struct {
|
||||
ClientID string
|
||||
SecretKey string
|
||||
BaseURL string
|
||||
CallbackURL string
|
||||
}
|
||||
|
||||
type PopOKCallback struct {
|
||||
TransactionID string `json:"transaction_id"`
|
||||
SessionID string `json:"session_id"`
|
||||
Type string `json:"type"` // BET, WIN, REFUND, JACKPOT_WIN
|
||||
Amount float64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Signature string `json:"signature"` // HMAC-SHA256 signature for verification
|
||||
}
|
||||
|
|
@ -96,8 +96,8 @@ func (r *ReferralRepo) GetReferralStats(ctx context.Context, userID string) (*do
|
|||
return &domain.ReferralStats{
|
||||
TotalReferrals: int(stats.TotalReferrals),
|
||||
CompletedReferrals: int(stats.CompletedReferrals),
|
||||
TotalRewardEarned: float64(stats.TotalRewardEarned),
|
||||
PendingRewards: float64(stats.PendingRewards),
|
||||
TotalRewardEarned: stats.TotalRewardEarned.(float64),
|
||||
PendingRewards: stats.PendingRewards.(float64),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
114
internal/repository/virtual_game.go
Normal file
114
internal/repository/virtual_game.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type VirtualGameRepository interface {
|
||||
CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error
|
||||
GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error)
|
||||
UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error
|
||||
CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error
|
||||
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
|
||||
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
|
||||
}
|
||||
|
||||
type VirtualGameRepo struct {
|
||||
store *Store
|
||||
}
|
||||
|
||||
func NewVirtualGameRepository(store *Store) VirtualGameRepository {
|
||||
return &VirtualGameRepo{store: store}
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error {
|
||||
params := dbgen.CreateVirtualGameSessionParams{
|
||||
UserID: session.UserID,
|
||||
GameID: session.GameID,
|
||||
SessionToken: session.SessionToken,
|
||||
Currency: session.Currency,
|
||||
Status: session.Status,
|
||||
ExpiresAt: pgtype.Timestamptz{Time: session.ExpiresAt, Valid: true},
|
||||
}
|
||||
_, err := r.store.queries.CreateVirtualGameSession(ctx, params)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error) {
|
||||
dbSession, err := r.store.queries.GetVirtualGameSessionByToken(ctx, token)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &domain.VirtualGameSession{
|
||||
ID: dbSession.ID,
|
||||
UserID: dbSession.UserID,
|
||||
GameID: dbSession.GameID,
|
||||
SessionToken: dbSession.SessionToken,
|
||||
Currency: dbSession.Currency,
|
||||
Status: dbSession.Status,
|
||||
CreatedAt: dbSession.CreatedAt.Time,
|
||||
UpdatedAt: dbSession.UpdatedAt.Time,
|
||||
ExpiresAt: dbSession.ExpiresAt.Time,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error {
|
||||
return r.store.queries.UpdateVirtualGameSessionStatus(ctx, dbgen.UpdateVirtualGameSessionStatusParams{
|
||||
ID: id,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error {
|
||||
params := dbgen.CreateVirtualGameTransactionParams{
|
||||
SessionID: tx.SessionID,
|
||||
UserID: tx.UserID,
|
||||
WalletID: tx.WalletID,
|
||||
TransactionType: tx.TransactionType,
|
||||
Amount: tx.Amount,
|
||||
Currency: tx.Currency,
|
||||
ExternalTransactionID: tx.ExternalTransactionID,
|
||||
Status: tx.Status,
|
||||
}
|
||||
_, err := r.store.queries.CreateVirtualGameTransaction(ctx, params)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error) {
|
||||
dbTx, err := r.store.queries.GetVirtualGameTransactionByExternalID(ctx, externalID)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &domain.VirtualGameTransaction{
|
||||
ID: dbTx.ID,
|
||||
SessionID: dbTx.SessionID,
|
||||
UserID: dbTx.UserID,
|
||||
WalletID: dbTx.WalletID,
|
||||
TransactionType: dbTx.TransactionType,
|
||||
Amount: dbTx.Amount,
|
||||
Currency: dbTx.Currency,
|
||||
ExternalTransactionID: dbTx.ExternalTransactionID,
|
||||
Status: dbTx.Status,
|
||||
CreatedAt: dbTx.CreatedAt.Time,
|
||||
UpdatedAt: dbTx.UpdatedAt.Time,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error {
|
||||
return r.store.queries.UpdateVirtualGameTransactionStatus(ctx, dbgen.UpdateVirtualGameTransactionStatusParams{
|
||||
ID: id,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
12
internal/services/virtualGame/port.go
Normal file
12
internal/services/virtualGame/port.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package virtualgameservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
)
|
||||
|
||||
type VirtualGameService interface {
|
||||
GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error)
|
||||
HandleCallback(ctx context.Context, callback *domain.PopOKCallback) error
|
||||
}
|
||||
169
internal/services/virtualGame/service.go
Normal file
169
internal/services/virtualGame/service.go
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
package virtualgameservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
repo repository.VirtualGameRepository
|
||||
walletSvc wallet.Service
|
||||
store *repository.Store
|
||||
config *config.Config
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func New(repo repository.VirtualGameRepository, walletSvc wallet.Service, store *repository.Store, cfg *config.Config, logger *slog.Logger) VirtualGameService {
|
||||
return &service{
|
||||
repo: repo,
|
||||
walletSvc: walletSvc,
|
||||
store: store,
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) GenerateGameLaunchURL(ctx context.Context, userID int64, gameID, currency, mode string) (string, error) {
|
||||
user, err := s.store.GetUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get user", "userID", userID, "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
sessionToken := fmt.Sprintf("%d-%s-%d", userID, gameID, time.Now().UnixNano())
|
||||
token, err := jwtutil.CreatePopOKJwt(
|
||||
userID,
|
||||
user.PhoneNumber,
|
||||
currency,
|
||||
"en",
|
||||
mode,
|
||||
sessionToken,
|
||||
s.config.PopOK.SecretKey,
|
||||
24*time.Hour,
|
||||
)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to create PopOK JWT", "userID", userID, "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
params := fmt.Sprintf(
|
||||
"client_id=%s&game_id=%s¤cy=%s&lang=en&mode=%s&token=%s",
|
||||
s.config.PopOK.ClientID, gameID, currency, mode, token,
|
||||
)
|
||||
signature := s.generateSignature(params)
|
||||
return fmt.Sprintf("%s/game/launch?%s&signature=%s", s.config.PopOK.BaseURL, params, signature), nil
|
||||
}
|
||||
|
||||
func (s *service) HandleCallback(ctx context.Context, callback *domain.PopOKCallback) error {
|
||||
s.logger.Info("Handling PopOK callback", "transactionID", callback.TransactionID, "type", callback.Type)
|
||||
|
||||
if !s.verifySignature(callback) {
|
||||
s.logger.Error("Invalid callback signature", "transactionID", callback.TransactionID)
|
||||
return errors.New("invalid signature")
|
||||
}
|
||||
|
||||
existingTx, err := s.repo.GetVirtualGameTransactionByExternalID(ctx, callback.TransactionID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to check existing transaction", "transactionID", callback.TransactionID, "error", err)
|
||||
return err
|
||||
}
|
||||
if existingTx != nil {
|
||||
s.logger.Warn("Transaction already processed", "transactionID", callback.TransactionID)
|
||||
return nil // Idempotency
|
||||
}
|
||||
|
||||
session, err := s.repo.GetVirtualGameSessionByToken(ctx, callback.SessionID)
|
||||
if err != nil || session == nil {
|
||||
s.logger.Error("Invalid or missing session", "sessionID", callback.SessionID, "error", err)
|
||||
return errors.New("invalid session")
|
||||
}
|
||||
|
||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, session.UserID)
|
||||
if err != nil || len(wallets) == 0 {
|
||||
s.logger.Error("Failed to get wallets or no wallet found", "userID", session.UserID, "error", err)
|
||||
return errors.New("user has no wallet")
|
||||
}
|
||||
|
||||
walletID := wallets[0].ID
|
||||
amount := int64(callback.Amount * 100) // Convert to cents
|
||||
transactionType := callback.Type
|
||||
|
||||
switch transactionType {
|
||||
case "BET":
|
||||
amount = -amount // Debit for bets
|
||||
case "WIN", "JACKPOT_WIN", "REFUND":
|
||||
default:
|
||||
s.logger.Error("Unknown transaction type", "transactionID", callback.TransactionID, "type", transactionType)
|
||||
return errors.New("unknown transaction type")
|
||||
}
|
||||
|
||||
err = s.walletSvc.Add(ctx, walletID, domain.Currency(amount))
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update wallet", "walletID", walletID, "userID", session.UserID, "amount", amount, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Record transaction
|
||||
tx := &domain.VirtualGameTransaction{
|
||||
SessionID: session.ID,
|
||||
UserID: session.UserID,
|
||||
WalletID: walletID,
|
||||
TransactionType: transactionType,
|
||||
Amount: amount,
|
||||
Currency: callback.Currency,
|
||||
ExternalTransactionID: callback.TransactionID,
|
||||
Status: "COMPLETED",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := s.repo.CreateVirtualGameTransaction(ctx, tx); err != nil {
|
||||
s.logger.Error("Failed to create transaction", "transactionID", callback.TransactionID, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("Callback processed successfully", "transactionID", callback.TransactionID, "type", transactionType, "amount", callback.Amount)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) generateSignature(params string) string {
|
||||
h := hmac.New(sha256.New, []byte(s.config.PopOK.SecretKey))
|
||||
h.Write([]byte(params))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func (s *service) verifySignature(callback *domain.PopOKCallback) bool {
|
||||
data, _ := json.Marshal(struct {
|
||||
TransactionID string `json:"transaction_id"`
|
||||
SessionID string `json:"session_id"`
|
||||
Type string `json:"type"`
|
||||
Amount float64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}{
|
||||
TransactionID: callback.TransactionID,
|
||||
SessionID: callback.SessionID,
|
||||
Type: callback.Type,
|
||||
Amount: callback.Amount,
|
||||
Currency: callback.Currency,
|
||||
Timestamp: callback.Timestamp,
|
||||
})
|
||||
|
||||
h := hmac.New(sha256.New, []byte(s.config.PopOK.SecretKey))
|
||||
h.Write(data)
|
||||
expected := hex.EncodeToString(h.Sum(nil))
|
||||
return expected == callback.Signature
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||
|
|
@ -28,6 +29,7 @@ type App struct {
|
|||
authSvc *authentication.Service
|
||||
userSvc *user.Service
|
||||
betSvc *bet.Service
|
||||
virtualGameSvc virtualgameservice.VirtualGameService
|
||||
walletSvc *wallet.Service
|
||||
transactionSvc *transaction.Service
|
||||
ticketSvc *ticket.Service
|
||||
|
|
@ -48,6 +50,7 @@ func NewApp(
|
|||
transactionSvc *transaction.Service,
|
||||
notidicationStore notificationservice.NotificationStore,
|
||||
referralSvc referralservice.ReferralStore,
|
||||
virtualGameSvc virtualgameservice.VirtualGameService,
|
||||
) *App {
|
||||
app := fiber.New(fiber.Config{
|
||||
CaseSensitive: true,
|
||||
|
|
@ -70,6 +73,7 @@ func NewApp(
|
|||
NotidicationStore: notidicationStore,
|
||||
referralSvc: referralSvc,
|
||||
Logger: logger,
|
||||
virtualGameSvc: virtualGameSvc,
|
||||
}
|
||||
|
||||
s.initAppRoutes()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/ticket"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||
|
|
@ -24,13 +25,14 @@ type Handler struct {
|
|||
transactionSvc *transaction.Service
|
||||
ticketSvc *ticket.Service
|
||||
betSvc *bet.Service
|
||||
virtualGameSvc virtualgameservice.VirtualGameService
|
||||
authSvc *authentication.Service
|
||||
jwtConfig jwtutil.JwtConfig
|
||||
validator *customvalidator.CustomValidator
|
||||
}
|
||||
|
||||
func New(logger *slog.Logger, notificationSvc notificationservice.NotificationStore, validator *customvalidator.CustomValidator, walletSvc *wallet.Service,
|
||||
referralSvc referralservice.ReferralStore, userSvc *user.Service, transactionSvc *transaction.Service, ticketSvc *ticket.Service, betSvc *bet.Service, authSvc *authentication.Service, jwtConfig jwtutil.JwtConfig) *Handler {
|
||||
referralSvc referralservice.ReferralStore, virtualGameSvc virtualgameservice.VirtualGameService, userSvc *user.Service, transactionSvc *transaction.Service, ticketSvc *ticket.Service, betSvc *bet.Service, authSvc *authentication.Service, jwtConfig jwtutil.JwtConfig) *Handler {
|
||||
return &Handler{
|
||||
logger: logger,
|
||||
notificationSvc: notificationSvc,
|
||||
|
|
@ -41,6 +43,7 @@ func New(logger *slog.Logger, notificationSvc notificationservice.NotificationSt
|
|||
transactionSvc: transactionSvc,
|
||||
ticketSvc: ticketSvc,
|
||||
betSvc: betSvc,
|
||||
virtualGameSvc: virtualGameSvc,
|
||||
authSvc: authSvc,
|
||||
jwtConfig: jwtConfig,
|
||||
}
|
||||
|
|
|
|||
83
internal/web_server/handlers/virtual_games_hadlers.go
Normal file
83
internal/web_server/handlers/virtual_games_hadlers.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// LaunchVirtualGame godoc
|
||||
// @Summary Launch a PopOK virtual game
|
||||
// @Description Generates a URL to launch a PopOK game
|
||||
// @Tags virtual-game
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param launch body launchVirtualGameReq true "Game launch details"
|
||||
// @Success 200 {object} launchVirtualGameRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /virtual-game/launch [post]
|
||||
func (h *Handler) LaunchVirtualGame(c *fiber.Ctx) error {
|
||||
type launchVirtualGameReq struct {
|
||||
GameID string `json:"game_id" validate:"required" example:"crash_001"`
|
||||
Currency string `json:"currency" validate:"required,len=3" example:"USD"`
|
||||
Mode string `json:"mode" validate:"required,oneof=REAL DEMO" example:"REAL"`
|
||||
}
|
||||
|
||||
type launchVirtualGameRes struct {
|
||||
LaunchURL string `json:"launch_url"`
|
||||
}
|
||||
|
||||
userID, ok := c.Locals("user_id").(int64)
|
||||
if !ok || userID == 0 {
|
||||
h.logger.Error("Invalid user ID in context")
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification")
|
||||
}
|
||||
|
||||
var req launchVirtualGameReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.logger.Error("Failed to parse LaunchVirtualGame request", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
}
|
||||
|
||||
url, err := h.virtualGameSvc.GenerateGameLaunchURL(c.Context(), userID, req.GameID, req.Currency, req.Mode)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to generate game launch URL", "userID", userID, "gameID", req.GameID, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to launch game")
|
||||
}
|
||||
|
||||
res := launchVirtualGameRes{LaunchURL: url}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Game launched successfully", res, nil)
|
||||
}
|
||||
|
||||
// HandleVirtualGameCallback godoc
|
||||
// @Summary Handle PopOK game callback
|
||||
// @Description Processes callbacks from PopOK for game events
|
||||
// @Tags virtual-game
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param callback body domain.PopOKCallback true "Callback data"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /virtual-game/callback [post]
|
||||
func (h *Handler) HandleVirtualGameCallback(c *fiber.Ctx) error {
|
||||
var callback domain.PopOKCallback
|
||||
if err := c.BodyParser(&callback); err != nil {
|
||||
h.logger.Error("Failed to parse callback", "error", err)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid callback data")
|
||||
}
|
||||
|
||||
if err := h.virtualGameSvc.HandleCallback(c.Context(), &callback); err != nil {
|
||||
h.logger.Error("Failed to handle callback", "transactionID", callback.TransactionID, "error", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to process callback")
|
||||
}
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Callback processed successfully", nil, nil)
|
||||
}
|
||||
|
|
@ -8,14 +8,11 @@ import (
|
|||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// type UserToken struct {
|
||||
// UserId string
|
||||
// }
|
||||
var (
|
||||
ErrExpiredToken = errors.New("token expired")
|
||||
ErrMalformedToken = errors.New("token malformed")
|
||||
ErrTokenNotExpired = errors.New("token not expired")
|
||||
ErrRefreshTokenNotFound = errors.New("refresh token not found") // i.e login again
|
||||
ErrRefreshTokenNotFound = errors.New("refresh token not found")
|
||||
)
|
||||
|
||||
type UserClaim struct {
|
||||
|
|
@ -23,23 +20,61 @@ type UserClaim struct {
|
|||
UserId int64
|
||||
Role domain.Role
|
||||
}
|
||||
|
||||
type PopOKClaim struct {
|
||||
jwt.RegisteredClaims
|
||||
UserID int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Currency string `json:"currency"`
|
||||
Lang string `json:"lang"`
|
||||
Mode string `json:"mode"`
|
||||
SessionID string `json:"session_id"`
|
||||
}
|
||||
|
||||
type JwtConfig struct {
|
||||
JwtAccessKey string
|
||||
JwtAccessExpiry int
|
||||
}
|
||||
|
||||
func CreateJwt(userId int64, Role domain.Role, key string, expiry int) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{RegisteredClaims: jwt.RegisteredClaims{Issuer: "github.com/lafetz/snippitstash",
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: "github.com/lafetz/snippitstash",
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Audience: jwt.ClaimStrings{"fortune.com"},
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * time.Second))},
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * time.Second)),
|
||||
},
|
||||
UserId: userId,
|
||||
Role: Role,
|
||||
})
|
||||
jwtToken, err := token.SignedString([]byte(key)) //
|
||||
jwtToken, err := token.SignedString([]byte(key))
|
||||
return jwtToken, err
|
||||
}
|
||||
|
||||
func CreatePopOKJwt(userID int64, username, currency, lang, mode, sessionID, key string, expiry time.Duration) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, PopOKClaim{
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: "fortune-bet",
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Audience: jwt.ClaimStrings{"popokgaming.com"},
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiry)),
|
||||
},
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
Currency: currency,
|
||||
Lang: lang,
|
||||
Mode: mode,
|
||||
SessionID: sessionID,
|
||||
})
|
||||
jwtToken, err := token.SignedString([]byte(key))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return jwtToken, nil
|
||||
}
|
||||
|
||||
func ParseJwt(jwtToken string, key string) (*UserClaim, error) {
|
||||
token, err := jwt.ParseWithClaims(jwtToken, &UserClaim{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(key), nil
|
||||
|
|
@ -56,5 +91,24 @@ func ParseJwt(jwtToken string, key string) (*UserClaim, error) {
|
|||
if claims, ok := token.Claims.(*UserClaim); ok && token.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
return nil, err
|
||||
return nil, errors.New("invalid token claims")
|
||||
}
|
||||
|
||||
func ParsePopOKJwt(jwtToken string, key string) (*PopOKClaim, error) {
|
||||
token, err := jwt.ParseWithClaims(jwtToken, &PopOKClaim{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(key), nil
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
return nil, ErrExpiredToken
|
||||
}
|
||||
if errors.Is(err, jwt.ErrTokenMalformed) {
|
||||
return nil, ErrMalformedToken
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if claims, ok := token.Claims.(*PopOKClaim); ok && token.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
return nil, errors.New("invalid PopOK token claims")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ func (a *App) initAppRoutes() {
|
|||
a.validator,
|
||||
a.walletSvc,
|
||||
a.referralSvc,
|
||||
a.virtualGameSvc,
|
||||
a.userSvc,
|
||||
a.transactionSvc,
|
||||
a.ticketSvc,
|
||||
|
|
@ -101,6 +102,10 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Get("/notifications/ws/connect/:recipientID", h.ConnectSocket)
|
||||
a.fiber.Post("/notifications/mark-as-read", h.MarkNotificationAsRead)
|
||||
a.fiber.Post("/notifications/create", h.CreateAndSendNotification)
|
||||
|
||||
// Virtual Game Routes
|
||||
a.fiber.Post("/virtual-game/launch", a.authMiddleware, h.LaunchVirtualGame)
|
||||
a.fiber.Post("/virtual-game/callback", h.HandleVirtualGameCallback)
|
||||
}
|
||||
|
||||
// @Router /user/resetPassword [post]
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user