# Refund

> Learn how to issue and manage refunds for completed transactions through APO.

For successfully paid transactions, if a refund needs to be issued to the buyer, use one of the following methods:

-   Initiate the refund by calling the [**refund**](https://docs.antom.com/ac/apo/refund_online.md) API.
-   Manually process the refund through the [APO Dashboard](https://dashboard.alipay.com/global-payments/home).

For specific refund policies, such as processing fees, refund settlement exchange rates, and refund validity period, refer to the actual agreements set by related acquirers.

> **Notes**:
>
> For the Antom:
>
> -   Detailed information about payment methods can be found in [Supported payment methods](https://docs.antom.com/ac/pm/supported_pm.md).
> -   Refund reversal: In rare cases, due to issues such as the buyer's abnormal account status on the bank's side, it may occur that after calling the [**refund**](https://docs.antom.com/ac/apo/refund_online.md) API, the refund is marked as successful, but the buyer does not receive the funds. In such situations, Antom will settle the corresponding funds and notify you via the settlement bill. It is your responsibility to decide how to handle such funds.

## Refund through APO Dashboard

You can issue a refund and check the refund details through [APO Dashboard](https://dashboard.alipay.com/global-payments/home).

## Refund using the refund API

The overall flow of the refund process is as follows:

![The workflow of the refund process.](https://idocs-assets.marmot-cloud.com/storage/idocs87c36dc8dac653c1/yuque/idocs/2025/png/d90c2511-4baa-4f0f-a82d-d1b63383ff0b.png)

Figure 1. Workflow of refund

### Integration steps

Start your integration by taking the following steps:

1.  Initiate a refund request
2.  Retrieve the refund result

#### Step 1: Initiate a refund request

The following requirements for a refund request must be met when you use the [**refund**](https://docs.antom.com/ac/apo/refund_online.md) API. Otherwise, the corresponding error codes are to be returned by APO:

| **Refund details** | **Requirements** | **Error code** |
| --- | --- | --- |
| **Refund currency** | The value of _refundAmount.currency_ in the refund request must be consistent with the value of _paymentAmount.currency_ used when initiating a payment. | `CURRENCY_NOT_SUPPORT` |
| **Refund amount** | The value of _refundAmount.value_ in the refund request must not be less than the minimum refund amount (which is usually the same as the minimum payment amount) and no greater than the refundable amount of a payment. | `REFUND_AMOUNT_EXCEED` |
| **Payment status** | For card payments: - Only captured payments can be refunded. - For a payment where a chargeback occurs and the liability is not judged, a refund is not allowed. | `ORDER_STATUS_INVALID` |
| **Refund period** | The refund is allowed only when the refund is issued within the refund period of the corresponding payment method. | `REFUND_WINDOW_EXCEED` |

Table 1. Refund requirements

The following sample code shows how to call the [**refund**](https://docs.antom.com/ac/ams_zh-cn/refund_online.md) API:

```json
public static void refund() {
    AlipayRefundRequest alipayRefundRequest = new AlipayRefundRequest();
    // replace with your refundRequestId
    String refundRequestId = UUID.randomUUID().toString();
    alipayRefundRequest.setRefundRequestId(refundRequestId);
    alipayRefundRequest.setPaymentId("202505151940108001001889A0235440943");
    alipayRefundRequest.setRefundReason("demo refund");
alipayRefundRequest.setRefundNotifyUrl("https://kademo.intlalipay.cn/payments/notifySuccess");
    // set refund amount
    Amount amount = Amount.builder().currency("USD").value("1000").build();
    alipayRefundRequest.setRefundAmount(amount);

    AlipayRefundResponse alipayRefundResponse;
    try {
        alipayRefundResponse = CLIENT.execute(alipayRefundRequest);
    } catch (AlipayApiException e) {
        String errorMsg = e.getMessage();
        // handle error condition
    }
}
```

When initiating a refund through the [**refund**](https://docs.antom.com/ac/ams_zh-cn/refund_online.md) API, the key parameters in the request are listed as follows:

| **Parameter** | **Is required?** | **Description** |
| --- | --- | --- |
| _refundRequestId_ | Yes | The unique identifier that you assign to a refund. |
| _paymentId_ | Yes | The unique identifier that APO assigns to the original payment to be refunded. |
| _refundAmount.value_ | Yes | The refund amount must be no less than the minimum refund amount (usually the same as the minimum payment amount) and no greater than the refundable amount of a payment. |
| _refundAmount.currency_ | Yes | The currency of the refund. The currency of the refund amount must be consistent with the currency of the payment amount (_paymentAmount.currency_) in the [**pay**](https://docs.antom.com/ac/apo/pay.md) API. |
| _refundNotifyUrl_ | No | The URL for receiving asynchronous refund notifications. If you want to receive asynchronous notifications for refund results, please specify this field. If both the request and the APO Dashboard have specified refund notification URLs, the value specified in the request takes precedence. |

Table 2. Key parameters of refund requests

The following shows the sample code of a refund request: 

```json
{
    "paymentId": "20241212194010800100188670211082739",
    "refundReason": "amsdemorefund",
    "refundRequestId": "yuqian_refund_654ac17e-bc5e-4648-b9de-a18f0a74aa2a",
    "refundAmount": {
        "currency": "SGD",
        "value": "1000"
    },
  "refundNotifyUrl":"https://kademo.intlalipay.cn/payments/notifySuccess"
}
```

The following shows the sample code of a response:

```json
{
    "acquirerInfo": {
        "acquirerMerchantId": "764764000015445",
        "acquirerName": "2C2P",
        "acquirerTransactionId": "750130125",
        "referenceRequestId": "2025043019031303099320289169177"
    },
    "acquirerReferenceNo": "2025043019031300010320289179466",
    "paymentId": "20250430194010890100111320263308754",
    "refundAmount": {
        "currency": "USD",
        "value": "110"
    },
    "refundId": "20250430194010890100111320263188180",
    "refundRequestId": "REFUND_20250430152656859_AUTO",
    "refundTime": "2025-04-30T00:26:58-07:00",
    "result": {
        "resultCode": "SUCCESS",
        "resultMessage": "success.",
        "resultStatus": "S"
    }
}
```

The table shows the possible values that the _result.resultStatus_ field in the response message may return. Please handle the result according to the guidances:

| _**result.resultStatus**_ | **Message** | **Further actions** |
| --- | --- | --- |
| `S` | Indicates that the refund is successful. | No further action is required. |
| `F` | Indicates that the refund failed. | Please replace the _refundRequestId_ and try again, or contact APO technical support. |
| `U` | Indicates that the refund is being processed. | The following scenarios may occur: - `REFUND_IN_PROCESS`: Indicates that the refund is being processed. You can call the [**inquiryRefund**](https://docs.antom.com/ac/apo/ir_online.md) API to obtain the refund result or wait for the refund result notification. - `UNKNOWN_EXCEPTION`/ `REQUEST_TRAFFIC_EXCEED_LIMIT`: Usually caused by APO system or network issues, you can call the [**refund**](https://docs.antom.com/ac/apo/refund_online.md) API again using the original _refundRequestId._ |

#### Step 2: Receive the refund result

After initiating a refund request, you can obtain the payment result using one of the following methods:

-   Receive asynchronous notifications
-   Inquire about the payment result

**Set up asynchronous notifications**

**1\. Set the webhook URL to receive notifications**

You can choose one of following two methods to set the webhook URL to receive notifications:

-   RecommendedIf each of your order has a unique notification URL, we recommend to set the webhook URL in each individual request. You can pass the asynchronous notification receiving URL for the specific order through the _refundNotifyUrl_ field in the [**refund**](https://docs.antom.com/ac/apo/refund_online.md) API.
-   If all your orders share a unified notification URL, you can set the webhook URL on [APO Dashboard](https://dashboard.alipay.com/global-payments/developers/iNotify) through **Developer > Notification Address**.

> **Note**: If both the request and the APO Dashboard have refund notification URLs specified, the value specified in the request takes precedence.

The following is the notification request sample code:

```json
{
    "notifyType": "REFUND_RESULT",
    "paymentId": "20250430194010890100111320263308754",
    "paymentRequestId": "PAYMENT_20250430151543077_AUTO",
    "refundAmount": {
        "currency": "USD",
        "value": "110"
    },
    "refundId": "20250430194010890100111320263188180",
    "refundRequestId": "REFUND_20250430152656859_AUTO",
    "refundStatus": "SUCCESS",
    "refundTime": "2025-04-30T00:26:58-07:00",
    "result": {
        "resultCode": "SUCCESS",
        "resultMessage": "success.",
        "resultStatus": "S"
    }
}
```

The table shows the possible values that the _result.resultStatus_ field in the request message may return. Please handle the result according to the guidances:

| _**result.resultStatus**_ | **Message** | **Further actions** |
| --- | --- | --- |
| `S` | Indicates that the refund is successful. | No further action is required. |
| `F` | Indicates that the refund failed. | Please replace the _refundRequestId_ and try again, or contact APO technical support. |

**2\. Verify asynchronous notifications**

If you receive an asynchronous notification from APO, you are required to return the response in the [Sample code](https://docs.antom.com/ac/apo/notifications.md#vQK5A) format, but you do not need to countersign the response.

You need to verify the signature of the refund notification sent by Antom.

```json
/**
 * receive notify
 *
 * @param request    request
 * @param notifyBody notify body
 * @return Result
 */
@PostMapping("/receiveNotify")
@ResponseBody
public Result receiveNotify(HttpServletRequest request, @RequestBody String notifyBody) {
    // retrieve the required parameters from http request
    String requestUri = request.getRequestURI();
    String requestMethod = request.getMethod();

    // retrieve the required parameters from request header
    String requestTime = request.getHeader("request-time");
    String clientId = request.getHeader("client-id");
    String signature = request.getHeader("signature");

    try {
        // verify the signature of notification
        boolean verifyResult = WebhookTool.checkSignature(requestUri, requestMethod, clientId,
                requestTime, signature, notifyBody, ANTOM_PUBLIC_KEY);
        if (!verifyResult) {
            throw new RuntimeException("Invalid notify signature");
        }

        // deserialize the notification body
        JSONObject jsonObject = JSON.parseObject(notifyBody);
        String notifyType = (String)jsonObject.get("notifyType");
        if("REFUND_RESULT".equals(notifyType)){
            AlipayRefundNotify paymentNotify = jsonObject.toJavaObject(AlipayRefundNotify.class);
            if (paymentNotify != null && "SUCCESS".equals(paymentNotify.getResult().getResultCode())) {
                // handle your own business logic.
                // e.g. The relationship between payment information and users is kept in the database.
                System.out.println("receive payment notify: " + JSON.toJSONString(paymentNotify));
                return Result.builder().resultCode("SUCCESS").resultMessage("success.").resultStatus(ResultStatusType.S).build();
            }
        }
        // other types of notifications

    } catch (Exception e) {
        // handle error condition
        return Result.builder().resultCode("FAIL").resultMessage("fail.").resultStatus(ResultStatusType.F).build();
    }

    return Result.builder().resultCode("SYSTEM_ERROR").resultMessage("system error.").resultStatus(ResultStatusType.F).build();
}
```

You do not need to countersign the response of the notification. However, you must respond to each notification request in the following fixed format, regardless of whether the refund is successful or not. 

```json
{
    "result": {
        "resultCode": "SUCCESS",
        "resultStatus": "S",
        "resultMessage": "success"
    }
}
```

**Inquire refunds**

In addition to obtaining the refund result through the asynchronous notification, you can retrieve the corresponding refund result through the inquire refunds service. You can call the [**inquiryRefund**](https://docs.antom.com/ac/apo/ir_online.md) API and use the _refundRequestId_ from the refund session to check the refund status. 

The following code shows how to call the [**inquiryRefund**](https://docs.antom.com/ac/apo/ir_online.md) API: 

```json
public static void inquiryRefund() {
    AlipayInquiryRefundRequest alipayInquiryRefundRequest = new AlipayInquiryRefundRequest();

    // replace with your refundId
    alipayInquiryRefundRequest.setRefundId("yourRefundId");

    AlipayInquiryRefundResponse alipayInquiryRefundResponse = null;
    try {
        alipayInquiryRefundResponse = CLIENT.execute(alipayInquiryRefundRequest);
    } catch (AlipayApiException e) {
        String errorMsg = e.getMessage();
        // handle error condition
    }
}
```

The following code shows an example of a response:

```json
{
    "acquirerInfo": {
        "acquirerMerchantId": "764764000015445",
        "acquirerName": "2C2P",
        "acquirerTransactionId": "750130125",
        "referenceRequestId": "2025043019031303099320289169177"
    },
    "refundAmount": {
        "currency": "USD",
        "value": "110"
    },
    "refundId": "20250430194010890100111320263188180",
    "refundRequestId": "REFUND_20250430152656859_AUTO",
    "refundStatus": "SUCCESS",
    "refundTime": "2025-04-30T00:26:58-07:00",
    "result": {
        "resultCode": "SUCCESS",
        "resultMessage": "success.",
        "resultStatus": "S"
    }
}
```

The table shows the possible values of _refundStatus_ returned in the response:

| **_refundStatus_** | **Message** | **Further actions** |
| --- | --- | --- |
| `SUCCESS` | Indicates that the refund is successful. | No further action is required. |
| `FAIL` | Indicates that the refund failed. | Please replace the _refundRequestId_ and try again, or contact APO technical support. |
| `PROCESSING` | Indicates that the refund is processing. | Please continue to inquire about the refund status until a final result is obtained or a refund asynchronous notification is received. |

> **Commom questions**
>
> **Q: Will the refund result be returned immediately after initiating a refund?**
>
> A: Not all payment methods can provide an immediate final refund result, so please refer to the refund result from asynchronous notifications.
>
> **Q: How long does it take for the buyer to receive the refund after it is successful?**
>
> A: A successful refund means the issuing bank has processed the buyer's refund request. The exact time for the funds to be credited depends on the issuing bank.