feat: accounting integration (D27), SMS relay driver (D26), rebuild user apps
- Add Tahesab/Zarrin accounting sync module with admin panel UI - Add SMS relay driver as default provider, refactor SMS into driver pattern - Update customer services, orders, products, and catalog queries - Rebuild user-app and user-app-v2 dist assets - Add Prisma migration for accounting tables - Update docker-compose and env examples - Update parity docs (DECISIONS, ISSUES, PROGRESS) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
a72fa607a5
commit
19d490e790
@@ -0,0 +1,92 @@
|
||||
# طرح افزودن درگاه پیامکی جدید (Relay / `createsms`)
|
||||
|
||||
> وضعیت: **اجرا شد** (2026-10-05) — تصمیم ثبتشده: D26.
|
||||
> تاریخ: 2026-10-05
|
||||
|
||||
## ۱. کد ورودی چه میکند
|
||||
|
||||
کد PHP ارسالی (Laravel) یک پیامک را به یک سرویس واسط (relay) میفرستد:
|
||||
|
||||
| مورد | مقدار |
|
||||
|---|---|
|
||||
| متد / آدرس | `POST http://185.231.112.101/api/createsms` (HTTP ساده، بدون TLS) |
|
||||
| بدنه | JSON |
|
||||
| هدر احراز هویت | `secret_key: <کلید>` |
|
||||
| timeout | ۱۵ ثانیه |
|
||||
| فیلد `phone` | شماره گیرنده |
|
||||
| فیلد `text` | متن پیام، `trim()` شده |
|
||||
| فیلد `host` | `Controller::Config('montek')` و اگر خالی بود IP خود سرور (`gethostbyname(gethostname())`) — یعنی شناسهی «این مشتری/سرور» نزد سرویس واسط |
|
||||
| فیلد `platform` | اگر `$sendFromKave` درست باشد `"kave"` (کاوهنگار)، وگرنه `null` (خط پیشفرض سرویس واسط) |
|
||||
| `CURLOPT_IPRESOLVE = V4` | فقط اجبار IPv4؛ چون آدرس خودش IP است در عمل اثری ندارد |
|
||||
|
||||
این کد در `henza_server` وجود ندارد (جستجو شد)، پس از سیستم دیگری آمده و مرجع parity ندارد.
|
||||
|
||||
## ۲. کجا قرار میگیرد
|
||||
|
||||
ماژول پیامک همین الان در [apps/api/src/modules/sms/sms.service.ts](../apps/api/src/modules/sms/sms.service.ts) هست و همهی مصرفکنندهها (OTP ورود، پیامک خوشآمد، اعلان تکمیل پروفایل، …) فقط `SmsService.send()` را صدا میزنند. پس:
|
||||
|
||||
- **هیچ مصرفکنندهای تغییر نمیکند.** فقط یک «درایور» دوم کنار Trez اضافه میشود و با `SMS_PROVIDER=relay` انتخاب میشود.
|
||||
- قوانین فعلی حفظ میشود: `send()` هرگز throw نمیکند (D20)، هر پیام در `SmsMessage` ثبت میشود، متن OTP ذخیره نمیشود (SEC-11)، نبود تنظیمات فقط هشدار میدهد و سرور بالا میآید.
|
||||
|
||||
## ۳. تغییرات پیشنهادی
|
||||
|
||||
### ۳.۱ کد — `sms.service.ts`
|
||||
1. `smsConfig()` متغیرهای جدید را هم میخواند (جدول ۳.۲).
|
||||
2. `smsConfigured()` برای `relay`: کافی است `SMS_RELAY_SECRET` و `SMS_RELAY_HOST` پر باشند.
|
||||
3. `assertSmsConfigured()` مقدار `relay` را میشناسد و متغیرهای خالی را با نام گزارش میکند (بدون throw).
|
||||
4. متد جدید `deliverViaRelay(mobile, text, platform)`:
|
||||
- `fetch` با `Content-Type: application/json` و هدر `secret_key`، بدنهی `{ phone, host, text, platform }`، timeout ۱۵ ثانیه با `AbortController` — همشکل `deliverViaTrez`.
|
||||
- موفقیت = پاسخ HTTP 2xx (تا وقتی قالب پاسخ سرویس واسط مشخص شود — سؤال ۴).
|
||||
5. `SmsSendOptions` یک گزینهی اختیاری `platform?: 'kave'` میگیرد؛ درایور Trez آن را نادیده میگیرد.
|
||||
6. اجبار IPv4 پیاده نمیشود چون آدرس IP است؛ اگر بعداً دامنه شد، با `dns.lookup` با `family: 4` اضافه میشود.
|
||||
|
||||
### ۳.۲ متغیرهای محیطی جدید
|
||||
|
||||
| متغیر | اجباری | توضیح |
|
||||
|---|---|---|
|
||||
| `SMS_PROVIDER` | — | مقدار جدید `relay` |
|
||||
| `SMS_RELAY_URL` | خیر | پیشفرض `http://185.231.112.101/api/createsms` |
|
||||
| `SMS_RELAY_SECRET` | **بله** | مقدار هدر `secret_key` — **فقط در env، هرگز در کد یا git** |
|
||||
| `SMS_RELAY_HOST` | **بله** | مقدار فیلد `host` (معادل `Config('montek')`) |
|
||||
| `SMS_RELAY_KAVE_FOR` | خیر | کدام پیامها از کاوهنگار بروند — بسته به پاسخ سؤال ۲ |
|
||||
|
||||
### ۳.۳ مستندات و استقرار
|
||||
- `.env.example` (ریشه)، `apps/api/.env.example`، `docker-compose.yml`، `DELIVERY.md` (بخش SMS و گام ۴): افزودن متغیرهای بالا.
|
||||
- `docs/parity/DECISIONS.md`: ثبت یک تصمیم جدید (D26) برای درایور relay.
|
||||
|
||||
### ۳.۴ تست — `sms.service.spec.ts`
|
||||
- تنظیمات: پیشفرض URL، `smsConfigured` با/بدون secret و host، هشدار با نام متغیرهای خالی.
|
||||
- ارسال با `fetch` ماکشده: آدرس، هدر `secret_key`، بدنهی JSON، `trim` متن، `platform` درست؛ و اینکه خطای شبکه/timeout/HTTP 500 به `{ ok: false }` تبدیل شود و throw نشود.
|
||||
|
||||
## ۴. هشدار امنیتی ⚠
|
||||
|
||||
1. **کلید `secret_key` لو رفته فرض شود.** در متن پیام چت آمده و احتمالاً در git پروژهی PHP هم هست (مشابه SEC-12). باید از صاحب سرویس واسط کلید جدید گرفت و فقط در env سرور گذاشت. این کلید در کد این مخزن نوشته نمیشود.
|
||||
2. **ارتباط HTTP ساده است**؛ کلید و متن پیام (از جمله کد OTP) رمزنشده از اینترنت عبور میکنند. اگر سرویس واسط HTTPS دارد از آن استفاده شود.
|
||||
|
||||
## ۵. سؤالها (با پیشنهاد)
|
||||
|
||||
1. **جایگزین یا کنار Trez؟** پیشنهاد: کنار Trez بماند و با `SMS_PROVIDER=relay` انتخاب شود؛ پیشفرض همان `trez` بماند تا رفتار فعلی عوض نشود. یا پیشفرض `relay` شود؟
|
||||
2. **`sendFromKave` کی درست است؟** در کد اصلی این متغیر از کجا مقدار میگیرد؟ پیشنهاد: فقط OTP ورود از کاوهنگار برود (معمولاً برای سرعت/تحویل بهتر) و بقیه از خط پیشفرض — قابل تنظیم با env.
|
||||
3. **مقدار `host` (`montek`) چیست؟** پیشنهاد: اجباری در env. fallback به IP خود سرور پیاده نمیشود، چون داخل Docker، IP کانتینر (مثلاً `172.x`) برمیگردد که برای سرویس واسط بیمعناست.
|
||||
4. **پاسخ سرویس واسط چه شکلی است؟** اگر در خطا هم 200 برمیگرداند (مثلاً `{"status": false}`)، نمونهی پاسخ موفق و ناموفق لازم است تا `ok` درست تشخیص داده شود. پیشنهاد فعلی: فقط وضعیت HTTP.
|
||||
5. **HTTPS دارد؟** (بند ۴.۲)
|
||||
|
||||
## ۷. پاسخهای مالک (2026-10-05)
|
||||
|
||||
| سؤال | پاسخ | اثر روی پیادهسازی |
|
||||
|---|---|---|
|
||||
| کلید | لو نرفته؛ تأیید دومرحلهای جای دیگری هست؛ داخل پروژه قرار بگیرد | کلید بهعنوان مقدار پیشفرض در کد میآید و `SMS_RELAY_SECRET` فقط آن را بازنویسی میکند. بند ۴.۱ لغو شد. |
|
||||
| ۱. جایگزین Trez؟ | پیشفرض `relay` شود؛ Trez حذف نشود؛ ممکن است بعداً سرویسهای بیشتری بیاید | سرویس به ساختار «درایور» بازنویسی میشود: یک اینترفیس `SmsDriver` و هر سرویس یک فایل جدا (`drivers/relay.driver.ts`، `drivers/trez.driver.ts`). برای اضافه کردن سرویس بعدی فقط یک فایل و یک خط ثبت لازم است. پیشفرض `SMS_PROVIDER` برابر `relay` میشود. |
|
||||
| ۳. `host` | IP سرور درست است | `SMS_RELAY_HOST` اختیاری است. اگر خالی باشد، مثل PHP اولین IPv4 غیرداخلی سرور فرستاده میشود. |
|
||||
| ۴. پاسخ | با تشخیص خودم | `ok` فقط وقتی است که HTTP 2xx باشد و بدنهی JSON هم نشانهی شکست صریح نداشته باشد (`status: false`، `success: false`، `error`). در غیر این صورت `ok: false` + لاگ؛ هیچوقت throw نمیشود. |
|
||||
| ۵. HTTPS | دارد | بررسی شد: روی `https://185.231.112.101` گواهی `*.montpar.ir` نصب است که **از 2026-08-22 منقضی شده** و روی IP هم معتبر نیست ← سؤال ب |
|
||||
|
||||
### سؤالهای باز
|
||||
- **الف) `sendFromKave`:** کدام پیامها از کاوهنگار بروند؟ پیشنهاد: فقط OTP ورود.
|
||||
- **ب) آدرس HTTPS:** نام دامنهی سرویس (زیرمجموعهای از `montpar.ir`) چیست؟ گواهی هم باید تمدید شود. تا آن موقع پیشفرض همان `http://185.231.112.101/api/createsms` میماند و با `SMS_RELAY_URL` عوض میشود.
|
||||
|
||||
## ۶. وضعیت اجرا
|
||||
|
||||
- [x] تأیید طرح و پاسخ سؤالها (الف: همهی پیامها از کاوهنگار؛ ب: همان IP و HTTP درست است)
|
||||
- [x] کد + تست (`apps/api/src/modules/sms/drivers/`، ۱۳۵ تست سبز)
|
||||
- [x] env / compose / DELIVERY / DECISIONS (D26) / ISSUES (REL-02)
|
||||
Reference in New Issue
Block a user