近期负责的项目中有关于充值会员的功能,特做一个会员充值流程小结,这中间或许也存在着不足。希望可以得到大家的理解和建议。在调用第三方接口支付时修改表的状态(此处不做详细阐述)。
业务分析:
如下图,用户可以选择想要开通的会员等级(初级、中级、高级、超级),不同等级有不同的开通时长选择(一个月、一个季度、半年、一年),每个等级的会员对应的权限不一致,用户可以随意选择。
思路介绍:
在购买VIP成功后,要判断该用户当前是否为会员:
场景一:用户之前没有充值过会员,或者已经过期,就根据当前系统时间,增加对应充值时长。
场景二:用户目前是会员状态; 会员状态下还有三种场景:
1.充值比当前会员等级低的会员; 例如:当前用户已经是超级会员,但是又充值了初级会员
2.充值比当前会员等级高的会员;例如:当前用户已经是初级会员,但是又充值了超级会员
3.充值与当前会员等级一样的会员;例如:当前用户已经是中级会员,又充值了中级会员
算法思路:
模拟会员价格表: 单位为分比较好计算
等级 | 月 | 季 | 半年 | 全年 | 价格单位 |
初级会员 | 600 | 1500 | 2500 | 15500 | 分 |
中级会员 | 700 | 1600 | 2600 | 15600 | 分 |
高级会员 | 800 | 1700 | 2700 | 15700 | 分 |
超级会员 | 900 | 1800 | 2800 | 15800 | 分 |
月标准都按31天计算
场景:
初级 升 超级:
1.先算出初级还剩多少天 :公式:到期时间-当前时间=相差时间 ,如果低于一天直接按一天算
2.算出初级每天多少钱: 公式:原来开通方式的价格/ 套餐天数=每天价格
3.初级剩余天数换算成钱还剩多少钱:公式:剩余天数*每天价格 =剩余金额
4.计算超级会员每天价格: 公式:开通方式价格/套餐时长=每天价格
5.初级换算的钱可以买几天超级:剩余金额/超级会员每天价格 =换n天
最终:超级会员套餐时长+换n天=超级会员到期时间
超级 升初级:
1.算出超级每天价格:公式: 开通方式价格/套餐时长=超级会员每天价格
2.算出初级会员的钱可以买几天超级会员:公式:初级会员开通方式价格/初级会员每天价格=换n天
最终:到期时间+换n天=超级会员到期时间
中间 升 中级:
到期时间+中间套餐时间=中级会员到期时间
代码实现
表结构:
会员规则表:
用户表:
请求实体:
@Data
public class PayMember {
// 充值对象
private Long userId;
// 商户ID
private Long dealerId;
// 会员[1:初级;2:中级;3:高级;4:超级]
private Byte memberType;
// 购买的套餐[0:月;1:季度;2:半年;3:全年;]
private Byte comboId;
}
业务代码:
@Override
public ResultUtils payMember(PayMember payMember) {
// combosMap的SQL:
// SELECT `month`,
// season,
// semester,
// yearly,
// type
// FROM dealer_member_rule
// WHERE dealer_id = #{dealerId}
Map<Integer, Object> combosMap = dealerMemberRuleMapper.selectCombos(payMember.getDealerId());
// 查询当前用户会员情况
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_id", payMember.getUserId())
.select("member_type", "membership_due","member_pay");
User user = userMapper.selectOne(queryWrapper);
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
// 判断会员情况
if (ObjectUtil.isEmpty(user.getMembershipDue()) || LocalDateTime.now().isAfter(user.getMembershipDue())) {//第一次开通会员|| 会员已经到期
// 非升级续费,第一次开通 || 到期后开通
wrapper.eq("user_id", payMember.getUserId())
.set("member_pay", payMember.getComboId())
.set("membership_due", LocalDateTime.now().plusMonths(combo(payMember.getComboId())))
.set("member_type", payMember.getMemberType());
} else { // 续费 || 升级
if (user.getMemberType() > payMember.getMemberType()) {// 购买了比当前低级的套餐 (当前超级升初级)
// 获取当前使用的套餐 旧套餐
Map<String, Object> oldCombo = (Map<String, Object>) combosMap.get(user.getMemberType().intValue());
// 得出上传开通旧套餐的时长名字
String timeName = comboName(user.getMemberPay());
Integer oldPrice = (Integer) oldCombo.get(timeName);// 得出旧套餐的价格
// totalDays=这个套餐有多少天
int totalDays = combo(user.getMemberPay()) * 31;// 套餐总天数=n月*标准天数(31)
// 每天价格=套餐价格/总天数
int oldEveryDayThePrice = oldPrice / totalDays;
// 算出新套餐可以买几天旧套餐
// 即将要买的套餐 新套餐
Map<String, Object> newCombo = (Map<String, Object>) combosMap.get(payMember.getMemberType().intValue());
Integer newPrice = (Integer) newCombo.get(comboName(payMember.getComboId()));// 新套餐价格
// 可以买几天旧套餐=新套餐价格/旧套餐每天价格
int day = newPrice / oldEveryDayThePrice;
if (day<0) day = 1;// 少用一天送一天
// 会员到期时间=当前时间+新套餐天数+旧套餐这算天数
//到期时间加上最终天数
wrapper.eq("user_id", payMember.getUserId())
.set("member_pay", user.getMemberPay())// 还是就套餐的时长
.set("membership_due", user.getMembershipDue().plusDays(day))//到期时间加最终天数
.set("member_type", user.getMemberType());
} else if (user.getMemberType() == payMember.getMemberType()) {// 购买了和当前一样的套餐
//一样的套餐直接加上对应的天数
wrapper.eq("user_id", payMember.getUserId())
.set("member_pay", user.getMemberPay())// 还是就套餐的时长
.set("membership_due", user.getMembershipDue().plusMonths(combo(payMember.getComboId())))//到期时间加最终天数
.set("member_type", user.getMemberType());
} else {// 购买的比当前高级的套餐
// 算法和 高买低相反
// 初级还剩余几天
Duration duration = Duration.between(LocalDateTime.now(), user.getMembershipDue());//到期时间和当前时间的差
long hours = duration.toHours();//相差的小时数
long residueDay = hours / 24L;//旧套餐剩余天数
if (residueDay < 1) residueDay = 1; // 小于一天就送一天
// 旧套餐每天价格=原来开通方式的价格/套餐天数
// 获取当前使用的套餐 旧套餐
Map<String, Object> oldCombo = (Map<String, Object>) combosMap.get(user.getMemberType().intValue());
// 得出上传开通旧套餐的时长名字
String timeName = comboName(user.getMemberPay());
Integer oldPrice = (Integer) oldCombo.get(timeName);// 得出旧套餐的价格
int totalDays = combo(user.getMemberPay()) * 31;// 套餐总天数=n月*标准天数(31)
// 每天价格=套餐价格/总天数
int oldEveryDayThePrice = oldPrice / totalDays;
long residueMoney = residueDay * oldEveryDayThePrice;//剩余的钱=剩余天数*每天价格
// 获取新套餐
Map<String, Object> newCombo = (Map<String, Object>) combosMap.get(payMember.getMemberType().intValue());
// 对应时长的套餐价格
Integer newPrice = (Integer) newCombo.get(comboName(payMember.getComboId()));
// 算出新套餐每天价格
int daysMoney = newPrice / (combo(payMember.getComboId()) * 31);
// 算出剩余的钱可以买几天新套餐 可购买天数=剩余的钱/新套餐每天价格
long buyDaysNumber = residueMoney / daysMoney;
if (buyDaysNumber < 1) {// 如果小于1天就直接送一天
buyDaysNumber = 1;
}
// 最终到期时间=到期时间+((月*31)+旧套餐可以买的天数)
LocalDateTime membership_due = user.getMembershipDue().plusDays((combo(payMember.getComboId()) * 31) + buyDaysNumber);
wrapper.eq("user_id", payMember.getUserId())
.set("member_pay", payMember.getComboId())// 还是就套餐的时长
.set("membership_due", membership_due)//到期时间加最终天数
.set("member_type", payMember.getMemberType());
}
}
int update = userMapper.update(new User(), wrapper);
log.info("充值会员模块-用户ID:{},充值结果:{}", payMember.getUserId(), update > 0 ? "成功" : "失败");
return ResultUtils.success();
}
/**
* 套餐名称 方便取套餐值
*
* @param type [0:月;1:季度;2:半年;3:全年;]
* @return
*/
private String comboName(Byte type) {
switch (type) {
case 0:
return "month"; // 取月字段的价格
case 1:
return "season";// 取季字段的价格
case 2:
return "semester";// 半年
case 3:
return "yearly";// 一年
}
return null;
}
/**
* 套餐时间
*
* @param type [0:月;1:季度;2:半年;3:全年;]
* @return
*/
private Integer combo(Byte type) {
switch (type) {
case 0:
return 1;// 一个月 1一个月
case 1:
return 3;// 一个季度 三个月
case 2:
return 6;// 半年 六个月
case 3:
return 12;// 一年 十二个月
}
return null;
}