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

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:

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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →