Created
January 30, 2026 20:18
-
-
Save agoiabel/0f16b459d7022353d30a6b6677491502 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Community Commission Framework | |
| > A complete guide to the community commission system in Propel - for Project Managers and Developers. | |
| --- | |
| ## Quick Links | |
| | Audience | Section | | |
| |----------|---------| | |
| | **Project Managers** | [Executive Summary](#executive-summary) \| [User Guide](#user-guide-for-project-managers) \| [FAQ](#frequently-asked-questions) | | |
| | **Developers** | [Technical Architecture](#technical-architecture) \| [API Reference](#api-endpoints) \| [Integration Guide](#integration-guide) \| [Testing Guide](#testing-guide) | | |
| --- | |
| # Part 1: For Project Managers | |
| ## Executive Summary | |
| ### What is the Commission Framework? | |
| The Commission Framework is a revenue-sharing system that allows **community administrators** to earn money when their community members use Propel's services. Think of it as a referral bonus - when community members take loans, get jobs, or complete tasks, the community earns a percentage of Propel's earnings. | |
| ### How Does It Work? | |
| ``` | |
| Member Activity --> Propel Earns --> Community Gets Commission | |
| | | | | |
| (Loan, Job, (Service fees, (20% of Propel's | |
| Quest, etc.) interest, etc.) earnings) | |
| ``` | |
| **Example:** | |
| 1. A community member takes a loan through Propel | |
| 2. When they repay, Propel earns interest (e.g., NGN 50,000) | |
| 3. The community automatically earns 20% commission (NGN 10,000) | |
| 4. Once the loan is fully repaid, the community can withdraw this money | |
| ### Key Benefits | |
| | Benefit | Description | | |
| |---------|-------------| | |
| | **Passive Income** | Communities earn money automatically from member activities | | |
| | **Multiple Sources** | Earn from loans, job placements, quests, and referrals | | |
| | **Easy Withdrawals** | Direct bank transfer to community accounts | | |
| | **Full Transparency** | Dashboard shows all earnings in real-time | | |
| --- | |
| ## User Guide for Project Managers | |
| ### Accessing the Commission Dashboard | |
| 1. **Who can access:** Community Admins and Managers only | |
| 2. **URL:** `/community-commissions` | |
| 3. **Navigation:** Go to your community dashboard > Click "Commissions" in the sidebar | |
| ### Understanding the Dashboard | |
| The commission dashboard has four main sections: | |
| #### 1. Stats Cards (Top Section) | |
| | Card | What It Shows | | |
| |------|---------------| | |
| | **Wallet Balance** | Money available for withdrawal RIGHT NOW | | |
| | **Potential Earnings** | Money earned but not yet available (waiting for loans to be fully repaid) | | |
| | **Total Withdrawn** | All money withdrawn to date | | |
| | **Commission Rate** | The percentage your community earns (typically 20%) | | |
| #### 2. Earnings by Type (Breakdown Section) | |
| Shows how much you've earned from each source: | |
| - **Loans** - From member loan repayments | |
| - **Jobs** - From member job placements | |
| - **Quests** - From member quest completions | |
| - **Referrals** - From member referrals | |
| Each type shows: | |
| - **Available** - Ready to withdraw | |
| - **Pending** - Earned but waiting (e.g., loan not fully repaid yet) | |
| #### 3. Commission History (Table) | |
| A detailed list of all commission records showing: | |
| - Member name | |
| - Type (Loan, Job, etc.) | |
| - Amount earned | |
| - Status (Pending, Available, Withdrawn) | |
| - Date | |
| **Filtering Options:** | |
| - By earning type (Loans only, Jobs only, etc.) | |
| - By status | |
| - By date range | |
| #### 4. Actions | |
| | Button | What It Does | | |
| |--------|--------------| | |
| | **Request Withdrawal** | Withdraw money to your bank account | | |
| | **View Commission Rules** | See how commissions are calculated | | |
| ### Making a Withdrawal | |
| **Prerequisites:** | |
| - Must have funds in "Wallet Balance" (Available balance) | |
| - Must have a Nigerian bank account | |
| **Steps:** | |
| 1. Click **"Request Withdrawal"** | |
| 2. Enter the amount to withdraw (or click "Max" for full balance) | |
| 3. Select your bank from the dropdown | |
| 4. Enter your 10-digit account number | |
| 5. Wait for automatic account verification | |
| 6. Review the summary (shows EUR amount and NGN equivalent) | |
| 7. Click **"Request Withdrawal"** | |
| **What Happens Next:** | |
| - Status changes to "Processing" | |
| - Funds are transferred via Paystack (1-2 business days) | |
| - You'll receive the NGN amount in your bank account | |
| ### Commission Status Explained | |
| | Status | Meaning | When It Happens | | |
| |--------|---------|-----------------| | |
| | **PENDING** | Earned but locked | Loan not fully repaid yet | | |
| | **AVAILABLE** | Ready to withdraw | Loan fully repaid, or instant for jobs/quests | | |
| | **WITHDRAWN** | Already paid out | Included in a withdrawal | | |
| **Important:** Loan commissions become AVAILABLE only after the member fully repays their loan. This protects against defaults. | |
| ### Commission Rates | |
| Default rates (may vary by community): | |
| | Earning Type | Commission Rate | Example | | |
| |--------------|-----------------|---------| | |
| | Loan Repayments | 20% | Propel earns EUR 100 interest → Community gets EUR 20 | | |
| | Job Placements | 15% | Propel earns EUR 500 placement fee → Community gets EUR 75 | | |
| | Quests | 10% | Propel earns EUR 50 from quest → Community gets EUR 5 | | |
| | Referrals | 5% | Propel earns EUR 100 from referral → Community gets EUR 5 | | |
| ### Currently Active Commission Types | |
| > **Important:** Not all commission types are currently active. See the table below for what's currently earning commissions. | |
| | Earning Type | Status | Notes | | |
| |--------------|--------|-------| | |
| | **Loan Repayments** | **Active** | Commissions are automatically recorded when members repay loans | | |
| | Job Placements | *Coming Soon* | Framework ready, pending integration with hiring workflow | | |
| | Quests | *Coming Soon* | Framework ready, pending integration with quest completion | | |
| | Referrals | *Coming Soon* | Framework ready, pending integration with referral system | | |
| **What does "Coming Soon" mean?** | |
| - The system is fully capable of tracking these commission types | |
| - The dashboard will display them once they're activated | |
| - Propel is working on integrating these into the respective workflows | |
| --- | |
| ## Frequently Asked Questions | |
| ### General Questions | |
| **Q: How often can I withdraw?** | |
| A: Anytime, as long as you have available balance. | |
| **Q: Is there a minimum withdrawal amount?** | |
| A: The minimum is typically EUR 10 (varies based on bank transfer limits). | |
| **Q: Why is my commission showing as "Pending"?** | |
| A: For loans, commissions are pending until the member fully repays their loan. This protects the community from earning commissions on defaulted loans. | |
| **Q: How long does a withdrawal take?** | |
| A: 1-2 business days via Paystack bank transfer. | |
| **Q: Can regular members see the commission dashboard?** | |
| A: No, only Community Admins and Managers can access it. | |
| ### Technical Questions | |
| **Q: What currency are commissions stored in?** | |
| A: All commissions are stored in EUR for consistency. Local currency amounts are shown for reference. | |
| **Q: What happens if a withdrawal fails?** | |
| A: The funds are returned to your wallet balance, and you can retry with corrected bank details. | |
| **Q: Can I change the commission rate?** | |
| A: Commission rates are set by Propel. Contact support for rate adjustments. | |
| --- | |
| ## Propel Admin Management | |
| ### Commission Settings Management (Propel Admin Only) | |
| **Who can access:** Propel Back Office Admins only | |
| **Purpose:** Allows Propel administrators to view and configure commission rates for all communities across the platform. | |
| #### Accessing Admin Commission Settings | |
| 1. Log in as a Propel Back Office Admin | |
| 2. Navigate to **Financial Services** in the admin sidebar | |
| 3. Click **"Commission Settings"** | |
| #### Features | |
| **View All Communities** | |
| - See all communities and their commission rates in a searchable table | |
| - Filter by earning type (LOAN, JOB, QUEST, REFERRAL) | |
| - Search communities by name | |
| **Individual Rate Management** | |
| - Edit commission rates for specific community/earning type combinations | |
| - View rate change history for each community | |
| - See when rates were last updated and by whom | |
| **Bulk Updates** | |
| - Select multiple communities | |
| - Update commission rates for a specific earning type across all selected communities | |
| - Efficient management of rate changes | |
| **Rate History** | |
| - View historical commission rate changes for any community | |
| - Track who made changes and when | |
| - See effective dates for each rate | |
| #### Default Commission Rates | |
| Communities without custom settings use these defaults: | |
| | Earning Type | Default Rate | | |
| |--------------|--------------| | |
| | LOAN | 20% | | |
| | JOB | 0% (pending activation) | | |
| | QUEST | 0% (pending activation) | | |
| | REFERRAL | 0% (pending activation) | | |
| --- | |
| # Part 2: For Developers | |
| ## Technical Architecture | |
| ### System Overview | |
| ``` | |
| +-------------------------------------------------------------------+ | |
| | Frontend (Vue.js) | | |
| | +-------------+ +-------------+ +-------------+ +--------------+ | | |
| | | Dashboard | | Stats Cards | |History Table| | Modals | | | |
| | +-------------+ +-------------+ +-------------+ +--------------+ | | |
| +-------------------------------------------------------------------+ | |
| | | |
| v | |
| +-------------------------------------------------------------------+ | |
| | API Layer (Laravel) | | |
| | +---------------------------------------------------------------+ | | |
| | | CommunityCommissionController | | | |
| | | * stats() * history() * withdraw() * getSettings() | | | |
| | +---------------------------------------------------------------+ | | |
| +-------------------------------------------------------------------+ | |
| | | |
| v | |
| +-------------------------------------------------------------------+ | |
| | Service Layer | | |
| | +---------------+ +--------------+ +------------------------+ | | |
| | |CommissionSvc | |LoanCommission| |CommissionWithdrawalSvc | | | |
| | | (Generic) | | Service | | | | | |
| | +---------------+ +--------------+ +------------------------+ | | |
| | | | | | | |
| | +---------------+ +--------------+ | | |
| | |JobCommission | |QuestCommission| (Future services) | | |
| | | Service | | Service | | | |
| | +---------------+ +--------------+ | | |
| +-------------------------------------------------------------------+ | |
| | | |
| v | |
| +-------------------------------------------------------------------+ | |
| | Data Layer | | |
| | +-------------------+ +-------------------+ +------------------+ | | |
| | |CommunityCommission| |CommunityCommission| |CommunityCommission| | | |
| | | Model | | Setting | | Withdrawal | | | |
| | +-------------------+ +-------------------+ +------------------+ | | |
| +-------------------------------------------------------------------+ | |
| ``` | |
| ### Key Design Decisions | |
| 1. **Single Table for All Commission Types**: Using `earning_type` column for differentiation | |
| 2. **Polymorphic Sources**: `source_type` and `source_id` link to any model (Loan, ProjectApplication, etc.) | |
| 3. **EUR as Base Currency**: All amounts stored in EUR for consistency | |
| 4. **FIFO Withdrawals**: Oldest available commissions are marked as withdrawn first | |
| 5. **Backward Compatibility**: `CommunityLoanCommission` model uses global scope for legacy code | |
| 6. **Centralized Rate Management**: Propel admins can configure rates for all communities from a single interface | |
| 7. **Rate History Tracking**: All rate changes are audited with timestamps and admin attribution | |
| --- | |
| ## Database Schema | |
| ### community_commissions | |
| The main table storing all commission records. | |
| ```sql | |
| CREATE TABLE community_commissions ( | |
| id BIGINT PRIMARY KEY AUTO_INCREMENT, | |
| community_id BIGINT NOT NULL, | |
| user_id BIGINT NOT NULL, | |
| earning_type VARCHAR(255) DEFAULT 'LOAN', | |
| loan_id BIGINT NULL, | |
| -- Financial amounts (EUR) | |
| repayment_amount_eur DECIMAL(15,2), | |
| propel_earnings_eur DECIMAL(15,2), | |
| commission_amount_eur DECIMAL(15,2), | |
| commission_rate DECIMAL(5,4), | |
| -- Local currency reference | |
| repayment_amount_local DECIMAL(15,2) NULL, | |
| commission_amount_local DECIMAL(15,2) NULL, | |
| local_currency VARCHAR(3) DEFAULT 'NGN', | |
| -- Status | |
| status ENUM('PENDING', 'AVAILABLE', 'WITHDRAWN') DEFAULT 'PENDING', | |
| -- Metadata | |
| provider VARCHAR(255) NULL, | |
| provider_transaction_id VARCHAR(255) NULL, | |
| source_type VARCHAR(255) NULL, | |
| source_id BIGINT NULL, | |
| metadata JSON NULL, | |
| description VARCHAR(255) NULL, | |
| repayment_date TIMESTAMP NULL, | |
| created_at TIMESTAMP, | |
| updated_at TIMESTAMP, | |
| deleted_at TIMESTAMP NULL, | |
| INDEX idx_community_earning_status (community_id, earning_type, status), | |
| FOREIGN KEY (community_id) REFERENCES communities(id) ON DELETE CASCADE, | |
| FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE | |
| ); | |
| ``` | |
| ### community_commission_settings | |
| Stores configurable commission rates per community and earning type. | |
| ```sql | |
| CREATE TABLE community_commission_settings ( | |
| id BIGINT PRIMARY KEY AUTO_INCREMENT, | |
| community_id BIGINT NOT NULL, | |
| earning_type VARCHAR(255) NOT NULL, | |
| commission_rate DECIMAL(5,4) NOT NULL, | |
| is_active BOOLEAN DEFAULT TRUE, | |
| effective_from TIMESTAMP NULL, | |
| effective_to TIMESTAMP NULL, | |
| set_by BIGINT NULL, | |
| created_at TIMESTAMP, | |
| updated_at TIMESTAMP, | |
| INDEX idx_community_earning_active (community_id, earning_type, is_active), | |
| FOREIGN KEY (community_id) REFERENCES communities(id) ON DELETE CASCADE | |
| ); | |
| ``` | |
| ### community_commission_withdrawals | |
| Tracks withdrawal requests and their status. | |
| ```sql | |
| CREATE TABLE community_commission_withdrawals ( | |
| id BIGINT PRIMARY KEY AUTO_INCREMENT, | |
| community_id BIGINT NOT NULL, | |
| requested_by BIGINT NOT NULL, | |
| -- Amounts | |
| amount_eur DECIMAL(15,2) NOT NULL, | |
| amount_local DECIMAL(15,2) NULL, | |
| currency VARCHAR(3) DEFAULT 'NGN', | |
| -- Bank details | |
| bank_name VARCHAR(255) NOT NULL, | |
| bank_code VARCHAR(255) NOT NULL, | |
| account_number VARCHAR(255) NOT NULL, | |
| account_name VARCHAR(255) NOT NULL, | |
| -- Status | |
| status ENUM('PENDING', 'PROCESSING', 'SUCCESSFUL', 'FAILED', 'CANCELLED') DEFAULT 'PENDING', | |
| -- Provider | |
| provider VARCHAR(255) DEFAULT 'paystack', | |
| provider_reference VARCHAR(255) NULL, | |
| transfer_code VARCHAR(255) NULL, | |
| failure_reason TEXT NULL, | |
| created_at TIMESTAMP, | |
| updated_at TIMESTAMP, | |
| deleted_at TIMESTAMP NULL, | |
| INDEX idx_community_status (community_id, status), | |
| FOREIGN KEY (community_id) REFERENCES communities(id) ON DELETE CASCADE, | |
| FOREIGN KEY (requested_by) REFERENCES users(id) ON DELETE CASCADE | |
| ); | |
| ``` | |
| --- | |
| ## Models | |
| ### CommunityCommission | |
| **Location:** `app/Models/CommunityCommission.php` | |
| ```php | |
| use App\Models\CommunityCommission; | |
| // Constants | |
| CommunityCommission::STATUS_PENDING // 'PENDING' | |
| CommunityCommission::STATUS_AVAILABLE // 'AVAILABLE' | |
| CommunityCommission::STATUS_WITHDRAWN // 'WITHDRAWN' | |
| CommunityCommission::EARNING_TYPE_LOAN // 'LOAN' | |
| CommunityCommission::EARNING_TYPE_JOB // 'JOB' | |
| CommunityCommission::EARNING_TYPE_QUEST // 'QUEST' | |
| CommunityCommission::EARNING_TYPE_REFERRAL // 'REFERRAL' | |
| // Relationships | |
| $commission->community; // Community model | |
| $commission->user; // User who generated commission | |
| $commission->loan; // Loan model (if type is LOAN) | |
| $commission->source; // Polymorphic source (Loan, ProjectApplication, etc.) | |
| // Scopes | |
| CommunityCommission::forCommunity($id)->get(); | |
| CommunityCommission::forEarningType('LOAN')->get(); | |
| CommunityCommission::available()->get(); | |
| CommunityCommission::pending()->get(); | |
| // Accessors | |
| $commission->earning_type_label; // "Loan Repayment", "Job Placement", etc. | |
| ``` | |
| ### CommunityLoanCommission (Backward Compatible) | |
| **Location:** `app/Models/CommunityLoanCommission.php` | |
| Extends `CommunityCommission` with a global scope for `earning_type = 'LOAN'`. | |
| ```php | |
| use App\Models\CommunityLoanCommission; | |
| // Automatically scoped to LOAN type only | |
| $loanCommissions = CommunityLoanCommission::where('community_id', $id)->get(); | |
| // Creating automatically sets earning_type = 'LOAN' | |
| CommunityLoanCommission::create([...]); | |
| ``` | |
| ### CommunityCommissionSetting | |
| **Location:** `app/Models/CommunityCommissionSetting.php` | |
| Manages commission rate configurations for each community and earning type. Supports rate history and audit tracking. | |
| ```php | |
| use App\Models\CommunityCommissionSetting; | |
| // Constants | |
| CommunityCommissionSetting::DEFAULT_LOAN_COMMISSION_RATE; // 0.20 (20%) | |
| // Get active rate for a community and earning type | |
| $rate = CommunityCommissionSetting::getActiveRate($communityId, 'LOAN'); | |
| // Returns: 0.20 (20%), or default if not set | |
| // Set a new rate (used by admin interface) | |
| CommunityCommissionSetting::setRate($communityId, 'LOAN', 0.25, $userId); | |
| // - Deactivates previous rate | |
| // - Creates new active rate | |
| // - Records who made the change | |
| // Relationships | |
| $setting->community; // Community model | |
| $setting->createdBy; // User who created | |
| $setting->updatedBy; // User who last updated | |
| ``` | |
| **How Rate Changes Work:** | |
| 1. When a new rate is set, the previous active rate is deactivated | |
| 2. A new record is created with `is_active = true` | |
| 3. The old record is updated with `effective_until = now()` | |
| 4. All changes are tracked with timestamps and user attribution | |
| ### CommunityCommissionWithdrawal | |
| **Location:** `app/Models/CommunityCommissionWithdrawal.php` | |
| ```php | |
| use App\Models\CommunityCommissionWithdrawal; | |
| // Constants | |
| CommunityCommissionWithdrawal::STATUS_PENDING | |
| CommunityCommissionWithdrawal::STATUS_PROCESSING | |
| CommunityCommissionWithdrawal::STATUS_SUCCESSFUL | |
| CommunityCommissionWithdrawal::STATUS_FAILED | |
| // Relationships | |
| $withdrawal->community; | |
| $withdrawal->requestedBy; // User who requested | |
| // Methods | |
| $withdrawal->markAsSuccessful($reference); | |
| $withdrawal->markAsFailed($reason); | |
| ``` | |
| --- | |
| ## Services | |
| ### CommissionService (Base Service) | |
| **Location:** `app/Classes/CommissionService.php` | |
| ```php | |
| use App\Classes\CommissionService; | |
| $service = app(CommissionService::class); | |
| // Record a commission | |
| $commission = $service->recordCommission( | |
| communityId: 123, | |
| userId: 456, | |
| earningType: 'JOB', | |
| propelEarningsLocal: 50000, | |
| localCurrency: 'NGN', | |
| source: $projectApplication, | |
| options: [ | |
| 'transaction_amount' => 100000, | |
| 'provider' => 'propel', | |
| 'transaction_id' => 'TXN123', | |
| 'metadata' => ['project_id' => 789], | |
| 'description' => 'Job placement commission', | |
| 'status' => CommunityCommission::STATUS_AVAILABLE, | |
| ] | |
| ); | |
| // Get statistics | |
| $stats = $service->getStats($communityId, $earningType = null); | |
| // Get earnings breakdown by type | |
| $breakdown = $service->getEarningsByType($communityId); | |
| // Balance methods | |
| $available = $service->getAvailableBalance($communityId); | |
| $pending = $service->getPotentialEarnings($communityId); | |
| $withdrawn = $service->getTotalWithdrawn($communityId); | |
| ``` | |
| ### LoanCommissionService | |
| **Location:** `app/Classes/LoanCommissionService.php` | |
| ```php | |
| use App\Classes\LoanCommissionService; | |
| $service = app(LoanCommissionService::class); | |
| // Record commission from a loan repayment | |
| $commission = $service->recordRepaymentCommission( | |
| loan: $loan, | |
| repaymentAmount: 5000, | |
| localCurrency: 'NGN', | |
| provider: 'klump', | |
| transactionId: 'KLP_123', | |
| metadata: ['webhook_event' => 'repayment.success'] | |
| ); | |
| // Finalize commissions when loan is fully repaid | |
| $service->finalizeCommissionsForLoan($loan); | |
| ``` | |
| ### CommunityCommissionWithdrawalService | |
| **Location:** `app/Classes/CommunityCommissionWithdrawalService.php` | |
| ```php | |
| use App\Classes\CommunityCommissionWithdrawalService; | |
| $service = app(CommunityCommissionWithdrawalService::class); | |
| // Process a withdrawal request | |
| $withdrawal = $service->processWithdrawal( | |
| community: $community, | |
| requestedBy: $user, | |
| amountEur: 100.00, | |
| bankDetails: [ | |
| 'bank_name' => 'First Bank', | |
| 'bank_code' => '011', | |
| 'account_number' => '0123456789', | |
| 'account_name' => 'John Doe', | |
| ] | |
| ); | |
| // Handle Paystack webhook | |
| $service->handleTransferWebhook($reference, $status, $reason); | |
| ``` | |
| --- | |
| ## API Endpoints | |
| All endpoints are prefixed with `/api/community/{communityId}/commissions` | |
| ### GET /stats | |
| Get commission statistics for a community. | |
| **Query Parameters:** | |
| - `earning_type` (optional): Filter by type (LOAN, JOB, QUEST, REFERRAL) | |
| **Response:** | |
| ```json | |
| { | |
| "data": { | |
| "wallet_balance_eur": 150.00, | |
| "potential_earnings_eur": 75.00, | |
| "total_withdrawn_eur": 500.00, | |
| "total_earnings_eur": 725.00, | |
| "current_commission_rate": 0.20, | |
| "commission_rates": { | |
| "LOAN": 0.20, | |
| "JOB": 0.15 | |
| }, | |
| "earnings_by_type": { | |
| "LOAN": { "pending": 50.00, "available": 100.00, "withdrawn": 200.00 } | |
| } | |
| } | |
| } | |
| ``` | |
| ### GET /history | |
| Get paginated commission history. | |
| **Query Parameters:** | |
| - `earning_type`: Filter by type | |
| - `status`: Filter by status (PENDING, AVAILABLE, WITHDRAWN) | |
| - `from_date`: Filter from date | |
| - `to_date`: Filter to date | |
| - `page`: Page number | |
| - `per_page`: Items per page (default: 20) | |
| ### GET /earning-types | |
| Get available earning types with descriptions. | |
| ### GET /breakdown | |
| Get earnings breakdown by type. | |
| ### POST /withdraw | |
| Request a withdrawal. | |
| **Request Body:** | |
| ```json | |
| { | |
| "amount_eur": 100.00, | |
| "bank_name": "First Bank", | |
| "bank_code": "011", | |
| "account_number": "0123456789", | |
| "account_name": "Community Name" | |
| } | |
| ``` | |
| ### GET /withdrawals | |
| Get withdrawal history. | |
| ### GET /settings | |
| Get commission settings for all earning types. | |
| ### PUT /settings | |
| Update commission rate for an earning type. | |
| --- | |
| ## Admin API Endpoints (Propel Admin Only) | |
| All endpoints are prefixed with `/api/admin/commission-settings` and require admin authentication. | |
| ### GET / | |
| Get commission settings for all communities. | |
| **Query Parameters:** | |
| - `search` (optional): Search by community name | |
| - `earning_type` (optional): Filter by type (LOAN, JOB, QUEST, REFERRAL) | |
| - `page`: Page number (default: 1) | |
| - `per_page`: Items per page (default: 20) | |
| **Response:** | |
| ```json | |
| { | |
| "data": { | |
| "data": [ | |
| { | |
| "community_id": 123, | |
| "community_name": "Tech Community", | |
| "community_logo": "https://...", | |
| "community_country": "Nigeria", | |
| "earning_type": "LOAN", | |
| "commission_rate": 0.20, | |
| "is_active": true, | |
| "effective_from": "2026-01-01T00:00:00Z", | |
| "updated_at": "2026-01-15T10:30:00Z", | |
| "has_custom_setting": true | |
| } | |
| ], | |
| "current_page": 1, | |
| "per_page": 20, | |
| "total": 100, | |
| "last_page": 5 | |
| } | |
| } | |
| ``` | |
| ### PUT /{communityId} | |
| Update commission rate for a specific community. | |
| **Request Body:** | |
| ```json | |
| { | |
| "earning_type": "LOAN", | |
| "commission_rate": 0.25 | |
| } | |
| ``` | |
| **Validation:** | |
| - `earning_type`: Required, must be one of: LOAN, JOB, QUEST, REFERRAL | |
| - `commission_rate`: Required, numeric, between 0 and 1 (0% to 100%) | |
| **Response:** | |
| ```json | |
| { | |
| "data": null, | |
| "message": "Commission rate updated successfully" | |
| } | |
| ``` | |
| ### GET /{communityId}/history | |
| Get commission rate change history for a community. | |
| **Query Parameters:** | |
| - `earning_type` (optional): Filter by earning type | |
| - `per_page`: Items per page (default: 20) | |
| **Response:** | |
| ```json | |
| { | |
| "data": { | |
| "data": [ | |
| { | |
| "id": 1, | |
| "community_id": 123, | |
| "earning_type": "LOAN", | |
| "commission_rate": 0.20, | |
| "is_active": false, | |
| "effective_from": "2026-01-01T00:00:00Z", | |
| "effective_until": "2026-01-15T10:30:00Z", | |
| "created_by": { | |
| "id": 1, | |
| "name": "Admin User" | |
| }, | |
| "updated_by": { | |
| "id": 1, | |
| "name": "Admin User" | |
| }, | |
| "created_at": "2026-01-01T00:00:00Z" | |
| } | |
| ] | |
| } | |
| } | |
| ``` | |
| ### POST /bulk-update | |
| Update commission rates for multiple communities at once. | |
| **Request Body:** | |
| ```json | |
| { | |
| "earning_type": "LOAN", | |
| "commission_rate": 0.25, | |
| "community_ids": [123, 456, 789] | |
| } | |
| ``` | |
| **Validation:** | |
| - `earning_type`: Required, must be one of: LOAN, JOB, QUEST, REFERRAL | |
| - `commission_rate`: Required, numeric, between 0 and 1 | |
| - `community_ids`: Required, array of valid community IDs | |
| **Response:** | |
| ```json | |
| { | |
| "data": null, | |
| "message": "Commission rates updated successfully for 3 communities" | |
| } | |
| ``` | |
| --- | |
| ## Frontend Components | |
| ### Page Access | |
| **URL:** `/community-commissions` | |
| **Route:** Defined in `routes/web.php` | |
| ```php | |
| Route::get('/community-commissions', [CommunityAdminController::class, 'commissions']) | |
| ->name('community-commissions.index'); | |
| ``` | |
| **Controller:** `app/Http/Controllers/CommunityAdminController.php` | |
| ```php | |
| public function commissions() | |
| { | |
| $community = Community::getCurrent(); | |
| $admin = Auth::user(); | |
| return view('community.commissions', compact('community', 'admin')); | |
| } | |
| ``` | |
| **Blade View:** `resources/views/community/commissions.blade.php` | |
| ```blade | |
| @extends('layouts.master') | |
| @section('content') | |
| <new-dashboard-layout page-title="Commissions"> | |
| <commission-dashboard communityidp="{{ $community->id }}" /> | |
| </new-dashboard-layout> | |
| @endsection | |
| ``` | |
| ### Component Hierarchy | |
| ``` | |
| CommissionDashboard | |
| |-- CommissionStatsCards | |
| |-- CommissionHistoryTable | |
| |-- CommissionWithdrawalModal | |
| |-- CommissionRulesModal | |
| |-- CommissionDetailModal | |
| ``` | |
| ### Component Locations | |
| | Component | File | Purpose | | |
| |-----------|------|---------| | |
| | CommissionDashboard | `resources/js/components/admin/commission-dashboard.vue` | Community admin view | | |
| | CommissionStatsCards | `resources/js/components/admin/commission-stats-cards.vue` | Stats display | | |
| | CommissionHistoryTable | `resources/js/components/admin/commission-history-table.vue` | History table | | |
| | CommissionWithdrawalModal | `resources/js/components/admin/commission-withdrawal-modal.vue` | Withdrawal form | | |
| | CommissionRulesModal | `resources/js/components/admin/commission-rules-modal.vue` | Rules display | | |
| | CommissionDetailModal | `resources/js/components/admin/commission-detail-modal.vue` | Detail view | | |
| | AdminCommissionSettings | `resources/js/components/admin/AdminCommissionSettings.vue` | Propel admin settings | | |
| ### Navigation Links | |
| **Sidebar (Old Layout):** `resources/js/layouts/dashboard/sidebar.vue` | |
| **Navbar (New Layout):** `resources/js/components/reusables/navbar.vue` | |
| Both show "Commissions" link only for `COMMUNITY_ADMIN` and `MANAGER` roles. | |
| ### Admin Commission Settings (Propel Admin Only) | |
| **URL:** `/admin/commission-settings` | |
| **Route:** Defined in `routes/web.php` under `isBackOfficeAdmin` middleware | |
| ```php | |
| Route::middleware('isBackOfficeAdmin')->group(function () { | |
| Route::get('/commission-settings', [AdminCommissionSettingsController::class, 'index']) | |
| ->name('commission-settings.index'); | |
| }); | |
| ``` | |
| **Controller:** `app/Http/Controllers/AdminCommissionSettingsController.php` | |
| ```php | |
| public function index() | |
| { | |
| return view('admin.commission-settings.index'); | |
| } | |
| ``` | |
| **Blade View:** `resources/views/admin/commission-settings/index.blade.php` | |
| ```blade | |
| @extends('layouts.admin') | |
| @section('title', 'Commission Settings Management') | |
| @section('content') | |
| <main> | |
| <div class="container-fluid"> | |
| <div class="row"> | |
| <div class="col-12"> | |
| <h1>Commission Settings Management</h1> | |
| <div class="card mb-4"> | |
| <div class="card-body"> | |
| <admin-commission-settings /> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| @endsection | |
| ``` | |
| **Vue Component:** `resources/js/components/admin/AdminCommissionSettings.vue` | |
| A comprehensive admin interface featuring: | |
| - **Search & Filter**: Find communities by name, filter by earning type | |
| - **Data Table**: Shows all communities with their commission rates, status, and last update | |
| - **Bulk Operations**: Select multiple communities and update rates simultaneously | |
| - **Edit Modal**: Individual rate editing for each community/earning type | |
| - **History Modal**: View complete rate change history with timestamps and admin attribution | |
| - **Pagination**: Navigate large datasets efficiently | |
| - **Responsive Design**: Mobile-friendly layout | |
| **Navigation:** Admin sidebar > Financial Services > Commission Settings | |
| #### Authorization & Access Control | |
| **Community Commission Dashboard:** | |
| - Requires: `COMMUNITY_ADMIN` or `MANAGER` role | |
| - Middleware: Standard auth | |
| - Scope: User sees only their own community's data | |
| **Admin Commission Settings:** | |
| - Requires: Propel Back Office Admin account | |
| - Middleware: `isBackOfficeAdmin` | |
| - Scope: User sees all communities across the platform | |
| - API endpoints validate admin status before allowing rate modifications | |
| --- | |
| ## Integration Status | |
| > **Current State:** The commission framework supports multiple earning types, but not all are currently integrated into the application workflows. | |
| | Earning Type | Framework Ready | Triggered in Code | Integration Location | Testing Endpoint | | |
| |--------------|-----------------|-------------------|---------------------|------------------| | |
| | **LOAN** | Yes | **Yes** | `app/Classes/Klump.php` (webhook handler) | `POST /api/simulate-klump-webhook` | | |
| | **JOB** | Yes | **No** | Needs integration in hiring workflow | N/A | | |
| | **QUEST** | Yes | **No** | Needs integration in quest completion | N/A | | |
| | **REFERRAL** | Yes | **No** | Needs integration in referral system | N/A | | |
| > **Testing**: See the [Testing Guide](#testing-guide) for detailed instructions on testing loan commissions using the simulation endpoint. | |
| ### What's Built vs What's Integrated | |
| **Built (Ready to Use):** | |
| - Database schema supports all earning types | |
| - `CommissionService` base class can record any type | |
| - `JobCommissionService` exists with `recordHiringCommission()` method | |
| - API endpoints support filtering by any earning type | |
| - Frontend dashboard displays all earning types | |
| **Not Yet Integrated:** | |
| - JOB: `JobCommissionService::recordHiringCommission()` is not called when a member is hired | |
| - QUEST: No `QuestCommissionService` or trigger exists | |
| - REFERRAL: No `ReferralCommissionService` or trigger exists | |
| ### Integration Checklist for New Types | |
| To fully integrate a commission type, you need to: | |
| 1. **Find the trigger point** - Where in the code does the qualifying event happen? | |
| 2. **Get community context** - Determine which community the user belongs to | |
| 3. **Calculate Propel's earnings** - What did Propel earn from this transaction? | |
| 4. **Call the service** - Use `CommissionService::recordCommission()` or a dedicated service | |
| 5. **Handle finalization** - If status should be PENDING first, implement finalization logic | |
| --- | |
| ## Integration Guide | |
| ### Recording a Loan Commission | |
| ```php | |
| // In Klump webhook handler | |
| $service = app(LoanCommissionService::class); | |
| $service->recordRepaymentCommission( | |
| $loan, | |
| $repaymentAmount, | |
| 'NGN', | |
| 'klump', | |
| $transactionId, | |
| ['webhook_event' => $event] | |
| ); | |
| // When loan is fully repaid | |
| if ($loan->isFullyRepaid()) { | |
| $service->finalizeCommissionsForLoan($loan); | |
| } | |
| ``` | |
| ### Recording a Job Commission | |
| ```php | |
| use App\Classes\JobCommissionService; | |
| $service = app(JobCommissionService::class); | |
| $service->recordHiringCommission( | |
| $projectApplication, | |
| $propelPlacementFee, | |
| 'EUR', | |
| [ | |
| 'placement_fee' => $totalPlacementFee, | |
| 'metadata' => ['hired_date' => now()], | |
| ] | |
| ); | |
| ``` | |
| ### Adding a New Earning Type | |
| 1. Add constant to `CommunityCommission` model | |
| 2. Create dedicated service extending `CommissionService` | |
| 3. Update API controller's `getEarningTypes()` method | |
| 4. Update frontend labels in Vue components | |
| ### Updating Commission Rates (Admin) | |
| **Via Admin Interface (Recommended):** | |
| 1. Navigate to `/admin/commission-settings` | |
| 2. Search/filter for the target community | |
| 3. Click edit icon on the desired community/earning type row | |
| 4. Enter new rate percentage (0-100) | |
| 5. Save changes | |
| **Via Code:** | |
| ```php | |
| use App\Models\CommunityCommissionSetting; | |
| // Update rate for a specific community | |
| CommunityCommissionSetting::setRate( | |
| $communityId, | |
| 'LOAN', | |
| 0.25, // 25% | |
| auth()->id() | |
| ); | |
| // Bulk update via service | |
| $service = app(\App\Http\Controllers\Api\AdminCommissionSettingsController::class); | |
| // Use the bulk-update API endpoint | |
| ``` | |
| **Rate Change Flow:** | |
| 1. Admin submits new rate via UI or API | |
| 2. System deactivates current active rate | |
| 3. New rate record is created with `is_active = true` | |
| 4. Change is audited with admin ID and timestamp | |
| 5. New rate takes effect immediately for future commissions | |
| 6. Existing commissions retain their original rates | |
| ### Integrating JOB Commissions (TODO) | |
| **Trigger Point:** When a community member is marked as "hired" for a job. | |
| **Likely Location:** | |
| - `app/Http/Controllers/Api/ProjectApplicationController.php` (status change) | |
| - `app/Http/Requests/` (application status update handler) | |
| - Or via an Observer on `ProjectApplication` model | |
| **Integration Example:** | |
| ```php | |
| // In the hiring logic (e.g., when application status changes to HIRED) | |
| use App\Classes\JobCommissionService; | |
| $commissionService = app(JobCommissionService::class); | |
| $commissionService->recordHiringCommission( | |
| $projectApplication, | |
| $propelPlacementFee, // Propel's earnings from this placement | |
| 'EUR', | |
| [ | |
| 'placement_fee' => $totalPlacementFee, | |
| 'metadata' => [ | |
| 'hired_date' => now(), | |
| 'project_id' => $projectApplication->project_id, | |
| ], | |
| ] | |
| ); | |
| ``` | |
| ### Integrating QUEST Commissions (TODO) | |
| **Trigger Point:** When a community member completes a quest. | |
| **Likely Location:** | |
| - Quest completion handler/controller | |
| - Or via an Observer on Quest completion model | |
| **Integration Example:** | |
| ```php | |
| use App\Classes\CommissionService; | |
| use App\Models\CommunityCommission; | |
| $service = app(CommissionService::class); | |
| $service->recordCommission( | |
| communityId: $user->community_id, | |
| userId: $user->id, | |
| earningType: CommunityCommission::EARNING_TYPE_QUEST, | |
| propelEarningsLocal: $propelQuestRevenue, | |
| localCurrency: 'EUR', | |
| source: $quest, | |
| options: [ | |
| 'description' => "Quest completion: {$quest->title}", | |
| 'status' => CommunityCommission::STATUS_AVAILABLE, // Immediate availability | |
| ] | |
| ); | |
| ``` | |
| ### Integrating REFERRAL Commissions (TODO) | |
| **Trigger Point:** When a referral leads to a qualifying action (e.g., referred user takes a loan, gets hired). | |
| **Likely Location:** | |
| - Referral tracking system | |
| - After qualifying action is completed | |
| **Integration Example:** | |
| ```php | |
| use App\Classes\CommissionService; | |
| use App\Models\CommunityCommission; | |
| $service = app(CommissionService::class); | |
| $service->recordCommission( | |
| communityId: $referrer->community_id, | |
| userId: $referrer->id, | |
| earningType: CommunityCommission::EARNING_TYPE_REFERRAL, | |
| propelEarningsLocal: $propelReferralBonus, | |
| localCurrency: 'EUR', | |
| source: $referral, | |
| options: [ | |
| 'description' => "Referral bonus for {$referredUser->name}", | |
| 'status' => CommunityCommission::STATUS_AVAILABLE, | |
| ] | |
| ); | |
| ``` | |
| --- | |
| ## Webhook Handling | |
| ### Paystack Transfer Webhooks | |
| ```php | |
| // In Paystack webhook controller | |
| public function handleWebhook(Request $request) | |
| { | |
| $event = $request->input('event'); | |
| $data = $request->input('data'); | |
| if (in_array($event, ['transfer.success', 'transfer.failed'])) { | |
| $service = app(CommunityCommissionWithdrawalService::class); | |
| $service->handleTransferWebhook( | |
| $data['reference'], | |
| $event === 'transfer.success' ? 'success' : 'failed', | |
| $data['reason'] ?? null | |
| ); | |
| } | |
| } | |
| ``` | |
| --- | |
| ## Troubleshooting | |
| ### Commission Not Recording | |
| 1. Check commission rate exists: | |
| ```php | |
| $rate = CommunityCommissionSetting::getActiveRate($communityId, $earningType); | |
| ``` | |
| 2. Check user belongs to community: | |
| ```php | |
| $communityUser = CommunityUser::where('user_id', $userId) | |
| ->where('community_id', $communityId) | |
| ->first(); | |
| ``` | |
| 3. Check application logs for errors | |
| ### Withdrawal Failed | |
| 1. Check Paystack balance | |
| 2. Verify bank details | |
| 3. Check `failure_reason` in `community_commission_withdrawals` table | |
| 4. Review Paystack API logs | |
| ### Commission Status Not Updating | |
| For loans, commissions stay PENDING until loan is fully repaid: | |
| ```php | |
| // Manually finalize if needed | |
| $service = app(LoanCommissionService::class); | |
| $service->finalizeCommissionsForLoan($loan); | |
| ``` | |
| --- | |
| ## File Reference | |
| ### Backend Files | |
| | File | Purpose | | |
| |------|---------| | |
| | `app/Models/CommunityCommission.php` | Main commission model | | |
| | `app/Models/CommunityLoanCommission.php` | Loan-specific model (backward compatible) | | |
| | `app/Models/CommunityCommissionSetting.php` | Rate settings model | | |
| | `app/Models/CommunityCommissionWithdrawal.php` | Withdrawal model | | |
| | `app/Classes/CommissionService.php` | Base commission service | | |
| | `app/Classes/LoanCommissionService.php` | Loan commission service | | |
| | `app/Classes/JobCommissionService.php` | Job commission service | | |
| | `app/Classes/CommunityCommissionWithdrawalService.php` | Withdrawal service | | |
| | `app/Http/Controllers/Api/CommunityCommissionController.php` | API controller (community) | | |
| | `app/Http/Controllers/Api/AdminCommissionSettingsController.php` | API controller (admin) | | |
| | `app/Http/Controllers/CommunityAdminController.php` | Web controller (community) | | |
| | `app/Http/Controllers/AdminCommissionSettingsController.php` | Web controller (admin) | | |
| | `routes/api.php` | API routes | | |
| | `routes/web.php` | Web routes | | |
| ### Frontend Files | |
| | File | Purpose | | |
| |------|---------| | |
| | `resources/js/components/admin/commission-dashboard.vue` | Main dashboard | | |
| | `resources/js/components/admin/commission-stats-cards.vue` | Stats display | | |
| | `resources/js/components/admin/commission-history-table.vue` | History table | | |
| | `resources/js/components/admin/commission-withdrawal-modal.vue` | Withdrawal form | | |
| | `resources/js/components/admin/commission-rules-modal.vue` | Rules display | | |
| | `resources/js/components/admin/commission-detail-modal.vue` | Detail view | | |
| | `resources/js/cass.js` | Component registration | | |
| | `resources/js/layouts/dashboard/sidebar.vue` | Sidebar nav (community) | | |
| | `resources/js/components/reusables/navbar.vue` | Navbar nav (community) | | |
| | `resources/views/community/commissions.blade.php` | Blade template (community) | | |
| | `resources/js/components/admin/AdminCommissionSettings.vue` | Admin settings interface | | |
| | `resources/views/admin/commission-settings/index.blade.php` | Blade template (admin) | | |
| | `resources/views/layouts/admin.blade.php` | Admin sidebar nav | | |
| ### Database Migrations | |
| | File | Purpose | | |
| |------|---------| | |
| | `2026_01_30_113357_create_community_loan_commissions_table.php` | Create main table | | |
| | `2026_01_30_113358_create_community_commission_settings_table.php` | Create settings table | | |
| | `2026_01_30_113358_create_community_commission_withdrawals_table.php` | Create withdrawals table | | |
| | `2026_01_30_113358_add_commission_fields_to_loans_table.php` | Add loan fields | | |
| | `2026_01_30_114417_refactor_community_commissions_for_multiple_types.php` | Multi-type support | | |
| --- | |
| ## Running Migrations | |
| ```bash | |
| php artisan migrate | |
| ``` | |
| This will: | |
| 1. Create `community_loan_commissions` table (or skip if exists) | |
| 2. Create `community_commission_settings` table | |
| 3. Create `community_commission_withdrawals` table | |
| 4. Add commission fields to `loans` table | |
| 5. Rename to `community_commissions` and add multi-type support | |
| All migrations are **idempotent** - safe to run multiple times. | |
| --- | |
| ## Testing Guide | |
| > **Quick Start**: Use `POST /api/simulate-klump-webhook` to simulate loan repayments and test commission recording without real Klump integration. | |
| ### Testing Loan Commissions | |
| Since loan commissions are the only currently active commission type, here's a complete guide to testing the commission system end-to-end. | |
| **What You'll Test:** | |
| - ✅ Commission creation on loan repayment | |
| - ✅ Commission rate calculation (default 20%) | |
| - ✅ Status transitions (PENDING → AVAILABLE) | |
| - ✅ Community admin dashboard display | |
| - ✅ Propel admin rate management | |
| #### Prerequisites | |
| 1. Have a community set up with members | |
| 2. Have the commission rate configured (default is 20% for loans) | |
| 3. Have a Klump loan created for a community member | |
| 4. Access to API testing tool (Postman, Insomnia, etc.) | |
| #### Step 1: Create a Test Loan | |
| First, create a loan for testing. You can either: | |
| - Use the regular loan application flow through the UI | |
| - Or use the Klump integration to create a loan | |
| **Important**: Note the loan's `internal_ref_number` - you'll need this for the webhook simulation. | |
| ```sql | |
| -- Find a recent loan for testing | |
| SELECT id, internal_ref_number, amount, status, user_id | |
| FROM loans | |
| WHERE status = 'DISBURSED' | |
| ORDER BY created_at DESC | |
| LIMIT 1; | |
| ``` | |
| #### Step 2: Simulate a Partial Repayment | |
| Use the simulation endpoint to trigger a repayment webhook event. | |
| **Endpoint:** `POST /api/simulate-klump-webhook` | |
| **Headers:** | |
| ``` | |
| Content-Type: application/json | |
| Accept: application/json | |
| ``` | |
| **Request Body (Partial Payment Example):** | |
| ```json | |
| { | |
| "event": "klump.loan.repayment.successful", | |
| "data": { | |
| "merchant_reference": "LOAN_INTERNAL_REF_123456", | |
| "amount": 50000, | |
| "transaction": { | |
| "id": "klump_txn_test_001", | |
| "merchant_reference": "LOAN_INTERNAL_REF_123456", | |
| "created_at": "2026-01-30T10:00:00Z", | |
| "loan_is_paid_off": false | |
| }, | |
| "loan_plan": { | |
| "id": "plan_001", | |
| "loan_is_paid_off": false | |
| } | |
| } | |
| } | |
| ``` | |
| **Request Body (Final Payment Example):** | |
| ```json | |
| { | |
| "event": "klump.loan.repayment.successful", | |
| "data": { | |
| "merchant_reference": "LOAN_INTERNAL_REF_123456", | |
| "amount": 50000, | |
| "transaction": { | |
| "id": "klump_txn_test_002", | |
| "merchant_reference": "LOAN_INTERNAL_REF_123456", | |
| "created_at": "2026-01-30T11:00:00Z", | |
| "loan_is_paid_off": true | |
| }, | |
| "loan_plan": { | |
| "id": "plan_001", | |
| "loan_is_paid_off": true | |
| } | |
| } | |
| } | |
| ``` | |
| **Expected Response:** | |
| ```json | |
| { | |
| "message": "Webhook processed successfully." | |
| } | |
| ``` | |
| #### Step 3: Verify Commission Recording | |
| **Check Commission was Created:** | |
| ```sql | |
| SELECT | |
| cc.id, | |
| cc.user_id, | |
| cc.community_id, | |
| cc.earning_type, | |
| cc.repayment_amount_local, | |
| cc.commission_amount_eur, | |
| cc.commission_rate, | |
| cc.status, | |
| cc.repayment_date, | |
| u.email as user_email, | |
| c.name as community_name | |
| FROM community_commissions cc | |
| JOIN users u ON cc.user_id = u.id | |
| JOIN communities c ON cc.community_id = c.id | |
| WHERE cc.loan_id = YOUR_LOAN_ID | |
| ORDER BY cc.created_at DESC; | |
| ``` | |
| **Expected Results:** | |
| - `earning_type`: "LOAN" | |
| - `status`: "PENDING" (for partial payments) | |
| - `status`: "AVAILABLE" (after final payment when `loan_is_paid_off = true`) | |
| - `commission_rate`: 0.20 (or your configured rate) | |
| - `commission_amount_eur`: Calculated as (propel_earnings_eur * commission_rate) | |
| #### Step 4: Test Commission Dashboard | |
| **View Community Commission Dashboard:** | |
| 1. Log in as a community admin | |
| 2. Navigate to `/community-commissions` | |
| 3. Check the stats: | |
| - **Wallet Balance**: Should show EUR 0 (commissions pending) | |
| - **Potential Earnings**: Should show the commission amount | |
| - **Commission Rate**: Should show 20% | |
| **View Commission History:** | |
| 1. Scroll to the commission history table | |
| 2. Find your test commission | |
| 3. Verify: | |
| - Status shows as "PENDING" | |
| - Amount matches calculated commission | |
| - Member name is correct | |
| #### Step 5: Finalize Loan & Test Withdrawal | |
| **Simulate Final Payment:** | |
| Send another webhook with `loan_is_paid_off: true` (see Final Payment Example above). | |
| **Verify Commission Status Changed:** | |
| ```sql | |
| SELECT status, commission_amount_eur | |
| FROM community_commissions | |
| WHERE loan_id = YOUR_LOAN_ID; | |
| ``` | |
| Expected: All commissions for this loan should now have `status = 'AVAILABLE'` | |
| **Check Wallet Balance Updated:** | |
| 1. Refresh the commission dashboard | |
| 2. **Wallet Balance** should now show the commission amount | |
| 3. **Potential Earnings** should be reduced by that amount | |
| **Test Withdrawal (Optional):** | |
| 1. Click "Request Withdrawal" | |
| 2. Enter an amount (up to wallet balance) | |
| 3. Enter bank details | |
| 4. Submit the withdrawal request | |
| 5. Check `community_commission_withdrawals` table for the record | |
| #### Step 6: Verify in Admin Settings | |
| **As Propel Admin:** | |
| 1. Navigate to `/admin/commission-settings` | |
| 2. Search for the test community | |
| 3. Click the history icon | |
| 4. Verify the commission rate history is shown correctly | |
| #### Troubleshooting | |
| **Commission Not Created:** | |
| - Check logs: `storage/logs/laravel.log` | |
| - Search for "Klump commission recording failed" | |
| - Verify the community has a commission rate set | |
| - Ensure the loan's user belongs to a community | |
| **Commission Status Not Changing:** | |
| ```php | |
| // Manually finalize if needed (in tinker) | |
| $loan = \App\Models\Loan::find(YOUR_LOAN_ID); | |
| $service = app(\App\Classes\LoanCommissionService::class); | |
| $service->finalizeCommissionsForLoan($loan); | |
| ``` | |
| **Webhook Simulation Fails:** | |
| - Verify loan exists with the provided `merchant_reference` | |
| - Check the `internal_ref_number` matches exactly | |
| - Ensure the loan status is not already "COMPLETED" | |
| #### Testing Different Scenarios | |
| **Scenario 1: Multiple Partial Payments** | |
| ```bash | |
| # First payment - 25,000 NGN | |
| curl -X POST http://localhost/api/simulate-klump-webhook \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"event":"klump.loan.repayment.successful","data":{"merchant_reference":"LOAN_REF","amount":25000,"transaction":{"id":"txn_001","merchant_reference":"LOAN_REF","loan_is_paid_off":false},"loan_plan":{"id":"plan_001","loan_is_paid_off":false}}}' | |
| # Second payment - 25,000 NGN | |
| curl -X POST http://localhost/api/simulate-klump-webhook \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"event":"klump.loan.repayment.successful","data":{"merchant_reference":"LOAN_REF","amount":25000,"transaction":{"id":"txn_002","merchant_reference":"LOAN_REF","loan_is_paid_off":false},"loan_plan":{"id":"plan_001","loan_is_paid_off":false}}}' | |
| # Final payment - 50,000 NGN (marks as paid off) | |
| curl -X POST http://localhost/api/simulate-klump-webhook \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"event":"klump.loan.repayment.successful","data":{"merchant_reference":"LOAN_REF","amount":50000,"transaction":{"id":"txn_003","merchant_reference":"LOAN_REF","loan_is_paid_off":true},"loan_plan":{"id":"plan_001","loan_is_paid_off":true}}}' | |
| ``` | |
| **Expected**: | |
| - 3 commission records created | |
| - All start as PENDING | |
| - All change to AVAILABLE when final payment is made | |
| **Scenario 2: Different Commission Rates** | |
| 1. As admin, change the commission rate for a community to 25% | |
| 2. Create a new loan for that community | |
| 3. Simulate a repayment | |
| 4. Verify commission is calculated at 25% instead of 20% | |
| **Scenario 3: Cross-Currency Testing** | |
| The system stores all commissions in EUR. Test with different local currencies: | |
| - Repayments in NGN are converted to EUR | |
| - Commission calculations happen on EUR amounts | |
| - Dashboard displays in EUR | |
| #### Expected Commission Calculation | |
| ``` | |
| Repayment Amount (Local): 100,000 NGN | |
| Exchange Rate: 1 EUR = 1,700 NGN | |
| Propel Interest Earned (EUR): (100,000 / 1,700) * 0.30 = 17.65 EUR (30% interest) | |
| Commission Rate: 20% | |
| Commission Amount (EUR): 17.65 * 0.20 = 3.53 EUR | |
| ``` | |
| **Note**: The actual Propel earnings calculation depends on the loan terms and interest structure. The commission service extracts this from the loan repayment data. | |
| --- | |
| ## Change Log | |
| ### January 30, 2026 | |
| - Added Propel Admin Commission Settings Management interface | |
| - Added bulk rate update functionality | |
| - Added commission rate history tracking | |
| - Added admin API endpoints for rate management | |
| - Updated documentation with admin management section | |
| ### January 2026 (Initial Release) | |
| - Initial commission framework implementation | |
| - Community admin dashboard | |
| - Loan commission integration | |
| - Withdrawal functionality | |
| --- | |
| ## Quick Reference | |
| ### For Community Admins | |
| - **Dashboard URL**: `/community-commissions` | |
| - **Who Can Access**: Community Admins & Managers | |
| - **Default Commission Rate**: 20% for loans | |
| - **Withdrawal**: Direct bank transfer via Paystack (1-2 business days) | |
| ### For Propel Admins | |
| - **Settings URL**: `/admin/commission-settings` | |
| - **Who Can Access**: Propel Back Office Admins | |
| - **Features**: View all communities, edit rates, bulk updates, view history | |
| ### For Developers Testing | |
| ```bash | |
| # Simulate loan repayment | |
| POST /api/simulate-klump-webhook | |
| Content-Type: application/json | |
| { | |
| "event": "klump.loan.repayment.successful", | |
| "data": { | |
| "merchant_reference": "LOAN_INTERNAL_REF", | |
| "amount": 50000, | |
| "transaction": { | |
| "id": "test_txn_001", | |
| "merchant_reference": "LOAN_INTERNAL_REF", | |
| "loan_is_paid_off": false | |
| }, | |
| "loan_plan": { | |
| "id": "plan_001", | |
| "loan_is_paid_off": false | |
| } | |
| } | |
| } | |
| ``` | |
| ### Key Database Tables | |
| - `community_commissions` - Main commission records | |
| - `community_commission_settings` - Rate configurations | |
| - `community_commission_withdrawals` - Withdrawal requests | |
| ### Commission Status Flow | |
| ``` | |
| PENDING (loan not fully paid) → AVAILABLE (loan paid off) → WITHDRAWN (paid out) | |
| ``` | |
| --- | |
| *Last updated: January 30, 2026* |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment