veli games fixes
This commit is contained in:
parent
4fdc76280a
commit
d654d5f2ef
|
|
@ -3,11 +3,8 @@ CREATE TABLE virtual_game_sessions (
|
||||||
user_id BIGINT NOT NULL REFERENCES users(id),
|
user_id BIGINT NOT NULL REFERENCES users(id),
|
||||||
game_id VARCHAR(50) NOT NULL,
|
game_id VARCHAR(50) NOT NULL,
|
||||||
session_token VARCHAR(255) NOT NULL UNIQUE,
|
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,
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_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 (
|
CREATE TABLE virtual_game_transactions (
|
||||||
|
|
|
||||||
|
|
@ -63,38 +63,38 @@ RETURNING id,
|
||||||
INSERT INTO virtual_game_sessions (
|
INSERT INTO virtual_game_sessions (
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
expires_at
|
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)
|
VALUES ($1, $2, $3)
|
||||||
RETURNING id,
|
RETURNING
|
||||||
|
id,
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token,
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at;
|
||||||
expires_at;
|
|
||||||
|
-- name: GetVirtualGameSessionByUserID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
game_id,
|
||||||
|
session_token,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM virtual_game_sessions
|
||||||
|
WHERE user_id = $1;
|
||||||
|
|
||||||
-- name: GetVirtualGameSessionByToken :one
|
-- name: GetVirtualGameSessionByToken :one
|
||||||
SELECT id,
|
SELECT id,
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token,
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
expires_at
|
|
||||||
FROM virtual_game_sessions
|
FROM virtual_game_sessions
|
||||||
WHERE session_token = $1;
|
WHERE session_token = $1;
|
||||||
-- name: UpdateVirtualGameSessionStatus :exec
|
|
||||||
UPDATE virtual_game_sessions
|
|
||||||
SET status = $2,
|
|
||||||
updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE id = $1;
|
|
||||||
-- name: CreateVirtualGameTransaction :one
|
-- name: CreateVirtualGameTransaction :one
|
||||||
INSERT INTO virtual_game_transactions (
|
INSERT INTO virtual_game_transactions (
|
||||||
session_id,
|
session_id,
|
||||||
|
|
|
||||||
129
docs/docs.go
129
docs/docs.go
|
|
@ -458,7 +458,7 @@ const docTemplate = `{
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.ArifpayB2CRequest"
|
"$ref": "#/definitions/domain.CheckoutSessionClientRequest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -585,7 +585,7 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/arifpay/checkout/{sessionId}/cancel": {
|
"/api/v1/arifpay/checkout/cancel/{sessionId}": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Cancels a payment session using Arifpay before completion.",
|
"description": "Cancels a payment session using Arifpay before completion.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
|
|
@ -2254,7 +2254,7 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/payments/webhook/verify": {
|
"/api/v1/chapa/payments/webhook/verify": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Handles payment notifications from Chapa",
|
"description": "Handles payment and transfer notifications from Chapa",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -2272,7 +2272,7 @@ const docTemplate = `{
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.ChapaWebhookPayload"
|
"$ref": "#/definitions/domain.ChapaWebhookPayment"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -2280,8 +2280,7 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/domain.Response"
|
||||||
"additionalProperties": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -2329,7 +2328,7 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"201": {
|
"200": {
|
||||||
"description": "Chapa withdrawal process initiated successfully",
|
"description": "Chapa withdrawal process initiated successfully",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.Response"
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
|
@ -2364,7 +2363,7 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/swap": {
|
"/api/v1/chapa/swap": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Perform a USD to ETB currency swap using Chapa's API",
|
"description": "Convert an amount from one currency to another using Chapa's currency swap API",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -2374,11 +2373,11 @@ const docTemplate = `{
|
||||||
"tags": [
|
"tags": [
|
||||||
"Chapa"
|
"Chapa"
|
||||||
],
|
],
|
||||||
"summary": "Initiate a currency swap",
|
"summary": "Swap currency using Chapa API",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Swap Request Payload",
|
"description": "Swap request payload",
|
||||||
"name": "payload",
|
"name": "request",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
|
|
@ -2536,7 +2535,7 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.ChapaVerificationResponse"
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -10229,28 +10228,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.ArifpayB2CRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"amount",
|
|
||||||
"customerEmail",
|
|
||||||
"customerPhone"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Phonenumber": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"amount": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"customerEmail": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"customerPhone": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.ArifpayVerifyByTransactionIDRequest": {
|
"domain.ArifpayVerifyByTransactionIDRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -10980,17 +10957,28 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.ChapaVerificationResponse": {
|
"domain.ChapaWebhookCustomization": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaWebhookPayment": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"amount": {
|
"amount": {
|
||||||
"type": "number"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"charge": {
|
"charge": {
|
||||||
"type": "number"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -10999,33 +10987,32 @@ const docTemplate = `{
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"customization": {
|
"customization": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/domain.ChapaWebhookCustomization"
|
||||||
"properties": {
|
|
||||||
"description": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"logo": {},
|
|
||||||
"title": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"event": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"first_name": {
|
"first_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"last_name": {
|
"last_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"meta": {},
|
"meta": {
|
||||||
"method": {
|
"description": "may vary in structure, so kept flexible"
|
||||||
|
},
|
||||||
|
"mobile": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"payment_method": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"reference": {
|
"reference": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -11043,33 +11030,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.ChapaWebhookPayload": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"description": "Currency string ` + "`" + `json:\"currency\"` + "`" + `",
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/domain.PaymentStatus"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"trx_ref": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.ChapaWithdrawalRequest": {
|
"domain.ChapaWithdrawalRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -12884,21 +12844,6 @@ const docTemplate = `{
|
||||||
"BANK"
|
"BANK"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"domain.PaymentStatus": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"success",
|
|
||||||
"pending",
|
|
||||||
"completed",
|
|
||||||
"failed"
|
|
||||||
],
|
|
||||||
"x-enum-varnames": [
|
|
||||||
"PaymentStatusSuccessful",
|
|
||||||
"PaymentStatusPending",
|
|
||||||
"PaymentStatusCompleted",
|
|
||||||
"PaymentStatusFailed"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"domain.PopOKCallback": {
|
"domain.PopOKCallback": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -450,7 +450,7 @@
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.ArifpayB2CRequest"
|
"$ref": "#/definitions/domain.CheckoutSessionClientRequest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -577,7 +577,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/v1/arifpay/checkout/{sessionId}/cancel": {
|
"/api/v1/arifpay/checkout/cancel/{sessionId}": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Cancels a payment session using Arifpay before completion.",
|
"description": "Cancels a payment session using Arifpay before completion.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
|
|
@ -2246,7 +2246,7 @@
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/payments/webhook/verify": {
|
"/api/v1/chapa/payments/webhook/verify": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Handles payment notifications from Chapa",
|
"description": "Handles payment and transfer notifications from Chapa",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -2264,7 +2264,7 @@
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.ChapaWebhookPayload"
|
"$ref": "#/definitions/domain.ChapaWebhookPayment"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -2272,8 +2272,7 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/domain.Response"
|
||||||
"additionalProperties": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -2321,7 +2320,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"201": {
|
"200": {
|
||||||
"description": "Chapa withdrawal process initiated successfully",
|
"description": "Chapa withdrawal process initiated successfully",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.Response"
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
|
@ -2356,7 +2355,7 @@
|
||||||
},
|
},
|
||||||
"/api/v1/chapa/swap": {
|
"/api/v1/chapa/swap": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Perform a USD to ETB currency swap using Chapa's API",
|
"description": "Convert an amount from one currency to another using Chapa's currency swap API",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
|
@ -2366,11 +2365,11 @@
|
||||||
"tags": [
|
"tags": [
|
||||||
"Chapa"
|
"Chapa"
|
||||||
],
|
],
|
||||||
"summary": "Initiate a currency swap",
|
"summary": "Swap currency using Chapa API",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Swap Request Payload",
|
"description": "Swap request payload",
|
||||||
"name": "payload",
|
"name": "request",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
|
|
@ -2528,7 +2527,7 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/domain.ChapaVerificationResponse"
|
"$ref": "#/definitions/domain.Response"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -10221,28 +10220,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.ArifpayB2CRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"amount",
|
|
||||||
"customerEmail",
|
|
||||||
"customerPhone"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"Phonenumber": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"amount": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"customerEmail": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"customerPhone": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.ArifpayVerifyByTransactionIDRequest": {
|
"domain.ArifpayVerifyByTransactionIDRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -10972,17 +10949,28 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"domain.ChapaVerificationResponse": {
|
"domain.ChapaWebhookCustomization": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaWebhookPayment": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"amount": {
|
"amount": {
|
||||||
"type": "number"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"charge": {
|
"charge": {
|
||||||
"type": "number"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -10991,33 +10979,32 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"customization": {
|
"customization": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/domain.ChapaWebhookCustomization"
|
||||||
"properties": {
|
|
||||||
"description": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"logo": {},
|
|
||||||
"title": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"event": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"first_name": {
|
"first_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"last_name": {
|
"last_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"meta": {},
|
"meta": {
|
||||||
"method": {
|
"description": "may vary in structure, so kept flexible"
|
||||||
|
},
|
||||||
|
"mobile": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"mode": {
|
"mode": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"payment_method": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"reference": {
|
"reference": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -11035,33 +11022,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.ChapaWebhookPayload": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"amount": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"description": "Currency string `json:\"currency\"`",
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/domain.PaymentStatus"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"trx_ref": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"domain.ChapaWithdrawalRequest": {
|
"domain.ChapaWithdrawalRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -12876,21 +12836,6 @@
|
||||||
"BANK"
|
"BANK"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"domain.PaymentStatus": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"success",
|
|
||||||
"pending",
|
|
||||||
"completed",
|
|
||||||
"failed"
|
|
||||||
],
|
|
||||||
"x-enum-varnames": [
|
|
||||||
"PaymentStatusSuccessful",
|
|
||||||
"PaymentStatusPending",
|
|
||||||
"PaymentStatusCompleted",
|
|
||||||
"PaymentStatusFailed"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"domain.PopOKCallback": {
|
"domain.PopOKCallback": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
||||||
|
|
@ -38,21 +38,6 @@ definitions:
|
||||||
user_id:
|
user_id:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
domain.ArifpayB2CRequest:
|
|
||||||
properties:
|
|
||||||
Phonenumber:
|
|
||||||
type: string
|
|
||||||
amount:
|
|
||||||
type: number
|
|
||||||
customerEmail:
|
|
||||||
type: string
|
|
||||||
customerPhone:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- amount
|
|
||||||
- customerEmail
|
|
||||||
- customerPhone
|
|
||||||
type: object
|
|
||||||
domain.ArifpayVerifyByTransactionIDRequest:
|
domain.ArifpayVerifyByTransactionIDRequest:
|
||||||
properties:
|
properties:
|
||||||
paymentType:
|
paymentType:
|
||||||
|
|
@ -546,37 +531,43 @@ definitions:
|
||||||
updated_at:
|
updated_at:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
domain.ChapaVerificationResponse:
|
domain.ChapaWebhookCustomization:
|
||||||
properties:
|
properties:
|
||||||
data:
|
description:
|
||||||
|
type: string
|
||||||
|
logo:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.ChapaWebhookPayment:
|
||||||
properties:
|
properties:
|
||||||
amount:
|
amount:
|
||||||
type: number
|
type: string
|
||||||
charge:
|
charge:
|
||||||
type: number
|
type: string
|
||||||
created_at:
|
created_at:
|
||||||
type: string
|
type: string
|
||||||
currency:
|
currency:
|
||||||
type: string
|
type: string
|
||||||
customization:
|
customization:
|
||||||
properties:
|
$ref: '#/definitions/domain.ChapaWebhookCustomization'
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
logo: {}
|
|
||||||
title:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
|
event:
|
||||||
|
type: string
|
||||||
first_name:
|
first_name:
|
||||||
type: string
|
type: string
|
||||||
last_name:
|
last_name:
|
||||||
type: string
|
type: string
|
||||||
meta: {}
|
meta:
|
||||||
method:
|
description: may vary in structure, so kept flexible
|
||||||
|
mobile:
|
||||||
type: string
|
type: string
|
||||||
mode:
|
mode:
|
||||||
type: string
|
type: string
|
||||||
|
payment_method:
|
||||||
|
type: string
|
||||||
reference:
|
reference:
|
||||||
type: string
|
type: string
|
||||||
status:
|
status:
|
||||||
|
|
@ -588,22 +579,6 @@ definitions:
|
||||||
updated_at:
|
updated_at:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.ChapaWebhookPayload:
|
|
||||||
properties:
|
|
||||||
amount:
|
|
||||||
type: integer
|
|
||||||
status:
|
|
||||||
allOf:
|
|
||||||
- $ref: '#/definitions/domain.PaymentStatus'
|
|
||||||
description: Currency string `json:"currency"`
|
|
||||||
trx_ref:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
domain.ChapaWithdrawalRequest:
|
domain.ChapaWithdrawalRequest:
|
||||||
properties:
|
properties:
|
||||||
account_name:
|
account_name:
|
||||||
|
|
@ -1844,18 +1819,6 @@ definitions:
|
||||||
- TELEBIRR_TRANSACTION
|
- TELEBIRR_TRANSACTION
|
||||||
- ARIFPAY_TRANSACTION
|
- ARIFPAY_TRANSACTION
|
||||||
- BANK
|
- BANK
|
||||||
domain.PaymentStatus:
|
|
||||||
enum:
|
|
||||||
- success
|
|
||||||
- pending
|
|
||||||
- completed
|
|
||||||
- failed
|
|
||||||
type: string
|
|
||||||
x-enum-varnames:
|
|
||||||
- PaymentStatusSuccessful
|
|
||||||
- PaymentStatusPending
|
|
||||||
- PaymentStatusCompleted
|
|
||||||
- PaymentStatusFailed
|
|
||||||
domain.PopOKCallback:
|
domain.PopOKCallback:
|
||||||
properties:
|
properties:
|
||||||
amount:
|
amount:
|
||||||
|
|
@ -4608,7 +4571,7 @@ paths:
|
||||||
name: request
|
name: request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.ArifpayB2CRequest'
|
$ref: '#/definitions/domain.CheckoutSessionClientRequest'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
|
|
@ -4695,7 +4658,7 @@ paths:
|
||||||
summary: Create Arifpay Checkout Session
|
summary: Create Arifpay Checkout Session
|
||||||
tags:
|
tags:
|
||||||
- Arifpay
|
- Arifpay
|
||||||
/api/v1/arifpay/checkout/{sessionId}/cancel:
|
/api/v1/arifpay/checkout/cancel/{sessionId}:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
|
|
@ -5789,22 +5752,21 @@ paths:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Handles payment notifications from Chapa
|
description: Handles payment and transfer notifications from Chapa
|
||||||
parameters:
|
parameters:
|
||||||
- description: Webhook payload
|
- description: Webhook payload
|
||||||
in: body
|
in: body
|
||||||
name: request
|
name: request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.ChapaWebhookPayload'
|
$ref: '#/definitions/domain.ChapaWebhookPayment'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
additionalProperties: true
|
$ref: '#/definitions/domain.Response'
|
||||||
type: object
|
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
|
|
@ -5832,7 +5794,7 @@ paths:
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"201":
|
"200":
|
||||||
description: Chapa withdrawal process initiated successfully
|
description: Chapa withdrawal process initiated successfully
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.Response'
|
$ref: '#/definitions/domain.Response'
|
||||||
|
|
@ -5861,11 +5823,12 @@ paths:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Perform a USD to ETB currency swap using Chapa's API
|
description: Convert an amount from one currency to another using Chapa's currency
|
||||||
|
swap API
|
||||||
parameters:
|
parameters:
|
||||||
- description: Swap Request Payload
|
- description: Swap request payload
|
||||||
in: body
|
in: body
|
||||||
name: payload
|
name: request
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.SwapRequest'
|
$ref: '#/definitions/domain.SwapRequest'
|
||||||
|
|
@ -5884,7 +5847,7 @@ paths:
|
||||||
description: Internal Server Error
|
description: Internal Server Error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.ErrorResponse'
|
$ref: '#/definitions/domain.ErrorResponse'
|
||||||
summary: Initiate a currency swap
|
summary: Swap currency using Chapa API
|
||||||
tags:
|
tags:
|
||||||
- Chapa
|
- Chapa
|
||||||
/api/v1/chapa/transaction/cancel/{tx_ref}:
|
/api/v1/chapa/transaction/cancel/{tx_ref}:
|
||||||
|
|
@ -5970,7 +5933,7 @@ paths:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/domain.ChapaVerificationResponse'
|
$ref: '#/definitions/domain.Response'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
|
|
|
||||||
|
|
@ -1093,11 +1093,8 @@ type VirtualGameSession struct {
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
GameID string `json:"game_id"`
|
GameID string `json:"game_id"`
|
||||||
SessionToken string `json:"session_token"`
|
SessionToken string `json:"session_token"`
|
||||||
Currency string `json:"currency"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||||
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type VirtualGameTransaction struct {
|
type VirtualGameTransaction struct {
|
||||||
|
|
|
||||||
|
|
@ -411,52 +411,34 @@ const CreateVirtualGameSession = `-- name: CreateVirtualGameSession :one
|
||||||
INSERT INTO virtual_game_sessions (
|
INSERT INTO virtual_game_sessions (
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
expires_at
|
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)
|
VALUES ($1, $2, $3)
|
||||||
RETURNING id,
|
RETURNING
|
||||||
|
id,
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token,
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
expires_at
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateVirtualGameSessionParams struct {
|
type CreateVirtualGameSessionParams struct {
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
GameID string `json:"game_id"`
|
GameID string `json:"game_id"`
|
||||||
SessionToken string `json:"session_token"`
|
SessionToken string `json:"session_token"`
|
||||||
Currency string `json:"currency"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateVirtualGameSession(ctx context.Context, arg CreateVirtualGameSessionParams) (VirtualGameSession, error) {
|
func (q *Queries) CreateVirtualGameSession(ctx context.Context, arg CreateVirtualGameSessionParams) (VirtualGameSession, error) {
|
||||||
row := q.db.QueryRow(ctx, CreateVirtualGameSession,
|
row := q.db.QueryRow(ctx, CreateVirtualGameSession, arg.UserID, arg.GameID, arg.SessionToken)
|
||||||
arg.UserID,
|
|
||||||
arg.GameID,
|
|
||||||
arg.SessionToken,
|
|
||||||
arg.Currency,
|
|
||||||
arg.Status,
|
|
||||||
arg.ExpiresAt,
|
|
||||||
)
|
|
||||||
var i VirtualGameSession
|
var i VirtualGameSession
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.UserID,
|
&i.UserID,
|
||||||
&i.GameID,
|
&i.GameID,
|
||||||
&i.SessionToken,
|
&i.SessionToken,
|
||||||
&i.Currency,
|
|
||||||
&i.Status,
|
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.ExpiresAt,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -751,11 +733,8 @@ SELECT id,
|
||||||
user_id,
|
user_id,
|
||||||
game_id,
|
game_id,
|
||||||
session_token,
|
session_token,
|
||||||
currency,
|
|
||||||
status,
|
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at
|
||||||
expires_at
|
|
||||||
FROM virtual_game_sessions
|
FROM virtual_game_sessions
|
||||||
WHERE session_token = $1
|
WHERE session_token = $1
|
||||||
`
|
`
|
||||||
|
|
@ -768,11 +747,34 @@ func (q *Queries) GetVirtualGameSessionByToken(ctx context.Context, sessionToken
|
||||||
&i.UserID,
|
&i.UserID,
|
||||||
&i.GameID,
|
&i.GameID,
|
||||||
&i.SessionToken,
|
&i.SessionToken,
|
||||||
&i.Currency,
|
|
||||||
&i.Status,
|
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
&i.ExpiresAt,
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetVirtualGameSessionByUserID = `-- name: GetVirtualGameSessionByUserID :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
game_id,
|
||||||
|
session_token,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM virtual_game_sessions
|
||||||
|
WHERE user_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetVirtualGameSessionByUserID(ctx context.Context, userID int64) (VirtualGameSession, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetVirtualGameSessionByUserID, userID)
|
||||||
|
var i VirtualGameSession
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.UserID,
|
||||||
|
&i.GameID,
|
||||||
|
&i.SessionToken,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
@ -1117,23 +1119,6 @@ func (q *Queries) UpdateVirtualGameProviderReportByDate(ctx context.Context, arg
|
||||||
return err
|
return 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 `json:"id"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
const UpdateVirtualGameTransactionStatus = `-- name: UpdateVirtualGameTransactionStatus :exec
|
||||||
UPDATE virtual_game_transactions
|
UPDATE virtual_game_transactions
|
||||||
SET status = $2,
|
SET status = $2,
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ type Config struct {
|
||||||
AFRO_SMS_SENDER_NAME string
|
AFRO_SMS_SENDER_NAME string
|
||||||
AFRO_SMS_RECEIVER_PHONE_NUMBER string
|
AFRO_SMS_RECEIVER_PHONE_NUMBER string
|
||||||
ADRO_SMS_HOST_URL string
|
ADRO_SMS_HOST_URL string
|
||||||
|
CHAPA_WEBHOOK_SECRET string
|
||||||
CHAPA_TRANSFER_TYPE string
|
CHAPA_TRANSFER_TYPE string
|
||||||
CHAPA_PAYMENT_TYPE string
|
CHAPA_PAYMENT_TYPE string
|
||||||
CHAPA_SECRET_KEY string
|
CHAPA_SECRET_KEY string
|
||||||
|
|
@ -259,6 +260,7 @@ func (c *Config) loadEnv() error {
|
||||||
c.TELEBIRR.TelebirrCallbackURL = os.Getenv("TELEBIRR_CALLBACK_URL")
|
c.TELEBIRR.TelebirrCallbackURL = os.Getenv("TELEBIRR_CALLBACK_URL")
|
||||||
|
|
||||||
//Chapa
|
//Chapa
|
||||||
|
c.CHAPA_WEBHOOK_SECRET = os.Getenv("CHAPA_WEBHOOK_SECRET")
|
||||||
c.CHAPA_SECRET_KEY = os.Getenv("CHAPA_SECRET_KEY")
|
c.CHAPA_SECRET_KEY = os.Getenv("CHAPA_SECRET_KEY")
|
||||||
c.CHAPA_PUBLIC_KEY = os.Getenv("CHAPA_PUBLIC_KEY")
|
c.CHAPA_PUBLIC_KEY = os.Getenv("CHAPA_PUBLIC_KEY")
|
||||||
c.CHAPA_ENCRYPTION_KEY = os.Getenv("CHAPA_ENCRYPTION_KEY")
|
c.CHAPA_ENCRYPTION_KEY = os.Getenv("CHAPA_ENCRYPTION_KEY")
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,12 @@ type WebhookRequest struct {
|
||||||
SessionID string `json:"sessionId"`
|
SessionID string `json:"sessionId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArifpayB2CRequest struct{
|
// type ArifpayB2CRequest struct{
|
||||||
PhoneNumber string `json:"Phonenumber"`
|
// PhoneNumber string `json:"Phonenumber"`
|
||||||
Amount float64 `json:"amount" binding:"required"`
|
// Amount float64 `json:"amount" binding:"required"`
|
||||||
CustomerEmail string `json:"customerEmail" binding:"required"`
|
// CustomerEmail string `json:"customerEmail" binding:"required"`
|
||||||
CustomerPhone string `json:"customerPhone" binding:"required"`
|
// // CustomerPhone string `json:"customerPhone" binding:"required"`
|
||||||
}
|
// }
|
||||||
|
|
||||||
type ArifpayVerifyByTransactionIDRequest struct{
|
type ArifpayVerifyByTransactionIDRequest struct{
|
||||||
TransactionId string `json:"transactionId"`
|
TransactionId string `json:"transactionId"`
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ type ChapaDepositVerification struct {
|
||||||
Currency string
|
Currency string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaVerificationResponse struct {
|
type ChapaPaymentVerificationResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Data struct {
|
Data struct {
|
||||||
|
|
@ -103,6 +103,31 @@ type ChapaVerificationResponse struct {
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChapaTransferVerificationResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
AccountName string `json:"account_name"`
|
||||||
|
AccountNumber string `json:"account_number"`
|
||||||
|
Mobile interface{} `json:"mobile"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Charge float64 `json:"charge"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
TransferMethod string `json:"transfer_method"`
|
||||||
|
Narration interface{} `json:"narration"`
|
||||||
|
ChapaTransferID string `json:"chapa_transfer_id"`
|
||||||
|
BankCode int `json:"bank_code"`
|
||||||
|
BankName string `json:"bank_name"`
|
||||||
|
CrossPartyReference interface{} `json:"cross_party_reference"`
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
type ChapaAllTransactionsResponse struct {
|
type ChapaAllTransactionsResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
|
@ -182,6 +207,57 @@ type ChapaCustomer struct {
|
||||||
// BankLogo string `json:"bank_logo"` // URL or base64
|
// BankLogo string `json:"bank_logo"` // URL or base64
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
type SwapResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
RefID string `json:"ref_id"`
|
||||||
|
FromCurrency string `json:"from_currency"`
|
||||||
|
ToCurrency string `json:"to_currency"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
ExchangedAmount float64 `json:"exchanged_amount"`
|
||||||
|
Charge float64 `json:"charge"`
|
||||||
|
Rate float64 `json:"rate"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaTransfersListResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Meta struct {
|
||||||
|
CurrentPage int `json:"current_page"`
|
||||||
|
FirstPageURL string `json:"first_page_url"`
|
||||||
|
LastPage int `json:"last_page"`
|
||||||
|
LastPageURL string `json:"last_page_url"`
|
||||||
|
NextPageURL string `json:"next_page_url"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
PerPage int `json:"per_page"`
|
||||||
|
PrevPageURL interface{} `json:"prev_page_url"`
|
||||||
|
To int `json:"to"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
Error []interface{} `json:"error"`
|
||||||
|
} `json:"meta"`
|
||||||
|
Data []struct {
|
||||||
|
AccountName string `json:"account_name"`
|
||||||
|
AccountNumber string `json:"account_number"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Charge float64 `json:"charge"`
|
||||||
|
TransferType string `json:"transfer_type"`
|
||||||
|
ChapaReference string `json:"chapa_reference"`
|
||||||
|
BankCode int `json:"bank_code"`
|
||||||
|
BankName string `json:"bank_name"`
|
||||||
|
BankReference interface{} `json:"bank_reference"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Reference interface{} `json:"reference"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
type BankResponse struct {
|
type BankResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
|
@ -246,44 +322,49 @@ type ChapaTransactionType struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaWebHookTransfer struct {
|
type ChapaWebhookTransfer struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
Type string `json:"type"`
|
||||||
AccountName string `json:"account_name"`
|
AccountName string `json:"account_name"`
|
||||||
AccountNumber string `json:"account_number"`
|
AccountNumber string `json:"account_number"`
|
||||||
BankId string `json:"bank_id"`
|
BankID int `json:"bank_id"`
|
||||||
BankName string `json:"bank_name"`
|
BankName string `json:"bank_name"`
|
||||||
Currency string `json:"currency"`
|
|
||||||
Amount string `json:"amount"`
|
Amount string `json:"amount"`
|
||||||
Type string `json:"type"`
|
Charge string `json:"charge"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Reference string `json:"reference"`
|
Reference string `json:"reference"`
|
||||||
TxRef string `json:"tx_ref"`
|
|
||||||
ChapaReference string `json:"chapa_reference"`
|
ChapaReference string `json:"chapa_reference"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
BankReference string `json:"bank_reference"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChapaWebHookPayment struct {
|
type ChapaWebhookPayment struct {
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
LastName string `json:"last_name"`
|
LastName string `json:"last_name"`
|
||||||
Email string `json:"email"`
|
Email *string `json:"email,omitempty"`
|
||||||
Mobile interface{} `json:"mobile"`
|
Mobile string `json:"mobile"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
Amount string `json:"amount"`
|
Amount string `json:"amount"`
|
||||||
Charge string `json:"charge"`
|
Charge string `json:"charge"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
Reference string `json:"reference"`
|
Reference string `json:"reference"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt string `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt string `json:"updated_at"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
TxRef string `json:"tx_ref"`
|
TxRef string `json:"tx_ref"`
|
||||||
PaymentMethod string `json:"payment_method"`
|
PaymentMethod string `json:"payment_method"`
|
||||||
Customization struct {
|
Customization ChapaWebhookCustomization `json:"customization"`
|
||||||
Title interface{} `json:"title"`
|
Meta interface{} `json:"meta"` // may vary in structure, so kept flexible
|
||||||
Description interface{} `json:"description"`
|
}
|
||||||
Logo interface{} `json:"logo"`
|
|
||||||
} `json:"customization"`
|
type ChapaWebhookCustomization struct {
|
||||||
Meta string `json:"meta"`
|
Title *string `json:"title,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Logo *string `json:"logo,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Balance struct {
|
type Balance struct {
|
||||||
|
|
@ -298,19 +379,6 @@ type SwapRequest struct {
|
||||||
Amount float64 `json:"amount"`
|
Amount float64 `json:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SwapResponse struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
RefID string `json:"ref_id"`
|
|
||||||
FromCurrency string `json:"from_currency"`
|
|
||||||
ToCurrency string `json:"to_currency"`
|
|
||||||
Amount float64 `json:"amount"`
|
|
||||||
ExchangedAmount float64 `json:"exchanged_amount"`
|
|
||||||
Charge float64 `json:"charge"`
|
|
||||||
Rate float64 `json:"rate"`
|
|
||||||
CreatedAt string `json:"created_at"`
|
|
||||||
UpdatedAt string `json:"updated_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChapaCancelResponse struct {
|
type ChapaCancelResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,10 @@ type GameStartRequest struct {
|
||||||
Country string `json:"country"`
|
Country string `json:"country"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
BrandID string `json:"brandId"`
|
BrandID string `json:"brandId"`
|
||||||
UserAgent string `json:"userAgent,omitempty"`
|
// UserAgent string `json:"userAgent,omitempty"`
|
||||||
LobbyURL string `json:"lobbyUrl,omitempty"`
|
// LobbyURL string `json:"lobbyUrl,omitempty"`
|
||||||
CashierURL string `json:"cashierUrl,omitempty"`
|
// CashierURL string `json:"cashierUrl,omitempty"`
|
||||||
PlayerName string `json:"playerName,omitempty"`
|
// PlayerName string `json:"playerName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DemoGameRequest struct {
|
type DemoGameRequest struct {
|
||||||
|
|
@ -71,8 +71,8 @@ type DemoGameRequest struct {
|
||||||
DeviceType string `json:"deviceType"`
|
DeviceType string `json:"deviceType"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
BrandID string `json:"brandId"`
|
BrandID string `json:"brandId"`
|
||||||
PlayerID string `json:"playerId,omitempty"`
|
// PlayerID string `json:"playerId,omitempty"`
|
||||||
Country string `json:"country,omitempty"`
|
// Country string `json:"country,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GameStartResponse struct {
|
type GameStartResponse struct {
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,9 @@ type VirtualGameRepository interface {
|
||||||
ListVirtualGameProviders(ctx context.Context, limit, offset int32) ([]dbgen.VirtualGameProvider, error)
|
ListVirtualGameProviders(ctx context.Context, limit, offset int32) ([]dbgen.VirtualGameProvider, error)
|
||||||
UpdateVirtualGameProviderEnabled(ctx context.Context, providerID string, enabled bool) (dbgen.VirtualGameProvider, error)
|
UpdateVirtualGameProviderEnabled(ctx context.Context, providerID string, enabled bool) (dbgen.VirtualGameProvider, error)
|
||||||
CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error
|
CreateVirtualGameSession(ctx context.Context, session *domain.VirtualGameSession) error
|
||||||
|
GetVirtualGameSessionByUserID(ctx context.Context, userID int64) (*domain.VirtualGameSession, error)
|
||||||
GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error)
|
GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error)
|
||||||
UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error
|
// UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error
|
||||||
CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error
|
CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error
|
||||||
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
|
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, error)
|
||||||
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
|
UpdateVirtualGameTransactionStatus(ctx context.Context, id int64, status string) error
|
||||||
|
|
@ -166,14 +167,33 @@ func (r *VirtualGameRepo) CreateVirtualGameSession(ctx context.Context, session
|
||||||
UserID: session.UserID,
|
UserID: session.UserID,
|
||||||
GameID: session.GameID,
|
GameID: session.GameID,
|
||||||
SessionToken: session.SessionToken,
|
SessionToken: session.SessionToken,
|
||||||
Currency: session.Currency,
|
// Currency: session.Currency,
|
||||||
Status: session.Status,
|
// Status: session.Status,
|
||||||
ExpiresAt: pgtype.Timestamptz{Time: session.ExpiresAt, Valid: true},
|
// ExpiresAt: pgtype.Timestamptz{Time: session.ExpiresAt, Valid: true},
|
||||||
}
|
}
|
||||||
_, err := r.store.queries.CreateVirtualGameSession(ctx, params)
|
_, err := r.store.queries.CreateVirtualGameSession(ctx, params)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *VirtualGameRepo) GetVirtualGameSessionByUserID(ctx context.Context, userID int64) (*domain.VirtualGameSession, error) {
|
||||||
|
dbSession, err := r.store.queries.GetVirtualGameSessionByUserID(ctx, userID)
|
||||||
|
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,
|
||||||
|
CreatedAt: dbSession.CreatedAt.Time,
|
||||||
|
UpdatedAt: dbSession.UpdatedAt.Time,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *VirtualGameRepo) GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error) {
|
func (r *VirtualGameRepo) GetVirtualGameSessionByToken(ctx context.Context, token string) (*domain.VirtualGameSession, error) {
|
||||||
dbSession, err := r.store.queries.GetVirtualGameSessionByToken(ctx, token)
|
dbSession, err := r.store.queries.GetVirtualGameSessionByToken(ctx, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -187,20 +207,20 @@ func (r *VirtualGameRepo) GetVirtualGameSessionByToken(ctx context.Context, toke
|
||||||
UserID: dbSession.UserID,
|
UserID: dbSession.UserID,
|
||||||
GameID: dbSession.GameID,
|
GameID: dbSession.GameID,
|
||||||
SessionToken: dbSession.SessionToken,
|
SessionToken: dbSession.SessionToken,
|
||||||
Currency: dbSession.Currency,
|
// Currency: dbSession.Currency,
|
||||||
Status: dbSession.Status,
|
// Status: dbSession.Status,
|
||||||
CreatedAt: dbSession.CreatedAt.Time,
|
CreatedAt: dbSession.CreatedAt.Time,
|
||||||
UpdatedAt: dbSession.UpdatedAt.Time,
|
UpdatedAt: dbSession.UpdatedAt.Time,
|
||||||
ExpiresAt: dbSession.ExpiresAt.Time,
|
// ExpiresAt: dbSession.ExpiresAt.Time,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *VirtualGameRepo) UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error {
|
// func (r *VirtualGameRepo) UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error {
|
||||||
return r.store.queries.UpdateVirtualGameSessionStatus(ctx, dbgen.UpdateVirtualGameSessionStatusParams{
|
// return r.store.queries.UpdateVirtualGameSessionStatus(ctx, dbgen.UpdateVirtualGameSessionStatusParams{
|
||||||
ID: id,
|
// ID: id,
|
||||||
Status: status,
|
// Status: status,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error {
|
func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error {
|
||||||
params := dbgen.CreateVirtualGameTransactionParams{
|
params := dbgen.CreateVirtualGameTransactionParams{
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
|
@ -31,7 +32,7 @@ func NewArifpayService(cfg *config.Config, transferStore wallet.TransferStore, w
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ArifpayService) CreateCheckoutSession(req domain.CheckoutSessionClientRequest, isDeposit bool) (map[string]any, error) {
|
func (s *ArifpayService) CreateCheckoutSession(req domain.CheckoutSessionClientRequest, isDeposit bool, userId int64) (map[string]any, error) {
|
||||||
// Generate unique nonce
|
// Generate unique nonce
|
||||||
nonce := uuid.NewString()
|
nonce := uuid.NewString()
|
||||||
|
|
||||||
|
|
@ -129,6 +130,10 @@ func (s *ArifpayService) CreateCheckoutSession(req domain.CheckoutSessionClientR
|
||||||
ReferenceNumber: nonce,
|
ReferenceNumber: nonce,
|
||||||
SessionID: fmt.Sprintf("%v", data["sessionId"]),
|
SessionID: fmt.Sprintf("%v", data["sessionId"]),
|
||||||
Status: string(domain.PaymentStatusPending),
|
Status: string(domain.PaymentStatusPending),
|
||||||
|
CashierID: domain.ValidInt64{
|
||||||
|
Value: userId,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.transferStore.CreateTransfer(context.Background(), transfer); err != nil {
|
if _, err := s.transferStore.CreateTransfer(context.Background(), transfer); err != nil {
|
||||||
|
|
@ -138,7 +143,7 @@ func (s *ArifpayService) CreateCheckoutSession(req domain.CheckoutSessionClientR
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ArifpayService) CancelCheckoutSession(ctx context.Context, sessionID string) (*domain.CancelCheckoutSessionResponse, error) {
|
func (s *ArifpayService) CancelCheckoutSession(ctx context.Context, sessionID string) (any, error) {
|
||||||
// Build the cancel URL
|
// Build the cancel URL
|
||||||
url := fmt.Sprintf("%s/api/sandbox/checkout/session/%s", s.cfg.ARIFPAY.BaseURL, sessionID)
|
url := fmt.Sprintf("%s/api/sandbox/checkout/session/%s", s.cfg.ARIFPAY.BaseURL, sessionID)
|
||||||
|
|
||||||
|
|
@ -176,17 +181,19 @@ func (s *ArifpayService) CancelCheckoutSession(ctx context.Context, sessionID st
|
||||||
return nil, fmt.Errorf("failed to unmarshal cancel response: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal cancel response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cancelResp, nil
|
return cancelResp.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ArifpayService) HandleWebhook(ctx context.Context, req domain.WebhookRequest, userId int64, isDepost bool) error {
|
func (s *ArifpayService) ProcessWebhook(ctx context.Context, req domain.WebhookRequest, isDeposit bool) error {
|
||||||
// 1. Get transfer by SessionID
|
// 1. Get transfer by SessionID
|
||||||
transfer, err := s.transferStore.GetTransferByReference(ctx, req.Transaction.TransactionID)
|
transfer, err := s.transferStore.GetTransferByReference(ctx, req.Nonce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
|
userId := transfer.DepositorID.Value
|
||||||
|
|
||||||
|
wallet, err := s.walletSvc.GetCustomerWallet(ctx, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +203,7 @@ func (s *ArifpayService) HandleWebhook(ctx context.Context, req domain.WebhookRe
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Update transfer status
|
// 2. Update transfer status
|
||||||
newStatus := req.Transaction.TransactionStatus
|
newStatus := strings.ToLower(req.Transaction.TransactionStatus)
|
||||||
// if req.Transaction.TransactionStatus != "" {
|
// if req.Transaction.TransactionStatus != "" {
|
||||||
// newStatus = req.Transaction.TransactionStatus
|
// newStatus = req.Transaction.TransactionStatus
|
||||||
// }
|
// }
|
||||||
|
|
@ -212,10 +219,10 @@ func (s *ArifpayService) HandleWebhook(ctx context.Context, req domain.WebhookRe
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. If SUCCESS -> update customer wallet balance
|
// 3. If SUCCESS -> update customer wallet balance
|
||||||
if (newStatus == "SUCCESS" && isDepost) || (newStatus == "FAILED" && !isDepost) {
|
if (newStatus == "success" && isDeposit) || (newStatus == "failed" && !isDeposit) {
|
||||||
_, err = s.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.TotalAmount), domain.ValidInt64{}, transfer.PaymentMethod, domain.PaymentDetails{
|
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.TotalAmount), domain.ValidInt64{}, transfer.PaymentMethod, domain.PaymentDetails{
|
||||||
ReferenceNumber: domain.ValidString{
|
ReferenceNumber: domain.ValidString{
|
||||||
Value: req.Transaction.TransactionID,
|
Value: req.Nonce,
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
BankNumber: domain.ValidString{
|
BankNumber: domain.ValidString{
|
||||||
|
|
@ -231,35 +238,94 @@ func (s *ArifpayService) HandleWebhook(ctx context.Context, req domain.WebhookRe
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ArifpayService) ExecuteTelebirrB2CTransfer(ctx context.Context, req domain.ArifpayB2CRequest, userId int64) error {
|
func (s *ArifpayService) ExecuteTelebirrB2CTransfer(ctx context.Context, req domain.CheckoutSessionClientRequest, userId int64) error {
|
||||||
// Step 1: Create Session
|
// Step 1: Create Session
|
||||||
|
|
||||||
|
userWallet, err := s.walletSvc.GetCustomerWallet(ctx, userId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get user wallets: %w", err)
|
||||||
|
}
|
||||||
|
// if len(userWallets) == 0 {
|
||||||
|
// return fmt.Errorf("no wallet found for user %d", userId)
|
||||||
|
// }
|
||||||
|
|
||||||
|
_, err = s.walletSvc.DeductFromWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
referenceNum := uuid.NewString()
|
referenceNum := uuid.NewString()
|
||||||
|
|
||||||
sessionReq := domain.CheckoutSessionClientRequest{
|
sessionReq := domain.CheckoutSessionClientRequest{
|
||||||
Amount: req.Amount,
|
Amount: req.Amount,
|
||||||
CustomerEmail: req.CustomerEmail,
|
CustomerEmail: req.CustomerEmail,
|
||||||
CustomerPhone: req.CustomerPhone,
|
CustomerPhone: "251" + req.CustomerPhone[:9],
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionResp, err := s.CreateCheckoutSession(sessionReq, false)
|
sessionResp, err := s.CreateCheckoutSession(sessionReq, false, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_, err = s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
return fmt.Errorf("failed to create session: %w", err)
|
return fmt.Errorf("failed to create session: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessionRespData := sessionResp["data"].(map[string]any)
|
||||||
|
|
||||||
// Step 2: Execute Transfer
|
// Step 2: Execute Transfer
|
||||||
transferURL := fmt.Sprintf("%s/api/Telebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
transferURL := fmt.Sprintf("%s/api/Telebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
||||||
reqBody := map[string]any{
|
reqBody := map[string]any{
|
||||||
"Sessionid": sessionResp["sessionId"],
|
"Sessionid": sessionRespData["sessionId"],
|
||||||
"Phonenumber": req.PhoneNumber,
|
"Phonenumber": "251" + req.CustomerPhone[:9],
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, err := json.Marshal(reqBody)
|
payload, err := json.Marshal(reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_, err = s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
return fmt.Errorf("failed to marshal transfer request: %w", err)
|
return fmt.Errorf("failed to marshal transfer request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_, err = s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
return fmt.Errorf("failed to build transfer request: %w", err)
|
return fmt.Errorf("failed to build transfer request: %w", err)
|
||||||
}
|
}
|
||||||
transferReq.Header.Set("Content-Type", "application/json")
|
transferReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
@ -267,11 +333,35 @@ func (s *ArifpayService) ExecuteTelebirrB2CTransfer(ctx context.Context, req dom
|
||||||
|
|
||||||
transferResp, err := s.httpClient.Do(transferReq)
|
transferResp, err := s.httpClient.Do(transferReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_, err = s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
return fmt.Errorf("failed to execute transfer request: %w", err)
|
return fmt.Errorf("failed to execute transfer request: %w", err)
|
||||||
}
|
}
|
||||||
defer transferResp.Body.Close()
|
defer transferResp.Body.Close()
|
||||||
|
|
||||||
if transferResp.StatusCode >= 300 {
|
if transferResp.StatusCode != http.StatusOK {
|
||||||
|
_, err = s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
body, _ := io.ReadAll(transferResp.Body)
|
body, _ := io.ReadAll(transferResp.Body)
|
||||||
return fmt.Errorf("transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
return fmt.Errorf("transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
@ -282,109 +372,33 @@ func (s *ArifpayService) ExecuteTelebirrB2CTransfer(ctx context.Context, req dom
|
||||||
Verified: false,
|
Verified: false,
|
||||||
Type: domain.WITHDRAW, // B2C = payout
|
Type: domain.WITHDRAW, // B2C = payout
|
||||||
ReferenceNumber: referenceNum,
|
ReferenceNumber: referenceNum,
|
||||||
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
|
SessionID: fmt.Sprintf("%v", sessionRespData["sessionId"]),
|
||||||
Status: string(domain.PaymentStatusPending),
|
Status: string(domain.PaymentStatusPending),
|
||||||
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
||||||
|
CashierID: domain.ValidInt64{
|
||||||
|
Value: userId,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
||||||
return fmt.Errorf("failed to store transfer: %w", err)
|
return fmt.Errorf("failed to store transfer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Deduct from wallet
|
// Step 4: Deduct from wallet
|
||||||
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get user wallets: %w", err)
|
|
||||||
}
|
|
||||||
if len(userWallets) == 0 {
|
|
||||||
return fmt.Errorf("no wallet found for user %d", userId)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.walletSvc.DeductFromWallet(
|
|
||||||
ctx,
|
|
||||||
userWallets[0].ID,
|
|
||||||
domain.Currency(req.Amount),
|
|
||||||
domain.ValidInt64{},
|
|
||||||
domain.TRANSFER_ARIFPAY,
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to deduct from wallet: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ArifpayService) ExecuteCBEB2CTransfer(ctx context.Context, req domain.ArifpayB2CRequest, userId int64) error {
|
func (s *ArifpayService) ExecuteCBEB2CTransfer(ctx context.Context, req domain.CheckoutSessionClientRequest, userId int64) error {
|
||||||
// Step 1: Create Session
|
// Step 1: Deduct from user wallet first
|
||||||
referenceNum := uuid.NewString()
|
userWallet, err := s.walletSvc.GetCustomerWallet(ctx, userId)
|
||||||
|
|
||||||
sessionReq := domain.CheckoutSessionClientRequest{
|
|
||||||
Amount: req.Amount,
|
|
||||||
CustomerEmail: req.CustomerEmail,
|
|
||||||
CustomerPhone: req.CustomerPhone,
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionResp, err := s.CreateCheckoutSession(sessionReq, false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cbebirr: failed to create session: %w", err)
|
return fmt.Errorf("cbebirr: failed to get user wallet: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Execute Transfer
|
|
||||||
transferURL := fmt.Sprintf("%s/api/Cbebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
|
||||||
reqBody := map[string]any{
|
|
||||||
"Sessionid": sessionResp["sessionId"],
|
|
||||||
"Phonenumber": req.PhoneNumber,
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, err := json.Marshal(reqBody)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cbebirr: failed to marshal transfer request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cbebirr: failed to build transfer request: %w", err)
|
|
||||||
}
|
|
||||||
transferReq.Header.Set("Content-Type", "application/json")
|
|
||||||
transferReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
|
|
||||||
|
|
||||||
transferResp, err := s.httpClient.Do(transferReq)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cbebirr: failed to execute transfer request: %w", err)
|
|
||||||
}
|
|
||||||
defer transferResp.Body.Close()
|
|
||||||
|
|
||||||
if transferResp.StatusCode >= 300 {
|
|
||||||
body, _ := io.ReadAll(transferResp.Body)
|
|
||||||
return fmt.Errorf("cbebirr: transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Store transfer in DB
|
|
||||||
transfer := domain.CreateTransfer{
|
|
||||||
Amount: domain.Currency(req.Amount),
|
|
||||||
Verified: false,
|
|
||||||
Type: domain.WITHDRAW, // B2C = payout
|
|
||||||
ReferenceNumber: referenceNum,
|
|
||||||
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
|
|
||||||
Status: string(domain.PaymentStatusPending),
|
|
||||||
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
|
||||||
}
|
|
||||||
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
|
||||||
return fmt.Errorf("cbebirr: failed to store transfer: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Deduct from user wallet
|
|
||||||
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cbebirr: failed to get user wallets: %w", err)
|
|
||||||
}
|
|
||||||
if len(userWallets) == 0 {
|
|
||||||
return fmt.Errorf("cbebirr: no wallet found for user %d", userId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.walletSvc.DeductFromWallet(
|
_, err = s.walletSvc.DeductFromWallet(
|
||||||
ctx,
|
ctx,
|
||||||
userWallets[0].ID,
|
userWallet.RegularID,
|
||||||
domain.Currency(req.Amount),
|
domain.Currency(req.Amount),
|
||||||
domain.ValidInt64{},
|
domain.ValidInt64{},
|
||||||
domain.TRANSFER_ARIFPAY,
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
|
@ -394,55 +408,68 @@ func (s *ArifpayService) ExecuteCBEB2CTransfer(ctx context.Context, req domain.A
|
||||||
return fmt.Errorf("cbebirr: failed to deduct from wallet: %w", err)
|
return fmt.Errorf("cbebirr: failed to deduct from wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ArifpayService) ExecuteMPesaB2CTransfer(ctx context.Context, req domain.ArifpayB2CRequest, userId int64) error {
|
|
||||||
// Step 1: Create Session
|
|
||||||
referenceNum := uuid.NewString()
|
referenceNum := uuid.NewString()
|
||||||
|
|
||||||
|
// Step 2: Create Session
|
||||||
sessionReq := domain.CheckoutSessionClientRequest{
|
sessionReq := domain.CheckoutSessionClientRequest{
|
||||||
Amount: req.Amount,
|
Amount: req.Amount,
|
||||||
CustomerEmail: req.CustomerEmail,
|
CustomerEmail: req.CustomerEmail,
|
||||||
CustomerPhone: req.CustomerPhone,
|
CustomerPhone: "251" + req.CustomerPhone[:9],
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionResp, err := s.CreateCheckoutSession(sessionReq, false)
|
sessionResp, err := s.CreateCheckoutSession(sessionReq, false, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Mpesa: failed to create session: %w", err)
|
// refund wallet if session creation fails
|
||||||
|
_, refundErr := s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if refundErr != nil {
|
||||||
|
return fmt.Errorf("cbebirr: refund failed after session creation error: %v", refundErr)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cbebirr: failed to create session: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Execute Transfer
|
// Step 3: Execute Transfer
|
||||||
transferURL := fmt.Sprintf("%s/api/Mpesa/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
transferURL := fmt.Sprintf("%s/api/Cbebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
||||||
reqBody := map[string]any{
|
reqBody := map[string]any{
|
||||||
"Sessionid": sessionResp["sessionId"],
|
"Sessionid": sessionResp["sessionId"],
|
||||||
"Phonenumber": req.PhoneNumber,
|
"Phonenumber": "251" + req.CustomerPhone[:9],
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, err := json.Marshal(reqBody)
|
payload, err := json.Marshal(reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Mpesa: failed to marshal transfer request: %w", err)
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("cbebirr: failed to marshal transfer request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Mpesa: failed to build transfer request: %w", err)
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("cbebirr: failed to build transfer request: %w", err)
|
||||||
}
|
}
|
||||||
transferReq.Header.Set("Content-Type", "application/json")
|
transferReq.Header.Set("Content-Type", "application/json")
|
||||||
transferReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
|
transferReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
|
||||||
|
|
||||||
transferResp, err := s.httpClient.Do(transferReq)
|
transferResp, err := s.httpClient.Do(transferReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Mpesa: failed to execute transfer request: %w", err)
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("cbebirr: failed to execute transfer request: %w", err)
|
||||||
}
|
}
|
||||||
defer transferResp.Body.Close()
|
defer transferResp.Body.Close()
|
||||||
|
|
||||||
if transferResp.StatusCode >= 300 {
|
if transferResp.StatusCode != http.StatusOK {
|
||||||
body, _ := io.ReadAll(transferResp.Body)
|
body, _ := io.ReadAll(transferResp.Body)
|
||||||
return fmt.Errorf("Mpesa: transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("cbebirr: transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Store transfer in DB
|
// Step 4: Store transfer in DB
|
||||||
transfer := domain.CreateTransfer{
|
transfer := domain.CreateTransfer{
|
||||||
Amount: domain.Currency(req.Amount),
|
Amount: domain.Currency(req.Amount),
|
||||||
Verified: false,
|
Verified: false,
|
||||||
|
|
@ -451,30 +478,116 @@ func (s *ArifpayService) ExecuteMPesaB2CTransfer(ctx context.Context, req domain
|
||||||
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
|
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
|
||||||
Status: string(domain.PaymentStatusPending),
|
Status: string(domain.PaymentStatusPending),
|
||||||
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
||||||
}
|
CashierID: domain.ValidInt64{
|
||||||
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
Value: userId,
|
||||||
return fmt.Errorf("Mpesa: failed to store transfer: %w", err)
|
Valid: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Deduct from user wallet
|
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
||||||
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
|
return fmt.Errorf("cbebirr: failed to store transfer: %w", err)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Mpesa: failed to get user wallets: %w", err)
|
|
||||||
}
|
}
|
||||||
if len(userWallets) == 0 {
|
|
||||||
return fmt.Errorf("Mpesa: no wallet found for user %d", userId)
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ArifpayService) ExecuteMPesaB2CTransfer(ctx context.Context, req domain.CheckoutSessionClientRequest, userId int64) error {
|
||||||
|
// Step 1: Deduct from user wallet first
|
||||||
|
userWallet, err := s.walletSvc.GetCustomerWallet(ctx, userId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("mpesa: failed to get user wallet: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.walletSvc.DeductFromWallet(
|
_, err = s.walletSvc.DeductFromWallet(
|
||||||
ctx,
|
ctx,
|
||||||
userWallets[0].ID,
|
userWallet.RegularID,
|
||||||
domain.Currency(req.Amount),
|
domain.Currency(req.Amount),
|
||||||
domain.ValidInt64{},
|
domain.ValidInt64{},
|
||||||
domain.TRANSFER_ARIFPAY,
|
domain.TRANSFER_ARIFPAY,
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Mpesa: failed to deduct from wallet: %w", err)
|
return fmt.Errorf("mpesa: failed to deduct from wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
referenceNum := uuid.NewString()
|
||||||
|
|
||||||
|
// Step 2: Create Session
|
||||||
|
sessionReq := domain.CheckoutSessionClientRequest{
|
||||||
|
Amount: req.Amount,
|
||||||
|
CustomerEmail: req.CustomerEmail,
|
||||||
|
CustomerPhone: "251" + req.CustomerPhone[:9],
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionResp, err := s.CreateCheckoutSession(sessionReq, false, userId)
|
||||||
|
if err != nil {
|
||||||
|
// Refund wallet if session creation fails
|
||||||
|
_, refundErr := s.walletSvc.AddToWallet(
|
||||||
|
ctx,
|
||||||
|
userWallet.RegularID,
|
||||||
|
domain.Currency(req.Amount),
|
||||||
|
domain.ValidInt64{},
|
||||||
|
domain.TRANSFER_ARIFPAY,
|
||||||
|
domain.PaymentDetails{},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if refundErr != nil {
|
||||||
|
return fmt.Errorf("mpesa: refund failed after session creation error: %v", refundErr)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("mpesa: failed to create session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Execute Transfer
|
||||||
|
transferURL := fmt.Sprintf("%s/api/Mpesa/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
||||||
|
reqBody := map[string]any{
|
||||||
|
"Sessionid": sessionResp["sessionId"],
|
||||||
|
"Phonenumber": "251" + req.CustomerPhone[:9],
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.Marshal(reqBody)
|
||||||
|
if err != nil {
|
||||||
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("mpesa: failed to marshal transfer request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
||||||
|
if err != nil {
|
||||||
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("mpesa: failed to build transfer request: %w", err)
|
||||||
|
}
|
||||||
|
transferReq.Header.Set("Content-Type", "application/json")
|
||||||
|
transferReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
|
||||||
|
|
||||||
|
transferResp, err := s.httpClient.Do(transferReq)
|
||||||
|
if err != nil {
|
||||||
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("mpesa: failed to execute transfer request: %w", err)
|
||||||
|
}
|
||||||
|
defer transferResp.Body.Close()
|
||||||
|
|
||||||
|
if transferResp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(transferResp.Body)
|
||||||
|
s.walletSvc.AddToWallet(ctx, userWallet.RegularID, domain.Currency(req.Amount), domain.ValidInt64{}, domain.TRANSFER_ARIFPAY, domain.PaymentDetails{}, "")
|
||||||
|
return fmt.Errorf("mpesa: transfer failed with status %d: %s", transferResp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Store transfer in DB
|
||||||
|
transfer := domain.CreateTransfer{
|
||||||
|
Amount: domain.Currency(req.Amount),
|
||||||
|
Verified: false,
|
||||||
|
Type: domain.WITHDRAW, // B2C = payout
|
||||||
|
ReferenceNumber: referenceNum,
|
||||||
|
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
|
||||||
|
Status: string(domain.PaymentStatusPending),
|
||||||
|
PaymentMethod: domain.TRANSFER_ARIFPAY,
|
||||||
|
CashierID: domain.ValidInt64{
|
||||||
|
Value: userId,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
||||||
|
return fmt.Errorf("mpesa: failed to store transfer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaInitDepo
|
||||||
"first_name": req.FirstName,
|
"first_name": req.FirstName,
|
||||||
"last_name": req.LastName,
|
"last_name": req.LastName,
|
||||||
"tx_ref": req.TxRef,
|
"tx_ref": req.TxRef,
|
||||||
"callback_url": req.CallbackURL,
|
// "callback_url": req.CallbackURL,
|
||||||
"return_url": req.ReturnURL,
|
"return_url": req.ReturnURL,
|
||||||
"phone_number": req.PhoneNumber,
|
"phone_number": req.PhoneNumber,
|
||||||
}
|
}
|
||||||
|
|
@ -131,7 +131,7 @@ func (c *Client) VerifyPayment(ctx context.Context, reference string) (domain.Ch
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain.ChapaPaymentVerificationResponse, error) {
|
||||||
url := fmt.Sprintf("%s/transaction/verify/%s", c.baseURL, txRef)
|
url := fmt.Sprintf("%s/transaction/verify/%s", c.baseURL, txRef)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
|
@ -153,7 +153,7 @@ func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain
|
||||||
return nil, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body))
|
return nil, fmt.Errorf("unexpected status code: %d - %s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
var verification domain.ChapaVerificationResponse
|
var verification domain.ChapaPaymentVerificationResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +169,7 @@ func (c *Client) ManualVerifyPayment(ctx context.Context, txRef string) (*domain
|
||||||
return &verification, nil
|
return &verification, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ManualVerifyTransfer(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
func (c *Client) ManualVerifyTransfer(ctx context.Context, txRef string) (*domain.ChapaTransferVerificationResponse, error) {
|
||||||
url := fmt.Sprintf("%s/transfers/verify/%s", c.baseURL, txRef)
|
url := fmt.Sprintf("%s/transfers/verify/%s", c.baseURL, txRef)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
|
|
@ -207,7 +207,7 @@ func (c *Client) ManualVerifyTransfer(ctx context.Context, txRef string) (*domai
|
||||||
status = domain.PaymentStatusFailed
|
status = domain.PaymentStatusFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
return &domain.ChapaVerificationResponse{
|
return &domain.ChapaTransferVerificationResponse{
|
||||||
Status: string(status),
|
Status: string(status),
|
||||||
// Amount: response.Amount,
|
// Amount: response.Amount,
|
||||||
// Currency: response.Currency,
|
// Currency: response.Currency,
|
||||||
|
|
@ -277,7 +277,7 @@ func (c *Client) GetTransactionEvents(ctx context.Context, refId string) ([]doma
|
||||||
return response.Data, nil
|
return response.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.BankData, error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/banks", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/banks", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
|
@ -300,9 +300,9 @@ func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var banks []domain.Bank
|
var banks []domain.BankData
|
||||||
for _, bankData := range bankResponse.Data {
|
for _, bankData := range bankResponse.Data {
|
||||||
bank := domain.Bank{
|
bank := domain.BankData{
|
||||||
ID: bankData.ID,
|
ID: bankData.ID,
|
||||||
Slug: bankData.Slug,
|
Slug: bankData.Slug,
|
||||||
Swift: bankData.Swift,
|
Swift: bankData.Swift,
|
||||||
|
|
@ -324,7 +324,7 @@ func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
||||||
return banks, nil
|
return banks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawalRequest) (bool, error) {
|
func (c *Client) InitializeTransfer(ctx context.Context, req domain.ChapaWithdrawalRequest) (bool, error) {
|
||||||
endpoint := c.baseURL + "/transfers"
|
endpoint := c.baseURL + "/transfers"
|
||||||
fmt.Printf("\n\nChapa withdrawal URL is %v\n\n", endpoint)
|
fmt.Printf("\n\nChapa withdrawal URL is %v\n\n", endpoint)
|
||||||
|
|
||||||
|
|
@ -361,7 +361,7 @@ func (c *Client) InitiateTransfer(ctx context.Context, req domain.ChapaWithdrawa
|
||||||
return response.Status == string(domain.WithdrawalStatusSuccessful), nil
|
return response.Status == string(domain.WithdrawalStatusSuccessful), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.ChapaVerificationResponse, error) {
|
func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.ChapaTransferVerificationResponse, error) {
|
||||||
base, err := url.Parse(c.baseURL)
|
base, err := url.Parse(c.baseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid base URL: %w", err)
|
return nil, fmt.Errorf("invalid base URL: %w", err)
|
||||||
|
|
@ -385,7 +385,7 @@ func (c *Client) VerifyTransfer(ctx context.Context, reference string) (*domain.
|
||||||
return nil, fmt.Errorf("chapa api returned status: %d", resp.StatusCode)
|
return nil, fmt.Errorf("chapa api returned status: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
var verification domain.ChapaVerificationResponse
|
var verification domain.ChapaTransferVerificationResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@ import (
|
||||||
|
|
||||||
type ChapaStore interface {
|
type ChapaStore interface {
|
||||||
InitializePayment(request domain.ChapaInitDepositRequest) (domain.ChapaDepositResponse, error)
|
InitializePayment(request domain.ChapaInitDepositRequest) (domain.ChapaDepositResponse, error)
|
||||||
ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error)
|
ManualVerifTransaction(ctx context.Context, txRef string) (*domain.ChapaTransferVerificationResponse, error)
|
||||||
FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
||||||
CreateWithdrawal(userID string, amount float64, accountNumber, bankCode string) (*domain.ChapaWithdrawal, error)
|
CreateWithdrawal(userID string, amount float64, accountNumber, bankCode string) (*domain.ChapaWithdrawal, error)
|
||||||
HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebHookTransfer) error
|
HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebhookTransfer) error
|
||||||
HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) error
|
HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebhookPayment) error
|
||||||
GetPaymentReceiptURL(ctx context.Context, chapaRef string) (string, error)
|
GetPaymentReceiptURL(ctx context.Context, chapaRef string) (string, error)
|
||||||
GetAllTransfers(ctx context.Context) ([]domain.Transfer, error)
|
GetAllTransfers(ctx context.Context) ([]domain.Transfer, error)
|
||||||
GetAccountBalance(ctx context.Context, currencyCode string) ([]domain.Balance, error)
|
GetAccountBalance(ctx context.Context, currencyCode string) ([]domain.Balance, error)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ package chapa
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -43,6 +46,31 @@ func NewService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) VerifyWebhookSignature(ctx context.Context, payload []byte, chapaSignature, xChapaSignature string) (bool, error) {
|
||||||
|
secret := s.cfg.CHAPA_WEBHOOK_SECRET // or os.Getenv("CHAPA_SECRET_KEY")
|
||||||
|
if secret == "" {
|
||||||
|
return false, fmt.Errorf("missing Chapa secret key in configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute expected signature using HMAC SHA256
|
||||||
|
h := hmac.New(sha256.New, []byte(secret))
|
||||||
|
h.Write(payload)
|
||||||
|
expected := hex.EncodeToString(h.Sum(nil))
|
||||||
|
|
||||||
|
// Check either header
|
||||||
|
if chapaSignature == expected || xChapaSignature == expected {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally log for debugging
|
||||||
|
var pretty map[string]interface{}
|
||||||
|
_ = json.Unmarshal(payload, &pretty)
|
||||||
|
fmt.Printf("[Webhook Verification Failed]\nExpected: %s\nGot chapa-signature: %s\nGot x-chapa-signature: %s\nPayload: %+v\n",
|
||||||
|
expected, chapaSignature, xChapaSignature, pretty)
|
||||||
|
|
||||||
|
return false, fmt.Errorf("invalid webhook signature")
|
||||||
|
}
|
||||||
|
|
||||||
// InitiateDeposit starts a new deposit process
|
// InitiateDeposit starts a new deposit process
|
||||||
func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount domain.Currency) (string, error) {
|
func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount domain.Currency) (string, error) {
|
||||||
// Validate amount
|
// Validate amount
|
||||||
|
|
@ -88,7 +116,7 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
ReferenceNumber: reference,
|
ReferenceNumber: reference,
|
||||||
// ReceiverWalletID: 1,
|
// ReceiverWalletID: 1,
|
||||||
SenderWalletID: domain.ValidInt64{
|
SenderWalletID: domain.ValidInt64{
|
||||||
Value: senderWallet.ID,
|
Value: senderWallet.RegularID,
|
||||||
Valid: true,
|
Valid: true,
|
||||||
},
|
},
|
||||||
Verified: false,
|
Verified: false,
|
||||||
|
|
@ -135,9 +163,9 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
||||||
return response.CheckoutURL, nil
|
return response.CheckoutURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ProcessVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaPaymentWebhookRequest) error {
|
func (s *Service) ProcessVerifyDepositWebhook(ctx context.Context, req domain.ChapaWebhookPayment) error {
|
||||||
// Find payment by reference
|
// Find payment by reference
|
||||||
payment, err := s.transferStore.GetTransferByReference(ctx, transfer.TxRef)
|
payment, err := s.transferStore.GetTransferByReference(ctx, req.TxRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ErrPaymentNotFound
|
return domain.ErrPaymentNotFound
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +187,7 @@ func (s *Service) ProcessVerifyDepositWebhook(ctx context.Context, transfer doma
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// If payment is completed, credit user's wallet
|
// If payment is completed, credit user's wallet
|
||||||
if transfer.Status == domain.PaymentStatusSuccessful {
|
if req.Status == string(domain.PaymentStatusSuccessful) {
|
||||||
|
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
||||||
return fmt.Errorf("failed to update is payment verified value: %w", err)
|
return fmt.Errorf("failed to update is payment verified value: %w", err)
|
||||||
|
|
@ -171,7 +199,7 @@ func (s *Service) ProcessVerifyDepositWebhook(ctx context.Context, transfer doma
|
||||||
|
|
||||||
if _, err := s.walletStore.AddToWallet(ctx, payment.SenderWalletID.Value, payment.Amount, domain.ValidInt64{}, domain.TRANSFER_CHAPA, domain.PaymentDetails{
|
if _, err := s.walletStore.AddToWallet(ctx, payment.SenderWalletID.Value, payment.Amount, domain.ValidInt64{}, domain.TRANSFER_CHAPA, domain.PaymentDetails{
|
||||||
ReferenceNumber: domain.ValidString{
|
ReferenceNumber: domain.ValidString{
|
||||||
Value: transfer.TxRef,
|
Value: req.TxRef,
|
||||||
},
|
},
|
||||||
}, fmt.Sprintf("Added %v to wallet using Chapa", payment.Amount)); err != nil {
|
}, fmt.Sprintf("Added %v to wallet using Chapa", payment.Amount)); err != nil {
|
||||||
return fmt.Errorf("failed to credit user wallet: %w", err)
|
return fmt.Errorf("failed to credit user wallet: %w", err)
|
||||||
|
|
@ -288,7 +316,7 @@ func (s *Service) FetchTransactionEvents(ctx context.Context, refID string) ([]d
|
||||||
|
|
||||||
func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req domain.ChapaWithdrawalRequest) (*domain.Transfer, error) {
|
func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req domain.ChapaWithdrawalRequest) (*domain.Transfer, error) {
|
||||||
// Parse and validate amount
|
// Parse and validate amount
|
||||||
amount, err := strconv.ParseInt(req.Amount, 10, 64)
|
amount, err := strconv.ParseFloat(req.Amount, 64)
|
||||||
if err != nil || amount <= 0 {
|
if err != nil || amount <= 0 {
|
||||||
return nil, domain.ErrInvalidWithdrawalAmount
|
return nil, domain.ErrInvalidWithdrawalAmount
|
||||||
}
|
}
|
||||||
|
|
@ -319,7 +347,7 @@ func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req doma
|
||||||
reference := uuid.New().String()
|
reference := uuid.New().String()
|
||||||
|
|
||||||
createTransfer := domain.CreateTransfer{
|
createTransfer := domain.CreateTransfer{
|
||||||
Message: fmt.Sprintf("Withdrawing %d into wallet using chapa. Reference Number %s", amount, reference),
|
Message: fmt.Sprintf("Withdrawing %f into wallet using chapa. Reference Number %s", amount, reference),
|
||||||
Amount: domain.Currency(amount),
|
Amount: domain.Currency(amount),
|
||||||
Type: domain.WITHDRAW,
|
Type: domain.WITHDRAW,
|
||||||
SenderWalletID: domain.ValidInt64{
|
SenderWalletID: domain.ValidInt64{
|
||||||
|
|
@ -341,40 +369,49 @@ func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req doma
|
||||||
transferReq := domain.ChapaWithdrawalRequest{
|
transferReq := domain.ChapaWithdrawalRequest{
|
||||||
AccountName: req.AccountName,
|
AccountName: req.AccountName,
|
||||||
AccountNumber: req.AccountNumber,
|
AccountNumber: req.AccountNumber,
|
||||||
Amount: fmt.Sprintf("%d", amount),
|
Amount: fmt.Sprintf("%f", amount),
|
||||||
Currency: req.Currency,
|
Currency: req.Currency,
|
||||||
Reference: reference,
|
Reference: reference,
|
||||||
// BeneficiaryName: fmt.Sprintf("%s %s", user.FirstName, user.LastName),
|
// BeneficiaryName: fmt.Sprintf("%s %s", user.FirstName, user.LastName),
|
||||||
BankCode: req.BankCode,
|
BankCode: req.BankCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
success, err := s.chapaClient.InitiateTransfer(ctx, transferReq)
|
|
||||||
if err != nil {
|
|
||||||
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
|
||||||
return nil, fmt.Errorf("failed to initiate transfer: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !success {
|
|
||||||
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
|
||||||
return nil, errors.New("chapa rejected the transfer request")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update withdrawal status to processing
|
|
||||||
if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusProcessing)); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to update withdrawal status: %w", err)
|
|
||||||
}
|
|
||||||
// Deduct from wallet (or wait for webhook confirmation depending on your flow)
|
|
||||||
newBalance := float64(wallet.RegularBalance) - float64(amount)
|
newBalance := float64(wallet.RegularBalance) - float64(amount)
|
||||||
if err := s.walletStore.UpdateBalance(ctx, wallet.RegularID, domain.Currency(newBalance)); err != nil {
|
if err := s.walletStore.UpdateBalance(ctx, wallet.RegularID, domain.Currency(newBalance)); err != nil {
|
||||||
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
|
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
success, err := s.chapaClient.InitializeTransfer(ctx, transferReq)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
||||||
|
newBalance := float64(wallet.RegularBalance) + float64(amount)
|
||||||
|
if err := s.walletStore.UpdateBalance(ctx, wallet.RegularID, domain.Currency(newBalance)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to initiate transfer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
_ = s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusFailed))
|
||||||
|
newBalance := float64(wallet.RegularBalance) + float64(amount)
|
||||||
|
if err := s.walletStore.UpdateBalance(ctx, wallet.RegularID, domain.Currency(newBalance)); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update wallet balance: %w", err)
|
||||||
|
}
|
||||||
|
return nil, errors.New("chapa rejected the transfer request")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update withdrawal status to processing
|
||||||
|
// if err := s.transferStore.UpdateTransferStatus(ctx, transfer.ID, string(domain.WithdrawalStatusProcessing)); err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to update withdrawal status: %w", err)
|
||||||
|
// }
|
||||||
|
// Deduct from wallet (or wait for webhook confirmation depending on your flow)
|
||||||
|
|
||||||
return &transfer, nil
|
return &transfer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ProcessVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) error {
|
func (s *Service) ProcessVerifyWithdrawWebhook(ctx context.Context, req domain.ChapaWebhookTransfer) error {
|
||||||
// Find payment by reference
|
// Find payment by reference
|
||||||
transfer, err := s.transferStore.GetTransferByReference(ctx, payment.Reference)
|
transfer, err := s.transferStore.GetTransferByReference(ctx, req.Reference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ErrPaymentNotFound
|
return domain.ErrPaymentNotFound
|
||||||
}
|
}
|
||||||
|
|
@ -395,7 +432,7 @@ func (s *Service) ProcessVerifyWithdrawWebhook(ctx context.Context, payment doma
|
||||||
// verified = true
|
// verified = true
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if payment.Status == string(domain.PaymentStatusSuccessful) {
|
if req.Status == string(domain.PaymentStatusSuccessful) {
|
||||||
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
if err := s.transferStore.UpdateTransferVerification(ctx, transfer.ID, true); err != nil {
|
||||||
return fmt.Errorf("failed to update payment status: %w", err)
|
return fmt.Errorf("failed to update payment status: %w", err)
|
||||||
} // If payment is completed, credit user's walle
|
} // If payment is completed, credit user's walle
|
||||||
|
|
@ -420,15 +457,7 @@ func (s *Service) GetPaymentReceiptURL(refId string) (string, error) {
|
||||||
return receiptURL, nil
|
return receiptURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (any, error) {
|
||||||
banks, err := s.chapaClient.FetchSupportedBanks(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to fetch banks: %w", err)
|
|
||||||
}
|
|
||||||
return banks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
|
||||||
// Lookup transfer by reference
|
// Lookup transfer by reference
|
||||||
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -437,7 +466,7 @@ func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (*domain.Cha
|
||||||
|
|
||||||
// If already verified, just return a completed response
|
// If already verified, just return a completed response
|
||||||
if transfer.Verified {
|
if transfer.Verified {
|
||||||
return &domain.ChapaVerificationResponse{}, errors.New("transfer already verified")
|
return map[string]any{}, errors.New("transfer already verified")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate sender wallet
|
// Validate sender wallet
|
||||||
|
|
@ -445,12 +474,12 @@ func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (*domain.Cha
|
||||||
return nil, fmt.Errorf("invalid sender wallet ID: %v", transfer.SenderWalletID)
|
return nil, fmt.Errorf("invalid sender wallet ID: %v", transfer.SenderWalletID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var verification *domain.ChapaVerificationResponse
|
var verification any
|
||||||
|
|
||||||
switch strings.ToLower(string(transfer.Type)) {
|
switch strings.ToLower(string(transfer.Type)) {
|
||||||
case string(domain.DEPOSIT):
|
case string(domain.DEPOSIT):
|
||||||
// Verify Chapa payment
|
// Verify Chapa payment
|
||||||
verification, err = s.chapaClient.ManualVerifyPayment(ctx, txRef)
|
verification, err := s.chapaClient.ManualVerifyPayment(ctx, txRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to verify deposit with Chapa: %w", err)
|
return nil, fmt.Errorf("failed to verify deposit with Chapa: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -481,7 +510,7 @@ func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (*domain.Cha
|
||||||
|
|
||||||
case string(domain.WITHDRAW):
|
case string(domain.WITHDRAW):
|
||||||
// Verify Chapa transfer
|
// Verify Chapa transfer
|
||||||
verification, err = s.chapaClient.ManualVerifyTransfer(ctx, txRef)
|
verification, err := s.chapaClient.ManualVerifyTransfer(ctx, txRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to verify withdrawal with Chapa: %w", err)
|
return nil, fmt.Errorf("failed to verify withdrawal with Chapa: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -516,8 +545,16 @@ func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (*domain.Cha
|
||||||
return verification, nil
|
return verification, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) {
|
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.BankData, error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.chapa.co/v1/transfers", nil)
|
banks, err := s.chapaClient.FetchSupportedBanks(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch banks: %w", err)
|
||||||
|
}
|
||||||
|
return banks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetAllTransfers(ctx context.Context) (*domain.ChapaTransfersListResponse, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.cfg.CHAPA_BASE_URL+"/transfers", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -535,26 +572,22 @@ func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error
|
||||||
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
|
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
var result struct {
|
var result domain.ChapaTransfersListResponse
|
||||||
Status string `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Data []domain.Transfer `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.Data, nil
|
// Return the decoded result directly; no intermediate dynamic map needed
|
||||||
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) GetAccountBalance(ctx context.Context, currencyCode string) ([]domain.Balance, error) {
|
func (s *Service) GetAccountBalance(ctx context.Context, currencyCode string) ([]domain.Balance, error) {
|
||||||
baseURL := "https://api.chapa.co/v1/balances"
|
URL := s.cfg.CHAPA_BASE_URL + "/balances"
|
||||||
if currencyCode != "" {
|
if currencyCode != "" {
|
||||||
baseURL = fmt.Sprintf("%s/%s", baseURL, strings.ToLower(currencyCode))
|
URL = fmt.Sprintf("%s/%s", URL, strings.ToLower(currencyCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, URL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create balance request: %w", err)
|
return nil, fmt.Errorf("failed to create balance request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -585,59 +618,103 @@ func (s *Service) GetAccountBalance(ctx context.Context, currencyCode string) ([
|
||||||
return result.Data, nil
|
return result.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) InitiateSwap(ctx context.Context, amount float64, from, to string) (*domain.SwapResponse, error) {
|
func (s *Service) SwapCurrency(ctx context.Context, reqBody domain.SwapRequest) (*domain.SwapResponse, error) {
|
||||||
if amount < 1 {
|
URL := s.cfg.CHAPA_BASE_URL + "/swap"
|
||||||
return nil, fmt.Errorf("amount must be at least 1 USD")
|
|
||||||
}
|
|
||||||
if strings.ToUpper(from) != "USD" || strings.ToUpper(to) != "ETB" {
|
|
||||||
return nil, fmt.Errorf("only USD to ETB swap is supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := domain.SwapRequest{
|
// Normalize currency codes
|
||||||
Amount: amount,
|
reqBody.From = strings.ToUpper(reqBody.From)
|
||||||
From: strings.ToUpper(from),
|
reqBody.To = strings.ToUpper(reqBody.To)
|
||||||
To: strings.ToUpper(to),
|
|
||||||
}
|
|
||||||
|
|
||||||
// payload := map[string]any{
|
// Marshal request body
|
||||||
// "amount": amount,
|
body, err := json.Marshal(reqBody)
|
||||||
// "from": strings.ToUpper(from),
|
|
||||||
// "to": strings.ToUpper(to),
|
|
||||||
// }
|
|
||||||
|
|
||||||
body, err := json.Marshal(payload)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to encode swap payload: %w", err)
|
return nil, fmt.Errorf("failed to marshal swap payload: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.chapa.co/v1/swap", bytes.NewBuffer(body))
|
// Create HTTP request
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, URL, bytes.NewBuffer(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create swap request: %w", err)
|
return nil, fmt.Errorf("failed to create swap request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.cfg.CHAPA_SECRET_KEY))
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.cfg.CHAPA_SECRET_KEY))
|
||||||
|
|
||||||
|
// Execute request
|
||||||
resp, err := s.chapaClient.httpClient.Do(req)
|
resp, err := s.chapaClient.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute swap request: %w", err)
|
return nil, fmt.Errorf("failed to execute swap request: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
// Handle unexpected status
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
|
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
var result struct {
|
// Decode response
|
||||||
Message string `json:"message"`
|
var result domain.SwapResponse
|
||||||
Status string `json:"status"`
|
|
||||||
Data domain.SwapResponse `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode swap response: %w", err)
|
return nil, fmt.Errorf("failed to decode swap response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &result.Data, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func (s *Service) InitiateSwap(ctx context.Context, amount float64, from, to string) (*domain.SwapResponse, error) {
|
||||||
|
// if amount < 1 {
|
||||||
|
// return nil, fmt.Errorf("amount must be at least 1 USD")
|
||||||
|
// }
|
||||||
|
// if strings.ToUpper(from) != "USD" || strings.ToUpper(to) != "ETB" {
|
||||||
|
// return nil, fmt.Errorf("only USD to ETB swap is supported")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// payload := domain.SwapRequest{
|
||||||
|
// Amount: amount,
|
||||||
|
// From: strings.ToUpper(from),
|
||||||
|
// To: strings.ToUpper(to),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // payload := map[string]any{
|
||||||
|
// // "amount": amount,
|
||||||
|
// // "from": strings.ToUpper(from),
|
||||||
|
// // "to": strings.ToUpper(to),
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// body, err := json.Marshal(payload)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to encode swap payload: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.chapa.co/v1/swap", bytes.NewBuffer(body))
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to create swap request: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.cfg.CHAPA_SECRET_KEY))
|
||||||
|
// req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// resp, err := s.chapaClient.httpClient.Do(req)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to execute swap request: %w", err)
|
||||||
|
// }
|
||||||
|
// defer resp.Body.Close()
|
||||||
|
|
||||||
|
// if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||||
|
// bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
// return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var result struct {
|
||||||
|
// Message string `json:"message"`
|
||||||
|
// Status string `json:"status"`
|
||||||
|
// Data domain.SwapResponse `json:"data"`
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
|
// return nil, fmt.Errorf("failed to decode swap response: %w", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return &result.Data, nil
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -102,13 +102,13 @@ func (s *AleaPlayService) HandleCallback(ctx context.Context, callback *domain.A
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update session status using the proper repository method
|
// Update session status using the proper repository method
|
||||||
if callback.Type == "SESSION_END" {
|
// if callback.Type == "SESSION_END" {
|
||||||
if err := s.repo.UpdateVirtualGameSessionStatus(ctx, session.ID, "COMPLETED"); err != nil {
|
// if err := s.repo.UpdateVirtualGameSessionStatus(ctx, session.ID, "COMPLETED"); err != nil {
|
||||||
s.logger.Error("failed to update session status",
|
// s.logger.Error("failed to update session status",
|
||||||
"sessionID", session.ID,
|
// "sessionID", session.ID,
|
||||||
"error", err)
|
// "error", err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
|
"github.com/google/uuid"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -167,7 +168,7 @@ func (s *Service) StartGame(ctx context.Context, req domain.GameStartRequest) (*
|
||||||
"playerId": req.PlayerID,
|
"playerId": req.PlayerID,
|
||||||
"currency": req.Currency,
|
"currency": req.Currency,
|
||||||
"deviceType": req.DeviceType,
|
"deviceType": req.DeviceType,
|
||||||
"country": "US",
|
"country": req.Country,
|
||||||
"ip": req.IP,
|
"ip": req.IP,
|
||||||
"brandId": req.BrandID,
|
"brandId": req.BrandID,
|
||||||
}
|
}
|
||||||
|
|
@ -178,6 +179,21 @@ func (s *Service) StartGame(ctx context.Context, req domain.GameStartRequest) (*
|
||||||
return nil, fmt.Errorf("failed to start game with provider %s: %w", req.ProviderID, err)
|
return nil, fmt.Errorf("failed to start game with provider %s: %w", req.ProviderID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playerIDInt64, err := strconv.ParseInt(req.PlayerID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid PlayerID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
session := &domain.VirtualGameSession{
|
||||||
|
UserID: playerIDInt64,
|
||||||
|
GameID: req.GameID,
|
||||||
|
SessionToken: uuid.NewString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.CreateVirtualGameSession(ctx, session); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create virtual game session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,15 @@ import (
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/arifpay/checkout [post]
|
// @Router /api/v1/arifpay/checkout [post]
|
||||||
func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
|
func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
|
||||||
|
|
||||||
|
userId, ok := c.Locals("user_id").(int64)
|
||||||
|
if !ok {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||||
|
Error: "missing user id",
|
||||||
|
Message: "Unauthorized",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var req domain.CheckoutSessionClientRequest
|
var req domain.CheckoutSessionClientRequest
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
|
@ -26,7 +35,7 @@ func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := h.arifpaySvc.CreateCheckoutSession(req, true)
|
data, err := h.arifpaySvc.CreateCheckoutSession(req, true, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
|
|
@ -53,7 +62,7 @@ func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
|
||||||
// @Success 200 {object} domain.Response
|
// @Success 200 {object} domain.Response
|
||||||
// @Failure 400 {object} domain.ErrorResponse
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/arifpay/checkout/{sessionId}/cancel [post]
|
// @Router /api/v1/arifpay/checkout/cancel/{sessionId} [post]
|
||||||
func (h *Handler) CancelCheckoutSessionHandler(c *fiber.Ctx) error {
|
func (h *Handler) CancelCheckoutSessionHandler(c *fiber.Ctx) error {
|
||||||
sessionID := c.Params("sessionId")
|
sessionID := c.Params("sessionId")
|
||||||
if sessionID == "" {
|
if sessionID == "" {
|
||||||
|
|
@ -103,15 +112,15 @@ func (h *Handler) HandleArifpayC2BWebhook(c *fiber.Ctx) error {
|
||||||
// 🚨 Decide how to get userId:
|
// 🚨 Decide how to get userId:
|
||||||
// If you get it from auth context/middleware, extract it here.
|
// If you get it from auth context/middleware, extract it here.
|
||||||
// For now, let's assume userId comes from your auth claims:
|
// For now, let's assume userId comes from your auth claims:
|
||||||
userId, ok := c.Locals("user_id").(int64)
|
// userId, ok := c.Locals("user_id").(int64)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
// return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||||
Error: "missing user id",
|
// Error: "missing user id",
|
||||||
Message: "Unauthorized",
|
// Message: "Unauthorized",
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
err := h.arifpaySvc.HandleWebhook(c.Context(), req, userId, true)
|
err := h.arifpaySvc.ProcessWebhook(c.Context(), req, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
|
|
@ -150,15 +159,15 @@ func (h *Handler) HandleArifpayB2CWebhook(c *fiber.Ctx) error {
|
||||||
// 🚨 Decide how to get userId:
|
// 🚨 Decide how to get userId:
|
||||||
// If you get it from auth context/middleware, extract it here.
|
// If you get it from auth context/middleware, extract it here.
|
||||||
// For now, let's assume userId comes from your auth claims:
|
// For now, let's assume userId comes from your auth claims:
|
||||||
userId, ok := c.Locals("user_id").(int64)
|
// userId, ok := c.Locals("user_id").(int64)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
// return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||||
Error: "missing user id",
|
// Error: "missing user id",
|
||||||
Message: "Unauthorized",
|
// Message: "Unauthorized",
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
err := h.arifpaySvc.HandleWebhook(c.Context(), req, userId, false)
|
err := h.arifpaySvc.ProcessWebhook(c.Context(), req, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
|
|
@ -253,7 +262,7 @@ func (h *Handler) ArifpayVerifyBySessionIDHandler(c *fiber.Ctx) error {
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param type query string true "Transfer type (telebirr, cbe, mpesa)"
|
// @Param type query string true "Transfer type (telebirr, cbe, mpesa)"
|
||||||
// @Param request body domain.ArifpayB2CRequest true "Transfer request payload"
|
// @Param request body domain.CheckoutSessionClientRequest true "Transfer request payload"
|
||||||
// @Success 200 {object} map[string]string "message: transfer executed successfully"
|
// @Success 200 {object} map[string]string "message: transfer executed successfully"
|
||||||
// @Failure 400 {object} map[string]string "error: invalid request or unsupported transfer type"
|
// @Failure 400 {object} map[string]string "error: invalid request or unsupported transfer type"
|
||||||
// @Failure 500 {object} map[string]string "error: internal server error"
|
// @Failure 500 {object} map[string]string "error: internal server error"
|
||||||
|
|
@ -275,7 +284,7 @@ func (h *Handler) ExecuteArifpayB2CTransfer(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var req domain.ArifpayB2CRequest
|
var req domain.CheckoutSessionClientRequest
|
||||||
if err := c.BodyParser(&req); err != nil {
|
if err := c.BodyParser(&req); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
Message: "Failed to process your withdrawal request",
|
Message: "Failed to process your withdrawal request",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
@ -61,33 +63,55 @@ func (h *Handler) InitiateDeposit(c *fiber.Ctx) error {
|
||||||
|
|
||||||
// WebhookCallback godoc
|
// WebhookCallback godoc
|
||||||
// @Summary Chapa payment webhook callback (used by Chapa)
|
// @Summary Chapa payment webhook callback (used by Chapa)
|
||||||
// @Description Handles payment notifications from Chapa
|
// @Description Handles payment and transfer notifications from Chapa
|
||||||
// @Tags Chapa
|
// @Tags Chapa
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param request body domain.ChapaWebhookPayload true "Webhook payload"
|
// @Param request body domain.ChapaWebhookPayment true "Webhook payload"
|
||||||
// @Success 200 {object} map[string]interface{}
|
// @Success 200 {object} domain.Response
|
||||||
// @Failure 400 {object} domain.ErrorResponse
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/chapa/payments/webhook/verify [post]
|
// @Router /api/v1/chapa/payments/webhook/verify [post]
|
||||||
func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
||||||
|
body := c.Body()
|
||||||
|
|
||||||
chapaTransactionType := new(domain.ChapaTransactionType)
|
// Retrieve signature headers
|
||||||
|
chapaSignature := c.Get("chapa-signature")
|
||||||
|
xChapaSignature := c.Get("x-chapa-signature")
|
||||||
|
|
||||||
if parseTypeErr := c.BodyParser(chapaTransactionType); parseTypeErr != nil {
|
// Verify webhook signature
|
||||||
|
valid, err := h.chapaSvc.VerifyWebhookSignature(c.Context(), body, chapaSignature, xChapaSignature)
|
||||||
|
if err != nil || !valid {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Invalid Chapa webhook signature",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try parsing as transfer webhook first
|
||||||
|
var transfer domain.ChapaWebhookTransfer
|
||||||
|
if err := json.Unmarshal(body, &transfer); err == nil &&
|
||||||
|
strings.EqualFold(transfer.Type, "payout") {
|
||||||
|
|
||||||
|
if err := h.chapaSvc.ProcessVerifyWithdrawWebhook(c.Context(), transfer); err != nil {
|
||||||
|
return domain.UnExpectedErrorResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
Message: "Chapa withdrawal webhook processed successfully",
|
||||||
|
// Data: transfer,
|
||||||
|
Success: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, try as payment webhook
|
||||||
|
var payment domain.ChapaWebhookPayment
|
||||||
|
if err := json.Unmarshal(body, &payment); err != nil {
|
||||||
return domain.UnProcessableEntityResponse(c)
|
return domain.UnProcessableEntityResponse(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch chapaTransactionType.Type {
|
if err := h.chapaSvc.ProcessVerifyDepositWebhook(c.Context(), payment); err != nil {
|
||||||
case h.Cfg.CHAPA_PAYMENT_TYPE:
|
|
||||||
chapaTransferVerificationRequest := new(domain.ChapaPaymentWebhookRequest)
|
|
||||||
|
|
||||||
if err := c.BodyParser(chapaTransferVerificationRequest); err != nil {
|
|
||||||
return domain.UnProcessableEntityResponse(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := h.chapaSvc.ProcessVerifyDepositWebhook(c.Context(), *chapaTransferVerificationRequest)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Message: "Failed to verify Chapa deposit",
|
Message: "Failed to verify Chapa deposit",
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
|
|
@ -96,35 +120,10 @@ func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Message: "Chapa deposit transaction verified successfully",
|
Message: "Chapa deposit webhook processed successfully",
|
||||||
Data: chapaTransferVerificationRequest,
|
// Data: payment,
|
||||||
Success: true,
|
Success: true,
|
||||||
})
|
})
|
||||||
case h.Cfg.CHAPA_TRANSFER_TYPE:
|
|
||||||
chapaPaymentVerificationRequest := new(domain.ChapaWebHookPayment)
|
|
||||||
if err := c.BodyParser(chapaPaymentVerificationRequest); err != nil {
|
|
||||||
return domain.UnProcessableEntityResponse(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := h.chapaSvc.ProcessVerifyWithdrawWebhook(c.Context(), *chapaPaymentVerificationRequest)
|
|
||||||
if err != nil {
|
|
||||||
return domain.UnExpectedErrorResponse(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
||||||
StatusCode: 200,
|
|
||||||
Message: "Chapa withdrawal transaction verified successfully",
|
|
||||||
Data: chapaPaymentVerificationRequest,
|
|
||||||
Success: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a 400 Bad Request if the type does not match any known case
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
||||||
Message: "Invalid Chapa webhook type",
|
|
||||||
Error: "Unknown transaction type",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelDeposit godoc
|
// CancelDeposit godoc
|
||||||
|
|
@ -248,7 +247,7 @@ func (h *Handler) GetTransactionEvents(c *fiber.Ctx) error {
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param tx_ref path string true "Transaction Reference"
|
// @Param tx_ref path string true "Transaction Reference"
|
||||||
// @Success 200 {object} domain.ChapaVerificationResponse
|
// @Success 200 {object} domain.Response
|
||||||
// @Failure 400 {object} domain.ErrorResponse
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/chapa/transaction/manual/verify/{tx_ref} [get]
|
// @Router /api/v1/chapa/transaction/manual/verify/{tx_ref} [get]
|
||||||
|
|
@ -311,7 +310,7 @@ func (h *Handler) GetSupportedBanks(c *fiber.Ctx) error {
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security ApiKeyAuth
|
// @Security ApiKeyAuth
|
||||||
// @Param request body domain.ChapaWithdrawalRequest true "Withdrawal request details"
|
// @Param request body domain.ChapaWithdrawalRequest true "Withdrawal request details"
|
||||||
// @Success 201 {object} domain.Response "Chapa withdrawal process initiated successfully"
|
// @Success 200 {object} domain.Response "Chapa withdrawal process initiated successfully"
|
||||||
// @Failure 400 {object} domain.ErrorResponse "Invalid request body"
|
// @Failure 400 {object} domain.ErrorResponse "Invalid request body"
|
||||||
// @Failure 401 {object} domain.ErrorResponse "Unauthorized"
|
// @Failure 401 {object} domain.ErrorResponse "Unauthorized"
|
||||||
// @Failure 422 {object} domain.ErrorResponse "Unprocessable entity"
|
// @Failure 422 {object} domain.ErrorResponse "Unprocessable entity"
|
||||||
|
|
@ -336,9 +335,9 @@ func (h *Handler) InitiateWithdrawal(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusCreated).JSON(domain.Response{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
Message: "Chapa withdrawal process initiated successfully",
|
Message: "Chapa withdrawal process initiated successfully",
|
||||||
StatusCode: 201,
|
StatusCode: 200,
|
||||||
Success: true,
|
Success: true,
|
||||||
Data: withdrawal,
|
Data: withdrawal,
|
||||||
})
|
})
|
||||||
|
|
@ -430,44 +429,56 @@ func (h *Handler) GetAccountBalance(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
Message: "Chapa account balance retrieved successfully",
|
Message: "Chapa account balances retrieved successfully",
|
||||||
Data: balances,
|
Data: balances,
|
||||||
StatusCode: fiber.StatusOK,
|
StatusCode: fiber.StatusOK,
|
||||||
Success: true,
|
Success: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitiateSwap godoc
|
// SwapCurrency godoc
|
||||||
// @Summary Initiate a currency swap
|
// @Summary Swap currency using Chapa API
|
||||||
// @Description Perform a USD to ETB currency swap using Chapa's API
|
// @Description Convert an amount from one currency to another using Chapa's currency swap API
|
||||||
// @Tags Chapa
|
// @Tags Chapa
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param payload body domain.SwapRequest true "Swap Request Payload"
|
// @Param request body domain.SwapRequest true "Swap request payload"
|
||||||
// @Success 200 {object} domain.Response
|
// @Success 200 {object} domain.Response
|
||||||
// @Failure 400 {object} domain.ErrorResponse
|
// @Failure 400 {object} domain.ErrorResponse
|
||||||
// @Failure 500 {object} domain.ErrorResponse
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
// @Router /api/v1/chapa/swap [post]
|
// @Router /api/v1/chapa/swap [post]
|
||||||
func (h *Handler) InitiateSwap(c *fiber.Ctx) error {
|
func (h *Handler) SwapCurrency(c *fiber.Ctx) error {
|
||||||
var req domain.SwapRequest
|
var reqBody domain.SwapRequest
|
||||||
if err := c.BodyParser(&req); err != nil {
|
|
||||||
|
// Parse request body
|
||||||
|
if err := c.BodyParser(&reqBody); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
Message: "Invalid request payload",
|
Message: "Invalid request payload",
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
swapResult, err := h.chapaSvc.InitiateSwap(c.Context(), req.Amount, req.From, req.To)
|
// Validate input
|
||||||
|
if reqBody.From == "" || reqBody.To == "" || reqBody.Amount <= 0 {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Missing or invalid swap parameters",
|
||||||
|
Error: "from, to, and amount are required fields",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call service
|
||||||
|
resp, err := h.chapaSvc.SwapCurrency(c.Context(), reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
Message: "Failed to initiate currency swap",
|
Message: "Failed to perform currency swap",
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Success response
|
||||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
Message: "Currency swap initiated successfully",
|
Message: "Currency swapped successfully",
|
||||||
Data: swapResult,
|
Data: resp,
|
||||||
StatusCode: fiber.StatusOK,
|
StatusCode: fiber.StatusOK,
|
||||||
Success: true,
|
Success: true,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,10 @@ func (h *Handler) StartGame(c *fiber.Ctx) error {
|
||||||
req.BrandID = h.Cfg.VeliGames.BrandID
|
req.BrandID = h.Cfg.VeliGames.BrandID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useId := c.Locals("user_id")
|
||||||
|
|
||||||
req.IP = c.IP()
|
req.IP = c.IP()
|
||||||
|
req.PlayerID = useId.(string)
|
||||||
|
|
||||||
// 1️⃣ Call StartGame service
|
// 1️⃣ Call StartGame service
|
||||||
res, err := h.veliVirtualGameSvc.StartGame(context.Background(), req)
|
res, err := h.veliVirtualGameSvc.StartGame(context.Background(), req)
|
||||||
|
|
|
||||||
|
|
@ -150,9 +150,9 @@ func (a *App) initAppRoutes() {
|
||||||
|
|
||||||
//Arifpay
|
//Arifpay
|
||||||
groupV1.Post("/arifpay/checkout", a.authMiddleware, h.CreateCheckoutSessionHandler)
|
groupV1.Post("/arifpay/checkout", a.authMiddleware, h.CreateCheckoutSessionHandler)
|
||||||
groupV1.Post("/arifpay/checkout/cancel/:session_id", a.authMiddleware, h.CancelCheckoutSessionHandler)
|
groupV1.Post("/arifpay/checkout/cancel/:sessionId", a.authMiddleware, h.CancelCheckoutSessionHandler)
|
||||||
groupV1.Post("/api/v1/arifpay/c2b-webhook", a.authMiddleware, h.HandleArifpayC2BWebhook)
|
groupV1.Post("/api/v1/arifpay/c2b-webhook", h.HandleArifpayC2BWebhook)
|
||||||
groupV1.Post("/api/v1/arifpay/b2c-webhook", a.authMiddleware, h.HandleArifpayB2CWebhook)
|
groupV1.Post("/api/v1/arifpay/b2c-webhook", h.HandleArifpayB2CWebhook)
|
||||||
groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.ExecuteArifpayB2CTransfer)
|
groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.ExecuteArifpayB2CTransfer)
|
||||||
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)
|
||||||
|
|
@ -381,17 +381,17 @@ func (a *App) initAppRoutes() {
|
||||||
|
|
||||||
//Chapa Routes
|
//Chapa Routes
|
||||||
groupV1.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
|
groupV1.Post("/chapa/payments/webhook/verify", h.WebhookCallback)
|
||||||
groupV1.Get("/chapa/transaction/manual/verify/:tx_ref", h.ManualVerifyTransaction)
|
groupV1.Get("/chapa/transaction/manual/verify/:tx_ref", a.authMiddleware, h.ManualVerifyTransaction)
|
||||||
groupV1.Put("/chapa/transaction/cancel/:tx_ref", a.authMiddleware, h.CancelDeposit)
|
groupV1.Put("/chapa/transaction/cancel/:tx_ref", a.authMiddleware, h.CancelDeposit)
|
||||||
groupV1.Get("/chapa/transactions", h.FetchAllTransactions)
|
groupV1.Get("/chapa/transactions", a.authMiddleware, h.FetchAllTransactions)
|
||||||
groupV1.Get("/chapa/transaction/events/:ref_id", h.GetTransactionEvents)
|
groupV1.Get("/chapa/transaction/events/:ref_id", a.authMiddleware, h.GetTransactionEvents)
|
||||||
groupV1.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
|
groupV1.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
|
||||||
groupV1.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
|
groupV1.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
|
||||||
groupV1.Get("/chapa/banks", h.GetSupportedBanks)
|
groupV1.Get("/chapa/banks", h.GetSupportedBanks)
|
||||||
groupV1.Get("/chapa/payments/receipt/:chapa_ref", h.GetPaymentReceipt)
|
groupV1.Get("/chapa/payments/receipt/:chapa_ref", a.authMiddleware, h.GetPaymentReceipt)
|
||||||
groupV1.Get("/chapa/transfers", h.GetAllTransfers)
|
groupV1.Get("/chapa/transfers", a.authMiddleware, h.GetAllTransfers)
|
||||||
groupV1.Get("/chapa/balance", h.GetAccountBalance)
|
groupV1.Get("/chapa/balance", a.authMiddleware, h.GetAccountBalance)
|
||||||
groupV1.Post("/chapa/init-swap", h.InitiateSwap)
|
groupV1.Post("/chapa/swap", a.authMiddleware, h.SwapCurrency)
|
||||||
|
|
||||||
// Currencies
|
// Currencies
|
||||||
groupV1.Get("/currencies", h.GetSupportedCurrencies)
|
groupV1.Get("/currencies", h.GetSupportedCurrencies)
|
||||||
|
|
@ -409,7 +409,7 @@ func (a *App) initAppRoutes() {
|
||||||
//Veli Virtual Game Routes
|
//Veli Virtual Game Routes
|
||||||
groupV1.Post("/veli/providers", h.GetProviders)
|
groupV1.Post("/veli/providers", h.GetProviders)
|
||||||
groupV1.Post("/veli/games-list", h.GetGamesByProvider)
|
groupV1.Post("/veli/games-list", h.GetGamesByProvider)
|
||||||
groupV1.Post("/veli/start-game", h.StartGame)
|
groupV1.Post("/veli/start-game", a.authMiddleware, h.StartGame)
|
||||||
groupV1.Post("/veli/start-demo-game", h.StartDemoGame)
|
groupV1.Post("/veli/start-demo-game", h.StartDemoGame)
|
||||||
a.fiber.Post("/balance", h.GetBalance)
|
a.fiber.Post("/balance", h.GetBalance)
|
||||||
groupV1.Post("/veli/gaming-activity", a.authMiddleware, h.GetGamingActivity)
|
groupV1.Post("/veli/gaming-activity", a.authMiddleware, h.GetGamingActivity)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user