-
Notifications
You must be signed in to change notification settings - Fork 19
Description
Overview
Add an Affiliate plugin that enables partner/referral marketing with trackable links, commission rules, conversion attribution, and payout workflows. The plugin should provide both admin tools (program management, approvals, commissions, payouts) and lightweight consumer-facing helpers (affiliate signup, dashboard, and tracking link generation).
The goal is a practical v1 that covers end-to-end affiliate operations without locking users into any specific payment processor.
Core Features
Affiliate Management
- Affiliate application + approval flow
- Affiliate profile CRUD (name, email, status, payout details)
- Affiliate status lifecycle:
pending->approved->suspended - Unique affiliate codes and custom referral slugs
Tracking & Attribution
- Track referral clicks from affiliate links
- Cookie + query-param attribution (
ref,affiliate, etc.) - Configurable attribution window (e.g. 30 days)
- First-touch / last-touch attribution mode toggle
- Conversion attribution to orders/events (manual event API + e-commerce integration)
Commissions
- Commission rules: flat amount or percentage
- Optional per-affiliate override rules
- Commission statuses:
pending->approved->paid/voided - Auto-calculate commission on attributed conversion
Payouts
- Payout batch creation for approved commissions
- Manual payout marking (v1), adapter-based automation later
- Payout history per affiliate
- CSV export for accounting
Affiliate Portal
- Affiliate dashboard: clicks, conversions, commission totals, payout history
- Generate/copy referral links for specific landing pages
- Basic marketing assets section (link templates, banners metadata)
Schema
import { createDbPlugin } from "@btst/stack/plugins/api"
export const affiliateSchema = createDbPlugin("affiliate", {
affiliate: {
modelName: "affiliate",
fields: {
name: { type: "string", required: true },
email: { type: "string", required: true },
code: { type: "string", required: true }, // public referral code
status: { type: "string", defaultValue: "pending" }, // "pending" | "approved" | "suspended"
payoutDetails: { type: "string", required: false }, // JSON (paypal/bank/crypto/etc)
notes: { type: "string", required: false },
createdAt: { type: "date", defaultValue: () => new Date() },
updatedAt: { type: "date", defaultValue: () => new Date() },
},
},
referralClick: {
modelName: "referralClick",
fields: {
affiliateId: { type: "string", required: true },
code: { type: "string", required: true },
landingPath: { type: "string", required: false },
referrer: { type: "string", required: false },
ipHash: { type: "string", required: false },
userAgent: { type: "string", required: false },
createdAt: { type: "date", defaultValue: () => new Date() },
},
},
conversion: {
modelName: "conversion",
fields: {
affiliateId: { type: "string", required: true },
clickId: { type: "string", required: false },
externalRef: { type: "string", required: false }, // order ID / event ID
amount: { type: "number", required: true },
currency: { type: "string", defaultValue: "USD" },
status: { type: "string", defaultValue: "pending" }, // "pending" | "approved" | "rejected"
createdAt: { type: "date", defaultValue: () => new Date() },
},
},
commission: {
modelName: "commission",
fields: {
affiliateId: { type: "string", required: true },
conversionId: { type: "string", required: true },
amount: { type: "number", required: true },
currency: { type: "string", defaultValue: "USD" },
status: { type: "string", defaultValue: "pending" }, // "pending" | "approved" | "paid" | "voided"
createdAt: { type: "date", defaultValue: () => new Date() },
updatedAt: { type: "date", defaultValue: () => new Date() },
},
},
payout: {
modelName: "payout",
fields: {
affiliateId: { type: "string", required: true },
amount: { type: "number", required: true },
currency: { type: "string", defaultValue: "USD" },
status: { type: "string", defaultValue: "pending" }, // "pending" | "sent" | "failed"
reference: { type: "string", required: false },
paidAt: { type: "date", required: false },
createdAt: { type: "date", defaultValue: () => new Date() },
},
},
})Plugin Structure
src/plugins/affiliate/
├── db.ts
├── types.ts
├── schemas.ts
├── attribution.ts # cookie/query attribution + matching logic
├── commission.ts # commission calculation engine
├── query-keys.ts
├── client.css
├── style.css
├── api/
│ ├── plugin.ts # defineBackendPlugin — affiliate, tracking, commission, payout endpoints
│ ├── getters.ts # listAffiliates, getAffiliateStats, listCommissions, listPayouts
│ ├── mutations.ts # approveAffiliate, trackClick, createConversion, approveCommission, createPayout
│ ├── query-key-defs.ts
│ ├── serializers.ts
│ └── index.ts
└── client/
├── plugin.tsx # defineClientPlugin — admin + affiliate portal routes
├── overrides.ts # AffiliatePluginOverrides
├── index.ts
├── hooks/
│ ├── use-affiliate.tsx # useAffiliateDashboard, useCommissions, usePayouts
│ └── index.tsx
└── components/
└── pages/
├── affiliates-page.tsx / .internal.tsx
├── affiliate-detail-page.tsx / .internal.tsx
├── commissions-page.tsx / .internal.tsx
├── payouts-page.tsx / .internal.tsx
├── affiliate-portal-page.tsx / .internal.tsx
└── affiliate-apply-page.tsx / .internal.tsx
Routes
| Route | Path | Description |
|---|---|---|
affiliates |
/affiliate/admin/affiliates |
Affiliate list + approval workflow |
affiliateDetail |
/affiliate/admin/affiliates/:id |
Affiliate profile + stats |
commissions |
/affiliate/admin/commissions |
Commission review and approval |
payouts |
/affiliate/admin/payouts |
Payout batches + history |
apply |
/affiliate/apply |
Public affiliate application page |
portal |
/affiliate/portal |
Affiliate self-serve dashboard |
Attribution API
// Public tracking pixel / redirect endpoint
GET /api/data/affiliate/track/:code?to=/landing/page
// Server-side conversion capture (e.g. checkout success)
await myStack.api.affiliate.createConversion({
externalRef: "order_123",
amount: 12900,
currency: "USD",
attribution: {
code: "partner-jane",
},
})Hooks
affiliateBackendPlugin({
attributionWindowDays?: number // default: 30
attributionMode?: "first_touch" | "last_touch" // default: "last_touch"
defaultCommissionType?: "percent" | "flat" // default: "percent"
defaultCommissionValue?: number // e.g. 20 (%), or 1000 (cents)
onBeforeApproveAffiliate?: (affiliate, ctx) => Promise<void>
onAfterConversion?: (conversion, commission, ctx) => Promise<void>
onBeforePayout?: (batch, ctx) => Promise<void>
onAfterPayout?: (payout, ctx) => Promise<void>
})Consumer Setup
// lib/stack.ts
import { affiliateBackendPlugin } from "@btst/stack/plugins/affiliate/api"
affiliate: affiliateBackendPlugin({
attributionWindowDays: 30,
attributionMode: "last_touch",
defaultCommissionType: "percent",
defaultCommissionValue: 20,
})// lib/stack-client.tsx
import { affiliateClientPlugin } from "@btst/stack/plugins/affiliate/client"
affiliate: affiliateClientPlugin({
apiBaseURL: "",
apiBasePath: "/api/data",
siteBasePath: "/pages",
queryClient,
})SSG Support
Affiliate admin and portal routes are user-specific and should be dynamic (force-dynamic). Public landing pages that read referral query params can remain static while attribution is recorded via tracking endpoint/cookie.
Non-Goals (v1)
- Multi-level referral trees
- Fraud detection / anti-abuse scoring
- Automated tax forms (W-8/W-9)
- Built-in payout processor automation (manual payout marking only)
- Multi-currency settlement logic
Plugin Configuration Options
| Option | Type | Description |
|---|---|---|
attributionWindowDays |
number |
Referral attribution window (default: 30) |
attributionMode |
`"first_touch" | "last_touch"` |
defaultCommissionType |
`"percent" | "flat"` |
defaultCommissionValue |
number |
Percent value or flat cents amount |
hooks |
AffiliatePluginHooks |
Lifecycle hooks |
Documentation
Add docs/content/docs/plugins/affiliate.mdx covering:
- Overview — affiliates, attribution, commissions, payouts
- Setup —
affiliateBackendPlugin+affiliateClientPlugin - Attribution model — cookie/query tracking and attribution window
- Commission calculation — percent vs flat examples
- Portal usage — affiliate dashboard + referral link generation
- Schema reference —
AutoTypeTablefor config + hooks - Routes — route key/path table for admin + portal pages
Related Issues
- E-commerce Plugin #78 E-commerce Plugin (order conversion source)
- Analytics Plugin #74 Analytics Plugin (attribution and revenue tracking)
- Newsletter / Marketing Emails Plugin #75 Newsletter / Marketing Emails Plugin (affiliate onboarding emails)
- CRM Plugin #76 CRM Plugin (affiliate relationship management)