# PropMine Affiliate Approval Workflow

_Last updated: 2026-06-12_

Purpose: approve affiliates manually, assign a promo code, and unlock the affiliate dashboard without overbuilding an admin system.

## Current Status

Already supported:
- Affiliate intake fields are defined in `AFFILIATE_ONBOARDING_FORM_FIELDS.md`.
- Tally intake is connected on the live `/affiliate` page.
- Terms/checklist language is drafted in `AFFILIATE_TERMS_V1_DRAFT.md`.
- Affiliate dashboard reads Clerk metadata for status, promo code, approved date, payout status, terms version, affiliate ID, payout threshold, and unpaid balance.
- Affiliate summary/payout request endpoints require an approved affiliate status and promo code.
- First affiliate test flow has been completed; do not treat the dashboard unlock/test run as unproven work.

Still manual for launch batch one:
- Review real applicants.
- Create Stripe promo code for each approved affiliate.
- Add affiliate metadata to Clerk using the canonical schema below.
- Send approval message.
- Add row to the live affiliate tracker/ledger.
- Review Stripe ledger before issuing payouts.

Technical hardening still needed before scale:
- Automatically map Stripe promotion-code usage to customer metadata as `affiliateCodeUsed`.
- Produce Stripe-backed payout ledger rows from actual invoice/payment/fee/refund/dispute data.
- Add an internal operator shortcut/admin flow only after the manual process exposes real friction.

## Recommended Status Values

Use these exact values:

- `pending` — application received, not approved yet
- `approved` — approved and active
- `paused` — temporarily disabled / under review
- `rejected` — application rejected
- `terminated` — removed from program

The dashboard treats these as active/approved:
- `approved`
- `active`
- `enabled`
- `live`

Recommendation: use `approved` only, unless a future tool needs the aliases.

## Approval Steps

### 1. Receive application

Application should collect:
- legal name
- display/creator name
- email
- main platform/link
- audience size/focus
- requested promo code
- terms acceptance checkboxes
- timestamp and submitted email

After an application is received, mark the Clerk user as pending so `/affiliate` shows the submitted/review state instead of the application form:

```json
{
  "propmine": {
    "affiliate": {
      "status": "pending",
      "applicationSubmittedAt": "2026-06-12T00:00:00.000Z"
    }
  }
}
```

Operator helper:

```bash
node scripts/affiliate/update_affiliate_clerk_metadata.js --email user@example.com --status pending --apply
```

Use dry-run first by omitting `--apply`.

Automated pending-state path:
- Tally should POST new submissions to `https://propmine.finishernetwork.com/internal/affiliate/tally-webhook?secret=<PROPMINE_TALLY_WEBHOOK_SECRET>`.
- The webhook only marks the matching Clerk user as `pending`; it never approves affiliates.
- Approval, promo-code assignment, and approval email remain manual.
- The endpoint writes `reports/propmine/affiliate_tally_webhook_latest.json` for the latest webhook result.

### 2. Review applicant

Approve only if:
- audience/channel is real enough to verify
- promotion fit is sports/betting/DFS/fantasy adjacent
- no obvious coupon-site or spam behavior
- applicant accepted terms
- requested code is brand-safe

Do not approve if:
- coupon/deal-site intent is clear
- guaranteed-profit betting claims are present
- applicant wants to use their own code
- promotion method is misleading, spammy, or compliance-risky

### 3. Pick final promo code

Rules:
- uppercase
- short and memorable
- no spaces
- avoid offensive/risky words
- avoid generic codes like `FREE`, `VIP`, `SAVE`, `PROP`, `MLB`

Example:
- requested: `JARED30`
- approved code: `JARED30`

### 4. Create Stripe promo code

Create a Stripe promotion code with:
- customer discount: 30% off
- first purchase only
- eligible PropMine plans: monthly, 3-month, 6-month, yearly
- code: approved affiliate promo code

Record:
- Stripe coupon ID
- Stripe promotion code ID
- approved code

### 5. Add Clerk affiliate metadata

Add this to the approved affiliate user in Clerk metadata.

Recommended location: `private_metadata.propmine.affiliate`

```json
{
  "propmine": {
    "affiliate": {
      "status": "approved",
      "promoCode": "CODEHERE",
      "affiliateApprovedAt": "2026-06-11T00:00:00.000Z",
      "affiliateTermsVersion": "v1",
      "affiliateId": "aff_CODEHERE",
      "payoutStatus": "not_requested",
      "payoutThreshold": 25,
      "accruedUnpaidBalance": 0,
      "stripePromotionCodeId": "promo_...",
      "stripeCouponId": "coupon_..."
    }
  }
}
```

Minimum fields required for dashboard unlock:

```json
{
  "propmine": {
    "affiliate": {
      "status": "approved",
      "promoCode": "CODEHERE",
      "affiliateApprovedAt": "2026-06-11T00:00:00.000Z",
      "affiliateTermsVersion": "v1",
      "affiliateId": "aff_CODEHERE",
      "payoutStatus": "not_requested",
      "payoutThreshold": 25
    }
  }
}
```

Notes:
- Keep payout/account details private. Do not store sensitive tax docs in Clerk.
- The dashboard accepts alternate key names, but use the canonical names above.

### 6. Add affiliate tracker row

Mission Control approval now auto-creates/updates the private master tracker row when an application is approved through the protected `Affiliate Applications` dashboard action.

The private tracker row records at minimum:
- affiliate legal name / display name when captured
- email
- status
- promo code
- Stripe promo code ID when supplied
- terms version accepted
- approved date / submitted date context
- payout method/tax placeholders
- unpaid balance defaults

If approval is performed outside Mission Control, update the private tracker manually.

### 7. Send approval message

Use the template in `AFFILIATE_ONBOARDING_FORM_FIELDS.md`.

Approval message must include:
- promo code
- customer offer: 30% off first purchase
- commission: 20% of net collected revenue after fees
- eligible cycles: first two successful billing cycles
- payout threshold: $25
- required disclosure
- approved promo copy

### 8. Verify dashboard access

After metadata is saved:
- affiliate signs in at `/affiliate`
- dashboard should show promo code
- payout bar should show current balance / $25
- referral breakdown should load if referral metadata exists

## Customer Attribution Metadata

For referred customers, store the affiliate code used in customer metadata so the affiliate dashboard can count referrals.

Recommended location: `private_metadata.propmine`

```json
{
  "propmine": {
    "affiliateCodeUsed": "CODEHERE",
    "billingPlan": "monthly",
    "subscriptionStatus": "active",
    "subscriberActive": true
  }
}
```

Accepted keys already supported by the API include:
- `affiliateCodeUsed`
- `referralCode`
- `referredByCode`
- `signupAffiliateCode`
- `affiliatePromoCode`
- `signupPromoCode`
- `promoCode`
- `promotionCode`
- `stripePromotionCode`
- `stripeCouponName`

Recommendation: use only `affiliateCodeUsed` going forward.

## Launch Recommendation

Keep approval manual for the first affiliate batch. The intake form and first test run are already done; the remaining launch risk is operational accuracy, not basic form/dashboard existence.

Manual approval is safer because it prevents:
- coupon-site abuse
- self-use
- bad claims
- duplicate/low-quality affiliates
- payout confusion before the ledger is mature

Automation can come later after the first 5–10 affiliates expose the real workflow friction.
