wallet security+log file

This commit is contained in:
Yared Yemane 2025-06-02 17:22:07 +03:00
parent 49d9dafccb
commit f2ec267347
16 changed files with 617 additions and 1219 deletions

View File

@ -297,6 +297,7 @@ ADD CONSTRAINT fk_branch_operations_operations FOREIGN KEY (operation_id) REFERE
ALTER TABLE branch_cashiers 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_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; ADD CONSTRAINT fk_branch_cashiers_branches FOREIGN KEY (branch_id) REFERENCES branches(id) ON DELETE CASCADE;
ALTER TABLE companies ALTER TABLE companies
ADD CONSTRAINT fk_companies_admin FOREIGN KEY (admin_id) REFERENCES users(id), 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; ADD CONSTRAINT fk_companies_wallet FOREIGN KEY (wallet_id) REFERENCES wallets(id) ON DELETE CASCADE;

View File

@ -306,7 +306,6 @@ const docTemplate = `{
}, },
"/api/v1/chapa/banks": { "/api/v1/chapa/banks": {
"get": { "get": {
"description": "Fetch all supported banks from Chapa",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -316,46 +315,42 @@ const docTemplate = `{
"tags": [ "tags": [
"Chapa" "Chapa"
], ],
"summary": "Get list of banks", "summary": "fetches chapa supported banks",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/domain.ChapaSupportedBanksResponse" "$ref": "#/definitions/domain.ChapaSupportedBanksResponseWrapper"
} }
} },
} "400": {
} "description": "Bad Request",
},
"/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,
"schema": { "schema": {
"type": "object" "$ref": "#/definitions/domain.Response"
} }
} },
], "401": {
"responses": { "description": "Unauthorized",
"200": {
"description": "ok",
"schema": { "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": { "/api/v1/chapa/payments/deposit": {
"post": { "post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Deposits money into user wallet from user account using Chapa", "description": "Deposits money into user wallet from user account using Chapa",
"consumes": [ "consumes": [
"application/json" "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": { "/api/v1/chapa/payments/verify": {
"post": { "post": {
"consumes": [ "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": { "/api/v1/chapa/payments/withdraw": {
"post": { "post": {
"description": "Initiates a withdrawal transaction using Chapa for the authenticated user.", "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}": { "/api/v1/virtual-games/recommendations/{userID}": {
"get": { "get": {
"description": "Returns a list of recommended virtual games for a specific user", "description": "Returns a list of recommended virtual games for a specific user",
@ -4666,17 +4524,18 @@ const docTemplate = `{
} }
} }
}, },
"domain.ChapaSupportedBanksResponse": { "domain.ChapaSupportedBanksResponseWrapper": {
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {},
"type": "array",
"items": {
"$ref": "#/definitions/domain.ChapaSupportedBank"
}
},
"message": { "message": {
"type": "string" "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": { "domain.Odd": {
"type": "object", "type": "object",
"properties": { "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": { "domain.UpcomingEvent": {
"type": "object", "type": "object",
"properties": { "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": { "domain.VirtualGame": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -298,7 +298,6 @@
}, },
"/api/v1/chapa/banks": { "/api/v1/chapa/banks": {
"get": { "get": {
"description": "Fetch all supported banks from Chapa",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -308,46 +307,42 @@
"tags": [ "tags": [
"Chapa" "Chapa"
], ],
"summary": "Get list of banks", "summary": "fetches chapa supported banks",
"responses": { "responses": {
"200": { "200": {
"description": "OK", "description": "OK",
"schema": { "schema": {
"$ref": "#/definitions/domain.ChapaSupportedBanksResponse" "$ref": "#/definitions/domain.ChapaSupportedBanksResponseWrapper"
} }
} },
} "400": {
} "description": "Bad Request",
},
"/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,
"schema": { "schema": {
"type": "object" "$ref": "#/definitions/domain.Response"
} }
} },
], "401": {
"responses": { "description": "Unauthorized",
"200": {
"description": "ok",
"schema": { "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": { "/api/v1/chapa/payments/deposit": {
"post": { "post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Deposits money into user wallet from user account using Chapa", "description": "Deposits money into user wallet from user account using Chapa",
"consumes": [ "consumes": [
"application/json" "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": { "/api/v1/chapa/payments/verify": {
"post": { "post": {
"consumes": [ "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": { "/api/v1/chapa/payments/withdraw": {
"post": { "post": {
"description": "Initiates a withdrawal transaction using Chapa for the authenticated user.", "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}": { "/api/v1/virtual-games/recommendations/{userID}": {
"get": { "get": {
"description": "Returns a list of recommended virtual games for a specific user", "description": "Returns a list of recommended virtual games for a specific user",
@ -4658,17 +4516,18 @@
} }
} }
}, },
"domain.ChapaSupportedBanksResponse": { "domain.ChapaSupportedBanksResponseWrapper": {
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {},
"type": "array",
"items": {
"$ref": "#/definitions/domain.ChapaSupportedBank"
}
},
"message": { "message": {
"type": "string" "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": { "domain.Odd": {
"type": "object", "type": "object",
"properties": { "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": { "domain.UpcomingEvent": {
"type": "object", "type": "object",
"properties": { "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": { "domain.VirtualGame": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -185,14 +185,15 @@ definitions:
updated_at: updated_at:
type: string type: string
type: object type: object
domain.ChapaSupportedBanksResponse: domain.ChapaSupportedBanksResponseWrapper:
properties: properties:
data: data: {}
items:
$ref: '#/definitions/domain.ChapaSupportedBank'
type: array
message: message:
type: string type: string
status_code:
type: integer
success:
type: boolean
type: object type: object
domain.ChapaTransactionType: domain.ChapaTransactionType:
properties: properties:
@ -254,52 +255,6 @@ definitions:
- $ref: '#/definitions/domain.OutcomeStatus' - $ref: '#/definitions/domain.OutcomeStatus'
example: 1 example: 1
type: object 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: domain.Odd:
properties: properties:
category: category:
@ -529,58 +484,6 @@ definitions:
example: 1 example: 1
type: integer type: integer
type: object 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: domain.UpcomingEvent:
properties: properties:
awayKitImage: awayKitImage:
@ -659,24 +562,6 @@ definitions:
description: Veli's user identifier description: Veli's user identifier
type: string type: string
type: object 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: domain.VirtualGame:
properties: properties:
category: category:
@ -1752,37 +1637,34 @@ paths:
get: get:
consumes: consumes:
- application/json - application/json
description: Fetch all supported banks from Chapa
produces: produces:
- application/json - application/json
responses: responses:
"200": "200":
description: OK description: OK
schema: schema:
$ref: '#/definitions/domain.ChapaSupportedBanksResponse' $ref: '#/definitions/domain.ChapaSupportedBanksResponseWrapper'
summary: Get list of banks "400":
tags: description: Bad Request
- 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
schema: schema:
type: string $ref: '#/definitions/domain.Response'
summary: Receive Chapa webhook "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: tags:
- Chapa - Chapa
/api/v1/chapa/payments/deposit: /api/v1/chapa/payments/deposit:
@ -1816,33 +1698,9 @@ paths:
description: Internal server error description: Internal server error
schema: schema:
$ref: '#/definitions/domain.Response' $ref: '#/definitions/domain.Response'
security:
- ApiKeyAuth: []
summary: Deposit money into user wallet using Chapa summary: Deposit money into user wallet using Chapa
tags: tags:
- Chapa - 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: /api/v1/chapa/payments/verify:
post: post:
consumes: consumes:
@ -1864,27 +1722,6 @@ paths:
summary: Verifies Chapa webhook transaction summary: Verifies Chapa webhook transaction
tags: tags:
- Chapa - 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: /api/v1/chapa/payments/withdraw:
post: post:
consumes: consumes:
@ -1929,49 +1766,6 @@ paths:
summary: Withdraw using Chapa summary: Withdraw using Chapa
tags: tags:
- Chapa - 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}: /api/v1/virtual-games/recommendations/{userID}:
get: get:
consumes: consumes:

19
go.mod
View File

@ -9,28 +9,28 @@ require (
github.com/gofiber/fiber/v2 v2.52.6 github.com/gofiber/fiber/v2 v2.52.6
github.com/golang-jwt/jwt/v5 v5.2.2 github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/jackc/pgx/v5 v5.7.4 github.com/jackc/pgx/v5 v5.7.4
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/robfig/cron/v3 v3.0.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/stretchr/testify v1.10.0
github.com/swaggo/fiber-swagger v1.3.0 github.com/swaggo/fiber-swagger v1.3.0
github.com/swaggo/swag v1.16.4 github.com/swaggo/swag v1.16.4
github.com/valyala/fasthttp v1.59.0
golang.org/x/crypto v0.36.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 ( 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/KyleBanks/depth v1.2.1 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
// github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // 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/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // 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-openapi/swag v0.23.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.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/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // 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-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // 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/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.1 // 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/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // 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/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/net v0.38.0 // indirect golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.12.0 // indirect golang.org/x/sync v0.12.0 // indirect

2
go.sum
View File

@ -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.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.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.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.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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

View File

@ -221,3 +221,8 @@ type ChapaPaymentUrlResponseWrapper struct {
Data ChapaPaymentUrlResponse `json:"data"` Data ChapaPaymentUrlResponse `json:"data"`
Response Response
} }
type ChapaSupportedBanksResponseWrapper struct {
Data []ChapaSupportedBank `json:"data"`
Response
}

View File

@ -18,13 +18,24 @@ var Environment = map[string]string{
func NewLogger(env string, lvl slog.Level) *slog.Logger { func NewLogger(env string, lvl slog.Level) *slog.Logger {
var logHandler slog.Handler 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 { switch env {
case "development": case "development":
logHandler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ logHandler = slog.NewTextHandler(file, &slog.HandlerOptions{
Level: lvl, Level: lvl,
}) })
default: default:
logHandler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ logHandler = slog.NewJSONHandler(file, &slog.HandlerOptions{
Level: lvl, Level: lvl,
}) })
} }

View File

@ -14,12 +14,14 @@ import (
type ChapaClient interface { type ChapaClient interface {
IssuePayment(ctx context.Context, payload domain.ChapaTransferPayload) (bool, error) IssuePayment(ctx context.Context, payload domain.ChapaTransferPayload) (bool, error)
InitPayment(ctx context.Context, req domain.InitPaymentRequest) (string, error) InitPayment(ctx context.Context, req domain.InitPaymentRequest) (string, error)
FetchBanks() ([]domain.ChapaSupportedBank, error)
} }
type Client struct { type Client struct {
BaseURL string BaseURL string
SecretKey string SecretKey string
HTTPClient *http.Client HTTPClient *http.Client
UserAgent string
} }
func NewClient(baseURL, secretKey string) *Client { func NewClient(baseURL, secretKey string) *Client {
@ -27,6 +29,7 @@ func NewClient(baseURL, secretKey string) *Client {
BaseURL: baseURL, BaseURL: baseURL,
SecretKey: secretKey, SecretKey: secretKey,
HTTPClient: http.DefaultClient, 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 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
}

View File

@ -11,4 +11,5 @@ type ChapaPort interface {
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 WithdrawUsingChapa(ctx context.Context, userID int64, req domain.ChapaWithdrawRequest) error
DepositUsingChapa(ctx context.Context, userID int64, req domain.ChapaDepositRequest) (string, error) DepositUsingChapa(ctx context.Context, userID int64, req domain.ChapaDepositRequest) (string, error)
GetSupportedBanks() ([]domain.ChapaSupportedBank, error)
} }

View File

@ -256,7 +256,7 @@ func (s *Service) DepositUsingChapa(ctx context.Context, userID int64, req domai
return "", err return "", err
} }
defer tx.Rollback(ctx) defer tx.Rollback(ctx)
user, err := s.userStore.GetUserByID(ctx, userID) user, err := s.userStore.GetUserByID(ctx, userID)
if err != nil { if err != nil {
return "", err return "", err
@ -315,3 +315,37 @@ func (s *Service) DepositUsingChapa(ctx context.Context, userID int64, req domai
return paymentURL, nil 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)
}

View File

@ -8,7 +8,7 @@ import (
) )
// NFL evaluations // 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 { switch outcome.OddHeader {
case "1": case "1":
if score.Home > score.Away { 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) handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
if err != nil { if err != nil {
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap) 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 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) totalPoints := float64(score.Home + score.Away)
threshold, err := strconv.ParseFloat(outcome.OddName, 64) threshold, err := strconv.ParseFloat(outcome.OddName, 64)
if err != nil { 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) return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
} }
// evaluateRugbyMoneyLine evaluates Rugby money line bets // EvaluateRugbyMoneyLine Evaluates Rugby money line bets
func evaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { func EvaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
switch outcome.OddHeader { switch outcome.OddHeader {
case "1": case "1":
if score.Home > score.Away { if score.Home > score.Away {
@ -99,8 +99,8 @@ func evaluateRugbyMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away
} }
} }
// evaluateRugbySpread evaluates Rugby spread bets // EvaluateRugbySpread Evaluates Rugby spread bets
func evaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { func EvaluateRugbySpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64) handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
if err != nil { if err != nil {
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap) 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 return domain.OUTCOME_STATUS_VOID, nil
} }
// evaluateRugbyTotalPoints evaluates Rugby total points bets // EvaluateRugbyTotalPoints Evaluates Rugby total points bets
func evaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { func EvaluateRugbyTotalPoints(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
totalPoints := float64(score.Home + score.Away) totalPoints := float64(score.Home + score.Away)
threshold, err := strconv.ParseFloat(outcome.OddName, 64) threshold, err := strconv.ParseFloat(outcome.OddName, 64)
if err != nil { 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) return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
} }
// evaluateBaseballMoneyLine evaluates Baseball money line bets // EvaluateBaseballMoneyLine Evaluates Baseball money line bets
func evaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { func EvaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
switch outcome.OddHeader { switch outcome.OddHeader {
case "1": case "1":
if score.Home > score.Away { if score.Home > score.Away {
@ -175,8 +175,8 @@ func evaluateBaseballMoneyLine(outcome domain.BetOutcome, score struct{ Home, Aw
} }
} }
// evaluateBaseballSpread evaluates Baseball spread bets // EvaluateBaseballSpread Evaluates Baseball spread bets
func evaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { func EvaluateBaseballSpread(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64) handicap, err := strconv.ParseFloat(outcome.OddHandicap, 64)
if err != nil { if err != nil {
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid handicap: %s", outcome.OddHandicap) 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 return domain.OUTCOME_STATUS_VOID, nil
} }
// evaluateBaseballTotalRuns evaluates Baseball total runs bets // EvaluateBaseballTotalRuns Evaluates Baseball total runs bets
func evaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) { func EvaluateBaseballTotalRuns(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
totalRuns := float64(score.Home + score.Away) totalRuns := float64(score.Home + score.Away)
threshold, err := strconv.ParseFloat(outcome.OddName, 64) threshold, err := strconv.ParseFloat(outcome.OddName, 64)
if err != nil { 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) 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) { func EvaluateBaseballFirstInning(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
switch outcome.OddHeader { switch outcome.OddHeader {
case "1": 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) { func EvaluateBaseballFirst5Innings(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
switch outcome.OddHeader { switch outcome.OddHeader {
case "1": case "1":

View File

@ -1,287 +1,288 @@
package handlers package handlers
import ( import (
"bytes" // "bytes"
"encoding/json" // "encoding/json"
// "fmt"
// "io"
// "net/http"
"fmt" "fmt"
"io"
"net/http"
"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"
) )
// GetBanks godoc // // GetBanks godoc
// @Summary Get list of banks // // @Summary Get list of banks
// @Description Fetch all supported banks from Chapa // // @Description Fetch all supported banks from Chapa
// @Tags Chapa // // @Tags Chapa
// @Accept json // // @Accept json
// @Produce json // // @Produce json
// @Success 200 {object} domain.ChapaSupportedBanksResponse // // @Success 200 {object} domain.ChapaSupportedBanksResponse
// @Router /api/v1/chapa/banks [get] // // @Router /api/v1/chapa/banks [get]
func (h *Handler) GetBanks(c *fiber.Ctx) error { // func (h *Handler) GetBanks(c *fiber.Ctx) error {
httpReq, err := http.NewRequest("GET", h.Cfg.CHAPA_BASE_URL+"/banks", nil) // 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) // // log.Printf("\n\nbase url is: %v\n\n", h.Cfg.CHAPA_BASE_URL)
if err != nil { // if err != nil {
return c.Status(500).JSON(fiber.Map{"error": "Failed to create request", "details": err.Error()}) // 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("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
resp, err := http.DefaultClient.Do(httpReq) // resp, err := http.DefaultClient.Do(httpReq)
if err != nil { // if err != nil {
return c.Status(500).JSON(fiber.Map{"error": "Failed to fetch banks", "details": err.Error()}) // return c.Status(500).JSON(fiber.Map{"error": "Failed to fetch banks", "details": err.Error()})
} // }
defer resp.Body.Close() // defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) // body, err := io.ReadAll(resp.Body)
if err != nil { // if err != nil {
return c.Status(500).JSON(fiber.Map{"error": "Failed to read response", "details": err.Error()}) // 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 // // InitializePayment godoc
// @Summary Initialize a payment transaction // // @Summary Initialize a payment transaction
// @Description Initiate a payment through Chapa // // @Description Initiate a payment through Chapa
// @Tags Chapa // // @Tags Chapa
// @Accept json // // @Accept json
// @Produce json // // @Produce json
// @Param payload body domain.InitPaymentRequest true "Payment initialization request" // // @Param payload body domain.InitPaymentRequest true "Payment initialization request"
// @Success 200 {object} domain.InitPaymentResponse // // @Success 200 {object} domain.InitPaymentResponse
// @Router /api/v1/chapa/payments/initialize [post] // // @Router /api/v1/chapa/payments/initialize [post]
func (h *Handler) InitializePayment(c *fiber.Ctx) error { // func (h *Handler) InitializePayment(c *fiber.Ctx) error {
var req InitPaymentRequest // var req InitPaymentRequest
if err := c.BodyParser(&req); err != nil { // if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ // return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request body", // "error": "Invalid request body",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
// Generate and assign a unique transaction reference // // Generate and assign a unique transaction reference
req.TxRef = uuid.New().String() // req.TxRef = uuid.New().String()
payload, err := json.Marshal(req) // payload, err := json.Marshal(req)
if err != nil { // if err != nil {
return c.Status(500).JSON(fiber.Map{ // return c.Status(500).JSON(fiber.Map{
"error": "Failed to serialize request", // "error": "Failed to serialize request",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transaction/initialize", bytes.NewBuffer(payload)) // httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transaction/initialize", bytes.NewBuffer(payload))
if err != nil { // if err != nil {
return c.Status(500).JSON(fiber.Map{ // return c.Status(500).JSON(fiber.Map{
"error": "Failed to create request", // "error": "Failed to create request",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY) // httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
httpReq.Header.Set("Content-Type", "application/json") // httpReq.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(httpReq) // resp, err := http.DefaultClient.Do(httpReq)
if err != nil { // if err != nil {
return c.Status(500).JSON(fiber.Map{ // return c.Status(500).JSON(fiber.Map{
"error": "Failed to initialize payment", // "error": "Failed to initialize payment",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
defer resp.Body.Close() // defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) // body, err := io.ReadAll(resp.Body)
if err != nil { // if err != nil {
return c.Status(500).JSON(fiber.Map{ // return c.Status(500).JSON(fiber.Map{
"error": "Failed to read response", // "error": "Failed to read response",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
return c.Status(resp.StatusCode).Type("json").Send(body) // return c.Status(resp.StatusCode).Type("json").Send(body)
} // }
// VerifyTransaction godoc // // VerifyTransaction godoc
// @Summary Verify a payment transaction // // @Summary Verify a payment transaction
// @Description Verify the transaction status from Chapa using tx_ref // // @Description Verify the transaction status from Chapa using tx_ref
// @Tags Chapa // // @Tags Chapa
// @Accept json // // @Accept json
// @Produce json // // @Produce json
// @Param tx_ref path string true "Transaction Reference" // // @Param tx_ref path string true "Transaction Reference"
// @Success 200 {object} domain.VerifyTransactionResponse // // @Success 200 {object} domain.VerifyTransactionResponse
// @Router /api/v1/chapa/payments/verify/{tx_ref} [get] // // @Router /api/v1/chapa/payments/verify/{tx_ref} [get]
func (h *Handler) VerifyTransaction(c *fiber.Ctx) error { // func (h *Handler) VerifyTransaction(c *fiber.Ctx) error {
txRef := c.Params("tx_ref") // txRef := c.Params("tx_ref")
if txRef == "" { // if txRef == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ // return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Missing transaction reference", // "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) // httpReq, err := http.NewRequest("GET", url, nil)
if err != nil { // if err != nil {
return c.Status(500).JSON(fiber.Map{ // return c.Status(500).JSON(fiber.Map{
"error": "Failed to create request", // "error": "Failed to create request",
"details": err.Error(), // "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) // resp, err := http.DefaultClient.Do(httpReq)
if err != nil { // if err != nil {
return c.Status(500).JSON(fiber.Map{ // return c.Status(500).JSON(fiber.Map{
"error": "Failed to verify transaction", // "error": "Failed to verify transaction",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
defer resp.Body.Close() // defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) // body, err := io.ReadAll(resp.Body)
if err != nil { // if err != nil {
return c.Status(500).JSON(fiber.Map{ // return c.Status(500).JSON(fiber.Map{
"error": "Failed to read response", // "error": "Failed to read response",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
return c.Status(resp.StatusCode).Type("json").Send(body) // return c.Status(resp.StatusCode).Type("json").Send(body)
} // }
// ReceiveWebhook godoc // // ReceiveWebhook godoc
// @Summary Receive Chapa webhook // // @Summary Receive Chapa webhook
// @Description Endpoint to receive webhook payloads from Chapa // // @Description Endpoint to receive webhook payloads from Chapa
// @Tags Chapa // // @Tags Chapa
// @Accept json // // @Accept json
// @Produce json // // @Produce json
// @Param payload body object true "Webhook Payload (dynamic)" // // @Param payload body object true "Webhook Payload (dynamic)"
// @Success 200 {string} string "ok" // // @Success 200 {string} string "ok"
// @Router /api/v1/chapa/payments/callback [post] // // @Router /api/v1/chapa/payments/callback [post]
func (h *Handler) ReceiveWebhook(c *fiber.Ctx) error { // func (h *Handler) ReceiveWebhook(c *fiber.Ctx) error {
var payload map[string]interface{} // var payload map[string]interface{}
if err := c.BodyParser(&payload); err != nil { // if err := c.BodyParser(&payload); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ // return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid webhook data", // "error": "Invalid webhook data",
"details": err.Error(), // "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 // // CreateTransfer godoc
// @Summary Create a money transfer // // @Summary Create a money transfer
// @Description Initiate a transfer request via Chapa // // @Description Initiate a transfer request via Chapa
// @Tags Chapa // // @Tags Chapa
// @Accept json // // @Accept json
// @Produce json // // @Produce json
// @Param payload body domain.TransferRequest true "Transfer request body" // // @Param payload body domain.TransferRequest true "Transfer request body"
// @Success 200 {object} domain.CreateTransferResponse // // @Success 200 {object} domain.CreateTransferResponse
// @Router /api/v1/chapa/transfers [post] // // @Router /api/v1/chapa/transfers [post]
func (h *Handler) CreateTransfer(c *fiber.Ctx) error { // func (h *Handler) CreateTransfer(c *fiber.Ctx) error {
var req TransferRequest // var req TransferRequest
if err := c.BodyParser(&req); err != nil { // if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ // return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request", // "error": "Invalid request",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
// Inject unique transaction reference // // Inject unique transaction reference
req.Reference = uuid.New().String() // req.Reference = uuid.New().String()
payload, err := json.Marshal(req) // payload, err := json.Marshal(req)
if err != nil { // if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ // return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to serialize request", // "error": "Failed to serialize request",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transfers", bytes.NewBuffer(payload)) // httpReq, err := http.NewRequest("POST", h.Cfg.CHAPA_BASE_URL+"/transfers", bytes.NewBuffer(payload))
if err != nil { // if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ // return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to create HTTP request", // "error": "Failed to create HTTP request",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY) // httpReq.Header.Set("Authorization", "Bearer "+h.Cfg.CHAPA_SECRET_KEY)
httpReq.Header.Set("Content-Type", "application/json") // httpReq.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(httpReq) // resp, err := http.DefaultClient.Do(httpReq)
if err != nil { // if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ // return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Transfer request failed", // "error": "Transfer request failed",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
defer resp.Body.Close() // defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) // body, err := io.ReadAll(resp.Body)
if err != nil { // if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ // return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to read response", // "error": "Failed to read response",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
return c.Status(resp.StatusCode).Type("json").Send(body) // return c.Status(resp.StatusCode).Type("json").Send(body)
} // }
// VerifyTransfer godoc // // VerifyTransfer godoc
// @Summary Verify a transfer // // @Summary Verify a transfer
// @Description Check the status of a money transfer via reference // // @Description Check the status of a money transfer via reference
// @Tags Chapa // // @Tags Chapa
// @Accept json // // @Accept json
// @Produce json // // @Produce json
// @Param transfer_ref path string true "Transfer Reference" // // @Param transfer_ref path string true "Transfer Reference"
// @Success 200 {object} domain.VerifyTransferResponse // // @Success 200 {object} domain.VerifyTransferResponse
// @Router /api/v1/chapa/transfers/verify/{transfer_ref} [get] // // @Router /api/v1/chapa/transfers/verify/{transfer_ref} [get]
func (h *Handler) VerifyTransfer(c *fiber.Ctx) error { // func (h *Handler) VerifyTransfer(c *fiber.Ctx) error {
transferRef := c.Params("transfer_ref") // transferRef := c.Params("transfer_ref")
if transferRef == "" { // if transferRef == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ // return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Missing transfer reference in URL", // "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) // httpReq, err := http.NewRequest("GET", url, nil)
if err != nil { // if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ // return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to create HTTP request", // "error": "Failed to create HTTP request",
"details": err.Error(), // "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) // resp, err := http.DefaultClient.Do(httpReq)
if err != nil { // if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ // return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Verification request failed", // "error": "Verification request failed",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
defer resp.Body.Close() // defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) // body, err := io.ReadAll(resp.Body)
if err != nil { // if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ // return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to read response body", // "error": "Failed to read response body",
"details": err.Error(), // "details": err.Error(),
}) // })
} // }
return c.Status(resp.StatusCode).Type("json").Send(body) // return c.Status(resp.StatusCode).Type("json").Send(body)
} // }
// VerifyChapaPayment godoc // VerifyChapaPayment godoc
// @Summary Verifies Chapa webhook transaction // @Summary Verifies Chapa webhook transaction
@ -384,7 +385,6 @@ func (h *Handler) WithdrawUsingChapa(c *fiber.Ctx) error {
// @Tags Chapa // @Tags Chapa
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth
// @Param payload body domain.ChapaDepositRequest true "Deposit request payload" // @Param payload body domain.ChapaDepositRequest true "Deposit request payload"
// @Success 200 {object} domain.ChapaPaymentUrlResponseWrapper // @Success 200 {object} domain.ChapaPaymentUrlResponseWrapper
// @Failure 400 {object} domain.Response "Invalid request" // @Failure 400 {object} domain.Response "Invalid request"
@ -407,7 +407,7 @@ func (h *Handler) DepositUsingChapa(c *fiber.Ctx) error {
return domain.UnProcessableEntityResponse(c) 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 { if err := req.Validate(); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.Response{ return c.Status(fiber.StatusBadRequest).JSON(domain.Response{
Message: err.Error(), 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,
},
})
}

View 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)
}

View File

@ -182,9 +182,10 @@ 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/verify", a.authMiddleware, h.VerifyChapaPayment)
group.Post("/chapa/payments/withdraw", h.WithdrawUsingChapa) group.Post("/chapa/payments/withdraw", a.authMiddleware, h.WithdrawUsingChapa)
group.Post("/chapa/payments/deposit", h.DepositUsingChapa) 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.Post("/chapa/payments/initialize", h.InitializePayment)
// group.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction) // group.Get("/chapa/payments/verify/:tx_ref", h.VerifyTransaction)

View File

@ -19,7 +19,7 @@ build:
.PHONY: run .PHONY: run
run: run:
@docker compose up -d @docker compose up
.PHONY: stop .PHONY: stop
stop: stop: