订单退款接口
接口说明
交易退款接口用于对已支付成功的订单发起退款操作,支持全额退款和部分退款。系统支持主流支付渠道的退款功能,确保商户能够灵活处理各种退款需求。
适用对象
直连商户、间连商户
请求 URL
https://api.example.com/openapi/v1/refund/create请求方法
POST
退款流程图
请求参数(bizContent)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
refundNo | String | Y | 退款订单号,必须唯一,用于幂等控制 |
transactionNo | String | C | 平台订单号(与商户订单号必需填一个) |
orderNo | String | C | 商户订单号(与平台订单号必需填一个) |
refundAmount | String | Y | 申请退款金额,单位元,需保留小数点后两位;示例值:100.00,最低传入 0.01 |
notifyUrl | String | N | 异步通知地址,示例值:http://service.example.com/to/path |
remark | String | N | 备注,原样返回;示例值:测试退款 |
注意事项:
- 退款订单号(
refundNo)必须唯一,避免重复退款 - 退款金额(
refundAmount)不能超过原订单可退款金额 - 对于部分退款,多次退款金额总和不能超过原订单金额
响应参数
公共返回参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | String | Y | 响应状态码,成功为 "0000" |
message | String | Y | 响应描述 |
timestamp | String | Y | 响应时间戳,UTC 时间,ISO 8601 格式 |
data | Object | N | 业务数据,无数据时返回空对象 {} |
响应附加参数(data)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
refundTransactionNo | String | Y | 平台退款订单号 |
transactionNo | String | Y | 平台订单号 |
refundAmount | Number | Y | 本次退款金额,单位元 |
totalRefunded | Number | Y | 累计退款金额,单位元 |
isFullRefund | Boolean | Y | 是否全额退款 |
status | Integer | Y | 退款状态(见下表) |
退款状态码说明
| 状态码 | 状态 | 说明 |
|---|---|---|
0 | 退款中 | 退款请求已提交,等待处理 |
1 | 退款成功 | 退款已成功到账 |
2 | 退款失败 | 退款处理失败 |
请求示例
退款示例
JSON
{
"refundNo": "REFUND202607041659484287",
"transactionNo": "SHPAY202607041623307050108",
"orderNo": "TESTPAY202607041623307050108",
"refundAmount": "100.00",
"notifyUrl": "",
"remark": "测试退款-165948"
}
响应示例
退款成功示例
JSON
{
"code": "0000",
"message": "success",
"timestamp": "2026-07-04T09:56:49.000Z",
"data": {
"refundTransactionNo": "SHRF20260704175649658133",
"transactionNo": "SHPAY202607041623307050108",
"refundAmount": 100,
"totalRefunded": 100,
"isFullRefund": false,
"status": 0
}
}
退款失败示例
JSON
// 退款金额超限
{
"code": "4001",
"message": "退款金额超过可退款金额",
"timestamp": "2026-07-04T09:56:49.000Z",
"data": {}
}
// 订单不存在
{
"code": "4004",
"message": "原订单不存在",
"timestamp": "2026-07-04T09:56:49.000Z",
"data": {}
}
退款异步通知
系统支持退款异步通知功能,可以在完成退款处理后向商户系统发送通知。
通知参数
| 参数名 | 类型 | 说明 |
|---|---|---|
transactionNo | String | 平台订单号 |
refundTransactionNo | String | 退款交易号 |
refundAmount | Number | 退款金额 |
totalRefunded | Number | 累计退款金额 |
isFullRefund | Boolean | 是否全额退款 |
status | Integer | 退款状态:0=成功,1=失败,2=提交成功,3=未知 |
sign | String | 签名 |
通知机制
通知规则:
- 退款请求提交后,系统会根据退款状态的变化主动向商户发送异步通知
- 异步通知采用 POST 方式,商户需确保通知地址可正常接收
- 商户接收到通知后,返回字符串
"SUCCESS"表示已收到通知,否则系统会按一定频率重复发送通知
通知时机
- status=0 退款成功时发送通知
- status=1 退款失败时发送通知
- 状态变更 某些渠道会在退款状态变更时发送通知
签名验证
为确保通知的真实性,商户应该验证签名:
- 将除
sign参数外的所有参数按照字母顺序排序 - 拼接成
key=value&key=value的形式 - 在拼接后的字符串末尾加上商户密钥
- 使用对应的签名算法(MD5 或 RSA2)计算签名
- 将计算得到的签名与通知中的
sign参数比对
代码示例
Java
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
public class OrderRefundExample {
public static void main(String[] args) {
// 退款示例
Map<String, String> refundParams = new HashMap<>();
refundParams.put("refundNo", "REFUND" + System.currentTimeMillis());
refundParams.put("transactionNo", "SHPAY202607041623307050108");
refundParams.put("refundAmount", "100.00");
refundParams.put("notifyUrl", "https://your-domain.com/refund-notify/");
refundParams.put("remark", "测试退款");
String result = submitRefund(refundParams);
System.out.println("退款结果:" + result);
}
public static String submitRefund(Map<String, String> refundParams) {
String openUserid = "your_userId";
String apiKey = "your_api_key";
String apiUrl = "https://openapi.example.com";
Map<String, String> requestData = new HashMap<>();
requestData.put("userId", openUserid);
requestData.put("service", "order.refund.api");
requestData.put("bizContent", JSON.toJSONString(refundParams));
String sign = SignUtil.generateSign(requestData, apiKey);
requestData.put("sign", sign);
String response = HttpUtil.post(apiUrl, requestData);
return response;
}
/** 处理退款异步通知 */
public static String handleRefundNotify(Map<String, String> notifyParams) {
String apiKey = "your_api_key";
String sign = notifyParams.remove("sign");
String calculatedSign = SignUtil.generateSign(notifyParams, apiKey);
if (!sign.equals(calculatedSign)) {
return "FAIL";
}
String status = notifyParams.get("status");
if ("0".equals(status)) {
System.out.println("退款成功");
} else if ("1".equals(status)) {
System.out.println("退款失败");
}
return "SUCCESS";
}
}
Python
import requests
import json
import hashlib
import time
def submit_refund(refund_params):
userId = "your_userId"
api_key = "your_api_key"
api_url = "https://openapi.example.com"
request_data = {
"userId": userId,
"service": "order.refund.api",
"bizContent": json.dumps(refund_params)
}
sign = generate_sign(request_data, api_key)
request_data["sign"] = sign
try:
response = requests.post(api_url, json=request_data, timeout=30)
return response.json()
except Exception as e:
print(f"退款请求失败: {e}")
return None
def generate_sign(params, api_key):
sorted_params = sorted(params.items())
sign_str = "&".join([f"{k}={v}" for k, v in sorted_params if v])
sign_str += f"&key={api_key}"
return hashlib.md5(sign_str.encode()).hexdigest().upper()
def handle_refund_notify(notify_params, api_key):
sign = notify_params.pop('sign', '')
calculated_sign = generate_sign(notify_params, api_key)
if sign != calculated_sign:
return 'FAIL'
status = notify_params.get('status')
if status == '0':
print("退款成功")
elif status == '1':
print("退款失败")
return 'SUCCESS'
# 退款示例
result = submit_refund({
"refundNo": f"REFUND{int(time.time())}",
"transactionNo": "SHPAY202607041623307050108",
"refundAmount": "100.00",
"notifyUrl": "https://your-domain.com/refund-notify/",
"remark": "测试退款"
})
PHP
<?php
class OrderRefund {
private $openUserid = 'your_userId';
private $apiKey = 'your_api_key';
private $apiUrl = 'https://openapi.example.com';
public function submitRefund($refundParams) {
$requestData = [
'userId' => $this->openUserid,
'service' => 'order.refund.api',
'bizContent' => json_encode($refundParams)
];
$requestData['sign'] = $this->generateSign($requestData);
$response = $this->httpPost($this->apiUrl, $requestData);
return json_decode($response, true);
}
public function handleRefundNotify($notifyParams) {
$sign = $notifyParams['sign'];
unset($notifyParams['sign']);
$calculatedSign = $this->generateSign($notifyParams);
if ($sign !== $calculatedSign) { return 'FAIL'; }
if ($notifyParams['status'] === '0') {
// 退款成功
} elseif ($notifyParams['status'] === '1') {
// 退款失败
}
return 'SUCCESS';
}
private function generateSign($params) {
ksort($params);
$signStr = '';
foreach ($params as $key => $value) {
if ($value !== '') { $signStr .= "{$key}={$value}&"; }
}
$signStr .= "key={$this->apiKey}";
return strtoupper(md5($signStr));
}
private function httpPost($url, $data) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}
// 退款示例
$refund = new OrderRefund();
$result = $refund->submitRefund([
'refundNo' => 'REFUND' . time(),
'transactionNo' => 'SHPAY202607041623307050108',
'refundAmount' => '100.00',
'notifyUrl' => 'https://your-domain.com/refund-notify/',
'remark' => '测试退款'
]);
print_r($result);
注意事项
重要提醒:
- 退款订单号(
refundNo)必须保证唯一性,重复的退款订单号会导致退款失败 - 退款金额不能超过原订单的可退款金额
- 部分退款时,需要确保多次退款总金额不超过原订单金额
- 退款结果以异步通知为准,同步返回结果仅表示退款请求已提交
- 一笔退款失败后重新提交,请不要更换退款单号,请使用原退款单号
退款限制:
- 微信支付:订单支付成功后 360 天内可发起退款
- 支付宝:订单支付成功后 360 天内可发起退款
- 银联二维码:订单支付成功后 360 天内可发起退款
- 抖音支付:交易时间超过一年的订单无法提交退款
多次退款规则:
- 同一笔订单多次退款的请求需相隔 1 分钟
- 支持单笔交易分多次退款,每个支付订单的部分退款次数不能超过 50 次
- 多次退款需要提交原支付订单的平台订单号(
transactionNo),并设置不同的退款单号(refundNo) - 申请退款总金额不能超过订单金额
- 如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败
频率限制:
- 错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过 6 次
最佳实践:
- 建议在业务层面记录退款请求流水号,便于后续查询和对账
- 接收到异步通知后,先验证签名再处理业务逻辑
- 处理完成后及时返回 SUCCESS,避免重复接收通知
- 申请退款接口的返回仅代表业务的受理情况,具体退款是否成功,需要通过退款查询接口获取结果