|
@@ -0,0 +1,320 @@
|
|
|
+package com.citu.module.pay.service.currency;
|
|
|
+
|
|
|
+import cn.hutool.core.date.LocalDateTimeUtil;
|
|
|
+import cn.hutool.core.lang.Assert;
|
|
|
+import cn.hutool.extra.spring.SpringUtil;
|
|
|
+import com.citu.framework.common.pojo.PageParam;
|
|
|
+import com.citu.framework.common.pojo.PageResult;
|
|
|
+import com.citu.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
|
|
|
+import com.citu.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
|
|
+import com.citu.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
|
|
+import com.citu.module.pay.controller.app.currency.vo.recharge.AppPayCurrencyRechargeCreateReqVO;
|
|
|
+import com.citu.module.pay.convert.currency.PayCurrencyRechargeConvert;
|
|
|
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyDO;
|
|
|
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargeDO;
|
|
|
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargePackageDO;
|
|
|
+import com.citu.module.pay.dal.dataobject.order.PayOrderDO;
|
|
|
+import com.citu.module.pay.dal.dataobject.refund.PayRefundDO;
|
|
|
+import com.citu.module.pay.dal.mysql.currency.PayCurrencyRechargeMapper;
|
|
|
+import com.citu.module.pay.enums.currency.PayCurrencyBizTypeEnum;
|
|
|
+import com.citu.module.pay.enums.order.PayOrderStatusEnum;
|
|
|
+import com.citu.module.pay.enums.refund.PayRefundStatusEnum;
|
|
|
+import com.citu.module.pay.service.order.PayOrderService;
|
|
|
+import com.citu.module.pay.service.refund.PayRefundService;
|
|
|
+import com.citu.module.system.api.social.SocialClientApi;
|
|
|
+import com.citu.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.scheduling.annotation.Async;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.time.Duration;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.Objects;
|
|
|
+
|
|
|
+import static cn.hutool.core.util.ObjectUtil.notEqual;
|
|
|
+import static com.citu.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
+import static com.citu.framework.common.util.date.LocalDateTimeUtils.addTime;
|
|
|
+import static com.citu.framework.common.util.json.JsonUtils.toJsonString;
|
|
|
+import static com.citu.framework.common.util.number.MoneyUtils.fenToYuanStr;
|
|
|
+import static com.citu.module.pay.enums.ErrorCodeConstants.*;
|
|
|
+import static com.citu.module.pay.enums.MessageTemplateConstants.WXA_WALLET_RECHARGER_PAID;
|
|
|
+import static com.citu.module.pay.enums.refund.PayRefundStatusEnum.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 货币账户充值 Service 实现类
|
|
|
+ *
|
|
|
+ * @author Rayson
|
|
|
+ */
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+public class PayCurrencyRechargeServiceImpl implements PayCurrencyRechargeService {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * TODO 芋艿:放到 payconfig
|
|
|
+ */
|
|
|
+ private static final Long WALLET_PAY_APP_ID = 8L;
|
|
|
+
|
|
|
+ private static final String WALLET_RECHARGE_ORDER_SUBJECT = "货币账户余额充值";
|
|
|
+ @Resource
|
|
|
+ public SocialClientApi socialClientApi;
|
|
|
+ @Resource
|
|
|
+ private PayCurrencyRechargeMapper currencyRechargeMapper;
|
|
|
+ @Resource
|
|
|
+ private PayCurrencyService payCurrencyService;
|
|
|
+ @Resource
|
|
|
+ private PayOrderService payOrderService;
|
|
|
+ @Resource
|
|
|
+ private PayRefundService payRefundService;
|
|
|
+ @Resource
|
|
|
+ private PayCurrencyRechargePackageService payCurrencyRechargePackageService;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public PayCurrencyRechargeDO createCurrencyRecharge(Long dataId, Long userId, Integer userType, String userIp,
|
|
|
+ AppPayCurrencyRechargeCreateReqVO reqVO) {
|
|
|
+ // 1.1 计算充值金额
|
|
|
+ long payPrice;
|
|
|
+ long bonusPrice = 0;
|
|
|
+ if (Objects.nonNull(reqVO.getPackageId())) {
|
|
|
+ PayCurrencyRechargePackageDO rechargePackage = payCurrencyRechargePackageService.validCurrencyRechargePackage(reqVO.getPackageId());
|
|
|
+ payPrice = rechargePackage.getPayPrice();
|
|
|
+ bonusPrice = rechargePackage.getBonusPrice();
|
|
|
+ } else {
|
|
|
+ payPrice = reqVO.getPayPrice();
|
|
|
+ }
|
|
|
+ // 1.2 插入充值记录
|
|
|
+ PayCurrencyDO currency = payCurrencyService.getOrCreateCurrency(dataId, userId, userType);
|
|
|
+ PayCurrencyRechargeDO recharge = PayCurrencyRechargeConvert.INSTANCE.convert(currency.getId(), payPrice, bonusPrice, reqVO.getPackageId());
|
|
|
+ currencyRechargeMapper.insert(recharge);
|
|
|
+
|
|
|
+ // 2.1 创建支付单
|
|
|
+ Long payOrderId = payOrderService.createOrder(new PayOrderCreateReqDTO()
|
|
|
+ .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
|
|
|
+ .setMerchantOrderId(recharge.getId().toString()) // 业务的订单编号
|
|
|
+ .setSubject(WALLET_RECHARGE_ORDER_SUBJECT).setBody("")
|
|
|
+ .setPrice(Math.toIntExact(recharge.getPayPrice()))
|
|
|
+ .setExpireTime(addTime(Duration.ofHours(2L)))); // TODO @芋艿:支付超时时间
|
|
|
+ // 2.2 更新货币账户充值记录中支付订单
|
|
|
+ currencyRechargeMapper.updateById(new PayCurrencyRechargeDO().setId(recharge.getId()).setPayOrderId(payOrderId));
|
|
|
+ recharge.setPayOrderId(payOrderId);
|
|
|
+ return recharge;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public PageResult<PayCurrencyRechargeDO> getCurrencyRechargePackagePage(Long dataId, Long userId, Integer userType,
|
|
|
+ PageParam pageReqVO, Boolean payStatus) {
|
|
|
+ PayCurrencyDO currency = payCurrencyService.getOrCreateCurrency(dataId, userId, userType);
|
|
|
+ return currencyRechargeMapper.selectPage(pageReqVO, currency.getId(), payStatus);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void updateCurrencyRechargerPaid(Long id, Long payOrderId) {
|
|
|
+ // 1.1 获取货币账户充值记录
|
|
|
+ PayCurrencyRechargeDO currencyRecharge = currencyRechargeMapper.selectById(id);
|
|
|
+ if (currencyRecharge == null) {
|
|
|
+ log.error("[updateCurrencyRechargerPaid][货币账户充值记录不存在,货币账户充值记录 id({})]", id);
|
|
|
+ throw exception(WALLET_RECHARGE_NOT_FOUND);
|
|
|
+ }
|
|
|
+ // 1.2 校验货币账户充值是否可以支付
|
|
|
+ PayOrderDO payOrderDO = validateCurrencyRechargerCanPaid(currencyRecharge, payOrderId);
|
|
|
+
|
|
|
+ // 2. 更新货币账户充值的支付状态
|
|
|
+ int updateCount = currencyRechargeMapper.updateByIdAndPaid(id, false,
|
|
|
+ new PayCurrencyRechargeDO().setId(id).setPayStatus(true).setPayTime(LocalDateTime.now())
|
|
|
+ .setPayChannelCode(payOrderDO.getChannelCode()));
|
|
|
+ if (updateCount == 0) {
|
|
|
+ throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 更新货币账户余额
|
|
|
+ // TODO @jason:这样的话,未来提现会不会把充值的,也提现走哈。类似先充 100,送 110;然后提现 110;
|
|
|
+ // TODO 需要货币账户中加个可提现余额
|
|
|
+ payCurrencyService.addCurrencyBalance(currencyRecharge.getCurrencyId(), String.valueOf(id),
|
|
|
+ PayCurrencyBizTypeEnum.RECHARGE, currencyRecharge.getTotalPrice());
|
|
|
+
|
|
|
+ // 4. 发送订阅消息
|
|
|
+ getSelf().sendCurrencyRechargerPaidMessage(payOrderId, currencyRecharge);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Async
|
|
|
+ public void sendCurrencyRechargerPaidMessage(Long payOrderId, PayCurrencyRechargeDO currencyRecharge) {
|
|
|
+ // 1. 获得会员货币账户信息
|
|
|
+ PayCurrencyDO currency = payCurrencyService.getCurrency(currencyRecharge.getCurrencyId());
|
|
|
+ // 2. 构建并发送模版消息
|
|
|
+ socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO()
|
|
|
+ .setUserId(currency.getUserId()).setUserType(currency.getUserType())
|
|
|
+ .setTemplateTitle(WXA_WALLET_RECHARGER_PAID)
|
|
|
+ .setPage("pages/user/currency/money") // 货币账户详情界面
|
|
|
+ .addMessage("character_string1", String.valueOf(payOrderId)) // 支付单编号
|
|
|
+ .addMessage("amount2", fenToYuanStr(Math.toIntExact(currencyRecharge.getTotalPrice()))) // 充值金额
|
|
|
+ .addMessage("time3", LocalDateTimeUtil.formatNormal(currencyRecharge.getCreateTime())) // 充值时间
|
|
|
+ .addMessage("phrase4", "充值成功")); // 充值状态
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void refundCurrencyRecharge(Long id, String userIp) {
|
|
|
+ // 1.1 获取货币账户充值记录
|
|
|
+ PayCurrencyRechargeDO currencyRecharge = currencyRechargeMapper.selectById(id);
|
|
|
+ if (currencyRecharge == null) {
|
|
|
+ log.error("[refundCurrencyRecharge][货币账户充值记录不存在,货币账户充值记录 id({})]", id);
|
|
|
+ throw exception(WALLET_RECHARGE_NOT_FOUND);
|
|
|
+ }
|
|
|
+ // 1.2 校验货币账户充值是否可以发起退款
|
|
|
+ PayCurrencyDO currency = validateCurrencyRechargeCanRefund(currencyRecharge);
|
|
|
+
|
|
|
+ // 2. 冻结退款的余额,暂时只处理赠送的余额也全部退回
|
|
|
+ payCurrencyService.freezePrice(currency.getId(), currencyRecharge.getTotalPrice());
|
|
|
+
|
|
|
+ // 3. 创建退款单
|
|
|
+ String currencyRechargeId = String.valueOf(id);
|
|
|
+ String refundId = currencyRechargeId + "-refund";
|
|
|
+ Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO()
|
|
|
+ .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
|
|
|
+ .setMerchantOrderId(currencyRechargeId)
|
|
|
+ .setMerchantRefundId(refundId)
|
|
|
+ .setReason("想退钱").setPrice(Math.toIntExact(currencyRecharge.getPayPrice())));
|
|
|
+
|
|
|
+ // 4. 更新充值记录退款单号
|
|
|
+ // TODO @jaosn:一般新建这种 update 对象,建议是,第一个 set id 属性,容易知道以它为更新
|
|
|
+ currencyRechargeMapper.updateById(new PayCurrencyRechargeDO().setPayRefundId(payRefundId)
|
|
|
+ .setRefundStatus(WAITING.getStatus()).setId(currencyRecharge.getId()));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void updateCurrencyRechargeRefunded(Long id, Long payRefundId) {
|
|
|
+ // 1.1 获取货币账户充值记录
|
|
|
+ PayCurrencyRechargeDO currencyRecharge = currencyRechargeMapper.selectById(id);
|
|
|
+ if (currencyRecharge == null) {
|
|
|
+ log.error("[updateCurrencyRechargerPaid][货币账户充值记录不存在,货币账户充值记录 id({})]", id);
|
|
|
+ throw exception(WALLET_RECHARGE_NOT_FOUND);
|
|
|
+ }
|
|
|
+ // 1.2 校验货币账户充值是否可以更新已退款
|
|
|
+ PayRefundDO payRefund = validateCurrencyRechargeCanRefunded(currencyRecharge, payRefundId);
|
|
|
+
|
|
|
+ PayCurrencyRechargeDO updateObj = new PayCurrencyRechargeDO().setId(id);
|
|
|
+ // 退款成功
|
|
|
+ if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
|
|
|
+ // 2.1 更新货币账户余额
|
|
|
+ payCurrencyService.reduceCurrencyBalance(currencyRecharge.getCurrencyId(), id,
|
|
|
+ PayCurrencyBizTypeEnum.RECHARGE_REFUND, currencyRecharge.getTotalPrice());
|
|
|
+
|
|
|
+ updateObj.setRefundStatus(SUCCESS.getStatus()).setRefundTime(payRefund.getSuccessTime())
|
|
|
+ .setRefundTotalPrice(currencyRecharge.getTotalPrice()).setRefundPayPrice(currencyRecharge.getPayPrice())
|
|
|
+ .setRefundBonusPrice(currencyRecharge.getBonusPrice());
|
|
|
+ }
|
|
|
+ // 退款失败
|
|
|
+ if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
|
|
|
+ // 2.2 解冻余额
|
|
|
+ payCurrencyService.unfreezePrice(currencyRecharge.getCurrencyId(), currencyRecharge.getTotalPrice());
|
|
|
+
|
|
|
+ updateObj.setRefundStatus(FAILURE.getStatus());
|
|
|
+ }
|
|
|
+ // 3. 更新货币账户充值的退款字段
|
|
|
+ currencyRechargeMapper.updateByIdAndRefunded(id, WAITING.getStatus(), updateObj);
|
|
|
+ }
|
|
|
+
|
|
|
+ private PayRefundDO validateCurrencyRechargeCanRefunded(PayCurrencyRechargeDO currencyRecharge, Long payRefundId) {
|
|
|
+ // 1. 校验退款订单匹配
|
|
|
+ if (notEqual(currencyRecharge.getPayRefundId(), payRefundId)) {
|
|
|
+ log.error("[validateCurrencyRechargeCanRefunded][货币账户充值({}) 退款单不匹配({}),请进行处理!货币账户充值的数据是:{}]",
|
|
|
+ currencyRecharge.getId(), payRefundId, toJsonString(currencyRecharge));
|
|
|
+ throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2.1 校验退款订单
|
|
|
+ PayRefundDO payRefund = payRefundService.getRefund(payRefundId);
|
|
|
+ if (payRefund == null) {
|
|
|
+ log.error("[validateCurrencyRechargeCanRefunded][payRefund({})不存在]", payRefundId);
|
|
|
+ throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND);
|
|
|
+ }
|
|
|
+ // 2.2 校验退款金额一致
|
|
|
+ if (notEqual(payRefund.getRefundPrice(), currencyRecharge.getPayPrice())) {
|
|
|
+ log.error("[validateCurrencyRechargeCanRefunded][货币账户({}) payRefund({}) 退款金额不匹配,请进行处理!货币账户数据是:{},payRefund 数据是:{}]",
|
|
|
+ currencyRecharge.getId(), payRefundId, toJsonString(currencyRecharge), toJsonString(payRefund));
|
|
|
+ throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
|
|
|
+ }
|
|
|
+ // 2.3 校验退款订单商户订单是否匹配
|
|
|
+ if (notEqual(payRefund.getMerchantOrderId(), currencyRecharge.getId().toString())) {
|
|
|
+ log.error("[validateCurrencyRechargeCanRefunded][货币账户({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
|
|
|
+ currencyRecharge.getId(), payRefundId, toJsonString(payRefund));
|
|
|
+ throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
|
|
|
+ }
|
|
|
+ return payRefund;
|
|
|
+ }
|
|
|
+
|
|
|
+ private PayCurrencyDO validateCurrencyRechargeCanRefund(PayCurrencyRechargeDO currencyRecharge) {
|
|
|
+ // 校验充值订单是否支付
|
|
|
+ if (!currencyRecharge.getPayStatus()) {
|
|
|
+ throw exception(WALLET_RECHARGE_REFUND_FAIL_NOT_PAID);
|
|
|
+ }
|
|
|
+ // 校验充值订单是否已退款
|
|
|
+ if (currencyRecharge.getPayRefundId() != null) {
|
|
|
+ throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUNDED);
|
|
|
+ }
|
|
|
+ // 校验货币账户余额是否足够
|
|
|
+ PayCurrencyDO currency = payCurrencyService.getCurrency(currencyRecharge.getCurrencyId());
|
|
|
+ Assert.notNull(currency, "用户货币账户({}) 不存在", currency.getId());
|
|
|
+ if (currency.getBalance() < currencyRecharge.getTotalPrice()) {
|
|
|
+ throw exception(WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH);
|
|
|
+ }
|
|
|
+ // TODO @芋艿:需要考虑下,赠送的金额,会不会导致提现超过;
|
|
|
+ return currency;
|
|
|
+ }
|
|
|
+
|
|
|
+ private PayOrderDO validateCurrencyRechargerCanPaid(PayCurrencyRechargeDO currencyRecharge, Long payOrderId) {
|
|
|
+ // 1.1 校验充值记录的支付状态
|
|
|
+ if (currencyRecharge.getPayStatus()) {
|
|
|
+ log.error("[validateCurrencyRechargerCanPaid][货币账户({}) 不处于未支付状态! 货币账户数据是:{}]",
|
|
|
+ currencyRecharge.getId(), toJsonString(currencyRecharge));
|
|
|
+ throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID);
|
|
|
+ }
|
|
|
+ // 1.2 校验支付订单匹配
|
|
|
+ if (notEqual(currencyRecharge.getPayOrderId(), payOrderId)) { // 支付单号
|
|
|
+ log.error("[validateCurrencyRechargerCanPaid][货币账户({}) 支付单不匹配({}),请进行处理! 货币账户数据是:{}]",
|
|
|
+ currencyRecharge.getId(), payOrderId, toJsonString(currencyRecharge));
|
|
|
+ throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2.1 校验支付单是否存在
|
|
|
+ PayOrderDO payOrder = payOrderService.getOrder(payOrderId);
|
|
|
+ if (payOrder == null) {
|
|
|
+ log.error("[validateCurrencyRechargerCanPaid][货币账户({}) payOrder({}) 不存在,请进行处理!]",
|
|
|
+ currencyRecharge.getId(), payOrderId);
|
|
|
+ throw exception(PAY_ORDER_NOT_FOUND);
|
|
|
+ }
|
|
|
+ // 2.2 校验支付单已支付
|
|
|
+ if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
|
|
|
+ log.error("[validateCurrencyRechargerCanPaid][货币账户({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
|
|
|
+ currencyRecharge.getId(), payOrderId, toJsonString(payOrder));
|
|
|
+ throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS);
|
|
|
+ }
|
|
|
+ // 2.3 校验支付金额一致
|
|
|
+ if (notEqual(payOrder.getPrice(), currencyRecharge.getPayPrice())) {
|
|
|
+ log.error("[validateDemoOrderCanPaid][货币账户({}) payOrder({}) 支付金额不匹配,请进行处理!货币账户 数据是:{},payOrder 数据是:{}]",
|
|
|
+ currencyRecharge.getId(), payOrderId, toJsonString(currencyRecharge), toJsonString(payOrder));
|
|
|
+ throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH);
|
|
|
+ }
|
|
|
+ // 2.4 校验支付订单的商户订单匹配
|
|
|
+ if (notEqual(payOrder.getMerchantOrderId(), currencyRecharge.getId().toString())) {
|
|
|
+ log.error("[validateDemoOrderCanPaid][货币账户({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
|
|
|
+ currencyRecharge.getId(), payOrderId, toJsonString(payOrder));
|
|
|
+ throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
|
|
|
+ }
|
|
|
+ return payOrder;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获得自身的代理对象,解决 AOP 生效问题
|
|
|
+ *
|
|
|
+ * @return 自己
|
|
|
+ */
|
|
|
+ private PayCurrencyRechargeServiceImpl getSelf() {
|
|
|
+ return SpringUtil.getBean(getClass());
|
|
|
+ }
|
|
|
+
|
|
|
+}
|