forked from Yara724/api
595 lines
46 KiB
HTML
595 lines
46 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<title>External Integrations Reference</title>
|
||
<style>
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
font-family: -apple-system, "Segoe UI", system-ui, sans-serif;
|
||
font-size: 14px; line-height: 1.6;
|
||
background: #ffffff; color: #1f2328; padding: 24px;
|
||
}
|
||
h1 { font-size: 20px; font-weight: 700; margin-bottom: 4px; }
|
||
.subtitle { font-size: 13px; color: #57606a; margin-bottom: 28px; }
|
||
h2 {
|
||
font-size: 15px; font-weight: 700;
|
||
margin-bottom: 10px; margin-top: 32px;
|
||
border-bottom: 1px solid #e5e7eb; padding-bottom: 6px;
|
||
}
|
||
h3 {
|
||
font-size: 12px; font-weight: 700;
|
||
text-transform: uppercase; letter-spacing: 0.05em;
|
||
color: #57606a; margin-bottom: 8px; margin-top: 14px;
|
||
}
|
||
.section-intro {
|
||
font-size: 13px; color: #57606a;
|
||
margin-bottom: 14px; line-height: 1.5;
|
||
}
|
||
.card {
|
||
border: 1px solid #e5e7eb; border-radius: 6px;
|
||
padding: 16px; background: #f7f8fa; margin-bottom: 16px;
|
||
}
|
||
.card.card-blue { border-left: 4px solid #3b82f6; }
|
||
.card.card-green { border-left: 4px solid #22c55e; }
|
||
.card.card-purple { border-left: 4px solid #8b5cf6; }
|
||
.card.card-orange { border-left: 4px solid #f97316; }
|
||
.card.card-teal { border-left: 4px solid #14b8a6; }
|
||
.card.card-indigo { border-left: 4px solid #6366f1; }
|
||
.card.card-gray { border-left: 4px solid #94a3b8; }
|
||
.card.card-red { border-left: 4px solid #ef4444; }
|
||
.card.card-yellow { border-left: 4px solid #eab308; }
|
||
table {
|
||
border-collapse: collapse; width: 100%;
|
||
font-size: 12px; margin-top: 4px;
|
||
}
|
||
th {
|
||
background: #f1f5f9; font-weight: 600;
|
||
text-align: left; padding: 5px 8px; border: 1px solid #e5e7eb;
|
||
}
|
||
td { padding: 4px 8px; border: 1px solid #e5e7eb; vertical-align: top; }
|
||
tr:nth-child(even) td { background: #ffffff; }
|
||
code { font-family: monospace; font-size: 11px; color: #3b82d4; }
|
||
.method {
|
||
font-family: monospace; font-size: 11px;
|
||
font-weight: 700; white-space: nowrap;
|
||
}
|
||
.method.get { color: #059669; }
|
||
.method.post { color: #2563eb; }
|
||
.method.put { color: #d97706; }
|
||
.method.patch { color: #7c3aed; }
|
||
.note { font-size: 11px; color: #57606a; font-style: italic; margin-top: 6px; }
|
||
.warn { font-size: 11px; color: #9a3412; font-style: italic; margin-top: 6px; }
|
||
.status-badge {
|
||
display: inline-block; font-size: 11px; font-weight: 600;
|
||
padding: 1px 7px; border-radius: 10px;
|
||
}
|
||
.status-live { background: #dcfce7; color: #166534; }
|
||
.status-partial { background: #ffedd5; color: #9a3412; }
|
||
.status-disabled { background: #fee2e2; color: #991b1b; }
|
||
.status-internal { background: #f1f5f9; color: #475569; border: 1px solid #e2e8f0; }
|
||
.toc {
|
||
background: #f7f8fa; border: 1px solid #e5e7eb;
|
||
border-radius: 6px; padding: 14px 18px; margin-bottom: 28px;
|
||
}
|
||
.toc-title { font-size: 13px; font-weight: 700; margin-bottom: 8px; }
|
||
.toc ol { padding-left: 18px; }
|
||
.toc li { font-size: 13px; margin-bottom: 3px; }
|
||
.toc a { color: #3b82d4; text-decoration: none; }
|
||
.toc a:hover { text-decoration: underline; }
|
||
footer {
|
||
text-align: center; font-size: 12px; color: #57606a;
|
||
border-top: 1px solid #e5e7eb; margin-top: 40px; padding-top: 12px;
|
||
}
|
||
.max-wrap { max-width: 760px; margin: 0 auto; }
|
||
.flow-box {
|
||
background: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px;
|
||
padding: 10px 14px; font-size: 12px; margin-top: 8px;
|
||
}
|
||
.flow-step {
|
||
display: flex; gap: 10px; align-items: flex-start; margin-bottom: 6px;
|
||
}
|
||
.flow-num {
|
||
flex-shrink: 0; width: 20px; height: 20px; border-radius: 50%;
|
||
background: #3b82d4; color: #fff; font-size: 11px; font-weight: 700;
|
||
display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.flow-text { flex: 1; padding-top: 2px; }
|
||
.decision-tree {
|
||
font-size: 12px; background: #ffffff;
|
||
border: 1px solid #e5e7eb; border-radius: 4px; padding: 12px 16px;
|
||
margin-top: 8px; line-height: 1.8;
|
||
}
|
||
.decision-tree ul { padding-left: 20px; }
|
||
.decision-tree li { margin-bottom: 2px; }
|
||
.env-table th:first-child { width: 220px; }
|
||
pre {
|
||
font-family: monospace; font-size: 11px;
|
||
background: #f1f5f9; border: 1px solid #e5e7eb;
|
||
border-radius: 4px; padding: 10px 12px;
|
||
white-space: pre-wrap; word-break: break-all;
|
||
margin-top: 6px; color: #1f2328;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="max-wrap">
|
||
<h1>External Integrations Reference</h1>
|
||
<p class="subtitle">
|
||
Every outbound integration: what it does, when it fires, how auth works,
|
||
retry behaviour, fallbacks, and all environment variables. Internal-only
|
||
services (captcha, offline inquiry seed) are included for completeness.
|
||
</p>
|
||
|
||
<!-- TOC -->
|
||
<div class="toc">
|
||
<div class="toc-title">Contents</div>
|
||
<ol>
|
||
<li><a href="#inquiry-routing">Inquiry routing decision tree</a></li>
|
||
<li><a href="#fanavaran">Fanavaran — insurance claims platform</a></li>
|
||
<li><a href="#sanhub">SandHub — legacy inquiry gateway</a></li>
|
||
<li><a href="#tejarat">Tejarat inquiry — block-inquiry gateway (V2+)</a></li>
|
||
<li><a href="#esg">ESG — Parsian-tenant inquiry provider</a></li>
|
||
<li><a href="#sms">SMS — Kavenegar and Parsian gateways</a></li>
|
||
<li><a href="#ai">AI service — car damage detection</a></li>
|
||
<li><a href="#car-pricing">Car pricing service — market value lookup</a></li>
|
||
<li><a href="#offline-inquiry">Offline inquiry — fallback seed data</a></li>
|
||
<li><a href="#env-ref">Environment variable reference</a></li>
|
||
</ol>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||
<h2 id="inquiry-routing">1 — Inquiry Routing Decision Tree</h2>
|
||
<p class="section-intro">
|
||
Every blame file starts with a "run-inquiries" call that fetches the
|
||
guilty party's insurance policy from an external provider. Which provider
|
||
is actually called depends on three factors: the tenant (<code>CLIENT_ID</code>),
|
||
the file type (THIRD_PARTY vs CAR_BODY), and whether live API mode is
|
||
enabled in system settings. The offline-inquiry seed layer sits in front
|
||
of all three providers.
|
||
</p>
|
||
|
||
<div class="card card-indigo">
|
||
<h3>Provider selection</h3>
|
||
<div class="decision-tree">
|
||
<strong>For every plate-based block inquiry:</strong>
|
||
<ul>
|
||
<li>The request always uses the submitted current plate and the resolved policyholder for that policy type. Recent-transfer metadata never triggers a previous-plate or previous-policyholder lookup.</li>
|
||
<li>1. Check offline-inquiry seeds (MongoDB) — if a matching seed exists, return it and skip all HTTP.</li>
|
||
<li>2. If <code>CLIENT_ID=8</code> (Parsian/ESG tenant) → route to <strong>ESG</strong> <code>/inquiry/policyByPlate</code> or <code>/inquiry/policyByChassis</code>.</li>
|
||
<li>3. Otherwise → route to <strong>Tejarat inquiry</strong> <code>/block-inquiry-tejarat</code> (THIRD_PARTY) or <code>/block-inquiry-tejarat/badane</code> (CAR_BODY).</li>
|
||
<li>4. If <code>system_settings.externalApis.sandHubUseLiveApi = false</code> (default) → return mock response instead of making HTTP calls.</li>
|
||
</ul>
|
||
<br>
|
||
<strong>For personal-identity, driving-licence, ownership, and Sheba checks:</strong>
|
||
<ul>
|
||
<li>If <code>CLIENT_ID=8</code> → ESG <code>/inquiry/person</code> and <code>/inquiry/sheba</code>.</li>
|
||
<li>Otherwise → Tejarat/SandHub <code>/personal-inquiry/tejarat-no</code>, <code>/driver-license-check</code>, <code>/ownership</code>, <code>/sheba/sheba-tejaratno</code>.</li>
|
||
</ul>
|
||
<br>
|
||
<strong>Key difference — birth date format:</strong>
|
||
SandHub/Tejarat expect a <em>Gregorian</em> birth date (converted internally from Jalali).
|
||
ESG expects the <em>Jalali</em> date directly.
|
||
</div>
|
||
<p class="note" style="margin-top:8px;">
|
||
SandHub endpoints are only used in legacy code paths. All active V2+ blame flows go through the Tejarat or ESG providers.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||
<h2 id="fanavaran">2 — Fanavaran <span class="status-badge status-live">live</span></h2>
|
||
<p class="section-intro">
|
||
Fanavaran (<code>apimanager.iraneit.com</code>) is the national insurance
|
||
damage-case platform. After the damage expert submits their assessment,
|
||
the system auto-submits a structured claim to Fanavaran through a
|
||
four-step protocol. Fanavaran also serves as the lookup source for
|
||
code-lists (accident types, car components, city codes, etc.) used
|
||
across the platform.
|
||
</p>
|
||
|
||
<div class="card card-blue">
|
||
<h3>Authentication lifecycle</h3>
|
||
<div class="flow-box">
|
||
<div class="flow-step"><div class="flow-num">1</div><div class="flow-text"><strong>GET AppToken</strong> — <code>POST /EITAuthentication/GetAppToken</code> with <code>appname</code> + <code>secret</code> headers. Returns <code>apptoken</code> header.</div></div>
|
||
<div class="flow-step"><div class="flow-num">2</div><div class="flow-text"><strong>Login</strong> — <code>POST /EITAuthentication/Login</code> with <code>appToken</code> + <code>userName</code> + <code>password</code> headers. Returns <code>authenticationToken</code> header.</div></div>
|
||
<div class="flow-step"><div class="flow-num">3</div><div class="flow-text"><strong>Cache</strong> — token is cached in memory <em>and</em> persisted to MongoDB (<code>fanavaran_auth_tokens</code>). Valid until midnight <strong>Asia/Tehran</strong> — the first call after 00:00 fetches a fresh token.</div></div>
|
||
<div class="flow-step"><div class="flow-num">4</div><div class="flow-text"><strong>All subsequent calls</strong> include four headers: <code>authenticationToken</code>, <code>CorpId</code>, <code>ContractId</code>, <code>Location</code> — tenant-specific, hardcoded per <code>FANAVARAN_CLIENT</code> key.</div></div>
|
||
</div>
|
||
<p class="note" style="margin-top:8px;">
|
||
A config fingerprint (hash of appName + secret + username + password + corpId + contractId + location)
|
||
forces a fresh login when any credential changes, even before midnight.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card card-blue">
|
||
<h3>Claim submission protocol (4 steps)</h3>
|
||
<div class="flow-box">
|
||
<div class="flow-step"><div class="flow-num">1</div><div class="flow-text"><strong>Base claim (GEN.03)</strong> — <code>POST /car/third-party-car-financial-claims</code>. Sends owner, driver, insurance, vehicle, and accident data. Returns a Fanavaran <code>claimId</code> and <code>claimNo</code>. SMS is sent to the owner with both identifiers.</div></div>
|
||
<div class="flow-step"><div class="flow-num">2</div><div class="flow-text"><strong>Damage cases (GEN.05)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/dmg-cases</code>. One entry per damaged part with component ID, severity, and price. Cap: total ≤ 53 000 000 Toman.</div></div>
|
||
<div class="flow-step"><div class="flow-num">3</div><div class="flow-text"><strong>Attachments (GEN.07)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/files</code>. Documents, car-capture images, and videos referenced by file ID.</div></div>
|
||
<div class="flow-step"><div class="flow-num">4</div><div class="flow-text"><strong>Expertise (GEN.08)</strong> — <code>POST /car/third-party-car-financial-claims/{claimId}/expertise</code>. Expert assessment metadata (expert role, date, result). Finalises the submission.</div></div>
|
||
</div>
|
||
<p class="note" style="margin-top:8px;">
|
||
All four steps are recorded in the <code>fanavaran_audit_logs</code> collection with full request/response bodies, HTTP status, duration, and tracking code for debugging.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card card-blue">
|
||
<h3>Lookup endpoints</h3>
|
||
<p class="note">All under <code>https://apimanager.iraneit.com/BimeApiManager/api/BimeApi/v2.0/</code>. Results are cached to disk (per client key) and in the <code>lookups</code> MongoDB collection. Parsian tenant reads DB before hitting the API; others go to the API first.</p>
|
||
<table>
|
||
<tr><th>Path</th><th>Used for</th></tr>
|
||
<tr><td><code>/car/base-info/accident-causes</code></td><td>accidentReason dropdown options (mapped to local IDs)</td></tr>
|
||
<tr><td><code>/car/code-list/accident-report-type</code></td><td>accidentWay options</td></tr>
|
||
<tr><td><code>/car/base-info/vehicle-use-types</code></td><td>vehicle usage classification</td></tr>
|
||
<tr><td><code>/car/code-list/dmg-pay-method</code></td><td>damage payment method</td></tr>
|
||
<tr><td><code>/car/base-info/driving-licence-types</code></td><td>licence type options</td></tr>
|
||
<tr><td><code>/car/code-list/accident-culprit-type</code></td><td>guilty-party classification</td></tr>
|
||
<tr><td><code>/car/code-list/inspection-place</code></td><td>inspection location options</td></tr>
|
||
<tr><td><code>/car/code-list/drop-amount-status</code></td><td>price-drop status codes</td></tr>
|
||
<tr><td><code>/car/base-info/car-components</code></td><td>component catalog (maps to outer/inner parts)</td></tr>
|
||
<tr><td><code>/car/code-list/accident-level</code></td><td>accident severity options</td></tr>
|
||
<tr><td><code>/common/code-list/insurance-corp</code></td><td>resolve <code>INSURANCE_CORP_ID</code> → Fanavaran corpId</td></tr>
|
||
<tr><td><code>/common/base-info/cities</code>, <code>/common/base-info/Provinces</code></td><td>city/province pickers</td></tr>
|
||
<tr><td><code>/car/third-party-car-policies/{policyId}</code></td><td>fetch full policy by ID after inquiry</td></tr>
|
||
<tr><td><code>/car/vehicles/inquiry-by-vin?vin=…</code></td><td>VIN-based vehicle lookup</td></tr>
|
||
<tr><td><code>/common/Policies/inquiry-my-policies</code></td><td>list policies for a national code</td></tr>
|
||
<tr><td><code>/common/customers/{customerId}</code></td><td>fetch customer record by ID</td></tr>
|
||
<tr><td><code>/common/parties/inquiry-by-unique-identifier</code></td><td>party lookup by national code + birth date</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card card-blue">
|
||
<h3>Error handling & resilience</h3>
|
||
<table>
|
||
<tr><th>Mechanism</th><th>Detail</th></tr>
|
||
<tr><td>Retry</td><td>3 attempts, 500 ms → 1 000 ms exponential backoff on all HTTP calls.</td></tr>
|
||
<tr><td>Transient backoff</td><td>When Fanavaran returns the Persian "try again later" message (or tracking-code 500), a tenant-wide 5-minute pause is activated. All calls during this window get <code>503 ServiceUnavailable</code> immediately — no hammering.</td></tr>
|
||
<tr><td>Token invalidation</td><td>On 401, token is cleared from memory and MongoDB; next call triggers a fresh GetAppToken + Login.</td></tr>
|
||
<tr><td>Inflight de-dup</td><td>Concurrent login requests for the same tenant are collapsed to a single in-flight Promise.</td></tr>
|
||
<tr><td>Audit log</td><td>Every step (GET_APP_TOKEN, LOGIN, and all four submission steps) is written to <code>fanavaran_audit_logs</code> with STARTED / SUCCESS / FAILURE status, full headers, body, and duration.</td></tr>
|
||
<tr><td>Timeout</td><td>20–30 s per HTTP call.</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card card-blue">
|
||
<h3>Tenant profiles (<code>FANAVARAN_CLIENT</code>)</h3>
|
||
<p class="section-intro" style="margin-top:6px; margin-bottom:8px;">Three pre-seeded tenant profiles exist. The active one is chosen by the <code>FANAVARAN_CLIENT</code> env var. Each profile carries its own <code>appName</code>, <code>secret</code>, <code>username</code>, <code>password</code>, <code>CorpId</code>, <code>ContractId</code>, and <code>Location</code> headers, plus payload defaults (AccidentCityId, etc.).</p>
|
||
<table>
|
||
<tr><th>Key</th><th>Insurance company</th></tr>
|
||
<tr><td><code>parsian</code></td><td>Parsian Insurance</td></tr>
|
||
<tr><td><code>tejaratno</code></td><td>Tejaratno Insurance</td></tr>
|
||
<tr><td><code>moallem</code></td><td>Moallem Insurance</td></tr>
|
||
</table>
|
||
<p class="note" style="margin-top:8px;">
|
||
<code>INSURANCE_CORP_ID</code> is a display-caption string (e.g. <em>"بیمه پارسیان"</em>) that is resolved against the live Fanavaran <code>insurance-corp</code> list to produce the numeric <code>corpId</code> used in submissions. The resolved ID is cached to disk.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||
<h2 id="sanhub">3 — SandHub <span class="status-badge status-partial">legacy</span></h2>
|
||
<p class="section-intro">
|
||
SandHub is the original inquiry gateway. It is still present in the
|
||
codebase but all active blame flows (V2+) have been migrated to the
|
||
Tejarat inquiry provider. SandHub endpoints remain callable but are
|
||
only reached through legacy code paths. Its mock mode is controlled
|
||
by the same <code>sandHubUseLiveApi</code> system setting.
|
||
</p>
|
||
|
||
<div class="card card-gray">
|
||
<h3>Auth</h3>
|
||
<p class="note">
|
||
<code>POST {SANHUB_BASE_URL}/user/login</code> with username + password JSON body.
|
||
Token cached in memory for <strong>55 minutes</strong>. On 401, token is cleared and one retry is made.
|
||
3 attempts with 1 000 ms → 2 000 ms exponential backoff.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card card-gray">
|
||
<h3>Endpoints</h3>
|
||
<table>
|
||
<tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/block-inquiry-tejarat</code></td><td>Plate-based insurance policy inquiry (THIRD_PARTY). Body: <code>leftTwoDigits</code>, <code>serialLetter</code>, <code>threeDigits</code>, <code>rightTwoDigits</code>, <code>nationalCode</code>.</td></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/block-inquiry-tejarat/badane</code></td><td>CAR_BODY policy inquiry. Timeout 50 s (longer than standard).</td></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/personal-inquiry/tejarat-no</code></td><td>Personal identity check. Body: <code>nationalCode</code> + <em>Gregorian</em> <code>birthDate</code> (converted from Jalali internally).</td></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/driver-license-check</code></td><td>Driving licence validation. Returns <code>IsSucceed</code> flag.</td></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/ownership</code></td><td>Vehicle ownership check. Returns <code>IsSuccess</code> flag.</td></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/sheba/sheba-tejaratno</code></td><td>Sheba / bank account validation. Returns <code>ReturnValue</code> + <code>HasError</code>.</td></tr>
|
||
</table>
|
||
<p class="note" style="margin-top:8px;">
|
||
All endpoints support full mock responses when <code>sandHubUseLiveApi=false</code> in system settings (default). Mock data is deterministic and produced locally without any HTTP calls.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||
<h2 id="tejarat">4 — Tejarat Inquiry <span class="status-badge status-live">live</span></h2>
|
||
<p class="section-intro">
|
||
The active block-inquiry gateway for all non-ESG tenants. Used in every
|
||
V2+ <code>run-inquiries</code> call where <code>CLIENT_ID ≠ 8</code>.
|
||
The base URL is configurable; in production it points to the same host
|
||
as SandHub but uses separate credentials.
|
||
</p>
|
||
|
||
<div class="card card-teal">
|
||
<h3>Auth</h3>
|
||
<p class="note">
|
||
<code>POST {TEJARAT_INQUIRY_BASE_URL}/user/login</code> with email + password JSON body.
|
||
Token cached for <strong>55 minutes</strong>. 2 attempts with 500 ms → 1 000 ms backoff.
|
||
Separate from SandHub credentials — uses <code>TEJARAT_INQUIRY_EMAIL</code> / <code>TEJARAT_INQUIRY_PASSWORD</code>.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card card-teal">
|
||
<h3>Endpoints</h3>
|
||
<table>
|
||
<tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/block-inquiry-tejarat</code></td><td>THIRD_PARTY plate inquiry. Body: plate fields + <code>nationalCode</code>. Offline seed checked first.</td></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/block-inquiry-tejarat/badane</code></td><td>CAR_BODY plate inquiry. Body: <code>part1–part4</code> (numeric) + <code>nationalCode</code>. Always goes live (no mock for badane path).</td></tr>
|
||
</table>
|
||
<p class="note" style="margin-top:8px;">
|
||
When <code>sandHubUseLiveApi=false</code>, the THIRD_PARTY path returns a mock response without HTTP. The CAR_BODY path always calls the live API regardless of this flag.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||
<h2 id="esg">5 — ESG <span class="status-badge status-live">live (CLIENT_ID=8)</span></h2>
|
||
<p class="section-intro">
|
||
ESG is an internal insurance API gateway used exclusively by the Parsian
|
||
tenant (<code>CLIENT_ID=8</code>). It replaces Tejarat/SandHub for all
|
||
inquiry types when this tenant is active. It has a different response
|
||
shape, a dynamic token TTL, and expects birth dates in <strong>Jalali</strong>
|
||
format (not Gregorian, unlike SandHub/Tejarat).
|
||
</p>
|
||
|
||
<div class="card card-purple">
|
||
<h3>Auth</h3>
|
||
<p class="note">
|
||
<code>POST {ESG_URL}/auth/login</code> with <code>{ username, password }</code> JSON body.
|
||
Token TTL is read from the response <code>expiresIn</code> field (default 14 min).
|
||
2 attempts with 500 ms → 1 000 ms backoff. On 401, token cleared and one retry.
|
||
Default URL: <code>http://192.168.20.22:8085</code> (internal network).
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card card-purple">
|
||
<h3>Endpoints</h3>
|
||
<table>
|
||
<tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/policyByPlate</code></td><td>Plate-based policy lookup (THIRD_PARTY). Body: <code>nationalCode</code>, <code>plk1–plk4</code>. Response is mapped to the old Tejarat format before being stored.</td></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/policyByChassis</code></td><td>VIN/chassis-based alternative to plate inquiry. Called by <code>run-inquiries-vin</code> endpoints. Uses ESG chassis lookup (not the SandHub path). Body: <code>nationalCode</code>, <code>chassis</code>.</td></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/person</code></td><td>Personal identity check. Body: <code>nationalCode</code>, <code>birthDate</code> (Jalali, NOT Gregorian).</td></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>/inquiry/sheba</code></td><td>Sheba / bank account validation.</td></tr>
|
||
</table>
|
||
<p class="note" style="margin-top:8px;">
|
||
ESG wraps every response as <code>{ success: boolean, data: … }</code>. For normalized error envelopes, the backend returns <code>error.messageFa</code> unchanged to the caller; technical fields such as <code>message</code>, <code>providerMessage</code>, and <code>providerCode</code> remain available for logging and classification. A business-level not-found response is not reported as a provider outage.
|
||
The offline-inquiry seed check still runs first, before any ESG HTTP call.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||
<h2 id="sms">6 — SMS <span class="status-badge status-live">live</span></h2>
|
||
<p class="section-intro">
|
||
Two SMS providers are supported: <strong>Kavenegar</strong> (default)
|
||
and <strong>Parsian SMS Gateway</strong>. The active provider is chosen
|
||
by the <code>SMS_PROVIDER</code> (or <code>SMS</code>) env var. Both
|
||
providers implement the same internal gateway interface so the
|
||
orchestration layer is provider-agnostic.
|
||
</p>
|
||
|
||
<div class="card card-green">
|
||
<h3>Provider selection</h3>
|
||
<table>
|
||
<tr><th>Env var</th><th>Value</th><th>Active provider</th></tr>
|
||
<tr><td><code>SMS_PROVIDER</code> (or <code>SMS</code>)</td><td><code>kavenegar</code> (default)</td><td>Kavenegar — <code>api.kavenegar.com</code></td></tr>
|
||
<tr><td><code>SMS_PROVIDER</code> (or <code>SMS</code>)</td><td><code>parsian</code></td><td>Parsian SMS Gateway — <code>PARSIAN_SMS_URL</code></td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card card-green">
|
||
<h3>Kavenegar endpoints</h3>
|
||
<p class="note">Base URL: <code>https://api.kavenegar.com/v1/{SMS_API_KEY}/</code></p>
|
||
<table>
|
||
<tr><th style="width:70px">Method</th><th>Path</th><th>When used</th></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>sms/send.json</code></td><td>Plain-text messages (e.g. key-based notification texts stored in <code>sms_texts</code> collection).</td></tr>
|
||
<tr><td><span class="method get">GET</span></td><td><code>verify/lookup.json</code></td><td>All template-based messages (OTPs, invite links, expert notifications). Params: <code>receptor</code>, <code>token</code>[, <code>token2</code>, <code>token3</code>, <code>token10</code>], <code>template</code>.</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card card-green">
|
||
<h3>Parsian SMS Gateway</h3>
|
||
<p class="note">Base URL from <code>PARSIAN_SMS_URL</code>. Auth: <code>X-PACKAGE-API-KEY</code> header + <code>Authorization: Basic {PARSIAN_BASIC_TOKEN}</code>. Sends as a GET with URL-encoded <code>ReceiverNumbers</code> and <code>Message</code> query params. Template messages are pre-rendered into a plain text body before sending (no verify/lookup equivalent).</p>
|
||
</div>
|
||
|
||
<div class="card card-green">
|
||
<h3>SMS templates in use</h3>
|
||
<table>
|
||
<tr><th>Template name</th><th>Trigger</th><th>Tokens</th></tr>
|
||
<tr><td><code>AUTH_SMS_TEMPLATE</code> (env)</td><td>User / actor OTP login, forget-password, party OTPs</td><td><code>token</code> = OTP code</td></tr>
|
||
<tr><td><code>yara724-invite-link</code></td><td>Second party receives blame invite link via SMS</td><td><code>token</code> = publicId, <code>token2</code> = link</td></tr>
|
||
<tr><td><code>yara-field-expert-link</code></td><td>Field expert sends link to a party</td><td><code>token</code> = file type, <code>token2</code> = expert surname, <code>token3</code> = link</td></tr>
|
||
<tr><td><code>yara-blame-agreement</code></td><td>Notify party that the other side agreed to the expert verdict</td><td><code>token</code> = publicId, <code>token2</code> = link</td></tr>
|
||
<tr><td><code>yara-claim-link</code></td><td>Damaged party notified to open claim flow after blame is complete</td><td><code>token</code> = publicId, <code>token2</code> = link</td></tr>
|
||
<tr><td><code>yara-expert-lock</code></td><td>Expert locks a blame or claim file</td><td><code>token</code> = "تصادف"/"خسارت", <code>token2</code> = publicId, <code>token3</code> = expert surname</td></tr>
|
||
<tr><td><code>yara-resend-documents</code></td><td>Expert requests document resend</td><td><code>token</code> = file kind, <code>token2</code> = publicId, <code>token3</code> = link</td></tr>
|
||
<tr><td><code>yara-signature</code></td><td>Party notified to sign the expert's damage assessment</td><td><code>token</code> = file kind, <code>token2</code> = publicId, <code>token3</code> = expert surname, <code>token10</code> = link</td></tr>
|
||
<tr><td><code>yara-fanavaran-claim</code></td><td>Fanavaran submission confirmed — sent to claim owner with Fanavaran claim number and ID</td><td><code>token</code> = publicId, <code>token2</code> = Fanavaran claimId, <code>token3</code> = Fanavaran claimNo</td></tr>
|
||
</table>
|
||
<p class="note" style="margin-top:8px;">
|
||
All SMS calls are fire-and-forget — they never throw. Failures are logged but do not block the main flow.
|
||
An <code>sms_send_logs</code> MongoDB collection records every outbound message with its kind (OTP vs TEMPLATE), provider, template name, and success/failure status.
|
||
Notification text messages (parties-disagree, one-party-signed, etc.) are seeded into the <code>sms_texts</code> collection on startup and editable at runtime.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||
<h2 id="ai">7 — AI Service <span class="status-badge status-disabled">disabled (code present)</span></h2>
|
||
<p class="section-intro">
|
||
An image-based car damage detection service is integrated in the
|
||
codebase but its HTTP calls are <strong>fully commented out</strong>.
|
||
The module initialises on startup, attempts a login (silently swallowed
|
||
if it fails), and exposes an <code>aiRequestImage</code> method — but
|
||
the underlying axios calls are disabled. The service does not affect
|
||
any production flow.
|
||
</p>
|
||
|
||
<div class="card card-yellow">
|
||
<h3>Intended interface (when re-enabled)</h3>
|
||
<table>
|
||
<tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>{AI_URL_V2}/auth/login</code></td><td>Authenticate with username + password. Returns <code>accessToken</code>.</td></tr>
|
||
<tr><td><span class="method get">GET</span></td><td><code>{AI_URL_V2}/auth/profile</code></td><td>Fetch <code>apiKey.key</code> needed as the <code>gateway-api-key</code> request header.</td></tr>
|
||
<tr><td><span class="method post">POST</span></td><td><code>{AI_URL_V2}/services/car-damage/detector?version=ai-v7</code></td><td>Submit a car part image (multipart). Returns <code>downloadLink</code> with annotated result.</td></tr>
|
||
</table>
|
||
<p class="warn" style="margin-top:8px;">
|
||
Status: all three calls are wrapped in commented-out <code>axios.request(…)</code> blocks.
|
||
<code>CW_URL</code> is not in <code>.env.example</code>. To re-enable, uncomment the login, getApiKey, and aiRequestImage axios calls, and configure <code>AI_URL_V2</code>, <code>AI_USERNAME</code>, <code>AI_PASSWORD</code>.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||
<h2 id="car-pricing">8 — Car Pricing Service <span class="status-badge status-partial">partially active</span></h2>
|
||
<p class="section-intro">
|
||
Used only during damage-expert price-drop calculation. When an expert
|
||
provides per-part severity values the system fetches real-time market
|
||
prices for the damaged car model, then computes the price-drop using
|
||
the formula: <strong>carPrice × yearCoefficient × sumOfPartCoefficients ÷ 400</strong>.
|
||
The service has two data sources (endpoints) that are tried in parallel.
|
||
</p>
|
||
|
||
<div class="card card-orange">
|
||
<h3>Endpoints</h3>
|
||
<table>
|
||
<tr><th style="width:70px">Method</th><th>Path</th><th>What it does</th></tr>
|
||
<tr><td><span class="method get">GET</span></td><td><code>{CW_URL}price?akharin</code></td><td>Fetch car market prices from the "Akharin" source. Returns array of <code>{ carName, marketPrice }</code>.</td></tr>
|
||
<tr><td><span class="method get">GET</span></td><td><code>{CW_URL}price?hamrah</code></td><td>Fetch car market prices from the "Hamrah" source. Same response shape.</td></tr>
|
||
</table>
|
||
<p class="note" style="margin-top:8px;">
|
||
Both endpoints are tried; results are merged and de-duplicated. The best match for
|
||
the damaged car's name is found using <strong>Levenshtein distance</strong> (fuzzy string match).
|
||
If both endpoints fail or return empty, the price-drop calculation is skipped (marked incomplete) — it does not block claim submission.
|
||
</p>
|
||
<p class="warn" style="margin-top:6px;">
|
||
<strong><code>CW_URL</code> is not documented in <code>.env.example</code>.</strong>
|
||
This service will silently produce no price-drop if the variable is unset.
|
||
</p>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||
<h2 id="offline-inquiry">9 — Offline Inquiry <span class="status-badge status-internal">internal / fallback</span></h2>
|
||
<p class="section-intro">
|
||
The offline inquiry layer intercepts plate-based inquiry calls before
|
||
any external HTTP is made. It is primarily used for development and
|
||
testing (pre-seeded known plates) but also acts as a resilience fallback
|
||
when live inquiry services are unavailable. It is controlled by a
|
||
runtime database flag, not an env var.
|
||
</p>
|
||
|
||
<div class="card card-gray">
|
||
<h3>How it works</h3>
|
||
<table>
|
||
<tr><th>Aspect</th><th>Detail</th></tr>
|
||
<tr><td>Storage</td><td>MongoDB collection <code>offline-inquiries</code>. Documents contain <code>clientKey</code>, normalised plate fields, <code>nationalCode</code>, and the pre-built <code>raw</code> + <code>mapped</code> response to return.</td></tr>
|
||
<tr><td>Master switch</td><td><code>system_settings.offlineInquiry.enabled</code> — defaults to <code>true</code>. Toggle via <code>PATCH /super-admin/system-settings/offline-inquiry</code>.</td></tr>
|
||
<tr><td>Lookup order</td><td>Normalised plate (digits-only, Arabic→Persian) + national code + Fanavaran client key must all match. If found, returned immediately; no HTTP call is made.</td></tr>
|
||
<tr><td>Scope</td><td>Only applies to plate-based block-inquiry (THIRD_PARTY). CAR_BODY inquiry (<code>/badane</code>) always hits the live API.</td></tr>
|
||
<tr><td>Live API flag</td><td><code>system_settings.externalApis.sandHubUseLiveApi</code> — when <code>false</code> (default), even if no offline seed matches, a built-in mock response is returned rather than calling Tejarat/ESG.</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════════════════════════════════ -->
|
||
<h2 id="env-ref">10 — Environment Variable Reference</h2>
|
||
<p class="section-intro">
|
||
All env vars across all integrations, grouped by service.
|
||
Variables marked <strong>*</strong> are not present in <code>.env.example</code>.
|
||
</p>
|
||
|
||
<div class="card card-blue">
|
||
<h3>Fanavaran</h3>
|
||
<table class="env-table">
|
||
<tr><th>Variable</th><th>Description</th></tr>
|
||
<tr><td><code>FANAVARAN_CLIENT</code></td><td>Active tenant profile key: <code>parsian</code> | <code>tejaratno</code> | <code>moallem</code></td></tr>
|
||
<tr><td><code>INSURANCE_CORP_ID</code></td><td>Display caption of the insurer company (e.g. <em>"بیمه پارسیان"</em>) — resolved to a numeric corpId at startup against the Fanavaran insurance-corp list.</td></tr>
|
||
</table>
|
||
<p class="note" style="margin-top:8px;">Per-tenant credentials (appName, secret, username, password, CorpId, ContractId, Location) are hardcoded in <code>src/core/config/fanavaran-client.config.ts</code> under <code>SEED_FANAVARAN_CLIENT_PROFILES</code>.</p>
|
||
</div>
|
||
|
||
<div class="card card-gray">
|
||
<h3>SandHub (legacy)</h3>
|
||
<table class="env-table">
|
||
<tr><th>Variable</th><th>Description</th></tr>
|
||
<tr><td><code>SANHUB_BASE_URL</code></td><td>Base URL for SandHub. Default: <code>http://82.99.202.245:3027</code></td></tr>
|
||
<tr><td><code>SANHUB_URL_LOGIN</code></td><td>Full login URL (usually base + <code>/user/login</code>)</td></tr>
|
||
<tr><td><code>SANHUB_USERNAME</code></td><td>SandHub login email</td></tr>
|
||
<tr><td><code>SANHUB_PASSWORD</code></td><td>SandHub login password</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card card-teal">
|
||
<h3>Tejarat inquiry</h3>
|
||
<table class="env-table">
|
||
<tr><th>Variable</th><th>Description</th></tr>
|
||
<tr><td><code>TEJARAT_INQUIRY_BASE_URL</code></td><td>Base URL. Default: <code>http://82.99.202.245:3027</code></td></tr>
|
||
<tr><td><code>TEJARAT_INQUIRY_EMAIL</code></td><td>Login email</td></tr>
|
||
<tr><td><code>TEJARAT_INQUIRY_PASSWORD</code></td><td>Login password</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card card-purple">
|
||
<h3>ESG (CLIENT_ID=8 only)</h3>
|
||
<table class="env-table">
|
||
<tr><th>Variable</th><th>Description</th></tr>
|
||
<tr><td><code>CLIENT_ID</code></td><td>Set to <code>8</code> to activate the ESG inquiry provider for the Parsian tenant.</td></tr>
|
||
<tr><td><code>ESG_URL</code></td><td>ESG base URL. Default: <code>http://192.168.20.22:8085</code> (internal network)</td></tr>
|
||
<tr><td><code>ESG_USERNAME</code></td><td>ESG login username</td></tr>
|
||
<tr><td><code>ESG_PASSWORD</code></td><td>ESG login password</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card card-green">
|
||
<h3>SMS</h3>
|
||
<table class="env-table">
|
||
<tr><th>Variable</th><th>Description</th></tr>
|
||
<tr><td><code>SMS_PROVIDER</code> (or <code>SMS</code>)</td><td><code>kavenegar</code> (default) or <code>parsian</code></td></tr>
|
||
<tr><td><code>SMS_API_KEY</code></td><td>Kavenegar API key (required when provider = kavenegar)</td></tr>
|
||
<tr><td><code>AUTH_SMS_TEMPLATE</code></td><td>Kavenegar template name for OTP messages (e.g. <code>yara-otp</code>)</td></tr>
|
||
<tr><td><code>PARSIAN_SMS_URL</code></td><td>Parsian SMS Gateway base URL (required when provider = parsian)</td></tr>
|
||
<tr><td><code>PARSIAN_API_KEY</code></td><td>Parsian SMS <code>X-PACKAGE-API-KEY</code> header value</td></tr>
|
||
<tr><td><code>PARSIAN_BASIC_TOKEN</code></td><td>Base64-encoded credentials for <code>Authorization: Basic …</code> header</td></tr>
|
||
<tr><td><code>URL</code></td><td>Frontend base URL — used to build all invite + claim links embedded in SMS messages</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card card-yellow">
|
||
<h3>AI service</h3>
|
||
<table class="env-table">
|
||
<tr><th>Variable</th><th>Description</th></tr>
|
||
<tr><td><code>AI_URL_V2</code></td><td>AI gateway base URL. Default: <code>https://ai-gw.ittalie.ir</code> (unused — service is disabled)</td></tr>
|
||
<tr><td><code>AI_USERNAME</code></td><td>AI service login username (unused)</td></tr>
|
||
<tr><td><code>AI_PASSWORD</code></td><td>AI service login password (unused)</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card card-orange">
|
||
<h3>Car pricing service</h3>
|
||
<table class="env-table">
|
||
<tr><th>Variable</th><th>Description</th></tr>
|
||
<tr><td><code>CW_URL</code> *</td><td>Base URL for car market price API (e.g. <code>https://…/</code>). Not in <code>.env.example</code>. Price-drop silently skipped if unset.</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card card-gray">
|
||
<h3>General / app</h3>
|
||
<table class="env-table">
|
||
<tr><th>Variable</th><th>Description</th></tr>
|
||
<tr><td><code>PORT</code></td><td>HTTP port (default 3000). Used by the Fanavaran insurance-corp fallback to call its own local lookup endpoint.</td></tr>
|
||
<tr><td><code>CAPTCHA_ENABLED</code></td><td><code>true</code> / <code>false</code> — enables/disables login CAPTCHA challenge. Internal, no external service.</td></tr>
|
||
<tr><td><code>EXP_CAPTCHA_TIME</code></td><td>CAPTCHA challenge TTL in minutes.</td></tr>
|
||
<tr><td><code>EXP_OTP_TIME</code></td><td>OTP TTL in minutes.</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<footer>Made by Sepehr</footer>
|
||
</div>
|
||
</body>
|
||
</html>
|