AML/docs/justsolutionsWebV2/KYC订阅字段与租用逻辑分析.html

328 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-HK">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>justsolutionsWebV2 · plans-plus KYC 訂閱欄位與租用邏輯分析</title>
<style>
:root {
--bg: #f6f8fa;
--card: #ffffff;
--text: #24292f;
--muted: #57606a;
--border: #d0d7de;
--accent: #0969da;
--accent-soft: #ddf4ff;
--code-bg: #f0f3f6;
--th-bg: #f0f3f6;
--row-alt: #fafbfc;
--warn-bg: #fff8c5;
--warn-border: #d4a72c;
--note-bg: #ddf4ff;
--note-border: #54aeff;
--gap-bg: #ffebe9;
--gap-border: #ff8182;
--ok-bg: #dafbe1;
--ok-border: #4ac26b;
--get: #1a7f37;
--post: #9a6700;
--new: #8250df;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "PingFang HK", "Hiragino Sans GB", "Microsoft YaHei", Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.65;
font-size: 15px;
}
.wrap { max-width: 1100px; margin: 0 auto; padding: 32px 24px 80px; }
header.page {
background: var(--card); border: 1px solid var(--border);
border-radius: 12px; padding: 28px 32px; margin-bottom: 24px;
}
header.page h1 { margin: 0 0 10px; font-size: 26px; }
header.page p { margin: 4px 0; color: var(--muted); }
header.page .meta { font-size: 13px; }
.toc {
background: var(--card); border: 1px solid var(--border);
border-radius: 12px; padding: 18px 28px; margin-bottom: 28px;
}
.toc h2 { font-size: 13px; margin: 0 0 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; }
.toc ol { margin: 0; padding-left: 20px; columns: 2; column-gap: 36px; }
.toc li { margin: 4px 0; break-inside: avoid; }
.toc a { color: var(--accent); text-decoration: none; }
.toc a:hover { text-decoration: underline; }
section { margin-bottom: 36px; }
h2.sec {
font-size: 21px; border-bottom: 2px solid var(--border);
padding-bottom: 8px; margin: 0 0 18px; scroll-margin-top: 16px;
}
h3 { font-size: 17px; margin: 24px 0 10px; scroll-margin-top: 16px; }
h4 { font-size: 15px; margin: 16px 0 6px; color: var(--muted); }
p { margin: 10px 0; }
table {
border-collapse: collapse; width: 100%; margin: 14px 0;
font-size: 14px; background: var(--card);
border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
}
th, td { border: 1px solid var(--border); padding: 8px 11px; text-align: left; vertical-align: top; }
th { background: var(--th-bg); font-weight: 600; }
tr:nth-child(even) td { background: var(--row-alt); }
code {
background: var(--code-bg); padding: 1.5px 6px; border-radius: 5px;
font-family: "SF Mono", "JetBrains Mono", "Fira Code", Consolas, monospace; font-size: 12.5px;
}
pre {
background: #0d1117; color: #e6edf3; padding: 16px 18px;
border-radius: 8px; overflow-x: auto; font-size: 12.5px; line-height: 1.55;
}
pre code { background: none; padding: 0; color: inherit; font-size: 12.5px; }
.tag { font-size: 11px; padding: 1px 7px; border-radius: 20px; border: 1px solid var(--border); color: var(--muted); white-space: nowrap; }
.callout { border-radius: 8px; padding: 12px 16px; margin: 14px 0; border: 1px solid; }
.callout p { margin: 4px 0; }
.callout.warn { background: var(--warn-bg); border-color: var(--warn-border); }
.callout.note { background: var(--note-bg); border-color: var(--note-border); }
.callout.gap { background: var(--gap-bg); border-color: var(--gap-border); }
.callout.ok { background: var(--ok-bg); border-color: var(--ok-border); }
.callout .lbl { font-weight: 700; }
.pill { display:inline-block; font-size:11px; font-weight:700; padding:1px 8px; border-radius:20px; }
.pill.done { background: var(--ok-bg); color:#1a7f37; border:1px solid var(--ok-border);}
.pill.partial { background: var(--warn-bg); color:#7a5c00; border:1px solid var(--warn-border);}
.pill.no { background: var(--gap-bg); color:#cf222e; border:1px solid var(--gap-border);}
.small { font-size: 13px; color: var(--muted); }
ul.tight { margin: 8px 0; padding-left: 22px; }
ul.tight li { margin: 5px 0; }
.flow { font-family:"SF Mono",Consolas,monospace; font-size:13px; background:var(--code-bg); padding:10px 14px; border-radius:8px; border:1px solid var(--border); overflow-x:auto; white-space:nowrap;}
hr.soft { border:none; border-top:1px dashed var(--border); margin: 26px 0; }
.path { font-family:"SF Mono",Consolas,monospace; font-size:12.5px; color:var(--muted); }
</style>
</head>
<body>
<div class="wrap">
<header class="page">
<h1>plans-plus · <span style="color:#1a7f37">KYC 訂閱欄位與租用邏輯分析</span></h1>
<p>釐清 <code>plans-plus.html</code> 續費查詢頁「上次訂閱資訊」中的 <b>KYC</b> 欄位到底代表什麼、KYC 功能是否受「有沒有租設備」限制、以及是否每個租戶預設開啟;並記錄本次為此做的文案修正。</p>
<p class="meta">範圍:<code>AML_Backend</code><code>iCON.Abp.AMLPortal</code> / <code>iCON.Abp.AML</code>)· <code>AML_Frontend</code>KYC section· <code>justsolutionsWebV2</code>plans-plus 前端文案)</p>
<p class="meta">結論一句話:<b>「上次訂閱資訊」的 KYC 上次訂閱是否含「KYC 讀卡設備租用」加購項</b>;租不租設備<b>只影響前端的「機器掃描」入口</b>,不影響上傳/手動錄入 KYC<b>非每個租戶預設開啟</b></p>
</header>
<nav class="toc">
<h2>目錄</h2>
<ol>
<li><a href="#tldr">一、結論速覽</a></li>
<li><a href="#chain">二、欄位資料鏈路(前端 → 後端)</a></li>
<li><a href="#assign">三、EnableKYC 何時被賦值為 true</a></li>
<li><a href="#gate">四、租設備是否影響「使用 KYC 功能」</a></li>
<li><a href="#default">五、是否每個租戶預設開啟</a></li>
<li><a href="#fix">六、本次文案修正</a></li>
<li><a href="#refs">七、關鍵代碼位置索引</a></li>
</ol>
</nav>
<!-- ───────────────────────────────────────────── -->
<section id="tldr">
<h2 class="sec">一、結論速覽</h2>
<table>
<thead>
<tr><th style="width:38%">問題</th><th style="width:12%">答案</th><th>說明</th></tr>
</thead>
<tbody>
<tr>
<td>「上次訂閱資訊」的 KYC 應該是「是否租用 KYC 設備」嗎?</td>
<td><span class="pill done">基本是</span></td>
<td>該值 <code>TenantProperty.EnableKYC</code>,而它由「下單方案是否含 <code>Tag1Code = KYC</code> 的方案行」決定;此方案行在 plans-plus 對應加購項「<b>KYC 讀卡設備租用</b>」。故它實質表示「上次訂閱有無租 KYC 讀卡設備」。</td>
</tr>
<tr>
<td>有沒有租 KYC 設備,會影響用戶在系統中使用 KYC 功能嗎?</td>
<td><span class="pill partial">只影響掃描入口</span></td>
<td><code>enableKYC</code> 在前端<b>只控制「ID Scan機器掃描」按鈕</b>的顯隱。「上傳」與「手動錄入」兩條路徑始終可用;後端 <code>KYCService/KYCController</code> <b>完全不校驗</b> EnableKYC。</td>
</tr>
<tr>
<td>「能否使用 KYC 功能」是每個租戶預設開啟的嗎?</td>
<td><span class="pill no">不是</span></td>
<td><code>EnableKYC</code> 對一般租戶<b>預設 false</b>,僅在下單方案含 KYC 加購時為 true續費沿用上次值。唯一預設 <code>true</code> 的是 <b>Host 宿主租戶</b>。但因前端僅擋掃描按鈕,所有租戶預設仍可用「上傳/錄入」做 KYC。</td>
</tr>
</tbody>
</table>
</section>
<!-- ───────────────────────────────────────────── -->
<section id="chain">
<h2 class="sec">二、欄位資料鏈路(前端 → 後端)</h2>
<p>plans-plus 續費頁「上次訂閱資訊」卡片的「已購增值 / Add-ons」列經由這條鏈路取得 KYC 標記:</p>
<div class="flow">plans-plus.js <code>previousAddonText()</code><code>currentSubscription.addons.kyc</code> → BFF 直通 → <code>CurrentSubscriptionAddonsDto.Kyc</code><code>tp.EnableKYC</code></div>
<table>
<thead><tr><th style="width:34%"></th><th>內容</th></tr></thead>
<tbody>
<tr>
<td>前端顯示<br><span class="path">plans-plus.js:570-581 / plans-plus.html:347</span></td>
<td><code>showTenantInfo()</code><code>previousAddonText(s)</code> 寫入 <code>#ppTiAddons</code>;當 <code>addons.kyc === true</code> 時,列表加入一個 KYC 標籤。</td>
</tr>
<tr>
<td>續費查詢 DTO<br><span class="path">RenewableTenantByEmailDto.cs:46-54</span></td>
<td><code>CurrentSubscriptionAddonsDto.Kyc</code>,欄位註解明寫「<b>是否已購 KYCTenantProperty.EnableKYC</b>」。</td>
</tr>
<tr>
<td>後端聚合<br><span class="path">OrderService.cs:2460</span></td>
<td><code>Addons = new CurrentSubscriptionAddonsDto { Kyc = tp.EnableKYC }</code>——直接取當前生效 <code>TenantProperty.EnableKYC</code></td>
</tr>
<tr>
<td>底層欄位<br><span class="path">TenantProperty.cs:69-71</span></td>
<td><code>public bool EnableKYC</code>,註解「是否啟用了 KYC 功能」。<b>命名雖是「功能開關」,但賦值來源只綁定「有沒有買 KYC 設備加購」(見下節)。</b></td>
</tr>
</tbody>
</table>
<div class="callout note">
<p><span class="lbl">加購項對應:</span>plans-plus 第 4 步的 KYC 加購項在 <span class="path">plans-plus.html:468</span> 標題為「<b>KYC ID Reader Rental / KYC 身份讀取設備租用</b>」——一台<b>實體讀卡機</b>,按訂閱期租用。這正是產生 <code>Tag1Code = KYC</code> 方案行、進而把 <code>EnableKYC</code> 置 true 的東西。</p>
</div>
</section>
<!-- ───────────────────────────────────────────── -->
<section id="assign">
<h2 class="sec">三、EnableKYC 何時被賦值為 true</h2>
<p><code>EnableKYC</code> 全庫僅有 3 種寫入來源(其餘皆為 EF 遷移快照),<b>沒有任何「按功能開關」的入口</b></p>
<table>
<thead><tr><th style="width:26%">場景</th><th style="width:30%">代碼</th><th>取值</th></tr></thead>
<tbody>
<tr>
<td>新租戶下單</td>
<td><span class="path">OrderService.cs:763</span></td>
<td><code>EnableKYC = checkHasEnableKYCInPlan(purchasePlanDetailList)</code></td>
</tr>
<tr>
<td>續費(新開訂單路徑)</td>
<td><span class="path">OrderService.cs:2633</span></td>
<td><code>EnableKYC = checkHasEnableKYCInPlan(purchasePlanDetailList)</code></td>
</tr>
<tr>
<td>續費(事件隊列生效路徑)</td>
<td><span class="path">OrderService.cs:872</span></td>
<td><code>EnableKYC = lastTenantProperty.EnableKYC</code><b>沿用上一份</b></td>
</tr>
<tr>
<td>Host 宿主初始化</td>
<td><span class="path">TenantPropertyDataSeeder.cs:63-79</span></td>
<td><code>EnableKYC = true</code><b>僅當 <code>currentTenant.Id == null</code>,即 Host</b></td>
</tr>
</tbody>
</table>
<p>判定函式只看方案的 Tag1</p>
<pre><code>// OrderService.cs:2694-2697
private bool checkHasEnableKYCInPlan(List&lt;PlanDetail&gt; purchasePlanDetailList)
{
return purchasePlanDetailList
.Where(a =&gt; a.Plan.Tag1Code == PlanTag1Enums.KYC.ToString()).Any();
}</code></pre>
<div class="callout ok">
<p><span class="lbl">推論:</span><code>EnableKYC</code> 語義上等同「<b>該訂閱是否含 KYC 讀卡設備租用</b>而不是一個獨立的「KYC 功能總開關」。所以把「上次訂閱資訊」裡的它讀作「是否租用 KYC 設備」是準確的。</p>
</div>
</section>
<!-- ───────────────────────────────────────────── -->
<section id="gate">
<h2 class="sec">四、租設備是否影響「使用 KYC 功能」</h2>
<h3>4.1 前端:只擋「機器掃描」按鈕</h3>
<p>AML_Frontend 的 KYC section 讀取當前租戶的 <code>enableKYC</code></p>
<pre><code>// kyc-section.component.ts:147-148
this.amlTenant.getCurrentTenantProperty$().subscribe(res =&gt; {
this.enableKYC = res?.enableKYC ?? false
})</code></pre>
<p>而它<b>只作用在「ID Scan機器讀卡掃描」這一個按鈕</b>上(<code>switchType === 1</code></p>
<table>
<thead><tr><th style="width:20%">按鈕</th><th style="width:16%">switchType</th><th style="width:22%">受 enableKYC 控制?</th><th>說明</th></tr></thead>
<tbody>
<tr><td>ID Scan機器掃描</td><td>1</td><td><span class="pill partial">是(*ngIf="enableKYC"</span></td><td>插實體讀卡機掃描證件;未租設備則隱藏。<span class="path">kyc-section.component.html:10</span></td></tr>
<tr><td>Upload2上傳</td><td>2</td><td><span class="pill done">否 · 始終顯示</span></td><td>上傳證件圖片走 OCR校驗。<span class="path">kyc-section.component.html:18-24</span></td></tr>
<tr><td>Input手動錄入</td><td>3</td><td><span class="pill done">否 · 始終顯示</span></td><td>手動輸入證件資訊。<span class="path">kyc-section.component.html:26-32</span></td></tr>
</tbody>
</table>
<h3>4.2 後端:無任何門檻</h3>
<p>後端 <code>KYCService</code> / <code>KYCController</code> <b>從不讀取 <code>EnableKYC</code></b>。全庫對該欄位的非遷移讀取僅有兩處:續費沿用(<span class="path">OrderService.cs:872</span>)與續費查詢上報(<span class="path">OrderService.cs:2460</span>)。因此它<b>純粹是前端那顆掃描按鈕的顯隱旗標</b>,不構成服務端鑑權。</p>
<div class="callout warn">
<p><span class="lbl">結論:</span>沒租設備的租戶<b>照樣能用完整 KYC</b>(上傳 / 手動錄入 → OCR、校驗、檢索只是看不到「插讀卡機掃證件」這個入口。<br>「租 KYC 設備」= 解鎖前端硬體掃描按鈕,而非解鎖 KYC 功能本身。</p>
</div>
</section>
<!-- ───────────────────────────────────────────── -->
<section id="default">
<h2 class="sec">五、是否每個租戶預設開啟</h2>
<ul class="tight">
<li><b>一般業務租戶:</b><code>EnableKYC</code> 預設 <b>false</b>,僅當下單方案含 KYC 設備加購才為 true續費時沿用上一份的值<span class="path">OrderService.cs:872</span>)。</li>
<li><b>Host 宿主租戶:</b>是唯一被 seeder 預設 <code>true</code> 的(<span class="path">TenantPropertyDataSeeder.cs:63-79</span><code>currentTenant.Id == null</code>),並非每個業務租戶。</li>
<li><b>但「使用 KYC 功能」廣義上仍預設可用:</b>因前端只擋掃描按鈕,所有租戶預設可走「上傳 / 錄入」完成 KYC。</li>
</ul>
<div class="callout note">
<p><span class="lbl">一句話:</span>廣義 KYC上傳錄入對所有租戶預設可用<code>EnableKYC</code>(=是否租了 KYC 讀卡設備)預設關閉,它只額外解鎖前端「機器掃描證件」按鈕。</p>
</div>
</section>
<!-- ───────────────────────────────────────────── -->
<section id="fix">
<h2 class="sec">六、本次文案修正</h2>
<p>原本 plans-plus「上次訂閱資訊」把 KYC 加購項顯示為含糊的「<code>KYC/KYB</code>」(易被誤解成一種核查功能)。已改為與加購項一致的「<b>KYC 設備租用</b>」表述,以準確反映「是否租用 KYC 設備」。<b>後端無改動。</b></p>
<table>
<thead><tr><th style="width:34%">位置</th><th style="width:33%">修改前</th><th style="width:33%">修改後</th></tr></thead>
<tbody>
<tr>
<td><span class="path">plans-plus.js:575</span><br><code>previousAddonText</code>,「已購增值」列)</td>
<td><code>parts.push('KYC/KYB')</code></td>
<td><code>parts.push(tr('ppx_addon_kyc_label'))</code></td>
</tr>
<tr>
<td><span class="path">plans-plus.html:579</span><br>(訂單摘要 <code>#ppSumKyc</code> 靜態回退文字)</td>
<td><code>KYC/KYB Verification</code></td>
<td><code>KYC ID Reader Rental</code></td>
</tr>
</tbody>
</table>
<p class="small">改用的 i18n 鍵 <code>ppx_addon_kyc_label</code> 三語皆已是租用表述繁中「KYC 身份讀取設備租用」/ EN「KYC ID Reader Rental」 日「KYC IDリーダー機器レンタル」故翻譯本體無需改動。訂單摘要 <code>ppx_sum_kyc</code> 的翻譯值原本就已是租用表述,本次僅同步修正其過期的 HTML 靜態回退文字。</p>
<div class="callout ok">
<p><span class="lbl">驗證:</span>本地以 <code>npm run start:local</code> 啟動 <code>justsolutionsWebV2</code> 即可看到「上次訂閱資訊 → 已購增值」與訂單摘要處的 KYC 顯示為「KYC 設備租用」表述。</p>
</div>
</section>
<!-- ───────────────────────────────────────────── -->
<section id="refs">
<h2 class="sec">七、關鍵代碼位置索引</h2>
<table>
<thead><tr><th style="width:30%">角色</th><th>檔案 : 行</th></tr></thead>
<tbody>
<tr><td>底層欄位定義</td><td class="path">AML_Backend/modules/iCON.Abp.AMLPortal/…/Domain/DbEntity/TenantProperty.cs:69-71</td></tr>
<tr><td>賦值:新租戶 / 續費 / 判定函式</td><td class="path">…/AMLPortal.Application/OrderService.cs:763、2633、872、2694-2697</td></tr>
<tr><td>續費查詢上報 KYC</td><td class="path">…/AMLPortal.Application/OrderService.cs:2460</td></tr>
<tr><td>續費查詢 DTO</td><td class="path">…/AMLPortal.Application.Contracts/OrderAppLayer/RenewableTenantByEmailDto.cs:46-54</td></tr>
<tr><td>Host 預設 true</td><td class="path">…/AMLPortal.Domain/Seeders/TenantPropertyDataSeeder.cs:63-79</td></tr>
<tr><td>前端讀 enableKYC / 掃描按鈕門檻</td><td class="path">AML_Frontend/…/kyc-section/kyc-section.component.ts:147-148、.html:10</td></tr>
<tr><td>AML DTO 映射</td><td class="path">…/iCON.Abp.AML.Application/AMLAppAutoMapperProfile.cs:78 · …/TenantConfigLayer/TenantPropertyDto.cs:118</td></tr>
<tr><td>plans-plus 顯示 / 加購項</td><td class="path">justsolutionsWebV2/public/js/plans-plus.js:570-581 · public/plans-plus.html:468、579</td></tr>
</tbody>
</table>
</section>
<hr class="soft">
<p class="small">本文為程式碼閱讀分析結論,隨代碼演進可能變動;引用行號以撰寫時的倉庫狀態為準。</p>
</div>
</body>
</html>