在线支付系列【15】微信支付实战篇之集成查询订单、支付通知API

文章介绍了在微信支付中,商户如何通过主动调用来查询订单状态,以及设置回调通知地址来获取用户支付成功的信息。详细阐述了主动调用查询接口的实现步骤,以及回调通知的处理流程,包括验签、解密和订单状态更新。同时,提供了测试方法以确保通知机制的正常运作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有道无术,术尚可求,有术无道,止于术。

前言

在上篇文档中,我们简单实现了Native支付下单功能,根据订单生成二维码,用户扫码支付。

但是用户支付是不经过商家的,商家需要通过以下两种方式获取订单状态:

  • 支付结果通知:用户支付成功后,微信支付会将支付成功的结果以回调通知的形式同步给商户,商户的回调地址需要在调用下单API时传入notify_url参数(10)。
  • 主动调用:当因网络抖动或本身notify_url存在问题等原因,导致无法接收到回调通知时,商户也可主动调用查询订单API来获取订单状态(11)。

用户支付,商家收到支付成功消息,进行发货操作(12)。

主动调用

微信官方API文档可以查看接口详情。
在这里插入图片描述

商户订单号查询

紧接上文,在WechatPayNativeApiEnum添加接口地址:

QUERY_BY_MERCHANT_ORDER_NO("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s?mchid=%s", "商户订单号查询订单"),

WechatPayService接口声明根据商户订单号查询订单方法:

    /**
     * 根据商户订单号查询订单
     *
     * @param outTradeNo 商户订单号:商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一。特殊规则:最小字符长度为6
     * @param merchantId 商户ID:直连商户的商户号
     * @throws Exception 异常
     */
    String selectPayOrderByOutTradeNo(String outTradeNo, String merchantId) throws Exception;

需要注意请求参数的长度和规则:
在这里插入图片描述
实现上述方法:

    @Override
    public String selectPayOrderByOutTradeNo(String outTradeNo, String merchantId) throws Exception {
        // 1. 构建请求对象
        String apiPath = String.format(WechatPayNativeApiEnum.QUERY_BY_MERCHANT_ORDER_NO.getAddress(), outTradeNo, merchantId);
        URIBuilder uriBuilder = new URIBuilder(apiPath);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader("Accept", "application/json");
        // 2. 执行
        CloseableHttpResponse response = httpClient.execute(httpGet);
        // 3. 获取响应
        String result = EntityUtils.toString(response.getEntity());
        log.info("查询微信交易订单返回结果:" + result);
        return result;
    }

服务类添加查询商户订单支付状态,商户的订单还是未支付,则去查询微信订单API。

    @Override
    public Boolean selectPayOrderStatus(String orderId) throws Exception {
        // 1. 首先查询商户自己的订单是否是已支付成功
        OrderEntity order = orderService.getById(orderId);
        if (OrderStatusEnum.SUCCESS.getCode() == order.getStatus()) {
            return true;
        }
        // 2. 商户的订单还是未支付,则去查询微信API。
        String response = selectPayOrderByOutTradeNo(order.getOutTradeNo(), wechatPayProperties.getMerchantId()); // 微信查询该订单状态
        Map<String, String> resultMap = objectMapper.readValue(response, Map.class);
        String tradeState = resultMap.get("trade_state"); // 订单状态
        // 3. 已支付时,修改订单状态
        if ("SUCCESS".equals(tradeState)) {
            // 修改订单为已支付
            order.setStatus(OrderStatusEnum.SUCCESS.getCode());
            String transactionId = resultMap.get("transaction_id");
            order.setTransactionId(transactionId);
            orderService.updateById(order);
            return true;
        }
        return false;
    }

添加访问接口,用户下订单生成支付二维码后,前端需要开启定时器,每三秒查询一次该订单支付状态,如果返回已支付,则关闭二维码,弹出下单成功,即将发货。如果超过规定的时间还未支付,则弹出订单支付超时已关闭

    @Operation(summary = "查询商户订单是否已支付,前端轮询")
    @GetMapping("/orderPayStatus")
    public R<Boolean> orderPayStatus(String orderId) throws Exception {
        Boolean status=wechatPayService.selectPayOrderStatus(orderId);
        return R.success(status);
    }

回调通知

微信官方API文档可以查看接口详情。
在这里插入图片描述

用户支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理该消息,并返回应答。

对后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。

通知频率为【15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h 】- 总计 24h4m。

集成支付通知大致分为以下几个步骤:

  1. 下单接口中的添加请求参数notify_url
  2. 添加回调访问接口,收到微信请求后,进行验签、查询订单、修改订单支付状态、应答、错误处理等

1. 添加通知回调地址

微信调用商户,肯定需要提供一个回调地址,而且是有要求的,否则可能导致商户无法接收到微信的回调通知信息。

要求如下:

  • 必须为https地址
  • 确保回调URL是外部可正常访问的
  • 不能携带后缀参数

开发本地的地址,微信无法访问,而且我们也没有Https证书,所以需要使用内网穿透工具。可以使用ngrok(本篇使用)或者花生壳

进入ngrok官网下载页面,下载对应安装文件。
在这里插入图片描述
注册,获取认证令牌。
**加粗样式**
注册完成后,会收到验证邮件,点击后在跳转页面查看令牌。
在这里插入图片描述
双击运行,复制上面的输入命令添加令牌。
在这里插入图片描述

输入ngrok http 服务端口命令,启动成功。可以在窗口中查看到当前内网地址对应的外网访问地址(重新启动会变化)。
在这里插入图片描述
修改之前的下单接口,将通知地址修改为外网地址(/pay/wechat/notify为回调接口路径,后面添加)。
在这里插入图片描述

2. 通知处理

支付结果通知是以POST 方法访问商户设置的通知地址,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情。

官方SDK提供了一个NotificationHandler通知处理器,它可以校验参数、验签、解密请求体。我们只需要获取一些消息头,传入处理器,调用其解析处理方法即可。

    public Notification notifyVerifier(HttpServletRequest request) throws ValidationException, ParseException {
        // 1. 获取回调请求头中的相关信息
        String nonce = request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_NONCE);  // 随机字符串
        String timestamp = request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP); // 时间戳
        // 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。
        // 商户应当验证签名,以确认请求来自微信,而不是其他的第三方
        String signature = request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE); // 签名数据
        String serial = request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_SERIAL); // 平台证书序列号
        // 2. 构建通知请求对象
        NotificationRequest notificationRequest = new NotificationRequest.Builder()
                .withSerialNumber(serial)
                .withNonce(nonce)
                .withTimestamp(timestamp)
                .withSignature(signature)
                .withBody(getBody(request))
                .build();
        // 3. 创建通知处理器,传入验签器、APIv3密钥(解密回调通知)
        NotificationHandler handler = new NotificationHandler(verifier, wechatPayProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        // 4. 处理器处理请求,校验参数、验签、AES解密请求体
        // 抛出ValidationException时,请先检查传入参数是否与回调通知参数一致。若一致,说明参数可能被恶意篡改导致验签失败。
        // 抛出ParseException时,请先检查传入包体是否与回调通知包体一致。若一致,请检查AES密钥是否正确设置。若正确,说明包体可能被恶意篡改导致解析失败。
        Notification notification = handler.parse(notificationRequest);
        log.info("收到微信通知数据:" + notification.toString());
        return notification;
    }
    /**
     * 将通知参数转化为字符串
     */
    private String getBody(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

3. 通知接口

在服务类中,添加通知回调处理逻辑,根据回调参数返回的订单状态,如果回调显示已支付,则商户订单状态也修改为已支付,最后返回应答消息给微信。

    @Override
    public String handleNativeNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 1. 微信通知校验、验签、解密请求体
        Notification notification = notifyVerifier(request);
        // 2. 获取回调信息
        String decryptData = notification.getDecryptData();
        Map<String, String> resultMap = objectMapper.readValue(decryptData, HashMap.class); // json字符串转map
        String outTradeNo = resultMap.get("out_trade_no"); // 商户订单
        String tradeState = resultMap.get("trade_state");   // 支付状态
        String transactionId = resultMap.get("transaction_id"); // 支付订单ID
        // 3. 查询商户订单
        OrderEntity order = orderService.getOneByOutTradeNo(outTradeNo);
        // 4. 订单不是支付成功更新订单状态
        if (OrderStatusEnum.SUCCESS.getCode() != order.getStatus()) {
            if ("SUCCESS".equals(tradeState)) {
                // 修改订单为已支付
                order.setStatus(OrderStatusEnum.SUCCESS.getCode());
                order.setTransactionId(transactionId);
                orderService.updateById(order);
            }
        }
        // 5. 应答
        // 接收成功:HTTP应答状态码需返回200或204,无需返回应答报文。
        // 接收失败:HTTP应答状态码需返回5XX或4XX
        Map<String, String> map = new HashMap<>(); // 应答对象
        response.setStatus(200); // 收到200后,判断成功,就不会再发了
        map.put("code", "SUCCESS"); // 错误码,SUCCESS为清算机构接收成功,其他错误码为失败。
        map.put("message", "成功"); // 返回信息,如非空,为错误原因。
        return JSONUtil.toJsonPrettyStr(map);
    }

添加回调访问接口:

    @Operation(summary = "支付回调通知")
    @PostMapping("/notify")
    public String notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return wechatPayService.handleNativeNotify(request,response);
    }

4. 测试

启动想问,断点定在handleNativeNotify方法中,访问微信Native下单接口,生成二维码,在库中生成了一条未支付的订单。
在这里插入图片描述
扫描二维码进行支付,马上就进入了断点,首先在请求头中获取到了很多信息。
在这里插入图片描述
经过通知处理器处理后的数据:
在这里插入图片描述
获取到了支付成功的回调参数:
在这里插入图片描述
最终订单状态修改为了已支付,且记录了微信支付订单号。
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨 禹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值