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:
- Check staff token is valid (any
requireStaff()role) - Validate that email is not already registered
- bcrypt-hash the password (12 rounds, matching existing auth)
- Create Provider with
platformFeeRate: 0.12, isFoundingPro: true - 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:
- Full name (text)
- Email (email)
- Temporary password (text โ staff sets it, provider changes it after first login)
- Phone (tel)
- Services (multi-select checkboxes: Lawn Care, Snow Plowing, Handyman)
- Service ZIPs (comma-separated text field)
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):
- Inline styles only โ no Tailwind, no CSS modules
- Forest green color scheme (
#166534/#14532d) - Founding Pro badge: amber pill (
#fffbebbg,#92400etext) reading "Founding Pro ยท 12%"
Step 7 โ Provider-facing: Display the fee rate on the provider portal
Files:
client/src/pages/ProviderPortal.jsx(orProviderDashboard.jsx) โ webmobile/src/screens/provider/ProviderProfileScreen.jsโ mobile
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
- The
/staff/founding-pro/*routes are protected byrequireStaff()โ any valid staff JWT is sufficient. No customer or provider JWT can reach these routes. - The founding pro registration does not expose a public URL. There is no self-enrollment path.
platformFeeRateis set server-side only. The provider cannot update it via any self-service endpoint.- The
PATCH /staff/founding-pro/:id/rateendpoint validates the rate to a reasonable range (0.05โ0.30) to prevent accidental zeroing out.
Migration Order
- Run DB migration (DDL via
evergrn_admin) - Run
npx prisma generate - Deploy API changes (quotes.js, payoutBatch.js, providers.js, staff.js)
- Deploy web changes (staff portal pages, provider portal badge)
- 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
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
PasswordResetTokenflow immediately after creation.Founding pro cap โ is there a limit on how many providers can receive the 12% rate? If so, the
POST /staff/founding-pro/registerendpoint should enforce a count check against a configurable env var.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.Visibility to customers โ customers currently see
totalAmounton quotes (provider amount + fee). They do not see the fee rate. No change needed to customer-facing UI.