Skip to content

feat: credit codes and invites (store credit pays for rented numbers) - #110

Merged
ralyodio merged 1 commit into
masterfrom
feat/credit-codes-invites
Oct 26, 2026
Merged

ralyodio merged 1 commit into
masterfrom
feat/credit-codes-invites

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Credit code redemptions and invite/affiliate links (fleet rollout, batch 1).

What a code grants: @profullstack/redeem-credits unit usd_cents = store credit toward rented numbers ($5/mo). Redeem on Settings ("Have a code?"), POST /api/v1/redeem, CLI smshub-cli redeem, MCP redeem_code.

How credit pays for a rental: payment method CREDIT (chain field on POST /api/v1/numbers/orders and /numbers/{id}/renew; "Pay with store credit" / "Renew with credit" on /numbers; rent --credit). createOrder checks the balance (402 if short, no order opened), inserts the normal number_orders row with chain CREDIT, spends the credit (credit_append_if_version CAS, so parallel orders cannot overdraw), then runs the same pending -> paid claim + provision() the CoinPay webhook runs (extracted as claimAndProvision). A renewal extends phone_numbers.expires_at exactly like a paid renewal. If provisioning gives up for good (status failed), the credit is granted back (refund:order:<id>). The whole order must be covered by credit (no split payments).

Invites: @profullstack/affiliates, 3 per account at /invites (API GET/POST /api/v1/invites, CLI invites [new], MCP list_invites / create_invite). /i/<code> sets a 7-day cookie and lands on /register, which says what the invite gives and also stores the code in signup metadata. On /auth/callback a new account (under 7 days old) with a confirmed email and a signup profile (smshub's signup checks) spends the invite, and both people get $2.50 of credit, so two accepted invites pay for a month. Unconfirmed, profile-less, old accounts and self-invites spend nothing.

?ref=: first touch on new accounts via refFromRequest = the existing referral_code cookie set by trackReferralCode; codes from the old referral_codes table come back external and are not recorded, so a sign-up is never counted twice.

DB: 017_redeem_credits.sql, 018_affiliates.sql copied from the packages (RLS on, no policies, service_role only); applied by hand on dev2 before merge, after a pg_dump -n public backup.

Minting: scripts/mint-credit-code.mjs --usd 10 --label "for Kim" with the app env.

Tests: src/lib/__tests__/rewards.test.ts (codes, quota, invite accepted once with both sides credited, checks, cookie vs metadata, ref first touch + external), src/lib/managed-numbers/__tests__/credit-orders.test.ts (402 when short, credit order provisions with no CoinPay call, renewal, refund on final failure). 317 pass; typecheck clean; next build passes.

🤖 Generated with Claude Code

- @profullstack/redeem-credits 0.2: unit usd_cents = store credit. "Have a
  code?" on Settings, POST /api/v1/redeem, GET /api/v1/credits.
- Store credit pays for a rental or renewal as payment method CREDIT: the
  same number_orders row and pending -> paid -> provision transition the
  CoinPay webhook runs, no parallel flag. Refunded if provisioning gives up.
- @profullstack/affiliates 0.2: 3 invites per account (/invites, /i/<code>).
  A new account that confirms its email with a signup profile spends the
  invite and both people get $2.50. ?ref= reads the existing referral_code
  cookie; old referral codes come back external and are not counted twice.
- MCP: redeem_code, credit_balance, list_invites, create_invite; CLI 1.7.0:
  redeem, credits, invites, rent/renew --credit. scripts/mint-credit-code.mjs.
- Migrations 017_redeem_credits.sql + 018_affiliates.sql (applied by hand).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

12 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 4 | LOW: 6

Severity Rule Location
HIGH sh-unquoted-expansion-destructive public/install.sh:67
HIGH sh-remote-script-execution public/install.sh:89
MEDIUM sh-remote-script-execution public/install.sh:133
MEDIUM js-open-redirect src/app/numbers/page.tsx:180
MEDIUM js-open-redirect src/components/inbox-client.tsx:35
MEDIUM js-unescaped-html-sink src/components/pit-footer.tsx:17
LOW secret-generic-credential src/lib/providers/__tests__/twilio.test.ts:24
LOW secret-generic-credential src/lib/providers/__tests__/twilio.test.ts:46
LOW secret-generic-credential src/lib/providers/__tests__/twilio.test.ts:60
LOW secret-generic-credential src/lib/providers/__tests__/twilio.test.ts:78
LOW secret-generic-credential supabase/config.toml:287
LOW secret-generic-credential supabase/config.toml:319

Snippets are redacted; ThreatCrush never prints matched credential material.

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​profullstack/​affiliates@​0.2.07610010090100
Added@​profullstack/​redeem-credits@​0.2.07610010090100

View full report

@ralyodio
ralyodio merged commit a3c3221 into master Oct 26, 2026
12 checks passed
@ralyodio
ralyodio deleted the feat/credit-codes-invites branch October 26, 2026 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant