Affiliate System
The Affiliate System is the core feature of the Market Module, providing a comprehensive referral and commission management system. This document details the implementation, data flow, and usage patterns for developers working with the affiliate functionality.
System Architecture
The affiliate system is built on an event-driven architecture that processes user interactions across multiple modules:
User Registration → Affiliate Policy Creation
Order Payment → Reward Calculation → Balance Update
Invite Code Generation → Code Validation → Referral Tracking
Core Components
1. Affiliate Policy (affiliate_policy)
Each user has an affiliate policy that defines their participation in the referral system:
- Reward Rate: Commission percentage (e.g., 0.1 = 10%)
- Trigger Time Per User: Maximum times a single referred user can trigger rewards
- Invitation Rights: Whether the user can create invite codes
- Referral Chain: Who invited this user (if anyone)
2. Invite Codes (invite_code)
Users generate unique codes to invite new customers:
- 8-character alphanumeric codes: Generated using secure randomization
- Active status: Codes can be deactivated without deletion
- User limits: Configurable maximum codes per user
- Collision handling: Automatic retry on code conflicts
3. Affiliate Statistics (affiliate_stats)
Real-time tracking of affiliate performance:
- Total Revenue: Cumulative commission earned
- Withdrawn Revenue: Amount already transferred to user balance
- Referral Count: Number of users successfully referred
- Available Balance:
total_revenue - withdrawn_revenue
4. Affiliate Relations (affiliate_relation)
Historical record of all reward transactions for audit and analytics.
Data Flow
1. User Registration Flow
When a new user registers with an invite code:
// Hook: RegisterHook in hooks/register.rs
impl Processor<UserRegisterEvent, Result<(), Error>> for RegisterHook {
async fn process(&self, ev: UserRegisterEvent) -> Result<(), Error> {
// 1. Load affiliate configuration
let cfg = self.load_config().await?;
// 2. Validate and find invite code
let mut invited_by = None;
if let Some(code) = ev.referral_code.clone() {
if let Some(inv) = self.db.process(FindInviteCodeByCode { code }).await? {
invited_by = Some(inv.user_id);
}
}
// 3. Create affiliate policy for new user
self.db.process(CreateAffiliatePolicy {
user_id: ev.user_id,
trigger_time_per_user: cfg.default_trigger_time_per_user,
cannot_invite: false,
rate: cfg.default_reward_rate,
invited_by,
}).await?;
}
}
2. Order Payment Flow
When a referred user makes a purchase:
// Hook: OrderHook in hooks/orders.rs
impl Processor<OrderPaidEvent, Result<(), Error>> for OrderHook {
async fn process(&self, event: OrderPaidEvent) -> Result<(), Error> {
// 1. Skip account balance payments (no commission)
if matches!(order.payment_method, Some(PaymentMethod::AccountBalance)) {
return Ok(());
}
// 2. Find invitee's affiliate policy
let invitee_policy = self.db.process(FindAffiliatePolicyByUser {
user_id: event.user_id,
}).await?;
// 3. Check if user was referred
if let Some(inviter) = invitee_policy.invited_by {
// 4. Verify trigger limits
let count = self.db.process(CountPaidOrders {
user_id: event.user_id,
}).await?;
if count <= inviter_policy.trigger_time_per_user as i64 {
// 5. Publish reward event
let reward = AffiliateReward {
inviter,
invitee: event.user_id,
order_id: event.order_id,
};
reward.send(&self.mq).await?;
}
}
}
}
3. Reward Processing Flow
The system processes affiliate rewards asynchronously:
// Hook: RewardHook in hooks/reward.rs
impl Processor<AffiliateReward, Result<(), Error>> for RewardHook {
async fn process(&self, ev: AffiliateReward) -> Result<(), Error> {
// 1. Load inviter's policy for commission rate
let policy = self.db.process(FindAffiliatePolicyByUser {
user_id: ev.inviter,
}).await?;
// 2. Calculate reward amount
let order = self.db.process(FindOrderByIdOnly {
order_id: ev.order_id,
}).await?;
let reward = order.total_amount * policy.rate;
// 3. Create affiliate relation and update stats
self.db.process(CreateAffiliateRelationAndReward {
from: ev.invitee,
to: ev.inviter,
order_id: ev.order_id,
rate: policy.rate,
reward,
}).await?;
}
}
Service Implementation
The AffiliateService provides the main business logic using the Processor pattern [[memory:6079830]]:
Core Operations
Get Affiliate Statistics
pub struct GetMyAffiliateStats {
pub user_id: Uuid,
}
// Returns: MyAffiliateStats with policy, stats, and invite codes
Invite Code Management
pub struct CreateMyInviteCode { pub user_id: Uuid }
pub struct DeleteMyInviteCode { pub user_id: Uuid, pub code_id: i64 }
pub struct ListMyInviteCodes { pub user_id: Uuid }
Reward Withdrawal
pub struct WithdrawAffiliateReward {
pub user_id: Uuid,
pub amount: Decimal,
}
The withdrawal operation is atomic and includes:
- Balance verification (sufficient available rewards)
- Affiliate stats update (increase withdrawn amount)
- User balance credit
- Transaction logging
Configuration
The affiliate system is configured through AffConfig:
pub struct AffConfig {
pub max_invite_code_per_user: i32, // Default: 10
pub default_reward_rate: Decimal, // Default: 0.1 (10%)
pub default_trigger_time_per_user: i32, // Default: 3
}
Configuration is stored in Redis under the key affiliate and loaded dynamically by services.
Database Operations
All database operations use the Processor pattern with strongly-typed inputs and outputs:
Affiliate Policy Operations
FindAffiliatePolicyByUser: Retrieve user’s policyCreateAffiliatePolicy: Initialize policy for new users
Invite Code Operations
CreateInviteCode: Generate new code with collision handlingListInviteCodesByUser: Get user’s active codesCountActiveInviteCodesByUser: Check against limitsSoftDeleteInviteCode: Deactivate codeFindInviteCodeByCode: Validate codes during registration
Statistics Operations
FindAffiliateStatsByUser: Get performance metricsAddAffiliateReward: Credit new rewardsWithdrawAffiliateRewardAtomic: Transfer to balance
gRPC API
The affiliate system exposes user-facing APIs through the Market service:
service Market {
rpc GetAffiliateStats (GetAffiliateStatsRequest) returns (GetAffiliateStatsReply);
rpc ListInviteCodes (ListInviteCodesRequest) returns (ListInviteCodesReply);
rpc CreateInviteCode (CreateInviteCodeRequest) returns (CreateInviteCodeReply);
rpc DeleteInviteCode (DeleteInviteCodeRequest) returns (DeleteInviteCodeReply);
rpc WithdrawAffiliateReward (WithdrawAffiliateRewardRequest) returns (WithdrawAffiliateRewardReply);
}
All APIs authenticate users and operate on their data only.
Event Integration
The system integrates with other modules through events:
Consumed Events
UserRegisterEvent(from auth module): Initialize affiliate policiesOrderPaidEvent(from shop module): Trigger reward calculations
Published Events
AffiliateReward(internal): Process commission calculations
Message Queues
helium_auth_user_register_market: User registration processinghelium_shop_order_paid: Order payment processinghelium_market_affiliate_reward: Internal reward processing
Business Rules
Reward Eligibility
- Payment Method: Only real payments trigger rewards (no account balance)
- Trigger Limits: Each referred user can only trigger rewards N times
- Active Codes: Only active invite codes establish referral relationships
- Valid Orders: Rewards only process for successfully paid orders
Security Considerations
- Atomic Withdrawals: Balance checks and updates are transactional
- Code Uniqueness: Invite codes are globally unique with retry logic
- Rate Validation: Commission rates are validated and stored as decimals
- Audit Trail: All reward transactions are recorded permanently
Error Handling
The system uses comprehensive error handling:
- Invalid Input: Invalid amounts, missing data
- Business Logic: Insufficient balance, code limits exceeded
- System Errors: Database failures, message queue issues
- Not Found: Missing policies, orders, or codes
Monitoring & Observability
Key metrics and logs for monitoring:
- Successful referral registrations
- Reward calculation events
- Withdrawal success/failure rates
- Invite code generation patterns
- Commission distribution analytics
Development Guidelines
When working with the affiliate system:
- Use Processors: All business logic must use the Processor pattern [[memory:6079830]]
- Avoid Static Lifetimes: Use owned connection types like
RedisConnection[[memory:7107428]] - Handle Decimals Carefully: Use
rust_decimal::Decimalfor all monetary calculations - Test Event Flows: Verify end-to-end event processing in integration tests
- Monitor Performance: Track database query performance for statistics operations
Common Integration Patterns
Adding New Reward Triggers
- Create event structure with routing information
- Implement event processor with business logic
- Register message queue and routing
- Add appropriate error handling and logging
Extending Statistics
- Update
AffiliateStatsentity - Modify aggregation queries
- Update API response structures
- Implement migration for existing data
This comprehensive documentation provides developers with the knowledge needed to maintain, extend, and troubleshoot the affiliate system effectively.