Appearance
Implementation handoff
This plan is for a later implementation agent. It is dependency-ordered and intentionally starts with provider proof and accounting invariants, not a Whop call. Re-read the repository and current Whop docs at implementation time; the research snapshot is 2026-09-01.
Non-negotiable implementation rules
- Do not add Whop dispatch to
utils/rails/dispatcher.tsor either current payout send route. - Do not let
metric-scrapercalculate earnings, choose rates, deplete budgets, or call Whop. - Do not make a provider call inside a Prisma/Postgres transaction.
- Do not automatically retry a timeout with a new idempotency key.
- Do not trust
WhopIdentityas a payment mapping without the new verification flow. - Do not use floats for new money/rate fields.
- Do not hard-delete a financially referenced campaign, submission, journal entry, payout item, provider operation, webhook, or audit event.
- Existing payout data is development-only and need not migrate, but preserve it until the cutover phase explicitly retires its code paths.
Phase 0 — freeze decisions and prove provider capability
Current execution status and the copy-ready provider request are maintained in 08-phase-0-execution.md.
Work
- Owners answer 07-open-decisions.md and record a signed-off policy version.
- Whop answers the provider questions in 02-whop-money-api-research.md, including direct
user_…capability and exact scopes. The source is not open for redesign: it must resolve to BloxClips' owner-confirmed Whop business balance. - Run the read-only then minimal sandbox sequence in 05-sandbox-validation.md. Preserve redacted evidence.
- Confirm legal/tax owner requirements for automatic balance credits. Do not assume the existing external-payout tax gate carries over unchanged.
- Select and record the API pin; as of research it is
2026-08-31. Recheck the changelog and installed SDK compatibility before coding.
Acceptance criteria/tests
- Sandbox host/key/origin/recipient are separately identified; the origin is demonstrably the sandbox BloxClips business balance, and no production secret/ID is used.
- Account identity, balance fields, direct recipient, one minimal transfer, same-key replay, retrieval/list, and signed webhook each have pass/fail evidence.
- Exact current permissions and BloxClips account enablement are documented by dashboard evidence or Whop support.
- Every owner choice is translated into named constants/policy fields and expected examples.
Gate
Do not start Phase 1 until the product policy is frozen enough to define schema units and the primary/fallback destination type is known. If sandbox ledger transfer is unavailable, Phase 1 may proceed only as provider-neutral local accounting work; no Whop adapter/dispatch work may be represented as validated.
Phase 1 — add exact money primitives and append-only schema
Likely files/modules
Bloxclips-backend/prisma/schema.prisma- new migration under
Bloxclips-backend/prisma/migrations/<timestamp>_add_earning_and_payout_ledger/ - new
Bloxclips-backend/src/domain/money.ts - new
Bloxclips-backend/src/domain/earnings/modules for policy types, state transitions, and invariants - new
Bloxclips-backend/src/domain/payouts/modules for state transitions and audit commands - backend test helpers/fixtures alongside those modules
Work
- Implement integer micro-USD parsing/formatting/arithmetic with no implicit
numberconversion. Define sign conventions and cumulative-target rounding. - Add policy/version, earning/state, campaign-budget, payout-run/item/allocation, provider-operation/attempt, webhook inbox, payment identity, and financial audit models from the target architecture.
- Add database constraints/indexes: unique source keys, effective-interval protection, unique active payment identity, unique allocation, unique schedule slot, unique idempotency/provider IDs per environment, check constraints for currency/amount/state, and restricted deletes.
- Implement state-transition functions that reject illegal transitions and require actor/reason/correlation. The mutation and audit insert occur in one transaction.
- Add projection/reconciliation queries, but make journals authoritative.
- Leave all current payout routes and calculations unchanged during this phase; new tables receive no production dispatch.
Acceptance criteria/tests
- Property/table tests prove many small view deltas equal one cumulative calculation under the chosen rate segment.
- Values at zero, one view, sub-cent, cap boundary,
BigIntlimit, and negative correction serialize correctly through Prisma/API DTOs. - Duplicate source/allocation/scheduled slot/idempotency/provider event is rejected by the database, not only application code.
- Illegal state transitions, missing audit actor/reason, currency mismatch, and hard delete fail.
- Sum-based invariants can be recomputed from journals with no mutable counter dependency.
- Migration applies to a fresh dev database and rolls back in a disposable database without touching current dev payout rows.
Gate
Do not start Phase 2 until schema constraints, money property tests, transition tests, and fresh-database migration pass. No route may read the new projection as authoritative yet.
Phase 2 — implement rate policy and shadow accrual at the metric boundary
Likely files/modules
Bloxclips-backend/src/utils/tracking/scrapeJobs.tsBloxclips-backend/src/utils/tracking/runTrackingTick.tsBloxclips-backend/src/utils/campaignBudget.ts(retain old exports temporarily; route new accrual around them)Bloxclips-backend/src/utils/calculateSubmissionEarnings.ts(shadow comparator only)Bloxclips-backend/src/api/routes/admin.tscampaign/submission mutationsBloxclips-backend/src/api/routes/adminClipperGroups.tsBloxclips-backend/src/utils/clipperGroups/domain.tsBloxclips-backend/src/utils/clipperGroups/campaignLifecycle.ts- new
src/domain/earnings/accrueMetricDelta.ts,resolveRate.ts,correctEarning.ts, and reconciliation service - frontend campaign/group form/API/type files under
features/admin/campaign-management/andfeatures/admin/clipper-groups/
Work
- Add distinct CPM/default RPM inputs with explicit per-1,000 units and currency. Add group RPM and creator/campaign override management with immutable effective versions.
- Enforce launch/funding/rate/cap policy and the chosen group-membership rule.
- In the backend
ScrapeJobapplication transaction, lock campaign/submission, calculate accepted delta, allocate CPM budget, and post earning/budget/state/audit entries exactly once. - Keep the scraper result technical. Delete no direct payout-rescrape code yet, but ensure the new journal never calls it.
- Implement approval, rejection, late-result, campaign-end/depletion, group auto-archive, cap, flat-fee, and correction commands.
- Run new accrual in
shadowmode. Compare old mutable estimates only as diagnostics; differences caused by intended CPM/RPM separation or snapshots need classified explanations, not forced equality. - Prevent new hard deletes when a source has financial entries; add soft-delete/archive behavior.
Acceptance criteria/tests
- Rate precedence tests cover individual > group > campaign, no override, archived group, effective-time change, invalid overlaps, and RPM above CPM exception policy.
- Budget tests cover simultaneous submissions, partial last delta, duplicate job, top-up, correction, depletion without auto-reopen, and CPM unchanged when RPM falls.
- Lifecycle tests cover pre-approval metrics, approval catch-up, later views, rejection before/after reserve, campaign end, final grace scrape, group auto/manual archive, and late correction.
- The same
ScrapeJobapplied twice produces one earning and one budget effect. - Two concurrent job applications cannot exceed campaign funding.
- Shadow reports identify every discrepancy by policy category and contain no unclassified amount drift.
Gate
Do not start Phase 3 until accrual/budget concurrency tests pass, all shadow differences are explained, and no scraper module contains financial/provider policy.
Phase 3 — payment identity, Whop adapter, and webhook inbox with dispatch disabled
Likely files/modules
Bloxclips-backend/src/utils/whopClient.ts(retain chat client compatibility; introduce explicit environment/API pin)- new
Bloxclips-backend/src/integrations/whop/payoutClient.ts - new
Bloxclips-backend/src/integrations/whop/transferMapper.ts - new
Bloxclips-backend/src/domain/payouts/paymentIdentity.ts - new route
Bloxclips-backend/src/api/routes/payoutIdentity.ts - new money webhook route/module, separate from chat side effects, near
src/api/routes/webhooks/whop.ts Bloxclips-backend/src/api/index.tsfor raw-body ordering/registrationBloxclips-backend/.env.templatefor explicitly separate payout environment/base/key/origin/webhook/API-date variables- frontend payout identity API/types/components under
BloxClips-frontend/features/payouts/
Work
- Build a money-specific SDK client factory that asserts environment/host/origin, pins API date, sets bounded timeout, disables SDK automatic retries, and never logs secrets.
- Implement typed adapter methods only for validated balance/recipient/create/retrieve/list operations. Map responses exhaustively; unknown status/shape is an error requiring review.
- Implement creator payment-identity connect/confirm/revoke/revalidate. Existing
WhopIdentitymay prefill a candidate but not activate it. - Add webhook raw-body verification, five-minute replay check through the official helper, unique inbox insertion, account/environment/API-date validation, and async processing that retrieves current transfer state.
- Build an in-memory/fake Whop adapter for deterministic failure/crash tests. Keep real dispatch feature disabled.
Acceptance criteria/tests
- Client refuses production hostname with sandbox mode and refuses an origin/environment mismatch.
- No browser bundle/API response contains key, webhook secret, signature, or privileged token.
- Identity tests cover absent/stale/duplicate/mismatch/revocation and chat mapping not being sufficient.
- Webhook tests cover valid, tampered, stale, duplicate, out-of-order, wrong account, wrong API date, unknown event, and failed-example inconsistency followed by retrieve.
- Adapter tests cover exact amount/currency/parties, processing/succeeded/failed, unknown fields, timeout, 409, 429, and 5xx without hidden SDK retry.
Gate
Do not start Phase 4 until payment identity is demonstrably consented/unique, webhook inbox is durable/idempotent, adapter contract tests match sandbox evidence, and real dispatch remains off.
Phase 4 — durable automatic reservation, dispatch, retry, and reconciliation
Likely files/modules
- new
Bloxclips-backend/src/domain/payouts/createRun.ts - new
reservePayableEarnings.ts,dispatchProviderOperation.ts,reconcileProviderOperation.ts, andprocessWebhookInbox.ts - new
Bloxclips-backend/src/utils/payouts/scheduler.tsor equivalent durable scheduler module Bloxclips-backend/src/api/index.ts/src/scheduler.tsfor startup registration- do not reuse
src/utils/payouts/processRequest.ts,rescrape.ts, orutils/rails/dispatcher.ts
Work
- Add daily scheduled-slot creation under PostgreSQL advisory lock.
- Reserve one creator/currency item using row locks/
SKIP LOCKED, unique allocations, threshold, holds, identity, negative balance, tax/legal gate, and exact sub-cent carry. - Implement prepare/claim/external/result transaction boundaries with leases and attempt records.
- Implement the documented retry classification. Timeouts and ambiguous conflicts become
UNKNOWN; reconcile before any resend. - Add 15-minute open-operation, daily seven-day-window, and weekly aggregate reconciliation.
- Add structured metrics/logging and alerts. Real adapter remains restricted to sandbox.
- Provide constrained admin commands: reconcile, release known-safe retry, resolve identity/funding issue, cancel before possible send, and propose exceptional supersession. No arbitrary amount or key input.
Acceptance criteria/tests
- Two schedulers create one run; two workers reserve an earning once; two dispatch calls claim one operation.
- Crash at every boundary (before call, request accepted/response lost, response received/commit lost, webhook before response) converges to one transfer.
- Repeated schedule execution and duplicate event delivery do not change total paid.
- Timeout never becomes retryable solely by elapsed time; after 24 hours, create is blocked absent formal no-movement evidence.
- Failed-then-succeeded/reversed provider states produce monotonic audit and correct local recovery state.
- Insufficient funds, permission failure, stale identity, amount mismatch, and reconciliation mismatch alert/block correctly.
- Every state mutation has same-transaction audit and every provider attempt has a correlation record.
Gate
Do not start Phase 5 until deterministic concurrency/chaos tests prove exactly-once economic effect and at-most-one semantic provider operation, and the complete sandbox validation passes with dispatch restricted to sandbox.
Phase 5 — replace creator/admin read and control surfaces
Backend likely files
- replace behavior in
Bloxclips-backend/src/api/routes/payouts.tswith ledger balance/history/identity reads; remove creator request from primary contract - new admin reconciliation routes rather than extending
adminPayoutReview.ts Bloxclips-backend/src/api/routes/admin.tsto remove legacy eligible/process exposure after cutover gateBloxclips-backend/src/api/index.tsregistration
Frontend likely files
BloxClips-frontend/features/payouts/api/payouts.ts,types.ts,lib/status.ts, hooks and overview componentssurfaces/content-rewards/screens/payouts/PayoutOverviewScreen.tsxand its balance/history/notices components- retire primary method UI under
surfaces/.../payouts/method/in favor of Whop identity/connect and “withdraw in Whop” guidance - admin payout list/detail feature APIs/types/formatters/screens/components
- admin user detail payout action and status components
- submission/campaign displays that currently show mutable estimates or ambiguous custom rate units
Work
- Creator API returns separately: held/estimated, payable, reserved/scheduled, paid, recovery/hold reasons, sub-cent carry, threshold, next sweep, and payment identity status.
- Remove “request cashout” as the normal action. Provide connect/revalidate Whop and link to Whop withdrawal after paid.
- Admin UI becomes reconciliation/incident visibility, not a per-creator approval/send queue. Exceptional actions reflect role and state-machine commands.
- Use shared backend DTO schemas/status vocabulary. Unknown states render as “needs review,” never success.
- Decide/refactor referral/tax presentation according to owner decision; do not silently add affiliate balance to creator content payout.
Acceptance criteria/tests
- Frontend displayed totals equal backend ledger projections for fixtures including holds, carries, corrections, reservations, unknown, failed, paid, and reversed.
- No creator request or staff send button can reach old endpoints under new mode.
- Creator cannot access another identity/ledger or any reconcile/retry command.
- Support/operator/finance permissions match the matrix; TOTP/step-up remains for exceptional finance commands where required.
- Accessibility and copy distinguish credit to Whop from external withdrawal.
Gate
Do not start Phase 6 until contract/E2E tests pass and every creator/admin money number has one documented backend projection.
Phase 6 — shadow verification and clean development cutover
Work
- Deploy schema and shadow accrual with dispatch off. Run journal invariants and compare accepted sources/rate decisions/budget against manually calculated fixtures.
- Since existing payout data is development-only, choose a cutover timestamp and start the new ledger from a clean explicitly seeded baseline. Do not delete old rows; mark old UI/routes read-only/disabled by feature flag.
- Stop
POST /api/payouts/request,adminPayoutReviewapprove/send, andadmin.tseligible/process/bulk routes. StopprocessPayoutRequeststartup resume and payout-timerescrapeForPayoutcalls. - Switch authoritative balance/history reads to the new ledger.
- Confirm there are zero old in-flight states that could still dispatch and no process still calls legacy rails for creator earnings. Existing unrelated provider/tax code may remain isolated.
Acceptance criteria/tests
- Code search and route tests prove all creator-money writes go through new domain commands.
- No
REQUESTED,SCRAPING,READY_FOR_REVIEW,AWAITING_SEND, or legacy process worker can call an external rail. - No new accounting reads
Submission.paidOut,paidAmount,paidViewsTotal,paidAmountTotal,PayoutItem.grossAmount, or mutable rate/view totals. - Journal/budget/allocation/provider aggregates reconcile exactly at cutover.
- Rollback feature flag restores old read-only UI if needed, but never re-enables both send systems concurrently.
Gate
Do not start Phase 7 until the old send paths are proven inert, shadow invariants remain clean for the agreed observation period, and rollback cannot activate two money movers.
Phase 7 — sandbox end-to-end rollout
Work
- Enable automatic scheduling and Whop dispatch only in a seeded development/staging environment pointed at sandbox.
- Execute every test in
05-sandbox-validation.md, plus multi-creator batching, holds, insufficient balance, unknown timeout harness, duplicate run, and webhook/reconciliation recovery. - Run for at least two complete scheduled cycles with no manual database correction.
- Produce a go/no-go evidence report with totals: earned, budget debited, payable, reserved, provider succeeded/failed/fees, paid, carry, and differences (must be zero except explicitly modeled fees/carry).
Gate
Do not start Phase 8 until all sandbox and chaos acceptance tests pass, Whop reconfirms production capability/limits, finance funds the origin/buffer, security reviews secrets/webhooks/roles, and owners sign the report.
Phase 8 — controlled production activation
This phase requires separate explicit authorization; the current audit does not grant it.
- Deploy with accrual authoritative but production dispatch off.
- Verify that the production origin is exactly BloxClips' Whop business balance, then verify API pin, scopes, webhook delivery, available/pending/reserve fields, alerts, and dashboards through read-only checks.
- Enable dispatch for a tiny allowlisted cohort and a low daily aggregate cap. No synthetic/real transfer solely for testing without authorization.
- Reconcile each canary manually via API and balance before widening cohort/cap.
- Increase gradually only after scheduled observation windows and zero unexplained mismatch.
- Keep legacy creator rails permanently disabled; remove their code/schema only in a later cleanup migration after retention requirements are set.
Production acceptance
- Every transfer links to immutable earnings, provider operation, signed event/API state, and audit.
- Alerting/on-call runbooks have been exercised.
- Provider and local daily totals reconcile exactly, with fees/carries separate.
- Creators see correct Whop credit and use Whop—not BloxClips—to withdraw externally.
Rollback strategy
Rollback is stop-dispatch and reconcile, never “switch back to the legacy payer.”
- Disable creation/claim of new provider operations with a server-side kill switch; leave webhook ingestion and reconciliation running.
- Allow already possible/sent operations to settle or become
UNKNOWN; never release their reservations or resend until provider state is known. - Keep accrual/budget journals running if correct. If accrual itself is suspect, pause new accrual application while preserving
ScrapeJobresults for later idempotent replay. - Revert frontend to read-only held/payable/history messaging; do not restore cashout/send actions.
- Restore application binaries only if schema is backward compatible. Do not drop new journal tables or erase audit while money is unresolved.
- After reconciliation, cancel only operations proven never sent, release their allocations transactionally, and document every action.
- Correct accounting with new entries; never edit original earnings, paid states, provider IDs, or evidence.
Exact starting instruction for the next agent
Begin with Phase 0 only: obtain owner decisions and a clearly isolated Whop sandbox/account-capability result. If provider validation remains unavailable, proceed only with a reviewed Phase 1 provider-neutral schema/ADR proposal—do not write a Whop transfer call or connect the existing payout flows.