> ## Documentation Index
> Fetch the complete documentation index at: https://sdk.qfapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 退款 API 指南

> 使用退款 API 處理成功交易的全額或部分退款。

本頁說明如何透過 `/trade/v1/refund` 對**已成功的付款交易**發起退款（全額或部分）。

<Tip>
  僅當付款交易返回碼為 **`0000（交易成功）`** 時，才能進行退款操作。
</Tip>

<Note>
  若為信用卡交易，QFPay 會自動進行 Capture；同日退款申請通常會以 **void** 方式處理（一般無需商戶額外判斷）。
</Note>

<Note>
  不同錢包的退款規則（例如可退款期限、是否支援部分退款）可能不同，請以實際開通配置為準。
</Note>

***

## API 端點

* **Endpoint**：`/trade/v1/refund`
* **方法**：`POST`

### HTTP Request

```http theme={null}
Content-Type: application/x-www-form-urlencoded
X-QF-APPCODE: <your-app-code>
X-QF-SIGN: <signature>
```

***

## 請求參數

| 參數             | 必填  | 類型          | 說明                                      |
| -------------- | --- | ----------- | --------------------------------------- |
| `syssn`        | 是   | String(128) | 欲退款的**原始付款交易** QFPay 交易號                |
| `out_trade_no` | 是   | String(128) | 商戶端退款單號（退款請求的唯一識別；同一商戶帳戶下不可重複）          |
| `txamt`        | 是   | Int(11)     | 退款金額（單位：分）。部分錢包不支援部分退款；建議金額 > 200 以避免風控 |
| `txdtm`        | 是   | String(20)  | 退款請求時間，格式：`YYYY-MM-DD HH:mm:ss`         |
| `mchid`        | 視情況 | String(16)  | 若系統有配置商戶號則必填，否則不可填寫                     |
| `udid`         | 否   | String(40)  | 裝置 ID，用於識別交易設備                          |

<Info>
  `syssn` 在不同 API 的語境下可能代表「付款交易號」或「退款交易號」。\
  在**退款 API 的請求**中，`syssn` 必須填「原始付款交易」的交易號；回應中的 `syssn` 才是「本次退款交易」的交易號。
</Info>

<Note>
  若未配置 `mchid`，請勿在請求中傳入該欄位（包含簽名字串亦不得包含）。
</Note>

***

## 回應參數

| 參數                     | 類型          | 說明                                        |
| ---------------------- | ----------- | ----------------------------------------- |
| `syssn`                | String(40)  | 此次退款交易所對應的 QFPay 交易號                      |
| `orig_syssn`           | String(128) | 原始付款交易號                                   |
| `txamt`                | Int(11)     | 退款金額（單位：分）                                |
| `sysdtm`               | String(20)  | 系統退款時間（`YYYY-MM-DD HH:mm:ss`，常用作結算分界時間）   |
| `respcd`               | String(4)   | `0000`=成功；`1143/1145`=處理中；其他=失敗（請參考狀態碼文件） |
| `resperr`              | String(128) | 返回訊息                                      |
| `cash_fee`             | String      | 實際支付金額（扣除折扣後）                             |
| `cash_fee_type`        | String      | 實際支付幣種（例如 CNY）                            |
| `cash_refund_fee`      | String      | 實際退款金額                                    |
| `cash_refund_fee_type` | String      | 實際退款幣種（例如 CNY）                            |

***

## 範例程式碼

<CodeGroup>
  ```python Python theme={null}
  import hashlib
  import requests
  import datetime
  import random
  import string

  environment = "https://test-openapi-hk.qfapi.com"
  app_code = "D5589D2A1F2E42A9A60C37**********"
  client_key = "0E32A59A8B454940A2FF39**********"

  def make_req_sign(data, key):
    keys = sorted(data.keys())
    p = [f"{k}={data[k]}" for k in keys]
    unsign_str = ("&".join(p) + key).encode("utf-8")
    return hashlib.md5(unsign_str).hexdigest().upper()

  current_time = datetime.datetime.now().replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S")
  refund_out_trade_no = "".join(random.choices(string.ascii_uppercase + string.digits, k=32))

  data = {
    "mchid": "ZaMVg*****",                 # 若未配置 mchid，請勿傳入
    "txamt": "10",
    "syssn": "20191227000200020061752831", # 原始付款交易號
    "out_trade_no": refund_out_trade_no,   # 退款單號（唯一）
    "txdtm": current_time
  }

  r = requests.post(
    environment + "/trade/v1/refund",
    data=data,
    headers={
        "X-QF-APPCODE": app_code,
        "X-QF-SIGN": make_req_sign(data, client_key),
    },
  )

  print(r.json())
  ```

  ```java Java theme={null}
  import java.text.SimpleDateFormat;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.Map;

  public class Refund {
  public static void main(String[] args) {
    String appcode = "D5589D2A1F2E42A9A60C37**********";
    String key = "0E32A59A8B454940A2FF39**********";
    String mchid = "ZaMVg*****"; // 若未配置 mchid，請勿傳入

    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String txdtm = df.format(new Date());

    Map<String, String> payload = new HashMap<>();
    payload.put("mchid", mchid);
    payload.put("txamt", "15");
    payload.put("syssn", "20191227000300020061662295"); // 原始付款交易號
    payload.put("out_trade_no", "REFUND_22333444455555"); // 退款單號（唯一）
    payload.put("txdtm", txdtm);

    // 簽名與送出請求請沿用你們現有的工具方法：
    // 1) payload key 依 ASCII 升冪排序組成 key=value&...
    // 2) 字串尾端加上 client_key
    // 3) MD5 後轉大寫作為 X-QF-SIGN
    // 4) POST x-www-form-urlencoded，並帶上 X-QF-APPCODE / X-QF-SIGN header
  }
  }
  ```

  ```javascript JavaScript theme={null}
  const crypto = require("crypto");
  const request = require("request");

  const environment = "https://test-openapi-hk.qfapi.com";
  const app_code = "D5589D2A1F2E42A9A60C37**********";
  const client_key = "0E32A59A8B454940A2FF3***********";

  const payload = {
  mchid: "ZaMVg*****", // 若未配置 mchid，請勿傳入
  syssn: "20191231000300020063521806", // 原始付款交易號
  txamt: "10",
  out_trade_no: String(Math.round(Math.random() * 1000000000)),
  txdtm: new Date().toISOString().replace(/T/, " ").replace(/\..+/, "")
  };

  const sorted = Object.keys(payload)
  .sort()
  .map(k => `${k}=${payload[k]}`)
  .join("&");

  const sign = crypto
  .createHash("md5")
  .update(sorted + client_key)
  .digest("hex")
  .toUpperCase();

  request({
  uri: environment + "/trade/v1/refund",
  headers: {
    "X-QF-APPCODE": app_code,
    "X-QF-SIGN": sign
  },
  method: "POST",
  form: payload
  }, function (error, response, body) {
  console.log(body);
  });
  ```

  ```php PHP theme={null}
  <?php
  function GetRandStr($length){
  $str='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  $len=strlen($str)-1;
  $randstr='';
  for($i=0;$i<$length;$i++){
    $num=mt_rand(0,$len);
    $randstr .= $str[$num];
  }
  return $randstr;
  }

  $url = 'https://test-openapi-hk.qfapi.com/trade/v1/refund';
  $app_code = 'FF2FF74F2F2E42769A4A73*********';
  $app_key  = '7BE791E0FD2E48E6926043B*********';
  $now_time = date("Y-m-d H:i:s");

  $fields = array(
  // 'mchid' => 'ZaMVg*****', // 若未配置 mchid，請勿傳入
  'syssn' => '20200311066100020000977840', // 原始付款交易號
  'out_trade_no' => GetRandStr(20),
  'txamt' => 2200,
  'txdtm' => $now_time
  );

  ksort($fields);

  $fields_string = '';
  foreach($fields as $k=>$v) { 
  $fields_string .= $k.'='.$v.'&';
  }
  $fields_string = substr($fields_string, 0, -1);

  $sign = strtoupper(md5($fields_string . $app_key));

  $header = array();
  $header[] = 'X-QF-APPCODE: ' . $app_code;
  $header[] = 'X-QF-SIGN: ' . $sign;

  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  curl_setopt($ch, CURLOPT_POST, 1);
  curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
  $output = curl_exec($ch);
  curl_close($ch);

  print_r(json_decode($output, true));
  ?>
  ```
</CodeGroup>

***

## 回應示例

```json JSON theme={null}
{
  "orig_syssn": "20191227000200020061752831",
  "sysdtm": "2019-12-27 11:11:23",
  "paydtm": "2019-12-27 11:11:26",
  "txdtm": "2019-12-27 11:10:38",
  "udid": "qiantai2",
  "txcurrcd": "EUR",
  "txamt": "10",
  "resperr": "success",
  "respmsg": "",
  "out_trade_no": "RGNOEIVU9JZLNP9GGYXWXCW7OEMI720F",
  "syssn": "20191227000300020061652643",
  "respcd": "0000",
  "chnlsn": "2019122722001411461404119764",
  "cardcd": ""
}
```

***

## 注意事項

* 請確保退款金額不超過原始交易金額。
* 部分錢包不支援部分退款，請先確認實際開通配置。
* 各支付通道的退款有效期限不同，請以渠道規則與商戶配置為準。
* 若退款結果為失敗（`respcd` 非 `0000`），建議實作重試邏輯，或透過 [交易查詢 API](/zh-hant/integration/common-api/transaction-enquiry) 驗證退款狀態。
