Files
customerTicket/docs/CHAHARSU_PAYMENTS_IMPLEMENTATION.md
2026-10-07 13:39:29 +03:30

111 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# راهنمای پیاده‌سازی و راه‌اندازی چهارسو و پرداخت‌ها
تاریخ: ۲۰۲۶/۱۰/۰۵
## مسیرها و سازگاری
- ورود عادی مشتری و مسیر `/` به `/chaharsu/` هدایت می‌شوند. لینک داخلی معتبرِ ادامه ورود حفظ می‌شود؛ لینک خارجی پذیرفته نمی‌شود.
- `/tickets/` همچنان تاریخچه فعلی درخواست‌هاست. `/tickets/new/`، قالب و منطق ثبت درخواست تغییر نکرده‌اند.
- خانه چهارسو فقط خدمات دارد: ارتباط با کارشناسان، ارسال فیش و شماره حساب‌ها.
- شماره حساب‌ها: `/chaharsu/bank-accounts/`؛ فرم فیش: `/chaharsu/payment-receipt/`؛ سوابق فیش مشتری: `/chaharsu/receipts/`.
- میزکار کارشناسان: `/staff/payments/`؛ مدیریت حساب‌ها: `/staff/payments/banks/`.
- PWA از `/` شروع می‌شود تا مشتری به چهارسو و کارشناس به میزکار خودش برسد. رفتار ثبت service worker و URLهای نسخه‌دار حفظ شده‌اند.
## ساختار و قواعد مالی
اپ مستقل `apps.payments` شامل مدل‌ها، سیاست دسترسی، اعتبارسنجی، سرویس‌های تراکنشی، خواندن داده، اعلان‌ها و صفحات است. وابستگی‌های بیرونی از زیرساخت فعلی کاربران، واحدها، پیامک و سیاست تصویر استفاده می‌کنند. `PaymentReceiptRevision` هم اطلاعات نسخه و هم مدرک تصویر را نگه می‌دارد؛ مدل جداگانه Document لازم نشده است.
- اعلامیه، فیش و تصمیم تأیید مستقل‌اند. ثبت تصویر یا تأیید یک نفر از دو نفر، پرداخت را نهایی نمی‌کند.
- مبلغ عدد صحیح **ریال** است؛ نمایش حروفی تومان در مرورگر با BigInt انجام می‌شود و مقدار ریال به سرور می‌رود.
- نمایش تاریخ شمسی و انتخاب سه‌قسمتی است. سرور با jdatetime روز/ماه/سال را مستقل از JS بررسی و Gregorian را در DateField ذخیره می‌کند. تاریخ ناقص، غیرواقعی، آینده و خارج از بازه مجاز رد می‌شود.
- شماره حساب، کارت و شبا رشته‌اند و صفر ابتدایی آن‌ها حفظ می‌شود. شناسه واریز رشته است اما صفرهای ابتدایی آن پس از محاسبه رقم کنترل حذف می‌شوند؛ تولید، ذخیره و نمایش همین مقدار را استفاده می‌کنند. migration `0006_normalize_deposit_identifiers` شناسه اعلامیه‌ها و نسخه‌های فیش قبلی را هماهنگ می‌کند و snapshot بانکی یا ممیزی تاریخی را تغییر نمی‌دهد. شبا و کارت موجود اعتبارسنجی می‌شوند.
- حساب شناسه‌دار در صدور اعلامیه الزاماً شناسه می‌خواهد. در ثبت فیش، شناسه اعلامیه فقط برای **همان حساب** معتبر است؛ مقصد متفاوت شناسه خودش را لازم دارد.
- حساب فقط فعال در پنل کارشناسان، در زمینه اعلامیه اختصاصی همان مشتری قابل استفاده است؛ در واریز مستقل قابل استفاده نیست. غیرفعال کلی بودن، ثبت جدید را متوقف می‌کند و سوابق را پنهان نمی‌کند.
- snapshot حساب و شناسه اعلامیه حفظ می‌شود؛ ویرایش بانک، شماره تاریخی اعلامیه یا نسخه فیش را تغییر نمی‌دهد.
- فرم صدور و فرم فیش nonce دارند؛ ارسال تکراری همان داده رکورد یا پیامک جدید ایجاد نمی‌کند. استفاده مجدد از nonce برای اطلاعات مالی متفاوت رد می‌شود.
- فرم مشترک فیش مشتری، کارشناس، دسترسی لینک پرداخت و اصلاح فیش، تصویر را با XMLHttpRequest ارسال می‌کند: درصد و حجم واقعی درخواست، زمان سپری‌شده و تخمین زمان باقی‌مانده نمایش داده می‌شوند. پس از پایان انتقال، مرحله بررسی و ثبت سرور جداگانه نمایش داده می‌شود. دکمه ارسال و فیلدها تا پاسخ سرور قفل‌اند؛ در خطا، تصویر و nonce حفظ می‌شوند و امکان تلاش مجدد وجود دارد. درخواست دارای `Accept: application/json` مقصد ادامه را در `redirect_url` یا خطاهای فرم را با وضعیت ۴۲۲ دریافت می‌کند؛ ارسال معمولی بدون JavaScript همچنان از redirect استفاده می‌کند.
- وضعیت اعلامیه از مبلغ نسخه جاری فیش‌های تأییدشده بدون مغایرت مقصد محاسبه می‌شود؛ پرداخت جزئی و باقی‌مانده نمایش داده می‌شوند.
## تنظیمات ضروری پیش از استفاده مالی واقعی
۱. migrationها را اجرا کنید:
```bash
.venv/Scripts/python.exe manage.py migrate
```
۲. مدیرعامل فعال، superuser از نوع STAFF یا کارشناس دارای مجوز `payments.manage_company_bankaccounts`، حساب‌های **واقعی** شرکت را در منوی «بانک‌ها» تعریف کند. اطلاعات واقعی حساب در migration قرار نگرفته است. برای حساب شناسه‌دار، «شناسه واریز الزامی» را فعال کنید. سه وضعیت فعال کلی، کارشناسان و مشتریان مستقل‌اند و تغییر جمعی هم دارند. حساب دارای سابقه قابل حذف نیست؛ غیرفعال کنید. فرم ویرایش قدیمی نمی‌تواند تغییر جدید را بازنویسی کند.
۳. در مدیریت Django `/x5admin/`، بخش `PaymentApprovalPolicy`، یک سیاست عمومی یا سیاست واحد بسازید. همه افراد فهرست `reviewers` باید رأی بدهند؛ یک نفر یعنی تأیید تک‌نفره، چند نفر یعنی شرط تأیید همه اشخاص تعیین‌شده. افراد `seniors` می‌توانند با دلیل، تأیید نهایی ارشد انجام دهند.
۴. **انتخاب کارشناس فعال در سیاست، اختیار تأیید یا رد را برای چرخه‌های آن سیاست می‌دهد.** افراد `reviewers` رأی الزامی و افراد `seniors` اختیار تأیید نهایی مستقل با دلیل دارند؛ مجوز جداگانه Django برای رأی لازم نیست. مدیر مالی یا نقش FINANCE بدون انتخاب در سیاست، خودکار تأییدکننده نمی‌شود. مجوزهای مدیریتی و مشاهده همچنان مستقل‌اند:
| مجوز | کاربرد |
| --- | --- |
| `payments.manage_payment_approval_policy` | مدیریت سیاست‌ها و تخصیص سیاست فعال به چرخه خالی |
| `payments.manage_company_bankaccounts` | مدیریت حساب‌ها |
| `payments.manage_payment_access` | تنظیم باز یا محدود بودن مشاهده و ایجاد |
| `payments.view_all_payments` | مشاهده مالی در همه واحدها |
| `payments.view_payment_audit` | دسترسی ممیزی مالی در مدیریت |
مشاهده همان فیش و تخصیص نقش عادی یا ارشد در همان چرخه لازم است. مجوزهای قدیمی `approve_paymentreceipt` و `senior_approve_paymentreceipt` به‌تنهایی اختیار رأی نمی‌دهند. ثبت‌کننده کارشناس، فیشی را که خودش ثبت کرده تأیید نمی‌کند. غیرفعال‌کردن حساب یا پروفایل کارشناس مانع رأی جدید او می‌شود. افراد هر چرخه هنگام ارسال فیش ذخیره می‌شوند؛ تغییر سیاست، افراد چرخه‌های قبلی را بازنویسی نمی‌کند. مدیریت Django شرایط ورود استاندارد پنل admin را لازم دارد.
۵. نبود سیاست فعال باعث **ماندن فیش در انتظار بررسی** می‌شود. تأیید خودکار یا تعیین فرضی مدیرعامل انجام نشده است. پس از تعریف سیاست، مدیر مجاز از جزئیات فیش، «اعمال سیاست فعال تأیید» را برای چرخه خالی بزند. سیاست چرخه دارای تخصیص/رأی بازنویسی نمی‌شود.
۶. تنظیم پیش‌فرض `PaymentAccessConfiguration` مشاهده و ایجاد را برای همه کارشناسان فعال باز می‌گذارد. برای محدودسازی بعدی این دو گزینه را غیرفعال و مجوزهای `view_paymentnotice`، `view_paymentreceipt` و `add_paymentnotice` را در دامنه موردنظر بدهید. تأیید از ابتدا محدود است.
## نسخه‌ها، تأیید و ممیزی
تصمیم در transaction و با قفل فیش و چرخه، تطبیق نسخه جاری و رأی یکتا ثبت می‌شود. PostgreSQL برای join واحد nullable فقط خود فیش را قفل می‌کند. ترتیب قفل مشتری/حساب در صدور و ثبت یکسان است. پیامک نهایی فقط با نهایی‌شدن شرط تأیید صف می‌شود.
فیش ردشده از صفحه مشتری با تصویر جدید اصلاح می‌شود. نسخه پیشین، فایل و رأی‌ها باقی می‌مانند؛ نسخه تازه از ابتدا با سیاست جدید بررسی می‌شود و رأی قدیمی در تأیید جدید شمرده نمی‌شود. رد و ارشد دلیل لازم دارند؛ مغایرت مقصد برای تأیید دلیل بررسی لازم دارد. تصویر تکراری روی فیش دیگر به کارشناس هشدار داده می‌شود.
صدور، ارسال، اصلاح، شروع چرخه، تخصیص، رأی، مشاهده مدرک و تغییر حساب با نام/شناسه عامل، زمان و جزئیات در `PaymentAuditEvent` ثبت می‌شوند. تغییرات مجوز مالی در مسیرهای مدیریت User/Group/Unit/Membership/StaffProfile نیز ممیزی می‌شوند. سیاست و داده‌های ممیزی از admin قابل حذف/ویرایش عادی نیستند. تغییر مستقیم دیتابیس خارج از مسیرهای برنامه، نیازمند کنترل عملیاتی و ممیزی مستقل دیتابیس است.
## تصاویر و امنیت مرورگر
تصاویر در `payments/` رسانه خصوصی ذخیره می‌شوند و فقط endpoint احراز هویت‌شده تصویر آن‌ها را ارائه می‌دهد. مجوز در هر بار مشاهده/دانلود بررسی می‌شود؛ Magic Link تیکت اجازه مالی نمی‌دهد. لینک مستقیم `/media/payments/` در توسعه و مسیر signed `/protected-media/payments/` در nginx مسدودند. پاسخ مالی/تصویر `no-store` است و service worker داده مالی را cache نمی‌کند.
اعتبارسنجی تصویر شامل سقف حجم سیاست فعلی، extension، MIME واقعی، decode با Pillow، سقف ابعاد و SHA-256 است. فایل ساخته‌شده در شکست transaction پاک می‌شود. دانلود نسخه قبلی همچنان کنترل دسترسی دارد.
فونت، SVG، CSS و JS داخل پروژه‌اند؛ وابستگی CDN اضافه نشده است. CSS چهارسو scoped است. کپی کامل اطلاعات از snapshot کامل، شامل شرح و فاکتور و تاریخ، ساخته می‌شود؛ فقط فیلدهای بانکی ضروری کپی تکی دارند. در HTTP شبکه محلی، اگر Clipboard مجاز نباشد، پنجره قابل انتخاب و کپی دستی نمایش داده می‌شود.
## پیامک
دو رویداد دیتابیسی `PAYMENT_NOTICE_CREATED` و `PAYMENT_RECEIPT_APPROVED` به `SMSTemplate` اضافه شده‌اند. migration الگوهای عمومی اولیه را ایجاد می‌کند؛ override واحد در الگوهای پیامک فعلی قابل تنظیم است. تگ‌ها:
`recipient_name`, `company_name`, `notice_number`, `receipt_number`, `amount_rial`, `invoice_number`, `payment_link`
صف فعلی `NotificationJob` و worker `process_notifications` به کار می‌روند. اعلامیه به معنی صف‌شدن پیامک است؛ دریافت پیامک نیازمند اجرای worker و تنظیم provider است. وضعیت ارسال و دلیوری مستقل از وضعیت مالی است. متن و نسخه الگو در اولین تلاش موفق آماده‌سازی snapshot می‌شود؛ retry همان متن را حفظ می‌کند. تلاش ناموفقِ آماده‌سازی قالب یا ارتباط provider نیز تاریخچه دارد. URL پیامک لینک موقت مستقل از ورود کامل مشتری است.
در جزئیات اعلامیه کارشناسان، «ارسال مجدد پیامک اعلامیه واریز» از مسیر POST `notices/<uuid>/resend-sms/` یک job و `PaymentNotification` مستقل می‌سازد؛ ارسال‌های قبلی و نتیجه دریافت آن‌ها بازنویسی نمی‌شوند. کلید UUID فرم درخواست تکراری را یکسان می‌کند و قفل مشتری سپس اعلامیه، درخواست‌های همزمان را هماهنگ می‌کند. تا پایان job در صف/پردازش/تلاش خودکار، درخواست جدید ثبت نمی‌شود. مجوز ایجاد در دامنه واحد و دسترسی به اعلامیه، فعال بودن مشتری و لغو/حذف نشدن اعلامیه بررسی می‌شوند؛ رویداد `NOTICE_SMS_RESEND_REQUESTED` ممیزی می‌شود. worker همان handler و قالب‌های فعلی را استفاده می‌کند و برای هر ارسال مجدد لینک پرداخت تازه می‌سازد.
سوابق، زمان درخواست و ارسال، درخواست‌کننده، متن آماده‌شده، تمام تلاش‌های ارسال با خطا و شناسه و کد provider، وضعیت دریافت، شرح/کد و زمان آخرین استعلام و استعلام بعدی را نمایش می‌دهند. `SENT` فقط پذیرش سرویس است؛ رسیدن به گوشی نیاز به نتیجه قطعی دلیوری دارد. پاسخ‌های غیرقطعی یا پایان پایش بدون تأیید دریافت، صریحاً غیرقطعی نمایش داده می‌شوند. سوابق با تازه‌سازی صفحه به‌روز می‌شوند.
میز پرداخت کارشناسان به‌طور پیش‌فرض فیش‌های «نیازمند اقدام من» را نمایش می‌دهد؛ `?tab=receipts` همه فیش‌های مجاز و `?tab=notices` اعلامیه‌ها را انتخاب می‌کند. صفحه‌بندی ۲۵تایی با شمارنده صفحه و محدوده نتایج، تب و فیلترها را نگه می‌دارد.
در آزمون‌ها provider mock شده است؛ پیامک واقعی برای مشتری ارسال نشده و worker روی صف واقعی توسعه اجرا نشده است.
## آزمون و محدودیت محیط
نتایج اجرا (اجرای مجدد را نباید با تعداد آزمون‌های یکتا جمع کرد):
- مجموعه کامل سمت سرور: **۳۲۲ موفق، ۴ skipped**؛ ۴۶ آزمون مرورگر در این اجرای جدا انتخاب نشده‌اند (`chaharsu-backend-final.log`).
- کنترل مجدد بخش‌های مالی، view، مجوز و دامنه: **۱۸۱ موفق** (`chaharsu-focused-final.log`).
- مجموعه کامل مرورگر: **۴۴ موفق، ۲ skipped**؛ دو smoke test محیط خارجی بدون تنظیمات آن محیط اجرا نشده‌اند (`chaharsu-e2e-final.log`).
- پس از تکمیل سناریوی ایجاد اعلامیه و ارسال فیش مرتبط: **۵ آزمون مرورگر مالی و یک آزمون مجوز audit موفق** (`chaharsu-payments-e2e-final.log`).
- کنترل نهایی ثبت مقادیر قبل/بعد حساب بانکی، جلوگیری از ویرایش فرم قدیمی و مجوز حساب مقصد: **۳ موفق** (`chaharsu-audit-final.log`).
- آزمون نمونه قبلی ثبت تیکت: **۳ موفق** (`chaharsu-prototype-test.log`)؛ آزمون JavaScript موجود: **۱ موفق**. بررسی syntax هر سه فایل JS جدید نیز موفق بود.
- بررسی نهایی Django بدون خطا؛ کنترل صفحه واقعی با نشست مشتری و اندازه **۳۲۰×۵۶۸** موفق: هر سه خدمت کامل بالای نوار پایین دیده می‌شوند، overflow افقی وجود ندارد و دارایی‌ها نسخه‌دارند (`chaharsu-live-final.log`). سرور توسعه روی `0.0.0.0:8000` فعال است.
- Graphify به‌روز و روابط معماری بازبینی شد. Ripwire `--test-gate` فهرست آزمون‌ها و مسیرهای dispatch فاقد اتصال ایستای آزمون را ارائه کرد (exit 4، به معنای تعهد بررسی، نه شکست pytest). آزمون‌های فوق اجرا شدند؛ `--quality-delta` با **gating=0 و preexisting-worse=0** پایان یافت. برای کد جدید موارد پیچیدگی و callbackهای چارچوب نیز گزارش شده‌اند؛ این گزارش جایگزین تست اجرایی نیست.
گزارش‌های فوق در `graphify-out/` نگهداری می‌شوند. آزمون‌های PostgreSQL در `tests/test_payment_concurrency.py` از connectionهای جدا استفاده می‌کنند و در SQLite صریحاً skip می‌شوند. تست SQLite اثبات صحت قفل PostgreSQL نیست. پیش از انتشار مالی روی production این **۴ آزمون** باید روی پایگاه **آزمایشی جداگانه PostgreSQL** اجرا شوند؛ در این محیط Docker daemon در دسترس نیست و binary قابل اجرای PostgreSQL موجود نیست.
پشتیبان دیتابیس توسعه پیش از migration در مسیر ignoredِ `graphify-out/db-pre-chaharsu.sqlite3` تهیه شد. `bootstrap_demo` اجرا نشده، دسترسی یا حساب واقعی فرضی ایجاد نشده و عملیات git commit/push انجام نشده است.
## انتخاب مشتری و میان‌بر ایجاد اعلامیه
انتخاب مشتری در ایجاد اعلامیه و انتخاب مشتری برای ثبت فیش، جست‌وجویی است؛ فهرست کامل مشتریان در HTML بارگیری نمی‌شود. جست‌وجو با نام، موبایل فارسی/لاتین، سازمان، شماره یا موضوع تیکت انجام می‌شود. هر پاسخ حداکثر ۲۰ مشتری فعال را همراه سه سابقه اخیر قابل مشاهده برمی‌گرداند؛ سوابق خصوصی خارج از مجوز کارشناس در نتیجه یا شمارنده دیده نمی‌شوند. انتخاب خودکار نتیجه اول وجود ندارد. نام، موبایل، سازمان و موقعیت در کارت انتخاب‌شده نمایش داده می‌شوند و تغییر عبارت جست‌وجو، انتخاب قبلی را پاک می‌کند.
دکمه «ایجاد اعلامیه واریز» در باکس مشتری تیکت و کارت مخاطب، فقط برای کارشناس دارای مجوز ایجاد و مشتری فعال نمایش داده می‌شود. پارامتر `customer` در لینک، UUID مشتری است؛ GET تنها مشتری فعال را پیش‌انتخاب می‌کند و هیچ اعلامیه یا پیامکی ایجاد نمی‌کند. POST همچنان شناسه مشتری را با queryset فعال فرم و مجوز مالی سرور اعتبارسنجی می‌کند. پاسخ جست‌وجو `private, no-store` است و داده مشتری در حافظه پایدار مرورگر ذخیره نمی‌شود.