createTransaction+createTransfer fix
This commit is contained in:
parent
eb8abfc963
commit
75d469be8c
|
|
@ -109,11 +109,15 @@ func main() {
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
recommendationSvc := recommendation.NewService(recommendationRepo)
|
recommendationSvc := recommendation.NewService(recommendationRepo)
|
||||||
|
chapaClient := chapa.NewClient(cfg.CHAPA_BASE_URL, cfg.CHAPA_SECRET_KEY)
|
||||||
|
|
||||||
chapaSvc := chapa.NewService(
|
chapaSvc := chapa.NewService(
|
||||||
transaction.TransactionStore(store),
|
transaction.TransactionStore(store),
|
||||||
wallet.WalletStore(store),
|
wallet.WalletStore(store),
|
||||||
user.UserStore(store),
|
user.UserStore(store),
|
||||||
referalSvc,
|
referalSvc,
|
||||||
|
branch.BranchStore(store),
|
||||||
|
chapaClient,
|
||||||
store,
|
store,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
259
docs/docs.go
259
docs/docs.go
|
|
@ -361,6 +361,63 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/chapa/payments/deposit": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Deposits money into user wallet from user account using Chapa",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Chapa"
|
||||||
|
],
|
||||||
|
"summary": "Deposit money into user wallet using Chapa",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Deposit request payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ChapaDepositRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ChapaPaymentUrlResponseWrapper"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/chapa/payments/initialize": {
|
"/api/v1/chapa/payments/initialize": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Initiate a payment through Chapa",
|
"description": "Initiate a payment through Chapa",
|
||||||
|
|
@ -395,6 +452,39 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/chapa/payments/verify": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Chapa"
|
||||||
|
],
|
||||||
|
"summary": "Verifies Chapa webhook transaction",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Webhook Payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ChapaTransactionType"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/chapa/payments/verify/{tx_ref}": {
|
"/api/v1/chapa/payments/verify/{tx_ref}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Verify the transaction status from Chapa using tx_ref",
|
"description": "Verify the transaction status from Chapa using tx_ref",
|
||||||
|
|
@ -427,6 +517,76 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/chapa/payments/withdraw": {
|
||||||
|
"post": {
|
||||||
|
"description": "Initiates a withdrawal transaction using Chapa for the authenticated user.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Chapa"
|
||||||
|
],
|
||||||
|
"summary": "Withdraw using Chapa",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Chapa Withdraw Request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ChapaWithdrawRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Withdrawal requested successfully",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Unprocessable Entity",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/chapa/transfers": {
|
"/api/v1/chapa/transfers": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Initiate a transfer request via Chapa",
|
"description": "Initiate a transfer request via Chapa",
|
||||||
|
|
@ -4413,6 +4573,46 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.ChapaDepositRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaPaymentUrlResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"payment_url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaPaymentUrlResponseWrapper": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status_code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.ChapaSupportedBank": {
|
"domain.ChapaSupportedBank": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -4480,6 +4680,44 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.ChapaTransactionType": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaWithdrawRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"account_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"account_number": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"bank_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"beneficiary_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"wallet_id": {
|
||||||
|
"description": "add this",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.CreateBetOutcomeReq": {
|
"domain.CreateBetOutcomeReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -4561,7 +4799,7 @@ const docTemplate = `{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"amount": {
|
"amount": {
|
||||||
"type": "string"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"callback_url": {
|
"callback_url": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -4832,6 +5070,21 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.Response": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status_code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.Role": {
|
"domain.Role": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|
@ -5041,6 +5294,10 @@ const docTemplate = `{
|
||||||
"description": "Match or event name",
|
"description": "Match or event name",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"source": {
|
||||||
|
"description": "bet api provider (bet365, betfair)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"sportID": {
|
"sportID": {
|
||||||
"description": "Sport ID",
|
"description": "Sport ID",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
|
||||||
|
|
@ -353,6 +353,63 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/chapa/payments/deposit": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Deposits money into user wallet from user account using Chapa",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Chapa"
|
||||||
|
],
|
||||||
|
"summary": "Deposit money into user wallet using Chapa",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Deposit request payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ChapaDepositRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ChapaPaymentUrlResponseWrapper"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/chapa/payments/initialize": {
|
"/api/v1/chapa/payments/initialize": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Initiate a payment through Chapa",
|
"description": "Initiate a payment through Chapa",
|
||||||
|
|
@ -387,6 +444,39 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/chapa/payments/verify": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Chapa"
|
||||||
|
],
|
||||||
|
"summary": "Verifies Chapa webhook transaction",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Webhook Payload",
|
||||||
|
"name": "payload",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ChapaTransactionType"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/chapa/payments/verify/{tx_ref}": {
|
"/api/v1/chapa/payments/verify/{tx_ref}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Verify the transaction status from Chapa using tx_ref",
|
"description": "Verify the transaction status from Chapa using tx_ref",
|
||||||
|
|
@ -419,6 +509,76 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/chapa/payments/withdraw": {
|
||||||
|
"post": {
|
||||||
|
"description": "Initiates a withdrawal transaction using Chapa for the authenticated user.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Chapa"
|
||||||
|
],
|
||||||
|
"summary": "Withdraw using Chapa",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Chapa Withdraw Request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.ChapaWithdrawRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Withdrawal requested successfully",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Unprocessable Entity",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/domain.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/chapa/transfers": {
|
"/api/v1/chapa/transfers": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Initiate a transfer request via Chapa",
|
"description": "Initiate a transfer request via Chapa",
|
||||||
|
|
@ -4405,6 +4565,46 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.ChapaDepositRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaPaymentUrlResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"payment_url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaPaymentUrlResponseWrapper": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status_code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.ChapaSupportedBank": {
|
"domain.ChapaSupportedBank": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -4472,6 +4672,44 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.ChapaTransactionType": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"domain.ChapaWithdrawRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"account_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"account_number": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"bank_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"beneficiary_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"branch_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"wallet_id": {
|
||||||
|
"description": "add this",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.CreateBetOutcomeReq": {
|
"domain.CreateBetOutcomeReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -4553,7 +4791,7 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"amount": {
|
"amount": {
|
||||||
"type": "string"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"callback_url": {
|
"callback_url": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -4824,6 +5062,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"domain.Response": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status_code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"domain.Role": {
|
"domain.Role": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
|
@ -5033,6 +5286,10 @@
|
||||||
"description": "Match or event name",
|
"description": "Match or event name",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"source": {
|
||||||
|
"description": "bet api provider (bet365, betfair)",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"sportID": {
|
"sportID": {
|
||||||
"description": "Sport ID",
|
"description": "Sport ID",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,32 @@ definitions:
|
||||||
example: 2
|
example: 2
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
domain.ChapaDepositRequest:
|
||||||
|
properties:
|
||||||
|
amount:
|
||||||
|
type: integer
|
||||||
|
branch_id:
|
||||||
|
type: integer
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
phone_number:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.ChapaPaymentUrlResponse:
|
||||||
|
properties:
|
||||||
|
payment_url:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.ChapaPaymentUrlResponseWrapper:
|
||||||
|
properties:
|
||||||
|
data: {}
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
status_code:
|
||||||
|
type: integer
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
domain.ChapaSupportedBank:
|
domain.ChapaSupportedBank:
|
||||||
properties:
|
properties:
|
||||||
acct_length:
|
acct_length:
|
||||||
|
|
@ -168,6 +194,31 @@ definitions:
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
domain.ChapaTransactionType:
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
domain.ChapaWithdrawRequest:
|
||||||
|
properties:
|
||||||
|
account_name:
|
||||||
|
type: string
|
||||||
|
account_number:
|
||||||
|
type: string
|
||||||
|
amount:
|
||||||
|
type: integer
|
||||||
|
bank_code:
|
||||||
|
type: string
|
||||||
|
beneficiary_name:
|
||||||
|
type: string
|
||||||
|
branch_id:
|
||||||
|
type: integer
|
||||||
|
currency:
|
||||||
|
type: string
|
||||||
|
wallet_id:
|
||||||
|
description: add this
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
domain.CreateBetOutcomeReq:
|
domain.CreateBetOutcomeReq:
|
||||||
properties:
|
properties:
|
||||||
event_id:
|
event_id:
|
||||||
|
|
@ -222,7 +273,7 @@ definitions:
|
||||||
domain.InitPaymentRequest:
|
domain.InitPaymentRequest:
|
||||||
properties:
|
properties:
|
||||||
amount:
|
amount:
|
||||||
type: string
|
type: integer
|
||||||
callback_url:
|
callback_url:
|
||||||
type: string
|
type: string
|
||||||
currency:
|
currency:
|
||||||
|
|
@ -408,6 +459,16 @@ definitions:
|
||||||
totalRewardEarned:
|
totalRewardEarned:
|
||||||
type: number
|
type: number
|
||||||
type: object
|
type: object
|
||||||
|
domain.Response:
|
||||||
|
properties:
|
||||||
|
data: {}
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
status_code:
|
||||||
|
type: integer
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
domain.Role:
|
domain.Role:
|
||||||
enum:
|
enum:
|
||||||
- super_admin
|
- super_admin
|
||||||
|
|
@ -555,6 +616,9 @@ definitions:
|
||||||
matchName:
|
matchName:
|
||||||
description: Match or event name
|
description: Match or event name
|
||||||
type: string
|
type: string
|
||||||
|
source:
|
||||||
|
description: bet api provider (bet365, betfair)
|
||||||
|
type: string
|
||||||
sportID:
|
sportID:
|
||||||
description: Sport ID
|
description: Sport ID
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -1721,6 +1785,42 @@ paths:
|
||||||
summary: Receive Chapa webhook
|
summary: Receive Chapa webhook
|
||||||
tags:
|
tags:
|
||||||
- Chapa
|
- Chapa
|
||||||
|
/api/v1/chapa/payments/deposit:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Deposits money into user wallet from user account using Chapa
|
||||||
|
parameters:
|
||||||
|
- description: Deposit request payload
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ChapaDepositRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ChapaPaymentUrlResponseWrapper'
|
||||||
|
"400":
|
||||||
|
description: Invalid request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"422":
|
||||||
|
description: Validation error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Deposit money into user wallet using Chapa
|
||||||
|
tags:
|
||||||
|
- Chapa
|
||||||
/api/v1/chapa/payments/initialize:
|
/api/v1/chapa/payments/initialize:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -1743,6 +1843,27 @@ paths:
|
||||||
summary: Initialize a payment transaction
|
summary: Initialize a payment transaction
|
||||||
tags:
|
tags:
|
||||||
- Chapa
|
- Chapa
|
||||||
|
/api/v1/chapa/payments/verify:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: Webhook Payload
|
||||||
|
in: body
|
||||||
|
name: payload
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ChapaTransactionType'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
summary: Verifies Chapa webhook transaction
|
||||||
|
tags:
|
||||||
|
- Chapa
|
||||||
/api/v1/chapa/payments/verify/{tx_ref}:
|
/api/v1/chapa/payments/verify/{tx_ref}:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
@ -1764,6 +1885,50 @@ paths:
|
||||||
summary: Verify a payment transaction
|
summary: Verify a payment transaction
|
||||||
tags:
|
tags:
|
||||||
- Chapa
|
- Chapa
|
||||||
|
/api/v1/chapa/payments/withdraw:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Initiates a withdrawal transaction using Chapa for the authenticated
|
||||||
|
user.
|
||||||
|
parameters:
|
||||||
|
- description: Chapa Withdraw Request
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.ChapaWithdrawRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Withdrawal requested successfully
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/domain.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Invalid request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"401":
|
||||||
|
description: Unauthorized
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"422":
|
||||||
|
description: Unprocessable Entity
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/domain.Response'
|
||||||
|
summary: Withdraw using Chapa
|
||||||
|
tags:
|
||||||
|
- Chapa
|
||||||
/api/v1/chapa/transfers:
|
/api/v1/chapa/transfers:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ChapaSecret string
|
ChapaSecret string
|
||||||
|
|
@ -8,7 +11,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type InitPaymentRequest struct {
|
type InitPaymentRequest struct {
|
||||||
Amount string `json:"amount"`
|
Amount Currency `json:"amount"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
|
|
@ -149,3 +152,72 @@ type ChapaWebHookPayment struct {
|
||||||
} `json:"customization"`
|
} `json:"customization"`
|
||||||
Meta string `json:"meta"`
|
Meta string `json:"meta"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChapaWithdrawRequest struct {
|
||||||
|
WalletID int64 `json:"wallet_id"` // add this
|
||||||
|
AccountName string `json:"account_name"`
|
||||||
|
AccountNumber string `json:"account_number"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
BeneficiaryName string `json:"beneficiary_name"`
|
||||||
|
BankCode string `json:"bank_code"`
|
||||||
|
BranchID int64 `json:"branch_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaTransferPayload struct {
|
||||||
|
AccountName string
|
||||||
|
AccountNumber string
|
||||||
|
Amount string
|
||||||
|
Currency string
|
||||||
|
BeneficiaryName string
|
||||||
|
TxRef string
|
||||||
|
Reference string
|
||||||
|
BankCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaDepositRequest struct {
|
||||||
|
Amount Currency `json:"amount"`
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
BranchID int64 `json:"branch_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ChapaDepositRequest) Validate() error {
|
||||||
|
if r.Amount <= 0 {
|
||||||
|
return errors.New("amount must be greater than zero")
|
||||||
|
}
|
||||||
|
if r.Currency == "" {
|
||||||
|
return errors.New("currency is required")
|
||||||
|
}
|
||||||
|
if r.PhoneNumber == "" {
|
||||||
|
return errors.New("phone number is required")
|
||||||
|
}
|
||||||
|
if r.BranchID == 0 {
|
||||||
|
return errors.New("branch ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AcceptChapaPaymentRequest struct {
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Currency string `json:"currency"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
|
TxRef string `json:"tx_ref"`
|
||||||
|
CallbackUrl string `json:"callback_url"`
|
||||||
|
ReturnUrl string `json:"return_url"`
|
||||||
|
CustomizationTitle string `json:"customization[title]"`
|
||||||
|
CustomizationDescription string `json:"customization[description]"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaPaymentUrlResponse struct {
|
||||||
|
PaymentURL string `json:"payment_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChapaPaymentUrlResponseWrapper struct {
|
||||||
|
Data ChapaPaymentUrlResponse `json:"data"`
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,11 +48,14 @@ func (m Currency) String() string {
|
||||||
return fmt.Sprintf("$%.2f", x)
|
return fmt.Sprintf("$%.2f", x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResponseWDataFactory[T any] struct {
|
||||||
|
Data T `json:"data"`
|
||||||
|
Response
|
||||||
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Data interface{} `json:"data,omitempty"`
|
Data interface{} `json:"data,omitempty"`
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
StatusCode int `json:"status_code"`
|
StatusCode int `json:"status_code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ type CreateTransaction struct {
|
||||||
PaymentOption PaymentOption
|
PaymentOption PaymentOption
|
||||||
FullName string
|
FullName string
|
||||||
PhoneNumber string
|
PhoneNumber string
|
||||||
// Payment Details for bank
|
|
||||||
BankCode string
|
BankCode string
|
||||||
BeneficiaryName string
|
BeneficiaryName string
|
||||||
AccountName string
|
AccountName string
|
||||||
|
|
|
||||||
98
internal/services/chapa/client.go
Normal file
98
internal/services/chapa/client.go
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
package chapa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChapaClient interface {
|
||||||
|
IssuePayment(ctx context.Context, payload domain.ChapaTransferPayload) (bool, error)
|
||||||
|
InitPayment(ctx context.Context, req domain.InitPaymentRequest) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
BaseURL string
|
||||||
|
SecretKey string
|
||||||
|
HTTPClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(baseURL, secretKey string) *Client {
|
||||||
|
return &Client{
|
||||||
|
BaseURL: baseURL,
|
||||||
|
SecretKey: secretKey,
|
||||||
|
HTTPClient: http.DefaultClient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) IssuePayment(ctx context.Context, payload domain.ChapaTransferPayload) (bool, error) {
|
||||||
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to serialize payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", c.BaseURL+"/transfers", bytes.NewBuffer(payloadBytes))
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to create HTTP request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "Bearer "+c.SecretKey)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := c.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("chapa HTTP request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("chapa error: status %d, body: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// service/chapa_service.go
|
||||||
|
func (c *Client) InitPayment(ctx context.Context, req domain.InitPaymentRequest) (string, error) {
|
||||||
|
payloadBytes, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to serialize payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequestWithContext(ctx, "POST", c.BaseURL+"/transaction/initialize", bytes.NewBuffer(payloadBytes))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to create HTTP request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq.Header.Set("Authorization", "Bearer "+c.SecretKey)
|
||||||
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := c.HTTPClient.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("chapa HTTP request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return "", fmt.Errorf("chapa error: status %d, body: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var response struct {
|
||||||
|
Data struct {
|
||||||
|
CheckoutURL string `json:"checkout_url"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, &response); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse chapa response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.CheckoutURL, nil
|
||||||
|
}
|
||||||
|
|
@ -9,4 +9,6 @@ import (
|
||||||
type ChapaPort interface {
|
type ChapaPort interface {
|
||||||
HandleChapaTransferWebhook(ctx context.Context, req domain.ChapaWebHookTransfer) error
|
HandleChapaTransferWebhook(ctx context.Context, req domain.ChapaWebHookTransfer) error
|
||||||
HandleChapaPaymentWebhook(ctx context.Context, req domain.ChapaWebHookPayment) error
|
HandleChapaPaymentWebhook(ctx context.Context, req domain.ChapaWebHookPayment) error
|
||||||
|
WithdrawUsingChapa(ctx context.Context, userID int64, req domain.ChapaWithdrawRequest) error
|
||||||
|
DepositUsingChapa(ctx context.Context, userID int64, req domain.ChapaDepositRequest) (string, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,22 @@ package chapa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
// "log/slog"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/branch"
|
||||||
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
referralservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/referal"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/transaction"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -19,6 +26,10 @@ type Service struct {
|
||||||
walletStore wallet.WalletStore
|
walletStore wallet.WalletStore
|
||||||
userStore user.UserStore
|
userStore user.UserStore
|
||||||
referralStore referralservice.ReferralStore
|
referralStore referralservice.ReferralStore
|
||||||
|
branchStore branch.BranchStore
|
||||||
|
chapaClient ChapaClient
|
||||||
|
config *config.Config
|
||||||
|
// logger *slog.Logger
|
||||||
store *repository.Store
|
store *repository.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,6 +38,8 @@ func NewService(
|
||||||
walletStore wallet.WalletStore,
|
walletStore wallet.WalletStore,
|
||||||
userStore user.UserStore,
|
userStore user.UserStore,
|
||||||
referralStore referralservice.ReferralStore,
|
referralStore referralservice.ReferralStore,
|
||||||
|
branchStore branch.BranchStore,
|
||||||
|
chapaClient ChapaClient,
|
||||||
store *repository.Store,
|
store *repository.Store,
|
||||||
) *Service {
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
|
|
@ -34,6 +47,8 @@ func NewService(
|
||||||
walletStore: walletStore,
|
walletStore: walletStore,
|
||||||
userStore: userStore,
|
userStore: userStore,
|
||||||
referralStore: referralStore,
|
referralStore: referralStore,
|
||||||
|
branchStore: branchStore,
|
||||||
|
chapaClient: chapaClient,
|
||||||
store: store,
|
store: store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -53,6 +68,9 @@ func (s *Service) HandleChapaTransferWebhook(ctx context.Context, req domain.Cha
|
||||||
|
|
||||||
txn, err := s.transactionStore.GetTransactionByID(ctx, referenceID)
|
txn, err := s.transactionStore.GetTransactionByID(ctx, referenceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return fmt.Errorf("transaction with ID %d not found", referenceID)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if txn.Verified {
|
if txn.Verified {
|
||||||
|
|
@ -93,6 +111,9 @@ func (s *Service) HandleChapaPaymentWebhook(ctx context.Context, req domain.Chap
|
||||||
// 2. Fetch transaction
|
// 2. Fetch transaction
|
||||||
txn, err := s.transactionStore.GetTransactionByID(ctx, referenceID)
|
txn, err := s.transactionStore.GetTransactionByID(ctx, referenceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return fmt.Errorf("transaction with ID %d not found", referenceID)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if txn.Verified {
|
if txn.Verified {
|
||||||
|
|
@ -122,7 +143,7 @@ func (s *Service) HandleChapaPaymentWebhook(ctx context.Context, req domain.Chap
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Check & Create Referral
|
// 7. Check & Create Referral
|
||||||
stats, err := s.referralStore.GetReferralStats(ctx, string(wallet.UserID))
|
stats, err := s.referralStore.GetReferralStats(ctx, strconv.FormatInt(wallet.UserID, 10))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -135,3 +156,162 @@ func (s *Service) HandleChapaPaymentWebhook(ctx context.Context, req domain.Chap
|
||||||
|
|
||||||
return tx.Commit(ctx)
|
return tx.Commit(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) WithdrawUsingChapa(ctx context.Context, userID int64, req domain.ChapaWithdrawRequest) error {
|
||||||
|
_, tx, err := s.store.BeginTx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
// Get the requesting user
|
||||||
|
user, err := s.userStore.GetUserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("user not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
branch, err := s.branchStore.GetBranchByID(ctx, req.BranchID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wallets, err := s.walletStore.GetWalletsByUser(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetWallet *domain.Wallet
|
||||||
|
for _, w := range wallets {
|
||||||
|
if w.ID == req.WalletID {
|
||||||
|
targetWallet = &w
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetWallet == nil {
|
||||||
|
return fmt.Errorf("no wallet found with the specified ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !targetWallet.IsWithdraw || !targetWallet.IsActive {
|
||||||
|
return fmt.Errorf("wallet not eligible for withdrawal")
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetWallet.Balance < domain.Currency(req.Amount) {
|
||||||
|
return fmt.Errorf("insufficient balance")
|
||||||
|
}
|
||||||
|
|
||||||
|
txID := uuid.New().String()
|
||||||
|
|
||||||
|
payload := domain.ChapaTransferPayload{
|
||||||
|
AccountName: req.AccountName,
|
||||||
|
AccountNumber: req.AccountNumber,
|
||||||
|
Amount: strconv.FormatInt(req.Amount, 10),
|
||||||
|
Currency: req.Currency,
|
||||||
|
BeneficiaryName: req.BeneficiaryName,
|
||||||
|
TxRef: txID,
|
||||||
|
Reference: txID,
|
||||||
|
BankCode: req.BankCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, err := s.chapaClient.IssuePayment(ctx, payload)
|
||||||
|
if err != nil || !ok {
|
||||||
|
return fmt.Errorf("chapa transfer failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create transaction using user and wallet info
|
||||||
|
_, err = s.transactionStore.CreateTransaction(ctx, domain.CreateTransaction{
|
||||||
|
Amount: domain.Currency(req.Amount),
|
||||||
|
Type: domain.TransactionType(domain.TRANSACTION_CASHOUT),
|
||||||
|
ReferenceNumber: txID,
|
||||||
|
AccountName: req.AccountName,
|
||||||
|
AccountNumber: req.AccountNumber,
|
||||||
|
BankCode: req.BankCode,
|
||||||
|
BeneficiaryName: req.BeneficiaryName,
|
||||||
|
PaymentOption: domain.PaymentOption(domain.BANK),
|
||||||
|
BranchID: req.BranchID,
|
||||||
|
BranchName: branch.Name,
|
||||||
|
BranchLocation: branch.Location,
|
||||||
|
// CashierID: user.ID,
|
||||||
|
// CashierName: user.FullName,
|
||||||
|
FullName: user.FirstName + " " + user.LastName,
|
||||||
|
PhoneNumber: user.PhoneNumber,
|
||||||
|
CompanyID: branch.CompanyID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newBalance := domain.Currency(req.Amount)
|
||||||
|
err = s.walletStore.UpdateBalance(ctx, targetWallet.ID, newBalance)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update wallet balance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DepositUsingChapa(ctx context.Context, userID int64, req domain.ChapaDepositRequest) (string, error) {
|
||||||
|
_, tx, err := s.store.BeginTx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
|
user, err := s.userStore.GetUserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
branch, err := s.branchStore.GetBranchByID(ctx, req.BranchID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
txID := uuid.New().String()
|
||||||
|
|
||||||
|
_, err = s.transactionStore.CreateTransaction(ctx, domain.CreateTransaction{
|
||||||
|
Amount: req.Amount,
|
||||||
|
Type: domain.TransactionType(domain.TRANSACTION_DEPOSIT),
|
||||||
|
ReferenceNumber: txID,
|
||||||
|
BranchID: req.BranchID,
|
||||||
|
BranchName: branch.Name,
|
||||||
|
BranchLocation: branch.Location,
|
||||||
|
FullName: user.FirstName + " " + user.LastName,
|
||||||
|
PhoneNumber: user.PhoneNumber,
|
||||||
|
CompanyID: branch.CompanyID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch user details for Chapa payment
|
||||||
|
userInfo, err := s.userStore.GetUserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build Chapa InitPaymentRequest (matches Chapa API)
|
||||||
|
paymentReq := domain.InitPaymentRequest{
|
||||||
|
Amount: req.Amount,
|
||||||
|
Currency: req.Currency,
|
||||||
|
Email: userInfo.Email,
|
||||||
|
FirstName: userInfo.FirstName,
|
||||||
|
LastName: userInfo.LastName,
|
||||||
|
TxRef: txID,
|
||||||
|
CallbackURL: s.config.CHAPA_CALLBACK_URL,
|
||||||
|
ReturnURL: s.config.CHAPA_RETURN_URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Chapa to initialize payment
|
||||||
|
paymentURL, err := s.chapaClient.InitPayment(ctx, paymentReq)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit DB transaction
|
||||||
|
if err := tx.Commit(ctx); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return paymentURL, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -299,7 +298,7 @@ func (h *Handler) VerifyChapaPayment(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch txType.Type {
|
switch txType.Type {
|
||||||
case config.ChapaConfig.ChapaTransferType:
|
case "Payout":
|
||||||
var payload domain.ChapaWebHookTransfer
|
var payload domain.ChapaWebHookTransfer
|
||||||
if err := c.BodyParser(&payload); err != nil {
|
if err := c.BodyParser(&payload); err != nil {
|
||||||
return domain.UnProcessableEntityResponse(c)
|
return domain.UnProcessableEntityResponse(c)
|
||||||
|
|
@ -315,7 +314,7 @@ func (h *Handler) VerifyChapaPayment(c *fiber.Ctx) error {
|
||||||
StatusCode: fiber.StatusOK,
|
StatusCode: fiber.StatusOK,
|
||||||
})
|
})
|
||||||
|
|
||||||
case config.ChapaConfig.ChapaPaymentType:
|
case "API":
|
||||||
var payload domain.ChapaWebHookPayment
|
var payload domain.ChapaWebHookPayment
|
||||||
if err := c.BodyParser(&payload); err != nil {
|
if err := c.BodyParser(&payload); err != nil {
|
||||||
return domain.UnProcessableEntityResponse(c)
|
return domain.UnProcessableEntityResponse(c)
|
||||||
|
|
@ -339,3 +338,98 @@ func (h *Handler) VerifyChapaPayment(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithdrawUsingChapa godoc
|
||||||
|
// @Summary Withdraw using Chapa
|
||||||
|
// @Description Initiates a withdrawal transaction using Chapa for the authenticated user.
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body domain.ChapaWithdrawRequest true "Chapa Withdraw Request"
|
||||||
|
// @Success 200 {object} domain.Response{data=string} "Withdrawal requested successfully"
|
||||||
|
// @Failure 400 {object} domain.Response "Invalid request"
|
||||||
|
// @Failure 401 {object} domain.Response "Unauthorized"
|
||||||
|
// @Failure 422 {object} domain.Response "Unprocessable Entity"
|
||||||
|
// @Failure 500 {object} domain.Response "Internal Server Error"
|
||||||
|
// @Router /api/v1/chapa/payments/withdraw [post]
|
||||||
|
func (h *Handler) WithdrawUsingChapa(c *fiber.Ctx) error {
|
||||||
|
var req domain.ChapaWithdrawRequest
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return domain.UnProcessableEntityResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, ok := c.Locals("user_id").(int64)
|
||||||
|
if !ok || userID == 0 {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(domain.Response{
|
||||||
|
Message: "Unauthorized",
|
||||||
|
Success: false,
|
||||||
|
StatusCode: fiber.StatusUnauthorized,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.chapaSvc.WithdrawUsingChapa(c.Context(), userID, req); err != nil {
|
||||||
|
return domain.FiberErrorResponse(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "Withdrawal requested successfully",
|
||||||
|
Success: true,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DepositUsingChapa godoc
|
||||||
|
// @Summary Deposit money into user wallet using Chapa
|
||||||
|
// @Description Deposits money into user wallet from user account using Chapa
|
||||||
|
// @Tags Chapa
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Param payload body domain.ChapaDepositRequest true "Deposit request payload"
|
||||||
|
// @Success 200 {object} domain.ChapaPaymentUrlResponseWrapper
|
||||||
|
// @Failure 400 {object} domain.Response "Invalid request"
|
||||||
|
// @Failure 422 {object} domain.Response "Validation error"
|
||||||
|
// @Failure 500 {object} domain.Response "Internal server error"
|
||||||
|
// @Router /api/v1/chapa/payments/deposit [post]
|
||||||
|
func (h *Handler) DepositUsingChapa(c *fiber.Ctx) error {
|
||||||
|
// Extract user info from token (adjust as per your auth middleware)
|
||||||
|
userID, ok := c.Locals("user_id").(int64)
|
||||||
|
if !ok || userID == 0 {
|
||||||
|
return c.Status(fiber.StatusUnauthorized).JSON(domain.Response{
|
||||||
|
Message: "Unauthorized",
|
||||||
|
Success: false,
|
||||||
|
StatusCode: fiber.StatusUnauthorized,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var req domain.ChapaDepositRequest
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return domain.UnProcessableEntityResponse(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate input in domain/model (you may have a Validate method)
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(domain.Response{
|
||||||
|
Message: err.Error(),
|
||||||
|
Success: false,
|
||||||
|
StatusCode: fiber.StatusBadRequest,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call service to handle the deposit logic and get payment URL
|
||||||
|
paymentUrl, svcErr := h.chapaSvc.DepositUsingChapa(c.Context(), userID, req)
|
||||||
|
if svcErr != nil {
|
||||||
|
return domain.FiberErrorResponse(c, svcErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.ResponseWDataFactory[domain.ChapaPaymentUrlResponse]{
|
||||||
|
Data: domain.ChapaPaymentUrlResponse{
|
||||||
|
PaymentURL: paymentUrl,
|
||||||
|
},
|
||||||
|
Response: domain.Response{
|
||||||
|
Message: "Deposit process started on wallet, fulfill payment using the URL provided",
|
||||||
|
Success: true,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -182,13 +182,16 @@ func (a *App) initAppRoutes() {
|
||||||
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
|
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
|
||||||
|
|
||||||
//Chapa Routes
|
//Chapa Routes
|
||||||
|
group.Post("/chapa/payments/verify", h.VerifyChapaPayment)
|
||||||
|
group.Post("/chapa/payments/withdraw", h.WithdrawUsingChapa)
|
||||||
|
group.Post("/chapa/payments/deposit", h.DepositUsingChapa)
|
||||||
|
|
||||||
group.Post("/chapa/payments/initialize", h.InitializePayment)
|
// group.Post("/chapa/payments/initialize", h.InitializePayment)
|
||||||
group.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction)
|
// group.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction)
|
||||||
group.Post("/chapa/payments/callback", h.ReceiveWebhook)
|
// group.Post("/chapa/payments/callback", h.ReceiveWebhook)
|
||||||
group.Get("/chapa/banks", h.GetBanks)
|
// group.Get("/chapa/banks", h.GetBanks)
|
||||||
group.Post("/chapa/transfers", h.CreateTransfer)
|
// group.Post("/chapa/transfers", h.CreateTransfer)
|
||||||
group.Get("/chapa/transfers/verify/:transfer_ref", h.VerifyTransfer)
|
// group.Get("/chapa/transfers/verify/:transfer_ref", h.VerifyTransfer)
|
||||||
|
|
||||||
//Alea Play Virtual Game Routes
|
//Alea Play Virtual Game Routes
|
||||||
group.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)
|
group.Get("/alea-play/launch", a.authMiddleware, h.LaunchAleaGame)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user