@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,103 @@
|
|
|
1
|
+
// Direct Pay (Rail 1) — 按产品认证 UI (PR-④b)。UI ONLY。两个面:
|
|
2
|
+
// ① SELLER 自助(seller workbench settings):逐产品申领验证码 → 贴到外部商品页 → 提交该产品外链 → 看逐产品状态。
|
|
3
|
+
// ② ADMIN 审核队列(#admin/product-verifications):打开卖家提交的外链手动核对验证码,verify/reject(ROOT + 真人 Passkey)。
|
|
4
|
+
// 硬门:每个产品都须【单独】被 admin 核验才可直付;一次验证绝不放行所有产品。诚实:WebAZ 不抓取链接,admin 手动核。
|
|
5
|
+
// 不碰 wallet/escrow/settlement/refund;只调既有受门控端点。
|
|
6
|
+
|
|
7
|
+
// ── 状态 → 双语文案 ──
|
|
8
|
+
window.dpPvCopy = () => ({
|
|
9
|
+
issued: { icon: '①', text: t('已签发验证码,请贴到该商品的外部页面后提交链接') },
|
|
10
|
+
submitted: { icon: '⏳', text: t('已提交,等待管理员核验') },
|
|
11
|
+
verified: { icon: '✅', text: t('已通过验证,可直付') },
|
|
12
|
+
rejected: { icon: '🚫', text: t('未通过,请修正后重新申请') },
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// ══════ SELLER 自助面板 ══════
|
|
16
|
+
window.dpSellerProductVerifySection = () => `
|
|
17
|
+
<div class="card" style="margin-bottom:12px;border:1px solid #e5e7eb">
|
|
18
|
+
<div style="font-size:14px;font-weight:700;margin-bottom:4px">🔖 ${t('逐产品直付验证(仅你可见)')}</div>
|
|
19
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('每个走直付收款的商品都需【单独】通过平台人工验证(防作弊):申领验证码 → 贴到该商品的外部平台页面 → 回来提交该商品链接 → 管理员手动核对。未验证的商品只能用托管交易。')}</div>
|
|
20
|
+
<div id="dp-seller-pv">${loading$()}</div>
|
|
21
|
+
</div>`
|
|
22
|
+
|
|
23
|
+
window.dpHydrateSellerProductVerify = async () => {
|
|
24
|
+
const box = document.getElementById('dp-seller-pv')
|
|
25
|
+
if (!box) return
|
|
26
|
+
const [products, vres] = await Promise.all([GET('/my-products'), GET('/direct-receive/product-verifications')])
|
|
27
|
+
if (!Array.isArray(products) || !vres || vres.error) { box.innerHTML = `<div style="font-size:12px;color:#dc2626">${window.dpErrorText ? window.dpErrorText(vres && vres.error_code, vres && vres.error) : t('操作失败,请重试')}</div>`; return }
|
|
28
|
+
const byProduct = {}; for (const v of (vres.verifications || [])) { if (!byProduct[v.product_id]) byProduct[v.product_id] = v } // 最新在前
|
|
29
|
+
const copy = window.dpPvCopy()
|
|
30
|
+
if (!products.length) { box.innerHTML = `<div style="font-size:12px;color:#9ca3af">${t('你还没有商品')}</div>`; return }
|
|
31
|
+
box.innerHTML = products.map(p => {
|
|
32
|
+
const v = byProduct[p.id]
|
|
33
|
+
const meta = v ? (copy[v.status] || { icon: 'ℹ️', text: v.status }) : null
|
|
34
|
+
let action = ''
|
|
35
|
+
if (!v || v.status === 'rejected') {
|
|
36
|
+
action = `<button onclick="window.dpRequestProductVerify('${p.id}')" style="padding:6px 12px;background:#2563eb;color:#fff;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer">${t('申请验证')}</button>`
|
|
37
|
+
} else if (v.status === 'issued') {
|
|
38
|
+
action = `
|
|
39
|
+
<div style="font-size:12px;color:#374151;margin-bottom:6px">${t('验证码')}: <code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-weight:700">${escHtml(v.code)}</code> · ${t('把它展示在该商品的外部页面')}</div>
|
|
40
|
+
<input id="dp-pv-url-${p.id}" type="url" placeholder="${t('该商品的外部链接(http/https)')}" style="width:100%;padding:6px;border:1px solid #d1d5db;border-radius:4px;font-size:12px;margin-bottom:6px">
|
|
41
|
+
<button onclick="window.dpSubmitProductVerify('${p.id}')" style="padding:6px 12px;background:#2563eb;color:#fff;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer">${t('提交链接')}</button>`
|
|
42
|
+
}
|
|
43
|
+
return `<div style="padding:8px 0;border-top:1px solid #f3f4f6">
|
|
44
|
+
<div style="font-size:13px;font-weight:600;color:#374151">${escHtml(p.title || p.id)}</div>
|
|
45
|
+
${meta ? `<div style="font-size:12px;color:#6b7280;margin:4px 0">${meta.icon} ${escHtml(meta.text)}</div>` : ''}
|
|
46
|
+
${action}
|
|
47
|
+
</div>`
|
|
48
|
+
}).join('')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
window.dpRequestProductVerify = async (productId) => {
|
|
52
|
+
const r = await POST('/direct-receive/product-verification', { product_id: productId })
|
|
53
|
+
if (!r || r.error) { if (typeof toast$ === 'function') toast$(window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试'), 'error'); return }
|
|
54
|
+
if (typeof toast$ === 'function') toast$(t('验证码已签发'))
|
|
55
|
+
window.dpHydrateSellerProductVerify()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
window.dpSubmitProductVerify = async (productId) => {
|
|
59
|
+
const el = document.getElementById('dp-pv-url-' + productId)
|
|
60
|
+
const url = el && el.value ? el.value.trim() : ''
|
|
61
|
+
if (!url) { if (typeof toast$ === 'function') toast$(t('请填写该商品的外部链接'), 'error'); return }
|
|
62
|
+
const r = await PUT('/direct-receive/product-verification', { product_id: productId, external_url: url })
|
|
63
|
+
if (!r || r.error) { if (typeof toast$ === 'function') toast$(window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试'), 'error'); return }
|
|
64
|
+
if (typeof toast$ === 'function') toast$(t('已提交,等待管理员核验'))
|
|
65
|
+
window.dpHydrateSellerProductVerify()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ══════ ADMIN 审核队列 ══════
|
|
69
|
+
window.renderAdminProductVerifications = async function (app) {
|
|
70
|
+
if (!state.user) { renderLogin(); return }
|
|
71
|
+
if (!isAdmin()) { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限管理员')}</div>`, 'admin'); return }
|
|
72
|
+
app.innerHTML = shell(loading$(), 'admin')
|
|
73
|
+
const data = await GET('/admin/direct-receive/product-verifications?status=submitted')
|
|
74
|
+
if (!data || data.error) { app.innerHTML = shell(alert$('error', (data && data.error) || t('加载失败')), 'admin'); return }
|
|
75
|
+
const rows = data.verifications || []
|
|
76
|
+
const list = rows.length ? rows.map(v => `
|
|
77
|
+
<div class="card" style="margin-bottom:10px">
|
|
78
|
+
<div style="font-size:11px;color:#9ca3af">${escHtml(v.seller_id)} · ${escHtml(v.product_id)} · ${fmtTime(v.created_at)}</div>
|
|
79
|
+
<div style="font-size:13px;margin-top:6px">${t('验证码')}: <code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-weight:700">${escHtml(v.code)}</code>${v.platform ? ` · ${escHtml(v.platform)}` : ''}</div>
|
|
80
|
+
<div style="font-size:12px;margin-top:4px">${t('外部链接')}: ${v.external_url ? `<a href="${encodeURI(v.external_url)}" target="_blank" rel="noopener noreferrer nofollow" style="color:#2563eb;text-decoration:underline;word-break:break-all">${escHtml(v.external_url)}</a>` : '—'}</div>
|
|
81
|
+
<div style="font-size:11px;color:#9ca3af;margin-top:4px">${t('请手动打开链接,确认该商品页面上展示了上面的验证码,再决定通过/拒绝。')}</div>
|
|
82
|
+
<div style="display:flex;gap:6px;margin-top:10px">
|
|
83
|
+
<button class="btn btn-primary btn-sm" style="font-size:12px" onclick="window.doReviewProductVerify('${v.id}','verified')">✓ ${t('通过(真人 Passkey)')}</button>
|
|
84
|
+
<button class="btn btn-sm" style="font-size:12px;background:#dc2626;border-color:#dc2626;color:#fff" onclick="window.doReviewProductVerify('${v.id}','rejected')">✗ ${t('拒绝(真人 Passkey)')}</button>
|
|
85
|
+
</div>
|
|
86
|
+
</div>`).join('') : `<div class="empty"><div class="empty-icon">📥</div><div class="empty-text">${t('暂无待核验商品')}</div></div>`
|
|
87
|
+
app.innerHTML = shell(`
|
|
88
|
+
<h1 class="page-title">🔖 ${t('逐产品直付验证审核')}</h1>
|
|
89
|
+
<div style="margin-bottom:12px"><button class="btn btn-outline btn-sm" style="width:auto" onclick="navigate('#admin')">${t('返回概览')}</button></div>
|
|
90
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('每个商品需单独核验:打开卖家提交的外部链接,确认页面展示了验证码再通过。通过=该商品可直付(逐品,绝不放行全部)。通过/拒绝均需真人 Passkey。')}</div>
|
|
91
|
+
${list}
|
|
92
|
+
`, 'admin')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// purpose_data 绑 verification_id + decision(签 A 用 B / 改结论一律拒)。
|
|
96
|
+
window.doReviewProductVerify = async function (id, decision) {
|
|
97
|
+
const token = await requestPasskeyGate('direct_pay_product_verify', { verification_id: id, decision })
|
|
98
|
+
if (!token) { if (typeof toast$ === 'function') toast$(t('需要真人 Passkey 确认'), 'error'); return }
|
|
99
|
+
const r = await POST(`/admin/direct-receive/product-verifications/${id}/review`, { decision, webauthn_token: token })
|
|
100
|
+
if (!r || r.error) { if (typeof toast$ === 'function') toast$(window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试'), 'error'); return }
|
|
101
|
+
if (typeof toast$ === 'function') toast$(decision === 'verified' ? t('该商品已通过验证') : t('该商品已拒绝'))
|
|
102
|
+
window.renderAdminProductVerifications(document.getElementById('app'))
|
|
103
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Direct Pay (Rail 1) — SELLER de-identified readiness panel (PR readiness-seller-ui)。UI ONLY。
|
|
2
|
+
// 只渲染【卖家可行动 / 脱敏状态】项(收款说明 / Passkey / 履约保证金 / 商户审核 / 暂停 / 平台开放)。
|
|
3
|
+
// 数据来自 GET /api/direct-receive/readiness(后端已脱敏:绝不下发 raw blocker、也绝不暴露 KYB/制裁/AML 分项)。
|
|
4
|
+
// 前端只做 code→卖家可读双语文案 的映射,绝不展示原始内部判定码。Direct Pay 仍 non-launchable / fail-closed。
|
|
5
|
+
// 不碰 wallet/escrow/settlement/refund/钱路;不开真实 rail;不声称已上线。买家侧不展示本面板。
|
|
6
|
+
|
|
7
|
+
// 内部 code → 卖家安全双语文案(ok / 未达成 两态 + 可行动入口)。绝不出现 KYB/sanctions/AML 字样。
|
|
8
|
+
window.dpSellerReadinessCopy = () => ({
|
|
9
|
+
PLATFORM_OPEN: { ok: t('平台侧直付已开放'), no: t('直付平台侧暂未开放(无需你操作)') },
|
|
10
|
+
PAYMENT_INSTRUCTION: { ok: t('已设置收款说明'), no: t('未设置收款说明'), action: t('去设置收款说明'), href: '#seller/settings' },
|
|
11
|
+
PASSKEY: { ok: t('已注册 Passkey'), no: t('未注册 Passkey'), action: t('去注册 Passkey'), href: '#me' },
|
|
12
|
+
BASE_BOND: { ok: t('履约保证金已完成'), no: t('履约保证金未完成') },
|
|
13
|
+
COMPLIANCE_REVIEW: { ok: t('商户审核已通过'), no: t('商户审核进行中或未通过') },
|
|
14
|
+
NOT_SUSPENDED: { ok: t('直付资格正常'), no: t('直付资格已被暂停') },
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
window.dpSellerReadinessSection = () => `
|
|
18
|
+
<div class="card" style="margin-bottom:12px;border:1px solid #e5e7eb">
|
|
19
|
+
<div style="font-size:14px;font-weight:700;margin-bottom:4px">🧭 ${t('直付开通进度(仅你可见)')}</div>
|
|
20
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('以上为你的直付开通进度;直付按轨道分阶段开放,完成可行动项不代表立即可用。')}</div>
|
|
21
|
+
<div id="dp-seller-readiness">${loading$()}</div>
|
|
22
|
+
</div>`
|
|
23
|
+
|
|
24
|
+
window.dpHydrateSellerReadiness = async () => {
|
|
25
|
+
const box = document.getElementById('dp-seller-readiness')
|
|
26
|
+
if (!box) return
|
|
27
|
+
const r = await GET('/direct-receive/readiness')
|
|
28
|
+
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 }
|
|
29
|
+
const copy = window.dpSellerReadinessCopy()
|
|
30
|
+
const rows = (r.items || []).map(it => {
|
|
31
|
+
const c = copy[it.code]; if (!c) return ''
|
|
32
|
+
const icon = it.ok ? '✅' : (it.actionable ? '⚠️' : '⏳')
|
|
33
|
+
const text = it.ok ? c.ok : c.no
|
|
34
|
+
const act = (!it.ok && it.actionable && c.action) ? ` · <a href="${c.href}" style="color:#2563eb;font-weight:600;text-decoration:underline">${c.action}</a>` : ''
|
|
35
|
+
return `<li style="display:flex;gap:8px;align-items:flex-start;font-size:13px;line-height:1.7;color:#374151"><span>${icon}</span><span>${escHtml(text)}${act}</span></li>`
|
|
36
|
+
}).join('')
|
|
37
|
+
box.innerHTML = `<ul style="margin:0;padding-left:2px;list-style:none">${rows}</ul>`
|
|
38
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// 直付卖家对账卡(审计项 F)。UI ONLY;不碰钱路。买家 mark_paid 后(accepted,发货前)在卖家视角显性化
|
|
2
|
+
// 对账三要素:①期望参考号(dpPayRef 派生,银行流水附言应恰为此值,一键复制便于流水搜索)②应付金额
|
|
3
|
+
// (E 冻结的 payable 快照,与买家所见同一数字)③同买家同金额在途多单告警(DTO duplicate_amount_alert,
|
|
4
|
+
// 与 mark_paid D2 时间线预警同口径 —— 防一笔转账冒充两单)。买家声称的参考在时间线(mark_paid note)可见。
|
|
5
|
+
// 发货即确认收款(D4 弹窗把关),此卡把核对动作前置到打开订单页的每一刻。中文 t(),英文 i18n.js _EN。
|
|
6
|
+
window.dpReconcileCard = (order, isSeller) => {
|
|
7
|
+
if (!order || !isSeller || order.payment_rail !== 'direct_p2p' || order.status !== 'accepted') return ''
|
|
8
|
+
const ref = window.dpPayRef ? window.dpPayRef(order.id) : ''
|
|
9
|
+
const pay = window.dpPayAmountText ? window.dpPayAmountText(order) : ''
|
|
10
|
+
const dup = Number(order.duplicate_amount_alert) || 0
|
|
11
|
+
return `<div class="card" style="border:1px solid #bbf7d0;background:linear-gradient(135deg,#f0fdf4,#dcfce7)">
|
|
12
|
+
<div style="font-size:13px;font-weight:700;color:#166534;margin-bottom:4px">🧾 ${t('发货前对账(买家已标记付款)')}</div>
|
|
13
|
+
<div style="font-size:12px;color:#374151;line-height:1.9">
|
|
14
|
+
<div>${t('银行/收款App流水附言应为')} <code style="font-size:13px;font-weight:700;background:#fff;border:1px solid #bbf7d0;border-radius:6px;padding:2px 6px">${escHtml(ref)}</code>${window.dpCopyBtn ? window.dpCopyBtn(ref) : ''}</div>
|
|
15
|
+
${pay ? `<div>💸 ${escHtml(pay)}</div>` : ''}
|
|
16
|
+
${dup > 0 ? `<div style="color:#b91c1c;font-weight:600">⚠️ ${t('同买家另有')} ${dup} ${t('笔同金额直付订单在途 —— 每笔转账只能核销一个订单,请逐单核对参考号,谨防一笔款冒充多单。')}</div>` : ''}
|
|
17
|
+
<div style="font-size:11px;color:#6b7280">${t('请核实款项【已到账】且附言/金额与本单一致再发货;发货即视为确认收款。未收到请点"未收到货款"。')}</div>
|
|
18
|
+
</div></div>`
|
|
19
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// 直付(direct_p2p)送达后退货·场外退款握手前端 —— 退货骨架 UI 在 app.js renderReturnWidgetForOrder,
|
|
2
|
+
// 本模块只提供直付分支的 hooks(状态标签/握手动作块/升级判定与文案)。UI ONLY,真正边界在后端
|
|
3
|
+
// routes/direct-pay-returns.ts + 域模块。中文 t(),英文 i18n.js _EN。库存绝不自动恢复(退货验收上架)。
|
|
4
|
+
|
|
5
|
+
window.dpReturnStatusLabels = () => ({ await_refund: t('待卖家场外退款'), refund_marked: t('卖家已声明退款 · 待你确认') })
|
|
6
|
+
window.dpReturnStatusColors = () => ({ await_refund: '#d97706', refund_marked: '#2563eb', accepted_pickup_pending: '#16a34a', picked_up: '#2563eb' })
|
|
7
|
+
|
|
8
|
+
// 握手动作块:卖家在 await_refund 声明退款;买家在 refund_marked 确认(Passkey)。其余状态返回 ''。
|
|
9
|
+
window.dpReturnHandshake = (item, isBuyer, isSellerView, order) => {
|
|
10
|
+
if (!order || order.payment_rail !== 'direct_p2p') return ''
|
|
11
|
+
const note = `<div style="font-size:11px;color:#92400e;background:#fffbeb;border:1px solid #fde68a;border-radius:6px;padding:6px 8px;margin-bottom:8px">${t('直付非托管:退款由卖家在协议外完成,平台只记录握手,不经手货款。')}</div>`
|
|
12
|
+
if (isSellerView && item.status === 'await_refund') {
|
|
13
|
+
return `${note}<div style="font-size:12px;color:#374151;margin-bottom:6px">${t('你已同意退货。请在协议外向买家退款,然后声明;买家确认后退货完成。')}</div>
|
|
14
|
+
<input id="dp-ret-ref-${item.id}" class="form-control" maxlength="200" placeholder="${t('退款参考(转账单号等,可选,买家可见)')}" style="margin-bottom:8px;font-size:12px">
|
|
15
|
+
<button class="btn btn-primary btn-sm" style="width:auto;font-size:12px" onclick="dpReturnMarkRefunded('${item.id}','${order.id}')">${t('我已退款')}</button>`
|
|
16
|
+
}
|
|
17
|
+
if (isBuyer && item.status === 'await_refund') {
|
|
18
|
+
return `${note}<div style="font-size:12px;color:#6b7280;margin-bottom:8px">⏳ ${t('卖家已同意退货,等待其在协议外向你退款并声明。超期未退款可升级争议。')}</div>`
|
|
19
|
+
}
|
|
20
|
+
if (isBuyer && item.status === 'refund_marked') {
|
|
21
|
+
return `${note}<div style="font-size:12px;color:#374151;margin-bottom:8px">💸 ${t('卖家已声明退款')}${item.refund_reference ? '「' + escHtml(item.refund_reference) + '」' : ''}。${t('请先核实退款已到账;未收到请勿确认,可发起争议。')}</div>
|
|
22
|
+
<button class="btn btn-primary btn-sm" style="width:auto;font-size:12px" onclick="dpReturnConfirmRefund('${item.id}','${order.id}')">${t('已收到退款,完成退货(需 Passkey)')}</button>`
|
|
23
|
+
}
|
|
24
|
+
if (isSellerView && item.status === 'refund_marked') {
|
|
25
|
+
return `${note}<div style="font-size:12px;color:#6b7280">⏳ ${t('你已声明退款,等待买家确认收到。提示:退回货物须验收后手动上架,库存不会自动恢复。')}</div>`
|
|
26
|
+
}
|
|
27
|
+
return ''
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 升级争议判定(与后端 directPayReturnEscalatable 同口径;窗口天数由后端最终裁决,前端只做展示预判)
|
|
31
|
+
window.dpReturnCanEscalate = (item, order) => {
|
|
32
|
+
if (!order || order.payment_rail !== 'direct_p2p') return false
|
|
33
|
+
if (item.status === 'refund_marked') return true
|
|
34
|
+
if (item.status === 'await_refund' && item.await_refund_since) return (Date.now() - new Date(String(item.await_refund_since).replace(' ', 'T') + 'Z').getTime()) >= 5 * 86400 * 1000
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
window.dpReturnEscalateHint = (item) => item.status === 'refund_marked'
|
|
38
|
+
? t('若卖家声明退款但你并未收到,可升级至平台仲裁(直付为信誉裁决)')
|
|
39
|
+
: t('卖家同意退货后超期未退款 — 可升级至平台仲裁(直付为信誉裁决)')
|
|
40
|
+
|
|
41
|
+
window.dpReturnMarkRefunded = async (rid, oid) => {
|
|
42
|
+
if (typeof confirmModal === 'function' && !(await confirmModal(t('确认你已在协议外向买家完成退货退款?声明后买家确认即完成;虚假声明将留痕并可被追责。'), t('我已退款'), { danger: true }))) return
|
|
43
|
+
const el = document.getElementById('dp-ret-ref-' + rid)
|
|
44
|
+
const r = await POST('/return-requests/' + rid + '/mark-refunded', { refund_reference: (el && el.value || '').trim() })
|
|
45
|
+
if (r.error) return void toast$(r.error, 'error')
|
|
46
|
+
toast$(t('已声明退款,等待买家确认'), 'success'); renderOrderDetail(document.getElementById('app'), oid)
|
|
47
|
+
}
|
|
48
|
+
window.dpReturnConfirmRefund = async (rid, oid) => {
|
|
49
|
+
if (typeof confirmModal === 'function' && !(await confirmModal(t('确认已收到卖家退款?确认后退货完成,不可撤销。若尚未到账请勿确认。'), t('已收到,完成退货'), { danger: true }))) return
|
|
50
|
+
let token
|
|
51
|
+
try { token = await requestPasskeyGate('direct_pay_order_action', { order_id: oid, action: 'return_refund_confirm' }) }
|
|
52
|
+
catch (e) { if (window.dpPromptRegisterPasskey) await window.dpPromptRegisterPasskey(e); return }
|
|
53
|
+
const r = await POST('/return-requests/' + rid + '/confirm-refund', { webauthn_token: token })
|
|
54
|
+
if (r.error) return void toast$(window.dpErrorText ? window.dpErrorText(r.error_code, r.error) : r.error, 'error')
|
|
55
|
+
toast$(t('退货已完成'), 'success'); renderOrderDetail(document.getElementById('app'), oid)
|
|
56
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Direct Pay (Rail 1) — 收款信息可见性生命周期 (PR-2)。UI ONLY。
|
|
2
|
+
// 模型:待支付(direct_pay_window)→ 自动展示卖家收款说明+QR 一个【30 分钟窗口】,超时自动隐藏,可【轻量】重新显示(重置窗口);
|
|
3
|
+
// 其它所有状态(已付/完成/取消/过期/争议/退款…)→ 默认隐藏,需【二次验证:现场 Passkey + 风险提示】才临时展示(同样 30 分钟窗口)。
|
|
4
|
+
// 金额(应付)非敏感,任何状态都常显。
|
|
5
|
+
// 诚实边界:这是【客户端展示/同意】控制 —— 防误付空单 + 不长留敏感收款信息在屏 + 现场在场二次同意;
|
|
6
|
+
// 【并非】新的服务器机密边界(收款目标对 both-acked 的订单买家经订单 API 仍可得,后端授权门仍是 both-acked 归属)。
|
|
7
|
+
// 订单隔离(P1):所有 DOM 写入只经 dpInstrBox(orderId) 取容器 —— 容器 data-order-id 必须仍等于该 orderId,
|
|
8
|
+
// 否则(已切到别的订单页)一律 bail。切换订单渲染时先清掉所有旧 timer,杜绝旧订单 timer/重看改写当前订单面板。
|
|
9
|
+
window._dpRevealTimers = window._dpRevealTimers || {}
|
|
10
|
+
window.DP_REVEAL_MS = 30 * 60 * 1000
|
|
11
|
+
|
|
12
|
+
// 终态/无效单(不应再付款)——重看时给强风险提示。
|
|
13
|
+
window.dpIsVoidOrder = (status) => ['cancelled', 'expired', 'direct_expired_unconfirmed', 'disputed', 'dispute_dismissed', 'refunded_full', 'refunded_partial', 'fault_seller', 'fault_buyer', 'fault_logistics'].includes(status)
|
|
14
|
+
|
|
15
|
+
// 订单隔离守卫:仅当当前 #dp-order-instr 容器仍属于 orderId 才返回它;否则 null(已切换订单 → 任何写入都不得落到别的订单页)。
|
|
16
|
+
window.dpInstrBox = (orderId) => { const b = document.getElementById('dp-order-instr'); return (b && b.getAttribute('data-order-id') === String(orderId)) ? b : null }
|
|
17
|
+
window.dpClearRevealTimer = (orderId) => { const tmr = window._dpRevealTimers[orderId]; if (tmr) { clearTimeout(tmr.to); clearInterval(tmr.iv); delete window._dpRevealTimers[orderId] } }
|
|
18
|
+
window.dpClearAllRevealTimers = () => { for (const k of Object.keys(window._dpRevealTimers)) window.dpClearRevealTimer(k) }
|
|
19
|
+
|
|
20
|
+
// 收款说明+QR 展示块(含倒计时);到点 → 隐藏。lightweight=true:重新显示无需 Passkey(待支付轨)。
|
|
21
|
+
window.dpShowPaymentInfo = (order, orderId, lightweight) => {
|
|
22
|
+
const box = window.dpInstrBox(orderId); if (!box) return // 已切到别的订单页 → 不写
|
|
23
|
+
window.dpClearRevealTimer(orderId)
|
|
24
|
+
const snap = order && order.direct_pay_instruction_snapshot ? order.direct_pay_instruction_snapshot : ''
|
|
25
|
+
const pay = window.dpPayAmountText ? window.dpPayAmountText(order) : ''
|
|
26
|
+
if (!snap) { box.innerHTML = `<div style="font-size:12px;color:#9ca3af">${t('卖家尚未设置收款说明,暂不可直付')}</div>`; return }
|
|
27
|
+
box.innerHTML = `<div style="font-size:12px;color:#374151;background:#fff;border:1px solid #fde68a;border-radius:8px;padding:8px 10px">
|
|
28
|
+
${pay ? `<div style="font-size:13px;font-weight:700;color:#92400e;margin-bottom:4px">💸 ${escHtml(pay)}</div>` : ''}
|
|
29
|
+
<div style="font-size:11px;color:#9ca3af;margin-bottom:2px">${t('卖家收款说明(下单时快照)')}${window.dpCopyBtn ? window.dpCopyBtn(snap, t('复制收款说明')) : ''}</div><div style="white-space:pre-wrap;word-break:break-word">${escHtml(snap)}</div><div id="dp-order-qr"></div>${lightweight && window.dpMemoInputHtml ? window.dpMemoInputHtml(orderId) : ''}
|
|
30
|
+
<div style="font-size:10px;color:#9ca3af;margin-top:6px">${t('自动隐藏倒计时')}: <span id="dp-reveal-countdown">30:00</span></div></div>`
|
|
31
|
+
if (window.dpLoadOrderQr) window.dpLoadOrderQr(orderId)
|
|
32
|
+
const end = Date.now() + window.DP_REVEAL_MS
|
|
33
|
+
const tick = () => { const b = window.dpInstrBox(orderId); if (!b) return window.dpClearRevealTimer(orderId); const el = b.querySelector('#dp-reveal-countdown'); if (!el) return; const s = Math.max(0, Math.round((end - Date.now()) / 1000)); el.textContent = `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}` }
|
|
34
|
+
tick()
|
|
35
|
+
window._dpRevealTimers[orderId] = { iv: setInterval(tick, 1000), to: setTimeout(() => window.dpHidePaymentInfo(order, orderId, lightweight), window.DP_REVEAL_MS) }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 隐藏收款信息 → 显示重看入口。lightweight:待支付轨点一下即可;否则需 Passkey+风险提示。
|
|
39
|
+
window.dpHidePaymentInfo = (order, orderId, lightweight) => {
|
|
40
|
+
window.dpClearRevealTimer(orderId)
|
|
41
|
+
const box = window.dpInstrBox(orderId); if (!box) return // 已切到别的订单页 → 不写
|
|
42
|
+
const pay = window.dpPayAmountText ? window.dpPayAmountText(order) : ''
|
|
43
|
+
const hint = lightweight ? t('为保护你的收款信息,已隐藏。') : t('订单已不在待支付阶段,收款信息默认隐藏。')
|
|
44
|
+
const btn = lightweight
|
|
45
|
+
? `<button class="btn btn-outline btn-sm" onclick="dpReShowPaymentInfo('${orderId}')">${t('重新显示')}</button>`
|
|
46
|
+
: `<button class="btn btn-outline btn-sm" onclick="dpGatedRevealPaymentInfo('${orderId}')">${t('查看收款信息(需 Passkey 验证)')}</button>`
|
|
47
|
+
box.innerHTML = `<div style="font-size:12px;color:#374151;background:#fff;border:1px solid #fde68a;border-radius:8px;padding:8px 10px">
|
|
48
|
+
${pay ? `<div style="font-size:13px;font-weight:700;color:#92400e;margin-bottom:4px">💸 ${escHtml(pay)}</div>` : ''}
|
|
49
|
+
<div style="font-size:11px;color:#9ca3af;margin-bottom:6px">${escHtml(hint)}</div>${btn}</div>`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 轻量重新显示:GET【最新】订单后按【当前状态】重新分派 —— 仍待支付才轻量重显;若期间已取消/过期/争议等,
|
|
53
|
+
// dpRenderPaymentInfo 会走隐藏+Passkey 二次验证路径(绝不用旧按钮 lightweight 展示一个已失效订单的收款信息)。
|
|
54
|
+
window.dpReShowPaymentInfo = async (orderId) => {
|
|
55
|
+
if (!window.dpInstrBox(orderId)) return
|
|
56
|
+
const o = await GET(`/orders/${orderId}`); const ord = o && o.order ? o.order : null
|
|
57
|
+
const box = window.dpInstrBox(orderId); if (ord && box) window.dpRenderPaymentInfo(box, ord, orderId)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 非待支付:二次验证(风险提示 + 现场 Passkey)后临时展示一个 30 分钟窗口。
|
|
61
|
+
window.dpGatedRevealPaymentInfo = async (orderId) => {
|
|
62
|
+
if (!window.dpInstrBox(orderId)) return
|
|
63
|
+
const o = await GET(`/orders/${orderId}`); const ord = o && o.order ? o.order : null
|
|
64
|
+
if (!ord) return
|
|
65
|
+
const warn = window.dpIsVoidOrder(ord.status)
|
|
66
|
+
? t('⚠️ 此订单已取消/终止,请【勿再付款】。收款信息仅供对账/存证查看。')
|
|
67
|
+
: t('⚠️ 你已完成付款。收款信息仅供对账/维权查看,请勿重复付款。')
|
|
68
|
+
const go = await confirmModal(`${warn}\n\n${t('继续查看需现场 Passkey 验证。')}`, t('继续(需 Passkey)'), { danger: true })
|
|
69
|
+
if (!go || !window.dpInstrBox(orderId)) return
|
|
70
|
+
try { await requestPasskeyGate('direct_pay_payment_info_reveal', { order_id: orderId }) }
|
|
71
|
+
catch (e) { if (window.dpPromptRegisterPasskey) await window.dpPromptRegisterPasskey(e); return }
|
|
72
|
+
window.dpShowPaymentInfo(ord, orderId, false)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 订单详情:按状态渲染收款信息可见性。【先确认当前容器仍属于该 orderId,再清 timer】——否则旧订单 stale hydrate 回包
|
|
76
|
+
// 会把当前订单的自动隐藏 timer 一并清掉(敏感收款信息永不隐藏)。both-acked 由调用方(dpHydrateOrderDisclosure)先保证。
|
|
77
|
+
window.dpRenderPaymentInfo = (_box, order, orderId) => {
|
|
78
|
+
if (!order || !window.dpInstrBox(orderId)) return // 当前页已不是该订单 → 不动任何 timer/DOM
|
|
79
|
+
window.dpClearAllRevealTimers()
|
|
80
|
+
if (order.payment_rail === 'direct_p2p' && order.status === 'direct_pay_window') window.dpShowPaymentInfo(order, orderId, true)
|
|
81
|
+
else window.dpHidePaymentInfo(order, orderId, false)
|
|
82
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Direct Pay 卖家【直接收款销售统计 + 对账 + 逐单平台费明细】section。UI ONLY,只读。
|
|
2
|
+
// 数据:GET /api/sellers/me/direct-pay-report(requireSeller,仅本人)。直付=非托管,货款买家直付卖家,平台不经手 →
|
|
3
|
+
// 钱包/收入视图看不到直付销售,故此处按订单聚合销售额 + 逐单平台服务费,便于对账(可选日期区间 + 客户端 CSV 导出)。
|
|
4
|
+
// 销售额=下单计价币(买家应付金额);平台服务费=USDC。两者不同币种,分列不混加。
|
|
5
|
+
;(function () {
|
|
6
|
+
const _n = (v) => (Number(v) || 0)
|
|
7
|
+
const _amt = (v) => _n(v).toLocaleString(undefined, { maximumFractionDigits: 2 }) // 销售额(计价币)
|
|
8
|
+
const _fee = (v) => v == null ? '—' : (_n(v).toFixed(6).replace(/\.?0+$/, '') || '0') + ' USDC' // 平台费(USDC)
|
|
9
|
+
let _last = null
|
|
10
|
+
|
|
11
|
+
window.dpSalesReportSection = () => {
|
|
12
|
+
setTimeout(() => window.dpHydrateSalesReport(), 60)
|
|
13
|
+
return `<div class="card" style="margin-bottom:12px;border:1px solid #e5e7eb">
|
|
14
|
+
<div style="font-size:14px;font-weight:700;margin-bottom:4px">🧾 ${t('直接收款销售统计 + 对账(仅你可见)')}</div>
|
|
15
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('直付为非托管:货款由买家直接付给你,平台不经手。此处按你的订单聚合销售额与逐单平台服务费,便于对账。')}</div>
|
|
16
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-bottom:10px">
|
|
17
|
+
<input type="date" id="dpsr-from" style="font-size:12px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:6px">
|
|
18
|
+
<span style="color:#9ca3af">→</span>
|
|
19
|
+
<input type="date" id="dpsr-to" style="font-size:12px;padding:4px 6px;border:1px solid #e5e7eb;border-radius:6px">
|
|
20
|
+
<button class="btn btn-outline btn-sm" style="font-size:11px;padding:4px 10px" onclick="dpHydrateSalesReport(document.getElementById('dpsr-from').value, document.getElementById('dpsr-to').value)">${t('查询')}</button>
|
|
21
|
+
<button class="btn btn-outline btn-sm" style="font-size:11px;padding:4px 10px" onclick="dpSalesReportExport()">📥 ${t('导出对账 CSV')}</button>
|
|
22
|
+
</div>
|
|
23
|
+
<div id="dpsr-body">${typeof loading$ === 'function' ? loading$() : ''}</div>
|
|
24
|
+
</div>`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
window.dpHydrateSalesReport = async (from, to) => {
|
|
28
|
+
const box = document.getElementById('dpsr-body'); if (!box) return
|
|
29
|
+
const q = []
|
|
30
|
+
if (from && /^\d{4}-\d{2}-\d{2}$/.test(from)) q.push('from=' + from)
|
|
31
|
+
if (to && /^\d{4}-\d{2}-\d{2}$/.test(to)) q.push('to=' + to)
|
|
32
|
+
const r = await GET('/sellers/me/direct-pay-report' + (q.length ? '?' + q.join('&') : '')).catch(() => null)
|
|
33
|
+
if (!r || r.error) { box.innerHTML = `<div style="font-size:12px;color:#dc2626">${t('操作失败,请重试')}</div>`; return }
|
|
34
|
+
_last = r
|
|
35
|
+
const s = r.summary
|
|
36
|
+
if (!s || s.order_count === 0) { box.innerHTML = `<div style="font-size:12px;color:#9ca3af;padding:8px 0">${t('该区间暂无直接收款订单')}</div>`; return }
|
|
37
|
+
const tile = (label, val, sub) => `<div style="flex:1;min-width:96px;background:#faf5ff;border:1px solid #ede9fe;border-radius:8px;padding:8px 10px"><div style="font-size:11px;color:#7c3aed">${label}</div><div style="font-size:16px;font-weight:700;color:#5b21b6">${val}</div>${sub ? `<div style="font-size:10px;color:#9ca3af">${sub}</div>` : ''}</div>`
|
|
38
|
+
const tiles = `<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px">
|
|
39
|
+
${tile(t('已完成销售额'), _amt(s.completed_sales), t('已完成') + ' ' + s.completed_count + ' ' + t('单'))}
|
|
40
|
+
${tile(t('订单总额'), _amt(s.gross_order_total), t('含在途/已关闭') + ' · ' + s.order_count + ' ' + t('单'))}
|
|
41
|
+
${tile(t('已计提平台费'), _fee(s.fee_accrued_total), s.fee_order_count + ' ' + t('单'))}
|
|
42
|
+
${tile(t('在途 / 已关闭'), s.in_flight_count + ' / ' + s.closed_count, t('单'))}
|
|
43
|
+
</div>`
|
|
44
|
+
const byMonth = (r.by_month && r.by_month.length > 1) ? `<div style="margin-bottom:10px"><div style="font-size:12px;color:#6b7280;margin-bottom:4px">${t('按月(已完成 / 订单总额)')}</div>${r.by_month.map(m => `<div style="display:flex;justify-content:space-between;font-size:12px;padding:2px 0;border-bottom:1px solid #f3f4f6"><span>${escHtml(m.month)}</span><span>${_amt(m.completed_sales)} <span style="color:#9ca3af">/ ${_amt(m.gross_order_total)}</span></span></div>`).join('')}</div>` : ''
|
|
45
|
+
const th = (txt, right) => `<th style="text-align:${right ? 'right' : 'left'};padding:5px 4px;font-size:11px;color:#9ca3af;white-space:nowrap">${txt}</th>`
|
|
46
|
+
const rowsHtml = r.orders.map(o => `<tr style="border-bottom:1px solid #f3f4f6">
|
|
47
|
+
<td style="padding:5px 4px;font-size:11px"><a href="#order/${o.id}" style="color:#6366f1;text-decoration:none">${escHtml(String(o.id).slice(0, 10))}</a></td>
|
|
48
|
+
<td style="padding:5px 4px;font-size:11px;color:#6b7280;white-space:nowrap">${typeof fmtTime === 'function' ? fmtTime(o.created_at) : escHtml(o.created_at)}</td>
|
|
49
|
+
<td style="padding:5px 4px;font-size:11px">${typeof statusBadge === 'function' ? statusBadge(o.status, 'direct_p2p') : escHtml(o.status)}</td>
|
|
50
|
+
<td style="padding:5px 4px;font-size:11px;text-align:right;font-weight:600">${_amt(o.total_amount)}</td>
|
|
51
|
+
<td style="padding:5px 4px;font-size:11px;text-align:right;color:#7c3aed">${_fee(o.fee_amount)}</td>
|
|
52
|
+
</tr>`).join('')
|
|
53
|
+
const table = `<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse">
|
|
54
|
+
<thead><tr style="border-bottom:1px solid #e5e7eb">${th(t('订单'))}${th(t('时间'))}${th(t('状态'))}${th(t('销售额'), true)}${th(t('平台费'), true)}</tr></thead>
|
|
55
|
+
<tbody>${rowsHtml}</tbody></table></div>${r.truncated ? `<div style="font-size:11px;color:#b45309;margin-top:6px">${t('仅显示最近 500 单,请用日期区间缩小范围')}</div>` : ''}`
|
|
56
|
+
box.innerHTML = tiles + byMonth + table
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 客户端 CSV 导出(对账用:逐单销售额 + 平台费一张表,对银行流水)。
|
|
60
|
+
window.dpSalesReportExport = () => {
|
|
61
|
+
if (!_last || !_last.orders || !_last.orders.length) { (window.toast$ || window.alert)(t('暂无数据可导出')); return }
|
|
62
|
+
const esc = (v) => { const x = v == null ? '' : String(v); return /[",\n]/.test(x) ? '"' + x.replace(/"/g, '""') + '"' : x }
|
|
63
|
+
const lines = [['order_id', 'created_at', 'status', 'sales_amount', 'platform_fee_usdc', 'fee_accrued_at', 'ship_to_region', 'product'].join(',')]
|
|
64
|
+
for (const o of _last.orders) lines.push([o.id, o.created_at, o.status, o.total_amount, o.fee_amount == null ? '' : o.fee_amount, o.fee_accrued_at || '', o.ship_to_region || '', o.product_title || ''].map(esc).join(','))
|
|
65
|
+
const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob(['' + lines.join('\n')], { type: 'text/csv;charset=utf-8' }))
|
|
66
|
+
a.download = 'webaz-directpay-reconcile-' + new Date().toISOString().slice(0, 10) + '.csv'
|
|
67
|
+
document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(a.href)
|
|
68
|
+
if (window.toast$) window.toast$(t('导出完成'))
|
|
69
|
+
}
|
|
70
|
+
})()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Direct Pay (Rail 1) — 店铺认证 UI (PR-⑤b) = 逐品验证的【豁免】路径。UI ONLY。两个面:
|
|
2
|
+
// ① SELLER 自助(seller workbench settings):申领店铺验证码 → 贴到外部店铺页 → 提交店铺链接 → 看状态(含是否已豁免)。
|
|
3
|
+
// ② ADMIN 审核队列(#admin/store-verifications):打开卖家提交的店铺链接手动核对验证码,verify/reject;verify 时可【勾选
|
|
4
|
+
// “免逐品验证”】(per_product_exempt)→ 该卖家所有商品免逐品、可直付。ROOT + 真人 Passkey。
|
|
5
|
+
// 诚实:WebAZ 不抓取链接,admin 手动核。不碰 wallet/escrow/settlement/refund;只调既有受门控端点。
|
|
6
|
+
|
|
7
|
+
window.dpSvCopy = () => ({
|
|
8
|
+
issued: { icon: '①', text: t('已签发验证码,请贴到你的外部店铺页后提交店铺链接') },
|
|
9
|
+
submitted: { icon: '⏳', text: t('已提交,等待管理员核验') },
|
|
10
|
+
verified: { icon: '✅', text: t('店铺已通过验证') },
|
|
11
|
+
rejected: { icon: '🚫', text: t('未通过,请修正后重新申请') },
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
// ══════ SELLER 自助面板 ══════
|
|
15
|
+
window.dpSellerStoreVerifySection = () => `
|
|
16
|
+
<div class="card" style="margin-bottom:12px;border:1px solid #e5e7eb">
|
|
17
|
+
<div style="font-size:14px;font-weight:700;margin-bottom:4px">🏬 ${t('店铺认证(可申请免逐品验证)')}</div>
|
|
18
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('默认每个商品需单独验证才能直付。你也可以申请【店铺认证】:申领验证码 → 贴到你的外部店铺主页 → 提交店铺链接 → 管理员手动核对;通过且管理员勾选豁免后,你【所有商品】免逐品验证即可直付。')}</div>
|
|
19
|
+
<div id="dp-seller-sv">${loading$()}</div>
|
|
20
|
+
</div>`
|
|
21
|
+
|
|
22
|
+
window.dpHydrateSellerStoreVerify = async () => {
|
|
23
|
+
const box = document.getElementById('dp-seller-sv')
|
|
24
|
+
if (!box) return
|
|
25
|
+
const r = await GET('/direct-receive/store-verification')
|
|
26
|
+
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 }
|
|
27
|
+
const v = r.verification, copy = window.dpSvCopy()
|
|
28
|
+
if (!v || v.status === 'rejected') {
|
|
29
|
+
const prior = v ? `<div style="font-size:12px;color:#6b7280;margin-bottom:8px">${copy.rejected.icon} ${escHtml(copy.rejected.text)}</div>` : ''
|
|
30
|
+
box.innerHTML = prior + `<button onclick="window.dpRequestStoreVerify()" style="padding:8px 16px;background:#2563eb;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer">${t('申请店铺认证')}</button>`
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
const meta = copy[v.status] || { icon: 'ℹ️', text: v.status }
|
|
34
|
+
const lines = [`<div style="font-size:13px;font-weight:600;color:#374151">${meta.icon} ${escHtml(meta.text)}</div>`]
|
|
35
|
+
if (v.status === 'issued') {
|
|
36
|
+
lines.push(`<div style="font-size:12px;color:#374151;margin:6px 0">${t('验证码')}: <code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-weight:700">${escHtml(v.code)}</code> · ${t('把它展示在你的外部店铺主页')}</div>`)
|
|
37
|
+
lines.push(`<input id="dp-sv-url" type="url" placeholder="${t('你的外部店铺链接(http/https)')}" style="width:100%;padding:6px;border:1px solid #d1d5db;border-radius:4px;font-size:12px;margin-bottom:6px">`)
|
|
38
|
+
lines.push(`<button onclick="window.dpSubmitStoreVerify()" style="padding:6px 12px;background:#2563eb;color:#fff;border:none;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer">${t('提交店铺链接')}</button>`)
|
|
39
|
+
} else if (v.status === 'verified') {
|
|
40
|
+
lines.push(`<div style="font-size:12px;color:${r.exempt ? '#166534' : '#6b7280'};margin-top:4px">${r.exempt ? '✅ ' + t('已豁免逐品验证:你的所有商品可直付') : t('已通过,但未获逐品豁免:商品仍需逐个验证')}</div>`)
|
|
41
|
+
}
|
|
42
|
+
box.innerHTML = `<div style="display:flex;flex-direction:column;gap:2px">${lines.join('')}</div>`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
window.dpRequestStoreVerify = async () => {
|
|
46
|
+
const r = await POST('/direct-receive/store-verification', {})
|
|
47
|
+
if (!r || r.error) { if (typeof toast$ === 'function') toast$(window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试'), 'error'); return }
|
|
48
|
+
if (typeof toast$ === 'function') toast$(t('验证码已签发'))
|
|
49
|
+
window.dpHydrateSellerStoreVerify()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
window.dpSubmitStoreVerify = async () => {
|
|
53
|
+
const el = document.getElementById('dp-sv-url')
|
|
54
|
+
const url = el && el.value ? el.value.trim() : ''
|
|
55
|
+
if (!url) { if (typeof toast$ === 'function') toast$(t('请填写你的外部店铺链接'), 'error'); return }
|
|
56
|
+
const r = await PUT('/direct-receive/store-verification', { external_url: url })
|
|
57
|
+
if (!r || r.error) { if (typeof toast$ === 'function') toast$(window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试'), 'error'); return }
|
|
58
|
+
if (typeof toast$ === 'function') toast$(t('已提交,等待管理员核验'))
|
|
59
|
+
window.dpHydrateSellerStoreVerify()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ══════ ADMIN 审核队列 ══════
|
|
63
|
+
window.renderAdminStoreVerifications = async function (app) {
|
|
64
|
+
if (!state.user) { renderLogin(); return }
|
|
65
|
+
if (!isAdmin()) { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限管理员')}</div>`, 'admin'); return }
|
|
66
|
+
app.innerHTML = shell(loading$(), 'admin')
|
|
67
|
+
const data = await GET('/admin/direct-receive/store-verifications?status=submitted')
|
|
68
|
+
if (!data || data.error) { app.innerHTML = shell(alert$('error', (data && data.error) || t('加载失败')), 'admin'); return }
|
|
69
|
+
const rows = data.verifications || []
|
|
70
|
+
const list = rows.length ? rows.map(v => `
|
|
71
|
+
<div class="card" style="margin-bottom:10px">
|
|
72
|
+
<div style="font-size:11px;color:#9ca3af">${escHtml(v.user_id)} · ${fmtTime(v.created_at)}</div>
|
|
73
|
+
<div style="font-size:13px;margin-top:6px">${t('验证码')}: <code style="background:#f3f4f6;padding:2px 6px;border-radius:4px;font-weight:700">${escHtml(v.code)}</code>${v.platform ? ` · ${escHtml(v.platform)}` : ''}</div>
|
|
74
|
+
<div style="font-size:12px;margin-top:4px">${t('店铺链接')}: ${v.external_url ? `<a href="${encodeURI(v.external_url)}" target="_blank" rel="noopener noreferrer nofollow" style="color:#2563eb;text-decoration:underline;word-break:break-all">${escHtml(v.external_url)}</a>` : '—'}</div>
|
|
75
|
+
<div style="font-size:11px;color:#9ca3af;margin-top:4px">${t('请手动打开链接,确认店铺主页展示了上面的验证码,再决定通过/拒绝。')}</div>
|
|
76
|
+
<label style="display:flex;align-items:center;gap:6px;font-size:12px;color:#374151;margin-top:8px"><input type="checkbox" id="sv-exempt-${v.id}"> ${t('免逐品验证(通过后该卖家所有商品可直付)')}</label>
|
|
77
|
+
<div style="display:flex;gap:6px;margin-top:8px">
|
|
78
|
+
<button class="btn btn-primary btn-sm" style="font-size:12px" onclick="window.doReviewStoreVerify('${v.id}','verified')">✓ ${t('通过(真人 Passkey)')}</button>
|
|
79
|
+
<button class="btn btn-sm" style="font-size:12px;background:#dc2626;border-color:#dc2626;color:#fff" onclick="window.doReviewStoreVerify('${v.id}','rejected')">✗ ${t('拒绝(真人 Passkey)')}</button>
|
|
80
|
+
</div>
|
|
81
|
+
</div>`).join('') : `<div class="empty"><div class="empty-icon">📥</div><div class="empty-text">${t('暂无待核验店铺')}</div></div>`
|
|
82
|
+
app.innerHTML = shell(`
|
|
83
|
+
<h1 class="page-title">🏬 ${t('店铺认证审核')}</h1>
|
|
84
|
+
<div style="margin-bottom:12px"><button class="btn btn-outline btn-sm" style="width:auto" onclick="navigate('#admin')">${t('返回概览')}</button></div>
|
|
85
|
+
<div style="font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px">${t('打开卖家提交的店铺链接,确认店铺页展示了验证码再通过。勾选“免逐品验证”=该卖家所有商品免逐个验证即可直付(请仅对可信商户勾选)。通过/拒绝均需真人 Passkey。')}</div>
|
|
86
|
+
${list}
|
|
87
|
+
`, 'admin')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// purpose_data 绑 verification_id + decision + per_product_exempt(改任一项一律拒)。
|
|
91
|
+
window.doReviewStoreVerify = async function (id, decision) {
|
|
92
|
+
const exempt = decision === 'verified' && !!(document.getElementById('sv-exempt-' + id) || {}).checked
|
|
93
|
+
const body = { verification_id: id, decision, per_product_exempt: exempt }
|
|
94
|
+
const token = await requestPasskeyGate('direct_pay_store_verify', body)
|
|
95
|
+
if (!token) { if (typeof toast$ === 'function') toast$(t('需要真人 Passkey 确认'), 'error'); return }
|
|
96
|
+
const r = await POST(`/admin/direct-receive/store-verifications/${id}/review`, { decision, per_product_exempt: exempt, webauthn_token: token })
|
|
97
|
+
if (!r || r.error) { if (typeof toast$ === 'function') toast$(window.dpErrorText ? window.dpErrorText(r && r.error_code, r && r.error) : t('操作失败,请重试'), 'error'); return }
|
|
98
|
+
if (typeof toast$ === 'function') toast$(decision === 'verified' ? (r.per_product_exempt ? t('店铺已通过,已豁免逐品验证') : t('店铺已通过')) : t('店铺申请已拒绝'))
|
|
99
|
+
window.renderAdminStoreVerifications(document.getElementById('app'))
|
|
100
|
+
}
|