Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. Balance verification (sufficient available rewards)
  2. Affiliate stats update (increase withdrawn amount)
  3. User balance credit
  4. 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 policy
  • CreateAffiliatePolicy: Initialize policy for new users

Invite Code Operations

  • CreateInviteCode: Generate new code with collision handling
  • ListInviteCodesByUser: Get user’s active codes
  • CountActiveInviteCodesByUser: Check against limits
  • SoftDeleteInviteCode: Deactivate code
  • FindInviteCodeByCode: Validate codes during registration

Statistics Operations

  • FindAffiliateStatsByUser: Get performance metrics
  • AddAffiliateReward: Credit new rewards
  • WithdrawAffiliateRewardAtomic: 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 policies
  • OrderPaidEvent (from shop module): Trigger reward calculations

Published Events

  • AffiliateReward (internal): Process commission calculations

Message Queues

  • helium_auth_user_register_market: User registration processing
  • helium_shop_order_paid: Order payment processing
  • helium_market_affiliate_reward: Internal reward processing

Business Rules

Reward Eligibility

  1. Payment Method: Only real payments trigger rewards (no account balance)
  2. Trigger Limits: Each referred user can only trigger rewards N times
  3. Active Codes: Only active invite codes establish referral relationships
  4. Valid Orders: Rewards only process for successfully paid orders

Security Considerations

  1. Atomic Withdrawals: Balance checks and updates are transactional
  2. Code Uniqueness: Invite codes are globally unique with retry logic
  3. Rate Validation: Commission rates are validated and stored as decimals
  4. 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:

  1. Use Processors: All business logic must use the Processor pattern [[memory:6079830]]
  2. Avoid Static Lifetimes: Use owned connection types like RedisConnection [[memory:7107428]]
  3. Handle Decimals Carefully: Use rust_decimal::Decimal for all monetary calculations
  4. Test Event Flows: Verify end-to-end event processing in integration tests
  5. Monitor Performance: Track database query performance for statistics operations

Common Integration Patterns

Adding New Reward Triggers

  1. Create event structure with routing information
  2. Implement event processor with business logic
  3. Register message queue and routing
  4. Add appropriate error handling and logging

Extending Statistics

  1. Update AffiliateStats entity
  2. Modify aggregation queries
  3. Update API response structures
  4. Implement migration for existing data

This comprehensive documentation provides developers with the knowledge needed to maintain, extend, and troubleshoot the affiliate system effectively.