docs: drop Phase D.x markers now that the referral loop is closed
The "Phase D.1/D.2/D.3" comment scaffolding and the "Paddle webhook will fill this in" references became actively misleading after D.3 landed — anyone reading the code would think referral conversion was still pending. Also corrects a stale "Paddle" reference to "Stripe" (we never shipped Paddle; ended up on Stripe after the Paddle → Polar → Stripe MoR onboarding pivot). Pure docstring sweep, no behaviour change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
ce36ce36fd
commit
1be0c5a436
5 changed files with 24 additions and 19 deletions
|
|
@ -11,8 +11,9 @@ Usage from the host::
|
|||
two months, not one (avoids accidental erosion of an existing grant
|
||||
when re-running the command).
|
||||
|
||||
This is the manual lever for Phase D.2. In D.3 the Paddle webhook will
|
||||
call the same helper for both sides of a referral conversion.
|
||||
This is the manual lever for admin grants. The Stripe webhook applies
|
||||
the same stacking rule via ``referral_service.convert_referral`` for
|
||||
both sides of a referral conversion.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
|
|||
|
|
@ -169,16 +169,17 @@ class User(Base):
|
|||
settings_json: Mapped[dict | None] = mapped_column(JSON)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
|
||||
last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
# Referrals (Phase D.1). The code is unique + URL-safe; generated on
|
||||
# first need rather than at row creation so existing accounts get one
|
||||
# the next time they hit /settings.
|
||||
# Referral code is unique + URL-safe; generated on first need rather
|
||||
# than at row creation so existing accounts get one the next time
|
||||
# they hit /settings.
|
||||
referral_code: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
||||
referred_by_user_id: Mapped[int | None] = mapped_column(
|
||||
ForeignKey("users.id", ondelete="SET NULL"), nullable=True,
|
||||
)
|
||||
# Paid-tier credit window (Phase D.2). Null = no credit. When set and
|
||||
# > now(), the user gets paid-tier features regardless of `tier`.
|
||||
# Populated by admin CLI (manual grants) or Paddle webhook (D.3).
|
||||
# Paid-tier credit window. Null = no credit. When set and > now(),
|
||||
# the user gets paid-tier features regardless of `tier`. Populated
|
||||
# by admin CLI (manual grants) and by referral conversion (45 days
|
||||
# per converted referral, both parties).
|
||||
credit_until: Mapped[datetime | None] = mapped_column(
|
||||
DateTime(timezone=True), nullable=True,
|
||||
)
|
||||
|
|
@ -248,8 +249,9 @@ class Referral(Base):
|
|||
"""One row per captured (referrer, referred) pair. Created at signup
|
||||
when the new user supplied a valid `?ref=<code>`. The conversion
|
||||
fields (`converted_at`, `credited_at`) stay null until the referred
|
||||
user makes their first paid subscription — Phase D.3 fills them in
|
||||
via the Paddle webhook."""
|
||||
user makes their first paid subscription — the Stripe webhook calls
|
||||
``referral_service.convert_referral`` to fill them in and extend
|
||||
both parties' ``credit_until``."""
|
||||
__tablename__ = "referrals"
|
||||
id: Mapped[int] = mapped_column(_PK, primary_key=True, autoincrement=True)
|
||||
referrer_user_id: Mapped[int] = mapped_column(
|
||||
|
|
|
|||
|
|
@ -117,9 +117,10 @@ async def settings_page(
|
|||
session: AsyncSession = Depends(get_session),
|
||||
principal: CurrentUser = Depends(require_auth),
|
||||
):
|
||||
"""Per-user settings. Currently shows email, tier, and the referral
|
||||
block (own code + invite link + counts of pending/converted
|
||||
referrals). The Credit / Paddle pieces land in D.3."""
|
||||
"""Per-user settings. Shows email, tier, Stripe subscription
|
||||
management, email-digest preferences, cloud-sync status, portfolio
|
||||
import, and the referral block (own code + invite link + counts of
|
||||
pending / converted / actively-credited referrals)."""
|
||||
user = principal.user
|
||||
if user is None:
|
||||
# Bearer-token admin path — no per-user settings to show.
|
||||
|
|
|
|||
|
|
@ -321,7 +321,7 @@ async def analyze_portfolio(
|
|||
is persisted. The ai_calls ledger row records tokens + cost, never
|
||||
holdings.
|
||||
|
||||
Gated behind ``require_paid`` (Phase D.2): free-tier users get 402.
|
||||
Gated behind ``require_paid``: free-tier users get 402.
|
||||
Admin bearer-token bypasses the gate for testing."""
|
||||
# Read JSON body manually so we can enforce a hard size cap. FastAPI's
|
||||
# default body limit is generous; we want tighter control here.
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
|
||||
Two sources can grant paid access:
|
||||
|
||||
1. ``user.tier in {"paid", "enterprise"}`` — set by Paddle webhook in
|
||||
Phase D.3 once a subscription is active.
|
||||
2. ``user.credit_until > now()`` — non-subscription credit. Currently
|
||||
populated by the admin CLI (`python -m app.cli grant-credit`) and, in
|
||||
D.3, by the referral-conversion path (3 months at 50% off).
|
||||
1. ``user.tier in {"paid", "enterprise"}`` — set by the Stripe webhook
|
||||
once a subscription is active.
|
||||
2. ``user.credit_until > now()`` — non-subscription credit. Populated
|
||||
by the admin CLI (``python -m app.cli grant-credit``) and by the
|
||||
referral-conversion path (45 days per converted referral, both
|
||||
parties).
|
||||
|
||||
Either is sufficient. We use a single ``paid_status`` function so the
|
||||
Settings page can show *why* a user has paid access ("paid subscription"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue