@seasonkoh/webaz 0.1.29 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bond-refund-blockers.js +50 -0
- package/dist/bond-slash.js +100 -0
- package/dist/bond-terms.js +31 -0
- package/dist/direct-pay-bond-rail-clearance.js +10 -5
- package/dist/direct-pay-cancel-refund.js +160 -0
- package/dist/direct-pay-create.js +78 -9
- package/dist/direct-pay-disclosures.js +17 -11
- package/dist/direct-pay-fee-ar.js +2 -2
- package/dist/direct-pay-fee-prepay-request.js +80 -0
- package/dist/direct-pay-returns.js +104 -0
- package/dist/direct-pay-stock.js +9 -0
- package/dist/direct-receive-deferral.js +30 -1
- package/dist/direct-receive-deposits.js +122 -17
- package/dist/free-shipping.js +37 -0
- package/dist/fx-rates.js +7 -0
- package/dist/layer0-foundation/L0-1-database/schema.js +246 -0
- package/dist/layer0-foundation/L0-2-state-machine/engine.js +1 -0
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +73 -0
- package/dist/layer1-agent/L1-1-mcp-server/server.js +81 -16
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +110 -41
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +133 -31
- package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +16 -4
- package/dist/layer3-trust/L3-1-dispute-engine/mutual-cancel.js +156 -0
- package/dist/platform-receive-accounts.js +94 -0
- package/dist/pwa/arbitration-read-admin.js +37 -0
- package/dist/pwa/arbitrator-lifecycle.js +100 -0
- package/dist/pwa/contract-fingerprint.js +15 -0
- package/dist/pwa/direct-pay-order-redaction.js +20 -2
- package/dist/pwa/human-presence.js +2 -6
- package/dist/pwa/public/app-account.js +9 -9
- package/dist/pwa/public/app-admin-disputes.js +55 -0
- package/dist/pwa/public/app-agent-appeal.js +90 -0
- package/dist/pwa/public/app-agent-approvals.js +93 -0
- package/dist/pwa/public/app-agent-pair.js +127 -0
- package/dist/pwa/public/app-arbitrator-admin.js +87 -0
- package/dist/pwa/public/app-arbitrator-entry.js +9 -0
- package/dist/pwa/public/app-bond-deferral-ui.js +9 -0
- package/dist/pwa/public/app-bond-refund-ui.js +66 -0
- package/dist/pwa/public/app-bond-slash-ui.js +74 -0
- package/dist/pwa/public/app-bond-terms-ui.js +23 -0
- package/dist/pwa/public/app-bond-ui.js +108 -0
- package/dist/pwa/public/app-chat-poll.js +6 -6
- package/dist/pwa/public/app-contribution-hub.js +23 -0
- package/dist/pwa/public/app-direct-pay-accounts.js +1 -1
- package/dist/pwa/public/app-direct-pay-buyer.js +1 -1
- package/dist/pwa/public/app-direct-pay-cancel-refund.js +72 -0
- package/dist/pwa/public/app-direct-pay-copy.js +12 -0
- package/dist/pwa/public/app-direct-pay-fee-history.js +39 -0
- package/dist/pwa/public/app-direct-pay-fee-ops.js +1 -1
- package/dist/pwa/public/app-direct-pay-fee-request.js +81 -0
- package/dist/pwa/public/app-direct-pay-fee-requests-admin.js +70 -0
- package/dist/pwa/public/app-direct-pay-memo.js +14 -0
- package/dist/pwa/public/app-direct-pay-negotiation.js +35 -0
- package/dist/pwa/public/app-direct-pay-pay.js +15 -0
- package/dist/pwa/public/app-direct-pay-paymodal.js +32 -0
- package/dist/pwa/public/app-direct-pay-reconcile.js +19 -0
- package/dist/pwa/public/app-direct-pay-returns.js +56 -0
- package/dist/pwa/public/app-direct-pay-reveal.js +82 -0
- package/dist/pwa/public/app-direct-pay-sales-report.js +70 -0
- package/dist/pwa/public/app-direct-pay.js +36 -37
- package/dist/pwa/public/app-dispute-close-ui.js +38 -0
- package/dist/pwa/public/app-free-shipping-ui.js +29 -0
- package/dist/pwa/public/app-gmv-rail-split.js +12 -0
- package/dist/pwa/public/app-listing-commerce-ui.js +72 -0
- package/dist/pwa/public/app-mutual-cancel.js +54 -0
- package/dist/pwa/public/app-notif-templates-orders.js +43 -0
- package/dist/pwa/public/app-notif-templates.js +22 -0
- package/dist/pwa/public/app-order-accept-ui.js +158 -0
- package/dist/pwa/public/app-order-errors.js +50 -0
- package/dist/pwa/public/app-order-labels.js +19 -0
- package/dist/pwa/public/app-order-rail-filter.js +26 -0
- package/dist/pwa/public/app-platform-receive-accounts.js +140 -0
- package/dist/pwa/public/app-poll-governor.js +22 -0
- package/dist/pwa/public/app-profile.js +4 -4
- package/dist/pwa/public/app-purchase-terms-ui.js +68 -0
- package/dist/pwa/public/app-sale-regions-ui.js +38 -0
- package/dist/pwa/public/app-seller.js +1 -1
- package/dist/pwa/public/app-trade-tax-ui.js +33 -0
- package/dist/pwa/public/app.js +133 -160
- package/dist/pwa/public/i18n.js +675 -5
- package/dist/pwa/public/index.html +41 -0
- package/dist/pwa/public/openapi.json +709 -11
- package/dist/pwa/public/style.css +3 -0
- package/dist/pwa/routes/admin-direct-receive-deposits.js +215 -3
- package/dist/pwa/routes/admin-protocol-params.js +16 -0
- package/dist/pwa/routes/admin-reports.js +31 -5
- package/dist/pwa/routes/agent-governance.js +1 -1
- package/dist/pwa/routes/agent-grants.js +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/direct-pay-cancel-refund.js +111 -0
- package/dist/pwa/routes/direct-pay-disclosure-acks.js +9 -4
- package/dist/pwa/routes/direct-pay-pending-accept.js +222 -0
- package/dist/pwa/routes/direct-pay-returns.js +74 -0
- package/dist/pwa/routes/direct-pay-timeouts.js +186 -9
- package/dist/pwa/routes/disputes-read.js +20 -6
- package/dist/pwa/routes/disputes-write.js +91 -33
- package/dist/pwa/routes/external-anchors.js +4 -2
- package/dist/pwa/routes/fee-prepay-requests.js +66 -0
- package/dist/pwa/routes/governance-onboarding.js +46 -8
- package/dist/pwa/routes/logistics.js +4 -4
- package/dist/pwa/routes/me-data.js +2 -2
- package/dist/pwa/routes/mutual-cancel.js +62 -0
- package/dist/pwa/routes/orders-action.js +182 -9
- package/dist/pwa/routes/orders-create.js +18 -7
- package/dist/pwa/routes/orders-read.js +76 -14
- package/dist/pwa/routes/platform-receive-accounts.js +111 -0
- package/dist/pwa/routes/products-create.js +13 -4
- package/dist/pwa/routes/products-update.js +15 -1
- package/dist/pwa/routes/profile-identity.js +4 -2
- package/dist/pwa/routes/returns.js +39 -8
- package/dist/pwa/routes/seller-directpay-report.js +110 -0
- package/dist/pwa/routes/seller-quota.js +5 -1
- package/dist/pwa/routes/shipping-templates.js +219 -0
- package/dist/pwa/routes/snf.js +4 -1
- package/dist/pwa/routes/webauthn.js +3 -3
- package/dist/pwa/server.js +59 -36
- package/dist/runtime/agent-grant-scopes.js +69 -1
- package/dist/runtime/webaz-schema-helpers.js +57 -3
- package/dist/sale-regions.js +116 -0
- package/dist/shipping-templates.js +119 -0
- package/dist/trade-tax.js +99 -0
- package/dist/trade-terms.js +76 -0
- package/dist/version.js +1 -1
- package/package.json +59 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// 单链接商务覆盖(S4)—— 上架/编辑表单里按【单个商品】覆盖店铺默认:接单/运费/询价/可售区/满额免邮/税费进口责任/清关字段。
|
|
2
|
+
// UI ONLY,写全部复用已验证端点(POST /seller/accept-mode + /seller/shipping-template + PUT /products/:id)。
|
|
3
|
+
// "留空=继承店铺默认"(值 null 清除该商品覆盖);编辑态从已取到的 product 行(formatProductForAgent 透传各覆盖列)回填。
|
|
4
|
+
// app.js 净零:add/edit 表单各注入一个 section hook + 保存时调 window.listingCommerceSave(productId)。
|
|
5
|
+
;(function () {
|
|
6
|
+
const J = (x) => { try { return typeof x === 'string' ? JSON.parse(x) : (x || null) } catch { return null } }
|
|
7
|
+
const tplText = (arr) => (Array.isArray(arr) ? arr : []).map(e => `${e.region} ${e.fee}${e.est_days ? ' ' + e.est_days : ''}`).join('\n')
|
|
8
|
+
const taxText = (arr) => (Array.isArray(arr) ? arr : []).map(l => `${l.region} ${l.label}${l.rate_pct != null ? ' ' + l.rate_pct : ''}`).join('\n')
|
|
9
|
+
const srCodes = (r) => r ? (r.mode === 'list' ? (r.include || []) : (r.exclude || [])).join(' ') : ''
|
|
10
|
+
|
|
11
|
+
// p = 商品对象(编辑)或 null(新建)。所有字段 id 前缀 lc-。
|
|
12
|
+
window.listingCommerceSectionHtml = (p) => {
|
|
13
|
+
p = p || {}
|
|
14
|
+
const sr = J(p.sale_regions)
|
|
15
|
+
const sel = (id, cur, opts) => `<select class="form-control" id="${id}" style="font-size:13px">${opts.map(([v, lbl]) => `<option value="${v}" ${String(cur ?? '') === v ? 'selected' : ''}>${lbl}</option>`).join('')}</select>`
|
|
16
|
+
return `<details style="margin-bottom:16px">
|
|
17
|
+
<summary style="font-size:13px;color:#6b7280;cursor:pointer;padding:4px 0">🌍 ${t('本商品:接单 / 运费 / 可售区 / 税费(留空=用店铺默认)')}</summary>
|
|
18
|
+
<div style="margin-top:12px;display:flex;flex-direction:column;gap:10px">
|
|
19
|
+
<div><label class="form-label" style="font-size:12px">${t('接单模式')}</label>${sel('lc-accept', p.accept_mode, [['', t('继承店铺默认')], ['auto', t('自动接单')], ['manual', t('手动接单')]])}</div>
|
|
20
|
+
<div><label class="form-label" style="font-size:12px">${t('运费模板(每行:地区码 运费 [时效];* 兜底;留空=继承)')}</label><textarea class="form-control" id="lc-tpl" rows="2" style="font-size:12px;font-family:monospace" placeholder="SG 5 3-5 * 25 10-20">${escHtml(tplText(J(p.shipping_template)))}</textarea></div>
|
|
21
|
+
<div><label class="form-label" style="font-size:12px">${t('模板外地区询价(直付)')}</label>${sel('lc-quote', p.shipping_quote_ok == null ? '' : (Number(p.shipping_quote_ok) === 1 ? '1' : '0'), [['', t('继承店铺默认')], ['1', t('开')], ['0', t('关')]])}</div>
|
|
22
|
+
<div style="display:flex;gap:8px">
|
|
23
|
+
<div style="flex:1"><label class="form-label" style="font-size:12px">${t('可售区域')}</label>${sel('lc-sr-mode', sr ? sr.mode : '', [['', t('继承/不限')], ['list', t('仅允许')], ['all', t('全球除外')]])}</div>
|
|
24
|
+
<div style="flex:1"><label class="form-label" style="font-size:12px">${t('地区码(空格分隔)')}</label><input class="form-control" id="lc-sr-codes" style="font-size:12px;font-family:monospace" value="${escHtml(srCodes(sr))}"></div>
|
|
25
|
+
</div>
|
|
26
|
+
<div style="display:flex;gap:8px">
|
|
27
|
+
<div style="flex:1"><label class="form-label" style="font-size:12px">${t('满额免邮阈值')}</label><input class="form-control" id="lc-free" type="number" min="0" step="0.01" placeholder="${t('继承')}" value="${p.free_shipping_threshold ?? ''}"></div>
|
|
28
|
+
<div style="flex:1"><label class="form-label" style="font-size:12px">${t('进口责任')}</label>${sel('lc-duty', p.import_duty_terms, [['', t('继承/不声明')], ['ddu', 'DDU'], ['ddp', 'DDP']])}</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div><label class="form-label" style="font-size:12px">${t('价内已含税(每行:地区码 税名 [税率%];留空=继承)')}</label><textarea class="form-control" id="lc-tax" rows="2" style="font-size:12px;font-family:monospace" placeholder="SG GST 9">${escHtml(taxText(J(p.tax_lines)))}</textarea></div>
|
|
31
|
+
<div style="border-top:1px dashed #e5e7eb;padding-top:8px;font-size:11px;color:#9ca3af">${t('跨境清关字段(证据用,可选)')}</div>
|
|
32
|
+
<div style="display:flex;gap:8px">
|
|
33
|
+
<input class="form-control" id="lc-weight" type="number" min="0" step="0.01" placeholder="${t('重量 kg')}" value="${p.weight_kg ?? ''}" style="font-size:12px">
|
|
34
|
+
<input class="form-control" id="lc-pkg" placeholder="${t('尺寸 长x宽x高cm')}" value="${escHtml(p.package_size || '')}" style="font-size:12px">
|
|
35
|
+
</div>
|
|
36
|
+
<div style="display:flex;gap:8px">
|
|
37
|
+
<input class="form-control" id="lc-origin" placeholder="${t('发货国码')}" value="${escHtml(p.origin_country || '')}" style="font-size:12px;text-transform:uppercase">
|
|
38
|
+
<input class="form-control" id="lc-coo" placeholder="${t('原产国码')}" value="${escHtml(p.country_of_origin || '')}" style="font-size:12px;text-transform:uppercase">
|
|
39
|
+
<input class="form-control" id="lc-hs" placeholder="${t('HS 编码')}" value="${escHtml(p.hs_code || '')}" style="font-size:12px">
|
|
40
|
+
</div>
|
|
41
|
+
<input class="form-control" id="lc-customs-desc" placeholder="${t('报关英文品名')}" value="${escHtml(p.customs_description || '')}" style="font-size:12px">
|
|
42
|
+
</div></details>`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const LC_FIELDS = ['lc-accept', 'lc-tpl', 'lc-quote', 'lc-sr-mode', 'lc-free', 'lc-duty', 'lc-tax', 'lc-weight', 'lc-pkg', 'lc-origin', 'lc-coo', 'lc-hs', 'lc-customs-desc']
|
|
46
|
+
window.listingCommerceHasOverrides = () => LC_FIELDS.some(id => ((document.getElementById(id)?.value) || '').trim() !== '') // 新建时据此决定"仓库优先"(有覆盖→先落仓库,全落定再激活)
|
|
47
|
+
// 收集并保存(新建/编辑同用;productId 必须已存在)。留空=null=清除该商品覆盖。复用已验证端点,任一报错即冒泡。opts.activate=全成功后激活(仓库优先新品的收尾)。
|
|
48
|
+
window.listingCommerceSave = async (productId, opts) => {
|
|
49
|
+
if (!productId || !document.getElementById('lc-accept')) return { ok: true } // section 未渲染 → 跳过
|
|
50
|
+
const v = (id) => (document.getElementById(id)?.value || '').trim()
|
|
51
|
+
const parseTpl = (txt) => txt.split('\n').map(l => l.trim()).filter(Boolean).map(l => { const m = l.split(/\s+/); const est = m.slice(2).join(' '); return { region: m[0], fee: Number(m[1]), ...(est ? { est_days: est } : {}) } })
|
|
52
|
+
const parseTax = (txt) => txt.split('\n').map(l => l.trim()).filter(Boolean).map(l => { const m = l.split(/\s+/); const region = m[0]; const last = m[m.length - 1]; const hasRate = m.length > 2 && /^[0-9.]+$/.test(last); const label = m.slice(1, hasRate ? -1 : undefined).join(' '); return { region, label, ...(hasRate ? { rate_pct: Number(last) } : {}), kind: 'included' } }) // 空格安全:label 可含空格(如 Sales Tax);末 token 为数字才当税率
|
|
53
|
+
const srMode = v('lc-sr-mode'); const srCodesArr = v('lc-sr-codes').split(/\s+/).filter(Boolean)
|
|
54
|
+
const tplTxt = v('lc-tpl'); const taxTxt = v('lc-tax'); const q = v('lc-quote'); const fr = v('lc-free')
|
|
55
|
+
const steps = [ // 顺序 fail-fast:校验最重的运费/税费 body 先行 → 它被拒时接单/清关都还没写,避免部分保存(各字段独立无跨字段不变量)
|
|
56
|
+
() => POST('/seller/shipping-template', {
|
|
57
|
+
product_id: productId,
|
|
58
|
+
template: tplTxt ? parseTpl(tplTxt) : null,
|
|
59
|
+
quote_ok: q === '' ? null : q === '1',
|
|
60
|
+
sale_regions: srMode ? { mode: srMode, ...(srMode === 'list' ? { include: srCodesArr } : { exclude: srCodesArr }) } : null,
|
|
61
|
+
free_shipping_threshold: fr === '' ? null : Number(fr),
|
|
62
|
+
import_duty_terms: v('lc-duty') || null,
|
|
63
|
+
tax_lines: taxTxt ? parseTax(taxTxt) : null,
|
|
64
|
+
}),
|
|
65
|
+
() => POST('/seller/accept-mode', { product_id: productId, accept_mode: v('lc-accept') || null }),
|
|
66
|
+
() => PUT(`/products/${productId}`, { weight_kg: v('lc-weight') ? Number(v('lc-weight')) : null, package_size: v('lc-pkg') || null, origin_country: v('lc-origin') || null, country_of_origin: v('lc-coo') || null, customs_description: v('lc-customs-desc') || null, hs_code: v('lc-hs') || null }),
|
|
67
|
+
]
|
|
68
|
+
for (const step of steps) { const r = await step(); if (r && r.error) return { ok: false, error: r.error } }
|
|
69
|
+
if (opts && opts.activate) { const a = await PATCH(`/products/${productId}/status`, { status: 'active' }); if (a && a.error) return { ok: false, error: a.error } } // 仓库优先新品:覆盖全落定 → 激活;激活失败也算失败(商品留仓库=不可售,安全)
|
|
70
|
+
return { ok: true }
|
|
71
|
+
}
|
|
72
|
+
})()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// 协商取消(mutual cancel)前端 —— 争议中订单的无责·双方合意下车口。UI ONLY(真正边界在后端 mutual-cancel 路由)。
|
|
2
|
+
// 握手:任一当事方提议 → 对方确认执行。无责:双方信誉不受影响。托管单买家全额退款+卖家质押返还;直付单零资金仅关单。
|
|
3
|
+
// 状态来自订单详情 DTO 的 order.mutual_cancel(仅 disputed 单计算),同步渲染,无需额外请求。中文 t(),英文 i18n.js _EN。
|
|
4
|
+
|
|
5
|
+
window.mutualCancelCard = (order, isBuyer, isSeller) => {
|
|
6
|
+
if (!order || order.status !== 'disputed' || !(isBuyer || isSeller)) return ''
|
|
7
|
+
const mc = order.mutual_cancel
|
|
8
|
+
if (!mc) return ''
|
|
9
|
+
const oid = order.id
|
|
10
|
+
const settleNote = order.payment_rail === 'direct_p2p' ? t('直付非托管:关闭订单,双方零资金往来') : t('托管:货款全额退回买家,卖家质押原样返还')
|
|
11
|
+
const head = `<div style="font-size:13px;font-weight:700;color:#3730a3;margin-bottom:4px">🤝 ${t('协商取消订单(无责)')}</div>
|
|
12
|
+
<div style="font-size:12px;color:#4b5563;line-height:1.7;margin-bottom:8px">${t('不走判罚:双方同意即可取消这笔争议订单,任何一方信誉都不受影响。')}${settleNote}。</div>`
|
|
13
|
+
let body
|
|
14
|
+
if (mc.proposal && mc.proposal.mine) {
|
|
15
|
+
body = `<div style="font-size:12px;color:#6b7280;margin-bottom:8px">⏳ ${t('你已提议协商取消,等待对方确认。')}</div>
|
|
16
|
+
<button class="btn btn-outline btn-sm" style="width:auto" onclick="mcWithdraw('${oid}')">${t('撤回提议')}</button>`
|
|
17
|
+
} else if (mc.proposal) {
|
|
18
|
+
body = `<div style="font-size:12px;color:#374151;margin-bottom:8px">📩 ${t('对方提议协商取消这笔订单。')}${mc.proposal.reason ? ' 「' + escHtml(mc.proposal.reason) + '」' : ''}</div>
|
|
19
|
+
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
20
|
+
<button class="btn btn-primary btn-sm" style="width:auto" onclick="mcAccept('${oid}')">${t('接受 · 取消订单')}</button>
|
|
21
|
+
<button class="btn btn-outline btn-sm" style="width:auto" onclick="mcDecline('${oid}')">${t('拒绝')}</button>
|
|
22
|
+
</div>`
|
|
23
|
+
} else if (mc.can_propose) {
|
|
24
|
+
body = `<textarea id="mc-reason-${oid}" class="form-control" rows="2" placeholder="${t('原因(可选)')}" style="margin-bottom:8px;font-size:12px"></textarea>
|
|
25
|
+
<button class="btn btn-outline btn-sm" style="width:auto" onclick="mcPropose('${oid}')">${t('提议协商取消')}</button>`
|
|
26
|
+
} else return ''
|
|
27
|
+
return `<div class="card" style="border:1px solid #c7d2fe;background:linear-gradient(135deg,#eef2ff,#e0e7ff)">${head}${body}</div>`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const mcToast = (m, kind) => { if (typeof toast$ === 'function') toast$(m, kind) }
|
|
31
|
+
const mcReload = (oid) => renderOrderDetail(document.getElementById('app'), oid)
|
|
32
|
+
|
|
33
|
+
window.mcPropose = async (oid) => {
|
|
34
|
+
const el = document.getElementById('mc-reason-' + oid)
|
|
35
|
+
const r = await POST('/orders/' + oid + '/mutual-cancel/propose', { reason: (el && el.value || '').trim() })
|
|
36
|
+
if (r.error) return void mcToast(r.error, 'error')
|
|
37
|
+
mcToast(t('已提议协商取消,等待对方确认'), 'success'); mcReload(oid)
|
|
38
|
+
}
|
|
39
|
+
window.mcAccept = async (oid) => {
|
|
40
|
+
if (typeof confirmModal === 'function' && !(await confirmModal(t('确认接受协商取消?订单将被取消,双方无责。'), t('接受'), {}))) return
|
|
41
|
+
const r = await POST('/orders/' + oid + '/mutual-cancel/accept', {})
|
|
42
|
+
if (r.error) return void mcToast(r.error, 'error')
|
|
43
|
+
mcToast(t('订单已协商取消'), 'success'); mcReload(oid)
|
|
44
|
+
}
|
|
45
|
+
window.mcDecline = async (oid) => {
|
|
46
|
+
const r = await POST('/orders/' + oid + '/mutual-cancel/decline', {})
|
|
47
|
+
if (r.error) return void mcToast(r.error, 'error')
|
|
48
|
+
mcReload(oid)
|
|
49
|
+
}
|
|
50
|
+
window.mcWithdraw = async (oid) => {
|
|
51
|
+
const r = await POST('/orders/' + oid + '/mutual-cancel/withdraw', {})
|
|
52
|
+
if (r.error) return void mcToast(r.error, 'error')
|
|
53
|
+
mcReload(oid)
|
|
54
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// 订单状态转移通知 i18n 模板(N1 迁移收口)。UI ONLY —— 服务端 notifyTransition 落库
|
|
2
|
+
// template_key + params {buyer,seller,product,amount,logistics};此处按 viewer locale 用 t() 渲染。
|
|
3
|
+
// 注册进 app-notif-templates.js 的 window.NOTIF_TEMPLATES(本文件在其后加载);旧行/未知 key 回退中文。
|
|
4
|
+
// _dp 变体 = direct_p2p 资金语义分轨(非托管:绝无"资金已释放/退回"话术)。新模板加 i18n.js 双语句对。
|
|
5
|
+
;(function () {
|
|
6
|
+
const S = window._notifSub
|
|
7
|
+
const P = (emoji, titleZh, bodyZh) => (p) => ({ title: emoji + ' ' + t(titleZh), body: S(t(bodyZh), { logistics: t('物流方'), ...p }) })
|
|
8
|
+
Object.assign(window.NOTIF_TEMPLATES, {
|
|
9
|
+
ord_created_paid: P('🛍️', '新订单', '{buyer} 下单了「{product}」,金额 {amount} WAZ。请在 24h 内接单,否则自动退款。'),
|
|
10
|
+
ord_paid_accepted: P('✅', '卖家已接单', '{seller} 已接受你的订单,预计 5 天内发货。'),
|
|
11
|
+
ord_paid_cancelled: P('❌', '订单已取消', '订单「{product}」已取消,{amount} WAZ 将原路退回。'),
|
|
12
|
+
ord_accepted_shipped: P('📦', '商品已发货', '{seller} 已发货,物流 48h 内揽收后你可以追踪包裹。'),
|
|
13
|
+
ord_shipped_picked_up: P('🚚', '物流已揽收', '包裹已由 {logistics} 揽收,正在运输中。'),
|
|
14
|
+
ord_picked_up_in_transit: P('🚛', '包裹运输中', '你的「{product}」正在运输途中。'),
|
|
15
|
+
ord_in_transit_delivered: P('📬', '包裹已投递', '你的包裹已送达,请确认收货。72 小时内未确认将自动完成。'),
|
|
16
|
+
ord_delivered_confirmed: P('💰', '买家确认收货', '{buyer} 已确认收货,{amount} WAZ 结算中。'),
|
|
17
|
+
ord_delivered_confirmed_dp: P('✅', '买家确认收货', '{buyer} 已确认收货,订单完成。直付为非托管:货款由你与买家场外结算,协议不代收、无平台资金入账。'),
|
|
18
|
+
ord_confirmed_completed: P('✅', '交易完成,资金到账', '订单「{product}」交易完成,收益已入账,查看钱包确认。'),
|
|
19
|
+
ord_confirmed_completed_dp: P('✅', '交易完成', '订单「{product}」交易完成。直付为非托管:无平台资金结算,货款以你与买家场外结算为准。'),
|
|
20
|
+
ord_paid_disputed: P('⚠️', '买家发起争议', '{buyer} 对订单「{product}」发起了争议。请在 48 小时内提交反驳证据,否则协议自动裁定退款。'),
|
|
21
|
+
ord_accepted_disputed: P('⚠️', '买家发起争议', '{buyer} 对订单「{product}」发起了争议,请在 48h 内回应。'),
|
|
22
|
+
ord_shipped_disputed: P('⚠️', '发生争议', '订单「{product}」出现争议,请提交相关证据。'),
|
|
23
|
+
ord_in_transit_disputed: P('⚠️', '运输中发生争议', '订单「{product}」运输过程中发生争议,请及时回应。'),
|
|
24
|
+
ord_delivered_disputed: P('⚠️', '买家对收货发起争议', '{buyer} 声称货物有问题,已发起争议。请在 48h 内提交证据。'),
|
|
25
|
+
ord_disputed_completed: P('⚖️', '争议裁定:卖家胜诉', '订单「{product}」争议已裁定,资金已释放给卖家。'),
|
|
26
|
+
ord_disputed_completed_dp: P('⚖️', '争议裁定:卖家胜诉', '订单「{product}」争议已裁定:卖家胜诉(直付为信誉裁决,不涉资金流转)。'),
|
|
27
|
+
ord_disputed_cancelled: P('⚖️', '争议裁定:退款买家', '订单「{product}」争议已裁定,{amount} WAZ 已退回买家。'),
|
|
28
|
+
ord_disputed_cancelled_dp: P('⚖️', '争议裁定:支持买家', '订单「{product}」争议已裁定支持买家。直付非托管:平台无资金可退,退款由双方场外处理;卖家信誉处罚已记录。'),
|
|
29
|
+
ord_paid_fault_seller: P('⏰', '卖家超时违约', '卖家超时未接单,订单已自动取消,{amount} WAZ 退款处理中。'),
|
|
30
|
+
ord_accepted_fault_seller: P('⏰', '卖家超时未发货', '卖家超时未发货,订单已判违约,资金退回。'),
|
|
31
|
+
ord_accepted_fault_seller_dp: P('⏰', '卖家超时未发货', '卖家超时未发货,订单已判卖家违约(直付非托管:平台无资金可退,违约已记入卖家信誉;退款请与卖家场外协商,协商未果可发起争议)。'),
|
|
32
|
+
ord_in_transit_fault_logistics: P('⏰', '物流超时', '物流方超时未完成投递,已自动记录违约。'),
|
|
33
|
+
ord_accepted_payment_query: P('🔎', '卖家未收到货款', '卖家报告尚未收到「{product}」的货款,请核实:若确已付款请提供付款参考,若未付款可取消订单。直付非托管,协议不代收/不退款。'),
|
|
34
|
+
ord_payment_query_accepted: P('✅', '卖家已确认收款', '卖家已确认收到「{product}」的货款,订单恢复,等待发货。'),
|
|
35
|
+
ord_payment_query_disputed: P('⚖️', '货款协商升级举证仲裁', '「{product}」货款协商未果,已进入举证仲裁(证据制信誉裁决,非托管:不涉退款/放款)。请提交证据。'),
|
|
36
|
+
ord_disputed_payment_query: P('↩️', '仲裁已撤回,回到协商', '「{product}」的仲裁申请已撤回,回到买卖双方协商。'),
|
|
37
|
+
ord_payment_query_cancelled: P('🚫', '直付订单已取消(协商)', '「{product}」订单已取消(货款协商未达成)。直付非托管,无平台退款。'),
|
|
38
|
+
// ── N3:平台服务费预充值申请三向通知(admin 待审 / 卖家获批 / 卖家被拒)──
|
|
39
|
+
dp_fee_prepay_requested: P('💳', '新预充值申请待审', '卖家 {seller} 申请平台服务费预充值 {amount} USDC,请到 admin 后台核对到账后处理。'),
|
|
40
|
+
dp_fee_prepay_approved: P('✅', '预充值已确认入账', '你的平台服务费预充值 {amount} USDC 已确认入账,直付新单额度已恢复。'),
|
|
41
|
+
dp_fee_prepay_rejected: P('❌', '预充值申请未通过', '你的平台服务费预充值申请未通过{note}。请核对付款凭据后重新提交,或联系平台。'),
|
|
42
|
+
})
|
|
43
|
+
})()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// 通知 i18n 模板注册表(N1,审计项 B)。UI ONLY。
|
|
2
|
+
// 服务端落库 template_key + params(JSON);此处按 viewer 当前 locale 用 t() 渲染整句(占位符 {k} 替换)。
|
|
3
|
+
// 旧行/未知 key/解析失败 → 一律回退存量中文 title/body(notifRender 返回原 n,向后兼容零迁移)。
|
|
4
|
+
// 新模板加这里 + i18n.js 双语句对;title 约定「emoji+空格」开头(列表把前 2 字符当图标)。
|
|
5
|
+
window._notifSub = (s, p) => String(s).replace(/\{(\w+)\}/g, (_, k) => (p && p[k] != null) ? String(p[k]) : '')
|
|
6
|
+
window.NOTIF_TEMPLATES = {
|
|
7
|
+
dp_new_order: (p) => ({ title: '🛒 ' + t('新直付订单,等买家付款'), body: window._notifSub(t('商品「{product}」× {qty},应付 {amount} USDC。买家完成场外付款并标记后你会收到发货提醒。'), p) }),
|
|
8
|
+
dp_marked_paid: (p) => ({ title: '💰 ' + t('买家已标记付款,请核对后发货'), body: window._notifSub(t('{detail}。请核对银行/收款App流水后再发货;未收到请点"未收到货款"。'), p) }),
|
|
9
|
+
dp_window_expired: (p) => ({ title: '⏰ ' + t('直付付款窗口已过期'), body: window._notifSub(t('若你已付款:请在 {graceHours} 小时宽限期内到订单页提交付款凭证发起争议;未付款可直接关闭订单,否则宽限期满将自动取消。'), p) }),
|
|
10
|
+
dp_grace_cancelled_buyer: () => ({ title: '🚫 ' + t('直付订单已自动取消'), body: t('付款窗口与宽限期均已过且未收到你的付款标记/凭证,订单已关闭。若你确已付款,请通过订单页联系卖家协商。') }),
|
|
11
|
+
dp_grace_cancelled_seller: () => ({ title: '🚫 ' + t('直付订单已自动取消(买家未付款)'), body: t('买家未在付款窗口+宽限期内付款,订单已自动关闭,库存已恢复。') }),
|
|
12
|
+
}
|
|
13
|
+
// 单条通知 → 本地化渲染(不可变:返回带覆盖 title/body 的浅拷贝或原对象)。
|
|
14
|
+
window.notifRender = (n) => {
|
|
15
|
+
try {
|
|
16
|
+
const f = n && n.template_key && window.NOTIF_TEMPLATES[n.template_key]
|
|
17
|
+
if (!f) return n
|
|
18
|
+
const p = typeof n.params === 'string' ? JSON.parse(n.params || '{}') : (n.params || {})
|
|
19
|
+
const r = f(p) || {}
|
|
20
|
+
return { ...n, title: r.title || n.title, body: r.body || n.body }
|
|
21
|
+
} catch { return n }
|
|
22
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// 接单模式+运费模板+询价系列 UI(PR-4;后端 v16-v18 已合)。UI ONLY,边界全在后端。中文 t(),英文 i18n.js _EN。
|
|
2
|
+
// ① 通知模板注册(PR-1..3 的 9 个 key);② pending_accept 徽标/标签;③ 买单 sheet 收货地区选择+运费预览(自调度 hydrate);④ 订单页接单/询价卡;⑤ 卖家店铺设置区块(接单/模板/询价)。
|
|
3
|
+
;(function () {
|
|
4
|
+
const S = window._notifSub
|
|
5
|
+
const P = (emoji, titleZh, bodyZh) => (p) => ({ title: emoji + ' ' + t(titleZh), body: S(t(bodyZh), p) })
|
|
6
|
+
Object.assign(window.NOTIF_TEMPLATES, {
|
|
7
|
+
dp_pending_accept_new: P('🛎️', '新直付订单,待你确认接单', '商品「{product}」× {qty},应付 {amount} USDC。请在 {hours} 小时内确认接单(核实可发货/物流),超时订单自动取消;接单后买家才会看到收款方式。'),
|
|
8
|
+
dp_pending_accept_accepted: P('✅', '卖家已确认接单,请付款', '卖家已确认可发货并接单。请在 {hours} 小时内完成风险确认后查看收款方式并付款;逾期未付订单将进入超时流程。'),
|
|
9
|
+
dp_pending_accept_declined: P('❌', '卖家未能接单,订单已取消', '卖家未能确认发货{reason}。订单已无责取消 —— 你尚未付款,无需任何操作;双方信誉均不受影响。'),
|
|
10
|
+
dp_pending_accept_cancelled: P('↩️', '买家已撤单', '买家在你确认接单前撤回了订单。订单已无责取消,库存已恢复。'),
|
|
11
|
+
dp_pending_accept_expired_buyer: P('⏰', '卖家超时未接单,订单已取消', '卖家未在接单窗口内确认,订单已自动取消 —— 你尚未付款,无需任何操作。可换商品或联系卖家后重新下单。'),
|
|
12
|
+
dp_pending_accept_expired_seller: P('⏰', '订单因超时未接单已取消', '你未在接单窗口内确认接单,订单已自动取消,库存已恢复。频繁超时会影响买家体验,可考虑改用自动接单或缩短响应时间。'),
|
|
13
|
+
dp_quote_needed: P('🛎️', '新直付订单(模板外地区),待你报价运费', '商品「{product}」× {qty},货款 {amount} USDC,收货地区 {region} 不在你的运费模板内。请在 {hours} 小时内核实可达并报价(运费+预计时效),买家确认后才进入付款;超时订单自动取消。'),
|
|
14
|
+
dp_quote_submitted: P('📦', '卖家已报价运费,请确认', '卖家确认可发货并报价:运费 {fee} USDC{est} 天。新总额 {total} USDC。请在 {hours} 小时内确认(确认后进入付款环节)或撤单;逾期订单自动取消。'),
|
|
15
|
+
dp_quote_confirmed: P('✅', '买家已确认运费报价', '买家已确认新总额 {total} USDC(含运费 {fee}),订单进入付款窗口。买家完成场外付款并标记后你会收到发货提醒。'),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
// ② 状态徽标/标签(app.js dp 链尾)
|
|
19
|
+
window.dpAcceptBadge = (s) => s === 'pending_accept' ? ['amber', t('待卖家接单')] : null
|
|
20
|
+
window.dpAcceptLabel = (s) => s === 'pending_accept' ? t('等待卖家确认接单') : null
|
|
21
|
+
|
|
22
|
+
// ③ 买单 sheet 收货地区(渲染期调用 → 调度 hydrate;无模板商品自动隐藏,零打扰)
|
|
23
|
+
window.shipRegionBlockHtml = (productId) => {
|
|
24
|
+
setTimeout(() => window._shipHydrateRegion(productId), 60)
|
|
25
|
+
return `<div id="ship-region-block" style="display:none;margin-top:10px"></div>`
|
|
26
|
+
}
|
|
27
|
+
window._shipHydrateRegion = async (productId) => {
|
|
28
|
+
const box = document.getElementById('ship-region-block'); if (!box) return
|
|
29
|
+
const o = await GET(`/products/${productId}/shipping-options`).catch(() => null)
|
|
30
|
+
if (!o || !o.region_required || !window._shipRegionInputHtml) return
|
|
31
|
+
box.innerHTML = window._shipRegionInputHtml(o) // 入口 HTML 委托 app-purchase-terms-ui(据模板/可售白名单/自由输入择一,含无模板+可售区的填写入口)
|
|
32
|
+
box.style.display = 'block'
|
|
33
|
+
}
|
|
34
|
+
window.shipSelectedRegion = () => {
|
|
35
|
+
const sel = document.getElementById('ship-region-select')
|
|
36
|
+
const other = () => (document.getElementById('ship-region-other')?.value || '').trim().toUpperCase() || undefined
|
|
37
|
+
if (!sel) return other() // 无下拉=自由输入态(无模板+排除式可售区/仅平台限制)
|
|
38
|
+
if (sel.value === '__other') return other()
|
|
39
|
+
return sel.value || undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ④ 订单页接单/询价卡(仅 direct_p2p + pending_accept;买卖双方各自动作;边界在后端,这里只是表单)
|
|
43
|
+
window.dpPendingAcceptCard = (order, isBuyer, isSeller) => {
|
|
44
|
+
if (!order || order.payment_rail !== 'direct_p2p' || order.status !== 'pending_accept' || !(isBuyer || isSeller)) return ''
|
|
45
|
+
const oid = order.id
|
|
46
|
+
const quote = Number(order.shipping_quote_required) === 1
|
|
47
|
+
const quoted = quote && order.shipping_quote_fee != null
|
|
48
|
+
const dl = order.pending_accept_deadline ? `<div style="font-size:11px;color:#92400e;margin-top:6px">⏳ ${t('响应截止')}:${fmtTime(order.pending_accept_deadline)}(${t('超时自动取消,无人担责')})</div>` : ''
|
|
49
|
+
const head = `<div style="font-size:13px;font-weight:700;color:#92400e;margin-bottom:4px">🛎️ ${quote ? t('询价接单(模板外地区)') : t('等待卖家确认接单')}</div>`
|
|
50
|
+
let body = ''
|
|
51
|
+
if (isSeller && quote && !quoted) {
|
|
52
|
+
body = `<div style="font-size:12px;color:#374151;margin-bottom:8px">${t('收货地区')} <strong>${escHtml(order.ship_to_region || '-')}</strong> ${t('不在你的运费模板内。请核实物流可达性后报价;买家确认新总额后订单才进入付款。')}</div>
|
|
53
|
+
<div style="display:flex;gap:6px;margin-bottom:6px">
|
|
54
|
+
<input class="form-control" id="dp-qt-fee-${oid}" type="number" min="0" step="0.01" placeholder="${t('运费(USDC)')}" style="flex:1;font-size:12px">
|
|
55
|
+
<input class="form-control" id="dp-qt-est-${oid}" maxlength="20" placeholder="${t('预计时效,如 10-15')}" style="flex:1;font-size:12px">
|
|
56
|
+
</div>
|
|
57
|
+
<input class="form-control" id="dp-qt-note-${oid}" maxlength="200" placeholder="${t('备注(物流方式等,可选,买家可见)')}" style="margin-bottom:8px;font-size:12px">
|
|
58
|
+
<div style="display:flex;gap:8px"><button class="btn btn-primary btn-sm" style="width:auto" onclick="dpQuoteSubmit('${oid}')">${t('提交报价')}</button>
|
|
59
|
+
<button class="btn btn-outline btn-sm" style="width:auto;color:#dc2626;border-color:#fecaca" onclick="dpAcceptDecline('${oid}')">${t('无法配送,谢绝')}</button></div>`
|
|
60
|
+
} else if (isSeller && quote && quoted) {
|
|
61
|
+
body = `<div style="font-size:12px;color:#6b7280;margin-bottom:8px">📦 ${t('你已报价')}:${t('运费')} ${order.shipping_quote_fee} USDC${order.shipping_quote_est_days ? ` · ${escHtml(order.shipping_quote_est_days)} ${t('天')}` : ''}。${t('等待买家确认;确认前可重新报价修正。')}</div>
|
|
62
|
+
<div style="display:flex;gap:6px;margin-bottom:6px"><input class="form-control" id="dp-qt-fee-${oid}" type="number" min="0" step="0.01" placeholder="${t('修正运费(USDC)')}" style="flex:1;font-size:12px"><input class="form-control" id="dp-qt-est-${oid}" maxlength="20" placeholder="${t('预计时效,如 10-15')}" style="flex:1;font-size:12px"></div>
|
|
63
|
+
<div style="display:flex;gap:8px"><button class="btn btn-outline btn-sm" style="width:auto" onclick="dpQuoteSubmit('${oid}')">${t('重新报价')}</button>
|
|
64
|
+
<button class="btn btn-outline btn-sm" style="width:auto;color:#dc2626;border-color:#fecaca" onclick="dpAcceptDecline('${oid}')">${t('无法配送,谢绝')}</button></div>`
|
|
65
|
+
} else if (isSeller) {
|
|
66
|
+
body = `<div style="font-size:12px;color:#374151;margin-bottom:8px">${t('买家已下单(尚未付款)。请核实库存与物流后确认接单;接单后买家才会看到收款方式。')}</div>
|
|
67
|
+
<input class="form-control" id="dp-acc-reason-${oid}" maxlength="200" placeholder="${t('谢绝理由(选填,买家可见)')}" style="margin-bottom:8px;font-size:12px">
|
|
68
|
+
<div style="display:flex;gap:8px"><button class="btn btn-primary btn-sm" style="width:auto" onclick="dpAcceptOk('${oid}')">${t('确认接单')}</button>
|
|
69
|
+
<button class="btn btn-outline btn-sm" style="width:auto;color:#dc2626;border-color:#fecaca" onclick="dpAcceptDecline('${oid}')">${t('无法接单,谢绝')}</button></div>`
|
|
70
|
+
} else if (isBuyer && quote && quoted) {
|
|
71
|
+
const newTotal = Math.round((Number(order.total_amount) + Number(order.shipping_quote_fee)) * 100) / 100
|
|
72
|
+
body = `<div style="font-size:12px;color:#374151;margin-bottom:8px">📦 ${t('卖家已报价')}:${t('运费')} <strong>${order.shipping_quote_fee} USDC</strong>${order.shipping_quote_est_days ? ` · ${t('预计时效')} ${escHtml(order.shipping_quote_est_days)} ${t('天')}` : ''}${order.shipping_quote_note ? `<br>💬 ${escHtml(order.shipping_quote_note)}` : ''}<br>${t('新总额')}:<strong>${newTotal} USDC</strong>(${t('货款')} ${order.total_amount} + ${t('运费')} ${order.shipping_quote_fee})</div>
|
|
73
|
+
<div style="display:flex;gap:8px"><button class="btn btn-primary btn-sm" style="width:auto" onclick="dpQuoteConfirm('${oid}', ${newTotal})">${t('接受报价,进入付款')}</button>
|
|
74
|
+
<button class="btn btn-outline btn-sm" style="width:auto" onclick="dpAcceptWithdraw('${oid}')">${t('不接受,撤单')}</button></div>`
|
|
75
|
+
} else if (isBuyer) {
|
|
76
|
+
body = `<div style="font-size:12px;color:#6b7280;margin-bottom:8px">⏳ ${quote ? t('等待卖家核实物流并报价运费;确认报价前你无需付款。') : t('等待卖家确认接单;接单后你才会看到收款方式并进入付款。')}</div>
|
|
77
|
+
<button class="btn btn-outline btn-sm" style="width:auto" onclick="dpAcceptWithdraw('${oid}')">${t('撤回订单')}</button>`
|
|
78
|
+
}
|
|
79
|
+
return `<div class="card" style="border:1px solid #fde68a;background:linear-gradient(135deg,#fffbeb,#fef3c7)">${head}${body}${dl}</div>`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const rload = (oid) => renderOrderDetail(document.getElementById('app'), oid)
|
|
83
|
+
window.dpAcceptOk = async (oid) => {
|
|
84
|
+
const r = await POST(`/orders/${oid}/pending-accept/accept`, {})
|
|
85
|
+
if (r.error) return void toast$(r.error, 'error')
|
|
86
|
+
toast$(t('已接单,买家进入付款环节'), 'success'); rload(oid)
|
|
87
|
+
}
|
|
88
|
+
window.dpAcceptDecline = async (oid) => {
|
|
89
|
+
if (typeof confirmModal === 'function' && !(await confirmModal(t('确认谢绝接单?订单将无责取消(买家尚未付款),库存自动恢复。'), t('谢绝接单'), { danger: true }))) return
|
|
90
|
+
const el = document.getElementById('dp-acc-reason-' + oid)
|
|
91
|
+
const r = await POST(`/orders/${oid}/pending-accept/decline`, { reason: (el && el.value || '').trim() })
|
|
92
|
+
if (r.error) return void toast$(r.error, 'error')
|
|
93
|
+
rload(oid)
|
|
94
|
+
}
|
|
95
|
+
window.dpAcceptWithdraw = async (oid) => {
|
|
96
|
+
if (typeof confirmModal === 'function' && !(await confirmModal(t('确认撤回订单?订单将无责取消,你尚未付款,无需任何操作。'), t('撤回订单'), { danger: true }))) return
|
|
97
|
+
const r = await POST(`/orders/${oid}/pending-accept/cancel`, {})
|
|
98
|
+
if (r.error) return void toast$(r.error, 'error')
|
|
99
|
+
rload(oid)
|
|
100
|
+
}
|
|
101
|
+
window.dpQuoteSubmit = async (oid) => {
|
|
102
|
+
const fee = Number(document.getElementById('dp-qt-fee-' + oid)?.value)
|
|
103
|
+
if (!isFinite(fee) || fee < 0) return void toast$(t('请填写合法运费'), 'error')
|
|
104
|
+
const r = await POST(`/orders/${oid}/pending-accept/quote`, { shipping_fee: fee, est_days: (document.getElementById('dp-qt-est-' + oid)?.value || '').trim() || undefined, note: (document.getElementById('dp-qt-note-' + oid)?.value || '').trim() || undefined })
|
|
105
|
+
if (r.error) return void toast$(r.error, 'error')
|
|
106
|
+
toast$(t('已报价,等待买家确认'), 'success'); rload(oid)
|
|
107
|
+
}
|
|
108
|
+
window.dpQuoteConfirm = async (oid, newTotal) => {
|
|
109
|
+
if (typeof confirmModal === 'function' && !(await confirmModal(S(t('确认接受报价?订单总额将变为 {total} USDC(含运费),随后进入付款环节。'), { total: newTotal }), t('接受报价'), {}))) return
|
|
110
|
+
const r = await POST(`/orders/${oid}/pending-accept/confirm-quote`, {})
|
|
111
|
+
if (r.error) return void toast$(r.error, 'error')
|
|
112
|
+
toast$(t('已确认,进入付款环节'), 'success'); rload(oid)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ⑤ 卖家店铺设置区块(settings tab 链尾;hydrate 回显)。模板编辑 = 行式文本:每行 "地区 运费 [时效]",* 为兜底。
|
|
116
|
+
window.shipSellerSettingsSection = () => `
|
|
117
|
+
<div class="card" id="ship-settings-card">
|
|
118
|
+
<div style="font-size:14px;font-weight:700;margin-bottom:6px">🛎️ ${t('接单与运费(店铺默认)')}</div>
|
|
119
|
+
<div style="font-size:11px;color:#6b7280;margin-bottom:8px">${t('单品可在接单/运费接口单独覆盖;设置只影响之后的新订单。')}</div>
|
|
120
|
+
<div id="ship-settings-body" style="font-size:12px;color:#6b7280">${loading$()}</div>
|
|
121
|
+
</div>`
|
|
122
|
+
window.shipHydrateSellerSettings = async () => {
|
|
123
|
+
const box = document.getElementById('ship-settings-body'); if (!box) return
|
|
124
|
+
const s = await GET('/seller/shipping-settings').catch(() => null)
|
|
125
|
+
if (!s) { box.textContent = t('加载失败,请刷新'); return }
|
|
126
|
+
const tplText = (s.store_template || []).map(e => `${e.region} ${e.fee}${e.est_days ? ' ' + e.est_days : ''}`).join('\n')
|
|
127
|
+
box.innerHTML = `
|
|
128
|
+
<label class="form-label" style="font-size:12px">${t('接单模式')}</label>
|
|
129
|
+
<select class="form-control" id="ship-set-mode" style="font-size:13px;margin-bottom:8px">
|
|
130
|
+
<option value="" ${s.store_accept_mode == null ? 'selected' : ''}>${t('默认(自动接单)')}</option>
|
|
131
|
+
<option value="auto" ${s.store_accept_mode === 'auto' ? 'selected' : ''}>${t('自动接单(下单/付款后直接进入下一环节)')}</option>
|
|
132
|
+
<option value="manual" ${s.store_accept_mode === 'manual' ? 'selected' : ''}>${t('手动接单(直付:买家付款前须你确认;担保:维持付款后确认)')}</option>
|
|
133
|
+
</select>
|
|
134
|
+
<label class="form-label" style="font-size:12px">${t('运费模板(每行:地区代码 运费 [预计时效];* 为其余地区兜底;满额免邮在「营销」页配置)')}</label>
|
|
135
|
+
<textarea class="form-control" id="ship-set-tpl" rows="4" placeholder="CN 0 2-4 SG 5 3-5 * 25 10-20" style="font-size:12px;font-family:monospace;margin-bottom:8px">${escHtml(tplText)}</textarea>
|
|
136
|
+
<label style="display:flex;align-items:center;gap:8px;font-size:12px;color:#374151;margin-bottom:10px">
|
|
137
|
+
<input type="checkbox" id="ship-set-quote" style="width:16px;height:16px" ${s.store_quote_ok ? 'checked' : ''}>
|
|
138
|
+
${t('接受模板外地区询价(直付):先报运费/时效,买家确认后再付款')}
|
|
139
|
+
</label>
|
|
140
|
+
<button class="btn btn-primary btn-sm" style="width:auto" onclick="shipSaveSellerSettings()">${t('保存接单与运费设置')}</button>
|
|
141
|
+
<div id="ship-set-msg" style="margin-top:6px;font-size:11px"></div>`
|
|
142
|
+
}
|
|
143
|
+
window.shipSaveSellerSettings = async () => {
|
|
144
|
+
const msg = document.getElementById('ship-set-msg')
|
|
145
|
+
const mode = document.getElementById('ship-set-mode').value || null
|
|
146
|
+
const lines = (document.getElementById('ship-set-tpl').value || '').split('\n').map(l => l.trim()).filter(Boolean)
|
|
147
|
+
const entries = []
|
|
148
|
+
for (const l of lines) {
|
|
149
|
+
const m = l.split(/\s+/)
|
|
150
|
+
if (m.length < 2) { msg.innerHTML = `<span style="color:#dc2626">${t('格式错误')}: ${escHtml(l)}</span>`; return }
|
|
151
|
+
entries.push({ region: m[0], fee: Number(m[1]), ...(m[2] ? { est_days: m[2] } : {}) })
|
|
152
|
+
}
|
|
153
|
+
const r1 = await POST('/seller/accept-mode', { store_accept_mode: mode })
|
|
154
|
+
const r2 = await POST('/seller/shipping-template', { store_template: entries.length ? entries : null, store_quote_ok: !!document.getElementById('ship-set-quote').checked })
|
|
155
|
+
if (r1.error || r2.error) { msg.innerHTML = `<span style="color:#dc2626">${r1.error || r2.error}</span>`; return }
|
|
156
|
+
msg.innerHTML = `<span style="color:#16a34a">✓ ${t('已保存')}</span>`
|
|
157
|
+
}
|
|
158
|
+
})()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// 交易流程错误码 → 双语文案。后端各路由返回 { error, error_code };前端统一经此映射为 t() 双语,
|
|
2
|
+
// 避免把中文 error 原样弹给英文界面(如 NOT_ORDER_BUYER 未映射→回退原始中文,即"只能确认自己订单的风险披露"混入英文 UI 的 bug)。
|
|
3
|
+
// 覆盖 orders-action(/orders/:id/action + confirm-in-person)与 direct-pay-disclosure-acks 的用户可达 error_code。
|
|
4
|
+
// dpErrorText(直付可用性/建单)也回退查此表。新增后端 error_code 须补此表 —— test-order-errors.ts 完整性守。
|
|
5
|
+
// 中文 t(),英文 i18n.js _EN(双语 parity 同样由 test-order-errors.ts 守)。
|
|
6
|
+
window.orderErrorLookup = (code) => ({
|
|
7
|
+
// 角色 / 所有权
|
|
8
|
+
NOT_ORDER_BUYER: t('只能操作自己的订单'), NOT_ORDER_SELLER: t('你不是本订单的卖家'),
|
|
9
|
+
NOT_ORDER_LOGISTICS: t('你不是本订单的物流方'), NOT_ORDER_PARTY: t('只有买卖双方可操作'),
|
|
10
|
+
TRUSTED_ROLE_NO_TRADE: t('运营角色账号不可参与订单流转'),
|
|
11
|
+
// 状态门
|
|
12
|
+
NOT_ACCEPTED: t('仅可在待发货阶段进行此操作'),
|
|
13
|
+
NOT_PAYMENT_QUERY: t('仅可在货款协商阶段进行此操作'),
|
|
14
|
+
NOT_DISPUTED: t('仅可在争议阶段撤回'),
|
|
15
|
+
NOT_DIRECT_PAY: t('该操作仅适用于直付订单'),
|
|
16
|
+
NOT_DIRECT_PAY_WINDOW: t('该操作仅适用于直付订单的付款窗口/协商阶段'),
|
|
17
|
+
NOT_DIRECT_PAY_ORDER: t('风险披露仅适用于直付订单'),
|
|
18
|
+
NOT_IN_PERSON: t('该订单不是面交订单'),
|
|
19
|
+
NOT_CONFIRMABLE_IN_PERSON: t('当前订单状态不可确认面交完成'),
|
|
20
|
+
HAS_PENDING_CLAIM: t('存在进行中的验证任务,暂不可确认'),
|
|
21
|
+
// 货款协商 / 争议 / 撤回
|
|
22
|
+
GRACE_NOT_ELAPSED: t('买家响应宽限期未过,暂不可申请取消'),
|
|
23
|
+
DISPUTE_ALREADY_RULED: t('争议已裁定,不可撤回'),
|
|
24
|
+
NOT_PAYMENT_QUERY_DISPUTE: t('仅可撤回由货款协商升级的仲裁;履约类争议(货损/货不对版)须经仲裁裁定'),
|
|
25
|
+
WITHDRAW_FAILED: t('撤回失败,请重试'), CANCEL_FAILED: t('取消失败,请重试'), DIRECT_PAY_SETTLE_FAILED: t('直付完成结算失败,订单未完成,可重试'),
|
|
26
|
+
// 买家撤诉并确认收货(dispute_withdraw_confirm)
|
|
27
|
+
ORDER_NOT_DISPUTED: t('订单不在争议中,不可撤诉确认收货'), NOT_DISPUTE_INITIATOR: t('仅争议发起人可撤诉'),
|
|
28
|
+
NOT_FULFILMENT_DISPUTE: t('仅"投递后未收到货/货有问题"类争议可撤诉确认收货;货款争议请用"撤回仲裁·回到协商"'),
|
|
29
|
+
DISPUTE_CLOSE_SETTLE_FAILED: t('撤诉确认收货失败,订单未变更(仍在争议中),请重试'),
|
|
30
|
+
ORDER_NOT_DELIVERED: t('订单尚未送达,暂不可确认收货'),
|
|
31
|
+
// RFC-007 拒单 / 举证
|
|
32
|
+
NOT_PROVISIONAL_DECLINE: t('本订单不是可举证的临时判责状态'),
|
|
33
|
+
ALREADY_CONTESTED: t('已在仲裁中,无需重复发起'),
|
|
34
|
+
CONTEST_WINDOW_CLOSED: t('举证窗口已过期'),
|
|
35
|
+
DECLINE_REASON_INVALID: t('拒单理由无效'),
|
|
36
|
+
DECLINE_WRONG_STATUS: t('当前状态不可拒单'),
|
|
37
|
+
DECLINE_SETTLEMENT_FAILED: t('拒单结算失败,请重试'),
|
|
38
|
+
// 直付 RISK 动作门(mark_paid/confirm/confirm_in_person 缺披露/缺 Passkey)。与 dpErrorText 同文案,
|
|
39
|
+
// 保证无论走 dpHandleAction(dpErrorText)还是通用 handleAction(orderErrorText)路径都双语一致。
|
|
40
|
+
DISCLOSURE_NOT_ACKED: t('需先完成两次风险披露确认(D1 + D2)'),
|
|
41
|
+
HUMAN_PRESENCE_REQUIRED: t('需现场真人 Passkey 确认'),
|
|
42
|
+
PASSKEY_REQUIRED_FOR_DIRECT_PAY: t('直付需要先注册 Passkey'),
|
|
43
|
+
// 通用
|
|
44
|
+
ORDER_NOT_FOUND: t('订单不存在'),
|
|
45
|
+
MISSING_ORDER_ID: t('缺少订单号'),
|
|
46
|
+
INVALID_STAGE: t('无效的披露阶段参数'),
|
|
47
|
+
}[code])
|
|
48
|
+
|
|
49
|
+
// 统一取文案:先查映射,未命中回退后端原始 error(仍是文案而非裸 JSON),再回退通用兜底。
|
|
50
|
+
window.orderErrorText = (code, fallback) => (code && window.orderErrorLookup(code)) || fallback || t('操作失败,请重试')
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// 直付(非托管)争议的 rail-aware 展示文案。UI ONLY。
|
|
2
|
+
// 非托管裁决 = 仅信誉:协议不持货款,【不退款、不释放资金、不收仲裁费】。故订单终态标签、时间线、仲裁面板都【绝不】
|
|
3
|
+
// 出现"全额退款/部分退款/资金释放/仲裁费"等托管语义;改用胜负/责任(信誉裁决)语义。托管(escrow)订单沿用原文案。
|
|
4
|
+
// 面向用户中文走 t(),英文在 i18n.js _EN(双语 parity 由 test-direct-pay-ui.ts 守)。
|
|
5
|
+
window.dpTerminalBadge = (status) => ({ refunded_full: ['blue', t('买家胜诉(信誉裁决)')], refunded_partial: ['blue', t('部分责任(信誉裁决)')], resolved_for_seller: ['green', t('卖家胜诉(信誉裁决)')] })[status] || null
|
|
6
|
+
window.dpTerminalLabel = (status) => ({ refunded_full: t('买家胜诉(信誉裁决)'), refunded_partial: t('部分责任(信誉裁决)') })[status] || null
|
|
7
|
+
// 争议裁定(ruling)→ 展示标题的 rail-aware 映射。direct_p2p 用胜负/责任(信誉裁决),不走全局 RULING_LABELS(退款/资金释放)。
|
|
8
|
+
window.dpRulingLabel = (ruling) => ({ refund_buyer: t('买家胜诉(信誉裁决)'), release_seller: t('卖家胜诉(信誉裁决)'), partial_refund: t('部分责任(信誉裁决)'), liability_split: t('责任分配(信誉裁决)'), dismiss_to_negotiation: t('驳回仲裁,退回协商') })[ruling] || null
|
|
9
|
+
|
|
10
|
+
// 仲裁面板顶部提示:非托管 → "仅信誉裁决,不涉资金";托管 → 原仲裁费提示。
|
|
11
|
+
window.dpArbFeeNote = (rail) => rail === 'direct_p2p'
|
|
12
|
+
? `<div style="font-size:12px;color:#1e40af;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;padding:6px 10px;margin-bottom:8px">🏷️ ${t('非托管(直付)争议:仅信誉裁决,不发生退款 / 资金释放 / 仲裁费。')}</div>`
|
|
13
|
+
: `<div style="font-size:12px;color:#92400e;background:#fffbeb;border:1px solid #fcd34d;border-radius:6px;padding:6px 10px;margin-bottom:8px">💰 败诉方须缴纳仲裁费:订单金额 × 1%(最低 1 WAZ)。部分退款时双方各付 0.5%。仲裁费 50% 归仲裁员,50% 归协议。</div>`
|
|
14
|
+
|
|
15
|
+
// 仲裁裁定选项:非托管用胜负/责任(信誉裁决)语义;托管保留原退款/释放语义。返回 [value,label][]。
|
|
16
|
+
// direct_p2p 无金额/无赔付,故【不含】liability_split(多方赔付分配是托管/物流概念);只保留胜负/部分责任的信誉裁决。
|
|
17
|
+
window.dpArbRulingOptions = (rail, canDismiss) => (rail === 'direct_p2p' /* canDismiss=direct_p2p+pq-origin → 追加"驳回仲裁,退回协商"非胜负裁决 */
|
|
18
|
+
? [['refund_buyer', '🔵 ' + t('判买家胜诉(信誉裁决)')], ['release_seller', '🟢 ' + t('判卖家胜诉(信誉裁决)')], ['partial_refund', '🟡 ' + t('判部分责任(信誉裁决)')]]
|
|
19
|
+
: [['refund_buyer', '🔵 全额退款买家(买家胜诉,卖家承担)'], ['release_seller', '🟢 资金释放给卖家(卖家胜诉)'], ['partial_refund', '🟡 部分退款(折中,需填金额)'], ['liability_split', '⚖️ 责任分配(指定各方赔付额)']]).concat(canDismiss ? [['dismiss_to_negotiation', '↩️ ' + t('驳回仲裁,退回协商')]] : [])
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// 订单按【支付轨(类型)】筛选 —— renderOrders 的类型 chip / badge / 筛选逻辑抽出(app.js 已达 ratchet 上限,新域不回塞)。
|
|
2
|
+
// 托管 escrow(默认)/ 直接收款 direct_p2p。纯只读客户端筛选,与 scope/status 分组同层;仅当有直接收款单才显示 chip。
|
|
3
|
+
;(function () {
|
|
4
|
+
// 按 state.ordersRail 过滤订单数组(全部/托管/直接收款);返回过滤后的新数组。
|
|
5
|
+
window.orderRailApply = (orders) => {
|
|
6
|
+
const r = state.ordersRail || 'all'
|
|
7
|
+
if (r === 'direct_p2p') return orders.filter(o => o.payment_rail === 'direct_p2p')
|
|
8
|
+
if (r === 'escrow') return orders.filter(o => (o.payment_rail || 'escrow') === 'escrow')
|
|
9
|
+
return orders
|
|
10
|
+
}
|
|
11
|
+
// 类型筛选 chip 行(传入【筛选前】订单以判定是否有直接收款单)。
|
|
12
|
+
// 隐藏条件:当前范围没有直付单【且】没有激活筛选 —— 纯托管用户不打扰。
|
|
13
|
+
// 但只要 ordersRail!=='all'(筛选激活),即使切到无直付单的范围(如"我买的")也保留 chip,
|
|
14
|
+
// 否则用户会被锁在空列表里、看不到"全部类型"按钮无法恢复(P2:切 scope 不重置 rail)。
|
|
15
|
+
window.orderRailChipsHtml = (ordersBeforeFilter) => {
|
|
16
|
+
const cur = state.ordersRail || 'all'
|
|
17
|
+
if (cur === 'all' && !ordersBeforeFilter.some(o => o.payment_rail === 'direct_p2p')) return ''
|
|
18
|
+
return `<div style="display:flex;gap:6px;margin-bottom:10px">${[['all', t('全部类型')], ['escrow', '🏦 ' + t('托管')], ['direct_p2p', '🤝 ' + t('直接收款')]].map(([k, label]) => {
|
|
19
|
+
const on = cur === k
|
|
20
|
+
return `<button onclick="setOrdersRail('${k}')" style="padding:4px 10px;border-radius:12px;font-size:11px;border:1px solid ${on ? '#5b21b6' : '#e5e7eb'};background:${on ? '#5b21b6' : '#fff'};color:${on ? '#fff' : '#374151'};cursor:pointer;font-weight:${on ? '600' : '400'}">${label}</button>`
|
|
21
|
+
}).join('')}</div>`
|
|
22
|
+
}
|
|
23
|
+
// 订单卡上的类型徽标(只给直接收款打标,托管是默认不打扰)。
|
|
24
|
+
window.orderRailBadge = (o) => o.payment_rail === 'direct_p2p' ? ` · <span style="color:#7c3aed;font-weight:600">🤝 ${t('直接收款')}</span>` : ''
|
|
25
|
+
window.setOrdersRail = (r) => { state.ordersRail = r; renderOrders(document.getElementById('app')) }
|
|
26
|
+
})()
|