/trade/v1/refund 對已成功的付款交易發起退款(全額或部分)。
僅當付款交易返回碼為
0000(交易成功) 時,才能進行退款操作。若為信用卡交易,QFPay 會自動進行 Capture;同日退款申請通常會以 void 方式處理(一般無需商戶額外判斷)。
不同錢包的退款規則(例如可退款期限、是否支援部分退款)可能不同,請以實際開通配置為準。
API 端點
- Endpoint:
/trade/v1/refund - 方法:
POST
HTTP Request
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,用於識別交易設備 |
syssn 在不同 API 的語境下可能代表「付款交易號」或「退款交易號」。在退款 API 的請求中,
syssn 必須填「原始付款交易」的交易號;回應中的 syssn 才是「本次退款交易」的交易號。若未配置
mchid,請勿在請求中傳入該欄位(包含簽名字串亦不得包含)。回應參數
| 參數 | 類型 | 說明 |
|---|---|---|
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) |
範例程式碼
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())
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
}
}
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
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));
?>
回應示例
JSON
{
"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 驗證退款狀態。

