AppiFire Referral Program (30% Revenue Share)
Status: Implemented (MVP).
Admin-only Referral console at /app/referral (visible only to zainalam1001@gmail.com), subscription commission accrual, and merchant referral code entry on Billing.
Related: Shopify App Pricing · Credits wallet · Promo codes
1. What shipped
Section titled “1. What shipped”| Area | Status | Notes |
|---|---|---|
| Partner / referral codes | Yes | ReferralPartner table; unique code |
| Referral admin console | Yes | /app/referral tabs + partner detail page |
| Shop attribution | Yes | shops.referred_by_partner_id + source/source |
| Commission ledger | Yes | ReferralCommission rows (pending → approved → paid / void) |
| Program settings | Yes | ReferralProgramSettings singleton row |
| Merchant code entry | Yes | Billing card “Have a referral code?” |
| Affiliate / partner portal | No | Out of scope for MVP — admin manages partners |
| Automatic bank payouts | No | Mark paid manually; pay outside Shopify (Wise / PayPal / bank) |
| Credit-pack commissions | No | Subscription revenue only in MVP |
2. Product rules (locked for MVP)
Section titled “2. Product rules (locked for MVP)”| Rule | Value |
|---|---|
| Commission rate | 30% (3000 bps), overridable per partner |
| Qualifying revenue | Paid subscription only ($20/mo → $6/mo commission) |
| Duration | 12 months from first paid period, then stop |
| Hold | 14 days before pending → approved |
| Min payout (settings) | $50 (5000 cents) — advisory for manual payouts |
| Currency | USD |
| Code after first payment | No (default allowCodeAfterPaid = false) |
| Attribution | One shop → one partner; merchant code or admin assign |
Free plan installs alone do not earn commission.
Example: referred shop pays $20/mo → partner earns $6/mo for up to 12 months while the shop stays paid (max $72 lifetime per referral if they stay paid the full window).
3. Routes and files
Section titled “3. Routes and files”| Area | Route / location | Purpose |
|---|---|---|
| Admin console | /app/referral | Tabs: Overview, Partners, Earnings, Attributions, Settings |
| Partner detail | /app/referral/partners/:id | Profile, referred shops, mark commissions paid |
| Merchant code entry | /app/billing | “Have a referral code?” card |
| Accrual (confirm) | app/routes/app.billing.confirm.jsx | Accrue on paid subscription confirm |
| Accrual / void (webhook) | app/routes/webhooks.app_subscriptions.update.jsx | Accrue on ACTIVE renew; void pending on cancel |
| Accrual / void (sync) | app/lib/billing-plans.server.js | Accrue on paid sync; void when plan drops to free |
| Shared helpers | app/lib/referral.shared.js | Code normalize/format, rates, USD formatting |
| Server logic | app/lib/referral.server.js | Accrue, approve held, void, apply code, assign |
| Schema | prisma/schema.prisma | Models + shop attribution fields |
| Migration | prisma/migrations/20260713120000_add_referral_tables/ | Tables + shop columns + settings seed |
Admin route files:
app/routes/app.referral.jsxapp/routes/app.referral_.partners.$id.jsx(URL/app/referral/partners/:id, pathless nesting escape)
4. Access control
Section titled “4. Access control”Same allowlist as Promo codes: zainalam1001@gmail.com.
| Piece | Implementation |
|---|---|
| Allowlist email | PROMO_ADMIN_EMAIL in app/lib/promo.shared.js |
| Check | canAccessPromoAdmin / assertPromoAdmin in app/lib/promo.server.js |
| Nav | showReferralAdminNav in app/routes/app.jsx → Referral link |
| Route guard | Every /app/referral* loader/action calls assertPromoAdmin |
Access passes if session email or shop email matches the allowlist. Merchants never see partner management, earnings, or settings — nav hide alone is not enough; loaders return 403.
5. Data model
Section titled “5. Data model”5.1 ReferralPartner
Section titled “5.1 ReferralPartner”| Field | Purpose |
|---|---|
id | UUID |
code | Unique referral code (normalized, no hyphens stored) |
name, email | Partner contact |
status | active | paused | rejected |
commissionRateBps | Default 3000 (= 30%) |
payoutCurrency | USD |
notes | Admin notes |
createdAt / updatedAt | — |
5.2 Shop attribution (on shops)
Section titled “5.2 Shop attribution (on shops)”| Field | Purpose |
|---|---|
referredByPartnerId | FK nullable |
referredAt | When attribution was set |
referralSource | code | admin |
referralCodeEnteredAt | When merchant entered a code |
5.3 ReferralCommission
Section titled “5.3 ReferralCommission”| Field | Purpose |
|---|---|
partnerId / shopId | FKs |
periodStart / periodEnd | Billing period |
revenueCents | Qualifying revenue for period |
commissionCents | Rate × revenue |
source | subscription (MVP) |
shopifyChargeId | Charge / subscription id when available |
status | pending | approved | paid | void |
paidAt / payoutReference | Manual payout tracking |
Idempotency: unique on (shopId, periodEnd, source).
5.4 ReferralProgramSettings (singleton id = singleton)
Section titled “5.4 ReferralProgramSettings (singleton id = singleton)”| Field | Default |
|---|---|
enabled | true |
defaultCommissionRateBps | 3000 |
revenueBasis | subscription |
durationMonths | 12 |
holdDays | 14 |
minPayoutCents | 5000 |
attributionWindowDays | 90 (reserved for future cookie/landing) |
allowMerchantCodeEntry | true |
allowCodeAfterPaid | false |
6. Commission flow
Section titled “6. Commission flow”flowchart TD install[Shop installs] --> attr[Attribute via Billing code or admin] attr --> free[Shop on free plan] free --> upgrade[Shop upgrades to paid] upgrade --> webhook[Billing confirm / subscription webhook / plan sync] webhook --> calc[Create ReferralCommission pending] calc --> hold[Hold 14 days] hold --> approve[Approve on next Referral admin load] approve --> balance[Approved unpaid balance] balance --> payout[Admin marks paid after Wise/PayPal transfer]Accrual
Section titled “Accrual”accrueCommission in app/lib/referral.server.js:
- Program must be enabled; shop must have an active partner attribution.
- Still inside 12 months from the first non-void commission period (or this period if first).
- Creates
pendingrow for$20revenue × partner rate (default 30% =$6). - Skips quietly if already accrued for that
(shopId, periodEnd, source).
Approval
Section titled “Approval”On each admin Referral page load, approveHeldCommissions moves pending → approved when older than holdDays.
On subscription cancel / decline / plan sync to free, voidCommissionsForShop voids pending rows for that shop.
7. Admin console UI
Section titled “7. Admin console UI”Single side-nav item Referral → /app/referral with tabs:
| Tab | What you do |
|---|---|
| Overview | KPIs (active partners, referred/paying shops, pending/approved/paid sums), recent attributions & earnings |
| Partners | Create / edit / pause / activate; App Store link with ref=CODE; open detail |
| Earnings | Filter by status/partner; bulk approve, void, mark paid (optional payout reference) |
| Attributions | Assign by shop domain; search; clear (force override for paid shops) |
| Settings | Toggle program, rates, duration, hold, min payout, merchant code entry flags |
Partner detail (/app/referral/partners/:id): profile, referred shops, commissions, mark approved → paid with payout reference.
Public App Store link format:
https://apps.shopify.com/appifire-ai-chat?utm_source=referral&ref=CODE
(Shopify does not always pass ref into the app; merchants should still enter the code on Billing, or you assign in Attributions.)
8. Merchant-facing (Billing)
Section titled “8. Merchant-facing (Billing)”On /app/billing:
- Card “Have a referral code?” when settings allow entry and the shop is not yet attributed.
- After apply: shows attributed partner code; cannot re-apply.
- Blocked when shop is already
paidunlessallowCodeAfterPaidis enabled in Settings. - Self-referral blocked when shop email matches partner email.
9. How to operate (admin checklist)
Section titled “9. How to operate (admin checklist)”- Run migration if needed:
npx prisma migrate deploy(ormigrate devlocally). - Open the app as
zainalam1001@gmail.com→ Referral. - Partners → create partner (name, email, code).
- Share the App Store
ref=link and/or tell the merchant to enter the code on Billing. - When they go paid, a
pendingcommission appears (Overview / Earnings / partner detail). - After the hold, status becomes
approved(auto on admin load). - Pay the partner outside Shopify; Mark paid and store Wise/PayPal reference.
- Adjust program knobs under Settings without redeploying.
10. Economics (reference)
Section titled “10. Economics (reference)”| Metric | Value |
|---|---|
| Paid plan | $20 / month |
| Partner share | 30% → $6 / month |
| Max duration | 12 months → $72 lifetime per paid referral (if they stay paid) |
| 10 active referred paid shops | $60 / month partner cost |
| Your net from those 10 | $140 / month after commissions |
11. Out of scope / later
Section titled “11. Out of scope / later”- Merchant-to-merchant “refer a friend for free credits” (use promo codes).
- Multi-level / MLM commissions.
- Automatic ACH inside Shopify Admin.
- Non-USD payouts.
- Public partner apply form / self-serve portal.
- Cookie / landing-page install attribution.
- Commissions on one-time AI credit packs (
revenueBasis = subscription_and_creditsis stored but not accrued yet). - CSV export (can add later; ledger is in-admin today).
12. Doc history
Section titled “12. Doc history”This file began as a design plan. MVP decisions locked and built:
- Subscription-only revenue, 12-month cap, 14-day hold, $50 min payout setting.
- Single
/app/referralpage with tabs (not separate nav items). - Reuse
canAccessPromoAdminas-is. - Merchant code entry only before first payment (default).
- Void pending commissions on cancel/downgrade within hold.