# How the Coupon and Voucher System Works in the Mall Project: A Complete Technical Guide

> Explore the coupon and voucher system in the Mall project. Learn how it manages discounts across data, admin, and portal layers with automatic rollback for order cancellations.

- Repository: [macro/mall](https://github.com/macrozheng/mall)
- Tags: deep-dive
- Published: 2026-02-28

---

**The Mall project implements a full coupon lifecycle across three architectural layers—data entities in `mall-mbg`, admin management in `mall-admin`, and member-side operations in `mall-portal`—supporting global, category-specific, and product-specific discounts with automatic rollback on order cancellation.**

The macrozheng/mall repository provides a production-ready e-commerce platform with a robust voucher system that handles everything from creation to redemption. This guide examines the complete technical implementation, referencing actual source files from the MyBatis data layer through to the portal service implementations.

## Coupon Data Model and Architecture

The system separates concerns across three distinct layers to manage voucher logic cleanly.

### Core Entities and Relationships

The data model centers on four main entities stored in the `mall-mbg` module:

- **`SmsCoupon`** – Stores the master definition including `type` (discount type), `amount` (face value), `minPoint` (minimum spend), `useType` (scope restriction), validity periods, and inventory counts (`count`, `receiveCount`, `publishCount`).
- **`SmsCouponHistory`** – Tracks every member claim with fields for `memberId`, `couponId`, `couponCode`, `useStatus` (0=unused, 1=used), and timestamps.
- **`SmsCouponProductRelation`** – Links coupons to specific product IDs when `useType = 2`.
- **`SmsCouponProductCategoryRelation`** – Links coupons to category IDs when `useType = 1`.

### Persistence Layer

MyBatis mappers handle all database interactions:

- **[`SmsCouponMapper.xml`](https://github.com/macrozheng/mall/blob/main/SmsCouponMapper.xml)** – CRUD operations for the `sms_coupon` table.
- **[`SmsCouponHistoryDao.xml`](https://github.com/macrozheng/mall/blob/main/SmsCouponHistoryDao.xml)** – Complex joins for retrieving detailed coupon histories with product and category data.

## Admin Coupon Management

Administrators define voucher rules through the `mall-admin` service layer before members can claim them.

### Creating Coupon Definitions

In [`mall-admin/src/main/java/com/macro/mall/service/impl/SmsCouponServiceImpl.java`](https://github.com/macrozheng/mall/blob/main/mall-admin/src/main/java/com/macro/mall/service/impl/SmsCouponServiceImpl.java), the `create` method (lines 38-58) processes new vouchers:

1. Receives a `SmsCouponParam` DTO from the controller.
2. Sets initial inventory: `count` equals `publishCount` (total available), `receiveCount` starts at 0.
3. Persists the base `SmsCoupon` entity.
4. Inserts relation records into `SmsCouponProductRelation` or `SmsCouponProductCategoryRelation` based on the `useType` parameter.

### Scope Restrictions and Use Types

The `useType` field determines voucher applicability:

- **0 (Global)** – Usable on any order meeting the minimum spend.
- **1 (Category-specific)** – Restricted to products in linked categories via `SmsCouponProductCategoryRelation`.
- **2 (Product-specific)** – Restricted to specific SKUs via `SmsCouponProductRelation`.

## Member Coupon Issuance and Claiming

The `mall-portal` module handles member-facing operations through `UmsMemberCouponServiceImpl`.

### The Claim Process

The `add(Long couponId)` method (lines 43-80) implements a thread-safe claiming workflow:

1. **Existence Check** – Verifies the coupon exists and `count > 0`.
2. **Timing Validation** – Confirms `enableTime` has passed (claim start date).
3. **Per-User Limit** – Queries `SmsCouponHistory` to ensure the member hasn't exceeded `perLimit`.
4. **Inventory Deduction** – Decrements `sms_coupon.count` and increments `receiveCount`.
5. **History Creation** – Inserts a new `SmsCouponHistory` record with a generated 16-digit code and `useStatus = 0`.

### Coupon Code Generation Logic

The `generateCouponCode` method (lines 85-99) constructs unique 16-digit codes using:
- Last 8 digits of current timestamp (millisecond precision).
- 4-digit random number (0000-9999).
- Last 4 digits of the `memberId`.

This ensures uniqueness while encoding claim metadata directly in the code string.

### Retrieving Detailed Histories

For validation purposes, `SmsCouponHistoryDao.getDetailList(memberId)` performs SQL joins across the coupon, product relation, and category relation tables to return `SmsCouponHistoryDetail` objects containing full voucher metadata.

## Cart-Level Validation and Eligibility

Before checkout, the system filters member coupons against current cart contents.

### Filtering Usable Coupons

The `listCart(List<CartPromotionItem> cartItems, int type)` method (lines 14-34) separates coupons into usable and unusable lists:

- **`type = 1`** returns the **enableList** (coupons applicable to current cart).
- **`type = 0`** returns the **disableList** (claimed but inapplicable coupons).

For each coupon in the member's history, the method evaluates:
- Expiration (`endTime` > now).
- Minimum spend (`minPoint` <= applicable cart total).
- Scope restrictions via `useType` and relation tables.

### Scope-Based Calculations

Private helper methods calculate the relevant cart subset for comparison:

- **`calcTotalAmount`** – Sum of all cart items.
- **`calcTotalAmountByproductCategoryId`** – Sum of items in specific categories (for `useType = 1`).
- **`calcTotalAmountByProductId`** – Sum of specific SKUs (for `useType = 2`).

## Order Creation and Coupon Application

The order service integrates vouchers into the transaction lifecycle.

### Prorating Discounts Across Items

In `OmsPortalOrderServiceImpl`, the `handleCouponAmount` method (lines 53-66) distributes the coupon value proportionally:

1. Identifies applicable order items based on `useType`.
2. Calculates each item's share of the discount using `calcPerCouponAmount` (lines 74-80).
3. Stores the prorated value in `OmsOrderItem.couponAmount`.
4. Reduces the order total accordingly.

### Status Management and Rollback

The system maintains coupon state consistency through status updates:

- **Usage Commit** – `updateCouponStatus(couponId, memberId, 1)` (lines 19-31) marks the `SmsCouponHistory` record as used upon successful order placement.
- **Cancellation Rollback** – In `cancelOrder` and `cancelTimeOutOrder` (lines 40-42), the method invokes `updateCouponStatus` with `useStatus = 0` to restore the coupon to unused status when orders expire or are cancelled by the member.

## Summary

- The Mall project implements a three-layer coupon architecture separating data entities (`mall-mbg`), admin operations (`mall-admin`), and member services (`mall-portal`).
- **Coupon creation** in `SmsCouponServiceImpl.create` supports global, category-specific, and product-specific scopes via `useType` and relation tables.
- **Member claiming** in `UmsMemberCouponServiceImpl.add` enforces inventory limits, per-user restrictions, and timing rules while generating unique 16-digit codes.
- **Cart validation** filters claimed coupons against real-time cart totals and scope restrictions before checkout.
- **Order integration** prorates discounts across applicable items and provides automatic rollback mechanisms when orders are cancelled or timeout.

## Frequently Asked Questions

### How does the Mall project prevent duplicate coupon claims?

The `UmsMemberCouponServiceImpl.add` method queries the `SmsCouponHistory` table to count existing claims for the specific `memberId` and `couponId` combination. If the count equals or exceeds the coupon's `perLimit` field, the service rejects the claim with an error before any database write occurs.

### What happens to a coupon when an order is cancelled?

When `OmsPortalOrderServiceImpl.cancelOrder` or `cancelTimeOutOrder` executes, it calls `updateCouponStatus` with `useStatus = 0` to revert the corresponding `SmsCouponHistory` record to unused status. This occurs synchronously during cancellation and also in the delayed message handler for timeout scenarios, ensuring inventory is accurately restored.

### How are category-specific and product-specific coupons validated against the cart?

The `UmsMemberCouponServiceImpl.listCart` method uses the `useType` field to determine validation logic. For `useType = 1` (category), it calls `calcTotalAmountByproductCategoryId` to sum only items in linked categories. For `useType = 2` (product), it uses `calcTotalAmountByProductId` to sum only specific SKUs. The coupon is valid only if the calculated subtotal meets the `minPoint` requirement.

### What is the format of generated coupon codes in the system?

Coupon codes are 16-digit strings generated in `UmsMemberCouponServiceImpl.generateCouponCode` by concatenating the last 8 digits of the current millisecond timestamp, a 4-digit random number, and the last 4 digits of the member ID. This format ensures temporal uniqueness while embedding claim metadata for potential debugging or tracing purposes.