Designing a Group Order Module: Unified Payment and Post‑Payment Cost Splitting

This article details a production‑grade group‑order system where a single initiator pays the total order, then uses WeChat's group‑collection API to split costs among participants, covering the full workflow, data model, Redis caching strategy, payment callbacks, cost‑allocation formulas, state machine, and operational constraints.

samdeepthink
samdeepthink
samdeepthink
Designing a Group Order Module: Unified Payment and Post‑Payment Cost Splitting

Overview

Group ordering (拼单) allows multiple users to collaboratively select items, merge them into a single order, and have the initiator pay the total amount. The core change is the pre‑order collaboration stage rather than the order structure itself.

Business Flow

The process is divided into three stages:

Selection stage : The initiator creates a group, shares a 10‑digit uniqueId link, friends join, select items, confirm, and the initiator locks the group.

Order & payment stage : The locked group is submitted; the initiator places a normal order with the uniqueId, the system merges all selections into one order, and the initiator pays.

Cost‑splitting stage : After payment, the system calculates each participant’s share and the initiator collects the amounts via group collection.

Key Constraints

One user can belong to only one active group at a time.

The initiator cannot exit the group; they can only cancel the whole group.

Locking can be undone before an order is generated; after order creation it is irreversible.

Groups expire automatically after 48 hours without order submission (handled by a delayed‑task queue).

Order Domain Changes

Only two tiny modifications are needed:

Add a new enum value (e.g., 4) to the existing order_type column in the orders table to identify group orders.

Create a relationship table order_group_order linking a group to its order:

CREATE TABLE order_group_order (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  group_id BIGINT NOT NULL COMMENT '拼单组ID',
  order_id BIGINT NOT NULL COMMENT '订单ID',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  KEY idx_group_id (group_id),
  KEY idx_order_id (order_id)
) COMMENT='拼单组与订单关系表';

The order_type field tells the system the order is a group order, while order_group_order tells which group it belongs to.

Payment Domain Change

The only addition is a new line in the payment‑success callback to update the group status and trigger cost splitting:

Payment success callback:
  → Mark order as paid
  → Update group status to "completed"   // new line
  → Trigger cost‑splitting calculation

No changes to the payment flow, interface, or refund logic are required.

Group‑Order Tables

Four tables store the group‑order data:

order_group : lifecycle of a group (id, unique_id, creator_id, shop_id, address_id, status, create_channel, share_channel, expire_at).

order_group_member : each participant (id, group_id, user_id, join_channel, status).

order_group_item : item details per participant (id, user_id, order_id, order_item_id, quantity, price, origin_price, discount_fee, promo_code, has_box_fee, add_item_channel).

order_group_fee_split : cost‑splitting results (id, group_id, order_id, user_id, goods_amount, delivery_fee, box_fee, discount_amount, total_amount).

Selection Stage Design

All selection data lives in Redis to handle high‑frequency reads/writes and large amounts of transient data. Keys are sharded by date:

order:group:{date}:members:{uniqueId}   → Hash, member info
order:group:{date}:goods:{uniqueId}     → Hash, each member’s selections
order:group:{date}:status:{uniqueId}   → String, group status snapshot

Data is persisted to MySQL only once—when the initiator submits the order. At that moment the system reads all members’ selections from Redis, writes the merged order to the normal order tables, and records each participant’s items in order_group_item.

Benefits:

Pure‑memory operations give extremely high read/write performance.

Expired or cancelled groups need no database cleanup; Redis TTL (1 day) automatically removes stale data.

Cost‑Splitting Calculation

Triggered after payment, the calculation splits four cost components:

Goods fee : each participant’s own item total (already fixed at order time).

Delivery fee : evenly divided by participant count, rounded with HALF_UP to two decimals.

Packaging fee : allocated proportionally to the number of items that require packaging (identified by has_box_fee).

Discount amount : total discount is allocated proportionally to each participant’s original item price using the formula<br/>

user_discount = user_origin_price / total_origin_price × total_discount

.

All calculations use BigDecimal with two‑decimal precision and HALF_UP rounding. After computing each participant’s share, the system verifies that the sum of allocated discounts matches the actual total discount; any 1‑cent discrepancy is added to the initiator’s discount (or to the first participant if the initiator selected no items).

Final payable amount per participant:

payable = goods_origin_price - allocated_discount + delivery_share + packaging_share

Edge case: if a participant’s payable amount becomes zero or negative (extreme discount), the front‑end must alert the initiator because group collection would fail.

Group Collection (WeChat API)

The group‑collection feature is a WeChat mini‑program API (

POST https://api.weixin.qq.com/wxa/business/groupBuy/createOrder

) that requires the following conditions:

The current user must be the initiator.

The client must be a WeChat mini‑program.

Both the group’s creation channel and share channel must be the mini‑program.

If any step uses Alipay or a native app, the button is hidden and the initiator must settle manually.

Maximum participants: 100 (WeChat API limit). The collection page is shown after the API returns, and participants pay via WeChat social transfer, which is outside the order system’s payment flow.

Cancellation and Refund

Two cancellation paths exist:

Manual cancel (pre‑payment) : User cancels while order is pending; the group status becomes “cancelled”, all member statuses change to “cancelled”, and Redis cache is cleared.

Timeout cancel (payment timeout) : An order that remains unpaid triggers a delayed‑task consumer; the group status becomes “expired” and member statuses become “expired”.

Cancellation uses distinct status codes (3 = cancelled, 4 = expired) for analytics.

Refunds follow the normal order‑refund flow and return money to the initiator’s account. Refunds are not automatically routed to participants because they never paid the order directly; participants must be reimbursed manually via WeChat.

State Machine

Group status values:

0 = Selecting

1 = Submitted

2 = Completed (order paid)

3 = Cancelled

4 = Expired (48 h without submission or payment timeout)

Member status values:

0 = Not selected

1 = Selecting

2 = Confirmed

3 = Completed

4 = Cancelled

5 = Expired

6 = Exited

The only linkage between the two layers is that order payment changes the group status from “submitted” to “completed” and triggers cost splitting.

Production Constraints

Group expires 48 hours after creation (prevents zombie groups).

Group‑collection participant limit: 100 (WeChat API).

No limit on join count; limit enforced only at collection.

One‑user‑one‑group per channel to avoid data confusion.

Initiator cannot exit; they can only cancel the group.

After order generation, the group cannot be unlocked.

Group‑collection button appears only in full‑WeChat mini‑program flow.

Selection data stored in Redis; persisted only at order submission.

Cancellation reason recorded in a separate table for operational analysis.

Summary

Although a group‑order system sounds like it would heavily remodel order and payment domains, the actual code changes are minimal—adding an enum value, a relationship table, and a single callback line. The real engineering effort lies in the real‑time collaboration layer (Redis caching, multi‑channel support) and precise cost‑splitting logic (BigDecimal handling, edge‑case adjustments). Implemented correctly, the feature delivers a smooth collaborative purchasing experience without disrupting existing order or payment pipelines.

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.

backendredisMySQLgroup-orderpayment-splitting
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.