当前位置: 首页 > news >正文

SpringBoot | SpringBoot中实现“微信支付“

SpringBoot中实现"微信支付":

    • 1.“微信支付”产品
    • 2."微信支付"接入流程
    • 3.“微信小程序支付”时序图:
      • 3.1 “商家端JSAPI下单” 接口
      • 3.2 “微信小程序端调起支付” 接口
    • 4.微信支付准备工作:
      • 4.1 获得微信支付平台证书、商户私钥文件
      • 4.2 获取临时域名 (内网穿透) :
        • ①下载且安装软件 : cpolar
        • ②获得 “Authtoken” 且配置 cpolar ,生成“内网穿透”工具配置文件
        • ③启动服务,临时获取到一个IP地址 (临时域名)
    • 5.“微信小程序支付”代码:
      • OrderControlle.java (订单Controller)
      • OrderService.java
      • OrderServiceImpl.java (包含 : 微信支付工具类)
      • OrderMapper.java
      • OrderMapper.xml
      • UserMapper.java
      • PayNotifyController.java / 支付回调相关接口
      • application.yml (springboot配置文件)
      • application-dev.xml

1.“微信支付”产品

微信支付提供了多种产品,即 微信支付多种支付的形式。如:

  • 付款码支付:打开微信展示“微信支付”二维码页面,让商家去扫

  • JSAPI支付:一般用于在H5页面进行微信支付JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款

  • 小程序支付:在微信小程序中调用“微信支付”功能。

  • Native支付:商家提供一个二维码,我们用微信扫一扫功能来进行支付。

  • APP支付:在手机应用中调起微信支付。

  • 刷脸支付:即刷脸完成付款。

  • “微信支付”产品详细介绍

  • 微信支付产品
    在这里插入图片描述

2."微信支付"接入流程

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

3.“微信小程序支付”时序图:

  • 微信小程序支付时序图

    在这里插入图片描述

  • 微信小程序支付 主要内容为以下三个部分:

    • “商家端JSAPI下单” 接口 / 微信下单接口 / 预下单接口
    • “微信小程序端调起支付” 接口 / 调起微信支付
    • 推送支付结果

3.1 “商家端JSAPI下单” 接口

  • 商户系统 调用 (小程序支付中的) JSAPI下单接口微信支付服务后台 生成 预支付交易单

  • “商家端JSAPI下单” 接口-详解

  • 商家端通过 访问 https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi 接口来生成预支付交易单
    商家端JSAPI下单 / 生成预支付交易单 要访问的接口

  • 请求示例

{
"mchid": "1900006XXX",
"out_trade_no": "1217752501201407033233368318",
"appid": "wxdace645e0bc2cXXX",
"description": "Image形象店-深圳腾大-QQ公仔",
"notify_url": "https://www.weixin.qq.com/wxpay/pay.php",
"amount": {"total": 1,"currency": "CNY"
},
"payer": {"openid": "o4GgauInH_RCEdvrrNGrntXDuXXX"}
}
  • 返回示例 (正常示例)
{"prepay_id": "wx26112221580621e9b071c00d9e093b0000"
}                             
  • 适用对象: 直连商户
    请求URLhttps://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
    请求方式:POST

  • 请求参数

请求参数-详解

参数名变量类型[长度限制]必填描述
应用IDappidstring[1,32]body 由微信生成的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的服务号APPID 示例值:wxd678efh567hg6787
直连商户号mchidstring[1,32]body 直连商户的商户号,由微信支付生成并下发。 示例值:1230000109
商品描述descriptionstring[1,127]body 商品描述 示例值:Image形象店-深圳腾大-QQ公仔
通知地址notify_urlstring[1,256]body异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http 示例值:https://www.weixin.qq.com/wxpay/pay.php
商户订单号out_trade_nostring[6,32]body 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 示例值:1217752501201407033233368018

在这里插入图片描述

订单金额amountobjectbody 订单金额信息
参数名变量类型[长度限制]必填描述
总金额totalint订单总金额,单位为分。 示例值:100
货币类型currencystring[1,16]CNY:人民币,境内商户号仅支持人民币。 示例值:CNY

在这里插入图片描述

支付者payerobjectbody 支付者信息
参数名变量类型[长度限制]必填描述
用户标识openidstring[1,128]用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid,Openid获取详见 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o

返回参数

参数名变量类型[长度限制]必填描述
预支付交易会话标识prepay_idstring[1,64]预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时 示例值:wx201410272009395522657a690389285100

3.2 “微信小程序端调起支付” 接口

  • 通过 JSAPI下单接口 获取到 发起支付 的必要参数 prepay_id,然后使用微信支付平台提供的小程序方法 (wx.requestPayment(OBJECT) )调起小程序支付 (即完成小程序的微信支付) 。
    “微信小程序端调起支付” 接口-详解

  • 微信小程序通过调用wx.requestPayment(OBJECT) 发起微信支付

  • 请求示例

    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){}}
    )
    
  • 适用对象: 直连商户

  • 接口定义

    此API无后台接口交互,需要将列表中的数据签名。

    参数名变量类型[长度限制]必填描述
    小程序IDappIdstring[1,32]商户申请的小程序对应的appid,由微信支付生成,可在小程序后台查看 示例值:wx8888888888888888
    时间戳timeStampstring[1,32]时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数。注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)。 示例值:1414561699
    随机字符串nonceStrstring[1,32]随机字符串,不长于32位。推荐随机数生成算法。 示例值:5K8264ILTKCH16CQ2502SI8ZNMTM67VS
    订单详情扩展字符串packagestring[1,128]小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** 示例值:prepay_id=
    wx201410272009395522657a690389285100
    签名方式signTypestring[1,32]签名类型,默认为RSA,仅支持RSA。 示例值:RSA
    签名paySignstring[1,512]签名,使用字段appId、timeStamp、nonceStr、
    package计算得出的签名值
    oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRA
    Z/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZ
    vI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4
    WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17
    D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYK
    UR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLm
    R9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDm
    XxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcun
    Xt8cqNjKNqZLhLw4jq/xDg==
  • 调用wx.requestPayment(OBJECT)发起微信支付

接口名称wx.requestPayment
Object 参数说明
( 这些需要用到的参数全都是后端计算好返回给微信小程序,然后小程序直接使用这些参数来调用方法来就会弹出“微信支付”的窗口,来完成微信支付。 )

参数名变量类型[长度限制]必填描述
时间戳timeStampstring[1,32]当前的时间,其他详见时间戳规则。 示例值:1414561699
随机字符串nonceStrstring[1,32]随机字符串,不长于32位。 示例值:5K8264ILTKCH16CQ2502SI8ZNMTM67VS
订单详情扩展字符串packagestring[1,128]小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** 示例值:prepay_id=wx201410272009395522657a690389285100
签名方式signTypestring[1,32]签名类型,默认为RSA,仅支持RSA。 示例值:RSA
签名paySignstring[1,512]签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值 示例值:oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq/xDg==
  • 回调结果
回调类型errMsg说明
successrequestPayment:ok调用支付成功
failrequestPayment:fail cancel用户取消支付
failrequestPayment:fail (detail message)调用支付失败,其中 detail message 为后台返回的详细失败原因

4.微信支付准备工作:

  • 要完成微信支付,其中一个关键的步骤是:需要在商户系统中来调用微信后台微信下单接口 (“客户端JSAPI下单”接口) 来生成 预支付交易单。由于这个接口是与“支付”相关的,所以这个接口的安全要求是非常高的,同时”推送支付结果“的安全性要求也是非常高的。

  • 如何保证调用过程的数据安全

    对数据进行加密解密签名

4.1 获得微信支付平台证书、商户私钥文件

  • 获得微信支付平台证书、商户私钥文件 :这两个文件是从微信的商户平台下载下来的,程序开发过程中会使用到这两个文件。
    ps :要获得这两个文件必须注册成商户
  • 在这里插入图片描述

4.2 获取临时域名 (内网穿透) :

  • 让当前 电脑能获取一个公网的IP地址,让微信后台能调用到当前外卖系统后端服务,这样我们就需要来 获取临时域名
    (这个临时域名对应的就是一个公网IP
①下载且安装软件 : cpolar
  • 下载链接:https://dashboard.cpolar.com/login
    cpolar软件下载
  • 安装包 (百度网盘下载链接):https://pan.baidu.com/s/10O1mK06ts-l37exniuTquw?pwd=ir23 提取码:ir23
    百度网盘获取cpolar安装包
②获得 “Authtoken” 且配置 cpolar ,生成“内网穿透”工具配置文件

  • 登录cpolar官网 : https://dashboard.cpolar.com/login 获得配置cpolar的cmd命令
    在这里插入图片描述


  • cpolar.exe的目录敲cmd进入 cmd页面
    命令行页面中输入cpolar.exe authtoken 获得的Authtoken

    命令生成了一个 .yml文件 : 该文件当前“内网穿透工具”配置文件
    在这里插入图片描述

    在这里插入图片描述

③启动服务,临时获取到一个IP地址 (临时域名)

输入命令 :cpolar.exe http 8080
在这里插入图片描述

5.“微信小程序支付”代码:

OrderControlle.java (订单Controller)

  • OrderController.java 中的代码 ( 订单Controller) :

    @RestController("userOrderController")  //起别名
    @Slf4j
    @RequestMapping("/user/order")
    @Api(tags = "用户端订单相关接口")
    public class OrderController {@Autowiredprivate OrderService orderService;/*** 订单支付  ** @param ordersPaymentDTO* @return*/@PutMapping("/payment")@ApiOperation("订单支付")public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {log.info("订单支付:{}", ordersPaymentDTO);OrderPaymentVO orderPaymentVO = orderService.payment(ordersPaymentDTO);log.info("生成预支付交易单:{}", orderPaymentVO);return Result.success(orderPaymentVO);}
    }
    
  • OrderPaymentVO.Java :

    @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 参数值
    }
    
  • OrdersPaymentDTO.java :

    @Data
    public class OrdersPaymentDTO implements Serializable {//订单号private String orderNumber;//付款方式private Integer payMethod;
    }
    

OrderService.java

  • OrderService.java

    public interface OrderService {/*** 订单支付* @param ordersPaymentDTO* @return*/OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception;/*** 支付成功,修改订单状态* @param outTradeNo*/void paySuccess(String outTradeNo);
    }
    
  • OrdersPaymentDTO.java :

    @Data
    public class OrdersPaymentDTO implements Serializable {//订单号private String orderNumber;//付款方式private Integer payMethod;
    }
    
  • OrderPaymentVO.java :

    @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 参数值
    }
    

OrderServiceImpl.java (包含 : 微信支付工具类)

  • OrderServiceImpl.java :

    @Service //将该类加入到容器中,成为bean
    public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate OrderdetailMapper orderdetailMapper;@Autowiredprivate AddressBookMapper addressBookMapper;@Autowiredprivate ShoppingcartMapper shoppingcartMapper;@Autowiredprivate WeChatPayUtil weChatPayUtil;@Autowiredprivate UserMapper userMapper;/*** 订单支付** @param ordersPaymentDTO* @return*/public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {// 当前登录用户idLong userId = BaseContext.getCurrentId();User user = userMapper.getById(userId);//调用微信支付接口,生成预支付交易单JSONObject jsonObject = weChatPayUtil.pay(ordersPaymentDTO.getOrderNumber(), //商户订单号new BigDecimal(0.01), //支付金额,单位 元"苍穹外卖订单", //商品描述user.getOpenid() //微信用户的openid);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;}/*** 支付成功,修改订单状态** @param outTradeNo*/public void paySuccess(String outTradeNo) {// 根据订单号查询订单Orders ordersDB = orderMapper.getByNumber(outTradeNo);// 根据订单id更新订单的状态、支付方式、支付状态、结账时间Orders orders = Orders.builder().id(ordersDB.getId()).status(Orders.TO_BE_CONFIRMED).payStatus(Orders.PAID).checkoutTime(LocalDateTime.now()).build();orderMapper.update(orders);}
    }
    
  • BaseContext.class

    public class BaseContext {  //该类对ThreadLocal对象本身其其下的三个方法进行了封装,方便且更好的调用//创建 ThreadLocal 对象,可在其中设置“线程局部变量”,存储数据该独享的线程中,后“该存入的数据”会被取出来public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();/*** 存入请求用户的id* (设置“线程局部变量”)* @param id*/public static void setCurrentId(Long id) {//调用ThreadLocal对象的.set(T value) 设置线程局部变量 / 存储数据进该“请求”独享的“线程”中threadLocal.set(id);}/*** 获得请求用户的id* (获得“线程局部变量”)* @return*/public static Long getCurrentId() {return threadLocal.get();}/*** 移除请求用户的id* (移除“线程局部变量”)*/public static void removeCurrentId() {threadLocal.remove();}
    }
    
  • 微信支付工具类 / WeChatPayUtil.java :

    *** 微信支付工具类*/
    @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";@Autowiredprivate 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);}
    }
    
  • OrderBusinessException.java :

    public class OrderBusinessException extends BaseException {public OrderBusinessException(String msg) {super(msg);}
    }
    

OrderMapper.java

  • OrderMapper.java :

    @Mapper 
    public interface OrderMapper {/*** 根据订单号查询订单* @param orderNumber*/@Select("select * from orders where number = #{orderNumber}")Orders getByNumber(String orderNumber);/*** 修改订单信息* @param orders*/void update(Orders orders);
    }
    
  • Orders.java :

    /*** 订单*/
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class Orders implements Serializable {/*** 订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消*/public static final Integer PENDING_PAYMENT = 1;public static final Integer TO_BE_CONFIRMED = 2;public static final Integer CONFIRMED = 3;public static final Integer DELIVERY_IN_PROGRESS = 4;public static final Integer COMPLETED = 5;public static final Integer CANCELLED = 6;/*** 支付状态 0未支付 1已支付 2退款*/public static final Integer UN_PAID = 0;public static final Integer PAID = 1;public static final Integer REFUND = 2;private static final long serialVersionUID = 1L;private Long id;//订单号private String number;//订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消 7退款private Integer status;//下单用户idprivate Long userId;//地址idprivate Long addressBookId;//下单时间private LocalDateTime orderTime;//结账时间private LocalDateTime checkoutTime;//支付方式 1微信,2支付宝private Integer payMethod;//支付状态 0未支付 1已支付 2退款private Integer payStatus;//实收金额private BigDecimal amount;//备注private String remark;//用户名private String userName;//手机号private String phone;//地址private String address;//收货人private String consignee;//订单取消原因private String cancelReason;//订单拒绝原因private String rejectionReason;//订单取消时间private LocalDateTime cancelTime;//预计送达时间private LocalDateTime estimatedDeliveryTime;//配送状态  1立即送出  0选择具体时间private Integer deliveryStatus;//送达时间private LocalDateTime deliveryTime;//打包费private int packAmount;//餐具数量private int tablewareNumber;//餐具数量状态  1按餐量提供  0选择具体数量private Integer tablewareStatus;
    }
    

OrderMapper.xml

  • OrderMapper.xml :

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.sky.mapper.OrderMapper"><update id="update" parameterType="com.sky.entity.Orders">update orders<set><if test="cancelReason != null and cancelReason!='' ">cancel_reason=#{cancelReason},</if><if test="rejectionReason != null and rejectionReason!='' ">rejection_reason=#{rejectionReason},</if><if test="cancelTime != null">cancel_time=#{cancelTime},</if><if test="payStatus != null">pay_status=#{payStatus},</if><if test="payMethod != null">pay_method=#{payMethod},</if><if test="checkoutTime != null">checkout_time=#{checkoutTime},</if><if test="status != null">status = #{status},</if><if test="deliveryTime != null">delivery_time = #{deliveryTime}</if></set>where id = #{id}</update></mapper>
    

UserMapper.java

  • UserMapper.java :

    @Mapper 
    public interface UserMapper {/*** 根据id查询数据*/@Select("select * from user where id = #{id}")User getById(Long userId);
    }
    

PayNotifyController.java / 支付回调相关接口

  • PayNotifyController.java / 支付回调相关接口:

    /*** 支付回调相关接口*/
    @RestController
    @RequestMapping("/notify")
    @Slf4j
    public class PayNotifyController {@Autowiredprivate OrderService orderService;@Autowiredprivate 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);//业务处理,修改订单状态、来单提醒orderService.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();}
    }
    

application.yml (springboot配置文件)

  • application.yml

    server:port: 8080spring:profiles:active: devmain:allow-circular-references: truedatasource:druid:driver-class-name: ${sky.datasource.driver-class-name}url: jdbc:mysql://${sky.datasource.host}:${sky.datasource.port}/${sky.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=trueusername: ${sky.datasource.username}password: ${sky.datasource.password}redis:host: ${sky.redis.host}port: ${sky.redis.port}password: ${sky.redis.password}database: ${sky.redis.database}mybatis:#mapper配置文件mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.sky.entityconfiguration:#开启驼峰命名map-underscore-to-camel-case: truelogging:level:com:sky:mapper: debugservice: infocontroller: infosky:jwt:# 设置jwt签名加密时使用的秘钥admin-secret-key: itcast# 设置jwt过期时间admin-ttl: 7200000# 设置前端传递过来的令牌名称admin-token-name: tokenuser-secret-key: itheimauser-ttl: 7200000user-token-name: authenticationalioss:endpoint: ${sky.alioss.endpoint}access-key-id: ${sky.alioss.access-key-id}access-key-secret: ${sky.alioss.access-key-secret}bucket-name: ${sky.alioss.bucket-name}wechat:appid: ${sky.wechat.appid}secret: ${sky.wechat.secret}mchid : ${sky.wechat.mchid}mchSerialNo: ${sky.wechat.mchSerialNo}privateKeyFilePath: ${sky.wechat.privateKeyFilePath}apiV3Key: ${sky.wechat.apiV3Key}weChatPayCertFilePath: ${sky.wechat.weChatPayCertFilePath}notifyUrl: ${sky.wechat.notifyUrl}refundNotifyUrl: ${sky.wechat.refundNotifyUrl}shop:address: 北京市海淀区上地十街10号baidu:ak: your-ak
    

application-dev.xml

  • application-dev.xml

    sky:datasource:driver-class-name: com.mysql.cj.jdbc.Driverhost: localhostport: 3306database: sky_take_outusername: rootpassword: rootalioss:endpoint: oss-cn-beijing.aliyuncs.comaccess-key-id: your-access-key-idaccess-key-secret: your-access-key-secretbucket-name: your-bucket-nameredis:host: localhostport: 6379password: 123456database: 10wechat:appid: wxffb3637a228223b8secret: 84311df9199ecacdf4f12d27b6b9522dmchid : 1561414331mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606privateKeyFilePath: D:\pay\apiclient_key.pemapiV3Key: CZBK51236435wxpay435434323FFDuv3weChatPayCertFilePath: D:\pay\wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pemnotifyUrl: https://58869fb.r2.cpolar.top/notify/paySuccessrefundNotifyUrl: https://58869fb.r2.cpolar.top/notify/refundSuccess
    

相关文章:

SpringBoot | SpringBoot中实现“微信支付“

SpringBoot中实现"微信支付": 1.“微信支付”产品2."微信支付"接入流程3.“微信小程序支付”时序图&#xff1a;3.1 “商家端JSAPI下单” 接口3.2 “微信小程序端调起支付” 接口 4.微信支付准备工作&#xff1a;4.1 获得微信支付平台证书、商户私钥文件4…...

基于SSM和VUE的留守儿童信息管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…...

VMware 16开启虚拟机电脑就蓝屏W11解决方法

问题现象 解决方法 控制面板->程序->启用或关闭windows功能->勾选虚拟机平台->重启...

【Bug——VMware Workstation】虚拟机桥接网络没有 VMnet0

此时 没有VMnet0用来桥接网络。 接下来进行解决 1.找到安装VM的路径&#xff0c;在安装的目录里面找到如图所示的三个文件&#xff1a; 2.依次点击鼠标右键 将这三个文件依次安装如图所示&#xff1a; 二.windows下的操作 1.首先 找到电脑的控制面板->网络和internet->…...

centos中安装Mysql8.0

其实和mysql5.7的安装差不多 1.root用户 2.更新密钥 rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 3.安装mysql yum库 rpm -Uvh https://dev.mysql.com/ get/mysql80-community-release-el7-2.noarch.rpm 4.通过上两步&#xff0c;我们就可以使用yum去安装…...

简化对象和函数写法

简化对象写法&#xff1a; 传统写法&#xff1a; var x 10, y 20; var obj {x: x, y: y};简化写法&#xff1a; var x 10, y 20; var obj {x, y};简化函数写法&#xff1a; 传统写法&#xff1a; function add(x, y) {return x y; }简化写法&#xff1a; var add …...

GB/T28181流媒体相关协议详解

GB/T28181流媒体相关协议详解 文章目录 GB/T28181流媒体相关协议详解1 GB/T28181协议中使用的应用层协议介绍2 实时视频点播协议交互流程2.1 设备注册2.2 设备保活2.3 视频播放 总结 本文主要主要针对28181协议中视频流的部分&#xff0c;来阐述视频流通过28181协议如何进行视频…...

十进制转二进制的算法代码 ← Python

【算法分析】 本算法需要用到的Python知识点&#xff1a; 1.求余%&#xff0c;整除 //。例如&#xff0c;7%21&#xff0c;7//23&#xff0c;而7/23.5。 2.Python列表的 append 及 pop 函数。 • append(x) 函数用于将 x 添加到现有列表中。 • pop() 函数默认移除列表中…...

智慧垃圾站:AI视频智能识别技术助力智慧环保项目,以“智”替人强监管

一、背景分析 建设“技术先进、架构合理、开放智能、安全可靠”的智慧环保平台&#xff0c;整合环境相关的数据&#xff0c;对接已建业务系统&#xff0c;将环境相关数据进行统一管理&#xff0c;结合GIS技术进行监测、监控信息的展现和挖掘分析&#xff0c;实现业务数据的快速…...

LeetCode 面试题 16.07. 最大数值

文章目录 一、题目二、C# 题解 一、题目 编写一个方法&#xff0c;找出两个数字a和b中最大的那一个。不得使用if-else或其他比较运算符。 示例&#xff1a; 输入&#xff1a; a 1, b 2 输出&#xff1a; 2 点击此处跳转题目。 二、C# 题解 找出最大的数&#xff0c;本质还是…...

PS 安装教程 2022版(全网最详细图文教程)

目录 一.简介 二.安装步骤 软件&#xff1a;PS版本&#xff1a;2022语言&#xff1a;简体中文大小&#xff1a;2.83G安装环境&#xff1a;Win10&#xff08;1903&#xff09;及以上版本&#xff0c;64位操作系统硬件要求&#xff1a;CPU2.0GHz 内存4G(或更高&#xff0c;不支…...

[Python] OSError: [E050] Can‘t find model ‘en_core_web_sm‘.

OSError: [E050] Can’t find model ‘en_core_web_sm’. It doesn’t seem to be a Python package or a valid path to a data directory. 下载模型 python -m spacy download en_core_web_sm加载模型 import spacy# 加载英语模型 nlp spacy.load("en_core_web_sm&q…...

集合总结(Java)

Collection 常用方法 package com.test01;import java.util.ArrayList; import java.util.Collection; /*添加元素---boolean add(E e);移除元素---boolean remove(Object c);判断元素是否存在---boolean contains(Object c);*/ public class S {public static void main(Str…...

【ONE·Linux || 网络基础(一)】

总言 主要内容&#xff1a;简述网络传输流程&#xff08;TCP/IP五层模式概念认知&#xff0c;Mac地址、端口号、网络字节序等&#xff09;&#xff0c;演示socke套接字编程&#xff08;UDP模式&#xff09;。 文章目录 总言1、基础简述1.1、计算机网络背景1.2、认识网络协议&a…...

Day12力扣打卡

打卡记录 找出满足差值条件的下标 II&#xff08;双指针维护最大最小&#xff09; 链接 采用双指针保留间隔 indexDifference 进行遍历&#xff0c;求出慢指针对应一路遍历过来的最大值和最小值。 class Solution { public:vector<int> findIndices(vector<int>…...

SQL注入原理及思路(mysql)

数据库知识 mysql数据库 show database; #列出所有数据库 show tables; #列出所有表名 show columns from 表名; #列出表的列 select * from 表名 #查询数据库中某表的信息 select * from 表名 where 列xx #查询某表中符合列xx的信息 select * from 表名 order by 数字 #用于将…...

vue核心面试题汇总【查缺补漏】

给大家推荐一个实用面试题库 1、前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;web前端面试题库 很喜欢‘万变不离其宗’这句话&#xff0c;希望在不断的思考和总结中找到Vue中的宗&#xff0c;来解答面试官抛出的…...

使用WebStorm创建和配置TypeScript项目

创建 这里我用的是WebStorm 2019.2.2版本 首先&#xff0c;创建一个空项目 File -> New -> Project->Empty Project生成配置文件 自动配置&#xff1a; 打开终端输入tsc --init&#xff0c;即可自动生成tsconfig.json文件 手动配置&#xff1a; 在项目根目录下新建一…...

vue源码分析(四)——vue 挂载($mount)的详细过程

文章目录 前言一、使用RuntimeCompiler解析$mount的原因二、$mount 解析的详细过程1.解析挂载的#app执行了vm.$mount2. 通过$mount方法执行以下文件的mount方法3. 执行util工具文件夹中的query方法4. 执行query方法后返回$mount方法判断el是否是body5. 判断!options.render&…...

真机环境配置教程

1.下载安装包 https://developers.google.com/android/images 2.刷机教程 Xposed精品连载 | 一篇文章彻底搞定安卓刷机与Root 3.配置root...

新电脑第一次重启后蓝屏

新电脑第一次重启后蓝屏 悲惨事故&#xff0c;远程参加插电第一次开机&#xff0c;按“FNShiftF10”启动cmd窗口输入oobe\bypassnro 回车重启跳过网络连接&#xff0c;设置一个用户名密码设置为空&#xff0c;不设密码确定&#xff0c;进入系统软件操作磁盘操作&#xff08;磁盘…...

k8s statefulSet 学习笔记

文章目录 缩写: stsweb-sts.yaml创建sts扩缩容金丝雀发布OnDelete 删除时更新 缩写: sts 通过 kubectl api-resources 可以查到&#xff1a; NAMESHORTNAMESAPIVERSIONNAMESPACEDKINDstatefulsetsstsapps/v1trueStatefulSet web-sts.yaml apiVersion: v1 kind: Service met…...

gitlab 通过变量连接自建K8S

services:- docker:19.03.7-dind- golang:1.17.8-alpine3.15- docker:stable stages:- package- build and push docker image- deploy variables:KUBECONFIG: /etc/deploy/config build:tags:- k8simage: golang:1.17.8-alpine3.15stage: package# 只作用在main分支only:- mai…...

LuatOS-SOC接口文档(air780E)--mcu - 封装mcu一些特殊操作

常量 常量 类型 解释 mcu.UART number 外设类型-串口 mcu.I2C number 外设类型-I2C mcu.SPI number 外设类型-SPI mcu.PWM number 外设类型-PWM mcu.GPIO number 外设类型-GPIO mcu.I2S number 外设类型-I2S mcu.LCD number 外设类型-LCD mcu.CAM num…...

第14期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练 Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大型语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以…...

【数据结构】优先级队列

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈数据结构 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; PriorityQueue 1. 什么是优先级队列…...

c语言宏相关高级用法

outline all可变参数宏c语言内置函数1.__typeof__2.__builtin_choose_expr all 记录一些c语言宏相关的高级用法 可变参数宏 c语言内置函数 1.typeof 2.__builtin_choose_expr 语法格式 type __builtin_choose_expr (const_exp, exp1, exp2)解释 这个函数的第一个参数必须…...

新款模块上线实现SIP模块与扩拨电话之间打点与喊话功能 IP矿用电话模块SV-2800VP

新款模块上线实现SIP模块与扩拨电话之间打点与喊话功能 IP矿用电话模块SV-2800VP 一、简介 SV-2800VP系列模块是我司设计研发的一款用于井下的矿用IP音频传输模块&#xff0c;可用此模块打造一套低延迟、高效率、高灵活和多扩展的IP矿用广播对讲系统&#xff0c;亦可对传统煤…...

前端开发---在vue项目中使用openLayers

前端开发之在vue项目中使用openLayers 前言效果图在vue中渲染地图安装ol插件1、调用插件2、 初始话地图3、地图点击事件4、重置坐标5、通过坐标改变视图6、保存坐标点 vue中使用的源码 前言 本篇文章主要讲解openLayers的初步使用&#xff0c;包括渲染地图、获取点坐标、标记点…...

C语言之结构体和共用体详解

目录 结构体 结构体的定义和使用 结构体数组的使用 结构体指针的使用 结构体大小的计算 共用体 共用体的定义和使用 typedef用法详解 enum枚举类型 结构体 结构体的定义和使用 C语言的结构体&#xff08;Struct&#xff09;是一种自定义的数据类型&#xff0c;它允许…...