Skip to content

Key concepts and domain model

Reference for terms, roles, and relationships between entities in the commissions module.


Glossary

Assignment (commission_assignments) Link between a tenant and a set of participants (reps) with the agreed commission terms. An assignment defines recurringPercent, recurringFixedCents, oneTimeAmountCents, and basis. Participants live in commission_participants.

Attribution log (commission_attribution_log) Append-only table that records every rep attribution change on a lead or tenant. Immutable from application code. See Attribution.

Basis Determines whether the commission % is applied on gross revenue (gross) or net of Stripe fees (net). Net deducts stripeFeePercent (2.9% default) plus stripeFeeFixedCents (30¢ per invoice).

Commission rep (commission_reps) The sales rep who earns commissions. Can be an external 1099 contractor (with their own portal at /seller/*) or an MSP team member linked via userId. A rep can also be a manager of other reps in the hierarchy.

Component The Stripe revenue component that a revenue_event belongs to: base, phones, lines, overage, or other. Starting from F-CMS-3, each line item in a Stripe invoice creates a separate revenue event labeled with its component. This allows setting different % per revenue component in the assignment.

IC (Individual Contributor) Rep who generates direct commission (not override). The manager earns override on the IC's commission, not on the tenant's revenue.

Line item (commission_line_items) A calculated line within a statement. Kind can be recurring_percent, recurring_fixed, one_time, or override. Override line items have a parentLineItemId pointing to the base line item they derive from.

Manager Rep where other reps have managerId pointing to them (their direct reports). The manager earns an overridePercent on the commissions of their direct and indirect reports, recursively.

MLM (Multi-Level Marketing) Hierarchy of managers and reps where each level can have an override % on the level below. Unlimited depth. Cycle detection via DB trigger + TS walk.

MSP (Managed Service Provider) Sopinf Tech LLC — the owner of the SipSop product.

Override Commission a manager earns on the commissions of their downlines. Calculated on the IC's commission (not on the underlying revenue). Non-compounding: each level applies its % on the IC's original commission, not on the previous level's override.

Participant (commission_participants) Rep within an assignment, with their sharePercent. The shares of all participants in an assignment must sum to exactly 100 (validated by a DEFERRABLE trigger constraint in the DB).

Ref code (commission_reps.refCode) Unique auto-generated slug from the rep's name (e.g., maria-gonzalez). Used in landing URLs (?ref=maria-gonzalez) to attribute the lead to the rep via first-touch cookie.

Revenue event (revenue_events) A tenant's income event. Source can be stripe_invoice, wire, check, manual, or other. Stripe events are immutable; manual events are editable. Multi-currency: each event stores originalCurrency, originalAmountCents, fxRate (snapshot), and amountUsdCents.

Statement (commission_statements) A commission document for a rep and a monthly period. Lifecycle: draftfinalizedpaid. Contains line items that are immutable snapshots of the calculation. Downloadable as PDF.

Statement lifecycle Possible states of a statement and the allowed transitions:

  • draftfinalized (via finalizeStatement)
  • finalizedpaid (via markStatementPaid)
  • draft can be deleted; finalized and paid are immutable.

Tenant A SipSop customer (supermarket, retail, etc.) who pays the monthly subscription. In Better Auth terminology, this corresponds to an organization.

Vesting Waiting period before the one-time bonus is included in the statement. Configurable globally in commission_settings.oneTimeQualificationMonths (default 3). The bonus is included in the statement for the month in which startsAt + qualificationMonths falls within the period, provided oneTimePaidAt IS NULL and the assignment hasn't ended.


System roles

RoleScopeAccess to commissions module
admin_msp with commissions.manage permissionInternal MSPFull admin access: CRUD reps, assignments, statements, settings
admin_msp without commissions.manageInternal MSPOnly /admin/my-commissions if linked as a rep (read-only)
sellerExternal (1099)Only /seller/* — their own statements, assignments, and leads. Read-only.
client_admin / client_userTenantNo access to the commissions module
super_adminInternal MSPSame as admin_msp with all permissions

The commissions.manage permission is assigned in the msp_role_permissions table and verified in each admin procedure via requirePermission('commissions.manage').


Domain model

The 8 module tables and their relationships:

mermaid
erDiagram
    commission_reps {
        uuid id PK
        text user_id FK
        varchar name
        varchar email
        varchar ref_code UK
        uuid manager_id FK
        numeric override_percent
        varchar status
    }

    commission_assignments {
        uuid id PK
        uuid tenant_id FK
        numeric recurring_percent
        integer recurring_fixed_cents
        integer one_time_amount_cents
        varchar basis
        date starts_at
        date ends_at
        date one_time_paid_at
        numeric recurring_percent_base
        numeric recurring_percent_phones
        numeric recurring_percent_lines
        numeric recurring_percent_overage
        varchar status
    }

    commission_participants {
        uuid id PK
        uuid assignment_id FK
        uuid commission_rep_id FK
        numeric share_percent
        varchar role
    }

    revenue_events {
        uuid id PK
        uuid tenant_id FK
        varchar period
        varchar original_currency
        integer original_amount_cents
        numeric fx_rate
        integer amount_usd_cents
        varchar source
        text source_ref UK
        varchar component
    }

    commission_settings {
        integer id PK
        varchar default_basis
        numeric stripe_fee_percent
        integer stripe_fee_fixed_cents
        integer one_time_qualification_months
        integer cron_day_of_month
        integer cron_hour
        boolean auto_finalize
    }

    commission_statements {
        uuid id PK
        uuid commission_rep_id FK
        date period_start
        date period_end
        varchar status
        integer subtotal_recurring_cents
        integer subtotal_one_time_cents
        integer subtotal_override_cents
        integer total_cents
    }

    commission_line_items {
        uuid id PK
        uuid statement_id FK
        uuid assignment_id FK
        uuid tenant_id FK
        varchar kind
        varchar basis
        integer revenue_usd_cents
        numeric rate_applied
        integer amount_cents
        uuid parent_line_item_id FK
        uuid override_of_rep_id FK
        varchar component
    }

    commission_attribution_log {
        uuid id PK
        uuid lead_id FK
        uuid tenant_id FK
        varchar action
        uuid old_rep_id FK
        uuid new_rep_id FK
        text changed_by FK
        text notes
    }

    seller_invitations {
        uuid id PK
        uuid commission_rep_id FK
        varchar token UK
        varchar email
        timestamp expires_at
        timestamp used_at
        text invited_by FK
    }

    commission_reps ||--o{ commission_participants : "participates as"
    commission_assignments ||--o{ commission_participants : "has"
    commission_assignments }o--|| tenants : "on"
    commission_reps ||--o{ commission_statements : "receives"
    commission_statements ||--o{ commission_line_items : "contains"
    commission_line_items }o--|| commission_assignments : "from"
    commission_line_items }o--|| tenants : "revenue from"
    commission_line_items ||--o| commission_line_items : "override of"
    commission_reps ||--o| commission_reps : "manager of"
    commission_reps ||--o{ seller_invitations : "invitation"
    commission_reps ||--o{ commission_attribution_log : "referenced in"

Attribution

Policy: first-touch, 90 days

When a prospect visits the landing page with ?ref=maria-gonzalez, a sipsop_ref cookie is set with Max-Age=7776000 (90 days). If the cookie already exists, it is not overwritten — the first touch wins.

When the contact form is filled out on the landing page, the refCode from the cookie (or from the querystring as fallback) is sent to the POST /api/v1/leads endpoint. The leads service resolves the ref code against commission_reps.ref_code (case-sensitive) and records leads.commissionRepId.

If the ref code doesn't resolve to any active rep, the ref code is saved as-is but commissionRepId remains null, and a log entry is created with notes='Unknown ref code: X'.

Lead-to-tenant conversion

When the admin approves the lead and creates the tenant (signup), the service checks if lead.commissionRepId is set. If it is, it automatically creates a commission_assignment with the rep as the sole participant at 100%, and records a log entry with action='lead_converted'.

If the tenant already has a pre-existing assignment (created manually), the auto-create is skipped with a warning in the logs.

Manual changes

The admin can:

  • Change the rep on a lead before conversion: commissions.leads.setRep → log lead_admin_set.
  • Change the participants of an active assignment: commissions.assignments.replaceParticipants → log admin_reassigned for each changed participant.

Every change is recorded in commission_attribution_log with changedBy of the authenticated user.


One-time bonus vesting

The one-time bonus (oneTimeAmountCents) requires that the customer has maintained their subscription for oneTimeQualificationMonths months (globally configurable, default 3).

Rules for including the bonus in a statement for period P:

  1. startsAt + qualificationMonths falls within [periodStart(P), periodEnd(P)].
  2. oneTimePaidAt IS NULL (not yet paid).
  3. The assignment didn't end before qualifiedAt (endsAt IS NULL OR endsAt >= qualifiedAt).

If oneTimeQualificationMonths = 0, the bonus is included in the same period as startsAt.

When finalizing a statement (not when marking as paid), the service updates oneTimePaidAt = periodStart on the assignments whose bonuses appear in that statement. This prevents the bonus from being included again in future periods.


MLM hierarchy and override

The hierarchy is defined with commission_reps.managerId (self-referential FK). A rep without a manager (managerId IS NULL) is top-level.

A manager's overridePercent is applied to each base line item of their reports (direct and indirect), generating kind='override' line items in the manager's statement. The calculation walks up the manager chain until reaching a rep without a manager.

Non-compounding stacking: if the chain is C → B → A (override 10% B, 5% A):

  • IC Maria generates $100 in commission.
  • B earns $10 (10% of $100).
  • A earns $5 (5% of $100, NOT of $110).

Each level applies its % on the IC's original commission, not on the previous level's override.

Cycle detection: double protection:

  1. Trigger trg_no_manager_cycle in the DB (BEFORE INSERT OR UPDATE OF manager_id).
  2. TS walk in the service before the UPDATE, converting the PG error into CommissionsError('CYCLE').

A detected cycle returns error CYCLE (HTTP 400) with a clear message.

SipSop documentation. Product operated by Sopinf Tech LLC.