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

微信小程序-支付功能

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

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

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

在这里插入图片描述

大体流程分析

1 到 3 步

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

4 到 8步

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

9 到 14步

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


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

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

详细流程分析

1 - 3

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

核心预支付接口

4 ~ 8 步

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

接口说明
在这里插入图片描述
首先是这里的api
整体这个api 拼接起来就是
https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi

请求头参数

第一个是,http请求头参数
在这里插入图片描述
特别要注意这些设置,它官方文档要怎么做,我们写代码的时候就要很注意了,它是硬性要求

body参数

第二个是body参数

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

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

appid 小程序id

mchid 商户号id

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

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

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

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

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

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

返回参数

这个返回的就只有一个东西
在这里插入图片描述
类似于这样的

{
  "prepay_id" : "wx201410272009395522657a690389285100"
}

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

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

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

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
在这里插入图片描述
然后下载之后,我们要打开所在的目录
第一次来搞的化,要先弄这个步骤
在这里插入图片描述
就是验证一下,这里它是linux,windows,就前面直接这样写
在这里插入图片描述
然后就ok了

启动!
在这里插入图片描述
像这里,我们也是windows版本的化,我这里的后端的接口是8080 所以这里不屑80
直接这样写

cploar.exe http 8080

在这里插入图片描述
这样之后就成功了
这里的卡面的以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方法
在这里插入图片描述
整体来看,就是先去调用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. 回调函数,我们就解密这些数据,最后调用我们业务回调代码

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/354515.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网页转文件下载工具

为了更快捷copy博客 做了个 网页转文件下载工具 1.0.1 更新如下&#xff1a; javaphpjava提供页面转换文件的微服务APIphp调用接口&#xff0c;输出文件下载支持网页转md 1.0.2 更新如下&#xff1a; 样式表切换&#xff0c;白天or黑夜&#xff0c;cookie七天保质期 未…

网络原理,网络通信以及网络协议

​​​​&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录专栏&#xff1a;网络原理,网络通信以及网络协议 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 网络原理概念网络通信局域网LAN广域网WAN 网络通信IP地址端口号…

【GitHub项目推荐--GPT开源项目】【转载】

Auto-GPT Auto-GPT 是一个实验性的开源项目&#xff0c;基于 GPT-4。你给出 Auto-GPT 一个的任务&#xff0c;它不会立即输出答案&#xff0c;而会先自己通过多轮对话来琢磨、验证、决策&#xff0c;从而自己找出一条达成目标的路&#xff0c;整个过程完全不需要人类插手&…

LeetCode:376.摆动序列

个人主页&#xff1a;仍有未知等待探索-CSDN博客 专题分栏&#xff1a;算法_仍有未知等待探索的博客-CSDN博客 题目链接&#xff1a;376. 摆动序列 - 力扣&#xff08;LeetCode&#xff09; 一、题目 如果连续数字之间的差严格地在正数和负数之间交替&#xff0c;则数字序列称…

【CANoe使用大全】——离线回放数据

&#x1f64b;‍♂️【CANoe使用大全】系列&#x1f481;‍♂️点击跳转 文章目录 1.概述2.Offline3.综述 1.概述 本章主要介绍数据的离线回放操作 2.Offline 导入需要加载的文件 导入完成效果&#xff1a; 点击开关便可进行数据回放 3.综述 Offline回放主要配合Trace和…

夏目友人帐 第五季 2016.10.04

夏目友人帐 第五季 2016.10.04 第1话&#xff1a;一如既往的样子 / 変わらぬ姿第2话&#xff1a;恶作剧之雨 / 悪戯な雨第3话&#xff1a;除妖人寄来的信 / 祓い屋からの手纸第4话&#xff1a;连锁的背后 / 连锁の阴第5话&#xff1a;不可结缘 / 结んではいけない第6话&#xf…

java eazyexcel 实现excel的动态多级联动下拉列表(2)使用MATCH+OFFSET函数

原理 同样是将数据源放到一个新建的隐藏的sheet中&#xff0c;第一行是第一个列表的数据&#xff0c;第二行是每一个有下级菜单的菜单&#xff0c;他下面的行就是他下级菜单的每一值使用MATCH函数从第二行找到上级菜单对应的列根据OFFSET函数从2中获取的列&#xff0c;取得下级…

python基础3

7.5 range range 可以生成数字供 for 循环遍历 , 它可以传递三个参数&#xff0c;分别表示 起始、结束和步长。 8. 数据类型高级 8.1 字符串高级 字符串的常见操作包括&#xff1a; 获取长度 :len len 函数可以获取字符串的长度。 查找内容 :find 查找指定内容在字符…

大创项目推荐 题目: 基于深度学习的疲劳驾驶检测 深度学习

文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 &#x1f525; 优…

【智能家居】6、语音控制及网络控制代码实现

一、语音控制 1、指令结构体编写 这个结构体定义了一个命令输入的模型。在这个模型中,包含以下几个部分: cmdName:一个长度为128的字符串,用于存储命令名称。dvicesName:一个长度为128的字符串,用于存储设备名称。cmd:一个长度为32的字符串,用于存储具体的命令。Init:…

数据结构-线性表

文章目录 数据结构—线性表1.线性表的定义和基本操作线性表的定义线性表的特点线性表的基本操作 2.线性表的顺序存储和链式存储表示顺序存储链式存储单链表循环链表双向链表 数据结构—线性表 1.线性表的定义和基本操作 线性表的定义 定义&#xff1a;线性表是具有相同数据类…

GPT-SoVITS 测试

开箱直用版&#xff08;使用 AutoDL&#xff09; step1 打开地址 https://www.codewithgpu.com/i/RVC-Boss/GPT-SoVITS/GPT-SoVITS-Official 选择 AutoDL创建实例&#xff0c;选择 3080ti 机器 step2 创建好实例之后&#xff0c;进入命令行&#xff0c;输入命令 echo {}>…

LC weekly-game 382

ExScorecomplete extentmean12char op20717/719(解答错误,迭代条件写错)按长度枚举30523/537(超时,弱智题已AC)枚举40 有用的是Ex2和Ex4 Experience each problem can be submitted infinitely with negligible punishment timesome cases of the problem is hidden.each ke…

Vue3探索编辑部——关于Pinia(1)

目录 什么是Pinia&#xff1f; Vue3中的Pinia 创建项目 数据准备和引入Pinia 使用Pinia 采用action修改数据 总结 什么是Pinia&#xff1f; Pinia是Vue3的专属的状态管理工具&#xff0c;什么是状态呢&#xff1f;其实我们可以把状态理解为数据&#xff0c;或者一个业务…

INFINI Labs 产品更新 | 统一版本号 1.22.0

INFINI Labs 产品又更新啦~&#xff0c;包括 Console&#xff0c;Gateway&#xff0c;Loadgen&#xff0c;Agent 1.22.0。为了避免版本不同带来的困扰&#xff0c;以后发布均统一版本号&#xff0c;此次版本重点修复历史遗留 Bug 、优化内存占用等。以下是本次更新的详细说明。…

Python中Numba库装饰器

一、运行速度是Python天生的短板 1.1 编译型语言&#xff1a;C 对于编译型语言&#xff0c;开发完成以后需要将所有的源代码都转换成可执行程序&#xff0c;比如 Windows 下的.exe文件&#xff0c;可执行程序里面包含的就是机器码。只要我们拥有可执行程序&#xff0c;就可以随…

通讯录小项目(上)

Start And Stick 通讯录的实现有很多种方式&#xff0c;今天我们将用结构体实现简单的通讯录项目功能。包括通讯录的增、删、查、改等功能。 思路&#xff1a; 此次代码文件分别为&#xff1a; 文件名用途sqlist.h用于函数和结构体的声明sqlist.c用于函数的实现test.c用于通讯…

【JaveWeb教程】(38)SpringBootWeb案例之《智能学习辅助系统》的详细实现步骤与代码示例(11)过滤器Filter讲解

目录 SpringBootWeb案例10 过滤器Filter2.4 过滤器Filter2.4.1 快速入门2.4.2 Filter详解2.4.2.1 执行流程2.4.2.2 拦截路径2.4.2.3 过滤器链 2.4.3 登录校验-Filter2.4.3.1 分析2.4.3.2 具体流程2.4.3.3 代码实现 SpringBootWeb案例10 过滤器Filter 2.4 过滤器Filter 刚才通…

VBA技术资料MF111:将表对象转换为正常范围

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

大数据期望最大化(EM)算法:从理论到实战全解析

文章目录 大数据期望最大化&#xff08;EM&#xff09;算法&#xff1a;从理论到实战全解析一、引言概率模型与隐变量极大似然估计&#xff08;MLE&#xff09;Jensen不等式 二、基础数学原理条件概率与联合概率似然函数Kullback-Leibler散度贝叶斯推断 三、EM算法的核心思想期…