WxPayController.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. package com.ruoyi.wx.web.controller;
  2. import cn.hutool.core.date.DateField;
  3. import cn.hutool.core.date.DateUtil;
  4. import com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse;
  5. import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
  6. import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
  7. import com.github.binarywang.wxpay.bean.request.WxPayOrderCloseRequest;
  8. import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryRequest;
  9. import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
  10. import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
  11. import com.github.binarywang.wxpay.bean.result.WxPayOrderCloseResult;
  12. import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult;
  13. import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
  14. import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
  15. import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
  16. import com.github.binarywang.wxpay.exception.WxPayException;
  17. import com.github.binarywang.wxpay.service.WxPayService;
  18. import com.google.gson.GsonBuilder;
  19. import com.ruoyi.common.core.domain.AjaxResult;
  20. import com.ruoyi.common.core.domain.entity.WxUser;
  21. import com.ruoyi.common.utils.StringUtils;
  22. import com.ruoyi.framework.recovery.domain.WxRecharge;
  23. import com.ruoyi.framework.recovery.service.impl.WxRechargeServiceImpl;
  24. import com.ruoyi.framework.recovery.service.impl.WxUserServiceImpl;
  25. import com.ruoyi.wx.web.domain.dto.WxPayDto;
  26. import com.ruoyi.wx.web.unit.WeixinNotifyUtils;
  27. import io.swagger.annotations.ApiOperation;
  28. import jakarta.servlet.http.HttpServletRequest;
  29. import jakarta.servlet.http.HttpServletResponse;
  30. import lombok.extern.slf4j.Slf4j;
  31. import org.springframework.beans.factory.annotation.Autowired;
  32. import org.springframework.web.bind.annotation.*;
  33. import java.math.BigDecimal;
  34. import java.util.Date;
  35. import java.util.concurrent.locks.ReentrantLock;
  36. /**
  37. *
  38. * 支付接口
  39. */
  40. @Slf4j
  41. @RestController
  42. @RequestMapping("/wx/pay")
  43. public class WxPayController {
  44. @Autowired
  45. private WxPayService wxPayService;
  46. @Autowired
  47. private WxRechargeServiceImpl wxRechargeService;
  48. @Autowired
  49. private WxUserServiceImpl wxUserService;
  50. private final ReentrantLock orderLock = new ReentrantLock();
  51. /**
  52. * <pre>
  53. * 查询订单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2)
  54. * 该接口提供所有微信支付订单的查询,商户可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。
  55. * 需要调用查询接口的情况:
  56. * ◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
  57. * ◆ 调用支付接口后,返回系统错误或未知交易状态情况;
  58. * ◆ 调用被扫支付API,返回USERPAYING的状态;
  59. * ◆ 调用关单或撤销接口API之前,需确认支付状态;
  60. * 接口地址:https://api.mch.weixin.qq.com/pay/orderquery
  61. * </pre>
  62. *
  63. * @param transactionId 微信订单号
  64. * @param outTradeNo 商户系统内部的订单号,当没提供transactionId时需要传这个。
  65. */
  66. @ApiOperation(value = "查询订单")
  67. @GetMapping("/query/order")
  68. public WxPayOrderQueryResult queryOrder(@RequestParam(required = false) String transactionId,
  69. @RequestParam(required = false) String outTradeNo)
  70. throws WxPayException {
  71. return this.wxPayService.queryOrder(transactionId, outTradeNo);
  72. }
  73. @ApiOperation(value = "查询订单")
  74. @PostMapping("/query/order")
  75. public WxPayOrderQueryResult queryOrder(@RequestBody WxPayOrderQueryRequest wxPayOrderQueryRequest) throws WxPayException {
  76. return this.wxPayService.queryOrder(wxPayOrderQueryRequest);
  77. }
  78. /**
  79. * <pre>
  80. * 关闭订单
  81. * 应用场景
  82. * 以下情况需要调用关单接口:
  83. * 1. 商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
  84. * 2. 系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
  85. * 注意:订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟。
  86. * 接口地址:https://api.mch.weixin.qq.com/pay/closeorder
  87. * 是否需要证书: 不需要。
  88. * </pre>
  89. *
  90. * @param outTradeNo 商户系统内部的订单号
  91. */
  92. @ApiOperation(value = "关闭订单")
  93. @GetMapping("/closeOrder/{outTradeNo}")
  94. public WxPayOrderCloseResult closeOrder(@PathVariable String outTradeNo) throws WxPayException {
  95. return this.wxPayService.closeOrder(outTradeNo);
  96. }
  97. @ApiOperation(value = "关闭订单")
  98. @PostMapping("/closeOrder")
  99. public WxPayOrderCloseResult closeOrder(@RequestBody WxPayOrderCloseRequest wxPayOrderCloseRequest) throws WxPayException {
  100. return this.wxPayService.closeOrder(wxPayOrderCloseRequest);
  101. }
  102. /**
  103. * 调用统一下单接口,并组装生成支付所需参数对象.
  104. *
  105. * @param dto 统一下单请求参数
  106. * @return 返回 {@link com.github.binarywang.wxpay.bean.order}包下的类对象
  107. */
  108. @ApiOperation(value = "统一下单,并组装所需支付参数")
  109. @PostMapping("/createOrder")
  110. public AjaxResult createOrder(@RequestBody WxPayDto dto) throws WxPayException {
  111. log.info("开始下单:{}", dto);
  112. WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
  113. request.setDescription("购买会员");
  114. // 商品订单号
  115. String orderNo = "ORDER_" + System.currentTimeMillis();
  116. request.setOutTradeNo(orderNo);
  117. // 订单金额
  118. WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
  119. amount.setCurrency("CNY");
  120. BigDecimal total = new BigDecimal(dto.gettBal());
  121. if (StringUtils.isNotEmpty(dto.getMoney())) {
  122. total = total.subtract(new BigDecimal(dto.getMoney()));
  123. } else if (StringUtils.isNotEmpty(dto.getDiscount())) {
  124. total = total.multiply(new BigDecimal(dto.getDiscount()).divide(new BigDecimal(10)));
  125. }
  126. log.info("用户:{},支付总金额为:{}, 订单Id为:{}", dto.getOpenId(), amount, orderNo);
  127. amount.setTotal(total.intValue() * 100);
  128. request.setAmount(amount);
  129. WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
  130. payer.setOpenid(dto.getOpenId());
  131. request.setPayer(payer);
  132. WxPayUnifiedOrderV3Result.JsapiResult orderV3 = this.wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
  133. AjaxResult result = new AjaxResult();
  134. WxRecharge wxRecharge = new WxRecharge();
  135. wxRecharge.setPoints(dto.getPoints());
  136. wxRecharge.setPrice(total.divide(new BigDecimal(100)));
  137. wxRecharge.setPayTime(new Date());
  138. WxUser wxUser = wxUserService.selectWxUSerByOpenid(dto.getOpenId());
  139. wxRecharge.setUserId(wxUser.getId());
  140. wxRecharge.setUserName(wxUser.getUsername());
  141. wxRechargeService.insertWxRecharge(wxRecharge);
  142. wxUser.setUserLevel(dto.getVipLv());
  143. wxUser.setVipStartTime(new Date());
  144. wxUser.setVipEndTime(defDate(new Date(), dto.getVipLv()));
  145. wxUserService.updateWxUser(wxUser);
  146. result.put("appId", orderV3.getAppId());
  147. result.put("timeStamp", orderV3.getTimeStamp());
  148. result.put("nonceStr", orderV3.getNonceStr());
  149. result.put("packageValue", orderV3.getPackageValue());
  150. result.put("signType", orderV3.getSignType());
  151. result.put("outTradeNo", orderNo);
  152. return result;
  153. }
  154. /**
  155. * 统一下单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)
  156. * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识"
  157. * 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
  158. *
  159. * @param request 请求对象,注意一些参数如appid、mchid等不用设置,方法内会自动从配置对象中获取到(前提是对应配置中已经设置)
  160. */
  161. @ApiOperation(value = "原生的统一下单接口")
  162. @PostMapping("/unifiedOrder")
  163. public WxPayUnifiedOrderResult unifiedOrder(@RequestBody WxPayUnifiedOrderRequest request) throws WxPayException {
  164. return this.wxPayService.unifiedOrder(request);
  165. }
  166. /**
  167. * 回调接口
  168. */
  169. @ApiOperation(value = "回调接口")
  170. @PostMapping("/notify")
  171. public AjaxResult notify(HttpServletRequest request, HttpServletResponse response, @RequestBody String notifyData) throws WxPayException {
  172. log.debug("======= 接收到通知 =========");
  173. // 获取请求头信息
  174. SignatureHeader signatureHeader = WeixinNotifyUtils.getSignatureHeader(request);
  175. // 将请求体json字符串转换为实体
  176. OriginNotifyResponse notifyResponse = new GsonBuilder().create().fromJson(notifyData, OriginNotifyResponse.class);
  177. // 支付成功通知
  178. if ("TRANSACTION.SUCCESS".equals(notifyResponse.getEventType())) {
  179. // 获取锁
  180. if (orderLock.tryLock()) {
  181. try {
  182. // 解析支付结果通知
  183. WxPayNotifyV3Result result = wxPayService.parseOrderNotifyV3Result(notifyData, signatureHeader);
  184. // TODO 此处根据返回结果处理订单信息
  185. log.debug("支付成功 ---> orderNo:" + result.getResult().getOutTradeNo());
  186. // 返回成功(无需数据,系统状态码为200即可)
  187. return AjaxResult.success();
  188. } catch (Exception e) {
  189. // 支付结果解析异常/订单处理异常
  190. log.error("支付通知处理异常:", e);
  191. response.setStatus(500);// 支付成功通知处理失败时需要将状态码修改为5xx/4xx,微信才会重新发送回调
  192. return AjaxResult.error();
  193. } finally {
  194. // 释放锁
  195. orderLock.unlock();
  196. }
  197. } else {
  198. // 锁获取失败,返回异常,等待下次消息
  199. response.setStatus(503);// 支付成功通知处理失败时需要将状态码修改为5xx/4xx,微信才会重新发送回调
  200. return AjaxResult.error();
  201. }
  202. }
  203. return AjaxResult.success();
  204. }
  205. private Date defDate(Date now, Integer offset) {
  206. Date defDate = new Date();
  207. switch (offset) {
  208. case 1:
  209. defDate = DateUtil.offset(now, DateField.MONTH, 1);
  210. break;
  211. case 2:
  212. defDate = DateUtil.offset(now, DateField.MONTH, 3);
  213. break;
  214. case 3:
  215. defDate = DateUtil.offset(now, DateField.MONTH, 12);
  216. break;
  217. default:
  218. break;
  219. }
  220. return defDate;
  221. }
  222. }