Skip to main content

Promoter

What it is​

Lets an event have promoters — people who share a referral link and earn a commission on baskets (checkouts) placed through it. Planners configure a commission rate and cap per event; promoters get a dashboard of their deal and referrals, and can cash out.

Core concepts​

  • Basket, not item — a basket is one complete checkout (orderReference), which may contain multiple items. Commission is calculated once per basket, on the basket total, never per line item.
  • Event promoter settings (event_promoter_settings): commissionPercentage, capAmount (max commission on a single basket), isEnabled. Configured per event by the planner.
  • Promoter code: the promoter's user ID, attached to a basket at checkout (via the referral link) as promoterCode.
  • Promoter vault wallet: a single shared wallet (PVB_PROMOTER_VAULT_WALLET_ID) that all commissions land in before a promoter cashes out.

How it works​

Commission calculation​

baseAmount = discount applied ? (basketOriginalAmount - basketDiscountAmount) : basketTotalPrice
rawCommission = baseAmount × (commissionPercentage / 100)
finalCommission = min(rawCommission, capAmount)

A ₦20,000 basket (3 items) at 10%/₦1,500 cap earns the promoter ₦1,500 once — not ₦2,000, and not three separate commissions for the three items.

Transfer flow​

  1. Basket completes (all items saved, orderReference created, transaction committed).
  2. PromoterCommissionService.processCommissionForBasket(...) calculates the commission per the formula above.
  3. Commission is transferred asynchronously from the event's PartyBank wallet to the promoter vault wallet — this happens after the checkout transaction has already committed, and a transfer failure does not roll back or block the customer's order. It's logged and can be reconciled manually via the orderReferenceId.
  4. Integration points: GuestsService (RSVP baskets) and MinishopService (minishop baskets, via setImmediate() after commit) both call into the same commission service — so the rule is consistent across both purchase paths.

Promoter-facing endpoints​

  • GET /v1/promoter/event/:eventId/deal — commission rate, cap, and this promoter's referral link for the event.
  • GET /v1/promoter/event/:eventId/referrals — paginated list of orders placed through this promoter's link.
  • POST /v1/promoter/event/:eventId/request-access / GET .../request-access — request to become a promoter for an event, and check the request's status.
  • POST /v1/promoter/event/:eventId/cashout — cash out accumulated commission. Requires user.isPromoter; automatically cashes out all outstanding commission rather than a partial amount.

Key business rules & edge cases​

  • Commission is silently skipped, not erred, when: there's no promoterCode on the order, no promoter settings exist for the event, settings are disabled, capAmount is 0, or the event's PartyBank wallet is missing its thirdPartyReference. If a promoter says "I didn't get paid," check these five conditions before assuming a bug.
  • Commission is computed on what the customer actually paid — if a discount code was applied, the base is the post-discount amount, not the original list price.
  • A transfer failure never blocks the customer's checkout. This is a deliberate tradeoff: reliability of the core purchase flow wins over commission accuracy, with manual reconciliation as the fallback (search logs for the orderReferenceId).
  • src/modules/promoter/promoter.routes.ts, promoter.controller.ts, promoter-commission.service.ts
  • src/modules/guests/guests.service.ts, src/modules/minishop/minishop.service.ts — commission integration points
  • event_promoter_settings, event_shop_orders tables

Not yet in the generated OpenAPI spec — this module hasn't been through the pilot OpenAPI pass.