@seasonkoh/webaz 0.1.28 → 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.
- package/README.md +3 -2
- package/README.zh-CN.md +7 -6
- package/dist/bond-refund-blockers.js +50 -0
- package/dist/bond-slash.js +100 -0
- package/dist/bond-terms.js +31 -0
- package/dist/currency.js +16 -0
- package/dist/deposit-rails.js +52 -0
- package/dist/direct-pay-aml-monitor.js +67 -0
- package/dist/direct-pay-aml-review.js +40 -0
- package/dist/direct-pay-base-bond-entry.js +5 -0
- package/dist/direct-pay-bond-rail-clearance.js +99 -0
- package/dist/direct-pay-cancel-refund.js +160 -0
- package/dist/direct-pay-compliance-ingress.js +145 -0
- package/dist/direct-pay-controls.js +136 -0
- package/dist/direct-pay-create.js +231 -0
- package/dist/direct-pay-deferral-quota.js +43 -0
- package/dist/direct-pay-disclosures.js +50 -0
- package/dist/direct-pay-eligibility.js +46 -0
- package/dist/direct-pay-fee-ar.js +241 -0
- package/dist/direct-pay-fee-prepay-request.js +80 -0
- package/dist/direct-pay-launch-readiness.js +108 -0
- package/dist/direct-pay-launch-summary.js +68 -0
- package/dist/direct-pay-ledger.js +94 -0
- package/dist/direct-pay-returns.js +104 -0
- package/dist/direct-pay-stock.js +9 -0
- package/dist/direct-receive-account-qr.js +77 -0
- package/dist/direct-receive-accounts.js +82 -0
- package/dist/direct-receive-deferral.js +171 -0
- package/dist/direct-receive-deposits.js +365 -0
- package/dist/direct-receive-payment-instruction.js +26 -0
- package/dist/free-shipping.js +37 -0
- package/dist/fx-rates.js +78 -0
- package/dist/layer0-foundation/L0-1-database/schema.js +777 -1
- package/dist/layer0-foundation/L0-2-state-machine/engine.js +1 -0
- package/dist/layer0-foundation/L0-2-state-machine/genuine-sale.js +15 -5
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +114 -0
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +1 -1
- package/dist/layer1-agent/L1-1-mcp-server/cli.js +64 -0
- package/dist/layer1-agent/L1-1-mcp-server/network-mode.js +11 -0
- package/dist/layer1-agent/L1-1-mcp-server/server.js +99 -32
- 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/layer4-economics/L4-4-skill-market/skill-engine.js +4 -4
- package/dist/ledger.js +12 -3
- package/dist/mcp.js +23 -4
- package/dist/merchant-bond-domain.js +64 -0
- package/dist/merchant-bond-exposure.js +78 -0
- package/dist/merchant-bond-watcher.js +26 -0
- package/dist/payment-rails.js +29 -0
- package/dist/platform-receive-accounts.js +94 -0
- package/dist/product-verification.js +93 -0
- package/dist/pwa/acp-feed.js +3 -2
- package/dist/pwa/arbitration-read-admin.js +37 -0
- package/dist/pwa/arbitrator-lifecycle.js +100 -0
- package/dist/pwa/contract-fingerprint.js +18 -0
- package/dist/pwa/direct-pay-guards.js +20 -0
- package/dist/pwa/direct-pay-order-redaction.js +42 -0
- package/dist/pwa/endpoint-actions.js +3 -0
- 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-ai.js +10 -10
- 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 +29 -0
- package/dist/pwa/public/app-contribution-hub.js +23 -0
- package/dist/pwa/public/app-create-kinds.js +17 -0
- package/dist/pwa/public/app-direct-pay-accounts.js +141 -0
- package/dist/pwa/public/app-direct-pay-buyer.js +72 -0
- package/dist/pwa/public/app-direct-pay-cancel-refund.js +72 -0
- package/dist/pwa/public/app-direct-pay-compliance.js +67 -0
- package/dist/pwa/public/app-direct-pay-copy.js +12 -0
- package/dist/pwa/public/app-direct-pay-deferral-admin.js +72 -0
- package/dist/pwa/public/app-direct-pay-deferral.js +61 -0
- package/dist/pwa/public/app-direct-pay-fee-center.js +33 -0
- package/dist/pwa/public/app-direct-pay-fee-history.js +39 -0
- package/dist/pwa/public/app-direct-pay-fee-ops.js +112 -0
- 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-product-verify.js +103 -0
- package/dist/pwa/public/app-direct-pay-readiness.js +38 -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-store-verify.js +100 -0
- package/dist/pwa/public/app-direct-pay.js +226 -0
- package/dist/pwa/public/app-discover.js +20 -20
- package/dist/pwa/public/app-dispute-close-ui.js +38 -0
- package/dist/pwa/public/app-external-links.js +32 -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-listings.js +4 -4
- 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-prelaunch-waz.js +39 -0
- package/dist/pwa/public/app-price.js +55 -0
- package/dist/pwa/public/app-product-media.js +15 -0
- package/dist/pwa/public/app-profile.js +10 -10
- 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 +5 -5
- package/dist/pwa/public/app-shop.js +19 -19
- package/dist/pwa/public/app-trade-tax-ui.js +33 -0
- package/dist/pwa/public/app.js +264 -295
- package/dist/pwa/public/i18n.js +1068 -197
- package/dist/pwa/public/index.html +58 -0
- package/dist/pwa/public/openapi.json +1845 -653
- package/dist/pwa/public/style.css +3 -0
- package/dist/pwa/routes/admin-analytics.js +4 -2
- package/dist/pwa/routes/admin-direct-receive-deposits.js +533 -0
- 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 +253 -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/buyer-feeds.js +2 -1
- package/dist/pwa/routes/chat.js +6 -1
- package/dist/pwa/routes/dashboards.js +2 -1
- package/dist/pwa/routes/direct-pay-availability.js +196 -0
- package/dist/pwa/routes/direct-pay-cancel-refund.js +111 -0
- package/dist/pwa/routes/direct-pay-disclosure-acks.js +79 -0
- 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 +248 -0
- package/dist/pwa/routes/direct-receive-accounts.js +155 -0
- package/dist/pwa/routes/direct-receive-payment-instructions.js +45 -0
- 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/fx.js +12 -0
- package/dist/pwa/routes/governance-onboarding.js +46 -8
- package/dist/pwa/routes/leaderboard.js +47 -9
- package/dist/pwa/routes/listings.js +2 -2
- package/dist/pwa/routes/logistics.js +6 -2
- package/dist/pwa/routes/manifests.js +38 -0
- package/dist/pwa/routes/me-data.js +5 -2
- package/dist/pwa/routes/mutual-cancel.js +62 -0
- package/dist/pwa/routes/orders-action.js +297 -16
- package/dist/pwa/routes/orders-create.js +21 -6
- package/dist/pwa/routes/orders-read.js +106 -8
- package/dist/pwa/routes/p2p-products.js +2 -2
- package/dist/pwa/routes/platform-receive-accounts.js +111 -0
- package/dist/pwa/routes/products-create.js +15 -4
- package/dist/pwa/routes/products-links.js +34 -0
- package/dist/pwa/routes/products-list.js +2 -1
- package/dist/pwa/routes/products-update.js +37 -3
- package/dist/pwa/routes/profile-identity.js +4 -2
- package/dist/pwa/routes/promoter.js +3 -0
- package/dist/pwa/routes/referral.js +4 -0
- package/dist/pwa/routes/returns.js +60 -4
- package/dist/pwa/routes/rewards-apply.js +10 -5
- package/dist/pwa/routes/rewards-clearing-mature.js +96 -0
- package/dist/pwa/routes/rewards-escrow-expire.js +6 -2
- 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/shops.js +2 -1
- package/dist/pwa/routes/snf.js +4 -1
- package/dist/pwa/routes/url-claim.js +2 -2
- package/dist/pwa/routes/users-public.js +8 -0
- package/dist/pwa/routes/wallet-read.js +3 -0
- package/dist/pwa/routes/webauthn.js +3 -3
- package/dist/pwa/server.js +112 -132
- package/dist/runtime/agent-grant-scopes.js +69 -1
- package/dist/runtime/webaz-schema-helpers.js +161 -3
- package/dist/sale-regions.js +116 -0
- package/dist/shipping-templates.js +119 -0
- package/dist/store-verification.js +77 -0
- package/dist/trade-tax.js +99 -0
- package/dist/trade-terms.js +76 -0
- package/dist/version.js +1 -1
- package/package.json +128 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Direct Pay (Rail 1) — ADMIN 缓交(deferred base-bond)审批队列 (PR-②c)。UI ONLY。
|
|
2
|
+
// ROOT admin 审批商户的缓交申请:列出 pending,批准(设压低额度系数 + 宽限天数)或拒绝。
|
|
3
|
+
// 铁律:批准/拒绝是 RISK 动作 → 必须真人 Passkey(requestPasskeyGate),purpose_data 绑定【完整条款】
|
|
4
|
+
// (deferral_id + reduced_quota_factor + grace_days),与请求体逐字一致;agent 无 Passkey 凭证会被后端硬拒。
|
|
5
|
+
// 纯前端:只调既有受门控端点 GET/POST /api/admin/direct-receive/deferrals[/:id/approve|reject];
|
|
6
|
+
// 不碰 wallet/escrow/settlement/refund;授予绝不自动(approveDeferral 是唯一 writer,后端 ROOT+Passkey 强制)。
|
|
7
|
+
|
|
8
|
+
window.renderAdminDirectPayDeferrals = async function (app) {
|
|
9
|
+
if (!state.user) { renderLogin(); return }
|
|
10
|
+
if (!isAdmin()) { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限管理员')}</div>`, 'admin'); return }
|
|
11
|
+
app.innerHTML = shell(loading$(), 'admin')
|
|
12
|
+
const data = await GET('/admin/direct-receive/deferrals?status=pending')
|
|
13
|
+
if (!data || data.error) { app.innerHTML = shell(alert$('error', (data && data.error) || t('加载失败')), 'admin'); return }
|
|
14
|
+
const rows = (data.deferrals || [])
|
|
15
|
+
const list = rows.length ? rows.map(d => `
|
|
16
|
+
<div class="card" style="margin-bottom:10px">
|
|
17
|
+
<div style="font-size:11px;color:#9ca3af">${escHtml(d.user_id)} · ${fmtTime(d.created_at)}</div>
|
|
18
|
+
<div style="font-size:13px;margin-top:6px">${t('缓交期(天)')}: <strong>${d.period_days}</strong></div>
|
|
19
|
+
${d.reason ? `<div style="font-size:12px;color:#6b7280;margin-top:6px;padding:6px;background:#f9fafb;border-radius:4px">${escHtml(d.reason)}</div>` : ''}
|
|
20
|
+
<div style="display:flex;gap:6px;margin-top:10px">
|
|
21
|
+
<button class="btn btn-primary btn-sm" style="font-size:12px" onclick="toggleInline('dfr-approve-${d.id}')">✓ ${t('批准')}</button>
|
|
22
|
+
<button class="btn btn-outline btn-sm" style="font-size:12px;color:#dc2626;border-color:#dc2626" onclick="toggleInline('dfr-reject-${d.id}')">✗ ${t('拒绝')}</button>
|
|
23
|
+
</div>
|
|
24
|
+
<div id="dfr-approve-${d.id}" style="display:none;margin-top:10px;padding:10px;background:#f0fdf4;border:1px solid #86efac;border-radius:6px">
|
|
25
|
+
<div style="font-size:12px;font-weight:600;color:#166534;margin-bottom:6px">✓ ${t('批准缓交(需真人 Passkey)')}</div>
|
|
26
|
+
<div style="font-size:11px;color:#6b7280;margin-bottom:6px">${t('缓交期额度系数(0–1,留空用默认;缓交期内必压低,有下限)')}</div>
|
|
27
|
+
<input id="dfr-approve-factor-${d.id}" type="number" min="0" max="1" step="0.05" placeholder="${t('额度系数(如 0.5)')}" style="width:100%;padding:6px;border:1px solid #d1d5db;border-radius:4px;font-size:12px;margin-bottom:6px">
|
|
28
|
+
<input id="dfr-approve-grace-${d.id}" type="number" min="0" placeholder="${t('宽限天数(留空用默认)')}" style="width:100%;padding:6px;border:1px solid #d1d5db;border-radius:4px;font-size:12px;margin-bottom:6px">
|
|
29
|
+
<div style="display:flex;gap:6px">
|
|
30
|
+
<button class="btn btn-outline btn-sm" style="font-size:11px" onclick="toggleInline('dfr-approve-${d.id}')">${t('取消')}</button>
|
|
31
|
+
<button class="btn btn-primary btn-sm" style="font-size:11px" onclick="doApproveDeferralInline('${d.id}')">${t('真人确认批准')}</button>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<div id="dfr-reject-${d.id}" style="display:none;margin-top:10px;padding:10px;background:#fef2f2;border:1px solid #fca5a5;border-radius:6px">
|
|
35
|
+
<div style="font-size:12px;font-weight:600;color:#991b1b;margin-bottom:6px">✗ ${t('拒绝缓交(需真人 Passkey)')}</div>
|
|
36
|
+
<div style="display:flex;gap:6px">
|
|
37
|
+
<button class="btn btn-outline btn-sm" style="font-size:11px" onclick="toggleInline('dfr-reject-${d.id}')">${t('取消')}</button>
|
|
38
|
+
<button class="btn btn-sm" style="font-size:11px;background:#dc2626;border-color:#dc2626;color:#fff" onclick="doRejectDeferralInline('${d.id}')">${t('真人确认拒绝')}</button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>`).join('') : `<div class="empty"><div class="empty-icon">📥</div><div class="empty-text">${t('暂无待审缓交申请')}</div></div>`
|
|
42
|
+
app.innerHTML = shell(`
|
|
43
|
+
<h1 class="page-title">🪙 ${t('履约保证金缓交审批')}</h1>
|
|
44
|
+
<div style="margin-bottom:12px"><button class="btn btn-outline btn-sm" style="width:auto" onclick="navigate('#admin')">${t('返回概览')}</button></div>
|
|
45
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('批准后该卖家可先入场直付、保证金延后交;直付仍需满足全部合规条件,且缓交期内额度被压低。批准/拒绝均需真人 Passkey。')}</div>
|
|
46
|
+
${list}
|
|
47
|
+
`, 'admin')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 批准:purpose_data 必须与请求体逐字一致(后端绑定 deferral_id + reduced_quota_factor + grace_days)。
|
|
51
|
+
window.doApproveDeferralInline = async function (id) {
|
|
52
|
+
const fEl = document.getElementById('dfr-approve-factor-' + id), gEl = document.getElementById('dfr-approve-grace-' + id)
|
|
53
|
+
const body = { deferral_id: id }
|
|
54
|
+
if (fEl && fEl.value !== '') body.reduced_quota_factor = Number(fEl.value)
|
|
55
|
+
if (gEl && gEl.value !== '') body.grace_days = Number(gEl.value)
|
|
56
|
+
const token = await requestPasskeyGate('direct_pay_deferral_approve', body)
|
|
57
|
+
if (!token) { if (typeof toast$ === 'function') toast$(t('需要真人 Passkey 确认'), 'error'); return }
|
|
58
|
+
const r = await POST(`/admin/direct-receive/deferrals/${id}/approve`, { ...body, webauthn_token: token })
|
|
59
|
+
if (!r || r.error) { if (typeof toast$ === 'function') toast$(window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试'), 'error'); return }
|
|
60
|
+
if (typeof toast$ === 'function') toast$(t('缓交已批准'))
|
|
61
|
+
window.renderAdminDirectPayDeferrals(document.getElementById('app'))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 拒绝:purpose_data 绑 deferral_id。
|
|
65
|
+
window.doRejectDeferralInline = async function (id) {
|
|
66
|
+
const token = await requestPasskeyGate('direct_pay_deferral_reject', { deferral_id: id })
|
|
67
|
+
if (!token) { if (typeof toast$ === 'function') toast$(t('需要真人 Passkey 确认'), 'error'); return }
|
|
68
|
+
const r = await POST(`/admin/direct-receive/deferrals/${id}/reject`, { deferral_id: id, webauthn_token: token })
|
|
69
|
+
if (!r || r.error) { if (typeof toast$ === 'function') toast$(window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试'), 'error'); return }
|
|
70
|
+
if (typeof toast$ === 'function') toast$(t('缓交申请已拒绝'))
|
|
71
|
+
window.renderAdminDirectPayDeferrals(document.getElementById('app'))
|
|
72
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Direct Pay (Rail 1) — SELLER 缓交(deferred base-bond)apply + status panel (PR-②b)。UI ONLY。
|
|
2
|
+
// 缓交 = 先入场、履约保证金延后交,由管理员【人工审批】(绝不自动批)。本面板让卖家:① 申请缓交;② 查看自己申请状态。
|
|
3
|
+
// 数据/动作:GET + POST /api/direct-receive/deferral(后端脱敏:不含 admin 身份)。申请只创建 pending,【不授予任何资格】;
|
|
4
|
+
// 真正放行 = 管理员 ROOT + 真人 Passkey 审批。即便批准,直付仍需满足全部合规门(实名/制裁/AML/Passkey/收款说明)。
|
|
5
|
+
// 不碰 wallet/escrow/settlement/refund/钱路;不开真实 rail;不声称已上线。买家侧不展示本面板。
|
|
6
|
+
|
|
7
|
+
window.dpDeferralCopy = () => ({
|
|
8
|
+
pending: { icon: '⏳', text: t('缓交申请审核中,等待管理员人工审批') },
|
|
9
|
+
granted: { icon: '✅', text: t('缓交已批准') },
|
|
10
|
+
rejected: { icon: '🚫', text: t('缓交申请未通过') },
|
|
11
|
+
expired: { icon: '⌛', text: t('缓交已到期') },
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
window.dpSellerDeferralSection = () => `
|
|
15
|
+
<div class="card" style="margin-bottom:12px;border:1px solid #e5e7eb">
|
|
16
|
+
<div style="font-size:14px;font-weight:700;margin-bottom:4px">🪙 ${t('履约保证金缓交(仅你可见)')}</div>
|
|
17
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('缓交 = 先入场、履约保证金延后交,由管理员人工审批。批准后直付仍需满足全部合规条件(身份与商户合规审核、Passkey、收款说明);缓交期内额度会被压低。')}</div>
|
|
18
|
+
<div id="dp-seller-deferral">${loading$()}</div>
|
|
19
|
+
</div>`
|
|
20
|
+
|
|
21
|
+
// 申请表单(无活跃申请时显示)。period 选填(留空走后端默认 30);reason 选填。
|
|
22
|
+
window.dpDeferralApplyForm = () => `
|
|
23
|
+
<div style="display:flex;flex-direction:column;gap:8px">
|
|
24
|
+
<textarea id="dp-dfr-reason" maxlength="500" placeholder="${t('申请原因(选填)')}" style="width:100%;min-height:56px;padding:8px;border:1px solid #d1d5db;border-radius:6px;font-size:13px"></textarea>
|
|
25
|
+
<input id="dp-dfr-days" type="number" min="1" placeholder="${t('缓交天数(选填,默认 30)')}" style="width:100%;padding:8px;border:1px solid #d1d5db;border-radius:6px;font-size:13px">
|
|
26
|
+
<button onclick="window.dpSubmitDeferral()" style="align-self:flex-start;padding:8px 16px;background:#2563eb;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer">${t('提交缓交申请')}</button>
|
|
27
|
+
</div>`
|
|
28
|
+
|
|
29
|
+
window.dpHydrateSellerDeferral = async () => {
|
|
30
|
+
const box = document.getElementById('dp-seller-deferral')
|
|
31
|
+
if (!box) return
|
|
32
|
+
const r = await GET('/direct-receive/deferral')
|
|
33
|
+
if (!r || r.error) { box.innerHTML = `<div style="font-size:12px;color:#dc2626">${window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试')}</div>`; return }
|
|
34
|
+
const d = r.deferral, active = r.active
|
|
35
|
+
// 无申请,或上一条已拒绝/过期(且当前无生效缓交)→ 可(重新)申请。
|
|
36
|
+
if (!d || (!active && (d.status === 'rejected' || d.status === 'expired'))) {
|
|
37
|
+
const prior = d ? `<div style="font-size:12px;color:#6b7280;margin-bottom:8px">${window.dpDeferralCopy()[d.status].icon} ${escHtml(window.dpDeferralCopy()[d.status].text)} · ${t('可重新申请')}</div>` : ''
|
|
38
|
+
box.innerHTML = prior + window.dpDeferralApplyForm()
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
const meta = window.dpDeferralCopy()[d.status] || { icon: 'ℹ️', text: d.status }
|
|
42
|
+
const lines = [`<div style="font-size:13px;font-weight:600;color:#374151">${meta.icon} ${escHtml(meta.text)}</div>`]
|
|
43
|
+
if (active) {
|
|
44
|
+
lines.push(`<div style="font-size:12px;color:#6b7280;line-height:1.7">${t('缓交期额度系数')}: <b>${active.reduced_quota_factor}</b></div>`)
|
|
45
|
+
if (active.expires_at) lines.push(`<div style="font-size:12px;color:#6b7280">${t('保证金到期日')}: ${escHtml(active.expires_at)}</div>`)
|
|
46
|
+
if (active.grace_until) lines.push(`<div style="font-size:12px;color:#6b7280">${t('宽限至')}: ${escHtml(active.grace_until)}</div>`)
|
|
47
|
+
if (active.in_grace) lines.push(`<div style="font-size:12px;color:#dc2626;font-weight:600">⚠️ ${t('已进入宽限期,请尽快补交履约保证金,否则直付资格将被暂停')}</div>`)
|
|
48
|
+
}
|
|
49
|
+
box.innerHTML = `<div style="display:flex;flex-direction:column;gap:4px">${lines.join('')}</div>`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
window.dpSubmitDeferral = async () => {
|
|
53
|
+
const reasonEl = document.getElementById('dp-dfr-reason'), daysEl = document.getElementById('dp-dfr-days')
|
|
54
|
+
const body = {}
|
|
55
|
+
if (reasonEl && reasonEl.value.trim()) body.reason = reasonEl.value.trim()
|
|
56
|
+
if (daysEl && daysEl.value) body.period_days = Number(daysEl.value)
|
|
57
|
+
const r = await POST('/direct-receive/deferral', body)
|
|
58
|
+
if (!r || r.error) { alert(window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试')); return }
|
|
59
|
+
toast$(t('缓交申请已提交,等待管理员审批'))
|
|
60
|
+
window.dpHydrateSellerDeferral()
|
|
61
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Direct Pay (Rail 1) — SELLER 平台服务费账户 section(PR-C)。UI ONLY。
|
|
2
|
+
// 卖家自查【本人】平台服务费预充值余额 / 已计提 / 退款 / 在途预估 / 首单宽限。
|
|
3
|
+
// 数据来自 GET /api/direct-receive/my-fee-account(requireSeller,仅本人;买家侧不展示)。
|
|
4
|
+
// 预充值 = 商家平台服务费预付款(非买家货款 / 非 escrow / 非保证金);充值由平台核实收款后由 admin 登记,卖家不自助。
|
|
5
|
+
// 纯读;不碰 wallet/escrow/settlement/refund/钱路。section 形态,嵌入卖家「设置」子页(与 readiness/缓交/验证/收款说明并列)。
|
|
6
|
+
|
|
7
|
+
function _dpFeeFmt2(u) { return (Number(u || 0) / 1e6).toFixed(6).replace(/\.?0+$/, '') + ' USDC' }
|
|
8
|
+
|
|
9
|
+
window.dpSellerFeeSection = () => `
|
|
10
|
+
<div class="card" style="margin-bottom:12px;border:1px solid #e5e7eb">
|
|
11
|
+
<div style="font-size:14px;font-weight:700;margin-bottom:4px">💰 ${t('平台服务费账户(仅你可见)')}</div>
|
|
12
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('直付平台服务费的预充值余额与已计提明细;预充值由平台核实收款后登记,如需充值请联系平台。')}</div>
|
|
13
|
+
<div id="dp-seller-fee">${typeof loading$ === 'function' ? loading$() : ''}</div>
|
|
14
|
+
</div>`
|
|
15
|
+
|
|
16
|
+
window.dpHydrateSellerFee = async () => {
|
|
17
|
+
const box = document.getElementById('dp-seller-fee')
|
|
18
|
+
if (!box) return
|
|
19
|
+
const r = await GET('/direct-receive/my-fee-account')
|
|
20
|
+
if (!r || r.error || !r.account) { box.innerHTML = `<div style="font-size:12px;color:#dc2626">${window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试')}</div>`; return }
|
|
21
|
+
const a = r.account
|
|
22
|
+
const owed = Number(a.availableUnits) < 0
|
|
23
|
+
const row = (k, v) => `<div style="display:flex;justify-content:space-between;font-size:13px;padding:3px 0"><span style="color:#6b7280">${k}</span><span style="font-weight:600">${v}</span></div>`
|
|
24
|
+
box.innerHTML = `
|
|
25
|
+
<div style="font-size:15px;font-weight:700;margin-bottom:6px">${owed ? t('待补平台服务费') : t('可用预充值余额')}: <span style="color:${owed ? '#dc2626' : '#059669'}">${_dpFeeFmt2(Math.abs(Number(a.availableUnits)))}</span></div>
|
|
26
|
+
${owed ? `<div style="font-size:12px;color:#b45309;line-height:1.6;margin-bottom:6px">${t('余额不足时新直付订单会被暂停,请联系平台补充平台服务费预充值。')}</div>` : ''}
|
|
27
|
+
${row(t('累计预充值'), _dpFeeFmt2(a.topupUnits))}
|
|
28
|
+
${row(t('已计提平台费'), _dpFeeFmt2(a.accruedUnits))}
|
|
29
|
+
${Number(a.adjustmentUnits) !== 0 ? row(t('账务更正合计'), _dpFeeFmt2(a.adjustmentUnits)) : ''}
|
|
30
|
+
${row(t('已退款合计'), _dpFeeFmt2(a.refundUnits))}
|
|
31
|
+
${row(t('在途单预估费'), _dpFeeFmt2(a.openEstFeeUnits))}
|
|
32
|
+
${a.graceEligible ? `<div style="font-size:12px;color:#059669;margin-top:6px">${t('首单宽限可用:你的第一笔直付无需预充值。')}</div>` : ''}`
|
|
33
|
+
}
|
|
@@ -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>`
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Direct Pay (Rail 1) — ADMIN 商户运营 hub + 平台服务费(预充值)账户。UI ONLY(PR-B)。
|
|
2
|
+
// hub 把散落的直付 admin 功能归类(资格合规 / 平台服务费 / 上线控制),只链接既有路由。
|
|
3
|
+
// fee 账户调既有 ROOT + 真人 Passkey 端点(admin-direct-receive-deposits):
|
|
4
|
+
// GET /admin/direct-receive/fee-account/:seller_id (ROOT 只读)
|
|
5
|
+
// POST /admin/direct-receive/fee-prepay (purpose direct_pay_fee_prepay_record)
|
|
6
|
+
// POST /admin/direct-receive/fee-adjust (purpose direct_pay_fee_adjust)
|
|
7
|
+
// POST /admin/direct-receive/fee-refund (purpose direct_pay_fee_refund)
|
|
8
|
+
// 钱相关写动作均 requestPasskeyGate(purpose, body) → POST(purpose_data 与 body 逐字绑定)。
|
|
9
|
+
// 不碰 buyer wallet/escrow/order/状态机;预充值 = 商家平台服务费预付款,非买家货款/escrow/保证金。
|
|
10
|
+
|
|
11
|
+
window.renderAdminDirectPayHub = function (app) {
|
|
12
|
+
if (!state.user) { renderLogin(); return }
|
|
13
|
+
if (!isAdmin()) { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限管理员')}</div>`, 'admin'); return }
|
|
14
|
+
const root = (state.user.admin_type || 'root') === 'root'
|
|
15
|
+
const grp = (title, cards) => `<div style="font-size:13px;font-weight:700;color:#374151;margin:14px 0 6px">${title}</div>${cards}`
|
|
16
|
+
app.innerHTML = shell(`
|
|
17
|
+
<h1 class="page-title">💳 ${t('Direct Pay 商户运营')}</h1>
|
|
18
|
+
<div style="margin-bottom:8px"><button class="btn btn-outline btn-sm" style="width:auto" onclick="navigate('#admin/protocol')">${t('返回协议管理')}</button></div>
|
|
19
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:6px">${t('直付(Rail 1)商户运营集中入口:资格合规、平台服务费预充值、上线控制。')}</div>
|
|
20
|
+
${root ? grp(t('资格与合规'),
|
|
21
|
+
adminLinkCard('🧾', t('商户合规录入'), t('KYB / 制裁筛查结论'), '#admin/compliance') +
|
|
22
|
+
adminLinkCard('🪙', t('履约保证金缓交审批'), t('缓交申请 + 压低额度'), '#admin/deferrals') +
|
|
23
|
+
adminLinkCard('🔖', t('逐产品直付验证'), t('核验商品外链验证码'), '#admin/product-verifications') +
|
|
24
|
+
adminLinkCard('🏬', t('店铺认证审核'), t('核验店铺外链 + 免逐品'), '#admin/store-verifications')) : ''}
|
|
25
|
+
${root ? grp(t('平台服务费(预充值)'),
|
|
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
|
+
${grp(t('上线控制'),
|
|
28
|
+
adminLinkCard('⚙️', t('直付参数'), t('开关 / 地区 / 单笔上限'), '#admin/params'))}
|
|
29
|
+
<div style="font-size:12px;color:#6b7280;margin-top:12px">${t('就绪报告(CLI):')} <code>npm run direct-pay:readiness</code></div>
|
|
30
|
+
`, 'admin')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// USDC 显示(units → 去尾零)
|
|
34
|
+
function _dpFeeFmt(u) { return (Number(u || 0) / 1e6).toFixed(6).replace(/\.?0+$/, '') + ' USDC' }
|
|
35
|
+
// 金额输入(USDC,最多 6 位小数)→ 整数 base-units(纯整数运算,无浮点四舍五入);松散/超精度输入 → NaN。
|
|
36
|
+
function _dpFeeUnits(id) {
|
|
37
|
+
const e = document.getElementById(id); const raw = e ? e.value.trim() : ''
|
|
38
|
+
if (!/^-?\d+(\.\d{1,6})?$/.test(raw)) return NaN // 拒 '1abc' / 超 6 位小数 / 空
|
|
39
|
+
const neg = raw[0] === '-', s = neg ? raw.slice(1) : raw
|
|
40
|
+
const [intp, frac = ''] = s.split('.')
|
|
41
|
+
const units = Number(intp) * 1e6 + Number((frac + '000000').slice(0, 6))
|
|
42
|
+
return neg ? -units : units
|
|
43
|
+
}
|
|
44
|
+
function _dpFeeStr(id) { const e = document.getElementById(id); return e ? e.value.trim() : '' }
|
|
45
|
+
|
|
46
|
+
window.renderAdminDirectPayFeeOps = function (app) {
|
|
47
|
+
if (!state.user) { renderLogin(); return }
|
|
48
|
+
if (!isAdmin()) { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限管理员')}</div>`, 'admin'); return }
|
|
49
|
+
if ((state.user.admin_type || 'root') !== 'root') { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限根管理员')}</div>`, 'admin'); return }
|
|
50
|
+
const inp = (id, ph, num) => `<input id="${id}" ${num ? 'type="number" step="any"' : ''} placeholder="${ph}" style="width:100%;padding:7px;border:1px solid #d1d5db;border-radius:6px;font-size:13px">`
|
|
51
|
+
const meth = (id) => `<select id="${id}" style="width:100%;padding:7px;border:1px solid #d1d5db;border-radius:6px;font-size:13px"><option value="usdc">USDC</option><option value="fiat">${t('法币 fiat')}</option></select>`
|
|
52
|
+
const card = (inner) => `<div class="card" style="margin-bottom:12px;border:1px solid #e5e7eb">${inner}</div>`
|
|
53
|
+
app.innerHTML = shell(`
|
|
54
|
+
<h1 class="page-title">💰 ${t('平台服务费预充值与账户')}</h1>
|
|
55
|
+
<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>
|
|
56
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:6px">${t('预充值 = 商家平台服务费预付款(非买家货款 / 非 escrow / 非保证金)。链下实际收款/退款由你核实后在此登记;均需真人 Passkey。')}</div>
|
|
57
|
+
${card(`<div style="font-size:13px;font-weight:700;margin-bottom:8px">${t('卖家用户 ID')}</div>${inp('fee-seller', t('卖家用户 ID(seller user id)'))}<button class="btn btn-primary btn-sm" style="margin-top:8px;width:auto" onclick="window.dpFeeLoad()">${t('加载账户')}</button>`)}
|
|
58
|
+
<div id="fee-acct"></div>
|
|
59
|
+
${card(`<div style="font-size:13px;font-weight:700;margin-bottom:8px">➕ ${t('记录预充值(收款)')}</div><div style="display:flex;flex-direction:column;gap:6px">${inp('fee-topup-amt', t('金额 USDC'), true)}${meth('fee-topup-method')}${inp('fee-topup-ev', t('收款凭证号 evidence_ref(选填)'))}${inp('fee-topup-note', t('备注(选填)'))}<button class="btn btn-primary btn-sm" style="align-self:flex-start;font-size:12px" onclick="window.dpFeeTopup()">${t('记录预充值(真人 Passkey)')}</button></div>`)}
|
|
60
|
+
${card(`<div style="font-size:13px;font-weight:700;margin-bottom:8px">✏️ ${t('账务更正(可正可负,非退款)')}</div><div style="display:flex;flex-direction:column;gap:6px">${inp('fee-adj-amt', t('更正额 USDC(负数=调减)'), true)}${inp('fee-adj-reason', t('原因(必填)'))}<button class="btn btn-outline btn-sm" style="align-self:flex-start;font-size:12px" onclick="window.dpFeeAdjust()">${t('记录更正(真人 Passkey)')}</button></div>`)}
|
|
61
|
+
${card(`<div style="font-size:13px;font-weight:700;margin-bottom:8px">↩️ ${t('退款(真实退还未消耗预付款)')}</div><div style="display:flex;flex-direction:column;gap:6px">${inp('fee-ref-amt', t('退款额 USDC'), true)}${meth('fee-ref-method')}${inp('fee-ref-ev', t('退款凭证号 evidence_ref(选填)'))}${inp('fee-ref-reason', t('原因(选填)'))}<button class="btn btn-outline btn-sm" style="align-self:flex-start;font-size:12px;color:#b45309;border-color:#fcd34d" onclick="window.dpFeeRefund()">${t('退款(真人 Passkey)')}</button></div>`)}
|
|
62
|
+
`, 'admin')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
window.dpFeeLoad = async function () {
|
|
66
|
+
const sid = _dpFeeStr('fee-seller'); const box = document.getElementById('fee-acct'); if (!box) return
|
|
67
|
+
if (!sid) { box.innerHTML = ''; return }
|
|
68
|
+
const r = await GET('/admin/direct-receive/fee-account/' + encodeURIComponent(sid))
|
|
69
|
+
if (!r || !r.account) { box.innerHTML = `<div class="alert alert-error">${t('加载失败')}</div>`; return }
|
|
70
|
+
const a = r.account
|
|
71
|
+
const row = (k, v) => `<div style="display:flex;justify-content:space-between;font-size:13px;padding:3px 0"><span style="color:#6b7280">${k}</span><span style="font-weight:600">${v}</span></div>`
|
|
72
|
+
box.innerHTML = `<div class="card" style="margin-bottom:12px;border:1px solid #e5e7eb">
|
|
73
|
+
<div style="font-size:15px;font-weight:700;margin-bottom:6px">${t('可用预充值余额')}: <span style="color:${a.availableUnits < 0 ? '#dc2626' : '#059669'}">${_dpFeeFmt(a.availableUnits)}</span></div>
|
|
74
|
+
${row(t('累计预充值'), _dpFeeFmt(a.topupUnits))}
|
|
75
|
+
${row(t('已计提平台费'), _dpFeeFmt(a.accruedUnits))}
|
|
76
|
+
${row(t('账务更正合计'), _dpFeeFmt(a.adjustmentUnits))}
|
|
77
|
+
${row(t('已退款合计'), _dpFeeFmt(a.refundUnits))}
|
|
78
|
+
${row(t('在途单预估费'), _dpFeeFmt(a.openEstFeeUnits))}
|
|
79
|
+
${row(t('可退款余额'), _dpFeeFmt(Math.max(0, Number(a.availableUnits) - Number(a.openEstFeeUnits))))}
|
|
80
|
+
${row(t('首单宽限'), a.graceEligible ? t('是(尚未成交)') : t('否(已用/有在途)'))}
|
|
81
|
+
</div>`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function _dpFeeAction(purpose, path, bind, extraBody, okMsg) {
|
|
85
|
+
const token = await requestPasskeyGate(purpose, bind)
|
|
86
|
+
if (!token) { if (typeof toast$ === 'function') toast$(t('需要真人 Passkey 确认'), 'error'); return }
|
|
87
|
+
const r = await POST(path, { ...bind, ...extraBody, webauthn_token: token })
|
|
88
|
+
if (!r || r.error) { if (typeof toast$ === 'function') toast$(window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试'), 'error'); return }
|
|
89
|
+
if (typeof toast$ === 'function') toast$(okMsg)
|
|
90
|
+
window.dpFeeLoad()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
window.dpFeeTopup = function () {
|
|
94
|
+
const seller_id = _dpFeeStr('fee-seller'); const amount_units = _dpFeeUnits('fee-topup-amt')
|
|
95
|
+
if (!seller_id || !(amount_units > 0)) { if (typeof toast$ === 'function') toast$(t('请填写卖家 ID 和正数金额'), 'error'); return }
|
|
96
|
+
const bind = { seller_id, amount_units, method: _dpFeeStr('fee-topup-method') || 'usdc', evidence_ref: _dpFeeStr('fee-topup-ev') }
|
|
97
|
+
return _dpFeeAction('direct_pay_fee_prepay_record', '/admin/direct-receive/fee-prepay', bind, { note: _dpFeeStr('fee-topup-note') }, t('预充值已记录'))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
window.dpFeeAdjust = function () {
|
|
101
|
+
const seller_id = _dpFeeStr('fee-seller'); const delta_units = _dpFeeUnits('fee-adj-amt'); const reason = _dpFeeStr('fee-adj-reason')
|
|
102
|
+
if (!seller_id || !Number.isFinite(delta_units) || delta_units === 0 || !reason) { if (typeof toast$ === 'function') toast$(t('请填写卖家 ID、非零更正额、原因'), 'error'); return }
|
|
103
|
+
const bind = { seller_id, delta_units, reason }
|
|
104
|
+
return _dpFeeAction('direct_pay_fee_adjust', '/admin/direct-receive/fee-adjust', bind, {}, t('账务更正已记录'))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
window.dpFeeRefund = function () {
|
|
108
|
+
const seller_id = _dpFeeStr('fee-seller'); const amount_units = _dpFeeUnits('fee-ref-amt')
|
|
109
|
+
if (!seller_id || !(amount_units > 0)) { if (typeof toast$ === 'function') toast$(t('请填写卖家 ID 和正数退款额'), 'error'); return }
|
|
110
|
+
const bind = { seller_id, amount_units, method: _dpFeeStr('fee-ref-method') || 'usdc', evidence_ref: _dpFeeStr('fee-ref-ev') }
|
|
111
|
+
return _dpFeeAction('direct_pay_fee_refund', '/admin/direct-receive/fee-refund', bind, { reason: _dpFeeStr('fee-ref-reason') }, t('退款已记录'))
|
|
112
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Direct Pay — 可验证付款附言(参考号)。UI ONLY;不碰钱路。
|
|
2
|
+
// 每单从订单号派生【确定性唯一参考号】,提示买家付款时填入收款附言/备注 → mark_paid 的 note 回填订单时间线
|
|
3
|
+
// (买卖双方可见),相同金额也能靠参考号区分付款方。协议不验证,仅辅助对账。
|
|
4
|
+
// 参考号【只读 + 一键复制】:锁定派生值买家不可改 —— 防"两单复用同一参考号"骗卖家重复确认;中文 t()/英文 i18n.js _EN。
|
|
5
|
+
window.dpPayRef = (orderId) => 'WAZ-' + String(orderId || '').replace(/[^a-zA-Z0-9]/g, '').slice(-8).toUpperCase()
|
|
6
|
+
window.dpMemoInputHtml = (orderId) => {
|
|
7
|
+
const ref = window.dpPayRef(orderId)
|
|
8
|
+
return `<div style="margin-top:8px;border-top:1px dashed #fde68a;padding-top:8px">
|
|
9
|
+
<div style="font-size:11px;color:#92400e;font-weight:600;margin-bottom:3px">${t('付款时请在附言/备注填入(便于卖家核对)')}</div>
|
|
10
|
+
<div style="display:flex;align-items:center;gap:6px"><code id="dp-buyer-memo" style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;font-size:13px;font-weight:700;letter-spacing:0.5px;background:#fffbeb;border:1px solid #fde68a;border-radius:6px;padding:6px 8px">${escHtml(ref)}</code>${window.dpCopyBtn ? window.dpCopyBtn(ref) : ''}</div>
|
|
11
|
+
<div style="font-size:10px;color:#9ca3af;margin-top:2px">${t('此参考号系统生成、不可修改;相同金额时卖家靠它区分付款方,标记"我已付款"时自动记入订单流程。')}</div></div>`
|
|
12
|
+
}
|
|
13
|
+
// "我已付款"时:note 恒为【派生参考号】(不再读可改输入框)—— 卖家对账口径唯一,买家无法用别单参考号冒充。
|
|
14
|
+
window.dpReadMemo = (orderId) => `${t('付款参考')}: ${window.dpPayRef(orderId)}`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Direct Pay 货款协商(payment_query)+ 付款窗过期(direct_expired_unconfirmed)前端 —— 标签/动作/说明卡。UI ONLY。
|
|
2
|
+
// payment_query = 双方【协商】非仲裁;expired = 窗口超时未标记付款(宽限期内买家仍可主张已付或确认关单,审计项 H:
|
|
3
|
+
// 此前该状态零 UI 动作=死角)。动作走通用 /orders/:id/action;非托管无退款语义。中文 t()/英文 i18n.js _EN。
|
|
4
|
+
window.dpNegotiationBadge = (status) => status === 'payment_query' ? ['yellow', t('货款协商中')] : null
|
|
5
|
+
window.dpNegotiationLabel = (status) => status === 'payment_query' ? t('货款协商中(非仲裁)') : null
|
|
6
|
+
|
|
7
|
+
// 订单页按状态/角色返回协商动作(仅 direct_p2p 调用)。返回数组=接管;null=交回默认。
|
|
8
|
+
window.dpNegotiationActions = (order, isBuyer, isSeller) => {
|
|
9
|
+
const s = order.status
|
|
10
|
+
if (s === 'payment_query') {
|
|
11
|
+
if (isSeller) return [
|
|
12
|
+
{ action: 'confirm_received', label: '确认已收到货款(恢复订单)', style: 'success' },
|
|
13
|
+
{ action: 'request_cancel', label: '申请取消订单(买家未回应时)', style: 'secondary' },
|
|
14
|
+
]
|
|
15
|
+
if (isBuyer) return [
|
|
16
|
+
{ action: 'pq_escalate', label: '我已付款 · 提交凭证升级举证', style: 'primary', needsEvidence: true, noteLabel: '付款凭证 / 参考号', evidencePlaceholder: '链上 tx / 银行回执 / 付款参考号(与下单时附言一致)' },
|
|
17
|
+
{ action: 'cancel', label: '我未付款 · 取消订单', style: 'secondary' },
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
// 审计项 H:付款窗过期(宽限期)买家双出口 —— 卡点付款的主张已付(证据升级争议),没付的即刻关单(免等 48h 自动取消)
|
|
21
|
+
if (s === 'direct_expired_unconfirmed' && isBuyer) return [
|
|
22
|
+
{ action: 'dispute', label: '我已付款 · 提交凭证发起争议', style: 'primary', needsEvidence: true, noteLabel: '付款凭证 / 参考号', evidencePlaceholder: '链上 tx / 银行回执 / 付款参考号(与下单时附言一致)' },
|
|
23
|
+
{ action: 'cancel', label: '我未付款 · 关闭订单', style: 'secondary' },
|
|
24
|
+
]
|
|
25
|
+
// 撤回仲裁【仅限货款协商升级】:后端 DTO can_withdraw_payment_query_dispute 判定;履约类争议拿不到此按钮。
|
|
26
|
+
if (s === 'disputed' && (isBuyer || isSeller) && order.can_withdraw_payment_query_dispute) return [
|
|
27
|
+
{ action: 'pq_withdraw', label: '撤回仲裁 · 回到协商', style: 'secondary' },
|
|
28
|
+
]
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 协商说明卡(payment_query 时展示):解释状态 + 提示买家复述付款参考。
|
|
33
|
+
window.dpNegotiationCard = (order) => (!order || order.status !== 'payment_query') ? '' : `
|
|
34
|
+
<div class="card" style="border:1px solid #fde68a;background:linear-gradient(135deg,#fffbeb,#fef3c7)"><div style="font-size:13px;font-weight:700;color:#92400e;margin-bottom:6px">🔎 ${t('货款协商中(非仲裁)')}</div>
|
|
35
|
+
<div style="font-size:12px;color:#374151;line-height:1.7">${t('卖家报告未收到货款。这是买卖双方【协商】阶段,不是仲裁:若你确已付款,请提交付款凭证升级举证;若未付款,可取消订单。谈不拢再进举证仲裁。直付非托管,WebAZ 不代收/不退款。')}</div></div>`
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Direct Pay (Rail 1) — 付款时刻 UI 辅助。UI ONLY;不碰钱路。dpPayAmountText(order):应付金额纯文本,
|
|
2
|
+
// 用于 D2 弹窗/风险确认弹窗/订单收款说明框。审计项 E:优先【下单时冻结的应付参考换算】(snapshot payable_* —
|
|
3
|
+
// 买家/卖家/时间线同一稳定数字,不随实时汇率漂移;标注"下单时参考,以卖家收款说明为准");旧单无快照 → 回落
|
|
4
|
+
// 实时换算(dpFxInCurrency);再无 → 仅 USDC。USDC/USD 账户:另附买家本地法币参考(_fxLocal)。中英 i18n parity。
|
|
5
|
+
window.dpPayAmountText = (order) => {
|
|
6
|
+
if (!order) return ''
|
|
7
|
+
const usdc = Number(order.total_amount)
|
|
8
|
+
const s = Number.isFinite(usdc) ? (Number.isInteger(usdc) ? String(usdc) : usdc.toFixed(2)) : '—'
|
|
9
|
+
let snap = {}; try { snap = JSON.parse(order.direct_pay_account_snapshot || '{}') || {} } catch {}
|
|
10
|
+
const cur = String(snap.currency || '').toUpperCase()
|
|
11
|
+
if (!cur || cur === 'USDC' || cur === 'USD') { const loc = window._fxLocal ? window._fxLocal(usdc) : ''; return `${t('应付')} ${s} USDC${loc ? ' ≈ ' + loc : ''}` }
|
|
12
|
+
if (Number.isFinite(Number(snap.payable_approx))) return `${t('应付')} ≈ ${cur} ${Number(snap.payable_approx).toFixed(2)}(${s} USDC · ${t('下单时参考价,以卖家收款说明为准')})`
|
|
13
|
+
const fx = window.dpFxInCurrency ? window.dpFxInCurrency(usdc, cur) : ''
|
|
14
|
+
return (fx && fx !== cur) ? `${t('应付')} ≈ ${fx}(${s} USDC)` : `${t('应付')} ${s} USDC · ${cur}`
|
|
15
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Direct Pay (Rail 1) — 融合付款弹窗。UI ONLY;不碰钱路(mark_paid 仍走 dpHandleAction 的 Passkey 门)。
|
|
2
|
+
// 两次风险披露完成后弹出:把【收款账号/说明 · 应付金额 · 付款附言(参考号)】三要素融合展示,每项一键复制
|
|
3
|
+
// (买家直接抄进银行/收款App),付款后就地点"我已付款"→ dpHandleAction('mark_paid')(不新增 Passkey 次数,
|
|
4
|
+
// 省掉"回订单页找按钮"一步)。也可"稍后处理"→ 订单页(同样能标记付款)。中文 t(),英文 i18n.js _EN。
|
|
5
|
+
// 复制用【纯数字+币种】(避免抄进 "应付 ≈ …" 整句):有下单冻结 payable 用其币种+值,否则 USDC 总额。
|
|
6
|
+
window.dpPayModalCopyAmount = (order) => {
|
|
7
|
+
try { const s = JSON.parse(order.direct_pay_account_snapshot || '{}'); if (Number.isFinite(Number(s.payable_approx)) && s.payable_currency) return s.payable_currency + ' ' + Number(s.payable_approx).toFixed(2) } catch {} return String(order.total_amount) + ' USDC'
|
|
8
|
+
}
|
|
9
|
+
window.dpShowPaymentModal = (order) => {
|
|
10
|
+
if (!order || !order.id) return
|
|
11
|
+
const oid = order.id
|
|
12
|
+
const instr = order.direct_pay_instruction_snapshot || ''
|
|
13
|
+
const amtText = window.dpPayAmountText ? window.dpPayAmountText(order) : (String(order.total_amount) + ' USDC') // 展示串:复用 #225 唯一真相(含 ≈/下单参考价 caveat/FX 回落),防三处漂移
|
|
14
|
+
const amtCopy = window.dpPayModalCopyAmount(order)
|
|
15
|
+
const ref = window.dpPayRef ? window.dpPayRef(oid) : ''
|
|
16
|
+
const row = (label, valueHtml, copyText) => `<div style="margin-bottom:10px">
|
|
17
|
+
<div style="font-size:11px;color:#6b7280;margin-bottom:3px">${label}${window.dpCopyBtn ? window.dpCopyBtn(copyText) : ''}</div>
|
|
18
|
+
<div style="font-size:13px;color:#111827;background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;padding:8px 10px;white-space:pre-wrap;word-break:break-word">${valueHtml}</div></div>`
|
|
19
|
+
window._openModal(`
|
|
20
|
+
<h2 style="font-size:16px;font-weight:700;margin:0 0 4px">💸 ${t('请按以下信息付款')}</h2>
|
|
21
|
+
<div style="font-size:12px;color:#92400e;background:#fffbeb;border:1px solid #fde68a;border-radius:8px;padding:8px 10px;margin-bottom:12px">${t('风险确认已完成。这是卖家收款信息(下单时快照,WebAZ 不验证也不经手)。请【场外】完成付款后回来标记。')}</div>
|
|
22
|
+
${instr ? row(t('收款账号 / 说明'), escHtml(instr), instr) : ''}
|
|
23
|
+
${row(t('应付金额'), escHtml(amtText), amtCopy)}
|
|
24
|
+
${row(t('付款附言(务必填写,用于卖家核对)'), '<b>' + escHtml(ref) + '</b>', ref)}
|
|
25
|
+
<div style="font-size:11px;color:#9ca3af;margin:6px 0 12px">${t('付款时请在银行/收款App的附言/备注里填入上面的参考号;完成付款后点下方按钮(需 Passkey)。')}</div>
|
|
26
|
+
<div style="display:flex;gap:8px">
|
|
27
|
+
<button class="btn btn-outline" style="flex:1" onclick="dpPayModalLater('${escHtml(oid)}')">${t('稍后在订单页处理')}</button>
|
|
28
|
+
<button class="btn btn-primary" style="flex:1;background:#16a34a;border-color:#16a34a" onclick="dpPayModalMarkPaid('${escHtml(oid)}')">${t('我已付款')}</button>
|
|
29
|
+
</div>`)
|
|
30
|
+
}
|
|
31
|
+
window.dpPayModalLater = (oid) => { if (typeof closeModal === 'function') closeModal(); navigate('#order/' + oid) }
|
|
32
|
+
window.dpPayModalMarkPaid = async (oid) => { if (typeof closeModal === 'function') closeModal(); await window.dpHandleAction(oid, 'mark_paid') } // mark_paid 自带 Passkey 门 + 成功后跳订单页
|