支付宝支付回调处理后如何返回成功或失败状态
结论
response.setContentType("success"); 的写法不正确。
setContentType() 设置的是 HTTP 响应的媒体类型,例如 text/plain 或 application/json,它不会把 success 写入响应正文。支付宝异步通知处理成功后,应通过 response.getWriter() 返回纯文本 success。处理失败时,可以返回 failure 或其他非 success 内容。
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("success");
支付宝通常根据响应正文是否为 success 来判断商户系统有没有成功接收通知,而不是检查 Content-Type。
正确的处理流程
收到支付宝异步通知后,可按以下顺序处理:
- 获取支付宝提交的全部参数。
- 使用支付宝 SDK 验证签名。
- 校验
app_id、订单号、金额、卖家信息等重要字段。 - 检查交易状态是否符合业务要求。
- 以幂等方式更新订单,防止重复入账或重复发货。
- 确认所有必要操作都已成功后,返回
success。 - 如果验签失败、参数不匹配或业务处理异常,不要返回
success。
必须确认通知合法,且相关业务已经处理完成,才能向支付宝返回成功。过早返回 success,一旦后续业务处理失败,可能无法再通过支付宝的重试通知进行补偿。
Java Servlet 示例
以下是一个简化示例。验签参数和交易状态需要根据实际使用的支付宝产品及 SDK 版本调整。
import com.alipay.api.internal.util.AlipaySignature;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
public void alipayNotify(
HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.setContentType("text/plain;charset=UTF-8");
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
String[] values = entry.getValue();
params.put(entry.getKey(), String.join(",", values));
}
PrintWriter writer = response.getWriter();
try {
boolean signVerified = AlipaySignature.rsaCheckV1(
params,
ALIPAY_PUBLIC_KEY,
"UTF-8",
"RSA2"
);
if (!signVerified) {
writer.write("failure");
return;
}
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
String appId = params.get("app_id");
// 校验通知是否属于当前应用
if (!EXPECTED_APP_ID.equals(appId)) {
writer.write("failure");
return;
}
// 查询本地订单,并校验订单号、金额、收款方等信息
Order order = orderService.findByOrderNo(outTradeNo);
if (order == null || !amountMatches(order, totalAmount)) {
writer.write("failure");
return;
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// 必须保证幂等:同一笔通知重复到达时,不能重复入账或发货
orderService.markPaidIfNecessary(outTradeNo, tradeNo);
writer.write("success");
return;
}
// 对不需要处理的合法状态,应依据具体支付宝产品协议决定响应。
// 如果当前业务不能确认已正确处理,则不要返回 success。
writer.write("failure");
} catch (Exception exception) {
// 记录日志,保留订单号、支付宝交易号和异常信息
logger.error("支付宝异步通知处理失败", exception);
writer.write("failure");
} finally {
writer.flush();
}
}
使用 Spring MVC 时,也可以直接返回字符串:
@PostMapping(
value = "/payment/alipay/notify",
produces = "text/plain;charset=UTF-8"
)
@ResponseBody
public String alipayNotify(HttpServletRequest request) {
try {
Map<String, String> params = extractParameters(request);
if (!verifyAlipaySignature(params)) {
return "failure";
}
validateNotification(params);
paymentService.handleNotificationIdempotently(params);
return "success";
} catch (Exception exception) {
logger.error("支付宝回调处理失败", exception);
return "failure";
}
}
不要返回 JSON:
{"status":"success"}
也不要附加说明文字:
payment success
如果接口协议要求响应正文为 success,就应只返回以下纯文本:
success
为什么 setContentType("success") 无效
这段代码只是尝试把 HTTP Content-Type 设置为 success:
response.setContentType("success");
其效果类似于设置下面的响应头:
Content-Type: success
success 不是常见的合法媒体类型,这段代码也没有向响应正文写入任何内容。支付宝收到的响应正文仍可能是空的,因此无法将它识别为成功确认。
响应类型和响应内容需要分别设置:
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("success");
处理失败时应如何返回
如果验签失败、订单不存在、金额不一致或数据库更新失败,可以返回:
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("failure");
通常情况下,只要响应正文不是协议约定的 success,支付宝就会认为通知没有得到正确处理,并可能按照通知机制再次发送。重复通知属于正常情况,因此回调接口必须具备幂等性。
不建议只通过抛出异常或返回 HTTP 500 表示处理失败。HTTP 状态可以根据系统的异常处理规范设置,但支付宝是否确认通知成功,仍取决于具体产品协议对响应正文的规定。
注意事项
必须先验签
不能只根据 trade_status 更新订单。回调参数可能被伪造,必须使用支付宝公钥和对应的签名算法完成验签。
校验关键业务参数
验签通过后也不能立即更新订单,至少还要核对:
app_id是否属于当前应用;out_trade_no是否为本系统订单;total_amount是否与订单金额一致;- 收款方信息是否符合预期;
- 当前订单状态是否允许变更;
trade_status是否为业务需要处理的状态。
需要校验的具体字段,应以当前接入产品的通知协议为准。
回调处理必须幂等
支付宝可能多次发送同一笔交易的通知。更新订单时,应通过条件更新、唯一约束或事务控制,确保一笔交易的核心业务操作只执行一次。
例如:
UPDATE payment_order
SET status = 'PAID',
alipay_trade_no = ?
WHERE order_no = ?
AND status = 'UNPAID';
如果受影响的行数为 0,还要继续查询订单状态。如果订单已经支付,通常应将其作为重复通知处理,不能再次发货。
返回内容应保持纯净
不要在 success 前后输出 HTML、JSON、调试信息或异常堆栈。同时检查全局拦截器、统一响应包装器和异常处理器,避免返回值被自动包装成:
{
"code": 200,
"data": "success"
}
区分同步跳转和异步通知
浏览器同步跳转地址主要用于展示支付结果,不适合作为最终记账依据。订单支付状态应以通过验签和业务校验的异步通知为准,必要时可主动调用支付宝交易查询接口核实。
成功响应的关键是将纯文本 success 写入 HTTP 响应正文,而不是调用 response.setContentType("success")。最终的响应格式以及需要处理的交易状态,应以当前支付宝产品的异步通知文档和所使用的 SDK 版本为准。
备注:内容仅供参考。
