harden OTP for real SMS and close the second audit round

Kavenegar is being switched on, so every OTP request now costs credit:
- phone numbers are normalised to 09XXXXXXXXX everywhere (login, OTP,
  family invite, TEST_LOGIN_PHONES keys) and existing rows are migrated;
  non-Iranian and landline numbers are rejected before any SMS
- cooldown and a daily per-phone cap are counted in the database, so the
  three gunicorn workers no longer triple them
- a global daily OTP SMS cap (default 300) stops SMS pumping
- per-IP rates widened for CGNAT / school Wi-Fi (env-overridable)
- provider message id is logged; SMS text warns not to share the code

Second audit round: an offer no longer shortens a longer paid plan,
Zarinpal error bodies no longer 500, island XP reaches the parent report,
a non-numeric questionId no longer 500s after the answer is generated.

596 tests green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 12:28:06 +03:30
parent 74aa9aa8ca
commit 0580aec7b0
24 changed files with 685 additions and 119 deletions

View File

@@ -3,8 +3,12 @@
تا ۲۰۲۶-۰۹-۲۵ هیچ کدی Conversion را از «pending» جلو نمی‌برد و پورسانتی
حساب نمی‌شد؛ برنامه‌ی معرفی فقط یک جدولِ ثبت بود. حالا اولین سفارشِ
پرداخت‌شده‌ی کاربرِ معرفی‌شده Conversion را `paid` می‌کند و
پرداخت‌شده‌ی کاربرِ معرفی‌شده *بعد از ثبتِ کد* Conversion را `paid` می‌کند و
`commission_amount = مبلغِ پرداختی × commission_rate` را ثبت می‌کند.
⚠️ «کاربرِ تازه» بررسی نمی‌شود: مشتریِ قدیمی هم هر وقت بخواهد کد ثبت می‌کند
و تمدیدِ بعدی‌اش پورسانت می‌سازد (بازبینیِ ۲۰۲۶-۰۹-۲۵، بازتولید شد). جلوی
معرفیِ خود با شماره‌ی دوم هم چیزی نیست.
پرداختِ خودِ پورسانت به معرف (تسویه) هنوز دستی و از پنلِ ادمین است.
ثبت‌نام یا ورودِ مهمان هیچ پورسانتی نمی‌سازد؛ فقط پول جابه‌جا شدنِ واقعی.