docs(referral): explain affonso_referral is a vendor contract, DO NOT RENAME (#3272)

`affonso_referral` reads like leftover prototype naming to anyone who
doesn't know the Dodo ↔ Affonso integration. Three PR reviews in a
row flagged it with some variant of "this should be wm_referral."

It's actually a vendor contract — Dodo forwards values under that
exact metadata key to Affonso's referral-tracking webhook. Renaming
desyncs the write path from Affonso's attribution pipeline and
silently breaks sharer credit with no exception raised.

Add a load-bearing comment at both call sites (the write in
`convex/payments/checkout.ts` and the read in
`convex/payments/subscriptionHelpers.ts`) plus a sentence in
`docs/api-commerce.mdx` so the next reviewer doesn't have to
re-discover the constraint.

PR-15 of the 14-PR rollout at
docs/plans/2026-04-21-002-feat-harden-auth-checkout-flow-ux-plan.md.
Comments-only; no runtime behavior change.

Typecheck + lint:md + test:data 6049/6049 all clean.
This commit is contained in:
Elie Habib
2026-04-22 14:59:37 +04:00
committed by GitHub
parent 607cb2e757
commit 81a8ac618a
3 changed files with 15 additions and 0 deletions

View File

@@ -128,6 +128,14 @@ async function _createCheckoutSession(
metadata.wm_user_id = user.userId;
metadata.wm_user_id_sig = await signUserId(user.userId);
if (args.referralCode) {
// `affonso_referral` is the Dodo ↔ Affonso vendor-contracted metadata
// key — Dodo forwards values on this exact key to Affonso's referral-
// tracking webhook. DO NOT RENAME (to `wm_referral`, `referral`,
// `ref`, or anything else) without coordinating with Dodo + Affonso;
// a rename silently breaks sharer attribution because Affonso stops
// receiving the signal and `userReferralCredits` rows are never
// created on this conversion path. Mirror read in
// `convex/payments/subscriptionHelpers.ts`.
metadata.affonso_referral = args.referralCode;
}

View File

@@ -296,6 +296,11 @@ export async function handleSubscriptionActive(
// insert a userReferralCredits row crediting the sharer. The
// `else` branch guards against double-crediting on webhook
// replays — existing subscription rows skip this path.
//
// `affonso_referral` is the Dodo ↔ Affonso vendor contract key —
// DO NOT RENAME here or on the write side in checkout.ts. A
// rename desyncs writer/reader and silently breaks every
// conversion-path credit.
const referralCode = data.metadata?.affonso_referral;
if (typeof referralCode === "string" && referralCode.length > 0) {
const referrer = await ctx.db

View File

@@ -110,6 +110,8 @@ Errors:
No `referrals` count or `rewardMonths` is returned today — Dodo's `affonso_referral` attribution doesn't yet flow into Convex, and exposing only the waitlist-side count would mislead.
`affonso_referral` is the vendor-contracted metadata key Dodo forwards to Affonso's referral-tracking webhook. The key name is load-bearing — renaming it (to `wm_referral`, `ref`, etc.) silently breaks Dodo→Affonso attribution. See `convex/payments/checkout.ts` and `convex/payments/subscriptionHelpers.ts` for the writer/reader call sites.
## Waitlist
### `POST /api/leads/v1/register-interest`