|
|
@@ -0,0 +1,355 @@
|
|
|
+package beilv.wx.pay.service;
|
|
|
+
|
|
|
+import beilv.carinformation.domain.CarInformation;
|
|
|
+import beilv.carinformation.mapper.CarInformationMapper;
|
|
|
+import beilv.competition.domain.Competition;
|
|
|
+import beilv.competition.mapper.CompetitionMapper;
|
|
|
+import beilv.order.domain.StoreOrder;
|
|
|
+import beilv.order.domain.StoreRefund;
|
|
|
+import beilv.order.mapper.StoreOrderMapper;
|
|
|
+import beilv.order.mapper.StoreRefundMapper;
|
|
|
+import beilv.system.domain.SysMember;
|
|
|
+import beilv.system.service.ISysMemberService;
|
|
|
+import beilv.wx.pay.config.WxPayProperties;
|
|
|
+import beilv.wx.pay.domain.vo.AppPayParam;
|
|
|
+import beilv.wx.pay.domain.vo.AppRefundParam;
|
|
|
+import beilv.wx.pay.enums.OrderTypeEnum;
|
|
|
+import beilv.wx.pay.enums.PayOrderConstants;
|
|
|
+import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
|
|
|
+import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Response;
|
|
|
+import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
|
|
|
+import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
|
|
|
+import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
|
|
|
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
|
|
|
+import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
|
|
|
+import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result.JsapiResult;
|
|
|
+import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
|
|
|
+import com.github.binarywang.wxpay.constant.WxPayConstants;
|
|
|
+import com.github.binarywang.wxpay.exception.WxPayException;
|
|
|
+import com.github.binarywang.wxpay.service.WxPayService;
|
|
|
+import lombok.AllArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.http.ResponseEntity;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@AllArgsConstructor
|
|
|
+public class IWxPayService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private CarInformationMapper carInformationMapper;
|
|
|
+ @Autowired
|
|
|
+ private StoreOrderMapper storeOrderMapper;
|
|
|
+ @Autowired
|
|
|
+ private CompetitionMapper competitionMapper;
|
|
|
+ @Autowired
|
|
|
+ private WxPayProperties wxPayProperties;
|
|
|
+ @Autowired
|
|
|
+ private ISysMemberService sysMemberService;
|
|
|
+ @Autowired
|
|
|
+ private StoreRefundMapper storeRefundMapper;
|
|
|
+
|
|
|
+ private WxPayService wxService;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 统一下单接口
|
|
|
+ *
|
|
|
+ * @return 预支付交易会话标识
|
|
|
+ */
|
|
|
+ public JsapiResult unifiedOrder(AppPayParam param) throws Exception {
|
|
|
+ SysMember user = getSysMember(param.getUserId());
|
|
|
+ //生成唯一标识商家订单号
|
|
|
+ String outTradeNo = generateOutTradeNo(param.getOrderType());
|
|
|
+ WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
|
|
+ request.setAppid(wxPayProperties.getAppId());
|
|
|
+ request.setMchid(wxPayProperties.getMchId());
|
|
|
+ request.setDescription(orderInfo(param, user));
|
|
|
+ request.setOutTradeNo(outTradeNo);
|
|
|
+ request.setNotifyUrl(wxPayProperties.getNotifyUrl());
|
|
|
+ WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
|
|
|
+ amount.setTotal(yuanToFen(param.getMoney()));
|
|
|
+ amount.setCurrency("CNY");
|
|
|
+ request.setAmount(amount);
|
|
|
+ request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(user.getOpenId()));
|
|
|
+ JsapiResult jsapiResult = this.wxService.createOrderV3(TradeTypeEnum.JSAPI, request);
|
|
|
+ if (StringUtils.isNotEmpty(jsapiResult.getPackageValue())) {
|
|
|
+ //成功之后创建订单
|
|
|
+ generateOrder(user, param, outTradeNo, jsapiResult.getPackageValue(), orderInfo(param, user));
|
|
|
+
|
|
|
+ }
|
|
|
+ return jsapiResult;
|
|
|
+ }
|
|
|
+
|
|
|
+ //获取当前用户信息
|
|
|
+ private SysMember getSysMember(Long userId) {
|
|
|
+ return sysMemberService.selectSysMemberById(userId);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 元转分(不四舍五入,直接截取)
|
|
|
+ *
|
|
|
+ * @param amount 金额(元)
|
|
|
+ * @return 分(整数)
|
|
|
+ */
|
|
|
+ public static int yuanToFen(BigDecimal amount) {
|
|
|
+ if (amount == null) return 0;
|
|
|
+ return amount.multiply(BigDecimal.valueOf(100))
|
|
|
+ .setScale(0, RoundingMode.DOWN) // 不四舍五入
|
|
|
+ .intValueExact();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 统一下单接口
|
|
|
+ *
|
|
|
+ * @return 预支付交易会话标识
|
|
|
+ */
|
|
|
+ private void generateOrder(SysMember user, AppPayParam param, String outTradeNo, String payId, String remark) {
|
|
|
+ StoreOrder storeOrder = new StoreOrder();
|
|
|
+ storeOrder.setOrderId(outTradeNo);
|
|
|
+ storeOrder.setOrderType(param.getOrderType());
|
|
|
+ storeOrder.setUid(user.getId());
|
|
|
+ storeOrder.setRealName(user.getUsername());
|
|
|
+ storeOrder.setUserPhone(user.getMobile());
|
|
|
+ storeOrder.setUserAddress(user.getAddress());
|
|
|
+ storeOrder.setTotalNum(1);
|
|
|
+ storeOrder.setPayType(param.getPayType());
|
|
|
+ storeOrder.setTotalPrice(param.getMoney());
|
|
|
+ storeOrder.setPayPrice(param.getMoney());
|
|
|
+ storeOrder.setPayTime(new Date());
|
|
|
+ storeOrder.setPayId(payId);
|
|
|
+ storeOrder.setCreateTime(new Date());
|
|
|
+ storeOrder.setRemark(remark);
|
|
|
+ storeOrderMapper.insertStoreOrder(storeOrder);
|
|
|
+ }
|
|
|
+
|
|
|
+ //生成商户订单号
|
|
|
+ public String generateOutTradeNo(String orderType) {
|
|
|
+ String prefix = "";
|
|
|
+ String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
|
|
|
+ String random = String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); // 6位随机
|
|
|
+ if (orderType.equals(OrderTypeEnum.MEMBER_CHARGING.getValue())) {
|
|
|
+ prefix = PayOrderConstants.RECHARGE_ORDER_PREFIX;
|
|
|
+ } else if (orderType.equals(OrderTypeEnum.SINGLE_TIME.getValue())) {
|
|
|
+ prefix = PayOrderConstants.CAR_ORDER_PREFIX;
|
|
|
+ } else if (orderType.equals(OrderTypeEnum.MANY_TIMES.getValue())) {
|
|
|
+ prefix = PayOrderConstants.EVENT_ORDER_PREFIX;
|
|
|
+ } else if (orderType.equals(OrderTypeEnum.APPOINTMENT.getValue())) {
|
|
|
+ prefix = PayOrderConstants.APPOINTMENT_ORDER_PREFIX;
|
|
|
+ } else if (orderType.equals(OrderTypeEnum.WATCH_GAME.getValue())) {
|
|
|
+ prefix = PayOrderConstants.REGISTRATION_ORDER_PREFIX;
|
|
|
+ } else if (orderType.equals(OrderTypeEnum.REFOUND_MONEY.getValue())) {
|
|
|
+ prefix = PayOrderConstants.REFOUND_MONEY_PREFIX;
|
|
|
+ }
|
|
|
+ return prefix + time + random;
|
|
|
+ }
|
|
|
+
|
|
|
+ //获取购买商品信息
|
|
|
+ private String orderInfo(AppPayParam param, SysMember user) {
|
|
|
+ String orderType = param.getOrderType();
|
|
|
+ String isVip = user.getIsVip();
|
|
|
+ if (orderType.equals(OrderTypeEnum.MEMBER_CHARGING.getValue())) {
|
|
|
+ return "会员充值";
|
|
|
+ } else if (orderType.equals(OrderTypeEnum.SINGLE_TIME.getValue())) {
|
|
|
+ CarInformation carInformation = carInformationMapper.selectCarInformationById(Long.valueOf(param.getOrderId()));
|
|
|
+ if ("1".equals(isVip)) {
|
|
|
+ return carInformation.getMemberPrice().toString();
|
|
|
+ }
|
|
|
+ return carInformation.getName();
|
|
|
+ } else if (orderType.equals(OrderTypeEnum.MANY_TIMES.getValue())) {
|
|
|
+ Competition competition = competitionMapper.selectCompetitionById(Integer.valueOf(param.getOrderId()));
|
|
|
+ return competition.getCompetitionTitle() + competition.getCompetitionType() + "报名";
|
|
|
+ } else if (orderType.equals(OrderTypeEnum.APPOINTMENT.getValue())) {
|
|
|
+ return "球场预约";
|
|
|
+ } else if (orderType.equals(OrderTypeEnum.WATCH_GAME.getValue())) {
|
|
|
+ Competition competition = competitionMapper.selectCompetitionById(Integer.valueOf(param.getOrderId()));
|
|
|
+ return competition.getCompetitionTitle() + competition.getCompetitionType() + "观赛";
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理支付结果通知
|
|
|
+ *
|
|
|
+ * @return 处理结果
|
|
|
+ */
|
|
|
+ public ResponseEntity<String> handleNotify(String notifyData, HttpServletRequest request) {
|
|
|
+ SignatureHeader header = getRequestHeader(request);
|
|
|
+ try {
|
|
|
+ WxPayNotifyV3Result res = this.wxService.parseOrderNotifyV3Result(notifyData, header);
|
|
|
+ WxPayNotifyV3Result.DecryptNotifyResult decryptRes = res.getResult();
|
|
|
+ // TODO 根据自己业务场景需要构造返回对象
|
|
|
+ if (WxPayConstants.WxpayTradeStatus.SUCCESS.equals(decryptRes.getTradeState())) {
|
|
|
+ // 成功处理订单
|
|
|
+ processOrderPayment(decryptRes.getOutTradeNo());
|
|
|
+ // 成功返回200/204,body无需有内容
|
|
|
+ return ResponseEntity.status(200).body("");
|
|
|
+ } else {
|
|
|
+ // 失败返回4xx或5xx,且需要构造body信息
|
|
|
+ return ResponseEntity.status(500).body(WxPayNotifyV3Response.fail("错误原因"));
|
|
|
+ }
|
|
|
+ } catch (WxPayException e) {
|
|
|
+ // 失败返回4xx或5xx,且需要构造body信息
|
|
|
+ return ResponseEntity.status(500).body(WxPayNotifyV3Response.fail("错误原因"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理订单支付完成后的业务逻辑
|
|
|
+ *
|
|
|
+ * @param outTradeNo 商户订单号
|
|
|
+ */
|
|
|
+ private void processOrderPayment(String outTradeNo) {
|
|
|
+ StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(outTradeNo);
|
|
|
+ if (order != null) {
|
|
|
+ // 检查订单是否已经处理过
|
|
|
+ if (order.getPaid() == null || order.getPaid() != 1) {
|
|
|
+ // 更新订单状态
|
|
|
+ order.setPaid(1); // 设置为已支付
|
|
|
+ order.setStatus(1); // 设置订单状态为已支付
|
|
|
+ order.setPayTime(new java.util.Date()); // 设置支付时间
|
|
|
+
|
|
|
+ storeOrderMapper.updateStoreOrder(order);
|
|
|
+
|
|
|
+ // TODO: 根据订单类型处理不同的业务逻辑
|
|
|
+ // 例如:会员充值订单更新用户会员状态、购买商品订单更新库存等
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 微信支付-申请退款
|
|
|
+ *
|
|
|
+ */
|
|
|
+ public WxPayRefundV3Result refund(AppRefundParam param) throws WxPayException {
|
|
|
+ WxPayRefundV3Request request = new WxPayRefundV3Request();
|
|
|
+ //商户退款单号
|
|
|
+ request.setOutRefundNo(param.getOutTradeNo());
|
|
|
+ //原支付商户订单号
|
|
|
+ request.setOutTradeNo(param.getOutTradeNo());
|
|
|
+ //退款原因说明
|
|
|
+ request.setReason("不想要了");
|
|
|
+ WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
|
|
|
+ //原订单总金额(单位:分)
|
|
|
+ amount.setTotal(yuanToFen(param.getMoney()));
|
|
|
+ //退款金额(单位:分)
|
|
|
+ amount.setRefund(yuanToFen(param.getMoney()));
|
|
|
+ //货币类型,默认 "CNY"
|
|
|
+ amount.setCurrency("CNY");
|
|
|
+ request.setAmount(amount);
|
|
|
+ request.setNotifyUrl(wxPayProperties.getRefundNotifyUrl());
|
|
|
+ String outTradeNo = param.getOutTradeNo();
|
|
|
+
|
|
|
+ refundOrder(getSysMember(param.getUid()), param, outTradeNo, request.getReason());
|
|
|
+ return this.wxService.refundV3(request);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 统一下单接口
|
|
|
+ *
|
|
|
+ * @return 预支付交易会话标识
|
|
|
+ */
|
|
|
+ private void refundOrder(SysMember user, AppRefundParam param, String outTradeNo, String reason) {
|
|
|
+ StoreRefund storeRefund = new StoreRefund();
|
|
|
+ storeRefund.setUid(user.getId());
|
|
|
+ storeRefund.setRefundNo(generateOutTradeNo(param.getOrderType()));
|
|
|
+ storeRefund.setOrderId(outTradeNo);
|
|
|
+ storeRefund.setRefundAmount(param.getMoney());
|
|
|
+ storeRefund.setRefundReason(reason);
|
|
|
+ storeRefund.setCreateTime(new Date());
|
|
|
+ storeRefundMapper.insertStoreRefund(storeRefund);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 退款结果通知
|
|
|
+ *
|
|
|
+ * @return 处理结果
|
|
|
+ */
|
|
|
+ public ResponseEntity<String> parseRefundNotify(String notifyData, HttpServletRequest request) {
|
|
|
+ SignatureHeader header = getRequestHeader(request);
|
|
|
+ try {
|
|
|
+ WxPayRefundNotifyV3Result res = this.wxService.parseRefundNotifyV3Result(notifyData, header);
|
|
|
+ WxPayRefundNotifyV3Result.DecryptNotifyResult decryptRes = res.getResult();
|
|
|
+ // TODO 根据自己业务场景需要构造返回对象
|
|
|
+ if (WxPayConstants.RefundStatus.SUCCESS.equals(decryptRes.getRefundStatus())) {
|
|
|
+ // 成功处理订单
|
|
|
+ processOrderRefund(decryptRes.getOutTradeNo());
|
|
|
+ //成功返回200/204,body无需有内容
|
|
|
+ return ResponseEntity.status(200).body("");
|
|
|
+ } else {
|
|
|
+ //失败返回4xx或5xx,且需要构造body信息
|
|
|
+ return ResponseEntity.status(500).body(WxPayNotifyV3Response.fail("错误原因"));
|
|
|
+ }
|
|
|
+ } catch (WxPayException e) {
|
|
|
+ //失败返回4xx或5xx,且需要构造body信息
|
|
|
+ return ResponseEntity.status(500).body(WxPayNotifyV3Response.fail("错误原因"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 退款完成后的业务逻辑
|
|
|
+ *
|
|
|
+ * @param outTradeNo 商户订单号
|
|
|
+ */
|
|
|
+ private void processOrderRefund(String outTradeNo) {
|
|
|
+ StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(outTradeNo);
|
|
|
+ if (order != null) {
|
|
|
+ // 检查订单是否已经处理过
|
|
|
+ if (order.getRefundStatus() == null && order.getPaid() == 1) {
|
|
|
+ // 更新订单状态
|
|
|
+ order.setStatus(1); // 设置订单状态为已退款
|
|
|
+ order.setUpdateTime(new Date());
|
|
|
+ storeOrderMapper.updateStoreOrder(order);
|
|
|
+
|
|
|
+ // TODO: 根据订单类型处理不同的业务逻辑
|
|
|
+ // 例如:会员充值订单更新用户会员状态、购买商品订单更新库存等
|
|
|
+ }
|
|
|
+ }
|
|
|
+ StoreRefund refund = storeRefundMapper.selectStoreRefundByOrderId(outTradeNo);
|
|
|
+ if (refund != null) {
|
|
|
+ // 检查订单是否已经处理过
|
|
|
+ if (refund.getRefundStatus() == null && refund.getRefundStatus().equals("1")) {
|
|
|
+ // 更新订单状态
|
|
|
+ refund.setRefundStatus("2"); // 设置订单状态为已退款
|
|
|
+ refund.setSuccessTime(new Date());
|
|
|
+ storeRefundMapper.updateStoreRefund(refund);
|
|
|
+
|
|
|
+ // TODO: 根据订单类型处理不同的业务逻辑
|
|
|
+ // 例如:会员充值订单更新用户会员状态、购买商品订单更新库存等
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装请求头重的前面信息
|
|
|
+ *
|
|
|
+ * @param request
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private SignatureHeader getRequestHeader(HttpServletRequest request) {
|
|
|
+ // 获取通知签名
|
|
|
+ String signature = request.getHeader("Wechatpay-Signature");
|
|
|
+ String nonce = request.getHeader("Wechatpay-Nonce");
|
|
|
+ String serial = request.getHeader("Wechatpay-Serial");
|
|
|
+ String timestamp = request.getHeader("Wechatpay-Timestamp");
|
|
|
+ SignatureHeader signatureHeader = new SignatureHeader();
|
|
|
+ signatureHeader.setSignature(signature);
|
|
|
+ signatureHeader.setNonce(nonce);
|
|
|
+ signatureHeader.setSerial(serial);
|
|
|
+ signatureHeader.setTimeStamp(timestamp);
|
|
|
+ return signatureHeader;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|