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...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...











