会员退款

退款回调
This commit is contained in:
2025-02-19 17:15:42 +08:00
parent c024d83309
commit 1ef28891a5
14 changed files with 323 additions and 41 deletions

View File

@@ -28,9 +28,18 @@ public class NotifyController {
@RequestMapping("/payCallBack")
public String notifyCallBack(@RequestBody CzgBaseRespParams respParams){
JSONObject czg = CzgPayUtils.getCzg(respParams);
AssertUtil.isNull(czg, "回调数据为空");
AssertUtil.isNull(czg, "支付回调数据为空");
log.info("支付回调数据为:{}", czg);
orderInfoService.payCallBackOrder(czg.getString("mchOrderNo"), czg);
return SUCCESS;
}
@RequestMapping("/refundCallBack")
public String refundCallBack(@RequestBody CzgBaseRespParams respParams){
JSONObject czg = CzgPayUtils.getCzg(respParams);
AssertUtil.isNull(czg, "退款回调数据为空");
log.info("退款回调数据为:{}", czg);
orderInfoService.refundCallBackOrder(czg.getString("mchOrderNo"), czg);
return SUCCESS;
}
}

View File

@@ -2,6 +2,7 @@ package com.czg.controller;
import com.czg.resp.CzgResult;
import com.czg.service.order.dto.VipPayParamDTO;
import com.czg.service.order.dto.VipRefundDTO;
import com.czg.service.order.service.PayService;
import com.czg.utils.AssertUtil;
import com.czg.utils.ServletUtil;
@@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.Map;
/**
@@ -68,7 +70,7 @@ public class VipPayController {
@PostMapping("/scanPayVip")
public CzgResult<Map<String, Object>> scanPayVip(HttpServletRequest request, @Validated @RequestBody VipPayParamDTO payParam) {
AssertUtil.isNull(payParam.getShopUserId(), "充值失败 未指定店铺用户Id");
payParam.setPlatformType(ServletUtil.getHeaderIgnoreCase(ServletUtil.getRequest(), "platformType"));
payParam.setPlatformType(ServletUtil.getHeaderIgnoreCase(request, "platformType"));
return payService.scanPayVip(ServletUtil.getClientIPByHeader(request), payParam);
}
@@ -82,4 +84,34 @@ public class VipPayController {
payParam.setPlatformType(ServletUtil.getHeaderIgnoreCase(ServletUtil.getRequest(), "platformType"));
return payService.microPayVip(payParam);
}
/**
* 退款前置
* 最大退款金额 为 充值金额 inAmount
*/
@PostMapping("/refundVipBefore")
public CzgResult<Map<String, BigDecimal>> refundVipBefore(@Validated @RequestBody VipRefundDTO payParam) {
return payService.refundVipBefore(payParam);
}
/**
* cashRefund 是否是现金退款
* 会员退款(先调用 退款前置接口 refundVipBefore)
* 最大退款金额为 充值金额 inAmount
* 理论可退最大金额为
* 用户余额-赠送金额 >充值金额 则可退充值金额
* 实际可退最大金额为 充值金额
* 如果实际 大于 理论 则 需要勾选 outOfRange 超额退款 为true 默认为false
*/
@PostMapping("/refundVip")
public CzgResult<Object> refundVip(HttpServletRequest request, @Validated @RequestBody VipRefundDTO payParam) {
AssertUtil.isNull(payParam.getRefAmount(), "退款金额不能为空");
if (payParam.getRefAmount().compareTo(BigDecimal.ZERO) <= 0) {
return CzgResult.failure("退款金额必须大于0");
} else if (payParam.getRefAmount().compareTo(new BigDecimal("10001")) > 0) {
return CzgResult.failure("退款金额过大");
}
payParam.setPlatformType(ServletUtil.getHeaderIgnoreCase(request, "platformType"));
return payService.refundVip(payParam);
}
}

View File

@@ -1,16 +1,19 @@
package com.czg.account.dto.flow;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.alibaba.fastjson2.annotation.JSONField;
import java.io.Serial;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 用户余额流水 实体类。
*
@@ -26,27 +29,56 @@ public class ShopUserFlowDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Id(keyType = KeyType.Auto)
private Long id;
private Long userId;
private Long shopId;
/**
* 充值金额
*/
private BigDecimal amount;
/**
* 当前余额
*/
private BigDecimal balance;
/**
* 已退款金额
*/
private BigDecimal refundAmount;
/**
* 类型:cashIn 会员充值awardIn 充值奖励wechatIn 微信小程序充值alipayIn 支付宝小程序充值orderPay 订单消费orderRefund 订单退款rechargeRefund 充值退款 adminOp 管理员手动增减
* 类型:
* cashIn 现金充值,
* wechatIn 微信小程序充值,
* alipayIn 支付宝小程序充值,
* awardIn 充值奖励,
* rechargeRefund 充值退款
* <p>
* orderPay 订单消费,
* orderRefund 订单退款,
* <p>
* adminIn 管理员充值
* adminOut管理员消费
*/
private String bizCode;
/**
* 备注
*/
private String remark;
/**
* 加减号
* 关联id
* 订单支付/订单退款/霸王餐时 订单id
* 支付/退款 tb_order_payment.id
*/
private String type;
private String remark;
private Long relationId;
/**
* 充值记录id 当前表的id
* 退款使用
*/
private Long rechargeId;
/**
* 关联订单编号,支付单号,退款单号

View File

@@ -31,6 +31,11 @@ public class ShopUserMoneyEditDTO {
* 支付/退款 tb_order_payment.id
*/
private Long relationId;
/**
* 充值记录id 当前表的id
* 退款使用
*/
private Long rechargeId;
/**
* 浮动金额
*/

View File

@@ -127,4 +127,11 @@ public class ShopUser implements Serializable {
@Column(onInsertValue = "now()", onUpdateValue = "now()")
private LocalDateTime updateTime;
public BigDecimal getAmount() {
if (amount == null) {
return BigDecimal.ZERO;
}
return amount;
}
}

View File

@@ -4,6 +4,7 @@ import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@@ -37,36 +38,67 @@ public class ShopUserFlow implements Serializable {
private Long userId;
private Long shopId;
/**
* 充值金额
*/
private BigDecimal amount;
/**
* 当前余额
*/
private BigDecimal balance;
/**
* 已退款金额
*/
private BigDecimal refundAmount;
/**
* 类型:cashIn 会员充值awardIn 充值奖励wechatIn 微信小程序充值alipayIn 支付宝小程序充值orderPay 订单消费orderRefund 订单退款rechargeRefund 充值退款 adminOp 管理员手动增减
* 类型:
* cashIn 现金充值,
* wechatIn 微信小程序充值,
* alipayIn 支付宝小程序充值,
* awardIn 充值奖励,
* rechargeRefund 充值退款
* <p>
* orderPay 订单消费,
* orderRefund 订单退款,
* <p>
* adminIn 管理员充值
* adminOut管理员消费
*/
private String bizCode;
/**
* 加减号
* 备注
*/
private String type;
private String remark;
/**
* 是否可退款
*/
private Integer isCanRefund;
/**
* 关联id
* 订单支付/订单退款/霸王餐时 订单id
* 支付/退款 tb_order_payment.id
*/
private Long relationId;
/**
* 充值记录id 当前表的id
* 退款使用
*/
private Long rechargeId;
@Column(onInsertValue = "now()")
private LocalDateTime createTime;
public BigDecimal getAmount() {
if (amount == null) {
return BigDecimal.ZERO;
}
return amount;
}
public BigDecimal getRefundAmount() {
if (refundAmount == null) {
return BigDecimal.ZERO;
}
return refundAmount;
}
}

View File

@@ -1,7 +1,7 @@
package com.czg.account.service;
import com.mybatisflex.core.service.IService;
import com.czg.account.entity.ShopUserFlow;
import com.mybatisflex.core.service.IService;
/**
* 用户余额流水 服务层。

View File

@@ -7,6 +7,8 @@ import com.czg.order.enums.PayEnums;
import com.czg.order.vo.OrderInfoVo;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.service.IService;
import jakarta.validation.constraints.NotBlank;
import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@@ -21,9 +23,9 @@ public interface OrderInfoService extends IService<OrderInfo> {
Page<OrderInfoVo> getOrderByPage(OrderInfoQueryDTO param);
void payCallBackOrder(String orderNo, JSONObject resultJson);
void payCallBackOrder(@NotBlank String orderNo, @NotNull JSONObject resultJson);
void refundCallBackOrder();
void refundCallBackOrder(@NotBlank String orderNo, @NotNull JSONObject resultJson);
void upOrderInfo(Long orderId, BigDecimal payAmount, LocalDateTime payTime, Long payOrderId, PayEnums payType);
}

View File

@@ -1,11 +1,10 @@
package com.czg.service.account.service.impl;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.account.entity.ShopUserFlow;
import com.czg.account.service.ShopUserFlowService;
import com.czg.service.account.mapper.ShopUserFlowMapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Service;
/**
* 用户余额流水 服务层实现。
@@ -14,6 +13,6 @@ import org.springframework.stereotype.Service;
* @since 2025-02-13
*/
@DubboService
public class ShopUserFlowServiceImpl extends ServiceImpl<ShopUserFlowMapper, ShopUserFlow> implements ShopUserFlowService{
public class ShopUserFlowServiceImpl extends ServiceImpl<ShopUserFlowMapper, ShopUserFlow> implements ShopUserFlowService {
}

View File

@@ -91,15 +91,14 @@ public class ShopUserServiceImpl extends ServiceImpl<ShopUserMapper, ShopUser> i
throw new ApiNotPrintException("增减用户余额操作失败");
}
userFlow.setIsCanRefund(0);
userFlow.setUserId(userInfo.getUserId());
userFlow.setShopId(userInfo.getShopId());
userFlow.setAmount(shopUserEditDTO.getMoney());
userFlow.setAmount(shopUserEditDTO.getType() == 0 ? shopUserEditDTO.getMoney().negate() : shopUserEditDTO.getMoney());
userFlow.setBalance(shopUserEditDTO.getType() == 0 ? userInfo.getAmount().subtract(shopUserEditDTO.getMoney()) : userInfo.getAmount().add(shopUserEditDTO.getMoney()));
userFlow.setBizCode(shopUserEditDTO.getBizEnum().getCode());
// userFlow.setType(shopUserEditDTO.getType() == 0 ? "-" : "+");
userFlow.setRemark(shopUserEditDTO.getRemark());
userFlow.setRelationId(shopUserEditDTO.getRelationId());
userFlow.setRechargeId(shopUserEditDTO.getRechargeId());
shopUserFlowService.save(userFlow);
return userFlow.getId();
}

View File

@@ -0,0 +1,47 @@
package com.czg.service.order.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
/**
* @author ww
* @description
*/
@Data
public class VipRefundDTO {
/**
* 店铺id
*/
@NotNull(message = "店铺不能为空")
private Long shopId;
/**
* 店铺用户id
*/
@NotNull(message = "店铺用户不能为空")
private Long shopUserId;
/**
* 退款id 会员流水id tb_shop_user_flow.id
*/
@NotNull(message = "会员流水不能为空")
private Long flowId;
/**
* 退款金额
*/
private BigDecimal refAmount;
/**
* 退款原因
*/
private String remark;
/**
* 超额退款
*/
private boolean outOfRange;
//现金退款
private boolean cashRefund;
private String platformType;
}

View File

@@ -5,6 +5,7 @@ import com.czg.entity.resp.CzgRefundResp;
import com.czg.resp.CzgResult;
import com.czg.service.order.dto.OrderPayParamDTO;
import com.czg.service.order.dto.VipPayParamDTO;
import com.czg.service.order.dto.VipRefundDTO;
import java.math.BigDecimal;
import java.util.Map;
@@ -75,6 +76,16 @@ public interface PayService {
*/
CzgResult<Map<String, Object>> microPayVip(VipPayParamDTO payParam);
/**
* 退款前校验
*/
CzgResult<Map<String, BigDecimal>> refundVipBefore(VipRefundDTO payParam);
/**
* 会员退款
*/
CzgResult<Object> refundVip(VipRefundDTO payParam);
/**
* 订单退款

View File

@@ -10,6 +10,7 @@ import com.czg.account.service.ShopActivateService;
import com.czg.account.service.ShopUserService;
import com.czg.config.RabbitPublisher;
import com.czg.entity.notify.CzgPayNotifyDTO;
import com.czg.entity.notify.CzgRefundNotifyDTO;
import com.czg.enums.ShopUserFlowBizEnum;
import com.czg.order.dto.OrderInfoQueryDTO;
import com.czg.order.entity.OrderDetail;
@@ -33,6 +34,7 @@ import jakarta.annotation.Resource;
import jakarta.validation.constraints.NotBlank;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -97,7 +99,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
@Override
@Transactional
public void payCallBackOrder(@NotBlank String orderNo, @NotBlank JSONObject resultJson) {
public void payCallBackOrder(@NotBlank String orderNo, @NotNull JSONObject resultJson) {
CzgPayNotifyDTO czgCallBackDto = JSONObject.parseObject(resultJson.toString(), CzgPayNotifyDTO.class);
OrderPayment payment = paymentService.queryChain().eq(OrderPayment::getOrderNo, orderNo).one();
paymentService.updateChain()
@@ -146,8 +148,21 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
}
@Override
public void refundCallBackOrder() {
public void refundCallBackOrder(@NotBlank String orderNo, @NotNull JSONObject resultJson) {
CzgRefundNotifyDTO czgCallBackDto = JSONObject.parseObject(resultJson.toString(), CzgRefundNotifyDTO.class);
OrderPayment payment = paymentService.queryChain().eq(OrderPayment::getOrderNo, orderNo).one();
paymentService.updateChain()
.set(OrderPayment::getTradeNumber, czgCallBackDto.getRefundOrderId())
.set(OrderPayment::getRespJson, resultJson.toString())
.where(OrderPayment::getId).eq(payment.getId())
.update();
if (!"SUCCESS".equals(czgCallBackDto.getState())) {
if ("refund".equals(payment.getPayType())) {
//订单退款回滚
} else if ("memberRefund".equals(payment.getPayType())) {
//会员退款回滚
}
}
}

View File

@@ -5,10 +5,7 @@ import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.dto.shopuser.ShopUserMoneyEditDTO;
import com.czg.account.entity.ShopInfo;
import com.czg.account.entity.ShopMerchant;
import com.czg.account.entity.ShopUser;
import com.czg.account.entity.UserInfo;
import com.czg.account.entity.*;
import com.czg.account.service.*;
import com.czg.config.RabbitPublisher;
import com.czg.config.RedisCst;
@@ -25,6 +22,7 @@ import com.czg.service.CzgPayService;
import com.czg.service.RedisService;
import com.czg.service.order.dto.OrderPayParamDTO;
import com.czg.service.order.dto.VipPayParamDTO;
import com.czg.service.order.dto.VipRefundDTO;
import com.czg.service.order.service.PayService;
import com.czg.system.enums.SysParamCodeEnum;
import com.czg.system.service.SysParamsService;
@@ -62,6 +60,10 @@ public class PayServiceImpl implements PayService {
private ShopInfoService shopInfoService;
@DubboReference
private ShopActivateService shopActivateService;
@DubboReference
private ShopUserFlowService userFlowService;
@DubboReference
private ShopActivateInRecordService inRecordService;
@Resource
private RabbitPublisher rabbitPublisher;
@Resource
@@ -294,6 +296,96 @@ public class PayServiceImpl implements PayService {
"会员充值", payParam.getAuthCode(), payParam.getBuyerRemark(), ""));
}
@Override
public CzgResult<Map<String, BigDecimal>> refundVipBefore(VipRefundDTO payParam) {
Map<String, BigDecimal> resultMap = new HashMap<>(5);
ShopUser shopUser = shopUserService.getById(payParam.getShopUserId());
AssertUtil.isNull(shopUser, "该店铺用户不存在");
ShopUserFlow inFlow = userFlowService.queryChain().select(ShopUserFlow::getAmount)
.eq(ShopUserFlow::getId, payParam.getFlowId()).one();
AssertUtil.isNull(inFlow, "充值记录不存在");
ShopUserFlow giftFlow = userFlowService.queryChain().select(ShopUserFlow::getAmount)
.eq(ShopUserFlow::getRelationId, payParam.getFlowId())
.eq(ShopUserFlow::getBizCode, ShopUserFlowBizEnum.AWARD_IN.getCode())
.one();
resultMap.put("amount", shopUser.getAmount());
resultMap.put("inAmount", inFlow.getAmount( ));
resultMap.put("inRefundAmount", inFlow.getRefundAmount());
resultMap.put("giftAmount", giftFlow == null ? BigDecimal.ZERO : giftFlow.getAmount());
resultMap.put("giftRefundAmount", giftFlow == null ? BigDecimal.ZERO : giftFlow.getRefundAmount());
return CzgResult.success(resultMap);
}
@Override
public CzgResult<Object> refundVip(VipRefundDTO refPayParam) {
ShopUser shopUser = shopUserService.getById(refPayParam.getShopUserId());
ShopUserFlow inFlow = userFlowService.queryChain().select(ShopUserFlow::getAmount)
.eq(ShopUserFlow::getId, refPayParam.getFlowId()).one();
AssertUtil.isNull(inFlow, "充值记录不存在");
ShopUserFlow giftFlow = userFlowService.queryChain().select(ShopUserFlow::getAmount)
.eq(ShopUserFlow::getRelationId, refPayParam.getFlowId())
.eq(ShopUserFlow::getBizCode, ShopUserFlowBizEnum.AWARD_IN.getCode())
.one();
if ((inFlow.getAmount().subtract(inFlow.getRefundAmount())).compareTo(refPayParam.getRefAmount()) < 0) {
return CzgResult.failure("退款失败,退款金额不可大于可退金额");
}
//用户余额减去赠送金额 小于 退款金额 则需要勾选 超额退款
if (giftFlow != null) {
if (shopUser.getAmount().subtract(giftFlow.getAmount().subtract(giftFlow.getRefundAmount())).compareTo(refPayParam.getRefAmount()) < 0 && !refPayParam.isOutOfRange()) {
return CzgResult.failure("超额退款,请勾选 超额退款后重试");
}
} else {
if (shopUser.getAmount().compareTo(refPayParam.getRefAmount()) < 0 && !refPayParam.isOutOfRange()) {
return CzgResult.failure("超额退款,请勾选 超额退款后重试");
}
}
Long refPaymentId = null;
if (!refPayParam.isCashRefund()) {
OrderPayment payment = null;
if (inFlow.getRelationId() != null) {
payment = paymentService.getById(inFlow.getRelationId());
} else {
return CzgResult.failure("退款失败,该充值记录不存在");
}
String refPayOrderNo = "REF" + refPayParam.getPlatformType() + IdUtil.getSnowflakeNextId();
refPaymentId = initOrderPayment(new OrderPayment(refPayParam.getShopId(), refPayParam.getShopUserId(),
"memberRefund", refPayOrderNo, null, refPayParam.getRefAmount()));
CzgResult<CzgRefundResp> res = refund(refPayParam.getShopId(), new CzgRefundReq(refPayOrderNo, refPayParam.getRemark(),
refPayParam.getRefAmount().multiply(MONEY_RATE).longValue(), payment.getOrderNo(), ""));
if (res.getCode() != 200 || res.getData() == null) {
return CzgResult.failure(res.getMsg());
}
} else {
ShopUserMoneyEditDTO shopUserMoneyEditDTO = ShopUserMoneyEditDTO.builder()
.id(shopUser.getId())
.money(refPayParam.getRefAmount())
.type(0)
.remark("退款")
.bizEnum(ShopUserFlowBizEnum.RECHARGE_REFUND)
.relationId(refPaymentId)
.rechargeId(inFlow.getId())
.build();
//更新会员余额 并生成流水
shopUserService.updateMoney(shopUser.getShopId(), shopUserMoneyEditDTO);
userFlowService.updateChain()
.setRaw(ShopUserFlow::getRefundAmount, "refund_amount" + refPayParam.getRefAmount())
.eq(ShopUserFlow::getId, inFlow.getId())
.update();
if (giftFlow != null) {
userFlowService.updateChain()
.setRaw(ShopUserFlow::getRefundAmount, "refund_amount" + giftFlow.getAmount())
.eq(ShopUserFlow::getId, giftFlow.getId())
.update();
}
inRecordService.updateChain()
.set(ShopActivateInRecord::getOverNum, 0)
.eq(ShopActivateInRecord::getId, inFlow.getId())
.update();
}
return CzgResult.success();
}
@Override
@Transactional