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