@seasonkoh/webaz 0.1.29 → 0.1.31
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.
- package/dist/bond-refund-blockers.js +50 -0
- package/dist/bond-slash.js +100 -0
- package/dist/bond-terms.js +31 -0
- package/dist/direct-pay-bond-rail-clearance.js +10 -5
- package/dist/direct-pay-cancel-refund.js +160 -0
- package/dist/direct-pay-create.js +78 -9
- package/dist/direct-pay-disclosures.js +17 -11
- package/dist/direct-pay-fee-ar.js +2 -2
- package/dist/direct-pay-fee-prepay-request.js +80 -0
- package/dist/direct-pay-returns.js +104 -0
- package/dist/direct-pay-stock.js +9 -0
- package/dist/direct-receive-deferral.js +30 -1
- package/dist/direct-receive-deposits.js +122 -17
- package/dist/free-shipping.js +37 -0
- package/dist/fx-rates.js +7 -0
- package/dist/layer0-foundation/L0-1-database/schema.js +246 -0
- package/dist/layer0-foundation/L0-2-state-machine/engine.js +1 -0
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +73 -0
- package/dist/layer1-agent/L1-1-mcp-server/server.js +150 -33
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +110 -41
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +133 -31
- package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +16 -4
- package/dist/layer3-trust/L3-1-dispute-engine/mutual-cancel.js +156 -0
- package/dist/platform-receive-accounts.js +94 -0
- package/dist/pwa/arbitration-read-admin.js +37 -0
- package/dist/pwa/arbitrator-lifecycle.js +100 -0
- package/dist/pwa/contract-fingerprint.js +15 -0
- package/dist/pwa/direct-pay-order-redaction.js +20 -2
- package/dist/pwa/human-presence.js +2 -6
- package/dist/pwa/public/app-account.js +9 -9
- package/dist/pwa/public/app-admin-disputes.js +55 -0
- package/dist/pwa/public/app-agent-appeal.js +90 -0
- package/dist/pwa/public/app-agent-approvals.js +93 -0
- package/dist/pwa/public/app-agent-pair.js +127 -0
- package/dist/pwa/public/app-arbitrator-admin.js +87 -0
- package/dist/pwa/public/app-arbitrator-entry.js +9 -0
- package/dist/pwa/public/app-bond-deferral-ui.js +9 -0
- package/dist/pwa/public/app-bond-refund-ui.js +66 -0
- package/dist/pwa/public/app-bond-slash-ui.js +74 -0
- package/dist/pwa/public/app-bond-terms-ui.js +23 -0
- package/dist/pwa/public/app-bond-ui.js +108 -0
- package/dist/pwa/public/app-chat-poll.js +6 -6
- package/dist/pwa/public/app-contribution-hub.js +23 -0
- package/dist/pwa/public/app-direct-pay-accounts.js +1 -1
- package/dist/pwa/public/app-direct-pay-buyer.js +1 -1
- package/dist/pwa/public/app-direct-pay-cancel-refund.js +72 -0
- package/dist/pwa/public/app-direct-pay-copy.js +12 -0
- package/dist/pwa/public/app-direct-pay-fee-history.js +39 -0
- package/dist/pwa/public/app-direct-pay-fee-ops.js +1 -1
- package/dist/pwa/public/app-direct-pay-fee-request.js +81 -0
- package/dist/pwa/public/app-direct-pay-fee-requests-admin.js +70 -0
- package/dist/pwa/public/app-direct-pay-memo.js +14 -0
- package/dist/pwa/public/app-direct-pay-negotiation.js +35 -0
- package/dist/pwa/public/app-direct-pay-pay.js +15 -0
- package/dist/pwa/public/app-direct-pay-paymodal.js +32 -0
- package/dist/pwa/public/app-direct-pay-reconcile.js +19 -0
- package/dist/pwa/public/app-direct-pay-returns.js +56 -0
- package/dist/pwa/public/app-direct-pay-reveal.js +82 -0
- package/dist/pwa/public/app-direct-pay-sales-report.js +70 -0
- package/dist/pwa/public/app-direct-pay.js +36 -37
- package/dist/pwa/public/app-dispute-close-ui.js +38 -0
- package/dist/pwa/public/app-free-shipping-ui.js +29 -0
- package/dist/pwa/public/app-gmv-rail-split.js +12 -0
- package/dist/pwa/public/app-listing-commerce-ui.js +72 -0
- package/dist/pwa/public/app-mutual-cancel.js +54 -0
- package/dist/pwa/public/app-notif-templates-orders.js +43 -0
- package/dist/pwa/public/app-notif-templates.js +22 -0
- package/dist/pwa/public/app-order-accept-ui.js +158 -0
- package/dist/pwa/public/app-order-errors.js +50 -0
- package/dist/pwa/public/app-order-labels.js +19 -0
- package/dist/pwa/public/app-order-rail-filter.js +26 -0
- package/dist/pwa/public/app-platform-receive-accounts.js +140 -0
- package/dist/pwa/public/app-poll-governor.js +22 -0
- package/dist/pwa/public/app-profile.js +4 -4
- package/dist/pwa/public/app-purchase-terms-ui.js +68 -0
- package/dist/pwa/public/app-sale-regions-ui.js +38 -0
- package/dist/pwa/public/app-seller.js +1 -1
- package/dist/pwa/public/app-trade-tax-ui.js +33 -0
- package/dist/pwa/public/app.js +133 -160
- package/dist/pwa/public/i18n.js +675 -5
- package/dist/pwa/public/index.html +41 -0
- package/dist/pwa/public/openapi.json +719 -11
- package/dist/pwa/public/style.css +3 -0
- package/dist/pwa/routes/admin-direct-receive-deposits.js +215 -3
- package/dist/pwa/routes/admin-protocol-params.js +16 -0
- package/dist/pwa/routes/admin-reports.js +31 -5
- package/dist/pwa/routes/agent-governance.js +1 -1
- package/dist/pwa/routes/agent-grants.js +281 -32
- package/dist/pwa/routes/analytics.js +2 -0
- package/dist/pwa/routes/arbitrator.js +67 -14
- package/dist/pwa/routes/bond-seller.js +162 -0
- package/dist/pwa/routes/direct-pay-cancel-refund.js +111 -0
- package/dist/pwa/routes/direct-pay-disclosure-acks.js +9 -4
- package/dist/pwa/routes/direct-pay-pending-accept.js +222 -0
- package/dist/pwa/routes/direct-pay-returns.js +74 -0
- package/dist/pwa/routes/direct-pay-timeouts.js +186 -9
- package/dist/pwa/routes/disputes-read.js +20 -6
- package/dist/pwa/routes/disputes-write.js +91 -33
- package/dist/pwa/routes/external-anchors.js +4 -2
- package/dist/pwa/routes/fee-prepay-requests.js +66 -0
- package/dist/pwa/routes/governance-onboarding.js +46 -8
- package/dist/pwa/routes/logistics.js +4 -4
- package/dist/pwa/routes/me-data.js +2 -2
- package/dist/pwa/routes/mutual-cancel.js +62 -0
- package/dist/pwa/routes/orders-action.js +182 -9
- package/dist/pwa/routes/orders-create.js +18 -7
- package/dist/pwa/routes/orders-read.js +76 -14
- package/dist/pwa/routes/platform-receive-accounts.js +111 -0
- package/dist/pwa/routes/products-create.js +45 -16
- package/dist/pwa/routes/products-update.js +15 -1
- package/dist/pwa/routes/profile-identity.js +4 -2
- package/dist/pwa/routes/returns.js +39 -8
- package/dist/pwa/routes/seller-directpay-report.js +110 -0
- package/dist/pwa/routes/seller-quota.js +5 -1
- package/dist/pwa/routes/shipping-templates.js +219 -0
- package/dist/pwa/routes/snf.js +4 -1
- package/dist/pwa/routes/webauthn.js +3 -3
- package/dist/pwa/server.js +63 -40
- package/dist/runtime/agent-grant-scopes.js +69 -1
- package/dist/runtime/webaz-schema-helpers.js +57 -3
- package/dist/sale-regions.js +116 -0
- package/dist/shipping-templates.js +119 -0
- package/dist/trade-tax.js +99 -0
- package/dist/trade-terms.js +76 -0
- package/dist/version.js +1 -1
- package/package.json +61 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// 保证金罚没 UI(B3)。UI ONLY —— 口径校验/冷静期/幂等全在后端。人工铁律:提案→冷静期→ROOT+Passkey 执行。
|
|
2
|
+
// 注入:卖家卡 bondSlashNotice(s)(app-bond-ui 净零 hook);admin 队列页追加提案区(包装 bondAdmHydrate)。
|
|
3
|
+
;(function () {
|
|
4
|
+
const S = window._notifSub
|
|
5
|
+
const P = (emoji, titleZh, bodyZh) => (p) => ({ title: emoji + ' ' + t(titleZh), body: S(t(bodyZh), p) })
|
|
6
|
+
Object.assign(window.NOTIF_TEMPLATES, {
|
|
7
|
+
bond_slash_proposed: P('⚠️', '保证金罚没提案(待复核)', '因争议 {dispute} 裁定卖家责任,平台已发起保证金罚没提案。冷静期 {days} 天内如有异议请联系平台并提供依据;冷静期满后将复核执行(全额罚没,进入处罚金专户,平台不获益)。'),
|
|
8
|
+
bond_slash_cancelled: P('✅', '保证金罚没提案已撤销', '此前的罚没提案经复核已撤销,你的保证金不受影响。'),
|
|
9
|
+
bond_slash_executed: P('❌', '保证金已罚没', '依据争议 {dispute} 的卖家责任裁定,你的履约保证金已全额罚没(进入处罚金专户,平台不获益),直付资格已吊销。重新缴纳保证金并通过审核后可再次申请开通。'),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
// 卖家卡:待复核提案警示(冷静期=申诉窗)
|
|
13
|
+
window.bondSlashNotice = (s) => {
|
|
14
|
+
const p = s.pending_slash
|
|
15
|
+
if (!p) return ''
|
|
16
|
+
return `<div style="margin-top:8px;padding:8px;background:#fef2f2;border:1px solid #fecaca;border-radius:8px;font-size:12px;color:#991b1b">
|
|
17
|
+
⚠️ ${t('保证金罚没提案待复核')}:${t('依据争议')} ${escHtml(p.dispute_id)}${p.reason ? ` · ${escHtml(p.reason)}` : ''}<br>
|
|
18
|
+
${t('冷静期至')} ${fmtTime(p.cooling_until)} —— ${t('如有异议请在此期间联系平台并提供依据。')}</div>`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// admin:队列页追加"罚没提案"区(包装既有 hydrate;容器自建,不动 capped 文件)
|
|
22
|
+
const _origHydrate = window.bondAdmHydrate
|
|
23
|
+
window.bondAdmHydrate = async (status) => {
|
|
24
|
+
await _origHydrate(status)
|
|
25
|
+
const box = document.getElementById('bond-adm-box'); if (!box) return
|
|
26
|
+
let sec = document.getElementById('bond-slash-sec')
|
|
27
|
+
if (!sec) { box.insertAdjacentHTML('afterend', `<div id="bond-slash-sec" style="margin-top:16px"></div>`); sec = document.getElementById('bond-slash-sec') }
|
|
28
|
+
const r = await GET('/admin/direct-receive/bond-slash').catch(() => null)
|
|
29
|
+
const items = (r && r.proposals) || []
|
|
30
|
+
sec.innerHTML = `<h2 style="font-size:15px;font-weight:700;margin-bottom:8px">⚖️ ${t('罚没提案')}(${t('人工铁律:提案→冷静期→Passkey 执行,绝不自动')})</h2>
|
|
31
|
+
<div style="font-size:11px;color:#6b7280;margin-bottom:8px">${t('仅「仲裁裁定卖家责的直付争议」可提案(refund_buyer / partial_refund);v1 全额罚没,进处罚金专户(只进不出),平台不获益。对 locked 行用下方表单提案。')}</div>
|
|
32
|
+
<div style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap">
|
|
33
|
+
<input class="form-control" id="bslash-dep" placeholder="${t('deposit_id')}" style="flex:1;min-width:120px;font-size:12px">
|
|
34
|
+
<input class="form-control" id="bslash-dsp" placeholder="${t('依据 dispute_id')}" style="flex:1;min-width:120px;font-size:12px">
|
|
35
|
+
<input class="form-control" id="bslash-note" placeholder="${t('说明(卖家可见,可选)')}" style="flex:2;min-width:160px;font-size:12px">
|
|
36
|
+
<button class="btn btn-outline btn-sm" style="width:auto;font-size:11px" onclick="bondSlashPropose()">${t('发起提案')}</button>
|
|
37
|
+
</div>
|
|
38
|
+
${items.length === 0 ? `<div class="alert alert-info">${t('暂无罚没提案')}</div>` : items.map(p => `
|
|
39
|
+
<div class="card" style="font-size:12px">
|
|
40
|
+
<div style="display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap">
|
|
41
|
+
<div><strong>${escHtml(p.seller_name || p.seller_id)}</strong> · ${t('依据争议')} ${escHtml(p.dispute_id)} · ${t('存款')} ${escHtml(p.deposit_id)}</div>
|
|
42
|
+
<div><strong>${({ proposed: t('待执行(冷静期)'), executed: t('已执行'), cancelled: t('已撤销') })[p.status] || p.status}</strong></div>
|
|
43
|
+
</div>
|
|
44
|
+
<div style="color:#6b7280;margin-top:4px">${p.reason ? escHtml(p.reason) + ' · ' : ''}${t('冷静期至')} ${fmtTime(p.cooling_until)} · ${fmtTime(p.proposed_at)}</div>
|
|
45
|
+
${p.status === 'proposed' ? `<div style="display:flex;gap:8px;margin-top:8px">
|
|
46
|
+
<button class="btn btn-primary btn-sm" style="width:auto;font-size:11px" onclick="bondSlashExecute('${p.id}')">${t('执行罚没(Passkey;冷静期未满会被拒)')}</button>
|
|
47
|
+
<button class="btn btn-outline btn-sm" style="width:auto;font-size:11px" onclick="bondSlashCancel('${p.id}')">${t('撤销提案')}</button>
|
|
48
|
+
</div>` : ''}
|
|
49
|
+
</div>`).join('')}`
|
|
50
|
+
}
|
|
51
|
+
window.bondSlashPropose = async () => {
|
|
52
|
+
const dep = (document.getElementById('bslash-dep')?.value || '').trim()
|
|
53
|
+
const dsp = (document.getElementById('bslash-dsp')?.value || '').trim()
|
|
54
|
+
if (!dep || !dsp) return void toast$(t('须提供 deposit_id 与依据 dispute_id'), 'error')
|
|
55
|
+
const r = await POST('/admin/direct-receive/bond-slash/propose', { deposit_id: dep, dispute_id: dsp, reason: (document.getElementById('bslash-note')?.value || '').trim() || undefined })
|
|
56
|
+
if (r.error) return void toast$(r.error, 'error')
|
|
57
|
+
toast$(t('提案已发起,卖家已通知(冷静期=申诉窗)'), 'success'); window.bondAdmHydrate()
|
|
58
|
+
}
|
|
59
|
+
window.bondSlashExecute = async (id) => {
|
|
60
|
+
if (typeof confirmModal === 'function' && !(await confirmModal(t('确认执行罚没?保证金将全额进入处罚金专户(不可逆),卖家直付资格吊销。'), t('执行罚没'), { danger: true }))) return
|
|
61
|
+
let token
|
|
62
|
+
try { token = await requestPasskeyGate('direct_pay_bond_slash', { proposal_id: id }) }
|
|
63
|
+
catch (e) { if (window.dpPromptRegisterPasskey) await window.dpPromptRegisterPasskey(e); return }
|
|
64
|
+
const r = await POST(`/admin/direct-receive/bond-slash/${id}/execute`, { webauthn_token: token })
|
|
65
|
+
if (r.error) return void toast$(r.error, 'error')
|
|
66
|
+
toast$(t('罚没已执行,卖家已通知'), 'success'); window.bondAdmHydrate()
|
|
67
|
+
}
|
|
68
|
+
window.bondSlashCancel = async (id) => {
|
|
69
|
+
const note = prompt(t('撤销说明(卖家可见,可空)'), '') ?? ''
|
|
70
|
+
const r = await POST(`/admin/direct-receive/bond-slash/${id}/cancel`, { note })
|
|
71
|
+
if (r.error) return void toast$(r.error, 'error')
|
|
72
|
+
toast$(t('提案已撤销'), 'success'); window.bondAdmHydrate()
|
|
73
|
+
}
|
|
74
|
+
})()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// 保证金条款 + 多币种账户选择 UI(Lock B 放行配套)。UI ONLY —— 条款版本/账户校验在后端(428 TERMS_NOT_AGREED)。
|
|
2
|
+
;(function () {
|
|
3
|
+
// 账户单选(多币种;admin 在 #admin/platform-receive 维护)。选中项凭据/币种由后端按账户推导。
|
|
4
|
+
window.bondAccountSelector = (s) => {
|
|
5
|
+
const accs = s.payment_accounts || []
|
|
6
|
+
if (!accs.length) return t('暂无收款方式,请联系平台')
|
|
7
|
+
return accs.map((a, i) => `<label style="display:flex;align-items:flex-start;gap:8px;margin-bottom:6px;cursor:pointer">
|
|
8
|
+
<input type="radio" name="bond-acc" value="${escHtml(a.id)}" ${i === 0 ? 'checked' : ''} style="margin-top:3px">
|
|
9
|
+
<span style="font-size:12px"><strong>${escHtml(a.label || a.method || '')}</strong> · ${escHtml(a.currency || '')}<br><span style="color:#6b7280">${escHtml(a.instruction || '')}</span></span>
|
|
10
|
+
</label>`).join('')
|
|
11
|
+
}
|
|
12
|
+
// 条款(折叠展示全文)+ 强制勾选;版本存全局供提交带上(后端精确匹配)
|
|
13
|
+
window.bondTermsBlock = (s) => {
|
|
14
|
+
if (!s.terms) return ''
|
|
15
|
+
window._bondTermsVersion = s.terms.version
|
|
16
|
+
const text = (window._lang === 'en' ? s.terms.en : s.terms.zh) || s.terms.zh
|
|
17
|
+
return `<details style="margin-bottom:8px"><summary style="cursor:pointer;font-size:12px;font-weight:600">📜 ${t('保证金条款')}(${escHtml(s.terms.version)})</summary>
|
|
18
|
+
<div style="font-size:11px;color:#4b5563;white-space:pre-wrap;line-height:1.7;padding:8px;background:#f9fafb;border-radius:8px;margin-top:6px">${escHtml(text)}</div></details>
|
|
19
|
+
<label style="display:flex;align-items:center;gap:8px;font-size:12px;color:#374151;margin-bottom:8px;cursor:pointer">
|
|
20
|
+
<input type="checkbox" id="bond-terms-agree" style="width:16px;height:16px">${t('我已阅读并同意当前版本保证金条款(缴纳前必须同意)')}
|
|
21
|
+
</label>`
|
|
22
|
+
}
|
|
23
|
+
})()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// 商家履约保证金 UI(B1 缴纳闭环)。UI ONLY —— 双锁/凭据/幂等全在后端。
|
|
2
|
+
// ① 通知模板(申报/确认/驳回);② 卖家 settings 状态卡(要求额度/状态/缓交/申报表单——通道未放行时隐藏表单,诚实提示);
|
|
3
|
+
// ③ admin 保证金申报队列页(#admin/bond-deposits:核对到账→确认[ROOT+Passkey,当前被 Lock B 挡属预期]/驳回)。
|
|
4
|
+
;(function () {
|
|
5
|
+
const S = window._notifSub
|
|
6
|
+
const P = (emoji, titleZh, bodyZh) => (p) => ({ title: emoji + ' ' + t(titleZh), body: S(t(bodyZh), p) })
|
|
7
|
+
Object.assign(window.NOTIF_TEMPLATES, {
|
|
8
|
+
bond_deposit_submitted: P('🏦', '新保证金缴纳申报待核实', '卖家 {seller} 申报已缴纳履约保证金(T0,凭据 {evidence})。请核对真实到账后在 admin 后台确认(ROOT+Passkey)。'),
|
|
9
|
+
bond_deposit_confirmed: P('✅', '履约保证金已确认锁定', '你的保证金已核实到账并正式锁定,直付入场的保证金门已满足。退出时可申请退还(须无未了结直付责任)。'),
|
|
10
|
+
bond_deposit_rejected: P('❌', '保证金申报未通过核实', '你的保证金缴纳申报未通过核实{note}。请核对付款凭据后重新提交,或联系平台。'),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const ST = () => ({ pending: t('待运营核实'), confirmed: t('已核实待锁定'), locked: t('已锁定生效'), insufficient: t('金额不足待补缴'), expired: t('已失效/被驳回'), refunding: t('退还中'), refunded: t('已退还'), slashed: t('已被罚没') })
|
|
14
|
+
|
|
15
|
+
// ② 卖家 settings 状态卡(settings 链尾注入 + hydrate)
|
|
16
|
+
window.bondSellerSection = () => `
|
|
17
|
+
<div class="card" id="bond-card">
|
|
18
|
+
<div style="font-size:14px;font-weight:700;margin-bottom:6px">🏦 ${t('直付履约保证金')}</div>
|
|
19
|
+
<div id="bond-body" style="font-size:12px;color:#6b7280">${loading$()}</div>
|
|
20
|
+
</div>`
|
|
21
|
+
window.bondHydrateSeller = async () => {
|
|
22
|
+
const box = document.getElementById('bond-body'); if (!box) return
|
|
23
|
+
const s = await GET('/direct-receive/bond-status').catch(() => null)
|
|
24
|
+
if (!s) { box.textContent = t('加载失败,请刷新'); return }
|
|
25
|
+
const d = s.deposit
|
|
26
|
+
const statusLine = d
|
|
27
|
+
? `<div style="margin-bottom:6px">${t('当前状态')}:<strong>${ST()[d.status] || d.status}</strong>${d.status === 'pending' ? ` · ${t('凭据')} ${escHtml(d.evidence_ref || '-')}` : ''}${d.reject_note ? `<br><span style="color:#dc2626">${t('驳回说明')}:${escHtml(d.reject_note)}</span>` : ''}</div>`
|
|
28
|
+
: `<div style="margin-bottom:6px">${t('当前状态')}:<strong>${t('未缴纳')}</strong></div>`
|
|
29
|
+
const deferralLine = s.deferral
|
|
30
|
+
? `<div style="margin-bottom:6px;color:#92400e">🕓 ${t('缓交生效中')}${s.deferral.expires_at ? ` · ${t('到期')} ${fmtTime(s.deferral.expires_at)}` : ''}(${t('额度受限;缴清保证金后转正式')})</div>` : ''
|
|
31
|
+
const payBlock = s.rail_cleared
|
|
32
|
+
? `<div style="margin:8px 0;padding:8px;background:#f9fafb;border-radius:8px">
|
|
33
|
+
<div style="font-weight:600;margin-bottom:4px">${t('缴纳方式')}(${t('转账后提交凭据申报')}):</div>
|
|
34
|
+
${window.bondAccountSelector ? window.bondAccountSelector(s) : (s.payment_accounts || []).map(a => `<div style="margin-bottom:4px">· ${escHtml(a.method || '')} ${escHtml(a.currency || '')}:${escHtml(a.instruction || '')}</div>`).join('') || t('暂无收款方式,请联系平台')}
|
|
35
|
+
</div>
|
|
36
|
+
${(!d || ['expired', 'refunded'].includes(d.status)) ? `${window.bondTermsBlock ? window.bondTermsBlock(s) : ''}
|
|
37
|
+
<input class="form-control" id="bond-evidence" maxlength="120" placeholder="${t('付款凭据号(转账单号/链上 tx,必填)')}" style="margin-bottom:8px;font-size:12px">
|
|
38
|
+
<button class="btn btn-primary btn-sm" style="width:auto" onclick="bondSubmitDeposit()">${t('提交缴纳申报')}</button>` : ''}
|
|
39
|
+
${d && d.status === 'pending' ? `<button class="btn btn-outline btn-sm" style="width:auto" onclick="bondCancelDeposit('${d.id}')">${t('撤回申报')}</button>` : ''}`
|
|
40
|
+
: `<div style="margin:8px 0;padding:8px;background:#fffbeb;border:1px solid #fde68a;border-radius:8px;color:#92400e">${escHtml(s.note || '')}</div>`
|
|
41
|
+
box.innerHTML = `
|
|
42
|
+
<div style="margin-bottom:6px">${t('要求额度')}:<strong>${s.required.display} ${s.required.currency}</strong>(${s.required.tier})</div>
|
|
43
|
+
${statusLine}${deferralLine}${payBlock}${window.bondRefundBlock ? window.bondRefundBlock(s) : ''}${window.bondSlashNotice ? window.bondSlashNotice(s) : ''}
|
|
44
|
+
<div id="bond-msg" style="margin-top:6px;font-size:11px"></div>`
|
|
45
|
+
}
|
|
46
|
+
window.bondSubmitDeposit = async () => {
|
|
47
|
+
const msg = document.getElementById('bond-msg')
|
|
48
|
+
const evidence = (document.getElementById('bond-evidence')?.value || '').trim()
|
|
49
|
+
if (!evidence) { msg.innerHTML = `<span style="color:#dc2626">${t('付款凭据号必填')}</span>`; return }
|
|
50
|
+
const r = await POST('/direct-receive/bond-deposit', { evidence_ref: evidence, platform_account_id: (document.querySelector('input[name="bond-acc"]:checked')?.value || ''), agree_terms_version: (document.getElementById('bond-terms-agree')?.checked ? (window._bondTermsVersion || '') : '') })
|
|
51
|
+
if (r.error) { msg.innerHTML = `<span style="color:#dc2626">${r.error}</span>`; return }
|
|
52
|
+
toast$(t('申报已提交,等待运营核实'), 'success'); window.bondHydrateSeller()
|
|
53
|
+
}
|
|
54
|
+
window.bondCancelDeposit = async (id) => {
|
|
55
|
+
if (typeof confirmModal === 'function' && !(await confirmModal(t('确认撤回保证金缴纳申报?'), t('撤回申报'), { danger: true }))) return
|
|
56
|
+
const r = await POST(`/direct-receive/bond-deposit/${id}/cancel`, {})
|
|
57
|
+
if (r.error) return void toast$(r.error, 'error')
|
|
58
|
+
window.bondHydrateSeller()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ③ admin 申报队列页(#admin/bond-deposits;确认走既有 confirm-production 端点 —— Lock B 未放行前 409 属预期,页面明示)
|
|
62
|
+
window.renderAdminBondDeposits = function (app) {
|
|
63
|
+
if (!state.user) { renderLogin(); return }
|
|
64
|
+
if (typeof isAdmin === 'function' && !isAdmin()) { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限管理员')}</div>`, 'admin'); return }
|
|
65
|
+
if ((state.user.admin_type || 'root') !== 'root') { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限根管理员')}</div>`, 'admin'); return }
|
|
66
|
+
app.innerHTML = shell(`
|
|
67
|
+
<h1 class="page-title">🏦 ${t('保证金缴纳申报')}</h1>
|
|
68
|
+
<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>
|
|
69
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('卖家的履约保证金缴纳申报。请【核对凭据对应的真实到账】后确认(ROOT+Passkey,双锁:生产放行 registry 未开时确认会被拒,属预期 fail-closed);核不上则驳回(留说明)。')}</div>
|
|
70
|
+
<div id="bond-adm-box">${loading$()}</div>
|
|
71
|
+
`, 'admin')
|
|
72
|
+
window.bondAdmHydrate()
|
|
73
|
+
}
|
|
74
|
+
window.bondAdmHydrate = async (status) => {
|
|
75
|
+
const box = document.getElementById('bond-adm-box'); if (!box) return
|
|
76
|
+
const r = await GET(`/admin/direct-receive/deposits${status ? `?status=${status}` : ''}`).catch(() => null)
|
|
77
|
+
if (!r || !Array.isArray(r.deposits)) { box.textContent = t('加载失败,请刷新'); return }
|
|
78
|
+
if (r.deposits.length === 0) { box.innerHTML = `<div class="alert alert-info">${t('暂无申报')}</div>`; return }
|
|
79
|
+
box.innerHTML = r.deposits.map(d => `
|
|
80
|
+
<div class="card" style="font-size:12px">
|
|
81
|
+
<div style="display:flex;justify-content:space-between;gap:8px;flex-wrap:wrap">
|
|
82
|
+
<div><strong>${escHtml(d.seller_name || d.user_id)}</strong> ${d.seller_handle ? '@' + escHtml(d.seller_handle) : ''} · ${d.tier} · ${t('要求')} ${d.required_amount} ${String(d.currency).toUpperCase()}</div>
|
|
83
|
+
<div><strong>${ST()[d.status] || d.status}</strong></div>
|
|
84
|
+
</div>
|
|
85
|
+
<div style="color:#6b7280;margin-top:4px">${t('申报单')} ${d.id} · ${t('凭据')}:${escHtml(d.external_ref || '-')} · ${fmtTime(d.created_at)}${d.reject_note ? ` · <span style="color:#dc2626">${escHtml(d.reject_note)}</span>` : ''}</div>${window.bondAdmRefundActions ? window.bondAdmRefundActions(d) : ''}
|
|
86
|
+
${d.status === 'pending' ? `
|
|
87
|
+
<div style="display:flex;gap:8px;margin-top:8px;flex-wrap:wrap">
|
|
88
|
+
<button class="btn btn-primary btn-sm" style="width:auto;font-size:11px" onclick="bondAdmConfirm('${d.id}', ${Math.round(Number(d.required_amount) * 1e6)}, '${escHtml(d.external_ref || '')}')">${t('已核实到账,确认锁定(Passkey)')}</button>
|
|
89
|
+
<button class="btn btn-outline btn-sm" style="width:auto;font-size:11px;color:#dc2626;border-color:#fecaca" onclick="bondAdmReject('${d.id}')">${t('核不上,驳回')}</button>
|
|
90
|
+
</div>` : ''}
|
|
91
|
+
</div>`).join('')
|
|
92
|
+
}
|
|
93
|
+
window.bondAdmConfirm = async (id, amountUnits, receiptRef) => {
|
|
94
|
+
const jurisdiction = prompt(t('法域代码(须在放行白名单内,如 SG)'), 'SG'); if (!jurisdiction) return
|
|
95
|
+
let token
|
|
96
|
+
try { token = await requestPasskeyGate('direct_receive_production_confirm', { deposit_id: id, rail_id: 'operator_attested', amount_units: amountUnits, receipt_ref: receiptRef, jurisdiction }) }
|
|
97
|
+
catch (e) { if (window.dpPromptRegisterPasskey) await window.dpPromptRegisterPasskey(e); return }
|
|
98
|
+
const r = await POST(`/admin/direct-receive/deposits/${id}/confirm-production`, { rail_id: 'operator_attested', expected_amount_units: amountUnits, receipt_ref: receiptRef, jurisdiction, webauthn_token: token })
|
|
99
|
+
if (r.error) return void toast$(r.error_code === 'PRODUCTION_RAIL_NOT_CLEARED' ? t('生产放行 registry 未开(法务清门后翻转)—— 确认被拒属预期 fail-closed') : r.error, 'error')
|
|
100
|
+
toast$(t('已确认锁定,卖家保证金门已满足'), 'success'); window.bondAdmHydrate()
|
|
101
|
+
}
|
|
102
|
+
window.bondAdmReject = async (id) => {
|
|
103
|
+
const note = prompt(t('驳回说明(卖家可见,可空)'), '') ?? ''
|
|
104
|
+
const r = await POST(`/admin/direct-receive/deposits/${id}/reject`, { note })
|
|
105
|
+
if (r.error) return void toast$(r.error, 'error')
|
|
106
|
+
toast$(t('已驳回'), 'success'); window.bondAdmHydrate()
|
|
107
|
+
}
|
|
108
|
+
})()
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
// 聊天详情实时轮询。renderChatDetail 只在进入/发消息时拉取消息(无实时推送),对方发来的消息原先停留会话时
|
|
2
2
|
// 看不到(只有通知那条独立轮询能提示)。这里每几秒拉一次会话,就地刷新 #chat-msgs:保持"已在底部则贴底"、
|
|
3
3
|
// 不碰输入框/快捷面板、查看时把新到消息标记已读。自清理:#chat-msgs 离开 DOM 或路由已不是本会话时立即停;
|
|
4
|
-
// 单计时器(window._chatPollTimer),重新进入任一会话先清旧的(不堆叠)。逻辑放独立文件因 app.js 已到 LOC
|
|
4
|
+
// 单计时器(window._chatPollTimer),重新进入任一会话先清旧的(不堆叠)。逻辑放独立文件因 app.js 已到 LOC 上限。后台/锁屏暂停,回前台由 app-poll-governor 经 _chatPollNow 补拉。
|
|
5
5
|
// ⚠️ 用【最后一条消息 id】判变化,不能用消息条数——后端默认只返回最近 50 条(滑动窗口),长会话里新消息进来后
|
|
6
6
|
// 条数仍是 50,用 length 比较会永远判"无变化"而不刷新/不标已读(长会话恰是最需要 live update 的场景)。
|
|
7
7
|
window.startChatPoll = (id, initialMsgs) => {
|
|
8
8
|
if (window._chatPollTimer) { clearInterval(window._chatPollTimer); window._chatPollTimer = null }
|
|
9
9
|
const lastOf = (arr) => (arr && arr.length) ? String(arr[arr.length - 1].id || arr[arr.length - 1].created_at || '') : ''
|
|
10
|
-
let last = lastOf(initialMsgs)
|
|
11
|
-
const
|
|
12
|
-
window._chatPollTimer = setInterval(async () => {
|
|
10
|
+
let last = lastOf(initialMsgs); const stop = () => { if (window._chatPollTimer) { clearInterval(window._chatPollTimer); window._chatPollTimer = null; window._chatPollNow = null } }
|
|
11
|
+
const tickFn = async () => {
|
|
13
12
|
const box = document.getElementById('chat-msgs')
|
|
14
13
|
if (!box || !location.hash.includes('chat/' + id)) return stop() // 已离开本会话 → 自清理
|
|
15
|
-
let rr = null
|
|
14
|
+
if (document.hidden) return; let rr = null // 后台/锁屏:暂停(定时器保留,回前台恢复)
|
|
16
15
|
try { rr = await GET('/conversations/' + encodeURIComponent(id)) } catch { return }
|
|
17
16
|
if (!rr || rr.error) return
|
|
18
17
|
const msgs = rr.messages || []
|
|
@@ -25,5 +24,6 @@ window.startChatPoll = (id, initialMsgs) => {
|
|
|
25
24
|
: msgs.map(m => window.renderChatBubble(m, state.user.id)).join('')
|
|
26
25
|
if (atBottom) box.scrollTop = box.scrollHeight // 原本贴底就继续贴底,否则不打扰阅读位置
|
|
27
26
|
POST('/conversations/' + encodeURIComponent(id) + '/read').catch(() => {}) // fire-and-forget:用 .catch 而非 try/catch(POST 返回 Promise,异步 reject 包不住)
|
|
28
|
-
}
|
|
27
|
+
}
|
|
28
|
+
window._chatPollTimer = setInterval(tickFn, 4000); window._chatPollNow = tickFn
|
|
29
29
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// 共建运营 admin hub —— 把散落在主 admin 面板的「共建(社区建设)」功能归到一处。UI ONLY,只链接既有路由,
|
|
2
|
+
// 不改任何后端/权限(每个子页自身的鉴权不变)。镜像 Direct Pay 商户运营 hub 的结构。
|
|
3
|
+
// 分区:共建建议收件箱(全 admin) / 建任务治理(root) / 贡献账号归属(自助 + root 审批)。中文 t(),英文 i18n.js _EN。
|
|
4
|
+
|
|
5
|
+
window.renderAdminContributionHub = function (app) {
|
|
6
|
+
if (!state.user) { renderLogin(); return }
|
|
7
|
+
if (!isAdmin()) { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限管理员')}</div>`, 'admin'); return }
|
|
8
|
+
const root = (state.user.admin_type || 'root') === 'root'
|
|
9
|
+
const grp = (title, cards) => `<div style="font-size:13px;font-weight:700;color:#374151;margin:14px 0 6px">${title}</div>${cards}`
|
|
10
|
+
app.innerHTML = shell(`
|
|
11
|
+
<h1 class="page-title">🌱 ${t('共建运营')}</h1>
|
|
12
|
+
<div style="margin-bottom:8px"><button class="btn btn-outline btn-sm" style="width:auto" onclick="navigate('#admin/protocol')">${t('返回协议管理')}</button></div>
|
|
13
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:6px">${t('共建(社区建设)集中入口:外部建议收件箱、建任务治理、贡献账号归属。')}</div>
|
|
14
|
+
${grp(t('共建建议(收件箱)'),
|
|
15
|
+
adminLinkCard('🛠️', t('任务建议收件箱'), t('陌生人 / agent 提交的共建任务建议;审阅 → 转正式任务'), '#admin/task-proposals') +
|
|
16
|
+
adminLinkCard('📨', t('Welcome 提交'), t('#welcome 留下的邮箱订阅 + 建议'), '#admin/public-ideas'))}
|
|
17
|
+
${root ? grp(t('建任务治理'),
|
|
18
|
+
adminLinkCard('🎟️', t('建任务额度审核'), t('非根管理员的建任务扩容申请;批准 = 限时计数授权(仅 root)'), '#admin/quota-requests')) : ''}
|
|
19
|
+
${grp(t('贡献账号归属'),
|
|
20
|
+
adminLinkCard('🔗', t('关联个人贡献账号'), t('把本管理席位的协调贡献归属到你的真实个人账号(需对方确认 + root 审批)'), '#me/operator-claims') +
|
|
21
|
+
(root ? adminLinkCard('🪪', t('操作席位关联审批'), t('管理席位→个人贡献账号的关联申请;确认 + 审批 / 撤销(仅 root)'), '#admin/operator-claims') : ''))}
|
|
22
|
+
`, 'admin')
|
|
23
|
+
}
|
|
@@ -82,7 +82,7 @@ window.draReadForm = (p) => ({
|
|
|
82
82
|
// 现场真人 Passkey token;失败(无 Passkey / 取消 / 不支持)→ 提示注册入口并返回 null。
|
|
83
83
|
window.draGate = async (action, accountId) => {
|
|
84
84
|
try { return await requestPasskeyGate('direct_receive_account_manage', accountId ? { action, account_id: accountId } : { action }) }
|
|
85
|
-
catch (e) { if (window.dpPromptRegisterPasskey) await window.dpPromptRegisterPasskey(e
|
|
85
|
+
catch (e) { if (window.dpPromptRegisterPasskey) await window.dpPromptRegisterPasskey(e); return null }
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
window.draAddAccount = async () => {
|
|
@@ -64,7 +64,7 @@ window.dpLoadOrderQr = async (orderId) => {
|
|
|
64
64
|
const box = document.getElementById('dp-order-qr')
|
|
65
65
|
if (!box) return
|
|
66
66
|
try {
|
|
67
|
-
const resp = await fetch('/api/orders/' + encodeURIComponent(orderId) + '/direct-pay-qr', { headers: { Authorization: 'Bearer ' + (window.state && window.state.apiKey) } })
|
|
67
|
+
const resp = await fetch('/api/orders/' + encodeURIComponent(orderId) + '/direct-pay-qr', { headers: { Authorization: 'Bearer ' + (window.state && window.state.apiKey) } }); if (!box.isConnected) return // 切走了(box 离屏)→ 绝不把本单 QR 写进别单面板(纵深防御:box 在 await 前捕获,离屏写入本不可见,此处显式封边)
|
|
68
68
|
if (!resp.ok) { box.innerHTML = ''; return } // 无 QR / 未 ack:静默不显(instruction 文本已单独展示)
|
|
69
69
|
const url = URL.createObjectURL(await resp.blob())
|
|
70
70
|
box.innerHTML = `<div style="font-size:11px;color:#9ca3af;margin:6px 0 2px">${t('收款二维码')}</div><img src="${url}" alt="${t('收款二维码')}" style="width:150px;height:150px;object-fit:contain;border:1px solid #e5e7eb;border-radius:8px">`
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// 直付取消退款握手前端(审计项 C)—— 付款后(accepted)·发货前:买家申请取消 → 卖家场外退款并声明 →
|
|
2
|
+
// 买家确认收到(Passkey)→ 系统无责关单。UI ONLY(真正边界在后端 direct-pay-cancel-refund 路由 + 域模块)。
|
|
3
|
+
// 状态来自订单详情 DTO 的 order.cancel_refund(仅 direct_p2p+accepted 计算),同步渲染。中文 t(),英文 i18n.js _EN。
|
|
4
|
+
|
|
5
|
+
window.dpCancelRefundCard = (order, isBuyer, isSeller) => {
|
|
6
|
+
if (!order || order.payment_rail !== 'direct_p2p' || order.status !== 'accepted' || !(isBuyer || isSeller)) return ''
|
|
7
|
+
const cr = order.cancel_refund
|
|
8
|
+
if (!cr) return ''
|
|
9
|
+
const oid = order.id
|
|
10
|
+
const req = cr.request
|
|
11
|
+
const eff = req && req.status
|
|
12
|
+
const head = `<div style="font-size:13px;font-weight:700;color:#92400e;margin-bottom:4px">↩️ ${t('取消订单并退款(直付)')}</div>
|
|
13
|
+
<div style="font-size:12px;color:#4b5563;line-height:1.7;margin-bottom:8px">${t('直付非托管:平台不持货款,退款由卖家在协议外完成,双方在此握手确认,订单无责取消。')}</div>`
|
|
14
|
+
let body = ''
|
|
15
|
+
if (isBuyer && cr.can_confirm) {
|
|
16
|
+
body = `<div style="font-size:12px;color:#374151;margin-bottom:8px">💸 ${t('卖家已声明退款')}${req && req.refund_reference ? '「' + escHtml(req.refund_reference) + '」' : ''}。${t('请先核实退款已到账;未收到请勿确认,可发起争议。')}</div>
|
|
17
|
+
<button class="btn btn-primary btn-sm" style="width:auto" onclick="dpCrConfirm('${oid}')">${t('已收到退款,确认取消订单(需 Passkey)')}</button>`
|
|
18
|
+
} else if (isBuyer && eff === 'requested') {
|
|
19
|
+
body = `<div style="font-size:12px;color:#6b7280;margin-bottom:8px">⏳ ${t('已向卖家发出取消退款请求,等待响应(超期可重新申请或发起争议)。')}</div>
|
|
20
|
+
<button class="btn btn-outline btn-sm" style="width:auto" onclick="dpCrWithdraw('${oid}')">${t('撤回请求')}</button>`
|
|
21
|
+
} else if (isBuyer && cr.can_request) {
|
|
22
|
+
const hint = eff === 'declined' ? `<div style="font-size:12px;color:#b91c1c;margin-bottom:6px">${t('卖家拒绝了上次请求(继续发货)。可再次申请或与卖家沟通。')}</div>` : (eff === 'expired' ? `<div style="font-size:12px;color:#b91c1c;margin-bottom:6px">${t('上次请求卖家未在期限内响应。可重新申请,或发起争议。')}</div>` : '')
|
|
23
|
+
body = `${hint}<textarea id="dp-cr-reason-${oid}" class="form-control" rows="2" placeholder="${t('取消理由(可选,卖家可见)')}" style="margin-bottom:8px;font-size:12px"></textarea>
|
|
24
|
+
<button class="btn btn-outline btn-sm" style="width:auto" onclick="dpCrRequest('${oid}')">${t('申请取消并退款')}</button>`
|
|
25
|
+
} else if (isSeller && cr.can_respond) {
|
|
26
|
+
body = `<div style="font-size:12px;color:#374151;margin-bottom:8px">📩 ${t('买家申请取消订单并退款。')}${req && req.reason ? '「' + escHtml(req.reason) + '」' : ''} ${t('同意:先在协议外退款,再点"我已退款";不同意:拒绝并继续发货。')}</div>
|
|
27
|
+
<input id="dp-cr-ref-${oid}" class="form-control" maxlength="200" placeholder="${t('退款参考(转账单号等,可选,买家可见)')}" style="margin-bottom:8px;font-size:12px">
|
|
28
|
+
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
29
|
+
<button class="btn btn-primary btn-sm" style="width:auto" onclick="dpCrMarkRefunded('${oid}')">${t('我已退款')}</button>
|
|
30
|
+
<button class="btn btn-outline btn-sm" style="width:auto" onclick="dpCrDecline('${oid}')">${t('拒绝(继续发货)')}</button>
|
|
31
|
+
</div>`
|
|
32
|
+
} else if (isSeller && eff === 'refund_marked') {
|
|
33
|
+
body = `<div style="font-size:12px;color:#6b7280">⏳ ${t('你已声明退款,等待买家确认收到后订单将无责取消。')}</div>`
|
|
34
|
+
} else return ''
|
|
35
|
+
return `<div class="card" style="border:1px solid #fde68a;background:linear-gradient(135deg,#fffbeb,#fef3c7)">${head}${body}</div>`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const dpCrToast = (m, kind) => { if (typeof toast$ === 'function') toast$(m, kind) }
|
|
39
|
+
const dpCrReload = (oid) => renderOrderDetail(document.getElementById('app'), oid)
|
|
40
|
+
|
|
41
|
+
window.dpCrRequest = async (oid) => {
|
|
42
|
+
const el = document.getElementById('dp-cr-reason-' + oid)
|
|
43
|
+
const r = await POST('/orders/' + oid + '/cancel-refund/request', { reason: (el && el.value || '').trim() })
|
|
44
|
+
if (r.error) return void dpCrToast(r.error, 'error')
|
|
45
|
+
dpCrToast(t('已发出取消退款请求,等待卖家响应'), 'success'); dpCrReload(oid)
|
|
46
|
+
}
|
|
47
|
+
window.dpCrDecline = async (oid) => {
|
|
48
|
+
const r = await POST('/orders/' + oid + '/cancel-refund/decline', {})
|
|
49
|
+
if (r.error) return void dpCrToast(r.error, 'error')
|
|
50
|
+
dpCrReload(oid)
|
|
51
|
+
}
|
|
52
|
+
window.dpCrMarkRefunded = async (oid) => {
|
|
53
|
+
if (typeof confirmModal === 'function' && !(await confirmModal(t('确认你已在协议外向买家完成退款?声明后买家确认即取消订单;虚假声明将留痕并可被追责。'), t('我已退款'), { danger: true }))) return
|
|
54
|
+
const el = document.getElementById('dp-cr-ref-' + oid)
|
|
55
|
+
const r = await POST('/orders/' + oid + '/cancel-refund/mark-refunded', { refund_reference: (el && el.value || '').trim() })
|
|
56
|
+
if (r.error) return void dpCrToast(r.error, 'error')
|
|
57
|
+
dpCrToast(t('已声明退款,等待买家确认'), 'success'); dpCrReload(oid)
|
|
58
|
+
}
|
|
59
|
+
window.dpCrWithdraw = async (oid) => {
|
|
60
|
+
const r = await POST('/orders/' + oid + '/cancel-refund/withdraw', {})
|
|
61
|
+
if (r.error) return void dpCrToast(r.error, 'error')
|
|
62
|
+
dpCrReload(oid)
|
|
63
|
+
}
|
|
64
|
+
window.dpCrConfirm = async (oid) => {
|
|
65
|
+
if (typeof confirmModal === 'function' && !(await confirmModal(t('确认已收到卖家退款?确认后订单将无责取消,不可撤销。若尚未到账请勿确认。'), t('已收到,取消订单'), { danger: true }))) return
|
|
66
|
+
let token
|
|
67
|
+
try { token = await requestPasskeyGate('direct_pay_order_action', { order_id: oid, action: 'cancel_refund_confirm' }) }
|
|
68
|
+
catch (e) { if (window.dpPromptRegisterPasskey) await window.dpPromptRegisterPasskey(e); return }
|
|
69
|
+
const r = await POST('/orders/' + oid + '/cancel-refund/confirm', { webauthn_token: token })
|
|
70
|
+
if (r.error) return void dpCrToast(window.dpErrorText ? window.dpErrorText(r.error_code, r.error) : r.error, 'error')
|
|
71
|
+
dpCrToast(t('订单已无责取消'), 'success'); dpCrReload(oid)
|
|
72
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Direct Pay (Rail 1) — 一键复制按钮(付款说明 / 付款参考号 / 金额)。UI ONLY;不碰任何钱路。
|
|
2
|
+
// 买家场外付款需把收款账号/参考号抄进银行 App —— 手抄是整条轨最易错的一步,复制按钮把出错率降到 0。
|
|
3
|
+
// 文本存 data-dpcopy(escHtml 转义双引号/单引号,属性安全);点按走 app.js 健壮 copyText(clipboard→execCommand 回退),
|
|
4
|
+
// 成功后短暂显示 ✓ 已复制再还原。escHtml/copyText/t 均为运行时(render/click 时)调用,此文件早于 app.js 加载无碍。
|
|
5
|
+
window.dpCopyBtn = (text, label) => `<button type="button" class="btn btn-sm dp-copy-btn" style="padding:2px 8px;font-size:11px;background:#eef2ff;color:#4338ca;border:none;border-radius:6px;cursor:pointer;white-space:nowrap;vertical-align:middle" data-dpcopy="${escHtml(String(text ?? ''))}" onclick="dpDoCopy(this)">📋 ${escHtml(label || t('复制'))}</button>`
|
|
6
|
+
window.dpDoCopy = async (el) => {
|
|
7
|
+
if (!el || el.dataset.busy === '1') return
|
|
8
|
+
const ok = await copyText(el.getAttribute('data-dpcopy') || '')
|
|
9
|
+
if (!ok) return
|
|
10
|
+
const orig = el.innerHTML; el.dataset.busy = '1'; el.innerHTML = '✓ ' + t('已复制')
|
|
11
|
+
setTimeout(() => { el.innerHTML = orig; el.dataset.busy = '' }, 1500)
|
|
12
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// 平台服务费预充值申请 —— 状态筛选 tab + 已处理(历史)只读卡。补齐"申请入账成功后在管理页不可查"的缺口:
|
|
2
|
+
// pending 走 afprCard(可操作),approved/rejected/cancelled 走 afprHistoryCard(只读,显审核人/时间/备注/入账流水)。
|
|
3
|
+
// backend GET /admin/direct-receive/fee-prepay-requests?status=X(无 status = 全部,最多 200 条)已支持,本文件仅补前端 UI。
|
|
4
|
+
// 面向管理员中文走 t(),英文 i18n.js _EN。
|
|
5
|
+
window.afprStatusFilter = 'pending'
|
|
6
|
+
|
|
7
|
+
// 状态筛选 tab 条。点击切换 → afprHydrate(status)(重拉 + 高亮)。
|
|
8
|
+
window.afprTabs = () => {
|
|
9
|
+
const tabs = [['pending', t('待审核')], ['approved', t('已入账')], ['rejected', t('已驳回')], ['all', t('全部')]]
|
|
10
|
+
return `<div id="afpr-tabs" style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px">${tabs.map(([s, label]) =>
|
|
11
|
+
`<button class="btn btn-sm" data-afpr-tab="${s}" style="width:auto;padding:4px 12px;${s === window.afprStatusFilter ? 'background:#111827;color:#fff' : 'background:#f3f4f6;color:#374151'}" onclick="afprHydrate('${s}')">${label}</button>`).join('')}</div>`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 高亮当前 tab(afprHydrate 拉数据后调用;同时记住当前筛选,供 approve/reject 后原地重拉)。
|
|
15
|
+
window.afprSetActiveTab = (status) => {
|
|
16
|
+
window.afprStatusFilter = status
|
|
17
|
+
document.querySelectorAll('[data-afpr-tab]').forEach(b => {
|
|
18
|
+
const on = b.getAttribute('data-afpr-tab') === status
|
|
19
|
+
b.style.background = on ? '#111827' : '#f3f4f6'; b.style.color = on ? '#fff' : '#374151'
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 已处理申请只读卡(不给 approve/reject 按钮;显状态徽章 + 审核人/时间/备注 + 入账流水 id)。
|
|
24
|
+
window.afprHistoryCard = (r) => `
|
|
25
|
+
<div style="border:1px solid #e5e7eb;border-radius:10px;padding:10px 12px;margin-bottom:10px;background:#fafafa" data-req="${escHtml(r.id)}">
|
|
26
|
+
<div style="display:flex;justify-content:space-between;align-items:baseline;gap:8px">
|
|
27
|
+
<div style="font-size:15px;font-weight:800">${(r.amount_units / 1e6).toFixed(2)} <span style="font-size:12px;color:#6b7280">${escHtml(r.currency || 'USDC')}</span></div>
|
|
28
|
+
<span style="font-size:11px;font-weight:700;padding:2px 8px;border-radius:999px;white-space:nowrap;${r.status === 'approved' ? 'background:#dcfce7;color:#166534' : r.status === 'rejected' ? 'background:#fee2e2;color:#991b1b' : 'background:#e5e7eb;color:#4b5563'}">${window.afprStatus ? window.afprStatus(r.status) : escHtml(r.status)}</span>
|
|
29
|
+
</div>
|
|
30
|
+
<div style="font-size:11px;color:#9ca3af;margin:2px 0 6px">${t('申请 id')}: <code>${escHtml(r.id)}</code></div>
|
|
31
|
+
<div style="font-size:12px;color:#374151;line-height:1.7">
|
|
32
|
+
<div>${t('卖家')}: <code>${escHtml(r.seller_id)}</code></div>
|
|
33
|
+
<div>${t('付款凭证号')}: <b>${escHtml(r.evidence_ref)}</b>${r.evidence_note ? ` · ${escHtml(r.evidence_note)}` : ''}</div>
|
|
34
|
+
<div>${t('申请时间')}: <span style="color:#6b7280">${escHtml(r.created_at || '-')}</span></div>
|
|
35
|
+
<div>${t('审核人')}: <code>${escHtml(r.reviewed_by || '-')}</code> · ${t('审核时间')}: <span style="color:#6b7280">${escHtml(r.reviewed_at || '-')}</span></div>
|
|
36
|
+
${r.review_note ? `<div>${t('审核备注')}: ${escHtml(r.review_note)}</div>` : ''}
|
|
37
|
+
${r.resulting_payment_id ? `<div>${t('入账流水')}: <code>${escHtml(r.resulting_payment_id)}</code></div>` : ''}
|
|
38
|
+
</div>
|
|
39
|
+
</div>`
|
|
@@ -23,7 +23,7 @@ window.renderAdminDirectPayHub = function (app) {
|
|
|
23
23
|
adminLinkCard('🔖', t('逐产品直付验证'), t('核验商品外链验证码'), '#admin/product-verifications') +
|
|
24
24
|
adminLinkCard('🏬', t('店铺认证审核'), t('核验店铺外链 + 免逐品'), '#admin/store-verifications')) : ''}
|
|
25
25
|
${root ? grp(t('平台服务费(预充值)'),
|
|
26
|
-
adminLinkCard('💰', t('预充值与账户'), t('充值 / 调整 / 退款 / 余额 / 应收'), '#admin/dp-fee')) : ''}
|
|
26
|
+
adminLinkCard('💰', t('预充值与账户'), t('充值 / 调整 / 退款 / 余额 / 应收'), '#admin/dp-fee') + adminLinkCard('🏦', t('平台收款方式'), t('WebAZ 收款账号(卖家充值平台服务费用),可多个'), '#admin/platform-receive') + adminLinkCard('🧾', t('平台服务费预充值申请'), t('核对到账 → 确认入账 / 驳回(卖家发起)'), '#admin/fee-prepay-requests') + adminLinkCard('🏦', t('保证金缴纳申报'), t('履约保证金:核对到账 → 确认锁定 / 驳回(双锁 fail-closed)'), '#admin/bond-deposits')) : ''}
|
|
27
27
|
${grp(t('上线控制'),
|
|
28
28
|
adminLinkCard('⚙️', t('直付参数'), t('开关 / 地区 / 单笔上限'), '#admin/params'))}
|
|
29
29
|
<div style="font-size:12px;color:#6b7280;margin-top:12px">${t('就绪报告(CLI):')} <code>npm run direct-pay:readiness</code></div>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// 平台服务费【预充值申请】—— 卖家侧 UI。看平台收款方式(据此线下付款)→ 填金额+凭据申请 → 看自己申请状态 → 撤销 pending。
|
|
2
|
+
// 申请【不动钱、不 Passkey】(申请不授予任何东西);真正入账由平台核实真实到账后确认。凭据必填 —— 杜绝"场外直接付、无据可查"。
|
|
3
|
+
// 面向用户中文走 t(),英文在 i18n.js;双语 parity 由 test-direct-pay-ui.ts 守。
|
|
4
|
+
|
|
5
|
+
window.dpFeeReqStatus = (s) => ({ pending: t('待审核'), approved: t('已入账'), rejected: t('已驳回'), cancelled: t('已撤销') }[s] || s)
|
|
6
|
+
// 金额(USDC 小数)→ base units(1 WAZ=1e6;正数、≤6 位小数)。非法 → NaN。
|
|
7
|
+
window.dpFeeReqUnits = (id) => {
|
|
8
|
+
const e = document.getElementById(id); const raw = e ? e.value.trim() : ''
|
|
9
|
+
if (!/^\d+(\.\d{1,6})?$/.test(raw)) return NaN
|
|
10
|
+
const [i, f = ''] = raw.split('.'); return Number(i) * 1e6 + Number((f + '000000').slice(0, 6))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
window.dpFeeRequestSection = () => `
|
|
14
|
+
<div class="card" style="margin-bottom:12px">
|
|
15
|
+
<div style="font-size:14px;font-weight:700;margin-bottom:6px">➕ ${t('申请平台服务费预充值')}</div>
|
|
16
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('按下列平台收款方式线下付款后,填金额和付款凭证号提交申请;平台核实真实到账后为你入账。申请本身不划扣任何款项。')}</div>
|
|
17
|
+
<div id="dp-feereq-box">${loading$()}</div>
|
|
18
|
+
</div>`
|
|
19
|
+
|
|
20
|
+
window.dpHydrateFeeRequest = async () => {
|
|
21
|
+
const box = document.getElementById('dp-feereq-box'); if (!box) return
|
|
22
|
+
const [accR, reqR] = await Promise.all([GET('/direct-receive/platform-receive-accounts'), GET('/direct-receive/fee-prepay-requests')])
|
|
23
|
+
const accounts = (accR && accR.accounts) || []
|
|
24
|
+
const requests = (reqR && reqR.requests) || []
|
|
25
|
+
const acctList = accounts.length ? accounts.map(a => `
|
|
26
|
+
<div style="border:1px solid #c7d2fe;background:#eef2ff;border-radius:8px;padding:8px 10px;margin-bottom:6px;display:flex;justify-content:space-between;gap:8px">
|
|
27
|
+
<div style="min-width:0">
|
|
28
|
+
<div style="font-size:12px;font-weight:700">${a.label ? escHtml(a.label) : (a.method ? escHtml(a.method) : t('平台收款方式'))}${a.currency ? ` · ${escHtml(a.currency)}` : ''}</div>
|
|
29
|
+
<div style="font-size:12px;color:#374151;white-space:pre-wrap;word-break:break-word">${escHtml(a.instruction)}</div>
|
|
30
|
+
</div>
|
|
31
|
+
${a.qr_data_uri ? `<img src="${a.qr_data_uri}" alt="${t('收款二维码')}" style="width:64px;height:64px;object-fit:contain;border-radius:6px;flex:0 0 auto">` : ''}
|
|
32
|
+
</div>`).join('') : `<div style="font-size:12px;color:#9ca3af">${t('平台暂未配置收款方式,请联系平台')}</div>`
|
|
33
|
+
const opts = accounts.map(a => `<option value="${escHtml(a.id)}">${a.label ? escHtml(a.label) : (a.method ? escHtml(a.method) : a.id)}${a.currency ? ` · ${escHtml(a.currency)}` : ''}</option>`).join('')
|
|
34
|
+
const reqRows = requests.length ? requests.map(r => `
|
|
35
|
+
<div style="border:1px solid #e5e7eb;border-radius:8px;padding:6px 10px;margin-bottom:6px;font-size:12px">
|
|
36
|
+
<div style="display:flex;justify-content:space-between;gap:8px">
|
|
37
|
+
<span><b>${(r.amount_units / 1e6).toFixed(2)}</b> ${escHtml(r.currency || 'USDC')} · <span style="color:#6b7280">${window.dpFeeReqStatus(r.status)}</span></span>
|
|
38
|
+
${r.status === 'pending' ? `<button class="btn btn-outline btn-sm" style="font-size:11px;padding:2px 8px" onclick="dpCancelFeeRequest('${r.id}')">${t('撤销')}</button>` : ''}
|
|
39
|
+
</div>
|
|
40
|
+
<div style="color:#9ca3af;margin-top:2px">${t('申请 id')}: <code>${escHtml(r.id)}</code> · ${t('凭证')}: ${escHtml(r.evidence_ref)}${r.review_note ? ` · ${escHtml(r.review_note)}` : ''}</div>
|
|
41
|
+
</div>`).join('') : `<div style="font-size:12px;color:#9ca3af">${t('暂无申请记录')}</div>`
|
|
42
|
+
// 无 active 平台收款方式 → 不给表单(没有对准的 WebAZ 收款账户就无法可追踪充值),只提示联系平台。
|
|
43
|
+
const formHtml = accounts.length ? `
|
|
44
|
+
<div id="dp-feereq-msg" style="margin-top:8px"></div>
|
|
45
|
+
<div class="form-group" style="margin-top:8px"><label class="form-label">${t('充值金额 USDC')}</label><input class="form-control" id="dp-feereq-amt" type="number" step="any" min="0" placeholder="${t('如 50')}"></div>
|
|
46
|
+
<div class="form-group"><label class="form-label">${t('付给哪个平台收款方式')} <span style="color:#dc2626">*</span></label><select class="form-control" id="dp-feereq-acct">${opts}</select></div>
|
|
47
|
+
<div class="form-group"><label class="form-label">${t('付款凭证号 evidence_ref')} <span style="color:#dc2626">*</span> <span style="font-size:11px;color:#9ca3af">${t('(转账流水号 / 交易 ID,必填)')}</span></label><input class="form-control" id="dp-feereq-ev" maxlength="200" placeholder="${t('如银行流水号 / 链上 txid')}"></div>
|
|
48
|
+
<div class="form-group"><label class="form-label">${t('备注(可选)')}</label><input class="form-control" id="dp-feereq-note" maxlength="500"></div>
|
|
49
|
+
<button class="btn btn-primary btn-sm" onclick="dpSubmitFeeRequest()">${t('提交申请')}</button>`
|
|
50
|
+
: `<div style="font-size:12px;color:#b45309;background:#fffbeb;border:1px solid #fde68a;border-radius:8px;padding:8px 10px;margin-top:8px">${t('平台暂未配置收款方式,请联系平台')}</div>`
|
|
51
|
+
box.innerHTML = `
|
|
52
|
+
<div style="font-size:12px;font-weight:600;color:#374151;margin-bottom:4px">${t('平台收款方式(据此付款)')}</div>
|
|
53
|
+
${acctList}
|
|
54
|
+
${formHtml}
|
|
55
|
+
<div style="font-size:12px;font-weight:600;color:#374151;margin:14px 0 4px">${t('我的申请')}</div>
|
|
56
|
+
${reqRows}`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
window.dpSubmitFeeRequest = async () => {
|
|
60
|
+
const msg = document.getElementById('dp-feereq-msg')
|
|
61
|
+
const show = (type, m) => { if (msg) msg.innerHTML = alert$(type, m); else if (typeof toast$ === 'function') toast$(m, type) }
|
|
62
|
+
const amount_units = window.dpFeeReqUnits('dp-feereq-amt')
|
|
63
|
+
if (!(amount_units > 0)) { show('error', t('请填写正数充值金额')); return }
|
|
64
|
+
const evidence_ref = document.getElementById('dp-feereq-ev')?.value?.trim() || ''
|
|
65
|
+
if (!evidence_ref) { show('error', t('付款凭证号必填(不能无据)')); return }
|
|
66
|
+
const platform_account_id = document.getElementById('dp-feereq-acct')?.value || ''
|
|
67
|
+
if (!platform_account_id) { show('error', t('请选择平台收款方式')); return }
|
|
68
|
+
const evidence_note = document.getElementById('dp-feereq-note')?.value?.trim() || null
|
|
69
|
+
const r = await POST('/direct-receive/fee-prepay-request', { amount_units, platform_account_id, evidence_ref, evidence_note })
|
|
70
|
+
if (r.error) { show('error', r.error || t('提交失败,请重试')); return }
|
|
71
|
+
if (typeof toast$ === 'function') toast$(t('申请已提交,等待平台核实入账'), 'success')
|
|
72
|
+
window.dpHydrateFeeRequest()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
window.dpCancelFeeRequest = async (id) => {
|
|
76
|
+
const go = await confirmModal(t('确定撤销这条预充值申请?'), t('撤销'), { danger: true })
|
|
77
|
+
if (!go) return
|
|
78
|
+
const r = await POST('/direct-receive/fee-prepay-request/' + id + '/cancel', {})
|
|
79
|
+
if (r.error) { if (typeof toast$ === 'function') toast$(r.error, 'error'); return }
|
|
80
|
+
if (typeof toast$ === 'function') toast$(t('已撤销'), 'success'); window.dpHydrateFeeRequest()
|
|
81
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// 平台服务费预充值申请 —— admin(ROOT)审核队列。核对真实到账 → 确认入账(Passkey,唯一动钱)/ 驳回。
|
|
2
|
+
// approve 的 Passkey 绑定 {request_id, seller_id, amount_units, method}(把入账金额/对象钉进 token,防替换);reject 绑 request_id。
|
|
3
|
+
// 每条显示【申请 id(fpr_…)】便于和卖家对齐管理。面向管理员中文走 t()。
|
|
4
|
+
|
|
5
|
+
window.afprStatus = (s) => ({ pending: t('待审核'), approved: t('已入账'), rejected: t('已驳回'), cancelled: t('已撤销') }[s] || s)
|
|
6
|
+
|
|
7
|
+
window.renderAdminFeePrepayRequests = function (app) {
|
|
8
|
+
if (!state.user) { renderLogin(); return }
|
|
9
|
+
if (typeof isAdmin === 'function' && !isAdmin()) { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限管理员')}</div>`, 'admin'); return }
|
|
10
|
+
if ((state.user.admin_type || 'root') !== 'root') { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限根管理员')}</div>`, 'admin'); return }
|
|
11
|
+
app.innerHTML = shell(`
|
|
12
|
+
<h1 class="page-title">🧾 ${t('平台服务费预充值申请')}</h1>
|
|
13
|
+
<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>
|
|
14
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('卖家发起的平台服务费预充值申请。请【核对凭证号对应的真实到账】后再确认入账;确认即为该商家记一笔预充值(真人 Passkey,唯一动钱步)。')}</div>
|
|
15
|
+
${window.afprTabs ? window.afprTabs() : ''}<div id="afpr-box">${loading$()}</div>
|
|
16
|
+
`, 'admin')
|
|
17
|
+
window.afprHydrate()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
window.afprHydrate = async (status) => {
|
|
21
|
+
const box = document.getElementById('afpr-box'); if (!box) return; const st = status || window.afprStatusFilter || 'pending'; if (window.afprSetActiveTab) window.afprSetActiveTab(st)
|
|
22
|
+
const r = await GET('/admin/direct-receive/fee-prepay-requests' + (st === 'all' ? '' : '?status=' + st))
|
|
23
|
+
if (r.error) { box.innerHTML = alert$('error', r.error || t('加载失败')); return }
|
|
24
|
+
const reqs = r.requests || []
|
|
25
|
+
box.innerHTML = `
|
|
26
|
+
<div id="afpr-msg"></div>
|
|
27
|
+
${reqs.length ? reqs.map(x => x.status === 'pending' ? window.afprCard(x) : (window.afprHistoryCard ? window.afprHistoryCard(x) : window.afprCard(x))).join('') : `<div style="font-size:12px;color:#9ca3af">${t('暂无申请')}</div>`}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
window.afprCard = (r) => `
|
|
31
|
+
<div style="border:1px solid #e5e7eb;border-radius:10px;padding:10px 12px;margin-bottom:10px" data-req="${escHtml(r.id)}">
|
|
32
|
+
<div style="font-size:15px;font-weight:800">${(r.amount_units / 1e6).toFixed(2)} <span style="font-size:12px;color:#6b7280">${escHtml(r.currency || 'USDC')}</span></div>
|
|
33
|
+
<div style="font-size:11px;color:#9ca3af;margin:2px 0 6px">${t('申请 id')}: <code>${escHtml(r.id)}</code></div>
|
|
34
|
+
<div style="font-size:12px;color:#374151;line-height:1.7">
|
|
35
|
+
<div>${t('卖家')}: <code>${escHtml(r.seller_id)}</code></div>
|
|
36
|
+
<div>${t('付给平台账户')}: <code>${escHtml(r.platform_account_id || '-')}</code></div>
|
|
37
|
+
<div>${t('付款凭证号')}: <b>${escHtml(r.evidence_ref)}</b>${r.evidence_note ? ` · ${escHtml(r.evidence_note)}` : ''}</div>
|
|
38
|
+
<div style="color:#9ca3af">${escHtml(r.created_at || '')}</div>
|
|
39
|
+
</div>
|
|
40
|
+
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:8px">
|
|
41
|
+
<select id="afpr-method-${r.id}" style="padding:6px;border:1px solid #d1d5db;border-radius:6px;font-size:12px"><option value="usdc">USDC</option><option value="fiat">${t('法币 fiat')}</option></select>
|
|
42
|
+
<button class="btn btn-primary btn-sm" onclick="afprApprove('${r.id}')">${t('确认到账并入账(真人 Passkey)')}</button>
|
|
43
|
+
<button class="btn btn-outline btn-sm" style="color:#dc2626;border-color:#dc2626" onclick="afprReject('${r.id}')">${t('驳回(真人 Passkey)')}</button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>`
|
|
46
|
+
|
|
47
|
+
window.afprApprove = async (id) => {
|
|
48
|
+
const card = document.querySelector(`[data-req="${id}"]`); if (!card) return
|
|
49
|
+
const r = await GET('/admin/direct-receive/fee-prepay-requests?status=pending')
|
|
50
|
+
const req = ((r && r.requests) || []).find(x => x.id === id)
|
|
51
|
+
if (!req) { if (typeof toast$ === 'function') toast$(t('该申请已不在待审核队列'), 'error'); window.afprHydrate(); return }
|
|
52
|
+
const method = document.getElementById('afpr-method-' + id)?.value || 'usdc'
|
|
53
|
+
let token
|
|
54
|
+
try { token = await requestPasskeyGate('direct_pay_fee_prepay_request_approve', { request_id: id, seller_id: req.seller_id, amount_units: req.amount_units, method }) }
|
|
55
|
+
catch (e) { if (typeof toast$ === 'function') toast$((e && e.message ? e.message + ' — ' : '') + t('需先注册 Passkey'), 'error'); return }
|
|
56
|
+
const res = await POST('/admin/direct-receive/fee-prepay-requests/' + id + '/approve', { method, note: null, webauthn_token: token })
|
|
57
|
+
if (res.error) { if (typeof toast$ === 'function') toast$(res.error || t('入账失败'), 'error'); return }
|
|
58
|
+
if (typeof toast$ === 'function') toast$(t('已确认入账'), 'success'); window.afprHydrate()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
window.afprReject = async (id) => {
|
|
62
|
+
const note = await confirmModal(t('确定驳回这条申请?(将通知卖家未通过)'), t('驳回'), { danger: true })
|
|
63
|
+
if (!note) return
|
|
64
|
+
let token
|
|
65
|
+
try { token = await requestPasskeyGate('direct_pay_fee_prepay_reject', { request_id: id }) }
|
|
66
|
+
catch (e) { if (typeof toast$ === 'function') toast$((e && e.message ? e.message + ' — ' : '') + t('需先注册 Passkey'), 'error'); return }
|
|
67
|
+
const res = await POST('/admin/direct-receive/fee-prepay-requests/' + id + '/reject', { note: null, webauthn_token: token })
|
|
68
|
+
if (res.error) { if (typeof toast$ === 'function') toast$(res.error || t('驳回失败'), 'error'); return }
|
|
69
|
+
if (typeof toast$ === 'function') toast$(t('已驳回'), 'success'); window.afprHydrate()
|
|
70
|
+
}
|