@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,219 @@
|
|
|
1
|
+
import { parseShippingTemplate, loadTemplateJson, resolveShipping } from '../../shipping-templates.js';
|
|
2
|
+
import { validateSaleRegionsInput, parseSaleRegionsRule, regionAllowedByRule, parsePlatformBlocklist } from '../../sale-regions.js'; // S1 可售区域(店铺+单品写入;gate 在 orders-create;S5 只读披露复用判定 + 与建单门同一 blocklist parser)
|
|
3
|
+
import { validateFreeShippingThreshold } from '../../free-shipping.js'; // 营销域满额免邮(S2 返工:写入口暂列本设置面,规则/判定在 free-shipping.ts)
|
|
4
|
+
import { validateImportDutyTerms, validateTaxLines, effectiveImportDutyTerms, effectiveTaxLines, parseTaxLines, taxLinesForRegion } from '../../trade-tax.js'; // S3 跨境税费/进口责任声明层(seller-declared,平台不算不收)
|
|
5
|
+
import { dbOne, dbRun } from '../../layer0-foundation/L0-1-database/db.js';
|
|
6
|
+
export function registerShippingTemplateRoutes(app, deps) {
|
|
7
|
+
const { auth, errorRes } = deps;
|
|
8
|
+
app.post('/api/seller/shipping-template', async (req, res) => {
|
|
9
|
+
const user = auth(req, res);
|
|
10
|
+
if (!user)
|
|
11
|
+
return;
|
|
12
|
+
if (user.role !== 'seller')
|
|
13
|
+
return void errorRes(res, 403, 'SELLER_ONLY', '仅卖家可设置运费模板');
|
|
14
|
+
const b = req.body || {};
|
|
15
|
+
const touched = {};
|
|
16
|
+
if ('store_template' in b) {
|
|
17
|
+
const p = parseShippingTemplate(b.store_template);
|
|
18
|
+
if (!p.ok)
|
|
19
|
+
return void errorRes(res, 400, 'BAD_SHIPPING_TEMPLATE', p.error);
|
|
20
|
+
await dbRun('UPDATE users SET store_shipping_template = ? WHERE id = ?', [p.entries ? JSON.stringify(p.entries) : null, user.id]);
|
|
21
|
+
touched.store_template = p.entries;
|
|
22
|
+
}
|
|
23
|
+
if ('store_quote_ok' in b) { // PR-3:模板外地区询价 opt-in(店铺默认;直付轨生效)
|
|
24
|
+
if (b.store_quote_ok !== null && typeof b.store_quote_ok !== 'boolean')
|
|
25
|
+
return void errorRes(res, 400, 'BAD_QUOTE_OK', 'store_quote_ok 只允许 true|false|null');
|
|
26
|
+
await dbRun('UPDATE users SET store_shipping_quote_ok = ? WHERE id = ?', [b.store_quote_ok === null ? null : (b.store_quote_ok ? 1 : 0), user.id]);
|
|
27
|
+
touched.store_quote_ok = b.store_quote_ok;
|
|
28
|
+
}
|
|
29
|
+
if ('store_sale_regions' in b) { // S1 可售区域(店铺默认):{mode:'all'|'list', include?, exclude?};null=清除(不限)
|
|
30
|
+
const v = validateSaleRegionsInput(b.store_sale_regions);
|
|
31
|
+
if ('error' in v)
|
|
32
|
+
return void errorRes(res, 400, 'BAD_SALE_REGIONS', v.error);
|
|
33
|
+
await dbRun('UPDATE users SET store_sale_regions = ? WHERE id = ?', [v.json, user.id]);
|
|
34
|
+
touched.store_sale_regions = v.json ? JSON.parse(v.json) : null;
|
|
35
|
+
}
|
|
36
|
+
if ('product_id' in b && 'sale_regions' in b) { // S1 单品覆盖(优先于店铺;null=回落店铺)
|
|
37
|
+
const v = validateSaleRegionsInput(b.sale_regions);
|
|
38
|
+
if ('error' in v)
|
|
39
|
+
return void errorRes(res, 400, 'BAD_SALE_REGIONS', v.error);
|
|
40
|
+
const prodS = await dbOne('SELECT seller_id FROM products WHERE id = ?', [b.product_id]);
|
|
41
|
+
if (!prodS)
|
|
42
|
+
return void errorRes(res, 404, 'PRODUCT_NOT_FOUND', '商品不存在');
|
|
43
|
+
if (prodS.seller_id !== user.id)
|
|
44
|
+
return void errorRes(res, 403, 'NOT_PRODUCT_OWNER', '只能设置自己商品');
|
|
45
|
+
await dbRun('UPDATE products SET sale_regions = ? WHERE id = ?', [v.json, b.product_id]);
|
|
46
|
+
touched.product_sale_regions = v.json ? JSON.parse(v.json) : null;
|
|
47
|
+
}
|
|
48
|
+
if ('store_free_shipping_threshold' in b) { // 营销:满额免邮(店铺默认;null=清除)。券后货款≥阈值→运费商家承担
|
|
49
|
+
const v = validateFreeShippingThreshold(b.store_free_shipping_threshold);
|
|
50
|
+
if ('error' in v)
|
|
51
|
+
return void errorRes(res, 400, 'BAD_FREE_SHIPPING_THRESHOLD', v.error);
|
|
52
|
+
await dbRun('UPDATE users SET store_free_shipping_threshold = ? WHERE id = ?', [v.value, user.id]);
|
|
53
|
+
touched.store_free_shipping_threshold = v.value;
|
|
54
|
+
}
|
|
55
|
+
if ('product_id' in b && 'free_shipping_threshold' in b) { // 营销:单品覆盖(null=回落店铺)
|
|
56
|
+
const v = validateFreeShippingThreshold(b.free_shipping_threshold);
|
|
57
|
+
if ('error' in v)
|
|
58
|
+
return void errorRes(res, 400, 'BAD_FREE_SHIPPING_THRESHOLD', v.error);
|
|
59
|
+
const prodF = await dbOne('SELECT seller_id FROM products WHERE id = ?', [b.product_id]);
|
|
60
|
+
if (!prodF)
|
|
61
|
+
return void errorRes(res, 404, 'PRODUCT_NOT_FOUND', '商品不存在');
|
|
62
|
+
if (prodF.seller_id !== user.id)
|
|
63
|
+
return void errorRes(res, 403, 'NOT_PRODUCT_OWNER', '只能设置自己商品');
|
|
64
|
+
await dbRun('UPDATE products SET free_shipping_threshold = ? WHERE id = ?', [v.value, b.product_id]);
|
|
65
|
+
touched.product_free_shipping_threshold = v.value;
|
|
66
|
+
}
|
|
67
|
+
if ('store_import_duty_terms' in b) { // S3:DDP/DDU 进口责任声明(店铺默认;null=清除)
|
|
68
|
+
const v = validateImportDutyTerms(b.store_import_duty_terms);
|
|
69
|
+
if ('error' in v)
|
|
70
|
+
return void errorRes(res, 400, 'BAD_IMPORT_DUTY_TERMS', v.error);
|
|
71
|
+
await dbRun('UPDATE users SET store_import_duty_terms = ? WHERE id = ?', [v.value, user.id]);
|
|
72
|
+
touched.store_import_duty_terms = v.value;
|
|
73
|
+
}
|
|
74
|
+
if ('product_id' in b && 'import_duty_terms' in b) { // S3:单品覆盖(null=回落店铺)
|
|
75
|
+
const v = validateImportDutyTerms(b.import_duty_terms);
|
|
76
|
+
if ('error' in v)
|
|
77
|
+
return void errorRes(res, 400, 'BAD_IMPORT_DUTY_TERMS', v.error);
|
|
78
|
+
const prodD = await dbOne('SELECT seller_id FROM products WHERE id = ?', [b.product_id]);
|
|
79
|
+
if (!prodD)
|
|
80
|
+
return void errorRes(res, 404, 'PRODUCT_NOT_FOUND', '商品不存在');
|
|
81
|
+
if (prodD.seller_id !== user.id)
|
|
82
|
+
return void errorRes(res, 403, 'NOT_PRODUCT_OWNER', '只能设置自己商品');
|
|
83
|
+
await dbRun('UPDATE products SET import_duty_terms = ? WHERE id = ?', [v.value, b.product_id]);
|
|
84
|
+
touched.product_import_duty_terms = v.value;
|
|
85
|
+
}
|
|
86
|
+
if ('store_tax_lines' in b) { // S3:价内已含税声明(店铺默认;仅 'included';null=清除)
|
|
87
|
+
const v = validateTaxLines(b.store_tax_lines);
|
|
88
|
+
if ('error' in v)
|
|
89
|
+
return void errorRes(res, 400, 'BAD_TAX_LINES', v.error);
|
|
90
|
+
await dbRun('UPDATE users SET store_tax_lines = ? WHERE id = ?', [v.value, user.id]);
|
|
91
|
+
touched.store_tax_lines = v.value ? JSON.parse(v.value) : null;
|
|
92
|
+
}
|
|
93
|
+
if ('product_id' in b && 'tax_lines' in b) { // S3:单品覆盖(null=回落店铺)
|
|
94
|
+
const v = validateTaxLines(b.tax_lines);
|
|
95
|
+
if ('error' in v)
|
|
96
|
+
return void errorRes(res, 400, 'BAD_TAX_LINES', v.error);
|
|
97
|
+
const prodT = await dbOne('SELECT seller_id FROM products WHERE id = ?', [b.product_id]);
|
|
98
|
+
if (!prodT)
|
|
99
|
+
return void errorRes(res, 404, 'PRODUCT_NOT_FOUND', '商品不存在');
|
|
100
|
+
if (prodT.seller_id !== user.id)
|
|
101
|
+
return void errorRes(res, 403, 'NOT_PRODUCT_OWNER', '只能设置自己商品');
|
|
102
|
+
await dbRun('UPDATE products SET tax_lines = ? WHERE id = ?', [v.value, b.product_id]);
|
|
103
|
+
touched.product_tax_lines = v.value ? JSON.parse(v.value) : null;
|
|
104
|
+
}
|
|
105
|
+
if ('product_id' in b && 'quote_ok' in b) {
|
|
106
|
+
if (b.quote_ok !== null && typeof b.quote_ok !== 'boolean')
|
|
107
|
+
return void errorRes(res, 400, 'BAD_QUOTE_OK', 'quote_ok 只允许 true|false|null');
|
|
108
|
+
const prodQ = await dbOne('SELECT seller_id FROM products WHERE id = ?', [b.product_id]);
|
|
109
|
+
if (!prodQ)
|
|
110
|
+
return void errorRes(res, 404, 'PRODUCT_NOT_FOUND', '商品不存在');
|
|
111
|
+
if (prodQ.seller_id !== user.id)
|
|
112
|
+
return void errorRes(res, 403, 'NOT_PRODUCT_OWNER', '只能设置自己商品');
|
|
113
|
+
await dbRun('UPDATE products SET shipping_quote_ok = ? WHERE id = ?', [b.quote_ok === null ? null : (b.quote_ok ? 1 : 0), b.product_id]);
|
|
114
|
+
touched.product_quote_ok = b.quote_ok;
|
|
115
|
+
}
|
|
116
|
+
if ('template' in b) { // 单品运费模板:仅在显式带 template 时进入 —— 关闭整类隐患(此前 'product_id' in b 会让 {product_id, store_*} 等组合尾随进来把模板静默清成 NULL;审计 P2)。清除走 {product_id, template:null}('template' in b 成立)
|
|
117
|
+
if (!b.product_id)
|
|
118
|
+
return void errorRes(res, 400, 'MISSING_PRODUCT_ID', '设置单品运费模板须带 product_id');
|
|
119
|
+
const p = parseShippingTemplate(b.template);
|
|
120
|
+
if (!p.ok)
|
|
121
|
+
return void errorRes(res, 400, 'BAD_SHIPPING_TEMPLATE', p.error);
|
|
122
|
+
const prod = await dbOne('SELECT seller_id FROM products WHERE id = ?', [b.product_id]);
|
|
123
|
+
if (!prod)
|
|
124
|
+
return void errorRes(res, 404, 'PRODUCT_NOT_FOUND', '商品不存在');
|
|
125
|
+
if (prod.seller_id !== user.id)
|
|
126
|
+
return void errorRes(res, 403, 'NOT_PRODUCT_OWNER', '只能设置自己商品的运费模板');
|
|
127
|
+
await dbRun('UPDATE products SET shipping_template = ? WHERE id = ?', [p.entries ? JSON.stringify(p.entries) : null, b.product_id]);
|
|
128
|
+
touched.product_template = p.entries;
|
|
129
|
+
}
|
|
130
|
+
if (Object.keys(touched).length === 0)
|
|
131
|
+
return void errorRes(res, 400, 'NOTHING_TO_SET', '未提供任何设置项');
|
|
132
|
+
return void res.json({ success: true, ...touched });
|
|
133
|
+
});
|
|
134
|
+
// 卖家读自己的店铺级设置(设置 UI 回显):接单模式 + 运费模板 + 询价开关。
|
|
135
|
+
app.get('/api/seller/shipping-settings', async (req, res) => {
|
|
136
|
+
const user = auth(req, res);
|
|
137
|
+
if (!user)
|
|
138
|
+
return;
|
|
139
|
+
if (user.role !== 'seller')
|
|
140
|
+
return void errorRes(res, 403, 'SELLER_ONLY', '仅卖家');
|
|
141
|
+
const row = await dbOne('SELECT store_accept_mode, store_shipping_template, store_shipping_quote_ok, store_sale_regions, store_free_shipping_threshold, store_import_duty_terms, store_tax_lines FROM users WHERE id = ?', [user.id]);
|
|
142
|
+
return void res.json({
|
|
143
|
+
store_accept_mode: row?.store_accept_mode ?? null,
|
|
144
|
+
store_sale_regions: parseSaleRegionsRule(row?.store_sale_regions ?? null),
|
|
145
|
+
store_free_shipping_threshold: row?.store_free_shipping_threshold ?? null,
|
|
146
|
+
store_import_duty_terms: (row?.store_import_duty_terms === 'ddu' || row?.store_import_duty_terms === 'ddp') ? row.store_import_duty_terms : null,
|
|
147
|
+
store_tax_lines: parseTaxLines(row?.store_tax_lines ?? null),
|
|
148
|
+
store_template: loadTemplateJson(row?.store_shipping_template),
|
|
149
|
+
store_quote_ok: row?.store_shipping_quote_ok == null ? null : Number(row.store_shipping_quote_ok) === 1,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
// 公开读:买家下单前查配送范围。生效 = 单品覆盖 ?? 店铺默认;template=null → 不按地区计费(下单不要求选地区)。
|
|
153
|
+
app.get('/api/products/:id/shipping-options', async (req, res) => {
|
|
154
|
+
const prod = await dbOne('SELECT id, seller_id, shipping_template, shipping_quote_ok, import_duty_terms, tax_lines, sale_regions, free_shipping_threshold FROM products WHERE id = ?', [req.params.id]);
|
|
155
|
+
if (!prod)
|
|
156
|
+
return void errorRes(res, 404, 'PRODUCT_NOT_FOUND', '商品不存在');
|
|
157
|
+
let entries = loadTemplateJson(prod.shipping_template);
|
|
158
|
+
let source = entries ? 'product' : null;
|
|
159
|
+
const seller = await dbOne('SELECT store_shipping_template, store_shipping_quote_ok, store_import_duty_terms, store_tax_lines, store_sale_regions, store_free_shipping_threshold FROM users WHERE id = ?', [prod.seller_id]);
|
|
160
|
+
if (!entries) {
|
|
161
|
+
entries = loadTemplateJson(seller?.store_shipping_template);
|
|
162
|
+
if (entries)
|
|
163
|
+
source = 'store';
|
|
164
|
+
}
|
|
165
|
+
const quoteOk = prod.shipping_quote_ok != null ? Number(prod.shipping_quote_ok) === 1 : Number(seller?.store_shipping_quote_ok) === 1;
|
|
166
|
+
// S3 税费/进口责任披露(买家下单前可见;卖家 seller-declared,平台不算不收)。
|
|
167
|
+
// 价内含税【按收货地区过滤】(?ship_to_region):目的区匹配项 + '*' 通用项;无地区参数=仅 '*'(只披露必然适用的,
|
|
168
|
+
// 不把不属于该目的地的税误示给买家)。DDP/DDU 是整单单一声明,不按区分。
|
|
169
|
+
const importDuty = effectiveImportDutyTerms(prod.import_duty_terms, seller?.store_import_duty_terms);
|
|
170
|
+
const shipToRegion = typeof req.query.ship_to_region === 'string' && req.query.ship_to_region.trim() ? req.query.ship_to_region.trim().toUpperCase() : null;
|
|
171
|
+
const taxLines = taxLinesForRegion(effectiveTaxLines(prod.tax_lines, seller?.store_tax_lines), shipToRegion);
|
|
172
|
+
// S5 买家下单前聚合(只读,复用 S1-S4 判定;不动订单金额/不收税)。
|
|
173
|
+
const saleRule = parseSaleRegionsRule(prod.sale_regions) ?? parseSaleRegionsRule(seller?.store_sale_regions ?? null); // 可售规则(商品??店铺)
|
|
174
|
+
// 平台合规名单:与建单门 gateSaleRegionForCreate 共用 parsePlatformBlocklist,坏配置 fail-closed 一致(建单 503 ⇄ 预检 platform_policy_invalid),
|
|
175
|
+
// 不再把坏配置当空名单静默放行(否则预检显示"可买"、提交才 503,与 create gate 不一致)。缺省 '[]'/无行=空名单=可售。
|
|
176
|
+
const pr = await dbOne("SELECT value FROM protocol_params WHERE key = 'trade.platform_region_blocklist'").catch(() => null);
|
|
177
|
+
const parsedBlock = parsePlatformBlocklist(pr ? pr.value : '[]');
|
|
178
|
+
const policyInvalid = !parsedBlock.ok;
|
|
179
|
+
const platformBlock = parsedBlock.ok ? parsedBlock.list : [];
|
|
180
|
+
const freeThreshold = (prod.free_shipping_threshold != null && Number(prod.free_shipping_threshold) > 0) ? Number(prod.free_shipping_threshold)
|
|
181
|
+
: (seller?.store_free_shipping_threshold != null && Number(seller.store_free_shipping_threshold) > 0 ? Number(seller.store_free_shipping_threshold) : null);
|
|
182
|
+
// 可售裁定(镜像建单 gateSaleRegionForCreate 的判定,只读不抛):平台配置异常 > 平台合规 > 商家意愿。
|
|
183
|
+
const restricted = policyInvalid || !!saleRule || platformBlock.length > 0;
|
|
184
|
+
let sellable = { ok: true, reason: 'ok' };
|
|
185
|
+
if (policyInvalid)
|
|
186
|
+
sellable = { ok: false, reason: 'platform_policy_invalid' }; // 坏配置 fail-closed:显示不可下单,不诱导买家提交后才 503
|
|
187
|
+
else if (restricted) {
|
|
188
|
+
if (!shipToRegion)
|
|
189
|
+
sellable = { ok: false, reason: 'region_required' };
|
|
190
|
+
else if (platformBlock.includes(shipToRegion))
|
|
191
|
+
sellable = { ok: false, reason: 'product_restricted' }; // 平台合规,商家不可放宽
|
|
192
|
+
else if (saleRule && !regionAllowedByRule(saleRule, shipToRegion))
|
|
193
|
+
sellable = { ok: false, reason: 'region_not_for_sale' }; // 商家不销往
|
|
194
|
+
}
|
|
195
|
+
// 目的区运费裁定(有模板时;免邮阈值仅回传数值供 UI 提示"满 X 免邮",实际是否免取决于买家券后货款,建单时定)
|
|
196
|
+
let resolvedShipping = null;
|
|
197
|
+
if (entries && shipToRegion) {
|
|
198
|
+
const r = resolveShipping(entries, shipToRegion);
|
|
199
|
+
resolvedShipping = { covered: r.covered, fee: r.covered ? r.fee : null, est_days: r.est_days, quote_required: !r.covered && quoteOk };
|
|
200
|
+
}
|
|
201
|
+
return void res.json({
|
|
202
|
+
product_id: prod.id,
|
|
203
|
+
region_required: !!entries || restricted, // S5:可售/合规限制也需买家指定目的地(与建单 SHIP_REGION_REQUIRED 一致)
|
|
204
|
+
template: entries,
|
|
205
|
+
source,
|
|
206
|
+
sellable, // S5:{ ok, reason: ok|region_required|product_restricted|region_not_for_sale|platform_policy_invalid }
|
|
207
|
+
sale_regions: saleRule, // S5:生效可售规则(商品??店铺){mode:'list',include:[...]} | {mode:'all',exclude:[...]} | null —— UI 据此在无运费模板时也能生成地区入口
|
|
208
|
+
resolved_shipping: resolvedShipping, // S5:目的区运费裁定(covered/fee/est_days/quote_required)| null(无模板或未选区)
|
|
209
|
+
free_shipping_threshold: freeThreshold, // S5:生效满额免邮阈值(UI 提示"满 X 免邮";实际免取决于建单时券后货款)
|
|
210
|
+
import_duty_terms: importDuty, // 'ddu'(买家到境自付关税/税)| 'ddp'(卖家已含)| null(未声明)
|
|
211
|
+
tax_included_lines: taxLines, // 价内已含税声明(如 [{region:'SG',label:'GST',rate_pct:9,kind:'included'}])
|
|
212
|
+
tax_disclosure: 'seller_declared_platform_no_collect', // S5:税费/进口责任均为卖家声明,平台不代收代缴(直付非托管)
|
|
213
|
+
quote_outside_template: !!entries && quoteOk, // 直付轨:模板外地区可询价(卖家报运费/时效,确认后付款)
|
|
214
|
+
note: entries
|
|
215
|
+
? (quoteOk ? '下单须选择收货地区;模板内地区运费自动计入,模板外地区(直付)可先询价再付款。' : '下单须选择收货地区;运费按模板计入总额(快照)。未覆盖地区暂不可下单。')
|
|
216
|
+
: (restricted ? '该商品设有可售地区限制,请选择收货地区确认。' : '该商品不按地区计运费。'),
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
package/dist/pwa/routes/shops.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
2
|
+
import { genuineSalePredicate } from '../../layer0-foundation/L0-2-state-machine/genuine-sale.js'; // RFC-018 PR4: 真实成交(排除全额退货)
|
|
2
3
|
export function registerShopsRoutes(app, deps) {
|
|
3
4
|
// db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
|
|
4
5
|
const { auth } = deps;
|
|
@@ -20,7 +21,7 @@ export function registerShopsRoutes(app, deps) {
|
|
|
20
21
|
const sellerId = String(seller.id);
|
|
21
22
|
const products = await dbAll(`
|
|
22
23
|
SELECT p.id, p.title, p.price, p.stock, p.category, p.images, p.has_variants, p.commission_rate,
|
|
23
|
-
(SELECT COUNT(1) FROM orders o WHERE o.product_id = p.id AND o
|
|
24
|
+
(SELECT COUNT(1) FROM orders o WHERE o.product_id = p.id AND ${genuineSalePredicate('o')}) as sales_count
|
|
24
25
|
FROM products p
|
|
25
26
|
WHERE p.seller_id = ? AND p.status = 'active'
|
|
26
27
|
ORDER BY sales_count DESC, p.created_at DESC
|
package/dist/pwa/routes/snf.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { dbOne } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
2
2
|
import { snfSend, snfPullInbox, snfListInbox, snfAck, snfPendingCount, snfVerify, snfDesignate, snfGetDesignation, snfNack, snfListDeadLetter, snfRevive, } from '../../layer2-business/L2-7-snf/snf-engine.js';
|
|
3
|
+
import { isEligibleArbitrator } from '../arbitrator-lifecycle.js'; // 仲裁能力唯一源=active 白名单(verify 是争议证据面,不认 legacy role)
|
|
3
4
|
export function registerSnfRoutes(app, deps) {
|
|
4
5
|
const { db, auth } = deps;
|
|
5
6
|
app.post('/api/snf/send', (req, res) => {
|
|
@@ -105,7 +106,9 @@ export function registerSnfRoutes(app, deps) {
|
|
|
105
106
|
if (!r)
|
|
106
107
|
return void res.status(404).json({ error: '消息不存在' });
|
|
107
108
|
const uid = user.id;
|
|
108
|
-
|
|
109
|
+
// 仲裁员认 active 白名单(争议证据验签是仲裁工作面);legacy role 旁路移除 —— 否则 role-only/已吊销账号可按 id
|
|
110
|
+
// 探测任意用户间私信的存在性+签名有效性,而真·白名单仲裁员(role=buyer)反被 403。admin 保持原样。
|
|
111
|
+
if (uid !== r.sender_id && uid !== r.recipient_id && !isEligibleArbitrator(db, uid).ok && user.role !== 'admin') {
|
|
109
112
|
return void res.status(403).json({ error: '无权验证' });
|
|
110
113
|
}
|
|
111
114
|
res.json(await snfVerify(db, req.params.id));
|
|
@@ -119,8 +119,8 @@ export function registerUrlClaimRoutes(app, deps) {
|
|
|
119
119
|
db.prepare(`INSERT INTO products (
|
|
120
120
|
id, seller_id, title, description, price, stock, category, stake_amount,
|
|
121
121
|
specs, source_url, handling_hours, return_days, warranty_days,
|
|
122
|
-
commitment_hash, description_hash, price_hash, hashed_at, status, stake_locked_at
|
|
123
|
-
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'warehouse',datetime('now'))`)
|
|
122
|
+
commitment_hash, description_hash, price_hash, hashed_at, status, stake_locked_at, currency
|
|
123
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'warehouse',datetime('now'),'WAZ')`)
|
|
124
124
|
.run(productId, user.id, title, description, priceNum, Number(stock), category, stakeAmount, specsJson, url, Number(handling_hours), Number(return_days), Number(warranty_days), makeCommitmentHash(pFields), makeDescriptionHash({ title, description, specs: specsJson }), makePriceHash(priceNum, now), now);
|
|
125
125
|
db.prepare(`INSERT INTO product_external_links
|
|
126
126
|
(id, product_id, url, source, verified, verify_note, platform, external_id, external_title)
|
|
@@ -25,6 +25,14 @@ export function registerUsersPublicRoutes(app, deps) {
|
|
|
25
25
|
return;
|
|
26
26
|
userId = user.id;
|
|
27
27
|
}
|
|
28
|
+
else {
|
|
29
|
+
// ref 三态(usr_xxx / permanent_code / @handle)必须解析为 canonical id;
|
|
30
|
+
// 漏掉解析会让 @handle/permanent_code 当字面 user_id 去查 → 永远落到默认 'new'。
|
|
31
|
+
const resolved = await resolveUserId(userId);
|
|
32
|
+
if (!resolved)
|
|
33
|
+
return void res.status(404).json({ error: 'user not found' });
|
|
34
|
+
userId = resolved;
|
|
35
|
+
}
|
|
28
36
|
const row = await dbOne(`
|
|
29
37
|
SELECT level, MAX(trust_score) as max_score
|
|
30
38
|
FROM agent_reputation WHERE user_id = ?
|
|
@@ -181,8 +181,11 @@ export function registerWalletReadRoutes(app, deps) {
|
|
|
181
181
|
FROM orders WHERE seller_id = ? AND status = 'completed'
|
|
182
182
|
`, [user.id]));
|
|
183
183
|
const totalIncome = commMap.l1.total + commMap.l2.total + commMap.l3.total + Number(sales.total);
|
|
184
|
+
// RFC-018: commission accrued but still in the clearing window (matures into commissions/total_income). Pure read.
|
|
185
|
+
const clearing = (await dbOne("SELECT COALESCE(SUM(amount),0) as s FROM pending_commission_escrow WHERE recipient_user_id = ? AND matures_at IS NOT NULL AND status = 'pending'", [user.id])).s;
|
|
184
186
|
res.json({
|
|
185
187
|
commissions: commMap,
|
|
188
|
+
commission_clearing: Number(clearing.toFixed(2)), // RFC-018: accrued, maturing after the return window (not yet paid)
|
|
186
189
|
sales: { count: sales.cnt, total: Number(Number(sales.total).toFixed(2)) },
|
|
187
190
|
total_income: Number(totalIncome.toFixed(2)),
|
|
188
191
|
});
|
|
@@ -74,13 +74,13 @@ export function registerWebauthnRoutes(app, deps) {
|
|
|
74
74
|
if (!user)
|
|
75
75
|
return;
|
|
76
76
|
const purpose = String(req.body?.purpose || '').trim();
|
|
77
|
-
const allowed = new Set(['withdraw', 'change-password', 'reveal-key', 'region', 'delete_passkey', 'governance_apply', 'governance_activate', 'governance_resign', 'governance_appeal_resolve', 'rewards_apply', 'rewards_deactivate', 'identity_claim', 'operator_claim_unlink']);
|
|
77
|
+
const allowed = new Set(['withdraw', 'change-password', 'reveal-key', 'region', 'delete_passkey', 'governance_apply', 'governance_activate', 'governance_resign', 'governance_appeal_resolve', 'rewards_apply', 'rewards_deactivate', 'identity_claim', 'operator_claim_unlink', 'direct_pay_disclosure_ack', 'direct_pay_order_action', 'direct_receive_production_confirm', 'direct_receive_bond_refund', 'direct_pay_bond_slash', 'direct_pay_aml_review', 'direct_pay_kyb_ingress', 'direct_pay_sanctions_ingress', 'direct_pay_aml_ingress', 'direct_pay_admin_readiness', 'direct_pay_deferral_approve', 'direct_pay_deferral_reject', 'direct_pay_product_verify', 'direct_pay_store_verify', 'direct_pay_fee_prepay_record', 'direct_pay_fee_adjust', 'direct_pay_fee_refund', 'direct_receive_account_manage', 'platform_receive_account_manage', 'direct_pay_fee_prepay_reject', 'direct_pay_fee_prepay_request_approve', 'direct_pay_payment_info_reveal', 'arbitrator_grant', 'arbitrator_suspend', 'arbitrator_reinstate', 'arbitrator_revoke', 'arbitrate', 'agent_pair_approve', 'agent_permission_approve']);
|
|
78
78
|
if (!allowed.has(purpose))
|
|
79
79
|
return void res.status(400).json({ error: 'invalid purpose' });
|
|
80
80
|
const purpose_data = req.body?.purpose_data ?? null;
|
|
81
81
|
const creds = await dbAll('SELECT id, transports FROM webauthn_credentials WHERE user_id = ?', [user.id]);
|
|
82
82
|
if (creds.length === 0)
|
|
83
|
-
return void res.status(403).json({ error: '尚未注册任何 Passkey' });
|
|
83
|
+
return void res.status(403).json({ error: '尚未注册任何 Passkey', error_code: 'NO_PASSKEY_REGISTERED' }); // 前端据此才提示注册;已注册者的取消/设备失败不误导去注册
|
|
84
84
|
const opts = await generateAuthenticationOptions({
|
|
85
85
|
rpID: rpId,
|
|
86
86
|
// H-1: 闸门必须真正 UV,不接受 "硬件按一下" 兜底
|
|
@@ -196,5 +196,5 @@ export function registerWebauthnRoutes(app, deps) {
|
|
|
196
196
|
// RFC-020 PR-B — agent delegation grants (issue/read/revoke). Co-registered with the
|
|
197
197
|
// Passkey security routes so the money-dense server.ts stays untouched. Safe scopes
|
|
198
198
|
// only; risk scopes default-hard-reject. Reuses db/auth/generateId from WebauthnDeps.
|
|
199
|
-
registerAgentGrantsRoutes(app, { db, auth, generateId, rateLimitOk });
|
|
199
|
+
registerAgentGrantsRoutes(app, { db, auth, generateId, rateLimitOk, requireHumanPresence });
|
|
200
200
|
}
|