Founding Pro Registration Portal โ€” Implementation Plan

Date: 2026-06-30
Status: Draft


Overview

Create an internal staff-only portal for registering Founding Professionals โ€” providers who sign up through a direct outreach channel and are offered a reduced platform fee of 12% (vs. the standard 18%) as an early-adopter incentive. This supports the go-to-market strategy of pre-recruiting anchor providers before public launch.

Only staff can issue Founding Pro status. Providers cannot self-select into the 12% tier.


Business Context

From current-documentation/business-plan.md, Section 7.2 (Provider Acquisition):

Provider acquisition script: "You already do this work. We just bring you the customers and handle the payment. No fee until you get paid โ€” we take 18% of what you earn, nothing upfront."

The Founding Pro program refines this pitch: the 12% rate is a time-limited, invitation-only reward for the providers who take the early-adopter risk. It is not advertised publicly. Staff use this portal during in-person or phone outreach to immediately register a provider and set their rate at signup.


Current Fee Architecture

The 18% platform fee is hardcoded in three places today:

File Location Effect
src/routes/quotes.js:11 const PLATFORM_FEE_RATE = 0.18 Used when a provider submits a quote
src/routes/providers.js:505-506 PROVIDER_SHARE = 0.82 / PLATFORM_FEE = 0.18 Smart Bundle estimated earnings display
src/jobs/payoutBatch.js:4 const PLATFORM_FEE_RATE = 0.18 Nightly payout batch calculates provider share

The Quote model already stores platformFeeRate per quote (default 0.18) โ€” the schema is ready to support per-provider rates. The Provider model does not yet have a fee rate field.


Implementation Plan

Step 1 โ€” Database: Add per-provider fee rate

File: prisma/schema.prisma

Add two fields to the Provider model:

isFoundingPro    Boolean  @default(false)
platformFeeRate  Float    @default(0.18)

isFoundingPro is a permanent label for tracking and reporting. platformFeeRate is the operative value used in fee calculations (0.12 for founding pros, 0.18 for everyone else).

Migration SQL (run as evergrn_admin โ€” DDL requires admin user, not evergrndev):

ALTER TABLE "Provider"
  ADD COLUMN "isFoundingPro"   BOOLEAN NOT NULL DEFAULT false,
  ADD COLUMN "platformFeeRate" DOUBLE PRECISION NOT NULL DEFAULT 0.18;

Run via:

$env:PGPASSWORD = "3vergrn!"
& "C:\Program Files\PostgreSQL\18\bin\psql.exe" `
  "host=evergrn-db.postgres.database.azure.com port=5432 dbname=postgres user=evergrn_admin sslmode=require" `
  -c "ALTER TABLE \"Provider\" ADD COLUMN \"isFoundingPro\" BOOLEAN NOT NULL DEFAULT false, ADD COLUMN \"platformFeeRate\" DOUBLE PRECISION NOT NULL DEFAULT 0.18;"

Then regenerate the Prisma client (kill node processes first):

Get-Process node | Stop-Process -Force
npx prisma generate

Step 2 โ€” API: Staff endpoint to register founding pros

File: src/routes/staff.js

Add a new route group for founding pro management. Accessible to any authenticated staff member (all four roles can recruit providers).

POST /staff/founding-pro/register

Creates a new Provider account with platformFeeRate = 0.12 and isFoundingPro = true.

Request body:

{
  "name": "Jane Smith",
  "email": "jane@example.com",
  "password": "temp-password-123",
  "phone": "207-555-0100",
  "services": ["lawncare", "snowplowing"],
  "serviceZips": ["04401", "04469"]
}

Response: 201 with provider object (excluding password).

Logic:

  1. Check staff token is valid (any requireStaff() role)
  2. Validate that email is not already registered
  3. bcrypt-hash the password (12 rounds, matching existing auth)
  4. Create Provider with platformFeeRate: 0.12, isFoundingPro: true
  5. Return the new provider record

GET /staff/founding-pros

Returns all providers where isFoundingPro = true. Lets staff see who has been enrolled and at what rate.

Response fields: id, name, email, companyName, services, serviceZips, platformFeeRate, isFoundingPro, createdAt

PATCH /staff/founding-pro/:id/rate

Lets staff update the fee rate on any provider (for edge cases โ€” e.g., a founding pro whose rate needs adjusting, or revoking founding pro status by setting rate back to 0.18).

Request body: { "platformFeeRate": 0.15 } โ€” validated to be between 0.05 and 0.30.


Step 3 โ€” API: Use per-provider fee rate in quote submission

File: src/routes/quotes.js

The quote creation route (POST /quotes) currently uses the hardcoded PLATFORM_FEE_RATE = 0.18. Change it to look up the submitting provider's platformFeeRate from the database.

Current code (line 50โ€“60):

const PLATFORM_FEE_RATE = 0.18
// ...
const providerAmount = parseFloat(amount)
const platformFee = parseFloat((providerAmount * PLATFORM_FEE_RATE).toFixed(2))
const totalAmount = parseFloat((providerAmount + platformFee).toFixed(2))
// ...
platformFeeRate: PLATFORM_FEE_RATE,

Change: Replace with a DB lookup of the provider's platformFeeRate:

// Already fetching providerCheck โ€” add platformFeeRate to the select
const providerCheck = await prisma.provider.findUnique({
  where: { id: req.user.id },
  select: {
    headshots: true, idDocFront: true, idDocBack: true,
    serviceZips: true, services: true,
    platformFeeRate: true,   // โ† add this
  },
})
// ...
const feeRate = providerCheck.platformFeeRate ?? 0.18
const providerAmount = parseFloat(amount)
const platformFee = parseFloat((providerAmount * feeRate).toFixed(2))
const totalAmount = parseFloat((providerAmount + platformFee).toFixed(2))
// ...
platformFeeRate: feeRate,

This is backward-compatible: any existing provider without the field gets 0.18 via the ?? fallback. Since the DB migration sets DEFAULT 0.18, the fallback is belt-and-suspenders only.


Step 4 โ€” API: Use per-provider fee rate in the payout batch

File: src/jobs/payoutBatch.js

The nightly batch uses PLATFORM_FEE_RATE = 0.18 to calculate provider share when payment.providerAmount is not already set. Update the provider select to include platformFeeRate and use it in the calculation.

Change: In the payment query's include.job.include.provider.select, add platformFeeRate: true. Then when computing the provider amount:

const feeRate = payment.job.provider.platformFeeRate ?? 0.18
const providerAmount = parseFloat((payment.amount * (1 - feeRate)).toFixed(2))

Note: payment.providerAmount is set at payment creation time and should already carry the correct value from the quote. The payout batch's hardcoded rate is only a fallback for legacy records โ€” but it should be fixed to be consistent.


Step 5 โ€” Smart Bundle estimated earnings (low priority)

File: src/routes/providers.js (lines 505โ€“506)

The Smart Bundle Engine uses PLATFORM_FEE = 0.18 to display estimated provider earnings. For founding pros seeing bundle previews, the estimated payout will be slightly low (showing 82% when they'd actually receive 88%).

Fix: fetch the authenticated provider's platformFeeRate at the top of the bundle route handler and use it in the earnings estimate. This is UX polish, not a financial error (actual payment uses the rate stored on the quote).


Step 6 โ€” Web: Staff portal page for founding pro registration

File: client/src/pages/staff/FoundingProRegister.jsx (new file)

A form-based page accessible only to logged-in staff. Route: /staff/founding-pro/register.

Fields:

Success state: Shows confirmation with the provider's name, email, and a clear label: "Founding Pro โ€” 12% platform rate".

List view: /staff/founding-pros โ€” table of all founding pros with name, email, services, serviceZips, rate, and join date.

UI conventions (per project rules):


Step 7 โ€” Provider-facing: Display the fee rate on the provider portal

Files:

When a provider views their profile/portal, show their platform fee rate. For founding pros, show a badge.

Example display:

Platform fee: 12%  [Founding Pro]
You keep: 88% of every job

For standard providers:

Platform fee: 18%
You keep: 82% of every job

The provider GET /providers/me endpoint already returns profile data โ€” add platformFeeRate and isFoundingPro to the PROVIDER_SELF_SELECT constant in src/routes/providers.js so the frontend receives them.


Step 8 โ€” Provider-facing: Display per-quote fee breakdown

Files: Quote display in mobile AvailableScreen.js / ProviderJobDetailScreen.js

When a provider submits or views a quote, the breakdown should show their specific rate, not a hardcoded "18% platform fee":

Your quote: $115.00
Platform fee (12%): $13.80
Customer pays: $128.80
You earn: $101.20

The Quote model already stores platformFeeRate per quote โ€” the data is there; it just needs to be rendered rather than assuming 18% in UI calculations.


Data Flow Summary

Staff fills out founding-pro form
  โ†’ POST /staff/founding-pro/register
  โ†’ Provider created with platformFeeRate=0.12, isFoundingPro=true

Provider logs in, views available jobs
  โ†’ GET /providers/me returns platformFeeRate=0.12, isFoundingPro=true
  โ†’ UI shows "Founding Pro ยท 12%" badge and "You keep 88%"

Provider submits quote for $100
  โ†’ POST /quotes reads provider.platformFeeRate = 0.12
  โ†’ platformFee = $12.00, totalAmount = $112.00
  โ†’ Quote stored with platformFeeRate=0.12

Customer accepts quote
  โ†’ Payment authorized for $112.00
  โ†’ PaymentIntent captured at job completion

Nightly payout batch
  โ†’ payment.job.provider.platformFeeRate = 0.12
  โ†’ providerAmount = $100 ร— 0.88 = $88.00
  โ†’ ProviderPayout created for $88.00

Files Touched

File Change
prisma/schema.prisma Add isFoundingPro, platformFeeRate to Provider
src/routes/staff.js Add POST /founding-pro/register, GET /founding-pros, PATCH /founding-pro/:id/rate
src/routes/quotes.js Read providerCheck.platformFeeRate instead of hardcoded 0.18
src/jobs/payoutBatch.js Use provider's platformFeeRate in fallback calculation
src/routes/providers.js Add platformFeeRate, isFoundingPro to PROVIDER_SELF_SELECT; use provider rate in bundle earnings estimate
client/src/pages/staff/FoundingProRegister.jsx New โ€” staff registration form
client/src/pages/staff/FoundingProList.jsx New โ€” list of founding pros
client/src/pages/ProviderPortal.jsx Show fee rate + founding pro badge
mobile/src/screens/provider/ProviderProfileScreen.js Show fee rate + founding pro badge

Security Notes


Migration Order

  1. Run DB migration (DDL via evergrn_admin)
  2. Run npx prisma generate
  3. Deploy API changes (quotes.js, payoutBatch.js, providers.js, staff.js)
  4. Deploy web changes (staff portal pages, provider portal badge)
  5. Update mobile (ProviderProfileScreen fee display)

Steps 3โ€“5 are safe to deploy before step 1 is run in staging/prod โ€” the ?? fallback ensures existing behavior is unchanged until the columns exist.


Open Questions

  1. Password reset for founding pros โ€” staff sets a temporary password. Should the API force a password change on first login, or rely on the provider to change it via the existing forgot-password flow? Recommendation: send a "Welcome โ€” please set your password" email using the existing PasswordResetToken flow immediately after creation.

  2. Founding pro cap โ€” is there a limit on how many providers can receive the 12% rate? If so, the POST /staff/founding-pro/register endpoint should enforce a count check against a configurable env var.

  3. Rate lock duration โ€” is the 12% rate permanent or time-limited (e.g., "12% for your first 12 months")? If time-limited, add a foundingProRateLockedUntil DateTime? field and add a nightly job to bump expired rates back to 0.18.

  4. Visibility to customers โ€” customers currently see totalAmount on quotes (provider amount + fee). They do not see the fee rate. No change needed to customer-facing UI.