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 / 支付回调相关接口
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 ( { "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 { @Autowired private 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 { @Autowired private OrderMapper orderMapper; @Autowired private OrderdetailMapper orderdetailMapper; @Autowired private AddressBookMapper addressBookMapper; @Autowired private ShoppingcartMapper shoppingcartMapper; @Autowired private WeChatPayUtil weChatPayUtil; @Autowired private UserMapper userMapper; /** * 订单支付 * * @param ordersPaymentDTO * @return */ public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception { // 当前登录用户id Long 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"; @Autowired private WeChatProperties weChatProperties; /** * 获取调用微信接口的客户端工具对象 * * @return */ private CloseableHttpClient getClient() { PrivateKey merchantPrivateKey = null; try { //merchantPrivateKey商户API私钥,如何加载商户API私钥请看常见问题 merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))); //加载平台证书文件 X509Certificate x509Certificate = PemUtil.loadCertificate(new FileInputStream(new File(weChatProperties.getWeChatPayCertFilePath()))); //wechatPayCertificates微信支付平台证书列表。你也可以使用后面章节提到的“定时更新平台证书功能”,而不需要关心平台证书的来龙去脉 List<X509Certificate> wechatPayCertificates = Arrays.asList(x509Certificate); WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create() .withMerchant(weChatProperties.getMchid(), weChatProperties.getMchSerialNo(), merchantPrivateKey) .withWechatPay(wechatPayCertificates); // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签 CloseableHttpClient httpClient = builder.build(); return httpClient; } catch (FileNotFoundException e) { e.printStackTrace(); return null; } } /** * 发送post方式请求 * * @param url * @param body * @return */ private String post(String url, String body) throws Exception { CloseableHttpClient httpClient = getClient(); HttpPost httpPost = new HttpPost(url); httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString()); httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo()); httpPost.setEntity(new StringEntity(body, "UTF-8")); CloseableHttpResponse response = httpClient.execute(httpPost); try { String bodyAsString = EntityUtils.toString(response.getEntity()); return bodyAsString; } finally { httpClient.close(); response.close(); } } /** * 发送get方式请求 * * @param url * @return */ private String get(String url) throws Exception { CloseableHttpClient httpClient = getClient(); HttpGet httpGet = new HttpGet(url); httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString()); httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo()); CloseableHttpResponse response = httpClient.execute(httpGet); try { String bodyAsString = EntityUtils.toString(response.getEntity()); return bodyAsString; } finally { httpClient.close(); response.close(); } } /** * jsapi下单 * * @param orderNum 商户订单号 * @param total 总金额 * @param description 商品描述 * @param openid 微信用户的openid * @return */ private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception { JSONObject jsonObject = new JSONObject(); jsonObject.put("appid", weChatProperties.getAppid()); jsonObject.put("mchid", weChatProperties.getMchid()); jsonObject.put("description", description); jsonObject.put("out_trade_no", orderNum); jsonObject.put("notify_url", weChatProperties.getNotifyUrl()); JSONObject amount = new JSONObject(); amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue()); amount.put("currency", "CNY"); jsonObject.put("amount", amount); JSONObject payer = new JSONObject(); payer.put("openid", openid); jsonObject.put("payer", payer); String body = jsonObject.toJSONString(); return post(JSAPI, body); } /** * 小程序支付 * * @param orderNum 商户订单号 * @param total 金额,单位 元 * @param description 商品描述 * @param openid 微信用户的openid * @return */ public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception { //统一下单,生成预支付交易单 String bodyAsString = jsapi(orderNum, total, description, openid); //解析返回结果 JSONObject jsonObject = JSON.parseObject(bodyAsString); System.out.println(jsonObject); String prepayId = jsonObject.getString("prepay_id"); if (prepayId != null) { String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); String nonceStr = RandomStringUtils.randomNumeric(32); ArrayList<Object> list = new ArrayList<>(); list.add(weChatProperties.getAppid()); list.add(timeStamp); list.add(nonceStr); list.add("prepay_id=" + prepayId); //二次签名,调起支付需要重新签名 StringBuilder stringBuilder = new StringBuilder(); for (Object o : list) { stringBuilder.append(o).append("\n"); } String signMessage = stringBuilder.toString(); byte[] message = signMessage.getBytes(); Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath())))); signature.update(message); String packageSign = Base64.getEncoder().encodeToString(signature.sign()); //构造数据给微信小程序,用于调起微信支付 JSONObject jo = new JSONObject(); jo.put("timeStamp", timeStamp); jo.put("nonceStr", nonceStr); jo.put("package", "prepay_id=" + prepayId); jo.put("signType", "RSA"); jo.put("paySign", packageSign); return jo; } return jsonObject; } /** * 申请退款 * * @param outTradeNo 商户订单号 * @param outRefundNo 商户退款单号 * @param refund 退款金额 * @param total 原订单金额 * @return */ public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception { JSONObject jsonObject = new JSONObject(); jsonObject.put("out_trade_no", outTradeNo); jsonObject.put("out_refund_no", outRefundNo); JSONObject amount = new JSONObject(); amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue()); amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue()); amount.put("currency", "CNY"); jsonObject.put("amount", amount); jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl()); String body = jsonObject.toJSONString(); //调用申请退款接口 return post(REFUNDS, body); } }
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; //下单用户id private Long userId; //地址id private 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 mapper PUBLIC "-//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 { @Autowired private OrderService orderService; @Autowired private WeChatProperties weChatProperties; /** * 支付成功回调 * * @param request */ @RequestMapping("/paySuccess") public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { //读取数据 String body = readData(request); log.info("支付成功回调:{}", body); //数据解密 String plainText = decryptData(body); log.info("解密后的文本:{}", plainText); JSONObject jsonObject = JSON.parseObject(plainText); String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号 String transactionId = jsonObject.getString("transaction_id");//微信支付交易号 log.info("商户平台订单号:{}", outTradeNo); log.info("微信支付交易号:{}", transactionId); //业务处理,修改订单状态、来单提醒 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(); } }