@seasonkoh/webaz 0.1.29 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/bond-refund-blockers.js +50 -0
  2. package/dist/bond-slash.js +100 -0
  3. package/dist/bond-terms.js +31 -0
  4. package/dist/direct-pay-bond-rail-clearance.js +10 -5
  5. package/dist/direct-pay-cancel-refund.js +160 -0
  6. package/dist/direct-pay-create.js +78 -9
  7. package/dist/direct-pay-disclosures.js +17 -11
  8. package/dist/direct-pay-fee-ar.js +2 -2
  9. package/dist/direct-pay-fee-prepay-request.js +80 -0
  10. package/dist/direct-pay-returns.js +104 -0
  11. package/dist/direct-pay-stock.js +9 -0
  12. package/dist/direct-receive-deferral.js +30 -1
  13. package/dist/direct-receive-deposits.js +122 -17
  14. package/dist/free-shipping.js +37 -0
  15. package/dist/fx-rates.js +7 -0
  16. package/dist/layer0-foundation/L0-1-database/schema.js +246 -0
  17. package/dist/layer0-foundation/L0-2-state-machine/engine.js +1 -0
  18. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +73 -0
  19. package/dist/layer1-agent/L1-1-mcp-server/server.js +81 -16
  20. package/dist/layer2-business/L2-6-notifications/notification-engine.js +110 -41
  21. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +133 -31
  22. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +16 -4
  23. package/dist/layer3-trust/L3-1-dispute-engine/mutual-cancel.js +156 -0
  24. package/dist/platform-receive-accounts.js +94 -0
  25. package/dist/pwa/arbitration-read-admin.js +37 -0
  26. package/dist/pwa/arbitrator-lifecycle.js +100 -0
  27. package/dist/pwa/contract-fingerprint.js +15 -0
  28. package/dist/pwa/direct-pay-order-redaction.js +20 -2
  29. package/dist/pwa/human-presence.js +2 -6
  30. package/dist/pwa/public/app-account.js +9 -9
  31. package/dist/pwa/public/app-admin-disputes.js +55 -0
  32. package/dist/pwa/public/app-agent-appeal.js +90 -0
  33. package/dist/pwa/public/app-agent-approvals.js +93 -0
  34. package/dist/pwa/public/app-agent-pair.js +127 -0
  35. package/dist/pwa/public/app-arbitrator-admin.js +87 -0
  36. package/dist/pwa/public/app-arbitrator-entry.js +9 -0
  37. package/dist/pwa/public/app-bond-deferral-ui.js +9 -0
  38. package/dist/pwa/public/app-bond-refund-ui.js +66 -0
  39. package/dist/pwa/public/app-bond-slash-ui.js +74 -0
  40. package/dist/pwa/public/app-bond-terms-ui.js +23 -0
  41. package/dist/pwa/public/app-bond-ui.js +108 -0
  42. package/dist/pwa/public/app-chat-poll.js +6 -6
  43. package/dist/pwa/public/app-contribution-hub.js +23 -0
  44. package/dist/pwa/public/app-direct-pay-accounts.js +1 -1
  45. package/dist/pwa/public/app-direct-pay-buyer.js +1 -1
  46. package/dist/pwa/public/app-direct-pay-cancel-refund.js +72 -0
  47. package/dist/pwa/public/app-direct-pay-copy.js +12 -0
  48. package/dist/pwa/public/app-direct-pay-fee-history.js +39 -0
  49. package/dist/pwa/public/app-direct-pay-fee-ops.js +1 -1
  50. package/dist/pwa/public/app-direct-pay-fee-request.js +81 -0
  51. package/dist/pwa/public/app-direct-pay-fee-requests-admin.js +70 -0
  52. package/dist/pwa/public/app-direct-pay-memo.js +14 -0
  53. package/dist/pwa/public/app-direct-pay-negotiation.js +35 -0
  54. package/dist/pwa/public/app-direct-pay-pay.js +15 -0
  55. package/dist/pwa/public/app-direct-pay-paymodal.js +32 -0
  56. package/dist/pwa/public/app-direct-pay-reconcile.js +19 -0
  57. package/dist/pwa/public/app-direct-pay-returns.js +56 -0
  58. package/dist/pwa/public/app-direct-pay-reveal.js +82 -0
  59. package/dist/pwa/public/app-direct-pay-sales-report.js +70 -0
  60. package/dist/pwa/public/app-direct-pay.js +36 -37
  61. package/dist/pwa/public/app-dispute-close-ui.js +38 -0
  62. package/dist/pwa/public/app-free-shipping-ui.js +29 -0
  63. package/dist/pwa/public/app-gmv-rail-split.js +12 -0
  64. package/dist/pwa/public/app-listing-commerce-ui.js +72 -0
  65. package/dist/pwa/public/app-mutual-cancel.js +54 -0
  66. package/dist/pwa/public/app-notif-templates-orders.js +43 -0
  67. package/dist/pwa/public/app-notif-templates.js +22 -0
  68. package/dist/pwa/public/app-order-accept-ui.js +158 -0
  69. package/dist/pwa/public/app-order-errors.js +50 -0
  70. package/dist/pwa/public/app-order-labels.js +19 -0
  71. package/dist/pwa/public/app-order-rail-filter.js +26 -0
  72. package/dist/pwa/public/app-platform-receive-accounts.js +140 -0
  73. package/dist/pwa/public/app-poll-governor.js +22 -0
  74. package/dist/pwa/public/app-profile.js +4 -4
  75. package/dist/pwa/public/app-purchase-terms-ui.js +68 -0
  76. package/dist/pwa/public/app-sale-regions-ui.js +38 -0
  77. package/dist/pwa/public/app-seller.js +1 -1
  78. package/dist/pwa/public/app-trade-tax-ui.js +33 -0
  79. package/dist/pwa/public/app.js +133 -160
  80. package/dist/pwa/public/i18n.js +675 -5
  81. package/dist/pwa/public/index.html +41 -0
  82. package/dist/pwa/public/openapi.json +709 -11
  83. package/dist/pwa/public/style.css +3 -0
  84. package/dist/pwa/routes/admin-direct-receive-deposits.js +215 -3
  85. package/dist/pwa/routes/admin-protocol-params.js +16 -0
  86. package/dist/pwa/routes/admin-reports.js +31 -5
  87. package/dist/pwa/routes/agent-governance.js +1 -1
  88. package/dist/pwa/routes/agent-grants.js +253 -32
  89. package/dist/pwa/routes/analytics.js +2 -0
  90. package/dist/pwa/routes/arbitrator.js +67 -14
  91. package/dist/pwa/routes/bond-seller.js +162 -0
  92. package/dist/pwa/routes/direct-pay-cancel-refund.js +111 -0
  93. package/dist/pwa/routes/direct-pay-disclosure-acks.js +9 -4
  94. package/dist/pwa/routes/direct-pay-pending-accept.js +222 -0
  95. package/dist/pwa/routes/direct-pay-returns.js +74 -0
  96. package/dist/pwa/routes/direct-pay-timeouts.js +186 -9
  97. package/dist/pwa/routes/disputes-read.js +20 -6
  98. package/dist/pwa/routes/disputes-write.js +91 -33
  99. package/dist/pwa/routes/external-anchors.js +4 -2
  100. package/dist/pwa/routes/fee-prepay-requests.js +66 -0
  101. package/dist/pwa/routes/governance-onboarding.js +46 -8
  102. package/dist/pwa/routes/logistics.js +4 -4
  103. package/dist/pwa/routes/me-data.js +2 -2
  104. package/dist/pwa/routes/mutual-cancel.js +62 -0
  105. package/dist/pwa/routes/orders-action.js +182 -9
  106. package/dist/pwa/routes/orders-create.js +18 -7
  107. package/dist/pwa/routes/orders-read.js +76 -14
  108. package/dist/pwa/routes/platform-receive-accounts.js +111 -0
  109. package/dist/pwa/routes/products-create.js +13 -4
  110. package/dist/pwa/routes/products-update.js +15 -1
  111. package/dist/pwa/routes/profile-identity.js +4 -2
  112. package/dist/pwa/routes/returns.js +39 -8
  113. package/dist/pwa/routes/seller-directpay-report.js +110 -0
  114. package/dist/pwa/routes/seller-quota.js +5 -1
  115. package/dist/pwa/routes/shipping-templates.js +219 -0
  116. package/dist/pwa/routes/snf.js +4 -1
  117. package/dist/pwa/routes/webauthn.js +3 -3
  118. package/dist/pwa/server.js +59 -36
  119. package/dist/runtime/agent-grant-scopes.js +69 -1
  120. package/dist/runtime/webaz-schema-helpers.js +57 -3
  121. package/dist/sale-regions.js +116 -0
  122. package/dist/shipping-templates.js +119 -0
  123. package/dist/trade-tax.js +99 -0
  124. package/dist/trade-terms.js +76 -0
  125. package/dist/version.js +1 -1
  126. package/package.json +59 -2
@@ -0,0 +1,140 @@
1
+ // 平台(WebAZ)收款方式 —— admin(ROOT)管理 UI。卖家申请充值平台服务费时看到这些方式并据此付款。
2
+ // 写操作(新增/编辑/停用/传码)均需 ROOT + 现场真人 Passkey(purpose platform_receive_account_manage,action[+account_id] 绑)。
3
+ // qr 内联 data-uri:前端先卡 png/webp≤64KB,后端 validateQrDataUri 再校验;预览直接 <img src=data-uri>(我方已校验、栅格图,安全)。
4
+ // 面向用户中文走 t(),英文在 i18n.js。双语 parity 由 test-direct-pay-ui.ts 守。
5
+
6
+ window.praErrText = (code, fallback) => ({
7
+ PLATFORM_ACCOUNT_INPUT_INVALID: t('收款方式信息不合法'),
8
+ PLATFORM_ACCOUNT_NOT_FOUND: t('平台收款方式不存在'),
9
+ HUMAN_PRESENCE_REQUIRED: t('需现场真人 Passkey 确认'),
10
+ PASSKEY_REQUIRED: t('需先注册 Passkey'),
11
+ }[code] || fallback || t('操作失败,请重试'))
12
+
13
+ window.renderAdminPlatformReceiveAccounts = function (app) {
14
+ if (!state.user) { renderLogin(); return }
15
+ if (typeof isAdmin === 'function' && !isAdmin()) { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限管理员')}</div>`, 'admin'); return }
16
+ if ((state.user.admin_type || 'root') !== 'root') { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限根管理员')}</div>`, 'admin'); return }
17
+ app.innerHTML = shell(`
18
+ <h1 class="page-title">🏦 ${t('平台收款方式')}</h1>
19
+ <div style="margin-bottom:8px"><button class="btn btn-outline btn-sm" style="width:auto" onclick="navigate('#admin/dp-ops')">${t('返回 Direct Pay 商户运营')}</button></div>
20
+ <div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('这些是 WebAZ 收取【平台服务费预充值】的收款方式。卖家申请充值时会看到 active 的方式并据此线下付款;可维护多个。改动 = 改平台收款流向,需真人 Passkey。')}</div>
21
+ <div id="pra-box">${loading$()}</div>
22
+ `, 'admin')
23
+ window.praHydrate()
24
+ }
25
+
26
+ window.praHydrate = async () => {
27
+ const box = document.getElementById('pra-box'); if (!box) return
28
+ const r = await GET('/admin/platform-receive-accounts')
29
+ if (r.error) { box.innerHTML = alert$('error', window.praErrText(r.error_code, r.error)); return }
30
+ const accounts = r.accounts || []
31
+ box.innerHTML = `
32
+ <div id="pra-msg"></div>
33
+ ${accounts.length ? accounts.map(a => window.praCard(a)).join('') : `<div style="font-size:12px;color:#9ca3af;margin-bottom:10px">${t('尚未添加平台收款方式')}</div>`}
34
+ <details style="margin-top:8px"><summary style="font-size:13px;font-weight:600;color:#2563eb;cursor:pointer">+ ${t('新增平台收款方式')}</summary>
35
+ <div style="padding:8px 2px 2px">${window.praForm('new')}</div></details>`
36
+ }
37
+
38
+ window.praCard = (a) => {
39
+ const inactive = a.status !== 'active'
40
+ return `<div style="border:1px solid ${inactive ? '#e5e7eb' : '#c7d2fe'};background:${inactive ? '#f9fafb' : '#eef2ff'};border-radius:10px;padding:10px 12px;margin-bottom:8px;opacity:${inactive ? '0.6' : '1'}">
41
+ <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px">
42
+ <div style="min-width:0">
43
+ <div style="font-size:13px;font-weight:700;color:#111827">${a.label ? escHtml(a.label) : (a.method ? escHtml(a.method) : t('平台收款方式'))}${inactive ? ` · <span style="color:#9ca3af;font-weight:400">${t('已停用')}</span>` : ''}</div>
44
+ <div style="font-size:11px;color:#6b7280;margin:2px 0 4px">${a.method ? escHtml(a.method) : ''}${a.method && a.currency ? ' · ' : ''}${a.currency ? escHtml(a.currency) : ''}</div>
45
+ <div style="font-size:12px;color:#374151;white-space:pre-wrap;word-break:break-word">${escHtml(a.instruction)}</div>
46
+ </div>
47
+ <div style="flex:0 0 auto;width:72px;height:72px;display:flex;align-items:center;justify-content:center;border:1px dashed #d1d5db;border-radius:8px;font-size:10px;color:#9ca3af;text-align:center">${a.qr_data_uri ? `<img src="${a.qr_data_uri}" alt="${t('收款二维码')}" style="width:72px;height:72px;object-fit:contain;border-radius:6px">` : t('无二维码')}</div>
48
+ </div>
49
+ ${inactive ? '' : `<details style="margin-top:8px"><summary style="font-size:12px;color:#2563eb;cursor:pointer">${t('编辑')}</summary><div style="padding:8px 2px 2px">${window.praForm(a.id, a)}</div></details>
50
+ <button class="btn btn-outline btn-sm" style="margin-top:6px;color:#dc2626;border-color:#dc2626" onclick="praDeactivate('${a.id}')">${t('停用')}</button>`}
51
+ </div>`
52
+ }
53
+
54
+ window.praForm = (id, a) => {
55
+ const p = id === 'new' ? 'new' : id
56
+ return `
57
+ <div class="form-group"><label class="form-label">${t('平台收款明细')} <span style="font-size:11px;color:#9ca3af">${t('(展示给卖家,如 PayNow 账号 / 银行账户 / USDC 地址)')}</span></label>
58
+ <textarea class="form-control" id="pra-instr-${p}" rows="3" maxlength="500" placeholder="${t('例:PayNow UEN 202xxxxx(备注填你的卖家 ID)')}">${a ? escHtml(a.instruction) : ''}</textarea></div>
59
+ <div style="display:flex;gap:8px">
60
+ <div class="form-group" style="flex:1"><label class="form-label">${t('收款方式')}</label><input class="form-control" id="pra-method-${p}" maxlength="40" value="${a && a.method ? escHtml(a.method) : ''}" placeholder="${t('如 PayNow')}"></div>
61
+ <div class="form-group" style="flex:1"><label class="form-label">${t('币种')}</label><input class="form-control" id="pra-currency-${p}" maxlength="8" value="${a && a.currency ? escHtml(a.currency) : ''}" placeholder="${t('如 SGD / USDC')}"></div>
62
+ </div>
63
+ <div class="form-group"><label class="form-label">${t('标签(可选)')}</label><input class="form-control" id="pra-label-${p}" maxlength="40" value="${a && a.label ? escHtml(a.label) : ''}" placeholder="${t('如 PayNow-主')}"></div>
64
+ <div class="form-group"><label class="form-label">${t('收款二维码(可选,PNG/WebP ≤64KB)')}</label>
65
+ <input type="file" accept="image/png,image/webp" id="pra-qr-${p}" style="font-size:12px">
66
+ ${a && a.qr_data_uri ? `<label style="display:block;font-size:12px;color:#b45309;margin-top:4px;cursor:pointer"><input type="checkbox" id="pra-qrremove-${p}"> ${t('移除现有二维码')}</label>` : ''}</div>
67
+ <button class="btn btn-primary btn-sm" onclick="${id === 'new' ? 'praAdd()' : `praUpdate('${id}')`}">${t('保存')}</button>`
68
+ }
69
+
70
+ window.praReadText = (p) => ({
71
+ instruction: document.getElementById(`pra-instr-${p}`)?.value?.trim() || '',
72
+ method: document.getElementById(`pra-method-${p}`)?.value?.trim() || '',
73
+ currency: document.getElementById(`pra-currency-${p}`)?.value?.trim() || '',
74
+ label: document.getElementById(`pra-label-${p}`)?.value?.trim() || '',
75
+ })
76
+
77
+ // 读 QR 输入 → 'keep'(不改) | ''(移除) | data-uri(新图,前端先卡 png/webp≤64KB)。异常抛错。
78
+ window.praReadQr = async (p) => {
79
+ const f = document.getElementById(`pra-qr-${p}`)?.files?.[0]
80
+ if (f) {
81
+ if (f.type !== 'image/png' && f.type !== 'image/webp') throw new Error(t('二维码仅支持 PNG 或 WebP 图片'))
82
+ if (f.size > 64 * 1024) throw new Error(t('二维码图片过大(需 ≤ 64KB)'))
83
+ return await new Promise((res, rej) => { const fr = new FileReader(); fr.onload = () => res(fr.result); fr.onerror = rej; fr.readAsDataURL(f) })
84
+ }
85
+ if (document.getElementById(`pra-qrremove-${p}`)?.checked) return ''
86
+ return 'keep'
87
+ }
88
+
89
+ // sha256 hex(浏览器,secure context)—— 绑 QR 内容进 purpose_data(不塞 64KB 原文)。
90
+ window.praSha256 = async (s) => {
91
+ const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(s))
92
+ return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('')
93
+ }
94
+ // 构造与后端 gateContentPayload 逐字一致的 purpose_data:绑定 action[+account_id]+ 收款内容(instruction/method/currency/label)+ qr_mode(+qr_sha256)。
95
+ window.praContentPayload = async (action, accountId, f, qr) => {
96
+ const p = { action }
97
+ if (accountId) p.account_id = accountId
98
+ if (action === 'add' || action === 'update') {
99
+ p.instruction = f.instruction; p.method = f.method; p.currency = f.currency; p.label = f.label
100
+ if (qr === 'keep') p.qr_mode = 'keep'
101
+ else if (qr === '') p.qr_mode = 'clear'
102
+ else { p.qr_mode = 'set'; p.qr_sha256 = await window.praSha256(qr) }
103
+ }
104
+ return p
105
+ }
106
+ window.praGate = async (purposeData) => {
107
+ try { return await requestPasskeyGate('platform_receive_account_manage', purposeData) }
108
+ catch (e) { if (typeof toast$ === 'function') toast$((e && e.message ? e.message + ' — ' : '') + t('需先注册 Passkey'), 'error'); return null }
109
+ }
110
+
111
+ window.praAdd = async () => {
112
+ const f = window.praReadText('new')
113
+ if (!f.instruction) { if (typeof toast$ === 'function') toast$(t('平台收款明细不能为空'), 'error'); return }
114
+ let qr; try { qr = await window.praReadQr('new') } catch (e) { if (typeof toast$ === 'function') toast$(e.message, 'error'); return }
115
+ const token = await window.praGate(await window.praContentPayload('add', null, f, qr)); if (!token) return
116
+ const body = { ...f, webauthn_token: token }; if (qr !== 'keep') body.qr_data_uri = qr
117
+ const r = await POST('/admin/platform-receive-accounts', body)
118
+ if (r.error) { if (typeof toast$ === 'function') toast$(window.praErrText(r.error_code, r.error), 'error'); return }
119
+ if (typeof toast$ === 'function') toast$(t('已保存'), 'success'); window.praHydrate()
120
+ }
121
+
122
+ window.praUpdate = async (id) => {
123
+ const f = window.praReadText(id)
124
+ if (!f.instruction) { if (typeof toast$ === 'function') toast$(t('平台收款明细不能为空'), 'error'); return }
125
+ let qr; try { qr = await window.praReadQr(id) } catch (e) { if (typeof toast$ === 'function') toast$(e.message, 'error'); return }
126
+ const token = await window.praGate(await window.praContentPayload('update', id, f, qr)); if (!token) return
127
+ const body = { ...f, webauthn_token: token }; if (qr !== 'keep') body.qr_data_uri = qr
128
+ const r = await PUT('/admin/platform-receive-accounts/' + id, body)
129
+ if (r.error) { if (typeof toast$ === 'function') toast$(window.praErrText(r.error_code, r.error), 'error'); return }
130
+ if (typeof toast$ === 'function') toast$(t('已保存'), 'success'); window.praHydrate()
131
+ }
132
+
133
+ window.praDeactivate = async (id) => {
134
+ const go = await confirmModal(t('停用后卖家将不再看到此平台收款方式,确定停用?'), t('停用'), { danger: true })
135
+ if (!go) return
136
+ const token = await window.praGate(await window.praContentPayload('deactivate', id)); if (!token) return
137
+ const r = await api('DELETE', '/admin/platform-receive-accounts/' + id, { webauthn_token: token })
138
+ if (r.error) { if (typeof toast$ === 'function') toast$(window.praErrText(r.error_code, r.error), 'error'); return }
139
+ if (typeof toast$ === 'function') toast$(t('已停用'), 'success'); window.praHydrate()
140
+ }
@@ -0,0 +1,22 @@
1
+ // 轮询节流治理(第一刀,UI-only,零服务端改动)。治"手机挂后台整夜打接口"(Tina 案根因之一):
2
+ // ① 信令按需化:无 P2P 活动时 3s → 60s(间隔仍 3s,gate 每 20 拍放行一次);收到信令/主动发 offer → 恢复 3s 快节奏 2 分钟。
3
+ // ② 页面不可见(锁屏/切后台)→ 所有轮询暂停(信令/心跳/聊天各自查 gate 或 document.hidden);
4
+ // ③ 回到前台 → 立即补拉一次信令+当前会话,并先快 30s 消化积压。
5
+ // fail-open:app.js 侧写成 !window.pollGate || pollGate(...) —— 本文件没加载时一切维持旧 3s 行为。
6
+ ;(function () {
7
+ let fastUntil = 0, tick = 0
8
+ const FAST_MS = 120_000, IDLE_EVERY = 20 // 3s × 20 = 60s 空闲节奏
9
+ window.pollBoost = (ms) => { fastUntil = Date.now() + (ms || FAST_MS) }
10
+ window.pollActivity = (kind, n) => { if (n > 0) window.pollBoost() }
11
+ window.pollGate = (kind) => {
12
+ if (document.hidden) return false // 后台/锁屏:暂停
13
+ tick++
14
+ return Date.now() < fastUntil || tick % IDLE_EVERY === 0
15
+ }
16
+ document.addEventListener('visibilitychange', () => {
17
+ if (document.visibilityState !== 'visible') return
18
+ window.pollBoost(30_000)
19
+ try { if (window._p2pSigTick) window._p2pSigTick() } catch {} // 补拉信令
20
+ try { if (window._chatPollNow) window._chatPollNow() } catch {} // 补拉当前会话
21
+ })
22
+ })()
@@ -1264,7 +1264,7 @@ async function renderSellerMyHome(app) {
1264
1264
  ` : ''
1265
1265
 
1266
1266
  // 顺序:工作中心 → 销售扩展 → 效率工具 → 资金/沟通 → 信任与协议(条件显示)→ 社交发现 → 公益折叠
1267
- app.innerHTML = shell(mySubTabsHTML('dashboard') + header + workGrid + marketGrid + commsGrid + trustGrid + socialGrid + charitySection, 'me')
1267
+ app.innerHTML = shell(mySubTabsHTML('dashboard') + header + workGrid + ((state.canArbitrate && role !== 'arbitrator' && window.arbTaishCard) ? window.arbTaishCard() : '') + marketGrid + commsGrid + trustGrid + socialGrid + charitySection, 'me')
1268
1268
  }
1269
1269
 
1270
1270
  // ─── 买家 #me 专业版(聚焦购物/订单/AI;慈善折叠次要) ────────
@@ -1512,7 +1512,7 @@ async function renderBuyerMyHome(app) {
1512
1512
  `
1513
1513
 
1514
1514
  // 顺序:我的购物 → 我的市场记录 → Agent 进阶 → 通信 → 信任与协议(条件显示)→ 公益折叠 → 账户与配置
1515
- app.innerHTML = shell(mySubTabsHTML('dashboard') + header + notePromptPlaceholder('me') + shopGrid + marketGrid + aiGrid + commsGrid + trustGrid + socialGrid + charitySection + settingsGrid, 'me')
1515
+ app.innerHTML = shell(mySubTabsHTML('dashboard') + header + notePromptPlaceholder('me') + shopGrid + marketGrid + aiGrid + commsGrid + trustGrid + ((state.canArbitrate && !isExternalArb && window.arbTaishCard) ? window.arbTaishCard() : '') + socialGrid + charitySection + settingsGrid, 'me') // isExternalArb 已在"外部仲裁员·已批准"区渲染仲裁台 → 兜底卡不再重复(走查:同页两张仲裁台卡)
1516
1516
  hydrateNotePrompt('me')
1517
1517
  }
1518
1518
 
@@ -1545,8 +1545,8 @@ async function renderMyHome(app, subTab) {
1545
1545
 
1546
1546
  // dashboard: 按角色分支(现有 renderer 内部会注入 mySubTabsHTML)
1547
1547
  app.innerHTML = shell(loading$(), 'me')
1548
- const role = state.user.role
1549
- const TRUSTED_ROLES = ['admin', 'verifier', 'logistics', 'arbitrator']
1548
+ if (!state.canArbitrate) { try { const _as = await GET('/arbitrator/status'); state.canArbitrate = !!(_as && _as.can_arbitrate); state.arbitratorStatus = (_as && _as.arbitrator_status) || 'none' } catch {} } // 现场重查:刚加白名单的仲裁员无需重登即可在 #me 刷出仲裁台入口
1549
+ const role = state.user.role; const TRUSTED_ROLES = ['admin', 'verifier', 'logistics', 'arbitrator']
1550
1550
  if (TRUSTED_ROLES.includes(role)) {
1551
1551
  return renderTrustedMyHome(app, role)
1552
1552
  }
@@ -0,0 +1,68 @@
1
+ // 买家下单前【购买条款聚合 + 收货地区入口】(S5)。UI ONLY,只读聚合 S1-S4 已存字段(GET /products/:id/shipping-options?ship_to_region)。
2
+ // ① 地区入口 _shipRegionInputHtml:运费模板→下拉;无模板但可售区=白名单→按白名单下拉;无模板+排除式/仅平台限制→自由输入(否则 region_required 却无入口=卡单)。
3
+ // ② 条款卡 _ptHydrate:可售裁定 / 运费覆盖·费用·时效 or 需询价 / 满额免邮 / 目的区价内含税 / DDU-DDP / 平台不代收税声明。
4
+ // 不动订单金额、不收税。收货地区变更由地区入口调 _purchaseTermsRefresh 重拉。诚实披露:税费/进口责任均卖家声明。
5
+ ;(function () {
6
+ // ① 收货地区输入(app-order-accept-ui 的地区块委托到此,便于随 shipping-options 字段演进;shipSelectedRegion 读 select/自由输入两态)
7
+ window._shipRegionInputHtml = (o) => {
8
+ const tpl = Array.isArray(o.template) ? o.template : []
9
+ const sr = o.sale_regions // {mode, include?, exclude?} | null
10
+ const listRegions = (!tpl.length && sr && sr.mode === 'list' && Array.isArray(sr.include)) ? sr.include : []
11
+ const label = `<label class="form-label" style="font-size:12px">📍 ${t('收货国家/地区')} *</label>`
12
+ const refresh = 'window._purchaseTermsRefresh && window._purchaseTermsRefresh()'
13
+ const opts = tpl.length
14
+ ? tpl.map(e => `<option value="${escHtml(e.region)}">${e.region === '*' ? t('其他地区(通用运费)') : escHtml(e.region)} · ${t('运费')} ${e.fee}${e.est_days ? ` · ${escHtml(e.est_days)} ${t('天')}` : ''}</option>`).join('')
15
+ : listRegions.map(r => `<option value="${escHtml(r)}">${escHtml(r)}</option>`).join('')
16
+ if (!opts && !o.quote_outside_template) // 无任何可选项且无询价 → 自由输入(排除式可售区 / 仅平台限制 / 配置异常)
17
+ return `${label}<input class="form-control" id="ship-region-other" maxlength="16" placeholder="${t('地区代码,如 US / JP / DE')}" oninput="${refresh}" style="font-size:13px;text-transform:uppercase">
18
+ <div style="font-size:11px;color:#6b7280;margin-top:4px">${t('该商品设有可售地区限制,请填写收货地区以确认可否下单。')}</div>`
19
+ return `${label}
20
+ <select class="form-control" id="ship-region-select" style="font-size:13px" onchange="document.getElementById('ship-region-other').style.display = this.value === '__other' ? 'block' : 'none'; ${refresh}">
21
+ <option value="">${t('请选择')}</option>${opts}
22
+ ${o.quote_outside_template ? `<option value="__other">${t('其他地区(需卖家报价运费,直付)')}</option>` : ''}
23
+ </select>
24
+ <input class="form-control" id="ship-region-other" maxlength="16" placeholder="${t('地区代码,如 US / JP / DE')}" oninput="${refresh}" style="display:none;margin-top:6px;font-size:13px;text-transform:uppercase">
25
+ <div style="font-size:11px;color:#6b7280;margin-top:4px">${o.quote_outside_template ? t('模板内地区运费自动计入总额;其他地区由卖家先报价、你确认后再付款。') : (tpl.length ? t('运费按所选地区自动计入订单总额。') : t('该商品按可售地区下单,运费以卖家约定为准。'))}</div>`
26
+ }
27
+
28
+ // ② 条款聚合卡
29
+ window.purchaseTermsBlockHtml = (productId) => { window._ptPid = productId; setTimeout(() => window._ptHydrate(productId), 70); return `<div id="purchase-terms-block" style="display:none;margin-top:10px"></div>` }
30
+ window._purchaseTermsRefresh = () => window._ptHydrate(window._ptPid, window.shipSelectedRegion ? window.shipSelectedRegion() : undefined)
31
+ window._ptHydrate = async (productId, region) => {
32
+ const box = document.getElementById('purchase-terms-block'); if (!box) return
33
+ const o = await GET(`/products/${productId}/shipping-options${region ? '?ship_to_region=' + encodeURIComponent(region) : ''}`).catch(() => null); if (!o) return
34
+ box.style.display = 'none'
35
+ const rows = []
36
+ // 可售裁定(最优先;配置异常无论是否选区都醒目红条,不诱导买家提交后才 503;其余不可售需选区后才判)
37
+ const sell = o.sellable || { ok: true, reason: 'ok' }
38
+ if (!sell.ok && (region || sell.reason === 'platform_policy_invalid')) {
39
+ const invalid = sell.reason === 'platform_policy_invalid'
40
+ const msg = invalid ? t('平台合规配置异常,该商品暂时无法下单,请稍后再试。')
41
+ : sell.reason === 'product_restricted' ? t('平台合规限制:该商品暂不支持销售到所选地区。')
42
+ : sell.reason === 'region_not_for_sale' ? t('卖家未将所选地区设为可售范围,该地区暂不可下单。')
43
+ : t('该商品设有可售地区限制,请选择收货地区确认。')
44
+ box.innerHTML = `<div class="card" style="border:1px solid #fca5a5;background:#fef2f2;padding:10px 12px;font-size:12px;color:#991b1b;line-height:1.6">🚫 <strong>${invalid ? t('暂时无法下单') : t('该地区不可售')}</strong> — ${msg}</div>`
45
+ box.style.display = 'block'; return
46
+ }
47
+ // 运费裁定(有模板+选了区)
48
+ const rs = o.resolved_shipping
49
+ if (rs) rows.push(rs.covered
50
+ ? `<div style="color:#374151">📦 ${t('运费')}:${rs.fee === 0 ? t('免运费') : rs.fee}${rs.est_days ? ` · ${t('约')} ${escHtml(String(rs.est_days))} ${t('天')}` : ''}</div>`
51
+ : rs.quote_required ? `<div style="color:#92400e">📦 ${t('该地区需卖家先报运费,确认后再付款(直付)。')}</div>`
52
+ : `<div style="color:#991b1b">📦 ${t('卖家暂不配送到该地区。')}</div>`)
53
+ if (o.free_shipping_threshold) rows.push(`<div style="color:#166534">🚚 ${t('满')} ${o.free_shipping_threshold} ${t('免运费')}</div>`)
54
+ // 进口责任 DDU/DDP
55
+ const duty = o.import_duty_terms
56
+ if (duty === 'ddu') rows.push(`<div style="color:#92400e">🛃 <strong>${t('进口关税/税:到境自付(DDU)')}</strong> — ${t('本商品价格不含目的国进口关税/税;跨境到货时可能由承运人向你收取,金额由海关核定,与卖家/平台无关。')}</div>`)
57
+ else if (duty === 'ddp') rows.push(`<div style="color:#166534">🛃 <strong>${t('进口关税/税:卖家已含(DDP)')}</strong> — ${t('卖家声明价格已包含目的国进口关税/税,正常到货应无额外费用。')}</div>`)
58
+ // 价内含税
59
+ const lines = Array.isArray(o.tax_included_lines) ? o.tax_included_lines : []
60
+ for (const l of lines) rows.push(`<div style="color:#374151">🧾 ${t('价内已含')}:${escHtml(l.label)}${l.rate_pct != null ? ' ' + l.rate_pct + '%' : ''}${l.region && l.region !== '*' ? ' (' + escHtml(l.region) + ')' : ''}${l.note ? ' · ' + escHtml(l.note) : ''}</div>`)
61
+ if (rows.length === 0) return // 无任何条款 → 不臆造
62
+ const hasTaxOrDuty = duty || lines.length > 0
63
+ box.innerHTML = `<div class="card" style="border:1px solid #e5e7eb;background:#fafafa;padding:10px 12px;font-size:12px;line-height:1.7">
64
+ <div style="font-weight:600;color:#6b7280;margin-bottom:4px">🌍 ${t('购买条款')}${region ? '(' + t('发往') + ' ' + escHtml(region) + ')' : ''}</div>${rows.join('')}
65
+ ${hasTaxOrDuty ? `<div style="color:#9ca3af;font-size:11px;margin-top:4px">${t('税费与进口责任为卖家自行声明,平台不代收代缴税费(直付非托管)。')}</div>` : ''}</div>`
66
+ box.style.display = 'block'
67
+ }
68
+ })()
@@ -0,0 +1,38 @@
1
+ // 可售区域 UI(跨境 S1)。UI ONLY —— 判定/校验全在后端(gate 在建单,REGION_NOT_FOR_SALE 硬拒不询价)。
2
+ // 注入:包装 shipHydrateSellerSettings / shipSaveSellerSettings(接单与运费卡尾部追加,capped 文件净零)。
3
+ // 语义:与运费模板分层 —— 这里是"卖不卖",运费模板是"多少钱";平台合规名单商家不可放宽。
4
+ ;(function () {
5
+ const _origHydrate = window.shipHydrateSellerSettings
6
+ window.shipHydrateSellerSettings = async () => {
7
+ await _origHydrate()
8
+ const box = document.getElementById('ship-settings-body'); if (!box || document.getElementById('sr-mode')) return
9
+ const s = await GET('/seller/shipping-settings').catch(() => null)
10
+ const rule = (s && s.store_sale_regions) || null
11
+ const codes = rule ? (rule.mode === 'list' ? (rule.include || []) : (rule.exclude || [])) : []
12
+ box.insertAdjacentHTML('beforeend', `
13
+ <div style="border-top:1px dashed #e5e7eb;margin:10px 0;padding-top:10px">
14
+ <label class="form-label" style="font-size:12px">${t('可售区域(店铺默认;与运费无关 —— 这里是"卖不卖",运费模板是"多少钱")')}</label>
15
+ <select class="form-control" id="sr-mode" style="font-size:13px;margin-bottom:6px" onchange="document.getElementById('sr-codes-row').style.display = this.value ? '' : 'none'">
16
+ <option value="" ${!rule ? 'selected' : ''}>${t('不限(全球可下单)')}</option>
17
+ <option value="list" ${rule && rule.mode === 'list' ? 'selected' : ''}>${t('仅允许这些地区')}</option>
18
+ <option value="all" ${rule && rule.mode === 'all' ? 'selected' : ''}>${t('全球可卖,但排除这些地区')}</option>
19
+ </select>
20
+ <div id="sr-codes-row" style="${rule ? '' : 'display:none'}">
21
+ <input class="form-control" id="sr-codes" placeholder="${t('地区代码,空格分隔,如:SG MY CN')}" value="${escHtml(codes.join(' '))}" style="font-size:12px;font-family:monospace;margin-bottom:6px">
22
+ </div>
23
+ <div style="font-size:11px;color:#9ca3af">${t('不可售地区的买家下单会被直接拒绝(不走询价);设置只影响之后的新订单,单品可在接口单独覆盖。')}</div>
24
+ </div>`)
25
+ }
26
+ const _origSave = window.shipSaveSellerSettings
27
+ window.shipSaveSellerSettings = async () => {
28
+ await _origSave()
29
+ const modeEl = document.getElementById('sr-mode'); if (!modeEl) return
30
+ const mode = modeEl.value
31
+ const codes = (document.getElementById('sr-codes')?.value || '').trim().split(/\s+/).filter(Boolean)
32
+ const body = !mode ? { store_sale_regions: null }
33
+ : { store_sale_regions: { mode, ...(mode === 'list' ? { include: codes } : { exclude: codes }) } }
34
+ const r = await POST('/seller/shipping-template', body)
35
+ const msg = document.getElementById('ship-set-msg')
36
+ if (r.error && msg) msg.innerHTML = `<span style="color:#dc2626">${escHtml(r.error)}</span>`
37
+ }
38
+ })()
@@ -80,7 +80,7 @@ async function renderSellerAnalytics(app) {
80
80
  <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;text-align:center">
81
81
  <div>
82
82
  <div style="font-size:18px;font-weight:700;color:#4f46e5">${fmt2(r.orders.gmv)}${delta(Number(r.orders.gmv), Number(r.prev_window?.gmv))}</div>
83
- <div style="font-size:10px;color:#6b7280">GMV (USDC)</div>
83
+ <div style="font-size:10px;color:#6b7280">GMV (USDC)</div>${window.gmvRailSplitHtml ? window.gmvRailSplitHtml(r.orders.gmv_escrow, r.orders.gmv_direct_pay, fmt2) : ''}
84
84
  </div>
85
85
  <div>
86
86
  <div style="font-size:18px;font-weight:700;color:#16a34a">${fmt(r.orders.completed_orders)}${delta(Number(r.orders.completed_orders), Number(r.prev_window?.completed_orders))}</div>
@@ -0,0 +1,33 @@
1
+ // 跨境税费/进口责任【卖家设置】(S3;S5 起买家披露移至 app-purchase-terms-ui.js)。UI ONLY —— seller-declared,平台不算不收代缴。
2
+ // 店铺设置卡追加 DDP/DDU 下拉 + 价内含税行(包装 shipHydrateSellerSettings/Save)。诚实纪律:仅"价内已含"披露,向买家加收税不支持。
3
+ ;(function () {
4
+ // ── 卖家店铺设置(包装既有运费设置卡)──
5
+ const _origHydrate = window.shipHydrateSellerSettings
6
+ window.shipHydrateSellerSettings = async () => {
7
+ await _origHydrate()
8
+ const box = document.getElementById('ship-settings-body'); if (!box || document.getElementById('tt-duty')) return
9
+ const s = await GET('/seller/shipping-settings').catch(() => null)
10
+ const duty = s && s.store_import_duty_terms; const lines = (s && s.store_tax_lines) || []
11
+ const tplText = lines.map(l => `${l.region} ${l.label}${l.rate_pct != null ? ' ' + l.rate_pct : ''}`).join('\n')
12
+ box.insertAdjacentHTML('beforeend', `
13
+ <div style="border-top:1px dashed #e5e7eb;margin:10px 0;padding-top:10px">
14
+ <label class="form-label" style="font-size:12px">${t('跨境进口责任(店铺默认;卖家声明,平台不代收税)')}</label>
15
+ <select class="form-control" id="tt-duty" style="font-size:13px;margin-bottom:8px">
16
+ <option value="" ${!duty ? 'selected' : ''}>${t('不声明(默认)')}</option>
17
+ <option value="ddu" ${duty === 'ddu' ? 'selected' : ''}>${t('DDU — 买家到境自付关税/税')}</option>
18
+ <option value="ddp" ${duty === 'ddp' ? 'selected' : ''}>${t('DDP — 卖家已含关税/税')}</option>
19
+ </select>
20
+ <label class="form-label" style="font-size:12px">${t('价内已含税声明(每行:地区码 税名 [税率%];* 为通用;如 SG GST 9)')}</label>
21
+ <textarea class="form-control" id="tt-tax" rows="2" placeholder="SG GST 9&#10;* VAT" style="font-size:12px;font-family:monospace">${escHtml(tplText)}</textarea>
22
+ <div style="font-size:11px;color:#9ca3af;margin-top:4px">${t('仅"价内已含"披露;向买家加收的税暂不支持(平台不代收)。设置只影响之后的新订单。')}</div>
23
+ </div>`)
24
+ }
25
+ const _origSave = window.shipSaveSellerSettings
26
+ window.shipSaveSellerSettings = async () => {
27
+ await _origSave()
28
+ const dutyEl = document.getElementById('tt-duty'); if (!dutyEl) return
29
+ const lines = (document.getElementById('tt-tax')?.value || '').split('\n').map(l => l.trim()).filter(Boolean).map(l => { const m = l.split(/\s+/); return { region: m[0], label: m[1] || '', ...(m[2] ? { rate_pct: Number(m[2]) } : {}), kind: 'included' } })
30
+ const r = await POST('/seller/shipping-template', { store_import_duty_terms: dutyEl.value || null, store_tax_lines: lines.length ? lines : null })
31
+ const msg = document.getElementById('ship-set-msg'); if (r.error && msg) msg.innerHTML = `<span style="color:#dc2626">${escHtml(r.error)}</span>`
32
+ }
33
+ })()