4-8 扩展退款创建与退款通知获取
退款接口概览
申请退款
| 项目 | 值 |
|---|---|
| URL | https://api.mch.weixin.qq.com/v3/refund/domestic/refunds |
| 方法 | POST |
查询单笔退款
| 项目 | 值 |
|---|---|
| URL | https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/{out_refund_no} |
| 方法 | GET |
TypeScript 接口定义
退款请求参数
// 基础退款参数
interface RefundRequestBase {
out_refund_no: string; // 商户退款单号(必填)
reason?: string; // 退款原因
notify_url?: string; // 退款结果回调地址
funds_account?: string; // 退款资金来源
amount: {
refund: number; // 退款金额(单位:分)
total: number; // 原订单金额(单位:分)
currency?: string; // 退款币种(CNY)
};
goods_detail?: Array<{
merchant_goods_id: string;
wechatpay_goods_id?: string;
goods_name?: string;
unit_price: number;
refund_amount: number;
refund_quantity: number;
}>;
}
// 使用微信订单号退款
interface RefundWithTransactionId extends RefundRequestBase {
transaction_id: string; // 微信支付订单号
}
// 使用商户订单号退款
interface RefundWithOutTradeNo extends RefundRequestBase {
out_trade_no: string; // 商户订单号
}
// 联合类型:二选一
export type RefundRequest =
| RefundWithTransactionId
| RefundWithOutTradeNo;
typescript
transaction_id 和 out_trade_no 二选一的设计通过 TypeScript 联合类型实现了类型级别的约束。
.env 配置补充
# 退款通知回调地址(与支付通知分开)
WECHAT_NOTIFY_REFUND=https://wechat-dev.domain.com/api/v1/pay/notify-refund
bash
PayService 退款方法
申请退款
async refund(params: RefundRequest) {
try {
const urlPath = '/v3/refund/domestic/refunds';
// 补充退款通知地址
const refundParams = {
...params,
notify_url: this.configService.get<string>('WECHAT_NOTIFY_REFUND'),
out_refund_no: params.out_refund_no || this.getTradeNumber(),
};
const body = JSON.stringify(refundParams);
const headers = this.getSignHeaders('POST', urlPath, body);
const result = await this.axiosInstance.post(
`https://api.mch.weixin.qq.com${urlPath}`,
refundParams,
{ headers },
);
this.logger.log('退款结果', JSON.stringify(result.data));
return result.data;
} catch (error) {
this.logger.error('退款失败', error.message);
throw error;
}
}
typescript
查询单笔退款
async getRefundInfo(outRefundNo: string) {
try {
const urlPath = `/v3/refund/domestic/refunds/${outRefundNo}`;
const headers = this.getSignHeaders('GET', urlPath, '');
const result = await this.axiosInstance.get(
`https://api.mch.weixin.qq.com${urlPath}`,
{ headers },
);
return result.data;
} catch (error) {
this.logger.error('查询退款失败', error.message);
throw error;
}
}
typescript
Controller 退款接口
@Controller('pay')
export class PayController {
constructor(private readonly payService: PayService) {}
// ... 之前的接口
/**
* 申请退款(测试用,生产环境需加审核流程)
*/
@Post('refund')
refund(@Body() body: RefundRequest) {
return this.payService.refund(body);
}
/**
* 查询退款状态
*/
@Get('refund/:outRefundNo')
getRefundInfo(@Param('outRefundNo') outRefundNo: string) {
return this.payService.getRefundInfo(outRefundNo);
}
/**
* 退款结果通知回调
*/
@Post('notify-refund')
async notifyRefund(@Body() body: WechatPayNotification) {
this.logger.log('收到退款通知', JSON.stringify(body));
try {
const result = this.payService.decryptApiV3(
body.resource.ciphertext,
body.resource.nonce,
body.resource.associated_data,
);
const refundResult = JSON.parse(result);
this.logger.log('解密后的退款数据', JSON.stringify(refundResult, null, 2));
// TODO: 更新数据库退款状态
return 'OK';
} catch (error) {
this.logger.error('解密退款通知失败', error.message);
return 'OK';
}
}
}
typescript
测试退款
退款请求示例
POST /api/v1/pay/refund
{
"out_trade_no": "20260518143015001_2345678901",
"out_refund_no": "20260518150000001_1234567890",
"reason": "七天无理由退款",
"amount": {
"refund": 1,
"total": 1,
"currency": "CNY"
}
}
json
| 参数 | 说明 |
|---|---|
out_trade_no | 之前支付成功的商户订单号 |
out_refund_no | 退款单号(使用 getTradeNumber() 生成) |
amount.refund | 退款金额(单位:分) |
amount.total | 原订单总金额(单位:分) |
退款成功响应
退款成功后,微信返回退款状态信息。同时微信会向用户推送退款到账通知,用户微信上会收到退款消息。
退款资金会原路返回到用户的支付账户(如零钱、银行卡等)。
查询退款状态
GET /api/v1/pay/refund/20260518150000001_1234567890
json
响应示例:
{
"refund_id": "50300901202405181234567890",
"out_refund_no": "20260518150000001_1234567890",
"transaction_id": "4200001234202605181234567890",
"out_trade_no": "20260518143015001_2345678901",
"channel": "ORIGINAL",
"funds_account": "NORMAL",
"amount": {
"total": 1,
"refund": 1,
"from": [
{
"account": "NORMAL",
"amount": 1
}
]
},
"status": "SUCCESS",
"success_time": "2026-05-18T15:00:30+08:00",
"user_received_account": "用户零钱"
}
json
退款业务安全注意事项
| 注意点 | 说明 |
|---|---|
| 接口安全 | 前端只传订单 ID 和退款原因,金额在服务端查询后填入 |
| 审核机制 | 生产环境建议加商户审核流程,审核通过后再调用退款接口 |
| 部分退款 | amount.refund 可以小于 amount.total,支持多次部分退款 |
| 退款单号唯一 | out_refund_no 必须全局唯一,避免重复退款 |
| 测试清理 | 测试用的退款接口不应暴露在生产环境中 |
参考资源
↑