从零玩转系列之微信支付实战PC端支付微信取消接口搭建 | 技术创作特训营第一期
阅读原文时间:2023年08月09日阅读:3

一、前言

从零玩转系列之微信支付实战PC端支付微信取消接口搭建 | 技术创作特训营第一期

halo各位大佬很久没更新了最近在搞微信支付,因商户号审核了我半个月和小程序认证也找了资料并且将商户号和小程序进行关联,至此微信支付Native支付完成.此篇文章过长我将分几个阶段的文章发布(项目源码都有,小程序和PC端)

在此之前已经更新了微信支付开篇、微信支付安全、微信实战基础框架搭建、本次更新为微信支付实战PC端接口搭建,实战篇分为几个章节因为代码量确实有点多哈.

本次项目使用技术栈

后端: SpringBoot3.1.x、Mysql8.0、MybatisPlus

前端: Vue3、Vite、ElementPlus

小程序: Uniapp、Uview

一、取消订单接口

场景

  1. 用户下单完毕后未支付 可进行取消订单
  2. 订单超时支付系统自动取消订单

修改 WechatNativeController

    /**
     * 用户取消订单
     */
    @PostMapping("/cancel/{orderNo}")
    public R<String> cancel(@PathVariable String orderNo) {
        log.info("取消订单");
        wxPayService.cancelOrder(orderNo);
        return R.ok("订单已取消");
    }

修改 wxPayService

    /**
     * 关闭订单
     */
    @SneakyThrows
    public void cancelOrder(String orderNo) {
        // ...../
    }

逻辑分析

在工作中我们逻辑可能是这样子的

step 1 查询该订单状态是否需要关闭订单,如果订单已经关闭则不需要再次关闭

思考: 我们应该去哪里查询订单的状态?

假如我们去数据库查询记录的话,如果数据库因为什么原因导致状态可能不对呢?

所以我们直接去微信服务器查最新的直接更新关闭状态到数据库当中

你看微信也要我们这样子玩那就万无一失直接干

================================================================

好既然我们要去调用微信查询接口我们是不是可以封装一个通用的 后续肯定有其他地方也会调用查询支付信息的情况

文档(商户): https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_2.shtml

有两种查询方式第二种方便就这么简单

代码很好理解传递商户订单号和直连商户号mchid

/**
     * 抽出公共获取微信订单信息
     */
    private WeChartOrderInfo selectOrderInfo(String orderNo) throws IOException {
        log.info("查询订单,直连商户号:{} , 微信支付订单号: {}", orderNo, wxPayConfig.getMchId());
        String url = wxPayConfig.getDomain().concat(String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo)).concat("?mchid=").concat(wxPayConfig.getMchId());
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
        WeChartOrderInfo weChartOrderInfo = buildBodyParams(response, WeChartOrderInfo.class);
        log.info("查询订单响应, {}", weChartOrderInfo);
        response.close();
        return weChartOrderInfo;
    }

响应结果

{
    "amount": {
        "currency": "CNY",
        "payer_currency": "CNY",
        "payer_total": 1,
        "total": 1
    },
    "appid": "wxdace645e0bc2cXXX",
    "attach": "",
    "bank_type": "OTHERS",
    "mchid": "1900006XXX",
    "out_trade_no": "44_2126281063_5504",
    "payer": {
        "openid": "o4GgauJP_mgWEWictzA15WT15XXX"
    },
    "promotion_detail": [],
    "success_time": "2021-03-22T10:29:05+08:00",
    "trade_state": "SUCCESS",
    "trade_state_desc": "支付成功",
    "trade_type": "JSAPI",
    "transaction_id": "4200000891202103228088184743"
}

完善逻辑

        /**
     * 关闭订单
     */
    @SneakyThrows
    public void cancelOrder(String orderNo) {
              // step 1 查询该订单状态是否需要关闭订单
          WeChartOrderInfo state = selectOrderInfo(orderNo);
            // 判断如果订单已经关闭则不需要再次关闭 更新数据库
        if (null == state || state.getTrade_state() == null) {
            // 更新本地订单状态超时关闭订单
            orderInfoService.lambdaUpdate().eq(OrderInfo::getOrderNo, orderNo).set(OrderInfo::getOrderStatus, OrderStatus.CLOSED.getType()).update();
            return;
        }

        // step 2 还未支付才能取消订单
        if (state.getTrade_state().equals(OrderStatus.NOTPAY.name())) {
            // step 2 进行调用微信关闭订单接口
            sendCloseRequest(orderNo);
            // step 2 更新商户端的订单状态用户取消订单
            this.orderInfoService.lambdaUpdate().eq(OrderInfo::getOrderNo, orderNo).set(OrderInfo::getOrderStatus, OrderStatus.CANCEL.getType()).update();
        }
    }

抽出通用订单关闭代码

文档: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_3.shtml

 /**
     * 发送关闭订单请求
     *
     * @param orderNo 订单编号
     */
    @SneakyThrows
    private void sendCloseRequest(String orderNo) {
        log.info("关闭订单, 订单号:{}", orderNo);
        String url = String.format(wxPayConfig.getDomain().concat(WxApiType.CLOSE_ORDER_BY_NO.getType()), orderNo);
        HttpPost httpPost = new HttpPost(url);
        Map<String, Object> paramsMap = new HashMap<>(1);
        paramsMap.put("mchid", wxPayConfig.getMchId());
        String jsonParams = JSONUtil.toJsonStr(paramsMap);
        log.info("请求参数 ===> {}", jsonParams);
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        try (CloseableHttpResponse response = wxPayClient.execute(httpPost)) {
            log.info("关单响应: {}", response.getEntity());
            log.info("解析body {}", buildBodyParams(response, Map.class));
        }
    }

到这里订单取消就完毕了

二、查询订单接口

根据上面编写的查询订单方法完善一下

修改 WechatNativeController

/**
 * 查询订单
 */
@PostMapping("/query/{orderNo}")
public R<WeChartOrderInfo> query(@PathVariable String orderNo) {
    log.info("查询订单");
    return R.ok(wxPayService.queryOrder(orderNo));
}

修改 WxPayService

/**
 * 查询订单信息
 *
 * @param orderNo 订单号
 * @return 订单信息
 */
@SneakyThrows
public WeChartOrderInfo queryOrder(String orderNo) {
    return this.selectOrderInfo(orderNo);
}

好了查询接口也写完了so easy to happy !

我们来测试看看是什么效果咯~

三、测试

下单

我们还没编写到前端来所以还是和之前的文章操作一样使接口调试工具操作这里我使用之前推荐的IDEA FastRequest插件如下图

发送

生成二维码

复制codeUrl参数打开草料 https://cli.im/url 不要支付哦我们确保订单是否成功创建,确保二维码没有过期哦

我们也可以先看看订单啥状态上面我们不是写了个查询订单详情的嘛

调用取消订单接口

复制 orderNo 订单编号直接发送取消完成

在查询一下订单看看状态是不是取消成功

本文章仓库WeChatPayScaffolding

【写作提纲】

I. 前言

A. 通过前言表达我每次的文章内容是什么东西

II. 取消订单接口实现

A. 集结生活之中的场景来解析他的逻辑

III. 查询订单接口实现

A. 完善取消订单接口当中已经实现了的功能

IV. 测试

A. 通过测试用例一步步排查编写是否存在BUG