微信支付和支付宝支付整合(异步回调篇)

微信支付和支付宝支付异步回调篇

前言: 第一章大概说明了,微信和支付宝大概支付的流程,这篇做个补充. 一般支付都要回调时补充自定义业务参数.

自定义业务参数
  • 支付宝
参数类型是否必填最大长度描述示例值
passback_paramsString可选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();
  • 微信

    字段名变量名必填类型示例值描述
    附加数据attachString(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;
        }
    

两种回调大致处理逻辑相同,主要看你自己得回调业务逻辑怎么走。喜欢可以点赞,板砖不易!

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值