微信小程序-支付功能-整合springboot功能

微信小程序-支付功能

首先我这里得声明,微信支付比较繁琐,所以我写的都很仔细,所以如果你没有耐心的化,应该是看不下去的,我力求,以后我们用到微信支付代码的时候,我看到这个文章就能很好的搭起来,而不是直接抄别人的代码,我觉得学的还是一个思想,和框架,了解它整体的流程,以后别人问了我们,肚子里才有墨水

流程
我们得从大体上来看,我们需要实现微信支付的化,要干什么

首先真正要实现微信支付的化,有一个大前提,就是我们得需要有商户认证,所以我这里无法真正实现这里的功能,也就是说,无法验证

微信小程序-支付功能-整合springboot功能_第1张图片

大体流程分析

1 到 3 步

对于1 到 3步,来说,就是前端发请求过来,到我们的springboot系统
然后我们返回订单号,这里不太属于微信支付的真正流程里边,这个属于个人的业务,我们内部维护的一个订单号

4 到 8步

这个流程是比较核心的流程,整体的流程就是为了得到一个预交易标识

9 到 14步

这个流程,就是用户点了支付之后,会真正调用一个支付接口,然后支付之后,会有一个支付成功的回调函数,这个回调函数的工作就是去14. 更新订单状态!


所以我们整体来看,我门真正很需要关心的接口有两个
也就是这里的 5.调用微信下单接口, 10.调起支付接口

其他接口基本上是业务接口,例如,最后一步,更新订单状态,也算是我们的业务代码

详细流程分析

1 - 3

这里的1 - 3 步,属于业务代码,只要返回订单号就ok

核心预支付接口

4 ~ 8 步

5. 调用微信下单接口
这个接口的在微信支付的文档里边,这里的小程序调起的api叫做jsapi

接口说明
微信小程序-支付功能-整合springboot功能_第2张图片
首先是这里的api
整体这个api 拼接起来就是
https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

请求头参数

第一个是,http请求头参数
微信小程序-支付功能-整合springboot功能_第3张图片
特别要注意这些设置,它官方文档要怎么做,我们写代码的时候就要很注意了,它是硬性要求

body参数

第二个是body参数

微信小程序-支付功能-整合springboot功能_第4张图片
微信小程序-支付功能-整合springboot功能_第5张图片微信小程序-支付功能-整合springboot功能_第6张图片

这些参数,都是我截取的必填的
我们来看比较重要的

appid 小程序id

mchid 商户号id

out_trade_no 商户系统自带的订单号,必须是只能是数字 + 大小写字母,并且还需要唯一,这个参数,其实就是我们1 ~ 3步生成的订单号

notify_url: 这个是支付成功后的回调地址,这个地址也是需要配置的,特别需要注意这个接口

amount: 多少钱,这里的amount还是一个对象,里边包括
total: 金额多少
currency : 货币体系,这里一般是CNY

payer: 谁付的钱,而这个payer,里边是用户的标识,也是微信独有的
openid标识,一个openid,代表着一个用户

这里的第七步,也就是将组合数据再次签名的目的,就是为了网络传输的安全,给这些数据签上名,相当于是加密,这里会设计到加密 + 解密的问题

最后回复给前端 也就是我这里的小程序

返回参数

这个返回的就只有一个东西
微信小程序-支付功能-整合springboot功能_第7张图片
类似于这样的

{
  "prepay_id" : "wx201410272009395522657a690389285100"
}

我们可以看作是一个预交易的标识

这个的作用在之后的调起真正的支付接口的时候,是有用的

然后是第9步,用户确认支付,他就会发起这样的请求
微信小程序-支付功能-整合springboot功能_第8张图片这里的照片有点模糊没什么办法,但是我们就只用知道,这里就是加密后的信息,前端给这些信息,进行了封装,调用了这个方法,之后,小程序就会出席那一个支付框
微信小程序-支付功能-整合springboot功能_第9张图片
这里的接口也有详细的介绍
我们直接用一个例子来看,就更为清楚了
微信小程序-支付功能-整合springboot功能_第10张图片

wx.requestPayment
(
  {
    "timeStamp": "1414561699",
    "nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
    "package": "prepay_id=wx201410272009395522657a690389285100",
    "signType": "RSA",
    "paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==",
    "success":function(res){},
    "fail":function(res){},
    "complete":function(res){}
  }
)

timeStamp就是时间戳,不重要

nonceStr 随机字符串,不知道干什么的

package: 这个就是核心预支付功能的返回值,prepare_id的值,而且他的提提交方式就是这样 prepare_id=xxx

signType: 就是加密的方式

paySign: 加密之后得出的签名值,这个pagSign会在之后,在我们真正支付的时候,会校验这个pagSign,实际就是校验appid是否是一致的,因为本来这个加密的东西里边就包括了appid


确认之后,就会真正发起一个请求,也就是我这里写的核心支付接口

支付接口

然后我们就来看这里的调起支付接口

掉了这个支付接口之后,就会回调给我们自己的商户系统,也就是支付成功的回调函数,这也是我们刚刚说过的事情,然后我们自己去更新支付状态

准备工作

这里的准备工作,就是要一般来说,我们这里的开发环境是内网,所以我们得需要有一个公网的ip地址才行,如果你有公网的ip的化,那么就可以跳过这里
这里用到的cploar

我们直接搜索cploar,然后注册一个账号,下载cploar
微信小程序-支付功能-整合springboot功能_第11张图片
然后下载之后,我们要打开所在的目录
第一次来搞的化,要先弄这个步骤
微信小程序-支付功能-整合springboot功能_第12张图片
就是验证一下,这里它是linux,windows,就前面直接这样写
在这里插入图片描述
然后就ok了

启动!
微信小程序-支付功能-整合springboot功能_第13张图片
像这里,我们也是windows版本的化,我这里的后端的接口是8080 所以这里不屑80
直接这样写

cploar.exe http 8080

微信小程序-支付功能-整合springboot功能_第14张图片
这样之后就成功了
这里的卡面的以top结尾的地址就是我的临时的公网ip地址

核心代码开发

配置

首先我们得先搞清楚配置项

@Component
@ConfigurationProperties(prefix = "sky.wechat")
@Data
public class WeChatProperties {

    private String appid; //小程序的appid
    private String secret; //小程序的秘钥
    private String mchid; //商户号
    private String mchSerialNo; //商户API证书的证书序列号
    private String privateKeyFilePath; //商户私钥文件
    private String apiV3Key; //证书解密的密钥
    private String weChatPayCertFilePath; //平台证书
    private String notifyUrl; //支付成功的回调地址
    private String refundNotifyUrl; //退款成功的回调地址
}

这是配置类,这个是和yml进行映射的,更好的做一个配置

wechat:
    appid: 小程序的id
    secret: 小程序的密钥
    #商户号
    mchid: 自己的商户号
    #商户API证书的证书序列号
    mchSerialNo:商户号的序列号
    #商户私钥文件
    privateKeyFilePath: D:\pay\apiclient_key.pem
    #证书解密的密钥
    apiV3Key: CZBK51236435wxpay435434323FFDuv3
    #平台证书
    weChatPayCertFilePath: D:\pay\wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pem
    #支付成功的回调地址
    notifyUrl: http://6a5f907f.r6.cpolar.top/notify/paySuccess
    #退款成功的回调地址
    refundNotifyUrl: http://6a5f907f.r6.cpolar.top/notify/refundSuccess

我们这里privateKeyFilePath 和 apiV3Key 就是关于安全的问题
然后其他都是关于微信的,我们要自己看自己的情况进行填写

privateKeyFilePath: 就是一个pem的文件,加密文件

weChatPayCertFilePath: 这个也是微信生成的

在这里插入图片描述

然后这里关于我们的商户系统的地方就在于这个

notifyUrl: 这里我们测试的化,用到的是我在准备工作用到的,从本地转换为公网ip的地址,这是一个支付成功的地址,这里的后边跟着的
/notity/paySuccess,也是我们这里的controller的方法

refundNotifyUrl:
/notify/refundSuccess,也是我们这里的controller的方法

核心代码

首先,1 ~ 3 步,生成订单号的接口代码,这一步我们有各种实现的方法,我这里贴出我这里业务的代码

@ApiOperation("下单")
@PostMapping("/submit")
public Result submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {
   log.info("下单 参数为: {}",ordersSubmitDTO);
   OrderSubmitVO orderSubmitVO = ordersService.submit(ordersSubmitDTO);
   return Result.success(orderSubmitVO);
}

这里的返回的vo就是如下

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderSubmitVO implements Serializable {
    //订单id
    private Long id;
    //订单号
    private String orderNumber;
    //订单金额
    private BigDecimal orderAmount;
    //下单时间
    private LocalDateTime orderTime;
}

我这里的订单号,就设置成了时间戳,没什么

5 到 9 步,预支付代码

controller

@ApiOperation("订单支付")
@PutMapping("/payment")
public Result payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
    log.info("订单支付 参数为: {}",ordersPaymentDTO);
    //预支付交易单
    OrderPaymentVO orderPaymentVO = ordersService.payment(ordersPaymentDTO);
    return Result.success(orderPaymentVO);
}

预支付的vo是如下

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderPaymentVO implements Serializable {

    private String nonceStr; //随机字符串
    private String paySign; //签名
    private String timeStamp; //时间戳
    private String signType; //签名算法
    private String packageStr; //统一下单接口返回的 prepay_id 参数值
}

这里的返回值就对应着我前面的核心预支付接口下的,第二个接口,也就是
wx.requestPayment这个接口需要的值,我们要返回这个东西给前端去发请求


Service

/**
* 订单支付
* @param ordersPaymentDTO
* @return
*/
@Override
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
   // 当前登录用户id
   Long userId = BaseContext.getCurrentId();
   User user = userService.getById(userId);

   //调用微信支付接口,生成预支付交易单
   JSONObject jsonObject = weChatPayUtil.pay(
           ordersPaymentDTO.getOrderNumber(), //商户订单号
           new BigDecimal(0.01), //支付金额,单位 元
           "苍穹外卖订单", //商品描述
           user.getOpenid() //微信用户的openid
   );

   //如果code是 orderpaid代表已支付!
   if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
       throw new OrderBusinessException("该订单已支付");
   }

   OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
   vo.setPackageStr(jsonObject.getString("package"));

   return vo;
}

我们先不看,最重要的预支付的接口,我们直接来看其他的,我们预支付接口返回的是jsonObject,这里就是把微信支付封装了起来,首先是判断是不是已经支付了,如果已经支付了,就不用了,直接返回异常
如果不是的化,那么就要设置PackageStr,这个PackageStr也就是加密后的密文

微信支付工具类

这里的微信支付的工具类也是封装好的,我先把整体的代码贴出来

/**
 * 微信支付工具类
 */
@Component
public class WeChatPayUtil {

    //微信支付下单接口地址
    public static final String JSAPI = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";

    //申请退款接口地址
    public static final String REFUNDS = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";

    @Autowired
    private WeChatProperties weChatProperties;

    /**
     * 获取调用微信接口的客户端工具对象
     *
     * @return
     */
    private CloseableHttpClient getClient() {
        PrivateKey merchantPrivateKey = null;
        try {
            //merchantPrivateKey商户API私钥,如何加载商户API私钥请看常见问题
            merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));
            //加载平台证书文件
            X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));
            //wechatPayCertificates微信支付平台证书列表。你也可以使用后面章节提到的“定时更新平台证书功能”,而不需要关心平台证书的来龙去脉
            List<X509Certificate> wechatPayCertificates = Arrays.asList(x509Certificate);

            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
                    .withWechatPay(wechatPayCertificates);

            // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
            CloseableHttpClient httpClient = builder.build();
            return httpClient;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 发送post方式请求
     *
     * @param url
     * @param body
     * @return
     */
    private String post(String url, String body) throws Exception {
        CloseableHttpClient httpClient = getClient();

        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
        httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
        httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
        httpPost.setEntity(new StringEntity(body, "UTF-8"));

        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            return bodyAsString;
        } finally {
            httpClient.close();
            response.close();
        }
    }

    /**
     * 发送get方式请求
     *
     * @param url
     * @return
     */
    private String get(String url) throws Exception {
        CloseableHttpClient httpClient = getClient();

        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
        httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
        httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());

        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            return bodyAsString;
        } finally {
            httpClient.close();
            response.close();
        }
    }

    /**
     * jsapi下单
     *
     * @param orderNum    商户订单号
     * @param total       总金额
     * @param description 商品描述
     * @param openid      微信用户的openid
     * @return
     */
    private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appid", weChatProperties.getAppid());
        jsonObject.put("mchid", weChatProperties.getMchid());
        jsonObject.put("description", description);
        jsonObject.put("out_trade_no", orderNum);
        jsonObject.put("notify_url", weChatProperties.getNotifyUrl());

        JSONObject amount = new JSONObject();
        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("currency", "CNY");

        jsonObject.put("amount", amount);

        JSONObject payer = new JSONObject();
        payer.put("openid", openid);

        jsonObject.put("payer", payer);

        String body = jsonObject.toJSONString();
        return post(JSAPI, body);
    }

    /**
     * 小程序支付
     *
     * @param orderNum    商户订单号
     * @param total       金额,单位 元
     * @param description 商品描述
     * @param openid      微信用户的openid
     * @return
     */
    public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
        //统一下单,生成预支付交易单
        String bodyAsString = jsapi(orderNum, total, description, openid);
        //解析返回结果
        JSONObject jsonObject = JSON.parseObject(bodyAsString);
        System.out.println(jsonObject);

        String prepayId = jsonObject.getString("prepay_id");
        if (prepayId != null) {
            String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
            String nonceStr = RandomStringUtils.randomNumeric(32);
            ArrayList<Object> list = new ArrayList<>();
            list.add(weChatProperties.getAppid());
            list.add(timeStamp);
            list.add(nonceStr);
            list.add("prepay_id=" + prepayId);
            //二次签名,调起支付需要重新签名
            StringBuilder stringBuilder = new StringBuilder();
            for (Object o : list) {
                stringBuilder.append(o).append("\n");
            }
            String signMessage = stringBuilder.toString();
            byte[] message = signMessage.getBytes();

            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));
            signature.update(message);
            String packageSign = Base64.getEncoder().encodeToString(signature.sign());

            //构造数据给微信小程序,用于调起微信支付
            JSONObject jo = new JSONObject();
            jo.put("timeStamp", timeStamp);
            jo.put("nonceStr", nonceStr);
            jo.put("package", "prepay_id=" + prepayId);
            jo.put("signType", "RSA");
            jo.put("paySign", packageSign);

            return jo;
        }
        return jsonObject;
    }

    /**
     * 申请退款
     *
     * @param outTradeNo    商户订单号
     * @param outRefundNo   商户退款单号
     * @param refund        退款金额
     * @param total         原订单金额
     * @return
     */
    public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("out_trade_no", outTradeNo);
        jsonObject.put("out_refund_no", outRefundNo);

        JSONObject amount = new JSONObject();
        amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("currency", "CNY");

        jsonObject.put("amount", amount);
        jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl());

        String body = jsonObject.toJSONString();

        //调用申请退款接口
        return post(REFUNDS, body);
    }
}

我们一个个拆解来看
首先是pay方法,我们这里下来整体来看这个pay方法
微信小程序-支付功能-整合springboot功能_第15张图片
整体来看,就是先去调用jsapi,这个jsapi也就是我们真正调用的预支付接口
也是前面的那个核心接口

然后我们来看jsapi

    /**
     * jsapi下单
     *
     * @param orderNum    商户订单号
     * @param total       总金额
     * @param description 商品描述
     * @param openid      微信用户的openid
     * @return
     */
    private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appid", weChatProperties.getAppid());
        jsonObject.put("mchid", weChatProperties.getMchid());
        jsonObject.put("description", description);
        jsonObject.put("out_trade_no", orderNum);
        jsonObject.put("notify_url", weChatProperties.getNotifyUrl());

        JSONObject amount = new JSONObject();
        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("currency", "CNY");

        jsonObject.put("amount", amount);

        JSONObject payer = new JSONObject();
        payer.put("openid", openid);

        jsonObject.put("payer", payer);

        String body = jsonObject.toJSONString();
        return post(JSAPI, body);
    }

整体也是很好理解,就是装参数,然后发送post请求
这里的参数,也是我们说过了的

我们再来看这里的post请求

/**
 * 发送post方式请求
 *
 * @param url
 * @param body
 * @return
 */
private String post(String url, String body) throws Exception {
    CloseableHttpClient httpClient = getClient();

    HttpPost httpPost = new HttpPost(url);
    httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
    httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
    httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
    httpPost.setEntity(new StringEntity(body, "UTF-8"));

    CloseableHttpResponse response = httpClient.execute(httpPost);
    try {
        String bodyAsString = EntityUtils.toString(response.getEntity());
        return bodyAsString;
    } finally {
        httpClient.close();
        response.close();
    }
}

首先,我们发请求,肯定是要得到一个httpClient,这个也是封住好的方法
我们看这里的之后的代码,实际上就是装载请求头,这里也是按照微信文档来写的

最后发送请求!!!

然后这里的getClient

    /**
     * 获取调用微信接口的客户端工具对象
     *
     * @return
     */
    private CloseableHttpClient getClient() {
        PrivateKey merchantPrivateKey = null;
        try {
            //merchantPrivateKey商户API私钥,如何加载商户API私钥请看常见问题
            merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())));
            //加载平台证书文件
            X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath())));
            //wechatPayCertificates微信支付平台证书列表。你也可以使用后面章节提到的“定时更新平台证书功能”,而不需要关心平台证书的来龙去脉
            List<X509Certificate> wechatPayCertificates = Arrays.asList(x509Certificate);

            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey)
                    .withWechatPay(wechatPayCertificates);

            // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
            CloseableHttpClient httpClient = builder.build();
            return httpClient;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

这里我不是能搞懂,但是我看着像整加密的,也是调用微信的加密的

所以整体看下来,这个工具类,就是发请求 + 设置加密

回调代码


/**
 * @author jjking
 * @date 2024-01-28 20:13
 */
@RestController
@RequestMapping("/notify")
@Slf4j
public class PayNotifyController {

    @Autowired
    private OrdersService ordersService;
    @Autowired
    private WeChatProperties weChatProperties;

    /**
     * 支付成功回调
     *
     * @param request
     */
    @RequestMapping("/paySuccess")
    public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //读取数据
        String body = readData(request);
        log.info("支付成功回调:{}", body);

        //数据解密
        String plainText = decryptData(body);
        log.info("解密后的文本:{}", plainText);

        JSONObject jsonObject = JSON.parseObject(plainText);
        String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号
        String transactionId = jsonObject.getString("transaction_id");//微信支付交易号

        log.info("商户平台订单号:{}", outTradeNo);
        log.info("微信支付交易号:{}", transactionId);

        //业务处理,修改订单状态、来单提醒
        ordersService.paySuccess(outTradeNo);

        //给微信响应
        responseToWeixin(response);
    }


    /**
     * 读取数据
     *
     * @param request
     * @return
     * @throws Exception
     */
    private String readData(HttpServletRequest request) throws Exception {
        BufferedReader reader = request.getReader();
        StringBuilder result = new StringBuilder();
        String line = null;
        while ((line = reader.readLine()) != null) {
            if (result.length() > 0) {
                result.append("\n");
            }
            result.append(line);
        }
        return result.toString();
    }

    /**
     * 数据解密
     *
     * @param body
     * @return
     * @throws Exception
     */
    private String decryptData(String body) throws Exception {
        JSONObject resultObject = JSON.parseObject(body);
        JSONObject resource = resultObject.getJSONObject("resource");
        String ciphertext = resource.getString("ciphertext");
        String nonce = resource.getString("nonce");
        String associatedData = resource.getString("associated_data");

        AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        //密文解密
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);

        return plainText;
    }

    /**
     * 给微信响应
     * @param response
     */
    private void responseToWeixin(HttpServletResponse response) throws Exception{
        response.setStatus(200);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("code", "SUCCESS");
        map.put("message", "SUCCESS");
        response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
        response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
    }
}

我们需要注意的就是这里的paySuccessNotify方法,这主要就是
加载数据 + 数据解密 + 业务回调 + 给微信响应

我这里也贴出我自己的业务回调方法,也是为了记录一下

@Override
public void paySuccess(String outTradeNo) {
   // 根据订单号查询订单
   LambdaQueryWrapper<Orders> wrapper = new LambdaQueryWrapper<>();
   wrapper.eq(Orders::getNumber,outTradeNo);
   Orders ordersDB = getOne(wrapper);


   // 根据订单id更新订单的状态、支付方式、支付状态、结账时间
   Orders orders = Orders.builder()
           .id(ordersDB.getId())
           .status(Orders.TO_BE_CONFIRMED)
           .payStatus(Orders.PAID)
           .checkoutTime(LocalDateTime.now())
           .build();

   updateById(orders);
}

就是设置一个status

总结

我们总结来看,其实也不是很难,看起来很复杂其实没有,这里我认为比较难的是这里的加密 + 解密,我这里并没有搞懂,等我搞懂了这里的加密 + 解密,我再来更新这里的代码

然后这里的代码实际上复用性,还行,我们需要改的是,yml里边的设置,

我这里最后再来总结一下总体的流程

  1. 前端发请求过来,后端返回订单号
  2. 后端发请求到wx,然后收到预支付标识prepare_id,然后设置信息加密给到前端
  3. 前端带着这些加密信息,发请求直接给到wx,wx支付成功就会有回调函数,如果失败,就不返回
  4. 回调函数,我们就解密这些数据,最后调用我们业务回调代码

整体的流程就是这样,我们要复用的化,也就是修改哪些加密的方式,这些

你可能感兴趣的:(微信小程序,微信小程序,spring,boot,小程序)