Processing Refunds
Relevant source files
The following files were used as context for generating this wiki page:
Introduction
The refund processing feature in the zalopay service provides a mechanism to reverse a previously successful transaction. It is exposed via a dedicated API endpoint that accepts refund details, processes them internally, and communicates with the ZaloPay payment gateway to execute the refund. The entire process is secured using an HMAC-based signature to ensure data integrity and authenticity.
The system is designed with a layered architecture, separating the API handling (controller), business logic (service), and external communication (gateway). This separation of concerns allows for modularity and easier maintenance of the refund functionality. The process involves generating a unique refund ID, constructing a request for the ZaloPay API, signing the request, and handling the response from the gateway.
Architectural Overview
The refund process follows a clear, layered architectural flow, starting from the client's API request and ending with a call to the external ZaloPay gateway. Each layer has a distinct responsibility.
- Controller: The
RefundControlleris the entry point. It receives the HTTP request, binds the JSON payload to a data model, and passes it to the service layer. - Service: The
RefundServiceorchestrates the refund logic. It prepares the data for the ZaloPay gateway, generates the required security signature (mac), and calls the gateway to perform the refund. - Gateway: The
ZaloPayGatewayis responsible for the actual communication with the ZaloPay API. It constructs the final HTTP request, sends it to the configured ZaloPay refund URL, and parses the response.
This diagram illustrates the high-level component interaction for a refund request.
API Endpoint
The refund functionality is exposed through a single API endpoint.
Sources: main.go:32, controller/refund.go:19-21
POST /refund
This endpoint initiates a refund request. The client must provide the ZaloPay transaction ID of the original payment, the amount to be refunded, and a description.
Request Body
The request body should be a JSON object matching the RefundRequest structure.
| Field | Type | Description |
|---|---|---|
zp_trans_id | string | The transaction ID of the original ZaloPay payment to be refunded. |
amount | int64 | The amount to be refunded. |
description | string | A description for the refund transaction. |
Sources: model/refund.go:8-14, controller/refund.go:25
Example Usage
// controller/refund.go:23-28
func (c *refundController) Refund(ctx *gin.Context) {
var req model.RefundRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
Sources: controller/refund.go:23-28
Refund Workflow
The end-to-end workflow involves coordination between the controller, service, and gateway layers.
This sequence diagram shows the detailed interactions for a successful refund request.
1. Request Handling (Controller)
The RefundController's Refund method is triggered by a POST request to /refund. It binds the incoming JSON to a model.RefundRequest struct and invokes the RefundService.
Sources: controller/refund.go:23-33
2. Business Logic (Service)
The refundService contains the core logic. Its Refund method performs the following steps:
- Retrieves the application configuration (
AppID,Key1,RefundURL). - Generates a unique refund ID (
m_refund_id) using the current timestamp. - Creates a
model.ZaloPayRefundRequestobject, populating it with data from the initial request and configuration. - Generates the HMAC signature (
mac) required for authenticating with the ZaloPay API. - Calls the
ZaloPayGateway'sRefundmethod with the prepared request.
Sources: service/refund.go:20-40
3. Gateway Communication
The zaloPayGateway's Refund method is responsible for the final communication step:
- Serializes the
ZaloPayRefundRequestobject into a JSON payload. - Creates a new HTTP POST request to the
RefundURLspecified in the configuration. - Sets the
Content-Typeheader toapplication/json. - Executes the request using a standard
http.Client. - Deserializes the JSON response from the ZaloPay API into a
model.ZaloPayRefundResponsestruct. - Returns the response to the
refundService.
Sources: gateway/zalopay/refund.go:18-42
Data Models
The refund process uses several data structures to manage request and response data.
Sources: model/refund.go
RefundRequest
This model represents the data received from the client in the initial API call.
| Field | Type | JSON Tag |
|---|---|---|
ZpTransID | string | zp_trans_id |
Amount | int64 | amount |
Description | string | description |
Sources: model/refund.go:8-14
ZaloPayRefundRequest
This model represents the full request payload sent to the ZaloPay gateway API. It includes fields from the client request plus system-generated values.
| Field | Type | JSON Tag | Description |
|---|---|---|---|
AppID | int64 | app_id | The application ID from the configuration. |
ZpTransID | string | zp_trans_id | The original ZaloPay transaction ID. |
MRefundID | string | m_refund_id | A unique ID for this refund request, generated by the service. |
Timestamp | int64 | timestamp | The request timestamp in milliseconds. |
Amount | int64 | amount | The amount to refund. |
Description | string | description | A description for the refund. |
Mac | string | mac | The HMAC signature to authenticate the request. |
Sources: model/refund.go:16-25
ZaloPayRefundResponse
This model represents the response received from the ZaloPay gateway.
| Field | Type | JSON Tag | Description |
|---|---|---|---|
ReturnCode | int | return_code | The result code from ZaloPay (1 = success). |
ReturnMessage | string | return_message | A descriptive message for the result code. |
SubReturnCode | int | sub_return_code | A more detailed result code. |
SubReturnMessage | string | sub_return_message | A descriptive message for the sub-result code. |
RefundID | string | refund_id | The unique refund ID generated by ZaloPay. |
Sources: model/refund.go:27-34
Security: MAC Signature Generation
To authenticate requests with the ZaloPay API, a mac (Message Authentication Code) field must be generated and included in the request. The system uses HMAC-SHA256 for this purpose.
The signature is created by concatenating several fields with a | delimiter, and then hashing the resulting string with Key1 from the application configuration.
This diagram shows the data flow for generating the mac signature.
Sources: service/refund.go:32-34, helper/hmac.go:8-16
The implementation can be seen in the refundService:
// service/refund.go:32-34
data := fmt.Sprintf("%v|%v|%v|%v|%v", cfg.AppID, req.ZpTransID, req.Amount, req.Description, timestamp)
zaloPayReq.Mac = helper.HmacSha256(data, cfg.Key1)
The helper.HmacSha256 function takes the data string and the secret key (Key1) to produce the final hexadecimal signature string.
Sources: service/refund.go:32-34, helper/hmac.go:8-16
Configuration
The refund feature relies on several configuration values defined in the Config struct and loaded at startup.
| Parameter | Type | Description |
|---|---|---|
AppID | int64 | The application ID provided by ZaloPay. |
Key1 | string | The secret key used for signing refund requests. |
RefundURL | string | The endpoint URL for the ZaloPay refund API. |
Sources: config/config.go:10-14, service/refund.go:22, gateway/zalopay/refund.go:20
Summary
The refund processing system is a well-defined module that securely handles refund requests by integrating with the ZaloPay payment gateway. It follows a layered architecture, ensuring a clean separation of concerns between API handling, business logic, and external communication. Key to its operation is the HMAC-based signature generation, which authenticates every refund request sent to ZaloPay, ensuring the integrity and security of the transaction. The entire flow is configurable and relies on data models that clearly define the contracts for both internal and external communication.