Skip to content

Referral program overhaul: commission, admin management, automated payouts#164

Merged
v0l merged 11 commits into
masterfrom
feat/referral-program
Jul 16, 2026
Merged

Referral program overhaul: commission, admin management, automated payouts#164
v0l merged 11 commits into
masterfrom
feat/referral-program

Conversation

@v0l

@v0l v0l commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Completes the referral program end-to-end. The payout half was previously dead code (insert/update_referral_payout were never called); commission had no configurable rate; and there were no admin management or user self-service endpoints. This adds all of it, delivered as four reviewable increments (see work/referral-program-apis.md).

PR1 — Commission model + flexible payout mode (4048398)

  • Commission = a configurable percentage of each referred VM's first payment. Effective rate is per-referrer override (referral.referral_rate, admin-set) falling back to a company default (company.referral_rate).
  • Replaced the use_nwc boolean with an extensible ReferralPayoutMode enum column (lightning_address | nwc | account_credit). POST/PATCH/GET /api/v1/referral use mode; account_credit is reserved/rejected. Migration maps use_nwc=1 → nwc.
  • earned and the admin referral report now report commission (amount * effective_rate%).

PR2 — Admin management + RBAC (4d19600)

  • New AdminResource::Referral = 25 (explicit per-feature grant migration to super_admin).
  • GET /api/admin/v1/referrals (paginated; search by code or 64-char hex pubkey), GET /api/admin/v1/referrals/{id} (earnings + payouts + counts), PATCH (set/clear the per-referrer override), GET/POST .../payouts (list / manual payout), PATCH .../payouts/{id} (mark paid / reconcile invoice + preimage). NWC secrets never exposed.

PR3 — Automated payout worker (8980e48)

  • Dedicated ReferralPayoutHandler (lnvps_api/src/referral/mod.rs) — kept out of SubscriptionHandler.
  • Accrual (owed BTC = commission − paid/reserved), reserve-then-pay (delete reservation on failure → no double-pay), pays via LNURL-pay or NWC make_invoice, records preimage, notifies referrer. Only BTC is auto-paid; fiat left for manual payout.
  • Opt-in via a referral config section (min-payout-sats, default 1000); WorkJob::ProcessReferralPayouts scheduled hourly only when configured. ApiReferralPayout exposes pre_image.

PR4 — User self-service (b5dac33)

  • DELETE /api/v1/referral (leave; 409 while a payout is pending or paid history exists).
  • GET /api/v1/referral/usage — per-referred-VM breakdown.

Notes

  • DB migrations: referral_rate (company + referral), use_nwc → mode, Referral RBAC permissions.
  • Includes a workspace-wide cargo fmt commit (32ee938).
  • All crates build; unit tests green (lnvps_db, lnvps_api, lnvps_api_common incl. --features admin). Docs updated: ADMIN_API_ENDPOINTS.md, API_CHANGELOG.md.

v0l added 9 commits July 16, 2026 12:13
Commission model — percentage of a referred VM's first payment:
- company.referral_rate (default) + referral.referral_rate (per-referrer
  override, admin-controlled); effective rate = override ?? company default
- earned amounts + admin referral report now report commission = amount * rate%
- admin company create/update/get expose referral_rate

Payout mode — replace referral.use_nwc bool with an extensible
ReferralPayoutMode enum column (lightning_address | nwc | account_credit):
- POST/PATCH/GET /api/v1/referral use 'mode' instead of 'use_nwc'
- account_credit reserved for a future account-balance payout (rejected now)
- migration maps use_nwc=1 -> nwc

Includes migrations, model/mock/e2e updates, tests, docs + changelog.
Part of work/referral-program-apis.md (PR1 of 4).
- Add AdminResource::Referral = 25 (Display/FromStr/TryFrom/all) + explicit
  permission migration granting super_admin (per-feature convention)
- DB: admin_list_referrals (paginated; search by code substring or 64-char hex
  pubkey via SQL HEX()), admin_get_referral; update_referral_payout also sets
  invoice; mock impls + tests
- New admin/referrals.rs endpoints:
  * GET  /api/admin/v1/referrals            list (paginated, search)
  * GET  /api/admin/v1/referrals/{id}       detail (earned + payouts + counts)
  * PATCH /api/admin/v1/referrals/{id}      set/clear per-referrer commission override
  * GET/POST /api/admin/v1/referrals/{id}/payouts   list / create manual payout
  * PATCH .../payouts/{payout_id}           mark paid / reconcile (invoice, preimage)
- Responses never expose NWC secrets. Docs + changelog.

Part of work/referral-program-apis.md (PR2 of 4).
Dedicated ReferralPayoutHandler (lnvps_api/src/referral/mod.rs) — kept out of
SubscriptionHandler; referrals are not a billing concern.

- Accrual: owed BTC = commission on referred VMs' first payments minus existing
  BTC payouts (paid + reserved). Only BTC is auto-paid (Lightning settles sats);
  fiat commission accrues for manual admin payout.
- Reserve-then-pay: insert an unpaid payout first, pay it, mark paid + record
  preimage; delete the reservation on failure so no double-pay and the balance
  can retry. Per-referrer errors don't abort the batch; referrer is notified.
- Payout methods: LNURL-pay (lightning_address) or NWC make_invoice (nwc);
  node.pay_invoice settles it. account_credit rejected.
- Opt-in via new 'referral' settings section (min-payout-sats, default 1000);
  WorkJob::ProcessReferralPayouts scheduled hourly only when configured.
- DB base methods list_all_referrals / delete_referral_payout;
  update_referral_payout now also persists invoice. ApiReferralPayout exposes
  pre_image (hex). Enabled lnurl-rs async-https-native. Worker::new gained node.
- Tests (payable math), config example, changelog.

Part of work/referral-program-apis.md (PR3 of 4).
- DELETE /api/v1/referral: leave the program. Blocked (409) while a payout is
  pending, and when paid payout history exists (retained for accounting; the
  referral_payout FK has no cascade). New base DB method delete_referral.
- GET /api/v1/referral/usage: per-referred-VM breakdown (vm_id, first payment
  amount, currency, effective_rate, commission).
- Mock test for delete_referral/list_all_referrals; docs + changelog.

Completes work/referral-program-apis.md (PR4 of 4).
@v0l v0l added enhancement New feature or request api User-facing or admin API changes database Migration or schema changes payments Payment/invoice logic labels Jul 16, 2026
v0l added 2 commits July 16, 2026 18:20
The PATCH-time 'resulting config must be payable' check rejected clearing the
lightning_address while in lightning_address mode, breaking a valid settings
update (and the e2e lifecycle test). The payout worker already skips referrers
whose method can't produce an invoice, so an incomplete config just defers
payouts rather than losing them; signup still requires a valid method up-front.
The lifecycle test used the admin client (which targets the admin API server)
to assert a non-owner cannot PATCH a subscription. That server doesn't mount the
user route /api/v1/subscriptions/{id}, so it returned 404 (route not found)
instead of exercising the ownership check (403). Use a second user client (the
referrer) so the request hits the user API and the ownership guard.

Pre-existing bug from the auto-renewal PATCH change; surfaced now because e2e
only runs on PRs and this is the first e2e run since.
@v0l
v0l merged commit d7cfb66 into master Jul 16, 2026
8 checks passed
@v0l
v0l deleted the feat/referral-program branch July 16, 2026 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api User-facing or admin API changes database Migration or schema changes enhancement New feature or request payments Payment/invoice logic

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant