微信支付和支付宝支付异步回调篇
前言: 第一章大概说明了,微信和支付宝大概支付的流程,这篇做个补充. 一般支付都要回调时补充自定义业务参数.
自定义业务参数
- 支付宝
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
passback_params | String | 可选 | 512 | 公用回传参数。 如果请求时传递了该参数,支付宝会在异步通知时将该参数原样返回。 本参数必须进行UrlEncode之后才可以发送给支付宝。 | merchantBizType%3d3C%26merchantBizNo%3d2016010101111 |
注意: passback_params:公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数。支付宝只会在异步通知时将该参数原样返回。
本参数必须进行UrlEncode之后才可以发送给支付宝 (注意不能为json)
示例:
//示例 merchantBizType=3C&merchantBizNo=2016010101111
String attach= "orderId="+payRequest.getOrderId()+"&payTradeType="+payRequest.getTradeType();
String passback_params = URLEncoder.encode(attach, "UTF-8");
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"body\":\""+ body +"\","
+ "\"passback_params\":\""+ passback_params +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//请求
String result = alipayClient.pageExecute(alipayRequest).getBody();
-
微信
字段名 变量名 必填 类型 示例值 描述 附加数据 attach 否 String(127) 深圳分店 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 注意: 与支付宝不同的是,这个字段可以为json,并且主动查询,和异步回调都会返回,只要注意类型长度就行。
示例:
//附加数据 String attach= "{\"orderId\":"+request.getOrderId()+",\"payTradeType\":"+request.getTradeType()+"}"; orderRequest = WxPayUnifiedOrderRequest.newBuilder().body(request.getBody()) // .totalFee(request.getPayAmount().multiply(new BigDecimal(PayConstant.multiple)).intValue()) .totalFee((int)request.getPayAmount()) .spbillCreateIp(request.getSpbillCreateIp()) .notifyUrl(wxCustomConfig.getWexinNotifyUrl()) .tradeType(tradeTypeStr) .outTradeNo(request.getRequestOrderSn()) .attach(attach) .productId(request.getSkuId()) .deviceInfo((PayTradeTypeEnum.PC_PAY_TRADE_TYPE.getCode().intValue() == request.getTradeType()) ? "WEB" : "") .build(); //创建预付订单 result = this.createOrder(orderRequest);
异步回调
配置中得notify_url
-
微信
异步回调,有两种,一种是微信公众号(统称H5),还有一种是原生APP(开放平台)
微信异步回调文档地址: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
响应成功返回 “OK” 失败返回 “NO”
@PostMapping(value = "app/order") @ApiOperation("微信H5回调") public String wxAppParseOrderNotifyResult(HttpServletRequest httpRequest, HttpServletResponse response) { String xmlData = null; InputStream is = null; try { is = httpRequest.getInputStream(); xmlData = IOUtils.toString(is, StandardCharsets.UTF_8); log.info("Log-in-wx-parseOrderNotifyResult,接收参数:{}", WxNotifyResponse.filterIllegalityChar(xmlData)); if (StringUtils.isEmpty(xmlData)) { return ackXml(FAIL_ACK_FLAG, response); } //app 类型 return wxCallBackInfo(xmlData, PayChannelEnum.PayConfigTypeEnum.APP.getCode(), response); } catch (Exception e) { if (is != null) { try { is.close(); } catch (IOException e1) { } } log.error("ERROR-IN-parseOrderNotifyResult,xmlData:{}", xmlData); } return ackXml(FAIL_ACK_FLAG, response); } @PostMapping(value = "h5/order") @ApiOperation("微信H5回调") public String wxH5ParseOrderNotifyResult(HttpServletRequest httpRequest, HttpServletResponse response) { String xmlData = null; InputStream is = null; try { is = httpRequest.getInputStream(); xmlData = IOUtils.toString(is, StandardCharsets.UTF_8); log.info("Log-in-wx-parseOrderNotifyResult,接收参数:{}", WxNotifyResponse.filterIllegalityChar(xmlData)); if (StringUtils.isEmpty(xmlData)) { return ackXml(FAIL_ACK_FLAG, response); } //h5 类型 return wxCallBackInfo(xmlData, PayChannelEnum.PayConfigTypeEnum.H5.getCode(), response); } catch (Exception e) { if (is != null) { try { is.close(); } catch (IOException e1) { } } log.error("ERROR-IN-parseOrderNotifyResult,xmlData:{}", xmlData); } return ackXml(FAIL_ACK_FLAG, response); } private String ackXml(String msg, HttpServletResponse response) { String responseContent = null; if (SUCCESS_ACK_FLAG.equalsIgnoreCase(msg)) { responseContent = WxNotifyResponse.successAckXml(SUCCESS_ACK_FLAG); } else { responseContent = WxNotifyResponse.failAckXml(FAIL_ACK_FLAG); } //response.getWriter().println(responseContent); return responseContent; } //业务逻辑处理 public String wxCallBackInfo(String xmlData, Integer configType, HttpServletResponse response) throws IOException { //xml 数据转map ReturnValue<PayOrderNotifyResponse> wxPayRcvContent = thirdApi.getWxPayRcvContent(xmlData, configType); log.info("Log-in-wx-parseOrderNotifyResult,处理接收参数:{}", JSONUtils.toString(wxPayRcvContent)); if (wxPayRcvContent.isFailed() || Objects.isNull(wxPayRcvContent.getValue())) { return ackXml(FAIL_ACK_FLAG, response); } PayOrderNotifyResponse wxPayRcvContentValue = wxPayRcvContent.getValue(); String attach = wxPayRcvContentValue.getAttach(); PayOrderResult payOrderResult = JSONUtils.parseObject(attach, PayOrderResult.class); String outTradeNo = wxPayRcvContentValue.getOutTradeNo(); String requestId = UUID.randomUUID().toString(); boolean lockStatus = false; int count = 1; while (!lockStatus) { lockStatus = redisBean.lock().tryLock(RedisKey.getWxPayCallBackKey(outTradeNo), requestId, TimeUnit.SECONDS.toSeconds(30)); if (lockStatus) { if ("SUCCESS".equals(wxPayRcvContentValue.getResultCode())) { log.info("[微信][加锁]成功 ,尝试:{}次。outTradeNo:{}", count, RedisKey.getAliPayCallBackKey(outTradeNo)); PayRequest request = new PayRequest(); request.setPayType(PayChannelEnum.PayTypeEnum.WEIPAY.getCode()); request.setRequestOrderSn(outTradeNo); request.setPayAmount(wxPayRcvContent.getValue().getTotalFee()); request.setThirdTradeNo(wxPayRcvContent.getValue().getTransactionId()); request.setPayStatus(PayStatusEnum.SUCCESS.equals(wxPayRcvContent.getValue().getResultCode()) ? PayStatusEnum.PayOrderStatusEnum.SUCCESS_PAY_STATUS_PAY.getCode() : PayStatusEnum.PayOrderStatusEnum.FAIL_PAY_STATUS_PAY.getCode()); //返回的时间 yyyyMMddHHmmSS request.setPaySuccessTime(DateUtils.toDate(wxPayRcvContent.getValue().getTimeEnd(), DateUtils.FORMAT_FOUR).getTime()); log.info("微信回调自定义参数:"); if(Objects.nonNull(payOrderResult)){ request.setOrderId(payOrderResult.getOrderId()); request.setTradeType(payOrderResult.getPayTradeType()); } try { ReturnValue<Boolean> messageVo = payApi.updateNotifyPay(request); log.info("Log-in-wx-parseOrderNotifyResult,处理更新支付结果参数:{},更新结果:{}", JSONUtils.toString(request), JSONUtils.toString(messageVo)); if (messageVo.isSuccessful()) { log.info("回调成功:返回微信格式消息给微信"); return ackXml(SUCCESS_ACK_FLAG, response); } } catch (Exception e) { log.error("更新支付流水失败:{}", JSONUtils.toString(request), e); } finally { redisBean.lock().tryReleaseLock(RedisKey.getWxPayCallBackKey(outTradeNo), requestId); } } else { //释放锁 redisBean.lock().tryReleaseLock(RedisKey.getWxPayCallBackKey(outTradeNo), requestId); } } else { log.info("[微信][加锁]失败,尝试:{}次。outTradeNo:{}", count, RedisKey.getAliPayCallBackKey(outTradeNo)); count++; } if (count > 3) { //redisBean.lock().tryReleaseLock(RedisKey.getAliPayCallBackKey(outTradeNo), requestId); return ackXml(FAIL_ACK_FLAG, response); } } return ackXml(FAIL_ACK_FLAG, response); //微信须返回这样得格式 /* private final String successXmlBack= "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg></xml>"; private final String failureXmlBack= "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg></xml> ";*/
-
支付宝
支付宝回调大致与微信一样。
业务处理正常返回 “success” 反之失败返回 “failure”
异步文档地址 : https://opendocs.alipay.com/open/204/105301
/** * {返回格式 * "memo" : "xxxxx", * "result" : "{ * \"alipay_trade_app_pay_response\":{ * \"code\":\"10000\", * \"msg\":\"Success\", * \"app_id\":\"2014072300007148\", * \"out_trade_no\":\"081622560194853\", * \"trade_no\":\"2016081621001004400236957647\", * \"total_amount\":\"0.01\", * \"seller_id\":\"2088702849871851\", * \"charset\":\"utf-8\", * \"timestamp\":\"2016-10-11 17:43:36\" * }, * \"sign\":\"NGfStJf3i3ooWBuCDIQSumOpaGBcQz+aoAqyGh3W6EqA/gmyPYwLJ2REFijY9XPTApI9YglZyMw+ZMhd3kb0mh4RAXMrb6mekX4Zu8Nf6geOwIa9kLOnw0IMCjxi4abDIfXhxrXyj********\", * \"sign_type\":\"RSA2\" * }", * "resultStatus" : "9000" * } 异步文档地址 : https://opendocs.alipay.com/open/204/105301 * * @param httpRequest 请求参数 */ @PostMapping(value = "app/order") @ApiOperation("支付宝APP回调") public String aliAppOrderNotifyResult(HttpServletRequest httpRequest) throws AlipayApiException, UnsupportedEncodingException { return aliCallbackInfo(httpRequest, PayChannelEnum.PayConfigTypeEnum.APP.getCode()); } @PostMapping(value = "h5/order") @ApiOperation("支付宝H5回调") public String aliH5OrderNotifyResult(HttpServletRequest httpRequest) throws AlipayApiException, UnsupportedEncodingException { return aliCallbackInfo(httpRequest, PayChannelEnum.PayConfigTypeEnum.H5.getCode()); } //urlget 参数转map public static Map<String,String> urlParamsToMap(String urlparam){ Map<String,String> map = new HashMap<String,String>(); String[] param = urlparam.split("&"); for(String keyvalue:param){ String[] pair = keyvalue.split("="); if(pair.length==2){ map.put(pair[0], pair[1]); } } return map; } /** * 程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是 success 这7个字符,支付宝服务器会不断重发通知, * 直到超过 24 小时 22 分钟。一般情况下,25 小时以内完成 8 次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h)。 * 注意,回调的时候是用的支付宝公钥而不是应用公钥 */ private String aliCallbackInfo(HttpServletRequest httpRequest, Integer confType) throws AlipayApiException, BusinessException, UnsupportedEncodingException { Map<String, String> params = convertRequestParamsToMap(httpRequest); PayRequest payRequest = new PayRequest(); log.info("支付宝回调,{}", JSONUtils.toString(params)); // 商户订单号(流水每次生成的订单号) String outTradeNo = params.get("out_trade_no"); String thirdTradeNo = params.get("trade_no"); String totalAmount = params.get("total_amount"); String msg = params.get("msg"); String code = params.get("code"); //获取自定义回调参数 String passback_params = URLDecoder.decode(params.get("passback_params"), "UTF-8"); //step1: 验证订单是否存在 OrderPayFlow byRequestTradeNo = orderPayFlowService.getByRequestTradeNo(outTradeNo); if (Objects.isNull(byRequestTradeNo)) { log.error("订单不存在 outTradeNo :{}", outTradeNo); //throw new BusinessException(ORDER_ERROR_CODE_3300003.getCode(), ORDER_ERROR_CODE_3300003.getDesc()); return fail; } //step2: 验证订单是否支付 if (byRequestTradeNo.getStatus()) { log.info("订单已支付,已主动查询,返回支付宝success"); // throw new BusinessException(ORDER_ERROR_CODE_3300004.getCode(), ORDER_ERROR_CODE_3300004.getDesc()); return success; } AliCustomConfig alipayConfig = null; if (confType.equals(PayChannelEnum.PayConfigTypeEnum.APP.getCode())) { alipayConfig = aliPayAppConfig; } else { alipayConfig = aliPayH5Config; } // step3 : 验证签名 调用SDK验证签名 boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getAliPayPublicKey(), alipayConfig.getCharset(), alipayConfig.getSignType()); // steo 4: 成功返回 ,更改订单状态 String key = outTradeNo; String requestId = UUID.randomUUID().toString(); boolean lockStatus = false; int count = 1; while (!lockStatus) { lockStatus = redisBean.lock().tryLock(RedisKey.getAliPayCallBackKey(key), requestId, TimeUnit.SECONDS.toSeconds(30)); if (lockStatus) { if (signVerified) { payRequest.setPayType(PayChannelEnum.PayTypeEnum.ALIPAY.getCode()); payRequest.setRequestOrderSn(outTradeNo); double amount = Double.parseDouble(totalAmount) * 100; payRequest.setPayAmount((long) amount); payRequest.setThirdTradeNo(thirdTradeNo); payRequest.setPayStatus(PayStatusEnum.PayOrderStatusEnum.SUCCESS_PAY_STATUS_PAY.getCode()); payRequest.setPaySuccessTime(System.currentTimeMillis()); try { log.info("[支付宝]开始更新流水和订单状态:{}", JSONUtils.toString(payRequest)); ReturnValue<Boolean> messageVo = payApi.updateNotifyPay(payRequest); if (messageVo.isSuccessful()) { log.info("回调成功:返回给支付宝"); return success; } } catch (Exception e) { log.error("开始更新流水和订单状态失败:{}", JSONUtils.toString(payRequest)); } finally { redisBean.lock().tryReleaseLock(RedisKey.getAliPayCallBackKey(key), requestId); } } else { redisBean.lock().tryReleaseLock(RedisKey.getAliPayCallBackKey(key), requestId); log.error("验证签名失败,信息 code:{} ,msg:{}", code, msg); return fail; } } else { log.info("[支付宝][加锁]失败,尝试:{}次。outTradeNo:{}", count, RedisKey.getAliPayCallBackKey(key)); count++; } if (count > 3) { //redisBean.lock().tryReleaseLock(RedisKey.getAliPayCallBackKey(key), requestId); return fail; } } return fail; } /** * 将request中的参数转换成Map * * @param request 入参 * @return map 参数 */ public static Map<String, String> convertRequestParamsToMap(HttpServletRequest request) { Map<String, String> params = new HashMap<>(); Map<String, String[]> requestParams = request.getParameterMap(); for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) { String name = (String) iter.next(); String[] values = requestParams.get(name); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } params.put(name, valueStr); } return params; }
两种回调大致处理逻辑相同,主要看你自己得回调业务逻辑怎么走。喜欢可以点赞,板砖不易!