I'm designing an affiliate/referral feature for an existing delivery platform and I'd like some architectural feedback before implementing it.
The business requirement sounds simple:
Customer A refers Customer B.
If B registers using A's referral and later sends parcels, A receives a configurable commission for every successfully delivered parcel.
For example:
- Affiliate A → $1.00 / delivered parcel
- Affiliate B → $0.50 / delivered parcel
The interesting part is handling the financial side correctly.
My current idea is:
Parcel status flow
Carrier Webhook/Admin → Parcel Status Service → DB + Status History → Outbox → Redis Queue → Affiliate Worker → Commission Engine → Ledger
When a parcel transitions:
IN_TRANSIT → DELIVERED
a ParcelDelivered event is produced.
The affiliate consumer then:
- checks idempotency;
- finds whether the parcel owner was referred;
- checks affiliate eligibility;
- resolves the commission policy that applied;
- creates an immutable earning transaction.
So instead of doing:
affiliate.balance += $1
I'm considering a ledger:
Parcel #100 delivered +$1.00
Parcel #101 delivered +$1.00
Parcel #100 reversed -$1.00
Payout -$50.00
The ledger would be the source of truth, while the displayed balance could eventually be a cached/materialized value.
The reversal problem
One requirement I'm particularly thinking about is:
IN_TRANSIT → DELIVERED → RETURNED
If delivery creates:
+$1.00
I don't want to delete that transaction when the parcel is later returned.
Instead I'd generate:
+$1.00 DELIVERY_COMMISSION
-$1.00 COMMISSION_REVERSAL
This keeps the financial history auditable.
The same raises another interesting case:
IN_TRANSIT → DELIVERED → RETURNED → DELIVERED
Should this be modeled as two separate delivery cycles, with:
+$1 → -$1 → +$1
rather than treating parcel_id itself as the idempotency key?
Where I'm uncertain: Redis + Outbox
I'm already using Redis queues in the application.
My current thinking is to persist the business event using an outbox in the same DB transaction as the parcel status transition, then asynchronously publish/process it through Redis.
The reason is that I don't want this failure:
Parcel successfully becomes DELIVERED → application crashes before affiliate event is queued → commission is permanently lost
But I'm wondering whether I'm adding unnecessary infrastructure for this use case.
So I'd especially like opinions on:
- Would you use an outbox + Redis worker here, or keep commission creation synchronous with the parcel transaction?
- Would Redis be sufficient for this workload, or would you keep the financial event processing DB-backed?
- Does an immutable ledger + compensating reversal make sense here, or is there a simpler model you'd prefer?
- How would you model idempotency for
Delivered → Returned → Deliveredwithout blocking the second legitimate delivery? - Would you model the affiliate feature as its own domain/module while keeping affiliates as normal customer accounts with an enabled capability?
- Would you introduce a pending commission period before making earnings withdrawable, rather than immediately making a delivered-parcel commission available?
- What race conditions or failure modes am I missing?
- At what point does this design become overengineering for a modular monolith?
I'm intentionally trying to keep this inside the existing application rather than turning it into microservices.
The priorities are financial correctness, idempotency, auditability and keeping the design as simple as possible.
Architecture diagram attached.
I'd appreciate criticism of the design, especially places where I'm introducing complexity without getting enough reliability in return.
https://i.redd.it/90nbl772c2sh1.png
Source: r/softwarearchitecture · by /u/Fuzzy_Composer6681
