Skip to content

Рефералка: процент на баланс, уровни, скидка и удержание - #400

Open
spacesarmat wants to merge 4 commits into
mainfrom
cursor/referral-variants-b584
Open

spacesarmat wants to merge 4 commits into
mainfrom
cursor/referral-variants-b584

Conversation

@spacesarmat

@spacesarmat spacesarmat commented Nov 12, 2026 •

Copy link
Copy Markdown
Owner

Четыре варианта рефералки считаются в биллинге. Пока Andy не включит переключатель, прод остается как сейчас: дни рефереру и другу, задержка и годовой лимит.

Варианты (все выключены по умолчанию)

  • Процент на баланс: N% оплаты друга на creditRub подписки реферера. Параметры: процент, максимум за оплату, потолок за календарный месяц (МСК). Списывается при следующей оплате, как админский зачет.
  • Уровни: список порогов по числу оплативших друзей. Дни уровня заменяют базовые, если они больше, но не больше 14. Процент уровня берется, если он выше процента на баланс.
  • Скидка другу: процент и/или рубли только на первую оплату подписки, пока у реферера есть доступ и не вышел срок. Цена в боте и в приложении уже со скидкой, сумма заморожена в заказе вместе с комиссией.
  • Удержание: дополнительные дни, когда друг платит N месяцев подряд (следующая оплата через 27-45 дней). Дни не больше 14.

Задержка и годовой лимит правятся в админке. Журнал показывает тип строки: дни, баланс, удержание.

Отмена

Ожидающая строка просто отменяется. Выданная строка снимает дни с конца подписки и рубли с баланса. Если часть уже потрачена, в журнале, во вспышке и в аудите написано, что именно не снято. Аудит: действие referral_cancel, актор и время МСК. Повтор той же отмены ничего не меняет. Повторный расчет тоже не начисляет второй раз.

Миграция

013_referral_variants. Таблицы referral_binds и referral_accruals из 011 получают ord, extra и колонки варианта и подключаются к хранилищу. Настройки по-прежнему в settings.referral. Старый JSON из legacy_state переносится в таблицы при первой записи.

Не вливать и не выкатывать

Нужно слово Andy. Если в проде переключатели уровней, скидки или удержания уже нажаты, а числа нулевые, начисления не меняются. Процент на баланс включается только новым переключателем, сохраненный процент сам по себе не включается.

Админка рефералки

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

RetriggerView in GreptileConfidence Score: 3/5

Fix the charged-but-rejected Stars payment path and preserve device-payment checks after old orders are removed before merging.

Fix All in CursorFindings

  1. P1 Stars charged without subscription time ▶
  2. P1 Device purchases count as months ▶
Fix with agent prompt
### Issue 1
ops/billing/src/logic.mjs:1688-1690
A second discounted Stars order can still charge the friend after their first payment succeeds. `starCheckoutDecision` checks whether that order was paid, but not whether another order already used the referral discount. This new check then rejects the successful charge with `error: 'amount'`, without adding subscription time or returning the Stars. The admin refund route also refuses payments with an error.

Check discount eligibility before approving checkout, and return the payment if eligibility changes before the charge arrives.

### Issue 2
ops/billing/src/logic.mjs:1313-1317
`friendPayments` includes extra-device purchases as subscription payments. Those rows carry the same `accountId`, so buying device slots 27–45 days apart can satisfy `retentionMonths` without renewing the subscription. The same helper also lets a device purchase consume the first-subscription discount. Resolve `buy:` orders to their saved `kind` and include only subscription purchases for these checks.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds referral balance credits, tiers, first-payment discounts, retention rewards, and cancellation records. The new variants remain off by default.

  • Referral payments can earn days, balance credit, and retention rewards.
  • A friend's first subscription payment can use a referral discount.
  • Admins can cancel referral rewards and see what was removed.
  • The admin panel can set each referral reward's switches, amounts, and limits.
  • PostgreSQL now stores referral binds and variant-specific reward details.
  • The phone invite screen can show balance credit from referrals.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Create two discounted Stars orders] --> B[Pay first order]
  B --> C[Approve checkout for second order]
  C --> D[Telegram charges Stars]
  D --> E[Prior subscription payment found]
  E --> F[Reject payment without adding days]
  F --> G[Admin refund refuses error row]
Loading

Reviews (2) · Last reviewed commit: "Рефералка: скидка в Stars и crypto, удер..." · Reviewed by Greptile

cursoragent and others added 3 commits November 12, 2026 14:59
Процент на баланс, уровни, скидка другу и удержание считаются при расчете.
Задержка и годовой лимит правятся в админке. Миграция 013 подключает журнал.

Co-authored-by: ANDY BUM <spacesarmat@users.noreply.github.com>
Co-authored-by: ANDY BUM <spacesarmat@users.noreply.github.com>
Co-authored-by: ANDY BUM <spacesarmat@users.noreply.github.com>
Comment thread ops/billing/src/logic.mjs Resolved
Comment thread ops/billing/src/logic.mjs Resolved
Comment thread ops/billing/src/logic.mjs Resolved
Comment thread ops/billing/src/logic.mjs Resolved
Comment thread ops/billing/src/logic.mjs Resolved
Comment thread ops/billing/src/logic.mjs
Comment on lines 1289 to +1293
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
if (!row || row.accountId !== friendId || row.pending || row.error) continue;
const t = Number(row.at) || 0;
if (t >= from && (at === 0 || t < at)) at = t;
if (t >= from) out.push(row);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Device purchases count as months

friendPayments includes extra-device purchases as subscription payments. Those rows carry the same accountId, so buying device slots 27–45 days apart can satisfy retentionMonths without renewing the subscription. The same helper also lets a device purchase consume the first-subscription discount. Resolve buy: orders to their saved kind and include only subscription purchases for these checks.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ops/billing/src/logic.mjs
Line: 1289-1293

Comment:
**Device purchases count as months**

`friendPayments` includes extra-device purchases as subscription payments. Those rows carry the same `accountId`, so buying device slots 27–45 days apart can satisfy `retentionMonths` without renewing the subscription. The same helper also lets a device purchase consume the first-subscription discount. Resolve `buy:` orders to their saved `kind` and include only subscription purchases for these checks.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor

Comment thread ops/billing/src/referralAdmin.mjs Resolved
Comment thread ops/billing/src/referralAdmin.mjs Resolved
Comment thread ops/billing/src/logic.mjs Resolved
…Greptile

Каждая оплата подписки дает свой процент на баланс. Скидка друга одноразовая и не достается повторному заказу. Устройство не считается месяцем удержания.

Co-authored-by: ANDY BUM <spacesarmat@users.noreply.github.com>
Comment thread ops/billing/src/logic.mjs
Comment on lines +1688 to +1690
if (quote && Number(quote.referralOff) > 0) {
const payer = quote.accountId || (act.indexOf('new:') === 0 ? act.slice(4) : ((state.subscriptions || []).find((s) => s.id === act.slice(4)) || {}).accountId);
if (payer && subscriptionPaidBefore(state, payer, operationId)) return rejectAmount(state, row);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stars charged without subscription time

A second discounted Stars order can still charge the friend after their first payment succeeds. starCheckoutDecision checks whether that order was paid, but not whether another order already used the referral discount. This new check then rejects the successful charge with error: 'amount', without adding subscription time or returning the Stars. The admin refund route also refuses payments with an error.

Check discount eligibility before approving checkout, and return the payment if eligibility changes before the charge arrives.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ops/billing/src/logic.mjs
Line: 1688-1690

Comment:
**Stars charged without subscription time**

A second discounted Stars order can still charge the friend after their first payment succeeds. `starCheckoutDecision` checks whether that order was paid, but not whether another order already used the referral discount. This new check then rejects the successful charge with `error: 'amount'`, without adding subscription time or returning the Stars. The admin refund route also refuses payments with an error.

Check discount eligibility before approving checkout, and return the payment if eligibility changes before the charge arrives.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor

This branch has not been deployed

No deployments
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.

2 participants