123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- package com.ruoyi.wx.web.controller;
- import cn.hutool.core.date.DateField;
- import cn.hutool.core.date.DateUtil;
- import com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse;
- import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
- import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
- import com.github.binarywang.wxpay.bean.request.WxPayOrderCloseRequest;
- import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryRequest;
- import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
- import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
- import com.github.binarywang.wxpay.bean.result.WxPayOrderCloseResult;
- import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult;
- import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderResult;
- import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
- import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
- import com.github.binarywang.wxpay.exception.WxPayException;
- import com.github.binarywang.wxpay.service.WxPayService;
- import com.google.gson.GsonBuilder;
- import com.ruoyi.common.core.domain.AjaxResult;
- import com.ruoyi.common.core.domain.entity.WxUser;
- import com.ruoyi.common.utils.StringUtils;
- import com.ruoyi.framework.recovery.domain.WxRecharge;
- import com.ruoyi.framework.recovery.service.impl.WxRechargeServiceImpl;
- import com.ruoyi.framework.recovery.service.impl.WxUserServiceImpl;
- import com.ruoyi.wx.web.domain.dto.WxPayDto;
- import com.ruoyi.wx.web.unit.WeixinNotifyUtils;
- import io.swagger.annotations.ApiOperation;
- import jakarta.servlet.http.HttpServletRequest;
- import jakarta.servlet.http.HttpServletResponse;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.*;
- import java.math.BigDecimal;
- import java.util.Date;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- *
- * 支付接口
- */
- @Slf4j
- @RestController
- @RequestMapping("/wx/pay")
- public class WxPayController {
- @Autowired
- private WxPayService wxPayService;
- @Autowired
- private WxRechargeServiceImpl wxRechargeService;
- @Autowired
- private WxUserServiceImpl wxUserService;
- private final ReentrantLock orderLock = new ReentrantLock();
- /**
- * <pre>
- * 查询订单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2)
- * 该接口提供所有微信支付订单的查询,商户可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。
- * 需要调用查询接口的情况:
- * ◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
- * ◆ 调用支付接口后,返回系统错误或未知交易状态情况;
- * ◆ 调用被扫支付API,返回USERPAYING的状态;
- * ◆ 调用关单或撤销接口API之前,需确认支付状态;
- * 接口地址:https://api.mch.weixin.qq.com/pay/orderquery
- * </pre>
- *
- * @param transactionId 微信订单号
- * @param outTradeNo 商户系统内部的订单号,当没提供transactionId时需要传这个。
- */
- @ApiOperation(value = "查询订单")
- @GetMapping("/query/order")
- public WxPayOrderQueryResult queryOrder(@RequestParam(required = false) String transactionId,
- @RequestParam(required = false) String outTradeNo)
- throws WxPayException {
- return this.wxPayService.queryOrder(transactionId, outTradeNo);
- }
- @ApiOperation(value = "查询订单")
- @PostMapping("/query/order")
- public WxPayOrderQueryResult queryOrder(@RequestBody WxPayOrderQueryRequest wxPayOrderQueryRequest) throws WxPayException {
- return this.wxPayService.queryOrder(wxPayOrderQueryRequest);
- }
- /**
- * <pre>
- * 关闭订单
- * 应用场景
- * 以下情况需要调用关单接口:
- * 1. 商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
- * 2. 系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
- * 注意:订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟。
- * 接口地址:https://api.mch.weixin.qq.com/pay/closeorder
- * 是否需要证书: 不需要。
- * </pre>
- *
- * @param outTradeNo 商户系统内部的订单号
- */
- @ApiOperation(value = "关闭订单")
- @GetMapping("/closeOrder/{outTradeNo}")
- public WxPayOrderCloseResult closeOrder(@PathVariable String outTradeNo) throws WxPayException {
- return this.wxPayService.closeOrder(outTradeNo);
- }
- @ApiOperation(value = "关闭订单")
- @PostMapping("/closeOrder")
- public WxPayOrderCloseResult closeOrder(@RequestBody WxPayOrderCloseRequest wxPayOrderCloseRequest) throws WxPayException {
- return this.wxPayService.closeOrder(wxPayOrderCloseRequest);
- }
- /**
- * 调用统一下单接口,并组装生成支付所需参数对象.
- *
- * @param dto 统一下单请求参数
- * @return 返回 {@link com.github.binarywang.wxpay.bean.order}包下的类对象
- */
- @ApiOperation(value = "统一下单,并组装所需支付参数")
- @PostMapping("/createOrder")
- public AjaxResult createOrder(@RequestBody WxPayDto dto) throws WxPayException {
- log.info("开始下单:{}", dto);
- WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
- request.setDescription("购买会员");
- // 商品订单号
- String orderNo = "ORDER_" + System.currentTimeMillis();
- request.setOutTradeNo(orderNo);
- // 订单金额
- WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
- amount.setCurrency("CNY");
- BigDecimal total = new BigDecimal(dto.gettBal());
- if (StringUtils.isNotEmpty(dto.getMoney())) {
- total = total.subtract(new BigDecimal(dto.getMoney()));
- } else if (StringUtils.isNotEmpty(dto.getDiscount())) {
- total = total.multiply(new BigDecimal(dto.getDiscount()).divide(new BigDecimal(10)));
- }
- log.info("用户:{},支付总金额为:{}, 订单Id为:{}", dto.getOpenId(), amount, orderNo);
- amount.setTotal(total.intValue() * 100);
- request.setAmount(amount);
- WxPayUnifiedOrderV3Request.Payer payer = new WxPayUnifiedOrderV3Request.Payer();
- payer.setOpenid(dto.getOpenId());
- request.setPayer(payer);
- WxPayUnifiedOrderV3Result.JsapiResult orderV3 = this.wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
- AjaxResult result = new AjaxResult();
- WxRecharge wxRecharge = new WxRecharge();
- wxRecharge.setPoints(dto.getPoints());
- wxRecharge.setPrice(total.divide(new BigDecimal(100)));
- wxRecharge.setPayTime(new Date());
- WxUser wxUser = wxUserService.selectWxUSerByOpenid(dto.getOpenId());
- wxRecharge.setUserId(wxUser.getId());
- wxRecharge.setUserName(wxUser.getUsername());
- wxRechargeService.insertWxRecharge(wxRecharge);
- wxUser.setUserLevel(dto.getVipLv());
- wxUser.setVipStartTime(new Date());
- wxUser.setVipEndTime(defDate(new Date(), dto.getVipLv()));
- wxUserService.updateWxUser(wxUser);
- result.put("appId", orderV3.getAppId());
- result.put("timeStamp", orderV3.getTimeStamp());
- result.put("nonceStr", orderV3.getNonceStr());
- result.put("packageValue", orderV3.getPackageValue());
- result.put("signType", orderV3.getSignType());
- result.put("outTradeNo", orderNo);
- return result;
- }
- /**
- * 统一下单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)
- * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识"
- * 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
- *
- * @param request 请求对象,注意一些参数如appid、mchid等不用设置,方法内会自动从配置对象中获取到(前提是对应配置中已经设置)
- */
- @ApiOperation(value = "原生的统一下单接口")
- @PostMapping("/unifiedOrder")
- public WxPayUnifiedOrderResult unifiedOrder(@RequestBody WxPayUnifiedOrderRequest request) throws WxPayException {
- return this.wxPayService.unifiedOrder(request);
- }
- /**
- * 回调接口
- */
- @ApiOperation(value = "回调接口")
- @PostMapping("/notify")
- public AjaxResult notify(HttpServletRequest request, HttpServletResponse response, @RequestBody String notifyData) throws WxPayException {
- log.debug("======= 接收到通知 =========");
- // 获取请求头信息
- SignatureHeader signatureHeader = WeixinNotifyUtils.getSignatureHeader(request);
- // 将请求体json字符串转换为实体
- OriginNotifyResponse notifyResponse = new GsonBuilder().create().fromJson(notifyData, OriginNotifyResponse.class);
- // 支付成功通知
- if ("TRANSACTION.SUCCESS".equals(notifyResponse.getEventType())) {
- // 获取锁
- if (orderLock.tryLock()) {
- try {
- // 解析支付结果通知
- WxPayNotifyV3Result result = wxPayService.parseOrderNotifyV3Result(notifyData, signatureHeader);
- // TODO 此处根据返回结果处理订单信息
- log.debug("支付成功 ---> orderNo:" + result.getResult().getOutTradeNo());
- // 返回成功(无需数据,系统状态码为200即可)
- return AjaxResult.success();
- } catch (Exception e) {
- // 支付结果解析异常/订单处理异常
- log.error("支付通知处理异常:", e);
- response.setStatus(500);// 支付成功通知处理失败时需要将状态码修改为5xx/4xx,微信才会重新发送回调
- return AjaxResult.error();
- } finally {
- // 释放锁
- orderLock.unlock();
- }
- } else {
- // 锁获取失败,返回异常,等待下次消息
- response.setStatus(503);// 支付成功通知处理失败时需要将状态码修改为5xx/4xx,微信才会重新发送回调
- return AjaxResult.error();
- }
- }
- return AjaxResult.success();
- }
- private Date defDate(Date now, Integer offset) {
- Date defDate = new Date();
- switch (offset) {
- case 1:
- defDate = DateUtil.offset(now, DateField.MONTH, 1);
- break;
- case 2:
- defDate = DateUtil.offset(now, DateField.MONTH, 3);
- break;
- case 3:
- defDate = DateUtil.offset(now, DateField.MONTH, 12);
- break;
- default:
- break;
- }
- return defDate;
- }
- }
|