wallet security+log file
This commit is contained in:
parent
49d9dafccb
commit
f2ec267347
|
|
@ -297,6 +297,7 @@ ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERE
|
|||
ALTER TABLE branch_cashiers
|
||||
ADD CONSTRAINT fk_branch_cashiers_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE companies
|
||||
ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id),
|
||||
ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE;
|
||||
|
|
|
|||
391
docs/docs.go
391
docs/docs.go
|
|
@ -306,7 +306,6 @@ const docTemplate = `{
|
|||
},
|
||||
"/api/v1/chapa/banks": {
|
||||
"get": {
|
||||
"description": "Fetch all supported banks from Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -316,46 +315,42 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Get list of banks",
|
||||
"summary": "fetches chapa supported banks",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ChapaSupportedBanksResponse"
|
||||
"$ref": "#/definitions/domain.ChapaSupportedBanksResponseWrapper"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/callback": {
|
||||
"post": {
|
||||
"description": "Endpoint to receive webhook payloads from Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Receive Chapa webhook",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Webhook Payload (dynamic)",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "object"
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Unprocessable Entity",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -363,11 +358,6 @@ const docTemplate = `{
|
|||
},
|
||||
"/api/v1/chapa/payments/deposit": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Deposits money into user wallet from user account using Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
|
|
@ -418,40 +408,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/initialize": {
|
||||
"post": {
|
||||
"description": "Initiate a payment through Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Initialize a payment transaction",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Payment initialization request",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.InitPaymentRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.InitPaymentResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/verify": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
|
|
@ -485,38 +441,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/verify/{tx_ref}": {
|
||||
"get": {
|
||||
"description": "Verify the transaction status from Chapa using tx_ref",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Verify a payment transaction",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Transaction Reference",
|
||||
"name": "tx_ref",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.VerifyTransactionResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/withdraw": {
|
||||
"post": {
|
||||
"description": "Initiates a withdrawal transaction using Chapa for the authenticated user.",
|
||||
|
|
@ -587,72 +511,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/transfers": {
|
||||
"post": {
|
||||
"description": "Initiate a transfer request via Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Create a money transfer",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Transfer request body",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.TransferRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.CreateTransferResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/transfers/verify/{transfer_ref}": {
|
||||
"get": {
|
||||
"description": "Check the status of a money transfer via reference",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Verify a transfer",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Transfer Reference",
|
||||
"name": "transfer_ref",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.VerifyTransferResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/virtual-games/recommendations/{userID}": {
|
||||
"get": {
|
||||
"description": "Returns a list of recommended virtual games for a specific user",
|
||||
|
|
@ -4666,17 +4524,18 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ChapaSupportedBanksResponse": {
|
||||
"domain.ChapaSupportedBanksResponseWrapper": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ChapaSupportedBank"
|
||||
}
|
||||
},
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status_code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -4770,76 +4629,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateTransferResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.TransferData"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.InitPaymentData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"checkout_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"tx_ref": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.InitPaymentRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"callback_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"return_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"tx_ref": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.InitPaymentResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.InitPaymentData"
|
||||
},
|
||||
"message": {
|
||||
"description": "e.g., \"Payment initialized\"",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "\"success\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.Odd": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5167,86 +4956,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.TransactionData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"tx_ref": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.TransferData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.TransferRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"account_number": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "string"
|
||||
},
|
||||
"bank_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string"
|
||||
},
|
||||
"recipient_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.TransferVerificationData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"account_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"bank_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.UpcomingEvent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5353,34 +5062,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.VerifyTransactionResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.TransactionData"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.VerifyTransferResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.TransferVerificationData"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.VirtualGame": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -298,7 +298,6 @@
|
|||
},
|
||||
"/api/v1/chapa/banks": {
|
||||
"get": {
|
||||
"description": "Fetch all supported banks from Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
|
|
@ -308,46 +307,42 @@
|
|||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Get list of banks",
|
||||
"summary": "fetches chapa supported banks",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ChapaSupportedBanksResponse"
|
||||
"$ref": "#/definitions/domain.ChapaSupportedBanksResponseWrapper"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/callback": {
|
||||
"post": {
|
||||
"description": "Endpoint to receive webhook payloads from Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Receive Chapa webhook",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Webhook Payload (dynamic)",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "object"
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ok",
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Unprocessable Entity",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -355,11 +350,6 @@
|
|||
},
|
||||
"/api/v1/chapa/payments/deposit": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Deposits money into user wallet from user account using Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
|
|
@ -410,40 +400,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/initialize": {
|
||||
"post": {
|
||||
"description": "Initiate a payment through Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Initialize a payment transaction",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Payment initialization request",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.InitPaymentRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.InitPaymentResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/verify": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
|
|
@ -477,38 +433,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/verify/{tx_ref}": {
|
||||
"get": {
|
||||
"description": "Verify the transaction status from Chapa using tx_ref",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Verify a payment transaction",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Transaction Reference",
|
||||
"name": "tx_ref",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.VerifyTransactionResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/payments/withdraw": {
|
||||
"post": {
|
||||
"description": "Initiates a withdrawal transaction using Chapa for the authenticated user.",
|
||||
|
|
@ -579,72 +503,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/transfers": {
|
||||
"post": {
|
||||
"description": "Initiate a transfer request via Chapa",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Create a money transfer",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Transfer request body",
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.TransferRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.CreateTransferResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/chapa/transfers/verify/{transfer_ref}": {
|
||||
"get": {
|
||||
"description": "Check the status of a money transfer via reference",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chapa"
|
||||
],
|
||||
"summary": "Verify a transfer",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Transfer Reference",
|
||||
"name": "transfer_ref",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.VerifyTransferResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/virtual-games/recommendations/{userID}": {
|
||||
"get": {
|
||||
"description": "Returns a list of recommended virtual games for a specific user",
|
||||
|
|
@ -4658,17 +4516,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.ChapaSupportedBanksResponse": {
|
||||
"domain.ChapaSupportedBanksResponseWrapper": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.ChapaSupportedBank"
|
||||
}
|
||||
},
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status_code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -4762,76 +4621,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateTransferResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.TransferData"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.InitPaymentData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"checkout_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"tx_ref": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.InitPaymentRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"callback_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"last_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"return_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"tx_ref": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.InitPaymentResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.InitPaymentData"
|
||||
},
|
||||
"message": {
|
||||
"description": "e.g., \"Payment initialized\"",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "\"success\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.Odd": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5159,86 +4948,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.TransactionData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"tx_ref": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.TransferData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.TransferRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"account_number": {
|
||||
"type": "string"
|
||||
},
|
||||
"amount": {
|
||||
"type": "string"
|
||||
},
|
||||
"bank_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string"
|
||||
},
|
||||
"recipient_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.TransferVerificationData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"account_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"bank_code": {
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.UpcomingEvent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -5345,34 +5054,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"domain.VerifyTransactionResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.TransactionData"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.VerifyTransferResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/domain.TransferVerificationData"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.VirtualGame": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -185,14 +185,15 @@ definitions:
|
|||
updated_at:
|
||||
type: string
|
||||
type: object
|
||||
domain.ChapaSupportedBanksResponse:
|
||||
domain.ChapaSupportedBanksResponseWrapper:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/domain.ChapaSupportedBank'
|
||||
type: array
|
||||
data: {}
|
||||
message:
|
||||
type: string
|
||||
status_code:
|
||||
type: integer
|
||||
success:
|
||||
type: boolean
|
||||
type: object
|
||||
domain.ChapaTransactionType:
|
||||
properties:
|
||||
|
|
@ -254,52 +255,6 @@ definitions:
|
|||
- $ref: '#/definitions/domain.OutcomeStatus'
|
||||
example: 1
|
||||
type: object
|
||||
domain.CreateTransferResponse:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/domain.TransferData'
|
||||
message:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
domain.InitPaymentData:
|
||||
properties:
|
||||
checkout_url:
|
||||
type: string
|
||||
tx_ref:
|
||||
type: string
|
||||
type: object
|
||||
domain.InitPaymentRequest:
|
||||
properties:
|
||||
amount:
|
||||
type: integer
|
||||
callback_url:
|
||||
type: string
|
||||
currency:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
first_name:
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
return_url:
|
||||
type: string
|
||||
tx_ref:
|
||||
type: string
|
||||
type: object
|
||||
domain.InitPaymentResponse:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/domain.InitPaymentData'
|
||||
message:
|
||||
description: e.g., "Payment initialized"
|
||||
type: string
|
||||
status:
|
||||
description: '"success"'
|
||||
type: string
|
||||
type: object
|
||||
domain.Odd:
|
||||
properties:
|
||||
category:
|
||||
|
|
@ -529,58 +484,6 @@ definitions:
|
|||
example: 1
|
||||
type: integer
|
||||
type: object
|
||||
domain.TransactionData:
|
||||
properties:
|
||||
amount:
|
||||
type: string
|
||||
currency:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
tx_ref:
|
||||
type: string
|
||||
type: object
|
||||
domain.TransferData:
|
||||
properties:
|
||||
amount:
|
||||
type: string
|
||||
currency:
|
||||
type: string
|
||||
reference:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
domain.TransferRequest:
|
||||
properties:
|
||||
account_number:
|
||||
type: string
|
||||
amount:
|
||||
type: string
|
||||
bank_code:
|
||||
type: string
|
||||
currency:
|
||||
type: string
|
||||
reason:
|
||||
type: string
|
||||
recipient_name:
|
||||
type: string
|
||||
reference:
|
||||
type: string
|
||||
type: object
|
||||
domain.TransferVerificationData:
|
||||
properties:
|
||||
account_name:
|
||||
type: string
|
||||
bank_code:
|
||||
type: string
|
||||
reference:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
domain.UpcomingEvent:
|
||||
properties:
|
||||
awayKitImage:
|
||||
|
|
@ -659,24 +562,6 @@ definitions:
|
|||
description: Veli's user identifier
|
||||
type: string
|
||||
type: object
|
||||
domain.VerifyTransactionResponse:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/domain.TransactionData'
|
||||
message:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
domain.VerifyTransferResponse:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/definitions/domain.TransferVerificationData'
|
||||
message:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
type: object
|
||||
domain.VirtualGame:
|
||||
properties:
|
||||
category:
|
||||
|
|
@ -1752,37 +1637,34 @@ paths:
|
|||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Fetch all supported banks from Chapa
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ChapaSupportedBanksResponse'
|
||||
summary: Get list of banks
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/payments/callback:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Endpoint to receive webhook payloads from Chapa
|
||||
parameters:
|
||||
- description: Webhook Payload (dynamic)
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: ok
|
||||
$ref: '#/definitions/domain.ChapaSupportedBanksResponseWrapper'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
type: string
|
||||
summary: Receive Chapa webhook
|
||||
$ref: '#/definitions/domain.Response'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/domain.Response'
|
||||
"404":
|
||||
description: Not Found
|
||||
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: fetches chapa supported banks
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/payments/deposit:
|
||||
|
|
@ -1816,33 +1698,9 @@ paths:
|
|||
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:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Initiate a payment through Chapa
|
||||
parameters:
|
||||
- description: Payment initialization request
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.InitPaymentRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.InitPaymentResponse'
|
||||
summary: Initialize a payment transaction
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/payments/verify:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -1864,27 +1722,6 @@ paths:
|
|||
summary: Verifies Chapa webhook transaction
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/payments/verify/{tx_ref}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Verify the transaction status from Chapa using tx_ref
|
||||
parameters:
|
||||
- description: Transaction Reference
|
||||
in: path
|
||||
name: tx_ref
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.VerifyTransactionResponse'
|
||||
summary: Verify a payment transaction
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/payments/withdraw:
|
||||
post:
|
||||
consumes:
|
||||
|
|
@ -1929,49 +1766,6 @@ paths:
|
|||
summary: Withdraw using Chapa
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/transfers:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Initiate a transfer request via Chapa
|
||||
parameters:
|
||||
- description: Transfer request body
|
||||
in: body
|
||||
name: payload
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/domain.TransferRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.CreateTransferResponse'
|
||||
summary: Create a money transfer
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/chapa/transfers/verify/{transfer_ref}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Check the status of a money transfer via reference
|
||||
parameters:
|
||||
- description: Transfer Reference
|
||||
in: path
|
||||
name: transfer_ref
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.VerifyTransferResponse'
|
||||
summary: Verify a transfer
|
||||
tags:
|
||||
- Chapa
|
||||
/api/v1/virtual-games/recommendations/{userID}:
|
||||
get:
|
||||
consumes:
|
||||
|
|
|
|||
19
go.mod
19
go.mod
|
|
@ -9,28 +9,28 @@ require (
|
|||
github.com/gofiber/fiber/v2 v2.52.6
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/jackc/pgx/v5 v5.7.4
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
// github.com/stretchr/testify v1.10.0
|
||||
github.com/swaggo/fiber-swagger v1.3.0
|
||||
github.com/swaggo/swag v1.16.4
|
||||
github.com/valyala/fasthttp v1.59.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
)
|
||||
|
||||
require github.com/gorilla/websocket v1.5.3 // indirect
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
// github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
// github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
// github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
|
|
@ -38,7 +38,6 @@ require (
|
|||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
|
|
@ -50,13 +49,13 @@ require (
|
|||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.59.0
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -120,6 +120,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
|
|
|
|||
|
|
@ -221,3 +221,8 @@ type ChapaPaymentUrlResponseWrapper struct {
|
|||
Data ChapaPaymentUrlResponse `json:"data"`
|
||||
Response
|
||||
}
|
||||
|
||||
type ChapaSupportedBanksResponseWrapper struct {
|
||||
Data []ChapaSupportedBank `json:"data"`
|
||||
Response
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,13 +18,24 @@ var Environment = map[string]string{
|
|||
|
||||
func NewLogger(env string, lvl slog.Level) *slog.Logger {
|
||||
var logHandler slog.Handler
|
||||
|
||||
err := os.MkdirAll("logs", os.ModePerm)
|
||||
if err != nil {
|
||||
panic("Failed to create log directory: " + err.Error())
|
||||
}
|
||||
|
||||
file, err := os.OpenFile("logs/app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
panic("Failed to open log file: " + err.Error())
|
||||
}
|
||||
|
||||
switch env {
|
||||
case "development":
|
||||
logHandler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
||||
logHandler = slog.NewTextHandler(file, &slog.HandlerOptions{
|
||||
Level: lvl,
|
||||
})
|
||||
default:
|
||||
logHandler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
logHandler = slog.NewJSONHandler(file, &slog.HandlerOptions{
|
||||
Level: lvl,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ import (
|
|||
type ChapaClient interface {
|
||||
IssuePayment(ctx context.Context, payload domain.ChapaTransferPayload) (bool, error)
|
||||
InitPayment(ctx context.Context, req domain.InitPaymentRequest) (string, error)
|
||||
FetchBanks() ([]domain.ChapaSupportedBank, error)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
SecretKey string
|
||||
HTTPClient *http.Client
|
||||
UserAgent string
|
||||
}
|
||||
|
||||
func NewClient(baseURL, secretKey string) *Client {
|
||||
|
|
@ -27,6 +29,7 @@ func NewClient(baseURL, secretKey string) *Client {
|
|||
BaseURL: baseURL,
|
||||
SecretKey: secretKey,
|
||||
HTTPClient: http.DefaultClient,
|
||||
UserAgent: "FortuneBet/1.0",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,3 +99,28 @@ func (c *Client) InitPayment(ctx context.Context, req domain.InitPaymentRequest)
|
|||
|
||||
return response.Data.CheckoutURL, nil
|
||||
}
|
||||
|
||||
func (c *Client) FetchBanks() ([]domain.ChapaSupportedBank, error) {
|
||||
req, _ := http.NewRequest("GET", c.BaseURL+"/banks", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+c.SecretKey)
|
||||
fmt.Printf("\n\nbase URL is: %s\n\n", c.BaseURL)
|
||||
|
||||
res, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var resp struct {
|
||||
Message string `json:"message"`
|
||||
Data []domain.ChapaSupportedBank `json:"data"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Printf("\n\nclient fetched banks: %+v\n\n", resp.Data)
|
||||
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ type ChapaPort interface {
|
|||
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)
|
||||
GetSupportedBanks() ([]domain.ChapaSupportedBank, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ func (s *Service) DepositUsingChapa(ctx context.Context, userID int64, req domai
|
|||
return "", err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
|
||||
user, err := s.userStore.GetUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -315,3 +315,37 @@ func (s *Service) DepositUsingChapa(ctx context.Context, userID int64, req domai
|
|||
|
||||
return paymentURL, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetSupportedBanks() ([]domain.ChapaSupportedBank, error) {
|
||||
banks, err := s.chapaClient.FetchBanks()
|
||||
fmt.Printf("\n\nfetched banks: %+v\n\n", banks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add formatting logic (same as in original controller)
|
||||
for i := range banks {
|
||||
if banks[i].IsMobilemoney != nil && *(banks[i].IsMobilemoney) == 1 {
|
||||
banks[i].AcctNumberRegex = "/^09[0-9]{8}$/"
|
||||
banks[i].ExampleValue = "0952097177"
|
||||
} else {
|
||||
switch banks[i].AcctLength {
|
||||
case 8:
|
||||
banks[i].ExampleValue = "16967608"
|
||||
case 13:
|
||||
banks[i].ExampleValue = "1000222215735"
|
||||
case 14:
|
||||
banks[i].ExampleValue = "01320089280800"
|
||||
case 16:
|
||||
banks[i].ExampleValue = "1000222215735123"
|
||||
}
|
||||
banks[i].AcctNumberRegex = formatRegex(banks[i].AcctLength)
|
||||
}
|
||||
}
|
||||
|
||||
return banks, nil
|
||||
}
|
||||
|
||||
func formatRegex(length int) string {
|
||||
return fmt.Sprintf("/^[0-9]{%d}$/", length)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
// NFL evaluations
|
||||
func evaluateNFLMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
func EvaluateNFLMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
|
|
@ -25,7 +25,7 @@ func evaluateNFLMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away in
|
|||
}
|
||||
}
|
||||
|
||||
func evaluateNFLSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
func EvaluateNFLSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
|
|
@ -56,7 +56,7 @@ func evaluateNFLSpread(outcome domain.BetOutcome, score struct{ Home, Away int }
|
|||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
|
||||
func evaluateNFLTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
func EvaluateNFLTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalPoints := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
if err != nil {
|
||||
|
|
@ -81,8 +81,8 @@ func evaluateNFLTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// evaluateRugbyMoneyLine evaluates Rugby money line bets
|
||||
func evaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateRugbyMoneyLine Evaluates Rugby money line bets
|
||||
func EvaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
|
|
@ -99,8 +99,8 @@ func evaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
}
|
||||
}
|
||||
|
||||
// evaluateRugbySpread evaluates Rugby spread bets
|
||||
func evaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateRugbySpread Evaluates Rugby spread bets
|
||||
func EvaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
|
|
@ -131,8 +131,8 @@ func evaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int
|
|||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
|
||||
// evaluateRugbyTotalPoints evaluates Rugby total points bets
|
||||
func evaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateRugbyTotalPoints Evaluates Rugby total points bets
|
||||
func EvaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalPoints := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
if err != nil {
|
||||
|
|
@ -157,8 +157,8 @@ func evaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Awa
|
|||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// evaluateBaseballMoneyLine evaluates Baseball money line bets
|
||||
func evaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateBaseballMoneyLine Evaluates Baseball money line bets
|
||||
func EvaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
if score.Home > score.Away {
|
||||
|
|
@ -175,8 +175,8 @@ func evaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Aw
|
|||
}
|
||||
}
|
||||
|
||||
// evaluateBaseballSpread evaluates Baseball spread bets
|
||||
func evaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateBaseballSpread Evaluates Baseball spread bets
|
||||
func EvaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
|
||||
if err != nil {
|
||||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap)
|
||||
|
|
@ -207,8 +207,8 @@ func evaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away
|
|||
return domain.OUTCOME_STATUS_VOID, nil
|
||||
}
|
||||
|
||||
// evaluateBaseballTotalRuns evaluates Baseball total runs bets
|
||||
func evaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
// EvaluateBaseballTotalRuns Evaluates Baseball total runs bets
|
||||
func EvaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
totalRuns := float64(score.Home + score.Away)
|
||||
threshold, err := strconv.ParseFloat(outcome.OddName, 64)
|
||||
if err != nil {
|
||||
|
|
@ -233,7 +233,7 @@ func evaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Aw
|
|||
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
|
||||
}
|
||||
|
||||
// evaluateBaseballFirstInning evaluates Baseball first inning bets
|
||||
// EvaluateBaseballFirstInning Evaluates Baseball first inning bets
|
||||
func EvaluateBaseballFirstInning(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
|
|
@ -256,7 +256,7 @@ func EvaluateBaseballFirstInning(outcome domain.BetOutcome, score struct{ Home,
|
|||
}
|
||||
}
|
||||
|
||||
// evaluateBaseballFirst5Innings evaluates Baseball first 5 innings bets
|
||||
// EvaluateBaseballFirst5Innings Evaluates Baseball first 5 innings bets
|
||||
func EvaluateBaseballFirst5Innings(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
|
||||
switch outcome.OddHeader {
|
||||
case "1":
|
||||
|
|
|
|||
|
|
@ -1,287 +1,288 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
// "bytes"
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "io"
|
||||
// "net/http"
|
||||
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// GetBanks godoc
|
||||
// @Summary Get list of banks
|
||||
// @Description Fetch all supported banks from Chapa
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.ChapaSupportedBanksResponse
|
||||
// @Router /api/v1/chapa/banks [get]
|
||||
func (h *Handler) GetBanks(c *fiber.Ctx) error {
|
||||
httpReq, err := http.NewRequest("GET", h.Cfg.CHAPA_BASE_URL+"/banks", nil)
|
||||
// log.Printf("\n\nbase url is: %v\n\n", h.Cfg.CHAPA_BASE_URL)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{"error": "Failed to create request", "details": err.Error()})
|
||||
}
|
||||
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||
// // GetBanks godoc
|
||||
// // @Summary Get list of banks
|
||||
// // @Description Fetch all supported banks from Chapa
|
||||
// // @Tags Chapa
|
||||
// // @Accept json
|
||||
// // @Produce json
|
||||
// // @Success 200 {object} domain.ChapaSupportedBanksResponse
|
||||
// // @Router /api/v1/chapa/banks [get]
|
||||
// func (h *Handler) GetBanks(c *fiber.Ctx) error {
|
||||
// httpReq, err := http.NewRequest("GET", h.Cfg.CHAPA_BASE_URL+"/banks", nil)
|
||||
// // log.Printf("\n\nbase url is: %v\n\n", h.Cfg.CHAPA_BASE_URL)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{"error": "Failed to create request", "details": err.Error()})
|
||||
// }
|
||||
// httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||
|
||||
resp, err := http.DefaultClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{"error": "Failed to fetch banks", "details": err.Error()})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// resp, err := http.DefaultClient.Do(httpReq)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{"error": "Failed to fetch banks", "details": err.Error()})
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{"error": "Failed to read response", "details": err.Error()})
|
||||
}
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{"error": "Failed to read response", "details": err.Error()})
|
||||
// }
|
||||
|
||||
return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||
}
|
||||
// return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||
// }
|
||||
|
||||
// InitializePayment godoc
|
||||
// @Summary Initialize a payment transaction
|
||||
// @Description Initiate a payment through Chapa
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param payload body domain.InitPaymentRequest true "Payment initialization request"
|
||||
// @Success 200 {object} domain.InitPaymentResponse
|
||||
// @Router /api/v1/chapa/payments/initialize [post]
|
||||
func (h *Handler) InitializePayment(c *fiber.Ctx) error {
|
||||
var req InitPaymentRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "Invalid request body",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// // InitializePayment godoc
|
||||
// // @Summary Initialize a payment transaction
|
||||
// // @Description Initiate a payment through Chapa
|
||||
// // @Tags Chapa
|
||||
// // @Accept json
|
||||
// // @Produce json
|
||||
// // @Param payload body domain.InitPaymentRequest true "Payment initialization request"
|
||||
// // @Success 200 {object} domain.InitPaymentResponse
|
||||
// // @Router /api/v1/chapa/payments/initialize [post]
|
||||
// func (h *Handler) InitializePayment(c *fiber.Ctx) error {
|
||||
// var req InitPaymentRequest
|
||||
// if err := c.BodyParser(&req); err != nil {
|
||||
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
// "error": "Invalid request body",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
// Generate and assign a unique transaction reference
|
||||
req.TxRef = uuid.New().String()
|
||||
// // Generate and assign a unique transaction reference
|
||||
// req.TxRef = uuid.New().String()
|
||||
|
||||
payload, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to serialize request",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// payload, err := json.Marshal(req)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{
|
||||
// "error": "Failed to serialize request",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transaction/initialize", bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to create request",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
// httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transaction/initialize", bytes.NewBuffer(payload))
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{
|
||||
// "error": "Failed to create request",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
// httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||
// httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to initialize payment",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// resp, err := http.DefaultClient.Do(httpReq)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{
|
||||
// "error": "Failed to initialize payment",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to read response",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{
|
||||
// "error": "Failed to read response",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||
}
|
||||
// return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||
// }
|
||||
|
||||
// VerifyTransaction godoc
|
||||
// @Summary Verify a payment transaction
|
||||
// @Description Verify the transaction status from Chapa using tx_ref
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param tx_ref path string true "Transaction Reference"
|
||||
// @Success 200 {object} domain.VerifyTransactionResponse
|
||||
// @Router /api/v1/chapa/payments/verify/{tx_ref} [get]
|
||||
func (h *Handler) VerifyTransaction(c *fiber.Ctx) error {
|
||||
txRef := c.Params("tx_ref")
|
||||
if txRef == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "Missing transaction reference",
|
||||
})
|
||||
}
|
||||
// // VerifyTransaction godoc
|
||||
// // @Summary Verify a payment transaction
|
||||
// // @Description Verify the transaction status from Chapa using tx_ref
|
||||
// // @Tags Chapa
|
||||
// // @Accept json
|
||||
// // @Produce json
|
||||
// // @Param tx_ref path string true "Transaction Reference"
|
||||
// // @Success 200 {object} domain.VerifyTransactionResponse
|
||||
// // @Router /api/v1/chapa/payments/verify/{tx_ref} [get]
|
||||
// func (h *Handler) VerifyTransaction(c *fiber.Ctx) error {
|
||||
// txRef := c.Params("tx_ref")
|
||||
// if txRef == "" {
|
||||
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
// "error": "Missing transaction reference",
|
||||
// })
|
||||
// }
|
||||
|
||||
url := fmt.Sprintf("%s/transaction/verify/%s", h.Cfg.CHAPA_BASE_URL, txRef)
|
||||
// url := fmt.Sprintf("%s/transaction/verify/%s", h.Cfg.CHAPA_BASE_URL, txRef)
|
||||
|
||||
httpReq, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to create request",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||
// httpReq, err := http.NewRequest("GET", url, nil)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{
|
||||
// "error": "Failed to create request",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
// httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||
|
||||
resp, err := http.DefaultClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to verify transaction",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// resp, err := http.DefaultClient.Do(httpReq)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{
|
||||
// "error": "Failed to verify transaction",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return c.Status(500).JSON(fiber.Map{
|
||||
"error": "Failed to read response",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// return c.Status(500).JSON(fiber.Map{
|
||||
// "error": "Failed to read response",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||
}
|
||||
// return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||
// }
|
||||
|
||||
// ReceiveWebhook godoc
|
||||
// @Summary Receive Chapa webhook
|
||||
// @Description Endpoint to receive webhook payloads from Chapa
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param payload body object true "Webhook Payload (dynamic)"
|
||||
// @Success 200 {string} string "ok"
|
||||
// @Router /api/v1/chapa/payments/callback [post]
|
||||
func (h *Handler) ReceiveWebhook(c *fiber.Ctx) error {
|
||||
var payload map[string]interface{}
|
||||
if err := c.BodyParser(&payload); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "Invalid webhook data",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// // ReceiveWebhook godoc
|
||||
// // @Summary Receive Chapa webhook
|
||||
// // @Description Endpoint to receive webhook payloads from Chapa
|
||||
// // @Tags Chapa
|
||||
// // @Accept json
|
||||
// // @Produce json
|
||||
// // @Param payload body object true "Webhook Payload (dynamic)"
|
||||
// // @Success 200 {string} string "ok"
|
||||
// // @Router /api/v1/chapa/payments/callback [post]
|
||||
// func (h *Handler) ReceiveWebhook(c *fiber.Ctx) error {
|
||||
// var payload map[string]interface{}
|
||||
// if err := c.BodyParser(&payload); err != nil {
|
||||
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
// "error": "Invalid webhook data",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
h.logger.Info("Chapa webhook received", "payload", payload)
|
||||
// h.logger.Info("Chapa webhook received", "payload", payload)
|
||||
|
||||
// Optional: you can verify tx_ref here again if needed
|
||||
// // Optional: you can verify tx_ref here again if needed
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
}
|
||||
// return c.SendStatus(fiber.StatusOK)
|
||||
// }
|
||||
|
||||
// CreateTransfer godoc
|
||||
// @Summary Create a money transfer
|
||||
// @Description Initiate a transfer request via Chapa
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param payload body domain.TransferRequest true "Transfer request body"
|
||||
// @Success 200 {object} domain.CreateTransferResponse
|
||||
// @Router /api/v1/chapa/transfers [post]
|
||||
func (h *Handler) CreateTransfer(c *fiber.Ctx) error {
|
||||
var req TransferRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "Invalid request",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// // CreateTransfer godoc
|
||||
// // @Summary Create a money transfer
|
||||
// // @Description Initiate a transfer request via Chapa
|
||||
// // @Tags Chapa
|
||||
// // @Accept json
|
||||
// // @Produce json
|
||||
// // @Param payload body domain.TransferRequest true "Transfer request body"
|
||||
// // @Success 200 {object} domain.CreateTransferResponse
|
||||
// // @Router /api/v1/chapa/transfers [post]
|
||||
// func (h *Handler) CreateTransfer(c *fiber.Ctx) error {
|
||||
// var req TransferRequest
|
||||
// if err := c.BodyParser(&req); err != nil {
|
||||
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
// "error": "Invalid request",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
// Inject unique transaction reference
|
||||
req.Reference = uuid.New().String()
|
||||
// // Inject unique transaction reference
|
||||
// req.Reference = uuid.New().String()
|
||||
|
||||
payload, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Failed to serialize request",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// payload, err := json.Marshal(req)
|
||||
// if err != nil {
|
||||
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
// "error": "Failed to serialize request",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transfers", bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Failed to create HTTP request",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transfers", bytes.NewBuffer(payload))
|
||||
// if err != nil {
|
||||
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
// "error": "Failed to create HTTP request",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
// httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||
// httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Transfer request failed",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// resp, err := http.DefaultClient.Do(httpReq)
|
||||
// if err != nil {
|
||||
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
// "error": "Transfer request failed",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Failed to read response",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
// "error": "Failed to read response",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||
}
|
||||
// return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||
// }
|
||||
|
||||
// VerifyTransfer godoc
|
||||
// @Summary Verify a transfer
|
||||
// @Description Check the status of a money transfer via reference
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param transfer_ref path string true "Transfer Reference"
|
||||
// @Success 200 {object} domain.VerifyTransferResponse
|
||||
// @Router /api/v1/chapa/transfers/verify/{transfer_ref} [get]
|
||||
func (h *Handler) VerifyTransfer(c *fiber.Ctx) error {
|
||||
transferRef := c.Params("transfer_ref")
|
||||
if transferRef == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "Missing transfer reference in URL",
|
||||
})
|
||||
}
|
||||
// // VerifyTransfer godoc
|
||||
// // @Summary Verify a transfer
|
||||
// // @Description Check the status of a money transfer via reference
|
||||
// // @Tags Chapa
|
||||
// // @Accept json
|
||||
// // @Produce json
|
||||
// // @Param transfer_ref path string true "Transfer Reference"
|
||||
// // @Success 200 {object} domain.VerifyTransferResponse
|
||||
// // @Router /api/v1/chapa/transfers/verify/{transfer_ref} [get]
|
||||
// func (h *Handler) VerifyTransfer(c *fiber.Ctx) error {
|
||||
// transferRef := c.Params("transfer_ref")
|
||||
// if transferRef == "" {
|
||||
// return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
// "error": "Missing transfer reference in URL",
|
||||
// })
|
||||
// }
|
||||
|
||||
url := fmt.Sprintf("%s/transfers/verify/%s", h.Cfg.CHAPA_BASE_URL, transferRef)
|
||||
// url := fmt.Sprintf("%s/transfers/verify/%s", h.Cfg.CHAPA_BASE_URL, transferRef)
|
||||
|
||||
httpReq, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Failed to create HTTP request",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// httpReq, err := http.NewRequest("GET", url, nil)
|
||||
// if err != nil {
|
||||
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
// "error": "Failed to create HTTP request",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||
// httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
|
||||
|
||||
resp, err := http.DefaultClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Verification request failed",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// resp, err := http.DefaultClient.Do(httpReq)
|
||||
// if err != nil {
|
||||
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
// "error": "Verification request failed",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Failed to read response body",
|
||||
"details": err.Error(),
|
||||
})
|
||||
}
|
||||
// body, err := io.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
// "error": "Failed to read response body",
|
||||
// "details": err.Error(),
|
||||
// })
|
||||
// }
|
||||
|
||||
return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||
}
|
||||
// return c.Status(resp.StatusCode).Type("json").Send(body)
|
||||
// }
|
||||
|
||||
// VerifyChapaPayment godoc
|
||||
// @Summary Verifies Chapa webhook transaction
|
||||
|
|
@ -384,7 +385,6 @@ func (h *Handler) WithdrawUsingChapa(c *fiber.Ctx) error {
|
|||
// @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"
|
||||
|
|
@ -407,7 +407,7 @@ func (h *Handler) DepositUsingChapa(c *fiber.Ctx) error {
|
|||
return domain.UnProcessableEntityResponse(c)
|
||||
}
|
||||
|
||||
// Validate input in domain/model (you may have a Validate method)
|
||||
// Validate input in domain/domain (you may have a Validate method)
|
||||
if err := req.Validate(); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.Response{
|
||||
Message: err.Error(),
|
||||
|
|
@ -433,3 +433,32 @@ func (h *Handler) DepositUsingChapa(c *fiber.Ctx) error {
|
|||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ReadChapaBanks godoc
|
||||
// @Summary fetches chapa supported banks
|
||||
// @Tags Chapa
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} domain.ChapaSupportedBanksResponseWrapper
|
||||
// @Failure 400,401,404,422,500 {object} domain.Response
|
||||
// @Router /api/v1/chapa/banks [get]
|
||||
func (h *Handler) ReadChapaBanks(c *fiber.Ctx) error {
|
||||
banks, err := h.chapaSvc.GetSupportedBanks()
|
||||
fmt.Printf("\n\nhandler fetched banks: %+v\n\n", banks)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.Response{
|
||||
Message: "Internal server error",
|
||||
Success: false,
|
||||
StatusCode: fiber.StatusInternalServerError,
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.ResponseWDataFactory[[]domain.ChapaSupportedBank]{
|
||||
Data: banks,
|
||||
Response: domain.Response{
|
||||
Message: "read successful on chapa supported banks",
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
131
internal/web_server/handlers/read_chapa_banks_handler_test.go
Normal file
131
internal/web_server/handlers/read_chapa_banks_handler_test.go
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// --- Mock service ---
|
||||
|
||||
type MockChapaService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockChapaService) GetSupportedBanks() ([]domain.ChapaSupportedBank, error) {
|
||||
args := m.Called()
|
||||
return args.Get(0).([]domain.ChapaSupportedBank), args.Error(1)
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
func (h *Handler) TestReadChapaBanks_Success(t *testing.T) {
|
||||
app := fiber.New()
|
||||
|
||||
mockService := new(MockChapaService)
|
||||
|
||||
now := time.Now()
|
||||
isMobile := 1
|
||||
isRtgs := 1
|
||||
is24hrs := 1
|
||||
|
||||
mockBanks := []domain.ChapaSupportedBank{
|
||||
{
|
||||
Id: 101,
|
||||
Slug: "bank-a",
|
||||
Swift: "BKAETHAA",
|
||||
Name: "Bank A",
|
||||
AcctLength: 13,
|
||||
AcctNumberRegex: "^[0-9]{13}$",
|
||||
ExampleValue: "1000222215735",
|
||||
CountryId: 1,
|
||||
IsMobilemoney: &isMobile,
|
||||
IsActive: 1,
|
||||
IsRtgs: &isRtgs,
|
||||
Active: 1,
|
||||
Is24Hrs: &is24hrs,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
Currency: "ETB",
|
||||
},
|
||||
}
|
||||
|
||||
mockService.On("GetSupportedBanks").Return(mockBanks, nil)
|
||||
|
||||
// handler := handlers.NewChapaHandler(mockService)
|
||||
app.Post("/chapa/banks", h.ReadChapaBanks)
|
||||
|
||||
req := createTestRequest(t, "POST", "/chapa/banks", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, fiber.StatusOK, resp.StatusCode)
|
||||
|
||||
var body domain.ResponseWDataFactory[[]domain.ChapaSupportedBank]
|
||||
err = parseJSONBody(resp, &body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, body.Success)
|
||||
assert.Equal(t, "read successful on chapa supported banks", body.Message)
|
||||
require.Len(t, body.Data, 1)
|
||||
assert.Equal(t, mockBanks[0].Name, body.Data[0].Name)
|
||||
assert.Equal(t, mockBanks[0].AcctNumberRegex, body.Data[0].AcctNumberRegex)
|
||||
|
||||
mockService.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func (h *Handler) TestReadChapaBanks_Failure(t *testing.T) {
|
||||
app := fiber.New()
|
||||
|
||||
mockService := new(MockChapaService)
|
||||
mockService.On("GetSupportedBanks").Return(nil, errors.New("chapa service unavailable"))
|
||||
|
||||
// handler := handlers.NewChapaHandler(mockService)
|
||||
app.Post("/chapa/banks", h.ReadChapaBanks)
|
||||
|
||||
req := createTestRequest(t, "POST", "/chapa/banks", nil)
|
||||
resp, err := app.Test(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
|
||||
|
||||
var body domain.Response
|
||||
err = parseJSONBody(resp, &body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.False(t, body.Success)
|
||||
assert.Equal(t, "Internal server error", body.Message)
|
||||
mockService.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func createTestRequest(t *testing.T, method, url string, body interface{}) *http.Request {
|
||||
var buf io.Reader
|
||||
if body != nil {
|
||||
b, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buf = bytes.NewBuffer(b)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func parseJSONBody(resp *http.Response, target interface{}) error {
|
||||
return json.NewDecoder(resp.Body).Decode(target)
|
||||
}
|
||||
|
|
@ -182,9 +182,10 @@ func (a *App) initAppRoutes() {
|
|||
a.fiber.Post("/transfer/refill/:id", a.authMiddleware, h.RefillWallet)
|
||||
|
||||
//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/verify", a.authMiddleware, h.VerifyChapaPayment)
|
||||
group.Post("/chapa/payments/withdraw", a.authMiddleware, h.WithdrawUsingChapa)
|
||||
group.Post("/chapa/payments/deposit", a.authMiddleware, h.DepositUsingChapa)
|
||||
group.Get("/chapa/banks", a.authMiddleware, h.ReadChapaBanks)
|
||||
|
||||
// group.Post("/chapa/payments/initialize", h.InitializePayment)
|
||||
// group.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user