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

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

自定义业务参数
  • 支付宝

参数

类型

是否必填

最大长度

描述

示例值

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;
    }

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