支付宝回调处理文档
前提:
服务器异步通知页面特性
这里使用springmvc的Controller处理,代码如下:
@Controller
public class AlipayCallbackController {
private static Logger logger = LoggerFactory.getLogger(AlipayCallbackController.class);
@Autowired
private AlipayConfig alipayConfig; // 支付宝支付配置
private ExecutorService executorService = Executors.newFixedThreadPool(20);
/**
* <pre>
* 第一步:验证签名,签名通过后进行第二步
* 第二步:按一下步骤进行验证
* 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
* 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
* 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
* 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
* 在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
* 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
* </pre>
*
* @param params
* @return
*/
@RequestMapping("alipay_callback")
@ResponseBody
public String callback(HttpServletRequest request) {
Map<String, String> params = convertRequestParamsToMap(request); // 将异步通知中收到的待验证所有参数都存放到map中
String paramsJson = JSON.toJSONString(params);
logger.info("支付宝回调,{}", paramsJson);
try {
// 调用SDK验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getAlipay_public_key(),
alipayConfig.getCharset(), alipayConfig.getSigntype());
if (signVerified) {
logger.info("支付宝回调签名认证成功");
// 按照支付结果异步通知中的描述,对支付结果中的业务内容进行1/2/3/4二次校验,校验成功后在response中返回success,校验失败返回failure
this.check(params);
// 另起线程处理业务
executorService.execute(new Runnable() {
@Override
public void run() {
AlipayNotifyParam param = buildAlipayNotifyParam(params);
String trade_status = param.getTradeStatus();
// 支付成功
if (trade_status.equals(AlipayTradeStatus.TRADE_SUCCESS.getStatus())
|| trade_status.equals(AlipayTradeStatus.TRADE_FINISHED.getStatus())) {
// 处理支付成功逻辑
try {
/*
// 处理业务逻辑。。。
myService.process(param);
*/
} catch (Exception e) {
logger.error("支付宝回调业务处理报错,params:" + paramsJson, e);
}
} else {
logger.error("没有处理支付宝回调业务,支付宝交易状态:{},params:{}",trade_status,paramsJson);
}
}
});
// 如果签名验证正确,立即返回success,后续业务另起线程单独处理
// 业务处理失败,可查看日志进行补偿,跟支付宝已经没多大关系。
return "success";
} else {
logger.info("支付宝回调签名认证失败,signVerified=false, paramsJson:{}", paramsJson);
return "failure";
}
} catch (AlipayApiException e) {
logger.error("支付宝回调签名认证失败,paramsJson:{},errorMsg:{}", paramsJson, e.getMessage());
return "failure";
}
}
// 将request中的参数转换成Map
private static Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
Map<String, String> retMap = new HashMap<String, String>();
Set<Entry<String, String[]>> entrySet = request.getParameterMap().entrySet();
for (Entry<String, String[]> entry : entrySet) {
String name = entry.getKey();
String[] values = entry.getValue();
int valLen = values.length;
if (valLen == 1) {
retMap.put(name, values[0]);
} else if (valLen > 1) {
StringBuilder sb = new StringBuilder();
for (String val : values) {
sb.append(",").append(val);
}
retMap.put(name, sb.toString().substring(1));
} else {
retMap.put(name, "");
}
}
return retMap;
}
private AlipayNotifyParam buildAlipayNotifyParam(Map<String, String> params) {
String json = JSON.toJSONString(params);
return JSON.parseObject(json, AlipayNotifyParam.class);
}
/**
* 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
* 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
* 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
* 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
* 在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
* 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
*
* @param params
* @throws AlipayApiException
*/
private void check(Map<String, String> params) throws AlipayApiException {
String outTradeNo = params.get("out_trade_no");
// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
Order order = getOrderByOutTradeNo(outTradeNo); // 这个方法自己实现
if (order == null) {
throw new AlipayApiException("out_trade_no错误");
}
// 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
long total_amount = new BigDecimal(params.get("total_amount")).multiply(new BigDecimal(100)).longValue();
if (total_amount != order.getPayPrice().longValue()) {
throw new AlipayApiException("error total_amount");
}
// 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
// 第三步可根据实际情况省略
// 4、验证app_id是否为该商户本身。
if (!params.get("app_id").equals(alipayConfig.getAppid())) {
throw new AlipayApiException("app_id不一致");
}
}
}
支付宝参数配置:
@Component
public class AlipayConfig {
// 商户ID
private String appid = "";
// 私钥
private String rsa_private_key = "";
// 异步回调地址
private String notify_url;
// 同步回调地址
private String return_url;
// 请求网关地址
private String gateway_url;
// 编码
private String charset = "UTF-8";
// 返回格式
private String format = "json";
// 支付宝公钥
private String alipay_public_key = "";
// RSA2
private String signtype = "RSA2";
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getRsa_private_key() {
return rsa_private_key;
}
public void setRsa_private_key(String rsa_private_key) {
this.rsa_private_key = rsa_private_key;
}
public String getNotify_url() {
return notify_url;
}
public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
}
public String getReturn_url() {
return return_url;
}
public void setReturn_url(String return_url) {
this.return_url = return_url;
}
public String getGateway_url() {
return gateway_url;
}
public void setGateway_url(String gateway_url) {
this.gateway_url = gateway_url;
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getAlipay_public_key() {
return alipay_public_key;
}
public void setAlipay_public_key(String alipay_public_key) {
this.alipay_public_key = alipay_public_key;
}
public String getSigntype() {
return signtype;
}
public void setSigntype(String signtype) {
this.signtype = signtype;
}
}
支付宝返回结果对应的参数类:
public class AlipayNotifyParam implements Serializable {
private String appId;
private String tradeNo; // 支付宝交易凭证号
private String outTradeNo; // 原支付请求的商户订单号
private String outBizNo; // 商户业务ID,主要是退款通知中返回退款申请的流水号
private String buyerId; // 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字
private String buyerLogonId; // 买家支付宝账号
private String sellerId; // 卖家支付宝用户号
private String sellerEmail; // 卖家支付宝账号
private String tradeStatus; // 交易目前所处的状态,见交易状态说明
private BigDecimal totalAmount; // 本次交易支付的订单金额
private BigDecimal receiptAmount; // 商家在交易中实际收到的款项
private BigDecimal buyerPayAmount; // 用户在交易中支付的金额
private BigDecimal refundFee; // 退款通知中,返回总退款金额,单位为元,支持两位小数
private String subject; // 商品的标题/交易标题/订单标题/订单关键字等
private String body; // 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来
private Date gmtCreate; // 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss
private Date gmtPayment; // 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss
private Date gmtRefund; // 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S
private Date gmtClose; // 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss
private String fundBillList; // 支付成功的各个渠道金额信息,array
private String passbackParams; // 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。
public void setAppId(String appId) {
this.appId = appId;
}
public String getAppId() {
return this.appId;
}
/** 设置 支付宝交易凭证号,对应字段 trade_record_alipay_detail.trade_no */
public void setTradeNo(String tradeNo) {
this.tradeNo = tradeNo;
}
/** 获取 支付宝交易凭证号,对应字段 trade_record_alipay_detail.trade_no */
public String getTradeNo() {
return this.tradeNo;
}
/** 设置 原支付请求的商户订单号,对应字段 trade_record_alipay_detail.out_trade_no */
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
/** 获取 原支付请求的商户订单号,对应字段 trade_record_alipay_detail.out_trade_no */
public String getOutTradeNo() {
return this.outTradeNo;
}
/** 设置 商户业务ID,主要是退款通知中返回退款申请的流水号,对应字段 trade_record_alipay_detail.out_biz_no */
public void setOutBizNo(String outBizNo) {
this.outBizNo = outBizNo;
}
/** 获取 商户业务ID,主要是退款通知中返回退款申请的流水号,对应字段 trade_record_alipay_detail.out_biz_no */
public String getOutBizNo() {
return this.outBizNo;
}
/** 设置 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字,对应字段 trade_record_alipay_detail.buyer_id */
public void setBuyerId(String buyerId) {
this.buyerId = buyerId;
}
/** 获取 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字,对应字段 trade_record_alipay_detail.buyer_id */
public String getBuyerId() {
return this.buyerId;
}
/** 设置 买家支付宝账号,对应字段 trade_record_alipay_detail.buyer_logon_id */
public void setBuyerLogonId(String buyerLogonId) {
this.buyerLogonId = buyerLogonId;
}
/** 获取 买家支付宝账号,对应字段 trade_record_alipay_detail.buyer_logon_id */
public String getBuyerLogonId() {
return this.buyerLogonId;
}
/** 设置 卖家支付宝用户号,对应字段 trade_record_alipay_detail.seller_id */
public void setSellerId(String sellerId) {
this.sellerId = sellerId;
}
/** 获取 卖家支付宝用户号,对应字段 trade_record_alipay_detail.seller_id */
public String getSellerId() {
return this.sellerId;
}
/** 设置 卖家支付宝账号,对应字段 trade_record_alipay_detail.seller_email */
public void setSellerEmail(String sellerEmail) {
this.sellerEmail = sellerEmail;
}
/** 获取 卖家支付宝账号,对应字段 trade_record_alipay_detail.seller_email */
public String getSellerEmail() {
return this.sellerEmail;
}
/** 设置 交易目前所处的状态,见交易状态说明,对应字段 trade_record_alipay_detail.trade_status */
public void setTradeStatus(String tradeStatus) {
this.tradeStatus = tradeStatus;
}
/** 获取 交易目前所处的状态,见交易状态说明,对应字段 trade_record_alipay_detail.trade_status */
public String getTradeStatus() {
return this.tradeStatus;
}
/** 设置 本次交易支付的订单金额,对应字段 trade_record_alipay_detail.total_amount */
public void setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
}
/** 获取 本次交易支付的订单金额,对应字段 trade_record_alipay_detail.total_amount */
public BigDecimal getTotalAmount() {
return this.totalAmount;
}
/** 设置 商家在交易中实际收到的款项,对应字段 trade_record_alipay_detail.receipt_amount */
public void setReceiptAmount(BigDecimal receiptAmount) {
this.receiptAmount = receiptAmount;
}
/** 获取 商家在交易中实际收到的款项,对应字段 trade_record_alipay_detail.receipt_amount */
public BigDecimal getReceiptAmount() {
return this.receiptAmount;
}
/** 设置 用户在交易中支付的金额,对应字段 trade_record_alipay_detail.buyer_pay_amount */
public void setBuyerPayAmount(BigDecimal buyerPayAmount) {
this.buyerPayAmount = buyerPayAmount;
}
/** 获取 用户在交易中支付的金额,对应字段 trade_record_alipay_detail.buyer_pay_amount */
public BigDecimal getBuyerPayAmount() {
return this.buyerPayAmount;
}
/** 设置 退款通知中,返回总退款金额,单位为元,支持两位小数,对应字段 trade_record_alipay_detail.refund_fee */
public void setRefundFee(BigDecimal refundFee) {
this.refundFee = refundFee;
}
/** 获取 退款通知中,返回总退款金额,单位为元,支持两位小数,对应字段 trade_record_alipay_detail.refund_fee */
public BigDecimal getRefundFee() {
return this.refundFee;
}
/** 设置 商品的标题/交易标题/订单标题/订单关键字等,对应字段 trade_record_alipay_detail.subject */
public void setSubject(String subject) {
this.subject = subject;
}
/** 获取 商品的标题/交易标题/订单标题/订单关键字等,对应字段 trade_record_alipay_detail.subject */
public String getSubject() {
return this.subject;
}
/** 设置 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来,对应字段 trade_record_alipay_detail.body */
public void setBody(String body) {
this.body = body;
}
/** 获取 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来,对应字段 trade_record_alipay_detail.body */
public String getBody() {
return this.body;
}
/** 设置 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_create */
public void setGmtCreate(Date gmtCreate) {
this.gmtCreate = gmtCreate;
}
/** 获取 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_create */
public Date getGmtCreate() {
return this.gmtCreate;
}
/** 设置 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_payment */
public void setGmtPayment(Date gmtPayment) {
this.gmtPayment = gmtPayment;
}
/** 获取 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_payment */
public Date getGmtPayment() {
return this.gmtPayment;
}
/** 设置 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S,对应字段 trade_record_alipay_detail.gmt_refund */
public void setGmtRefund(Date gmtRefund) {
this.gmtRefund = gmtRefund;
}
/** 获取 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S,对应字段 trade_record_alipay_detail.gmt_refund */
public Date getGmtRefund() {
return this.gmtRefund;
}
/** 设置 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_close */
public void setGmtClose(Date gmtClose) {
this.gmtClose = gmtClose;
}
/** 获取 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss,对应字段 trade_record_alipay_detail.gmt_close */
public Date getGmtClose() {
return this.gmtClose;
}
/** 设置 支付成功的各个渠道金额信息,array,对应字段 trade_record_alipay_detail.fund_bill_list */
public void setFundBillList(String fundBillList) {
this.fundBillList = fundBillList;
}
/** 获取 支付成功的各个渠道金额信息,array,对应字段 trade_record_alipay_detail.fund_bill_list */
public String getFundBillList() {
return this.fundBillList;
}
/** 设置 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。,对应字段 trade_record_alipay_detail.passback_params */
public void setPassbackParams(String passbackParams) {
this.passbackParams = passbackParams;
}
/** 获取 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。,对应字段 trade_record_alipay_detail.passback_params */
public String getPassbackParams() {
return this.passbackParams;
}
}