Using Strategy Pattern for Settlement Prorate: Calculating Fees Across Different Marketing Campaigns

The article explains how an e‑commerce platform’s settlement module can use the Strategy pattern to cleanly handle varied fee calculations for group‑buy, flash‑sale, special‑price and full‑discount promotions, avoiding tangled if‑else logic and simplifying future extensions.

samdeepthink
samdeepthink
samdeepthink
Using Strategy Pattern for Settlement Prorate: Calculating Fees Across Different Marketing Campaigns

Problem

In an e‑commerce settlement module the platform must calculate how much revenue from each order belongs to the platform versus the merchant. Plain orders are simple, but marketing activities such as group‑buy, flash‑sale, special‑price, and full‑discount each provide the three settlement parameters (prorate distribution, marketing ratio, activity price) from different tables, with distinct validation rules and calculation methods. A monolithic if‑else implementation becomes fragile and hard to maintain.

Core Concepts

Prorate Distribution (prorateDistribution): the base proportion the platform extracts from the merchant’s amount; stored per SKU.

Marketing Ratio (marketRatio): a finer‑grained service‑fee rate that takes precedence over prorateDistribution unless it is null or –1.

Activity Price (price): the promotional selling price that directly influences the merchant’s earnings.

All three values are Integer fields in a SettlementBO object.

Why Strategy Pattern

Each activity type obtains the three values from different tables and applies its own validation (e.g., time‑window checks). Encapsulating these differences in separate handlers lets the upper‑level code depend only on a common interface, eliminating the need to modify core logic whenever a new activity is added.

Interface Design

The ProrateDistributionHandler interface defines three methods: type() – returns the activity‑type code used for routing. getActivityProrateDistribution() – returns only the prorate distribution (legacy method). getActivitySettlementParam() – returns a full SettlementBO (newer method).

The input DTO ProrateDistributionDTO carries activity ID, activity type, product ID, SKU ID, child item ID, child SKU ID, and the payment time payTime, which is used for validity checks.

Handler Implementations

Group‑Buy, Flash‑Sale, Special‑Price (Expiration Check)

These three handlers share a two‑step logic:

Query the activity record and verify that payTime falls within the activity’s start and end times. If it is outside, return 0 for the distribution, treating the order as if it did not participate.

If the activity is valid, fetch the SKU‑level configuration to obtain prorateDistribution, marketRatio, and price. Missing configuration defaults to 0.

if (payTime.after(activity.getEndTime()) || payTime.before(activity.getStartTime())) {
    return 0;
}
return Optional.ofNullable(grouponSku.getProrateDistribution()).orElse(0);

When the activity record cannot be found, the handler throws a BizException. SKU‑level missing values are tolerated with a default 0.

Full‑Discount (No Expiration Check)

The Full‑Discount handler differs:

No activity‑time validation because the validity period is managed upstream in the PMS system.

The prorate distribution is hard‑coded to 0; only marketRatio and price are read from the full_off_activity_item_settlement table.

FullOffActivityItemSettlement item = activityMapper.getByActivityIdAndParentSkuId(
    dto.getActivityId(), dto.getChildItemId(), dto.getSkuId());
if (item == null) return new SettlementBO(0);
return new SettlementBO(0,
    Optional.ofNullable(item.getMarketRatio()).orElse(0),
    item.getPrice());

Comparison of Handlers

Activity type codes: Group‑Buy = 2, Flash‑Sale = 1, Special‑Price = 3, Full‑Discount = 6.

Expiration check: present for the first three, absent for Full‑Discount.

Prorate distribution source: SKU config for the first three, fixed 0 for Full‑Discount.

Marketing ratio source: SKU config for the first three, settlement config table for Full‑Discount.

Activity price source: SKU config for the first three, settlement config table for Full‑Discount.

Data source: activity table + SKU table for the first three; independent settlement config table for Full‑Discount.

Strategy Router

Spring injects all ProrateDistributionHandler beans into a list. The router iterates the list and returns the handler whose type() matches the activity type code.

@Autowired
private List<ProrateDistributionHandler> handlerList;

public ProrateDistributionHandler getProrateDistributionHandler(Integer handlerType) {
    for (ProrateDistributionHandler handler : handlerList) {
        if (Objects.equals(handler.type(), handlerType)) return handler;
    }
    return null;
}

With only four handlers the linear traversal cost is negligible; if the number grows, a map‑based lookup can replace the list.

Two‑Step Compatibility Query

During settlement the system first attempts to read a manually reported prorate distribution from activity_report tables (cached in Redis). If a positive value is found, it is used directly, bypassing the handler. Otherwise the handler’s calculation is invoked. Additionally, when marketRatio == -1, the system falls back to the prorate distribution.

Benefits of the Strategy Pattern

Eliminates sprawling if‑else blocks, making the codebase easier to extend.

Provides each activity its own encapsulated space, allowing distinct validation (e.g., time checks for group‑buy), separate data sources (SKU tables vs. independent settlement tables), and tailored error handling (exception for missing activity ID, silent zero for expired activity).

Design decision: returning 0 for expired activities treats expiration as a normal business scenario; throwing an exception for missing activity data surfaces data inconsistency.

When to Apply the Pattern

If a single operation must query different tables, perform different validations, or return different parameter structures based on a type code, the Strategy pattern is appropriate. If only parameter values differ while the logic remains identical, a simple configuration table suffices.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

design patternse-commercejavaStrategy PatternSpringSettlement
samdeepthink
Written by

samdeepthink

Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.