支付宝支付后回调处理(Java版)

您所在的位置:网站首页 支付宝接口请求失败 支付宝支付后回调处理(Java版)

支付宝支付后回调处理(Java版)

2023-12-25 05:16| 来源: 网络整理| 查看: 265

支付宝回调处理文档

前提:

下载alipay-sdkmaven依赖 slf4j,fastjson 认真阅读服务器异步通知页面特性

服务器异步通知页面特性

必须保证服务器异步通知页面(notify_url)上无任何字符,如空格、HTML标签、开发系统自带抛出的异常提示信息等;支付宝是用POST方式发送通知信息,因此该页面中获取参数的方式,如:request.Form(“out_trade_no”)、$_POST[‘out_trade_no’];支付宝主动发起通知,该方式才会被启用;只有在支付宝的交易管理中存在该笔交易,且发生了交易状态的改变,支付宝才会通过该方式发起服务器通知(即时到账交易状态为“等待买家付款”的状态默认是不会发送通知的);服务器间的交互,不像页面跳转同步通知可以在页面上显示出来,这种交互方式是不可见的;第一次交易状态改变(即时到账中此时交易状态是交易完成)时,不仅会返回同步处理结果,而且服务器异步通知页面也会收到支付宝发来的处理结果通知;程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);程序执行完成后,该页面不能执行页面跳转。如果执行页面跳转,支付宝会收不到success字符,会被支付宝服务器判定为该页面程序运行出现异常,而重发处理结果通知;cookies、session等在此页面会失效,即无法获取这些数据;该方式的调试与运行必须在服务器上,即互联网上能访问;该方式的作用主要防止订单丢失,即页面跳转同步通知没有处理订单更新,它则去处理;当商户收到服务器异步通知并打印出success时,服务器异步通知参数notify_id才会失效。也就是说在支付宝发送同一条异步通知时(包含商户并未成功打印出success导致支付宝重发数次通知),服务器异步通知参数notify_id是不变的。

这里使用springmvc的Controller处理,代码如下:

@Controller public class AlipayCallbackController { private static Logger logger = LoggerFactory.getLogger(AlipayCallbackController.class); private ExecutorService executorService = Executors.newFixedThreadPool(20); /** * * 第一步:验证签名,签名通过后进行第二步 * 第二步:按一下步骤进行验证 * 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 * @return */ @RequestMapping("alipay_callback") @ResponseBody public String callback(HttpServletRequest request) { Map params = convertRequestParamsToMap(request); // 将异步通知中收到的待验证所有参数都存放到map中 String paramsJson = JSON.toJSONString(params); logger.info("支付宝回调,{}", paramsJson); try { AlipayConfig alipayConfig = new AlipayConfig();// 支付宝配置 // 调用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 convertRequestParamsToMap(HttpServletRequest request) { Map retMap = new HashMap(); Set entrySet = request.getParameterMap().entrySet(); for (Entry 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 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 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"; // 省略get set }

支付宝返回结果对应的参数类:

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; // 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。 // 省略get set }

注意: 如果做了服务器集群,考虑使用分布式锁,锁住outTradeNo后再处理业务,不然有可能重复处理。

做好订单状态判断,防止重复处理。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3