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),
|
||||
game_id VARCHAR(50) NOT NULL,
|
||||
session_token VARCHAR(255) NOT NULL UNIQUE,
|
||||
currency VARCHAR(3) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE', -- ACTIVE, COMPLETED, FAILED
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE virtual_game_transactions (
|
||||
|
|
|
|||
|
|
@ -63,38 +63,38 @@ RETURNING id,
|
|||
INSERT INTO virtual_game_sessions (
|
||||
user_id,
|
||||
game_id,
|
||||
session_token,
|
||||
currency,
|
||||
status,
|
||||
expires_at
|
||||
session_token
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id,
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING
|
||||
id,
|
||||
user_id,
|
||||
game_id,
|
||||
session_token,
|
||||
currency,
|
||||
status,
|
||||
created_at,
|
||||
updated_at,
|
||||
expires_at;
|
||||
updated_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
|
||||
SELECT id,
|
||||
user_id,
|
||||
game_id,
|
||||
session_token,
|
||||
currency,
|
||||
status,
|
||||
created_at,
|
||||
updated_at,
|
||||
expires_at
|
||||
updated_at
|
||||
FROM virtual_game_sessions
|
||||
WHERE session_token = $1;
|
||||
-- name: UpdateVirtualGameSessionStatus :exec
|
||||
UPDATE virtual_game_sessions
|
||||
SET status = $2,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: CreateVirtualGameTransaction :one
|
||||
INSERT INTO virtual_game_transactions (
|
||||
session_id,
|
||||
|
|
|
|||
129
docs/docs.go
129
docs/docs.go
|
|
@ -458,7 +458,7 @@ const docTemplate = `{
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"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": {
|
||||
"description": "Cancels a payment session using Arifpay before completion.",
|
||||
"consumes": [
|
||||
|
|
@ -2254,7 +2254,7 @@ const docTemplate = `{
|
|||
},
|
||||
"/api/v1/chapa/payments/webhook/verify": {
|
||||
"post": {
|
||||
"description": "Handles payment notifications from Chapa",
|
||||
"description": "Handles payment and transfer notifications from Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -2272,7 +2272,7 @@ const docTemplate = `{
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ChapaWebhookPayload"
|
||||
"$ref": "#/definitions/domain.ChapaWebhookPayment"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -2280,8 +2280,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -2329,7 +2328,7 @@ const docTemplate = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"200": {
|
||||
"description": "Chapa withdrawal process initiated successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
|
|
@ -2364,7 +2363,7 @@ const docTemplate = `{
|
|||
},
|
||||
"/api/v1/chapa/swap": {
|
||||
"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": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -2374,11 +2373,11 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Initiate a currency swap",
|
||||
"summary": "Swap currency using Chapa API",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Swap Request Payload",
|
||||
"name": "payload",
|
||||
"description": "Swap request payload",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
|
|
@ -2536,7 +2535,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ChapaVerificationResponse"
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -10980,17 +10957,28 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ChapaVerificationResponse": {
|
||||
"domain.ChapaWebhookCustomization": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"logo": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ChapaWebhookPayment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"charge": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
|
|
@ -10999,33 +10987,32 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"customization": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"logo": {},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
"$ref": "#/definitions/domain.ChapaWebhookCustomization"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"event": {
|
||||
"type": "string"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"meta": {},
|
||||
"method": {
|
||||
"meta": {
|
||||
"description": "may vary in structure, so kept flexible"
|
||||
},
|
||||
"mobile": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_method": {
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -12884,21 +12844,6 @@ const docTemplate = `{
|
|||
"BANK"
|
||||
]
|
||||
},
|
||||
"domain.PaymentStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"success",
|
||||
"pending",
|
||||
"completed",
|
||||
"failed"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"PaymentStatusSuccessful",
|
||||
"PaymentStatusPending",
|
||||
"PaymentStatusCompleted",
|
||||
"PaymentStatusFailed"
|
||||
]
|
||||
},
|
||||
"domain.PopOKCallback": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -450,7 +450,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"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": {
|
||||
"description": "Cancels a payment session using Arifpay before completion.",
|
||||
"consumes": [
|
||||
|
|
@ -2246,7 +2246,7 @@
|
|||
},
|
||||
"/api/v1/chapa/payments/webhook/verify": {
|
||||
"post": {
|
||||
"description": "Handles payment notifications from Chapa",
|
||||
"description": "Handles payment and transfer notifications from Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -2264,7 +2264,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ChapaWebhookPayload"
|
||||
"$ref": "#/definitions/domain.ChapaWebhookPayment"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
@ -2272,8 +2272,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
|
@ -2321,7 +2320,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"200": {
|
||||
"description": "Chapa withdrawal process initiated successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
|
|
@ -2356,7 +2355,7 @@
|
|||
},
|
||||
"/api/v1/chapa/swap": {
|
||||
"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": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -2366,11 +2365,11 @@
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Initiate a currency swap",
|
||||
"summary": "Swap currency using Chapa API",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Swap Request Payload",
|
||||
"name": "payload",
|
||||
"description": "Swap request payload",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
|
|
@ -2528,7 +2527,7 @@
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ChapaVerificationResponse"
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -10972,17 +10949,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ChapaVerificationResponse": {
|
||||
"domain.ChapaWebhookCustomization": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"logo": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.ChapaWebhookPayment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"charge": {
|
||||
"type": "number"
|
||||
"type": "string"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
|
|
@ -10991,33 +10979,32 @@
|
|||
"type": "string"
|
||||
},
|
||||
"customization": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"logo": {},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
"$ref": "#/definitions/domain.ChapaWebhookCustomization"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"event": {
|
||||
"type": "string"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"meta": {},
|
||||
"method": {
|
||||
"meta": {
|
||||
"description": "may vary in structure, so kept flexible"
|
||||
},
|
||||
"mobile": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string"
|
||||
},
|
||||
"payment_method": {
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"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": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -12876,21 +12836,6 @@
|
|||
"BANK"
|
||||
]
|
||||
},
|
||||
"domain.PaymentStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"success",
|
||||
"pending",
|
||||
"completed",
|
||||
"failed"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"PaymentStatusSuccessful",
|
||||
"PaymentStatusPending",
|
||||
"PaymentStatusCompleted",
|
||||
"PaymentStatusFailed"
|
||||
]
|
||||
},
|
||||
"domain.PopOKCallback": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -38,21 +38,6 @@ definitions:
|
|||
user_id:
|
||||
type: string
|
||||
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:
|
||||
properties:
|
||||
paymentType:
|
||||
|
|
@ -546,37 +531,43 @@ definitions:
|
|||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
domain.ChapaVerificationResponse:
|
||||
domain.ChapaWebhookCustomization:
|
||||
properties:
|
||||
data:
|
||||
description:
|
||||
type: string
|
||||
logo:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
type: object
|
||||
domain.ChapaWebhookPayment:
|
||||
properties:
|
||||
amount:
|
||||
type: number
|
||||
type: string
|
||||
charge:
|
||||
type: number
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
currency:
|
||||
type: string
|
||||
customization:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
logo: {}
|
||||
title:
|
||||
type: string
|
||||
type: object
|
||||
$ref: '#/definitions/domain.ChapaWebhookCustomization'
|
||||
email:
|
||||
type: string
|
||||
event:
|
||||
type: string
|
||||
first_name:
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
meta: {}
|
||||
method:
|
||||
meta:
|
||||
description: may vary in structure, so kept flexible
|
||||
mobile:
|
||||
type: string
|
||||
mode:
|
||||
type: string
|
||||
payment_method:
|
||||
type: string
|
||||
reference:
|
||||
type: string
|
||||
status:
|
||||
|
|
@ -588,22 +579,6 @@ definitions:
|
|||
updated_at:
|
||||
type: string
|
||||
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:
|
||||
properties:
|
||||
account_name:
|
||||
|
|
@ -1844,18 +1819,6 @@ definitions:
|
|||
- TELEBIRR_TRANSACTION
|
||||
- ARIFPAY_TRANSACTION
|
||||
- BANK
|
||||
domain.PaymentStatus:
|
||||
enum:
|
||||
- success
|
||||
- pending
|
||||
- completed
|
||||
- failed
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- PaymentStatusSuccessful
|
||||
- PaymentStatusPending
|
||||
- PaymentStatusCompleted
|
||||
- PaymentStatusFailed
|
||||
domain.PopOKCallback:
|
||||
properties:
|
||||
amount:
|
||||
|
|
@ -4608,7 +4571,7 @@ paths:
|
|||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ArifpayB2CRequest'
|
||||
$ref: '#/definitions/domain.CheckoutSessionClientRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
|
@ -4695,7 +4658,7 @@ paths:
|
|||
summary: Create Arifpay Checkout Session
|
||||
tags:
|
||||
- Arifpay
|
||||
/api/v1/arifpay/checkout/{sessionId}/cancel:
|
||||
/api/v1/arifpay/checkout/cancel/{sessionId}:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
|
|
@ -5789,22 +5752,21 @@ paths:
|
|||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Handles payment notifications from Chapa
|
||||
description: Handles payment and transfer notifications from Chapa
|
||||
parameters:
|
||||
- description: Webhook payload
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ChapaWebhookPayload'
|
||||
$ref: '#/definitions/domain.ChapaWebhookPayment'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
additionalProperties: true
|
||||
type: object
|
||||
$ref: '#/definitions/domain.Response'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
@ -5832,7 +5794,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
"200":
|
||||
description: Chapa withdrawal process initiated successfully
|
||||
schema:
|
||||
$ref: '#/definitions/domain.Response'
|
||||
|
|
@ -5861,11 +5823,12 @@ paths:
|
|||
post:
|
||||
consumes:
|
||||
- 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:
|
||||
- description: Swap Request Payload
|
||||
- description: Swap request payload
|
||||
in: body
|
||||
name: payload
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.SwapRequest'
|
||||
|
|
@ -5884,7 +5847,7 @@ paths:
|
|||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Initiate a currency swap
|
||||
summary: Swap currency using Chapa API
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/transaction/cancel/{tx_ref}:
|
||||
|
|
@ -5970,7 +5933,7 @@ paths:
|
|||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ChapaVerificationResponse'
|
||||
$ref: '#/definitions/domain.Response'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
|
|
|||
|
|
@ -1093,11 +1093,8 @@ type VirtualGameSession struct {
|
|||
UserID int64 `json:"user_id"`
|
||||
GameID string `json:"game_id"`
|
||||
SessionToken string `json:"session_token"`
|
||||
Currency string `json:"currency"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
||||
}
|
||||
|
||||
type VirtualGameTransaction struct {
|
||||
|
|
|
|||
|
|
@ -411,52 +411,34 @@ const CreateVirtualGameSession = `-- name: CreateVirtualGameSession :one
|
|||
INSERT INTO virtual_game_sessions (
|
||||
user_id,
|
||||
game_id,
|
||||
session_token,
|
||||
currency,
|
||||
status,
|
||||
expires_at
|
||||
session_token
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id,
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING
|
||||
id,
|
||||
user_id,
|
||||
game_id,
|
||||
session_token,
|
||||
currency,
|
||||
status,
|
||||
created_at,
|
||||
updated_at,
|
||||
expires_at
|
||||
updated_at
|
||||
`
|
||||
|
||||
type CreateVirtualGameSessionParams struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
GameID string `json:"game_id"`
|
||||
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) {
|
||||
row := q.db.QueryRow(ctx, CreateVirtualGameSession,
|
||||
arg.UserID,
|
||||
arg.GameID,
|
||||
arg.SessionToken,
|
||||
arg.Currency,
|
||||
arg.Status,
|
||||
arg.ExpiresAt,
|
||||
)
|
||||
row := q.db.QueryRow(ctx, CreateVirtualGameSession, arg.UserID, arg.GameID, arg.SessionToken)
|
||||
var i VirtualGameSession
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.GameID,
|
||||
&i.SessionToken,
|
||||
&i.Currency,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ExpiresAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -751,11 +733,8 @@ SELECT id,
|
|||
user_id,
|
||||
game_id,
|
||||
session_token,
|
||||
currency,
|
||||
status,
|
||||
created_at,
|
||||
updated_at,
|
||||
expires_at
|
||||
updated_at
|
||||
FROM virtual_game_sessions
|
||||
WHERE session_token = $1
|
||||
`
|
||||
|
|
@ -768,11 +747,34 @@ func (q *Queries) GetVirtualGameSessionByToken(ctx context.Context, sessionToken
|
|||
&i.UserID,
|
||||
&i.GameID,
|
||||
&i.SessionToken,
|
||||
&i.Currency,
|
||||
&i.Status,
|
||||
&i.CreatedAt,
|
||||
&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
|
||||
}
|
||||
|
|
@ -1117,23 +1119,6 @@ func (q *Queries) UpdateVirtualGameProviderReportByDate(ctx context.Context, arg
|
|||
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
|
||||
UPDATE virtual_game_transactions
|
||||
SET status = $2,
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ type Config struct {
|
|||
AFRO_SMS_SENDER_NAME string
|
||||
AFRO_SMS_RECEIVER_PHONE_NUMBER string
|
||||
ADRO_SMS_HOST_URL string
|
||||
CHAPA_WEBHOOK_SECRET string
|
||||
CHAPA_TRANSFER_TYPE string
|
||||
CHAPA_PAYMENT_TYPE string
|
||||
CHAPA_SECRET_KEY string
|
||||
|
|
@ -259,6 +260,7 @@ func (c *Config) loadEnv() error {
|
|||
c.TELEBIRR.TelebirrCallbackURL = os.Getenv("TELEBIRR_CALLBACK_URL")
|
||||
|
||||
//Chapa
|
||||
c.CHAPA_WEBHOOK_SECRET = os.Getenv("CHAPA_WEBHOOK_SECRET")
|
||||
c.CHAPA_SECRET_KEY = os.Getenv("CHAPA_SECRET_KEY")
|
||||
c.CHAPA_PUBLIC_KEY = os.Getenv("CHAPA_PUBLIC_KEY")
|
||||
c.CHAPA_ENCRYPTION_KEY = os.Getenv("CHAPA_ENCRYPTION_KEY")
|
||||
|
|
|
|||
|
|
@ -59,12 +59,12 @@ type WebhookRequest struct {
|
|||
SessionID string `json:"sessionId"`
|
||||
}
|
||||
|
||||
type ArifpayB2CRequest struct{
|
||||
PhoneNumber string `json:"Phonenumber"`
|
||||
Amount float64 `json:"amount" binding:"required"`
|
||||
CustomerEmail string `json:"customerEmail" binding:"required"`
|
||||
CustomerPhone string `json:"customerPhone" binding:"required"`
|
||||
}
|
||||
// type ArifpayB2CRequest struct{
|
||||
// PhoneNumber string `json:"Phonenumber"`
|
||||
// Amount float64 `json:"amount" binding:"required"`
|
||||
// CustomerEmail string `json:"customerEmail" binding:"required"`
|
||||
// // CustomerPhone string `json:"customerPhone" binding:"required"`
|
||||
// }
|
||||
|
||||
type ArifpayVerifyByTransactionIDRequest struct{
|
||||
TransactionId string `json:"transactionId"`
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ type ChapaDepositVerification struct {
|
|||
Currency string
|
||||
}
|
||||
|
||||
type ChapaVerificationResponse struct {
|
||||
type ChapaPaymentVerificationResponse struct {
|
||||
Message string `json:"message"`
|
||||
Status string `json:"status"`
|
||||
Data struct {
|
||||
|
|
@ -103,6 +103,31 @@ type ChapaVerificationResponse struct {
|
|||
} `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 {
|
||||
Message string `json:"message"`
|
||||
Status string `json:"status"`
|
||||
|
|
@ -182,6 +207,57 @@ type ChapaCustomer struct {
|
|||
// 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 {
|
||||
Message string `json:"message"`
|
||||
Status string `json:"status"`
|
||||
|
|
@ -246,44 +322,49 @@ type ChapaTransactionType struct {
|
|||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type ChapaWebHookTransfer struct {
|
||||
type ChapaWebhookTransfer struct {
|
||||
Event string `json:"event"`
|
||||
Type string `json:"type"`
|
||||
AccountName string `json:"account_name"`
|
||||
AccountNumber string `json:"account_number"`
|
||||
BankId string `json:"bank_id"`
|
||||
BankID int `json:"bank_id"`
|
||||
BankName string `json:"bank_name"`
|
||||
Currency string `json:"currency"`
|
||||
Amount string `json:"amount"`
|
||||
Type string `json:"type"`
|
||||
Charge string `json:"charge"`
|
||||
Currency string `json:"currency"`
|
||||
Status string `json:"status"`
|
||||
Reference string `json:"reference"`
|
||||
TxRef string `json:"tx_ref"`
|
||||
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"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
Mobile interface{} `json:"mobile"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
Mobile string `json:"mobile"`
|
||||
Currency string `json:"currency"`
|
||||
Amount string `json:"amount"`
|
||||
Charge string `json:"charge"`
|
||||
Status string `json:"status"`
|
||||
Mode string `json:"mode"`
|
||||
Reference string `json:"reference"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Type string `json:"type"`
|
||||
TxRef string `json:"tx_ref"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
Customization struct {
|
||||
Title interface{} `json:"title"`
|
||||
Description interface{} `json:"description"`
|
||||
Logo interface{} `json:"logo"`
|
||||
} `json:"customization"`
|
||||
Meta string `json:"meta"`
|
||||
Customization ChapaWebhookCustomization `json:"customization"`
|
||||
Meta interface{} `json:"meta"` // may vary in structure, so kept flexible
|
||||
}
|
||||
|
||||
type ChapaWebhookCustomization struct {
|
||||
Title *string `json:"title,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Logo *string `json:"logo,omitempty"`
|
||||
}
|
||||
|
||||
type Balance struct {
|
||||
|
|
@ -298,19 +379,6 @@ type SwapRequest struct {
|
|||
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 {
|
||||
Message string `json:"message"`
|
||||
Status string `json:"status"`
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ type GameStartRequest struct {
|
|||
Country string `json:"country"`
|
||||
IP string `json:"ip"`
|
||||
BrandID string `json:"brandId"`
|
||||
UserAgent string `json:"userAgent,omitempty"`
|
||||
LobbyURL string `json:"lobbyUrl,omitempty"`
|
||||
CashierURL string `json:"cashierUrl,omitempty"`
|
||||
PlayerName string `json:"playerName,omitempty"`
|
||||
// UserAgent string `json:"userAgent,omitempty"`
|
||||
// LobbyURL string `json:"lobbyUrl,omitempty"`
|
||||
// CashierURL string `json:"cashierUrl,omitempty"`
|
||||
// PlayerName string `json:"playerName,omitempty"`
|
||||
}
|
||||
|
||||
type DemoGameRequest struct {
|
||||
|
|
@ -71,8 +71,8 @@ type DemoGameRequest struct {
|
|||
DeviceType string `json:"deviceType"`
|
||||
IP string `json:"ip"`
|
||||
BrandID string `json:"brandId"`
|
||||
PlayerID string `json:"playerId,omitempty"`
|
||||
Country string `json:"country,omitempty"`
|
||||
// PlayerID string `json:"playerId,omitempty"`
|
||||
// Country string `json:"country,omitempty"`
|
||||
}
|
||||
|
||||
type GameStartResponse struct {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@ type VirtualGameRepository interface {
|
|||
ListVirtualGameProviders(ctx context.Context, limit, offset int32) ([]dbgen.VirtualGameProvider, error)
|
||||
UpdateVirtualGameProviderEnabled(ctx context.Context, providerID string, enabled bool) (dbgen.VirtualGameProvider, 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)
|
||||
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
|
||||
GetVirtualGameTransactionByExternalID(ctx context.Context, externalID string) (*domain.VirtualGameTransaction, 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,
|
||||
GameID: session.GameID,
|
||||
SessionToken: session.SessionToken,
|
||||
Currency: session.Currency,
|
||||
Status: session.Status,
|
||||
ExpiresAt: pgtype.Timestamptz{Time: session.ExpiresAt, Valid: true},
|
||||
// Currency: session.Currency,
|
||||
// Status: session.Status,
|
||||
// ExpiresAt: pgtype.Timestamptz{Time: session.ExpiresAt, Valid: true},
|
||||
}
|
||||
_, err := r.store.queries.CreateVirtualGameSession(ctx, params)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) 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) {
|
||||
dbSession, err := r.store.queries.GetVirtualGameSessionByToken(ctx, token)
|
||||
if err != nil {
|
||||
|
|
@ -187,20 +207,20 @@ func (r *VirtualGameRepo) GetVirtualGameSessionByToken(ctx context.Context, toke
|
|||
UserID: dbSession.UserID,
|
||||
GameID: dbSession.GameID,
|
||||
SessionToken: dbSession.SessionToken,
|
||||
Currency: dbSession.Currency,
|
||||
Status: dbSession.Status,
|
||||
// Currency: dbSession.Currency,
|
||||
// Status: dbSession.Status,
|
||||
CreatedAt: dbSession.CreatedAt.Time,
|
||||
UpdatedAt: dbSession.UpdatedAt.Time,
|
||||
ExpiresAt: dbSession.ExpiresAt.Time,
|
||||
// ExpiresAt: dbSession.ExpiresAt.Time,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *VirtualGameRepo) UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error {
|
||||
return r.store.queries.UpdateVirtualGameSessionStatus(ctx, dbgen.UpdateVirtualGameSessionStatusParams{
|
||||
ID: id,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
// func (r *VirtualGameRepo) UpdateVirtualGameSessionStatus(ctx context.Context, id int64, status string) error {
|
||||
// return r.store.queries.UpdateVirtualGameSessionStatus(ctx, dbgen.UpdateVirtualGameSessionStatusParams{
|
||||
// ID: id,
|
||||
// Status: status,
|
||||
// })
|
||||
// }
|
||||
|
||||
func (r *VirtualGameRepo) CreateVirtualGameTransaction(ctx context.Context, tx *domain.VirtualGameTransaction) error {
|
||||
params := dbgen.CreateVirtualGameTransactionParams{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
"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
|
||||
nonce := uuid.NewString()
|
||||
|
||||
|
|
@ -129,6 +130,10 @@ func (s *ArifpayService) CreateCheckoutSession(req domain.CheckoutSessionClientR
|
|||
ReferenceNumber: nonce,
|
||||
SessionID: fmt.Sprintf("%v", data["sessionId"]),
|
||||
Status: string(domain.PaymentStatusPending),
|
||||
CashierID: domain.ValidInt64{
|
||||
Value: userId,
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := s.transferStore.CreateTransfer(context.Background(), transfer); err != nil {
|
||||
|
|
@ -138,7 +143,7 @@ func (s *ArifpayService) CreateCheckoutSession(req domain.CheckoutSessionClientR
|
|||
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
|
||||
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 &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
|
||||
transfer, err := s.transferStore.GetTransferByReference(ctx, req.Transaction.TransactionID)
|
||||
transfer, err := s.transferStore.GetTransferByReference(ctx, req.Nonce)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
|
||||
userId := transfer.DepositorID.Value
|
||||
|
||||
wallet, err := s.walletSvc.GetCustomerWallet(ctx, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -196,7 +203,7 @@ func (s *ArifpayService) HandleWebhook(ctx context.Context, req domain.WebhookRe
|
|||
}
|
||||
|
||||
// 2. Update transfer status
|
||||
newStatus := req.Transaction.TransactionStatus
|
||||
newStatus := strings.ToLower(req.Transaction.TransactionStatus)
|
||||
// if 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
|
||||
if (newStatus == "SUCCESS" && isDepost) || (newStatus == "FAILED" && !isDepost) {
|
||||
_, err = s.walletSvc.AddToWallet(ctx, wallets[0].ID, domain.Currency(req.TotalAmount), domain.ValidInt64{}, transfer.PaymentMethod, domain.PaymentDetails{
|
||||
if (newStatus == "success" && isDeposit) || (newStatus == "failed" && !isDeposit) {
|
||||
_, err = s.walletSvc.AddToWallet(ctx, wallet.RegularID, domain.Currency(req.TotalAmount), domain.ValidInt64{}, transfer.PaymentMethod, domain.PaymentDetails{
|
||||
ReferenceNumber: domain.ValidString{
|
||||
Value: req.Transaction.TransactionID,
|
||||
Value: req.Nonce,
|
||||
Valid: true,
|
||||
},
|
||||
BankNumber: domain.ValidString{
|
||||
|
|
@ -231,35 +238,94 @@ func (s *ArifpayService) HandleWebhook(ctx context.Context, req domain.WebhookRe
|
|||
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
|
||||
|
||||
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()
|
||||
|
||||
sessionReq := domain.CheckoutSessionClientRequest{
|
||||
Amount: req.Amount,
|
||||
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 {
|
||||
_, 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)
|
||||
}
|
||||
|
||||
sessionRespData := sessionResp["data"].(map[string]any)
|
||||
|
||||
// Step 2: Execute Transfer
|
||||
transferURL := fmt.Sprintf("%s/api/Telebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
||||
reqBody := map[string]any{
|
||||
"Sessionid": sessionResp["sessionId"],
|
||||
"Phonenumber": req.PhoneNumber,
|
||||
"Sessionid": sessionRespData["sessionId"],
|
||||
"Phonenumber": "251" + req.CustomerPhone[:9],
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(reqBody)
|
||||
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)
|
||||
}
|
||||
|
||||
transferReq, err := http.NewRequestWithContext(ctx, http.MethodPost, transferURL, bytes.NewBuffer(payload))
|
||||
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)
|
||||
}
|
||||
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)
|
||||
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)
|
||||
}
|
||||
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)
|
||||
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,
|
||||
Type: domain.WITHDRAW, // B2C = payout
|
||||
ReferenceNumber: referenceNum,
|
||||
SessionID: fmt.Sprintf("%v", sessionResp["sessionId"]),
|
||||
SessionID: fmt.Sprintf("%v", sessionRespData["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("failed to store transfer: %w", err)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (s *ArifpayService) ExecuteCBEB2CTransfer(ctx context.Context, req domain.ArifpayB2CRequest, userId int64) error {
|
||||
// Step 1: Create Session
|
||||
referenceNum := uuid.NewString()
|
||||
|
||||
sessionReq := domain.CheckoutSessionClientRequest{
|
||||
Amount: req.Amount,
|
||||
CustomerEmail: req.CustomerEmail,
|
||||
CustomerPhone: req.CustomerPhone,
|
||||
}
|
||||
|
||||
sessionResp, err := s.CreateCheckoutSession(sessionReq, false)
|
||||
func (s *ArifpayService) ExecuteCBEB2CTransfer(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("cbebirr: failed to create session: %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)
|
||||
return fmt.Errorf("cbebirr: failed to get user wallet: %w", err)
|
||||
}
|
||||
|
||||
_, err = s.walletSvc.DeductFromWallet(
|
||||
ctx,
|
||||
userWallets[0].ID,
|
||||
userWallet.RegularID,
|
||||
domain.Currency(req.Amount),
|
||||
domain.ValidInt64{},
|
||||
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 nil
|
||||
}
|
||||
|
||||
func (s *ArifpayService) ExecuteMPesaB2CTransfer(ctx context.Context, req domain.ArifpayB2CRequest, userId int64) error {
|
||||
// Step 1: Create Session
|
||||
referenceNum := uuid.NewString()
|
||||
|
||||
// Step 2: Create Session
|
||||
sessionReq := domain.CheckoutSessionClientRequest{
|
||||
Amount: req.Amount,
|
||||
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 {
|
||||
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
|
||||
transferURL := fmt.Sprintf("%s/api/Mpesa/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
||||
// Step 3: Execute Transfer
|
||||
transferURL := fmt.Sprintf("%s/api/Cbebirr/b2c/transfer", s.cfg.ARIFPAY.BaseURL)
|
||||
reqBody := map[string]any{
|
||||
"Sessionid": sessionResp["sessionId"],
|
||||
"Phonenumber": req.PhoneNumber,
|
||||
"Phonenumber": "251" + req.CustomerPhone[:9],
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(reqBody)
|
||||
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))
|
||||
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("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
|
||||
|
||||
transferResp, err := s.httpClient.Do(transferReq)
|
||||
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()
|
||||
|
||||
if transferResp.StatusCode >= 300 {
|
||||
if transferResp.StatusCode != http.StatusOK {
|
||||
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{
|
||||
Amount: domain.Currency(req.Amount),
|
||||
Verified: false,
|
||||
|
|
@ -451,30 +478,116 @@ func (s *ArifpayService) ExecuteMPesaB2CTransfer(ctx context.Context, req domain
|
|||
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("Mpesa: failed to store transfer: %w", err)
|
||||
CashierID: domain.ValidInt64{
|
||||
Value: userId,
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Step 4: Deduct from user wallet
|
||||
userWallets, err := s.walletSvc.GetWalletsByUser(ctx, userId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Mpesa: failed to get user wallets: %w", err)
|
||||
if _, err := s.transferStore.CreateTransfer(ctx, transfer); err != nil {
|
||||
return fmt.Errorf("cbebirr: failed to store transfer: %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(
|
||||
ctx,
|
||||
userWallets[0].ID,
|
||||
userWallet.RegularID,
|
||||
domain.Currency(req.Amount),
|
||||
domain.ValidInt64{},
|
||||
domain.TRANSFER_ARIFPAY,
|
||||
"",
|
||||
)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func (c *Client) InitializePayment(ctx context.Context, req domain.ChapaInitDepo
|
|||
"first_name": req.FirstName,
|
||||
"last_name": req.LastName,
|
||||
"tx_ref": req.TxRef,
|
||||
"callback_url": req.CallbackURL,
|
||||
// "callback_url": req.CallbackURL,
|
||||
"return_url": req.ReturnURL,
|
||||
"phone_number": req.PhoneNumber,
|
||||
}
|
||||
|
|
@ -131,7 +131,7 @@ func (c *Client) VerifyPayment(ctx context.Context, reference string) (domain.Ch
|
|||
}, 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)
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
var verification domain.ChapaVerificationResponse
|
||||
var verification domain.ChapaPaymentVerificationResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return &domain.ChapaVerificationResponse{
|
||||
return &domain.ChapaTransferVerificationResponse{
|
||||
Status: string(status),
|
||||
// Amount: response.Amount,
|
||||
// Currency: response.Currency,
|
||||
|
|
@ -277,7 +277,7 @@ func (c *Client) GetTransactionEvents(ctx context.Context, refId string) ([]doma
|
|||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
var banks []domain.Bank
|
||||
var banks []domain.BankData
|
||||
for _, bankData := range bankResponse.Data {
|
||||
bank := domain.Bank{
|
||||
bank := domain.BankData{
|
||||
ID: bankData.ID,
|
||||
Slug: bankData.Slug,
|
||||
Swift: bankData.Swift,
|
||||
|
|
@ -324,7 +324,7 @@ func (c *Client) FetchSupportedBanks(ctx context.Context) ([]domain.Bank, error)
|
|||
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"
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
var verification domain.ChapaVerificationResponse
|
||||
var verification domain.ChapaTransferVerificationResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&verification); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@ import (
|
|||
|
||||
type ChapaStore interface {
|
||||
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)
|
||||
CreateWithdrawal(userID string, amount float64, accountNumber, bankCode string) (*domain.ChapaWithdrawal, error)
|
||||
HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebHookTransfer) error
|
||||
HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebHookPayment) error
|
||||
HandleVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaWebhookTransfer) error
|
||||
HandleVerifyWithdrawWebhook(ctx context.Context, payment domain.ChapaWebhookPayment) error
|
||||
GetPaymentReceiptURL(ctx context.Context, chapaRef string) (string, error)
|
||||
GetAllTransfers(ctx context.Context) ([]domain.Transfer, error)
|
||||
GetAccountBalance(ctx context.Context, currencyCode string) ([]domain.Balance, error)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package chapa
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"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
|
||||
func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount domain.Currency) (string, error) {
|
||||
// Validate amount
|
||||
|
|
@ -88,7 +116,7 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
|||
ReferenceNumber: reference,
|
||||
// ReceiverWalletID: 1,
|
||||
SenderWalletID: domain.ValidInt64{
|
||||
Value: senderWallet.ID,
|
||||
Value: senderWallet.RegularID,
|
||||
Valid: true,
|
||||
},
|
||||
Verified: false,
|
||||
|
|
@ -135,9 +163,9 @@ func (s *Service) InitiateDeposit(ctx context.Context, userID int64, amount doma
|
|||
return response.CheckoutURL, nil
|
||||
}
|
||||
|
||||
func (s *Service) ProcessVerifyDepositWebhook(ctx context.Context, transfer domain.ChapaPaymentWebhookRequest) error {
|
||||
func (s *Service) ProcessVerifyDepositWebhook(ctx context.Context, req domain.ChapaWebhookPayment) error {
|
||||
// Find payment by reference
|
||||
payment, err := s.transferStore.GetTransferByReference(ctx, transfer.TxRef)
|
||||
payment, err := s.transferStore.GetTransferByReference(ctx, req.TxRef)
|
||||
if err != nil {
|
||||
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 transfer.Status == domain.PaymentStatusSuccessful {
|
||||
if req.Status == string(domain.PaymentStatusSuccessful) {
|
||||
|
||||
if err := s.transferStore.UpdateTransferVerification(ctx, payment.ID, true); err != nil {
|
||||
return fmt.Errorf("failed to update is payment verified value: %w", err)
|
||||
|
|
@ -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{
|
||||
ReferenceNumber: domain.ValidString{
|
||||
Value: transfer.TxRef,
|
||||
Value: req.TxRef,
|
||||
},
|
||||
}, fmt.Sprintf("Added %v to wallet using Chapa", payment.Amount)); err != nil {
|
||||
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) {
|
||||
// Parse and validate amount
|
||||
amount, err := strconv.ParseInt(req.Amount, 10, 64)
|
||||
amount, err := strconv.ParseFloat(req.Amount, 64)
|
||||
if err != nil || amount <= 0 {
|
||||
return nil, domain.ErrInvalidWithdrawalAmount
|
||||
}
|
||||
|
|
@ -319,7 +347,7 @@ func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req doma
|
|||
reference := uuid.New().String()
|
||||
|
||||
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),
|
||||
Type: domain.WITHDRAW,
|
||||
SenderWalletID: domain.ValidInt64{
|
||||
|
|
@ -341,40 +369,49 @@ func (s *Service) InitiateWithdrawal(ctx context.Context, userID int64, req doma
|
|||
transferReq := domain.ChapaWithdrawalRequest{
|
||||
AccountName: req.AccountName,
|
||||
AccountNumber: req.AccountNumber,
|
||||
Amount: fmt.Sprintf("%d", amount),
|
||||
Amount: fmt.Sprintf("%f", amount),
|
||||
Currency: req.Currency,
|
||||
Reference: reference,
|
||||
// BeneficiaryName: fmt.Sprintf("%s %s", user.FirstName, user.LastName),
|
||||
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)
|
||||
if err := s.walletStore.UpdateBalance(ctx, wallet.RegularID, domain.Currency(newBalance)); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
transfer, err := s.transferStore.GetTransferByReference(ctx, payment.Reference)
|
||||
transfer, err := s.transferStore.GetTransferByReference(ctx, req.Reference)
|
||||
if err != nil {
|
||||
return domain.ErrPaymentNotFound
|
||||
}
|
||||
|
|
@ -395,7 +432,7 @@ func (s *Service) ProcessVerifyWithdrawWebhook(ctx context.Context, payment doma
|
|||
// 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 {
|
||||
return fmt.Errorf("failed to update payment status: %w", err)
|
||||
} // If payment is completed, credit user's walle
|
||||
|
|
@ -420,15 +457,7 @@ func (s *Service) GetPaymentReceiptURL(refId string) (string, error) {
|
|||
return receiptURL, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.Bank, error) {
|
||||
banks, err := s.chapaClient.FetchSupportedBanks(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch banks: %w", err)
|
||||
}
|
||||
return banks, nil
|
||||
}
|
||||
|
||||
func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (*domain.ChapaVerificationResponse, error) {
|
||||
func (s *Service) ManuallyVerify(ctx context.Context, txRef string) (any, error) {
|
||||
// Lookup transfer by reference
|
||||
transfer, err := s.transferStore.GetTransferByReference(ctx, txRef)
|
||||
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 transfer.Verified {
|
||||
return &domain.ChapaVerificationResponse{}, errors.New("transfer already verified")
|
||||
return map[string]any{}, errors.New("transfer already verified")
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
var verification *domain.ChapaVerificationResponse
|
||||
var verification any
|
||||
|
||||
switch strings.ToLower(string(transfer.Type)) {
|
||||
case string(domain.DEPOSIT):
|
||||
// Verify Chapa payment
|
||||
verification, err = s.chapaClient.ManualVerifyPayment(ctx, txRef)
|
||||
verification, err := s.chapaClient.ManualVerifyPayment(ctx, txRef)
|
||||
if err != nil {
|
||||
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):
|
||||
// Verify Chapa transfer
|
||||
verification, err = s.chapaClient.ManualVerifyTransfer(ctx, txRef)
|
||||
verification, err := s.chapaClient.ManualVerifyTransfer(ctx, txRef)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
func (s *Service) GetAllTransfers(ctx context.Context) ([]domain.Transfer, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.chapa.co/v1/transfers", nil)
|
||||
func (s *Service) GetSupportedBanks(ctx context.Context) ([]domain.BankData, 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) GetAllTransfers(ctx context.Context) (*domain.ChapaTransfersListResponse, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, s.cfg.CHAPA_BASE_URL+"/transfers", nil)
|
||||
if err != nil {
|
||||
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))
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data []domain.Transfer `json:"data"`
|
||||
}
|
||||
|
||||
var result domain.ChapaTransfersListResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
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) {
|
||||
baseURL := "https://api.chapa.co/v1/balances"
|
||||
URL := s.cfg.CHAPA_BASE_URL + "/balances"
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
func (s *Service) SwapCurrency(ctx context.Context, reqBody domain.SwapRequest) (*domain.SwapResponse, error) {
|
||||
URL := s.cfg.CHAPA_BASE_URL + "/swap"
|
||||
|
||||
payload := domain.SwapRequest{
|
||||
Amount: amount,
|
||||
From: strings.ToUpper(from),
|
||||
To: strings.ToUpper(to),
|
||||
}
|
||||
// Normalize currency codes
|
||||
reqBody.From = strings.ToUpper(reqBody.From)
|
||||
reqBody.To = strings.ToUpper(reqBody.To)
|
||||
|
||||
// payload := map[string]any{
|
||||
// "amount": amount,
|
||||
// "from": strings.ToUpper(from),
|
||||
// "to": strings.ToUpper(to),
|
||||
// }
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
// Marshal request body
|
||||
body, err := json.Marshal(reqBody)
|
||||
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 {
|
||||
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("Authorization", fmt.Sprintf("Bearer %s", s.cfg.CHAPA_SECRET_KEY))
|
||||
|
||||
// Execute request
|
||||
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 {
|
||||
// Handle unexpected status
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
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"`
|
||||
}
|
||||
|
||||
// Decode response
|
||||
var result domain.SwapResponse
|
||||
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
|
||||
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
|
||||
if callback.Type == "SESSION_END" {
|
||||
if err := s.repo.UpdateVirtualGameSessionStatus(ctx, session.ID, "COMPLETED"); err != nil {
|
||||
s.logger.Error("failed to update session status",
|
||||
"sessionID", session.ID,
|
||||
"error", err)
|
||||
}
|
||||
}
|
||||
// if callback.Type == "SESSION_END" {
|
||||
// if err := s.repo.UpdateVirtualGameSessionStatus(ctx, session.ID, "COMPLETED"); err != nil {
|
||||
// s.logger.Error("failed to update session status",
|
||||
// "sessionID", session.ID,
|
||||
// "error", err)
|
||||
// }
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
|
@ -167,7 +168,7 @@ func (s *Service) StartGame(ctx context.Context, req domain.GameStartRequest) (*
|
|||
"playerId": req.PlayerID,
|
||||
"currency": req.Currency,
|
||||
"deviceType": req.DeviceType,
|
||||
"country": "US",
|
||||
"country": req.Country,
|
||||
"ip": req.IP,
|
||||
"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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,15 @@ import (
|
|||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/arifpay/checkout [post]
|
||||
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
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
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 {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Error: err.Error(),
|
||||
|
|
@ -53,7 +62,7 @@ func (h *Handler) CreateCheckoutSessionHandler(c *fiber.Ctx) error {
|
|||
// @Success 200 {object} domain.Response
|
||||
// @Failure 400 {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 {
|
||||
sessionID := c.Params("sessionId")
|
||||
if sessionID == "" {
|
||||
|
|
@ -103,15 +112,15 @@ func (h *Handler) HandleArifpayC2BWebhook(c *fiber.Ctx) error {
|
|||
// 🚨 Decide how to get userId:
|
||||
// If you get it from auth context/middleware, extract it here.
|
||||
// For now, let's assume userId comes from your auth claims:
|
||||
userId, ok := c.Locals("user_id").(int64)
|
||||
if !ok {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||
Error: "missing user id",
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
}
|
||||
// userId, ok := c.Locals("user_id").(int64)
|
||||
// if !ok {
|
||||
// return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||
// Error: "missing user id",
|
||||
// Message: "Unauthorized",
|
||||
// })
|
||||
// }
|
||||
|
||||
err := h.arifpaySvc.HandleWebhook(c.Context(), req, userId, true)
|
||||
err := h.arifpaySvc.ProcessWebhook(c.Context(), req, true)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Error: err.Error(),
|
||||
|
|
@ -150,15 +159,15 @@ func (h *Handler) HandleArifpayB2CWebhook(c *fiber.Ctx) error {
|
|||
// 🚨 Decide how to get userId:
|
||||
// If you get it from auth context/middleware, extract it here.
|
||||
// For now, let's assume userId comes from your auth claims:
|
||||
userId, ok := c.Locals("user_id").(int64)
|
||||
if !ok {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||
Error: "missing user id",
|
||||
Message: "Unauthorized",
|
||||
})
|
||||
}
|
||||
// userId, ok := c.Locals("user_id").(int64)
|
||||
// if !ok {
|
||||
// return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||
// Error: "missing user id",
|
||||
// Message: "Unauthorized",
|
||||
// })
|
||||
// }
|
||||
|
||||
err := h.arifpaySvc.HandleWebhook(c.Context(), req, userId, false)
|
||||
err := h.arifpaySvc.ProcessWebhook(c.Context(), req, false)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Error: err.Error(),
|
||||
|
|
@ -253,7 +262,7 @@ func (h *Handler) ArifpayVerifyBySessionIDHandler(c *fiber.Ctx) error {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @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"
|
||||
// @Failure 400 {object} map[string]string "error: invalid request or unsupported transfer type"
|
||||
// @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 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to process your withdrawal request",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
|
@ -61,33 +63,55 @@ func (h *Handler) InitiateDeposit(c *fiber.Ctx) error {
|
|||
|
||||
// WebhookCallback godoc
|
||||
// @Summary Chapa payment webhook callback (used by Chapa)
|
||||
// @Description Handles payment notifications from Chapa
|
||||
// @Description Handles payment and transfer notifications from Chapa
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body domain.ChapaWebhookPayload true "Webhook payload"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Param request body domain.ChapaWebhookPayment true "Webhook payload"
|
||||
// @Success 200 {object} domain.Response
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/chapa/payments/webhook/verify [post]
|
||||
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)
|
||||
}
|
||||
|
||||
switch chapaTransactionType.Type {
|
||||
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 {
|
||||
if err := h.chapaSvc.ProcessVerifyDepositWebhook(c.Context(), payment); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to verify Chapa deposit",
|
||||
Error: err.Error(),
|
||||
|
|
@ -96,35 +120,10 @@ func (h *Handler) WebhookCallback(c *fiber.Ctx) error {
|
|||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
StatusCode: 200,
|
||||
Message: "Chapa deposit transaction verified successfully",
|
||||
Data: chapaTransferVerificationRequest,
|
||||
Message: "Chapa deposit webhook processed successfully",
|
||||
// Data: payment,
|
||||
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
|
||||
|
|
@ -248,7 +247,7 @@ func (h *Handler) GetTransactionEvents(c *fiber.Ctx) error {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param tx_ref path string true "Transaction Reference"
|
||||
// @Success 200 {object} domain.ChapaVerificationResponse
|
||||
// @Success 200 {object} domain.Response
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/chapa/transaction/manual/verify/{tx_ref} [get]
|
||||
|
|
@ -311,7 +310,7 @@ func (h *Handler) GetSupportedBanks(c *fiber.Ctx) error {
|
|||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @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 401 {object} domain.ErrorResponse "Unauthorized"
|
||||
// @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",
|
||||
StatusCode: 201,
|
||||
StatusCode: 200,
|
||||
Success: true,
|
||||
Data: withdrawal,
|
||||
})
|
||||
|
|
@ -430,44 +429,56 @@ func (h *Handler) GetAccountBalance(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "Chapa account balance retrieved successfully",
|
||||
Message: "Chapa account balances retrieved successfully",
|
||||
Data: balances,
|
||||
StatusCode: fiber.StatusOK,
|
||||
Success: true,
|
||||
})
|
||||
}
|
||||
|
||||
// InitiateSwap godoc
|
||||
// @Summary Initiate a currency swap
|
||||
// @Description Perform a USD to ETB currency swap using Chapa's API
|
||||
// SwapCurrency godoc
|
||||
// @Summary Swap currency using Chapa API
|
||||
// @Description Convert an amount from one currency to another using Chapa's currency swap API
|
||||
// @Tags Chapa
|
||||
// @Accept 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
|
||||
// @Failure 400 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/chapa/swap [post]
|
||||
func (h *Handler) InitiateSwap(c *fiber.Ctx) error {
|
||||
var req domain.SwapRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
func (h *Handler) SwapCurrency(c *fiber.Ctx) error {
|
||||
var reqBody domain.SwapRequest
|
||||
|
||||
// Parse request body
|
||||
if err := c.BodyParser(&reqBody); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid request payload",
|
||||
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 {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to initiate currency swap",
|
||||
Message: "Failed to perform currency swap",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Success response
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "Currency swap initiated successfully",
|
||||
Data: swapResult,
|
||||
Message: "Currency swapped successfully",
|
||||
Data: resp,
|
||||
StatusCode: fiber.StatusOK,
|
||||
Success: true,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -135,7 +135,10 @@ func (h *Handler) StartGame(c *fiber.Ctx) error {
|
|||
req.BrandID = h.Cfg.VeliGames.BrandID
|
||||
}
|
||||
|
||||
useId := c.Locals("user_id")
|
||||
|
||||
req.IP = c.IP()
|
||||
req.PlayerID = useId.(string)
|
||||
|
||||
// 1️⃣ Call StartGame service
|
||||
res, err := h.veliVirtualGameSvc.StartGame(context.Background(), req)
|
||||
|
|
|
|||
|
|
@ -150,9 +150,9 @@ func (a *App) initAppRoutes() {
|
|||
|
||||
//Arifpay
|
||||
groupV1.Post("/arifpay/checkout", a.authMiddleware, h.CreateCheckoutSessionHandler)
|
||||
groupV1.Post("/arifpay/checkout/cancel/:session_id", a.authMiddleware, h.CancelCheckoutSessionHandler)
|
||||
groupV1.Post("/api/v1/arifpay/c2b-webhook", a.authMiddleware, h.HandleArifpayC2BWebhook)
|
||||
groupV1.Post("/api/v1/arifpay/b2c-webhook", a.authMiddleware, h.HandleArifpayB2CWebhook)
|
||||
groupV1.Post("/arifpay/checkout/cancel/:sessionId", a.authMiddleware, h.CancelCheckoutSessionHandler)
|
||||
groupV1.Post("/api/v1/arifpay/c2b-webhook", h.HandleArifpayC2BWebhook)
|
||||
groupV1.Post("/api/v1/arifpay/b2c-webhook", h.HandleArifpayB2CWebhook)
|
||||
groupV1.Post("/arifpay/b2c/transfer", a.authMiddleware, h.ExecuteArifpayB2CTransfer)
|
||||
groupV1.Post("/arifpay/transaction-id/verify-transaction", a.authMiddleware, h.ArifpayVerifyByTransactionIDHandler)
|
||||
groupV1.Get("/arifpay/session-id/verify-transaction/:session_id", a.authMiddleware, h.ArifpayVerifyBySessionIDHandler)
|
||||
|
|
@ -381,17 +381,17 @@ func (a *App) initAppRoutes() {
|
|||
|
||||
//Chapa Routes
|
||||
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.Get("/chapa/transactions", h.FetchAllTransactions)
|
||||
groupV1.Get("/chapa/transaction/events/:ref_id", h.GetTransactionEvents)
|
||||
groupV1.Get("/chapa/transactions", a.authMiddleware, h.FetchAllTransactions)
|
||||
groupV1.Get("/chapa/transaction/events/:ref_id", a.authMiddleware, h.GetTransactionEvents)
|
||||
groupV1.Post("/chapa/payments/deposit", a.authMiddleware, h.InitiateDeposit)
|
||||
groupV1.Post("/chapa/payments/withdraw", a.authMiddleware, h.InitiateWithdrawal)
|
||||
groupV1.Get("/chapa/banks", h.GetSupportedBanks)
|
||||
groupV1.Get("/chapa/payments/receipt/:chapa_ref", h.GetPaymentReceipt)
|
||||
groupV1.Get("/chapa/transfers", h.GetAllTransfers)
|
||||
groupV1.Get("/chapa/balance", h.GetAccountBalance)
|
||||
groupV1.Post("/chapa/init-swap", h.InitiateSwap)
|
||||
groupV1.Get("/chapa/payments/receipt/:chapa_ref", a.authMiddleware, h.GetPaymentReceipt)
|
||||
groupV1.Get("/chapa/transfers", a.authMiddleware, h.GetAllTransfers)
|
||||
groupV1.Get("/chapa/balance", a.authMiddleware, h.GetAccountBalance)
|
||||
groupV1.Post("/chapa/swap", a.authMiddleware, h.SwapCurrency)
|
||||
|
||||
// Currencies
|
||||
groupV1.Get("/currencies", h.GetSupportedCurrencies)
|
||||
|
|
@ -409,7 +409,7 @@ func (a *App) initAppRoutes() {
|
|||
//Veli Virtual Game Routes
|
||||
groupV1.Post("/veli/providers", h.GetProviders)
|
||||
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)
|
||||
a.fiber.Post("/balance", h.GetBalance)
|
||||
groupV1.Post("/veli/gaming-activity", a.authMiddleware, h.GetGamingActivity)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user