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" }
适用对象: 直连商户
请求URL:https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
请求方式:POST请求参数
请求参数-详解
参数名 变量 类型[长度限制] 必填 描述 应用ID appid string[1,32] 是 body 由微信生成的应用ID,全局唯一。请求基础下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的服务号APPID 示例值:wxd678efh567hg6787 直连商户号 mchid string[1,32] 是 body 直连商户的商户号,由微信支付生成并下发。 示例值:1230000109 商品描述 description string[1,127] 是 body 商品描述 示例值:Image形象店-深圳腾大-QQ公仔 通知地址 notify_url string[1,256] 是 body异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http 示例值:https://www.weixin.qq.com/wxpay/pay.php 商户订单号 out_trade_no string[6,32] 是 body 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 示例值:1217752501201407033233368018
订单金额 amount object 是 body 订单金额信息 参数名 变量 类型[长度限制] 必填 描述 总金额 total int 是 订单总金额,单位为分。 示例值:100 货币类型 currency string[1,16] 否 CNY:人民币,境内商户号仅支持人民币。 示例值:CNY
支付者 payer object 是 body 支付者信息 参数名 变量 类型[长度限制] 必填 描述 用户标识 openid string[1,128] 是 用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid,Openid获取详见 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o 返回参数
参数名 变量 类型[长度限制] 必填 描述 预支付交易会话标识 prepay_id string[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无后台接口交互,需要将列表中的数据签名。
参数名 变量 类型[长度限制] 必填 描述 小程序ID appId string[1,32] 是 商户申请的小程序对应的appid,由微信支付生成,可在小程序后台查看 示例值:wx8888888888888888 时间戳 timeStamp string[1,32] 是 时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数。注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)。 示例值:1414561699 随机字符串 nonceStr string[1,32] 是 随机字符串,不长于32位。推荐随机数生成算法。 示例值:5K8264ILTKCH16CQ2502SI8ZNMTM67VS 订单详情扩展字符串 package string[1,128] 是 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** 示例值:prepay_id=
wx201410272009395522657a690389285100签名方式 signType string[1,32] 是 签名类型,默认为RSA,仅支持RSA。 示例值:RSA 签名 paySign string[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 参数说明:
( 这些需要用到的参数全都是后端计算好,返回给微信小程序,然后小程序直接使用这些参数来调用方法来就会弹出“微信支付”的窗口,来完成微信支付。 )
参数名 变量 类型[长度限制] 必填 描述 时间戳 timeStamp string[1,32] 是 当前的时间,其他详见时间戳规则。 示例值:1414561699 随机字符串 nonceStr string[1,32] 是 随机字符串,不长于32位。 示例值:5K8264ILTKCH16CQ2502SI8ZNMTM67VS 订单详情扩展字符串 package string[1,128] 是 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** 示例值:prepay_id=wx201410272009395522657a690389285100 签名方式 signType string[1,32] 是 签名类型,默认为RSA,仅支持RSA。 示例值:RSA 签名 paySign string[1,512] 是 签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值 示例值:oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq/xDg==
- 回调结果
回调类型 errMsg 说明 success requestPayment:ok 调用支付成功 fail requestPayment:fail cancel 用户取消支付 fail requestPayment: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.“微信小程序支付”时序图:3.1 “商家端JSAPI下单” 接口3.2 “微信小程序端调起支付” 接口 4.微信支付准备工作:4.1 获得微信支付平台证书、商户私钥文件4…...

基于SSM和VUE的留守儿童信息管理系统
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…...

VMware 16开启虚拟机电脑就蓝屏W11解决方法
问题现象 解决方法 控制面板->程序->启用或关闭windows功能->勾选虚拟机平台->重启...

【Bug——VMware Workstation】虚拟机桥接网络没有 VMnet0
此时 没有VMnet0用来桥接网络。 接下来进行解决 1.找到安装VM的路径,在安装的目录里面找到如图所示的三个文件: 2.依次点击鼠标右键 将这三个文件依次安装如图所示: 二.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.通过上两步,我们就可以使用yum去安装…...

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

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

十进制转二进制的算法代码 ← Python
【算法分析】 本算法需要用到的Python知识点: 1.求余%,整除 //。例如,7%21,7//23,而7/23.5。 2.Python列表的 append 及 pop 函数。 • append(x) 函数用于将 x 添加到现有列表中。 • pop() 函数默认移除列表中…...

智慧垃圾站:AI视频智能识别技术助力智慧环保项目,以“智”替人强监管
一、背景分析 建设“技术先进、架构合理、开放智能、安全可靠”的智慧环保平台,整合环境相关的数据,对接已建业务系统,将环境相关数据进行统一管理,结合GIS技术进行监测、监控信息的展现和挖掘分析,实现业务数据的快速…...

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

PS 安装教程 2022版(全网最详细图文教程)
目录 一.简介 二.安装步骤 软件:PS版本:2022语言:简体中文大小:2.83G安装环境:Win10(1903)及以上版本,64位操作系统硬件要求:CPU2.0GHz 内存4G(或更高,不支…...

[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 || 网络基础(一)】
总言 主要内容:简述网络传输流程(TCP/IP五层模式概念认知,Mac地址、端口号、网络字节序等),演示socke套接字编程(UDP模式)。 文章目录 总言1、基础简述1.1、计算机网络背景1.2、认识网络协议&a…...

Day12力扣打卡
打卡记录 找出满足差值条件的下标 II(双指针维护最大最小) 链接 采用双指针保留间隔 indexDifference 进行遍历,求出慢指针对应一路遍历过来的最大值和最小值。 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、前端面试题库 (面试必备) 推荐:★★★★★ 地址:web前端面试题库 很喜欢‘万变不离其宗’这句话,希望在不断的思考和总结中找到Vue中的宗,来解答面试官抛出的…...

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

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...

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

k8s statefulSet 学习笔记
文章目录 缩写: stsweb-sts.yaml创建sts扩缩容金丝雀发布OnDelete 删除时更新 缩写: sts 通过 kubectl api-resources 可以查到: 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是一个涵盖了前沿学术研究和实践经验分享的社区,集成了生成预训练 Transformer(GPT)、人工智能生成内容(AIGC)以及大型语言模型(LLM)等安全领域应用的知识。在这里,您可以…...

【数据结构】优先级队列
⭐ 作者:小胡_不糊涂 🌱 作者主页:小胡_不糊涂的个人主页 📀 收录专栏:浅谈数据结构 💖 持续更文,关注博主少走弯路,谢谢大家支持 💖 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音频传输模块,可用此模块打造一套低延迟、高效率、高灵活和多扩展的IP矿用广播对讲系统,亦可对传统煤…...

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

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