← Back to the site

Case study

Franchising and commissions module

Client
Empréstimo Sábio
Role
Functional analyst and developer
Stack
PHP, MySQL, WordPress, React
Status
Live in production, still in active development

Commission isn't one number. It's a stack of numbers.

A collaborator's rate sits inside a franchising's rate. A team leader takes a cut of what their team produces. A payout is promised at one moment but only becomes due when a deal reaches a status the business defines – sometimes months later.

Once that stack is real, two things break. Nobody can say with certainty what rate applied to a specific deal after the tables have changed. And nobody can say which technically-won deals have actually been paid.

Spreadsheets survive the first few offices. They do not survive the fifth franchising, the third rate revision, or the first dispute over a commission that should have been settled three months ago.

What was built

Four connected concerns, shipped in phases against the client's already-live site rather than as a green-field rebuild. No downtime.

  • Network and roles. Franchising registration, an onboarding landing page, a per-franchising dashboard. Roles – Admin, Master, Team Leader, Collaborator – with their own permissions. Monthly goals per collaborator and per franchising.
  • Commission engine. A master commission table plus an independently configurable table for each franchising. Per-collaborator, per-bank percentages. A team-leader cut layered on top. Live calculation shown on every process.
  • Pipeline and settlement. Bank, internal status, and status-specific detail fields on every process. Commission frozen automatically when a process reaches a payment-triggering status. An independent paid/pending flag with its own change history.
  • Reporting and oversight. Filterable by franchising, collaborator and period, with Excel export. Pipeline versus booked amounts. Month-by-month financing evolution. Volume by credit typology and partner bank. Collaborator ranking against goal. A one-click view of everything pending settlement.

Architecture

The network already ran its public site and franchising portal on WordPress, and rebuilding was not on the table. The engine was therefore built as six cooperating modules rather than a monolith: a dedicated PHP API and MySQL database sitting behind the existing WordPress install, with a purpose-built React app mounted into it and reaching the API through WordPress's REST proxy.

Existing WordPress

React app

mounted in the client's site

REST proxy

auth and routing

below this line, nothing depends on the CMS

PHP API

commission maths and audit rules

MySQL

versioned history, uniqueness enforced by the database

Six cooperating modules, not a monolith. The plugin and the React app only ever see the API output.

The division keeps every concern testable and replaceable on its own. Commission maths and audit rules live entirely in the API and the database, independent of WordPress; the plugin and the React app only ever see the API's output. Nothing about the commission engine depends on the CMS underneath it.

Engineering decisions

Resolution with no silent fallback

The effective rate resolves collaborator → franchising, and stops. A missing or 0% franchising rate is never filled in from the master table – it means the commission does not apply.

A configuration gap surfaces as a visible N/A, never as a guessed number. A system that guesses produces wrong payouts that nobody catches.

Crystallization at the payment trigger

Commission is calculated and frozen the instant a process enters a payment-triggering status – company, master, team-leader and collaborator cuts, plus the exact percentages and the identities behind them.

A rate change next month never rewrites what was promised on a deal already committed. This is the decision that makes it possible to answer, months later, the question "what rate applied to this process?"

Two audit patterns, matched to what is actually versioned

Rate tables use full soft-delete-and-reinsert versioning, because the whole row is the thing with history.

A single settlement flag gets a slimmer append-only log instead – who flipped it, to what, when. Re-versioning a frozen commission snapshot because one boolean changed would be waste, and noise in the history.

Uniqueness enforced by the database

MySQL has no partial unique index. The rule "one active row per key, old rows stay for history" is enforced with a stored generated column that resolves to the key only while the row is live.

The database refuses a duplicate active row. The alternative is application code that can forget to check – and one day does.

Zero-downtime schema discipline

Live in production since phase three. Every change ships as its own dated, safe-to-reapply migration – never a hand edit, never a rebuild. The schema's full history is reviewable one migration at a time.

Who this is for

None of this is specific to credit intermediation. The shape is structural: selling through franchised offices, brokers or agents; rates that vary by office, person, product or partner and change over time; needing to answer months later what rate applied to a specific deal; payouts that happen in stages and eventually require someone to ask whether a given one was actually paid.

Adjacent sectors where the same shape turns up: mortgage brokerages, insurance MGAs, real-estate franchise brands, equipment and solar dealer networks, and tiered referral and affiliate programmes.

← Back to the site