一、功能概述
美团买菜系统的优惠券通用功能需要支持多种类型的优惠券(满减券、折扣券、无门槛券等)在商品、订单层面的灵活使用,同时满足业务运营的多样化需求。
二、核心功能设计
1. 优惠券类型定义
```java
public enum CouponType {
FULL_REDUCTION, // 满减券
DISCOUNT, // 折扣券
FIXED_AMOUNT, // 固定金额券
FREE_DELIVERY, // 免运费券
NEW_USER_EXCLUSIVE // 新人专享券
}
```
2. 优惠券实体设计
```java
public class Coupon {
private String id;
private String name;
private CouponType type;
private BigDecimal amount; // 优惠金额或折扣比例
private BigDecimal minOrderAmount; // 最低使用门槛
private Date startTime;
private Date endTime;
private Integer validityDays; // 有效期天数(从领取算起)
private Integer totalQuantity; // 总数量
private Integer remainingQuantity; // 剩余数量
private String applicableScope; // 适用范围(全平台/特定品类/特定商品)
private List applicableGoodsIds; // 适用商品ID列表
private List excludedGoodsIds; // 排除商品ID列表
private Boolean isStackable; // 是否可叠加使用
private Integer priority; // 优先级(用于多券选择)
// getters & setters
}
```
3. 用户优惠券实体
```java
public class UserCoupon {
private String id;
private String userId;
private String couponId;
private Coupon coupon;
private Date obtainedTime;
private Date expiryTime;
private Boolean isUsed;
private String orderId; // 使用订单ID
// getters & setters
}
```
三、核心业务逻辑实现
1. 优惠券发放
```java
public class CouponService {
// 发放优惠券给用户
public UserCoupon issueCoupon(String userId, String couponId) {
Coupon coupon = couponRepository.findById(couponId)
.orElseThrow(() -> new RuntimeException("优惠券不存在"));
if (coupon.getRemainingQuantity() <= 0) {
throw new RuntimeException("优惠券已领完");
}
UserCoupon userCoupon = new UserCoupon();
userCoupon.setUserId(userId);
userCoupon.setCouponId(couponId);
userCoupon.setObtainedTime(new Date());
// 计算过期时间
Date expiryTime;
if (coupon.getValidityDays() != null) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, coupon.getValidityDays());
expiryTime = calendar.getTime();
} else {
expiryTime = coupon.getEndTime();
}
userCoupon.setExpiryTime(expiryTime);
// 保存用户优惠券
UserCoupon saved = userCouponRepository.save(userCoupon);
// 更新优惠券剩余数量
coupon.setRemainingQuantity(coupon.getRemainingQuantity() - 1);
couponRepository.save(coupon);
return saved;
}
}
```
2. 优惠券适用性检查
```java
public class CouponValidator {
// 检查优惠券是否适用于当前购物车
public boolean isCouponApplicable(UserCoupon userCoupon, Cart cart) {
Coupon coupon = userCoupon.getCoupon();
// 检查有效期
Date now = new Date();
if (now.before(userCoupon.getObtainedTime()) ||
now.after(userCoupon.getExpiryTime())) {
return false;
}
// 检查最低消费金额
if (coupon.getMinOrderAmount() != null &&
cart.getTotalAmount().compareTo(coupon.getMinOrderAmount()) < 0) {
return false;
}
// 检查商品适用范围
if (coupon.getApplicableScope() != null) {
switch (coupon.getApplicableScope()) {
case "ALL":
break;
case "CATEGORY":
// 检查购物车中是否有不适用品类的商品
break;
case "GOODS":
// 检查购物车中是否有不适用商品
List excludedIds = coupon.getExcludedGoodsIds();
for (CartItem item : cart.getItems()) {
if (excludedIds.contains(item.getGoodsId())) {
return false;
}
}
break;
}
}
return true;
}
}
```
3. 优惠券计算服务
```java
public class CouponCalculator {
// 计算订单优惠金额
public BigDecimal calculateDiscount(UserCoupon userCoupon, Cart cart) {
Coupon coupon = userCoupon.getCoupon();
BigDecimal orderAmount = cart.getTotalAmount();
switch (coupon.getType()) {
case FULL_REDUCTION:
if (orderAmount.compareTo(coupon.getMinOrderAmount()) >= 0) {
return coupon.getAmount();
}
break;
case DISCOUNT:
if (orderAmount.compareTo(coupon.getMinOrderAmount()) >= 0) {
return orderAmount.multiply(coupon.getAmount())
.setScale(2, RoundingMode.DOWN);
}
break;
case FIXED_AMOUNT:
return coupon.getAmount();
case FREE_DELIVERY:
return cart.getDeliveryFee();
default:
break;
}
return BigDecimal.ZERO;
}
}
```
四、订单结算流程集成
1. 优惠券选择接口
```java
public class OrderService {
// 获取用户可用优惠券列表
public List getAvailableCoupons(String userId, Cart cart) {
List allCoupons = userCouponRepository.findByUserIdAndIsUsedFalse(userId);
List available = new ArrayList<>();
for (UserCoupon coupon : allCoupons) {
if (new CouponValidator().isCouponApplicable(coupon, cart)) {
available.add(coupon);
}
}
// 按优先级排序
available.sort(Comparator.comparingInt(c ->
c.getCoupon().getPriority() != null ? c.getCoupon().getPriority() : Integer.MAX_VALUE));
return available;
}
// 使用优惠券结算
public Order createOrderWithCoupon(String userId, Cart cart, String couponId) {
UserCoupon userCoupon = userCouponRepository.findByIdAndUserIdAndIsUsedFalse(couponId, userId)
.orElseThrow(() -> new RuntimeException("优惠券不可用"));
if (!new CouponValidator().isCouponApplicable(userCoupon, cart)) {
throw new RuntimeException("当前购物车不适用此优惠券");
}
// 计算优惠金额
BigDecimal discount = new CouponCalculator().calculateDiscount(userCoupon, cart);
// 创建订单
Order order = new Order();
order.setUserId(userId);
order.setGoodsAmount(cart.getTotalAmount());
order.setDeliveryFee(cart.getDeliveryFee());
order.setCouponDiscount(discount);
order.setActualAmount(cart.getTotalAmount()
.add(cart.getDeliveryFee())
.subtract(discount));
// 标记优惠券已使用
userCoupon.setUsed(true);
userCoupon.setOrderId(order.getId());
userCouponRepository.save(userCoupon);
return order;
}
}
```
五、数据库设计
1. 优惠券表(coupon)
```sql
CREATE TABLE coupon (
id VARCHAR(32) PRIMARY KEY,
name VARCHAR(64) NOT NULL,
type VARCHAR(32) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
min_order_amount DECIMAL(10,2),
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
validity_days INTEGER,
total_quantity INTEGER NOT NULL,
remaining_quantity INTEGER NOT NULL,
applicable_scope VARCHAR(32),
applicable_goods_ids TEXT, -- JSON数组
excluded_goods_ids TEXT, -- JSON数组
is_stackable BOOLEAN DEFAULT FALSE,
priority INTEGER DEFAULT 999,
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL
);
```
2. 用户优惠券表(user_coupon)
```sql
CREATE TABLE user_coupon (
id VARCHAR(32) PRIMARY KEY,
user_id VARCHAR(32) NOT NULL,
coupon_id VARCHAR(32) NOT NULL,
obtained_time DATETIME NOT NULL,
expiry_time DATETIME NOT NULL,
is_used BOOLEAN DEFAULT FALSE,
order_id VARCHAR(32),
FOREIGN KEY (coupon_id) REFERENCES coupon(id)
);
```
六、前端交互设计
1. 优惠券选择弹窗
```javascript
// 伪代码示例
function showCouponSelectDialog(cart) {
api.getAvailableCoupons(userId, cart).then(coupons => {
// 渲染优惠券列表
coupons.forEach(coupon => {
const isApplicable = checkCouponApplicable(coupon, cart);
renderCouponItem(coupon, isApplicable);
});
// 用户选择优惠券
document.getElementById(coupon-select).addEventListener(change, (e) => {
const selectedId = e.target.value;
if (selectedId) {
applyCoupon(selectedId);
} else {
removeCoupon();
}
});
});
}
```
2. 订单结算页显示
```javascript
// 伪代码示例
function updateOrderSummary() {
const couponId = getSelectedCouponId();
if (couponId) {
api.calculateOrderWithCoupon(userId, cart, couponId).then(result => {
document.getElementById(discount).textContent = `-¥${result.discount}`;
document.getElementById(total).textContent = `¥${result.total}`;
});
} else {
// 不使用优惠券的计算
updateOrderWithoutCoupon();
}
}
```
七、扩展功能考虑
1. 优惠券叠加使用:实现多张优惠券的组合使用逻辑
2. 优惠券分享:支持用户分享优惠券给好友
3. 优惠券核销:线下场景的优惠券核销功能
4. 优惠券统计:运营后台的优惠券使用统计和分析
5. 优惠券防刷:防止用户通过非法手段获取大量优惠券
八、测试要点
1. 优惠券有效期测试(边界值测试)
2. 最低消费金额测试
3. 商品适用范围测试
4. 优惠券叠加使用测试
5. 并发领取测试
6. 退款后优惠券状态测试
以上是美团买菜系统优惠券通用功能的核心实现方案,可根据实际业务需求进行调整和扩展。