|
|
@@ -0,0 +1,300 @@
|
|
|
+package com.sqx.modules.pay.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
+import cn.hutool.json.JSONObject;
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
|
|
+import com.ekyong.www.pay.common.Constant;
|
|
|
+import com.ekyong.www.pay.pay.qrcode.api.RhtQrcodePayApi;
|
|
|
+import com.ekyong.www.pay.pay.qrcode.bean.QrcodeOpenPayRequestBean;
|
|
|
+import com.ekyong.www.pay.pay.qrcode.bean.QrcodeOpenPayResponseBean;
|
|
|
+import com.ekyong.www.pay.pay.qrcode.bean.QrcodeRefundRequestBean;
|
|
|
+import com.ekyong.www.pay.pay.qrcode.bean.QrcodeRefundResponseBean;
|
|
|
+import com.ekyong.www.pay.pay.split.api.RhtSplitApi;
|
|
|
+import com.ekyong.www.pay.pay.split.bean.SplitPayRequestBean;
|
|
|
+import com.ekyong.www.pay.pay.split.bean.SplitPayResponseBean;
|
|
|
+import com.ekyong.www.pay.util.SignUtil;
|
|
|
+import com.sqx.common.exception.SqxException;
|
|
|
+import com.sqx.common.utils.SpringContextUtils;
|
|
|
+import com.sqx.modules.app.bo.UpdateMoneyBO;
|
|
|
+import com.sqx.modules.app.service.UserMoneyService;
|
|
|
+import com.sqx.modules.common.service.CommonInfoService;
|
|
|
+import com.sqx.modules.datacentre.service.DataCentreService;
|
|
|
+import com.sqx.modules.goods.service.GoodsShopService;
|
|
|
+import com.sqx.modules.order.entity.TbOrder;
|
|
|
+import com.sqx.modules.order.service.AppOrderService;
|
|
|
+import com.sqx.modules.pay.config.WechatPayConfig;
|
|
|
+import com.sqx.modules.pay.dto.GetPayParamDTO;
|
|
|
+import com.sqx.modules.pay.entity.PayDetails;
|
|
|
+import com.sqx.modules.pay.enums.PayChannelEnum;
|
|
|
+import com.sqx.modules.pay.enums.PaySceneEnum;
|
|
|
+import com.sqx.modules.pay.enums.PayStateEnums;
|
|
|
+import com.sqx.modules.pay.service.NewPayService;
|
|
|
+import com.sqx.modules.pay.service.PayDetailsService;
|
|
|
+import com.sqx.modules.pay.vo.WechatTransactionsParamVO;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.net.URLDecoder;
|
|
|
+import java.time.Duration;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class NewPayServiceImpl implements NewPayService {
|
|
|
+
|
|
|
+ private final PayDetailsService payDetailsService;
|
|
|
+ private final CommonInfoService commonInfoService;
|
|
|
+ private final UserMoneyService userMoneyService;
|
|
|
+ private final GoodsShopService goodsShopService;
|
|
|
+ private final DataCentreService dataCentreService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 润海通支付接口地址
|
|
|
+ */
|
|
|
+ private final static String RHT_PAY_BASE_URL = "https://api.ekbuyclub.com";
|
|
|
+
|
|
|
+ private final static DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public WechatTransactionsParamVO getWechatTransactionsParamVO(GetPayParamDTO dto) {
|
|
|
+ // 润海通支付,目前只测试了小程序支付,其余支付方式不考虑
|
|
|
+ if (dto.getPayChannel() != PayChannelEnum.WECHAT_JS_API) {
|
|
|
+ throw new SqxException("不支持的支付方式!");
|
|
|
+ }
|
|
|
+
|
|
|
+ PayDetails payDetails = payDetailsService.getByOrderNo(dto.getOrderNo());
|
|
|
+ if (ObjectUtil.isNotNull(payDetails)) {
|
|
|
+ // 如果在支付有效期内,则直接返回支付参数,否则提示订单超时
|
|
|
+ LocalDateTime createTime = LocalDateTime.parse(payDetails.getCreateTime(), DTF);
|
|
|
+
|
|
|
+ // 支付时间大于2小时后不能再次支付
|
|
|
+ if (Duration.between(createTime, LocalDateTime.now()).toMinutes() > 120) {
|
|
|
+ throw new SqxException("当前订单已超时关闭,请重新下单!");
|
|
|
+ }
|
|
|
+
|
|
|
+ return JSONUtil.toBean(payDetails.getRemark(), WechatTransactionsParamVO.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ WechatPayConfig wechatMchConfig = getWechatMchConfig();
|
|
|
+
|
|
|
+ // h5服务域名配置
|
|
|
+ String notifyUrl = wechatMchConfig.getH5Url() + "/sqx_fast/app/new-pay/notify";
|
|
|
+
|
|
|
+ RhtQrcodePayApi qrcodePay = new RhtQrcodePayApi(wechatMchConfig.getMchId(), wechatMchConfig.getMchKey(), RHT_PAY_BASE_URL);
|
|
|
+ QrcodeOpenPayRequestBean openRequest = new QrcodeOpenPayRequestBean();
|
|
|
+ openRequest.setAmount(dto.getAmount().toString());
|
|
|
+ openRequest.setTraceno(dto.getOrderNo());
|
|
|
+ openRequest.setPayType(Constant.PAY_TYPE_WECHAT);
|
|
|
+ openRequest.setGoodsName(dto.getOrderDesc());
|
|
|
+ openRequest.setNotifyUrl(notifyUrl);
|
|
|
+
|
|
|
+ // 小程序支付,固定填1
|
|
|
+ openRequest.setCallType("1");
|
|
|
+ openRequest.setAppid(wechatMchConfig.getAppId());
|
|
|
+ openRequest.setOpenid(dto.getUserThirdId());
|
|
|
+
|
|
|
+ WechatTransactionsParamVO wechatPayParamVO;
|
|
|
+ try {
|
|
|
+ QrcodeOpenPayResponseBean response = qrcodePay.openPay(openRequest);
|
|
|
+ JSONObject barCodeJson = JSONUtil.parseObj(response.getBarCode());
|
|
|
+ wechatPayParamVO = barCodeJson.toBean(WechatTransactionsParamVO.class);
|
|
|
+ wechatPayParamVO.setPackageStr(barCodeJson.getStr("package"));
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new SqxException("获取支付参数失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化支付订单
|
|
|
+ payDetailsService.addPayDetail(dto, JSONUtil.toJsonStr(wechatPayParamVO));
|
|
|
+ return wechatPayParamVO;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean wechatRefund(String orderNo) {
|
|
|
+ WechatPayConfig wechatMchConfig = getWechatMchConfig();
|
|
|
+ RhtQrcodePayApi qrcodePay = new RhtQrcodePayApi(wechatMchConfig.getMchId(), wechatMchConfig.getMchKey(), RHT_PAY_BASE_URL);
|
|
|
+ QrcodeRefundRequestBean request = new QrcodeRefundRequestBean();
|
|
|
+ // 商户流水号
|
|
|
+ request.setTraceno(orderNo);
|
|
|
+ // 接口版本号
|
|
|
+ request.setVersion("1.0");
|
|
|
+
|
|
|
+ try {
|
|
|
+ QrcodeRefundResponseBean response = qrcodePay.refund(request);
|
|
|
+ String respCode = response.getRespCode();
|
|
|
+ if (StrUtil.equals("00", respCode)) {
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ log.error("订单【{}】退款失败,失败原因:{}", orderNo, response.getMessage());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ log.error("订单【{}】退款失败,失败原因:{}", orderNo, e.getMessage());
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String wechatSplitBill(String inMchId, BigDecimal amount) {
|
|
|
+ WechatPayConfig wechatMchConfig = getWechatMchConfig();
|
|
|
+ RhtSplitApi splitApi = new RhtSplitApi(wechatMchConfig.getMchId(), wechatMchConfig.getMchKey(), RHT_PAY_BASE_URL);
|
|
|
+
|
|
|
+ String traceNo = IdWorker.getTimeId();
|
|
|
+ String outAmount = amount.toString();
|
|
|
+
|
|
|
+ SplitPayRequestBean request = new SplitPayRequestBean();
|
|
|
+ request.setVersion("1.0");
|
|
|
+ request.setTraceno(traceNo);
|
|
|
+ request.setOutMerchno(wechatMchConfig.getMchId());
|
|
|
+ request.setOutAmount(outAmount);
|
|
|
+
|
|
|
+ SplitPayRequestBean.SplitDetail splitDetail = new SplitPayRequestBean.SplitDetail();
|
|
|
+ splitDetail.setInAmount(outAmount);
|
|
|
+ splitDetail.setInMerchno(inMchId);
|
|
|
+
|
|
|
+ request.setSplitDetailList(JSONUtil.toJsonStr(Arrays.asList(splitDetail)));
|
|
|
+
|
|
|
+ try {
|
|
|
+ SplitPayResponseBean splitPayResponse = splitApi.splitPay(request);
|
|
|
+ if (splitPayResponse.getStatus() != 1) {
|
|
|
+ throw new SqxException("分账支付失败," + splitPayResponse.getStatusDesc());
|
|
|
+ }
|
|
|
+
|
|
|
+ return traceNo;
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ throw new SqxException("分账," + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public String wechatPayNotify(String reqJsonData) {
|
|
|
+ try {
|
|
|
+ String decode = URLDecoder.decode(reqJsonData, "GBK");
|
|
|
+ Map<String, String> map = JSONUtil.toBean(decode, Map.class);
|
|
|
+
|
|
|
+ // 微信商户key
|
|
|
+ String mchKey = commonInfoService.findOne(75).getValue();
|
|
|
+ boolean valid = SignUtil.validSignature(map, mchKey);
|
|
|
+ if (!valid) {
|
|
|
+ throw new SqxException("验签失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ String status = map.get("status");
|
|
|
+ String orderNo = map.get("traceno");
|
|
|
+ String transDateStr = map.get("transDate");
|
|
|
+ String transTime = map.get("transTime");
|
|
|
+ if (StrUtil.equals(String.valueOf(PayStateEnums.PAY_SUCCESS.getStateCode()), status)) {
|
|
|
+ PayDetails payDetails = payDetailsService.getByOrderNo(orderNo);
|
|
|
+ if (ObjectUtil.isNull(payDetails)) {
|
|
|
+ log.error("订单【{}】支付成功,但未获取到支付订单!异步通知信息:{}", orderNo, reqJsonData);
|
|
|
+ return "success";
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!StrUtil.equals(status, String.valueOf(PayStateEnums.WAIT_PAY.getStateCode()))) {
|
|
|
+ // 支付订单不是未支付状态,直接返回
|
|
|
+ return "success";
|
|
|
+ }
|
|
|
+
|
|
|
+ payDetails.setState(PayStateEnums.PAY_SUCCESS.getStateCode());
|
|
|
+ payDetails.setPayTime(transDateStr + " " + transTime);
|
|
|
+ payDetails.setRemark("RHT PAY SUCCESS");
|
|
|
+ payDetailsService.updateDetail(payDetails);
|
|
|
+
|
|
|
+ PaySceneEnum payScene = PaySceneEnum.getBySceneCode(payDetails.getClassify());
|
|
|
+ switch (payScene) {
|
|
|
+ case ORDER_PAY: handleTakeoutOrderPaySuccess(orderNo); break;
|
|
|
+ case BALANCE_RECHARGE: handleBalanceRechargePaySuccess(payDetails); break;
|
|
|
+ case BOND_PAY: handleBondPaySuccess(payDetails); break;
|
|
|
+ case VIP_PAY: handleVipPaySuccess(payDetails); break;
|
|
|
+ default:
|
|
|
+ log.error("监测到不支持的支付成功场景,支付订单信息:{}", JSONUtil.toJsonStr(payDetails));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return "success";
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ log.error("异步通知处理异常:{}", e.getMessage());
|
|
|
+ return "fail";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean enableNewPay() {
|
|
|
+ String value = commonInfoService.findOne(433).getValue();
|
|
|
+ return StrUtil.equals(value, "1");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理vip购买成功通知
|
|
|
+ * @param payDetails
|
|
|
+ */
|
|
|
+ private void handleVipPaySuccess(PayDetails payDetails) {
|
|
|
+ dataCentreService.presenterVip(payDetails.getUserId());
|
|
|
+
|
|
|
+ // 改动钱包明细??
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理充值成功通知
|
|
|
+ * @param payDetails
|
|
|
+ */
|
|
|
+ private void handleBalanceRechargePaySuccess(PayDetails payDetails) {
|
|
|
+ UpdateMoneyBO updateMoneyBO = UpdateMoneyBO.builder()
|
|
|
+ .userId(payDetails.getUserId())
|
|
|
+ .type(1)
|
|
|
+ .classify(3)
|
|
|
+ .amount(new BigDecimal(payDetails.getMoney()))
|
|
|
+ .title("充值入账")
|
|
|
+ .remark(payDetails.getOrderId())
|
|
|
+ .build();
|
|
|
+ userMoneyService.updateUserMoney(updateMoneyBO);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理充值成功通知
|
|
|
+ * @param payDetails
|
|
|
+ */
|
|
|
+ private void handleBondPaySuccess(PayDetails payDetails) {
|
|
|
+ goodsShopService.updateCashDeposit(payDetails.getMoney(), payDetails.getShopId(), payDetails.getOrderId());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理外卖订单支付成功通知
|
|
|
+ * @param orderNo 订单编号
|
|
|
+ */
|
|
|
+ private void handleTakeoutOrderPaySuccess(String orderNo) {
|
|
|
+ AppOrderService appOrderService = SpringContextUtils.getBean("appOrderService", AppOrderService.class);
|
|
|
+
|
|
|
+ // 根据订单编号查询订单
|
|
|
+ TbOrder order = appOrderService.selectOrderByNum(orderNo);
|
|
|
+ // 1 表示微信支付
|
|
|
+ order.setPayType(1);
|
|
|
+ // 订单支付成功,修改订单状态
|
|
|
+ appOrderService.updateOrderAfterPaySuccess(order);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取微信支付商户配置
|
|
|
+ * @return 商户配置
|
|
|
+ */
|
|
|
+ private WechatPayConfig getWechatMchConfig() {
|
|
|
+ return WechatPayConfig.builder()
|
|
|
+ .appId(commonInfoService.findOne(45).getValue())
|
|
|
+ .mchId(commonInfoService.findOne(434).getValue())
|
|
|
+ .mchKey(commonInfoService.findOne(435).getValue())
|
|
|
+ // .h5Url(commonInfoService.findOne(19).getValue())
|
|
|
+ .h5Url("http://mprds4.natappfree.cc")
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+}
|