支付宝回调处理文档
前提:
服务器异步通知页面特性
这里使用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; } }