@seasonkoh/webaz 0.1.28 → 0.1.29
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/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 +94 -0
- package/dist/direct-pay-compliance-ingress.js +145 -0
- package/dist/direct-pay-controls.js +136 -0
- package/dist/direct-pay-create.js +162 -0
- package/dist/direct-pay-deferral-quota.js +43 -0
- package/dist/direct-pay-disclosures.js +44 -0
- package/dist/direct-pay-eligibility.js +46 -0
- package/dist/direct-pay-fee-ar.js +241 -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-receive-account-qr.js +77 -0
- package/dist/direct-receive-accounts.js +82 -0
- package/dist/direct-receive-deferral.js +142 -0
- package/dist/direct-receive-deposits.js +260 -0
- package/dist/direct-receive-payment-instruction.js +26 -0
- package/dist/fx-rates.js +71 -0
- package/dist/layer0-foundation/L0-1-database/schema.js +531 -1
- package/dist/layer0-foundation/L0-2-state-machine/genuine-sale.js +15 -5
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +41 -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 +18 -16
- 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/product-verification.js +93 -0
- package/dist/pwa/acp-feed.js +3 -2
- package/dist/pwa/contract-fingerprint.js +3 -0
- package/dist/pwa/direct-pay-guards.js +20 -0
- package/dist/pwa/direct-pay-order-redaction.js +24 -0
- package/dist/pwa/endpoint-actions.js +3 -0
- package/dist/pwa/public/app-ai.js +10 -10
- package/dist/pwa/public/app-chat-poll.js +29 -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-compliance.js +67 -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-ops.js +112 -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-store-verify.js +100 -0
- package/dist/pwa/public/app-direct-pay.js +227 -0
- package/dist/pwa/public/app-discover.js +20 -20
- package/dist/pwa/public/app-external-links.js +32 -0
- package/dist/pwa/public/app-listings.js +4 -4
- 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 +6 -6
- package/dist/pwa/public/app-seller.js +5 -5
- package/dist/pwa/public/app-shop.js +19 -19
- package/dist/pwa/public/app.js +142 -146
- package/dist/pwa/public/i18n.js +398 -197
- package/dist/pwa/public/index.html +17 -0
- package/dist/pwa/public/openapi.json +495 -1
- package/dist/pwa/routes/admin-analytics.js +4 -2
- package/dist/pwa/routes/admin-direct-receive-deposits.js +321 -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-disclosure-acks.js +74 -0
- package/dist/pwa/routes/direct-pay-timeouts.js +71 -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/fx.js +12 -0
- package/dist/pwa/routes/leaderboard.js +47 -9
- package/dist/pwa/routes/listings.js +2 -2
- package/dist/pwa/routes/logistics.js +4 -0
- package/dist/pwa/routes/manifests.js +38 -0
- package/dist/pwa/routes/me-data.js +5 -2
- package/dist/pwa/routes/orders-action.js +117 -9
- package/dist/pwa/routes/orders-create.js +4 -0
- package/dist/pwa/routes/orders-read.js +36 -0
- package/dist/pwa/routes/p2p-products.js +2 -2
- package/dist/pwa/routes/products-create.js +5 -3
- 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 +22 -2
- package/dist/pwa/routes/promoter.js +3 -0
- package/dist/pwa/routes/referral.js +4 -0
- package/dist/pwa/routes/returns.js +26 -1
- 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/shops.js +2 -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 +1 -1
- package/dist/pwa/server.js +59 -102
- package/dist/runtime/webaz-schema-helpers.js +104 -0
- package/dist/store-verification.js +77 -0
- package/dist/version.js +1 -1
- package/package.json +71 -2
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// RFC-016 Phase 1 — sweep read → async seam; the per-row pay tx stays sync (Phase 3 → pg).
|
|
2
|
+
import { dbAll } from '../../layer0-foundation/L0-1-database/db.js';
|
|
3
|
+
import { generateId } from '../../layer0-foundation/L0-1-database/schema.js';
|
|
4
|
+
import { applyWalletDelta } from '../../ledger.js';
|
|
5
|
+
import { toUnits, toDecimal } from '../../money.js';
|
|
6
|
+
// #7 Commission source_type (moved from settleCommission). Pure read — stamps commission_records.source_type.
|
|
7
|
+
function commissionSourceType(db, productId, uid) {
|
|
8
|
+
if (!uid)
|
|
9
|
+
return 'sponsor';
|
|
10
|
+
const attr = db.prepare(`SELECT shareable_id FROM product_share_attribution WHERE product_id = ? AND sharer_id = ? AND shareable_id IS NOT NULL ORDER BY created_at DESC LIMIT 1`).get(productId, uid);
|
|
11
|
+
if (!attr)
|
|
12
|
+
return 'sponsor';
|
|
13
|
+
const sh = db.prepare(`SELECT type FROM shareables WHERE id = ?`).get(attr.shareable_id);
|
|
14
|
+
if (!sh)
|
|
15
|
+
return 'sponsor';
|
|
16
|
+
return sh.type === 'note' ? 'note' : 'link';
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Mature ONE clearing commission row. Re-validates genuine closure (completed + no open dispute), then
|
|
20
|
+
* writes commission_records + credits the wallet in one tx. CAS on status='pending' → idempotent; races
|
|
21
|
+
* and re-runs lose. Holds (no write) while a dispute is open. Exported for direct unit testing.
|
|
22
|
+
*/
|
|
23
|
+
export function matureClearingRow(db, row) {
|
|
24
|
+
let outcome = 'skipped';
|
|
25
|
+
db.transaction(() => {
|
|
26
|
+
const order = db.prepare("SELECT buyer_id, product_id, snapshot_commission_rate, buyer_region, status FROM orders WHERE id = ?").get(row.order_id);
|
|
27
|
+
if (!order || order.status !== 'completed') {
|
|
28
|
+
outcome = 'held';
|
|
29
|
+
return;
|
|
30
|
+
} // completed is terminal; defensive hold
|
|
31
|
+
const openDispute = db.prepare("SELECT 1 FROM disputes WHERE order_id = ? AND status NOT IN ('resolved','dismissed','closed') LIMIT 1").get(row.order_id);
|
|
32
|
+
if (openDispute) {
|
|
33
|
+
outcome = 'held';
|
|
34
|
+
return;
|
|
35
|
+
} // hold while unresolved (e.g. return escalated to dispute)
|
|
36
|
+
const claimed = db.prepare("UPDATE pending_commission_escrow SET status='settled', settled_at=? WHERE id=? AND status='pending' AND matures_at IS NOT NULL").run(Date.now(), row.id);
|
|
37
|
+
if (claimed.changes !== 1) {
|
|
38
|
+
outcome = 'skipped';
|
|
39
|
+
return;
|
|
40
|
+
} // race lost / already settled / reversed
|
|
41
|
+
const amountU = toUnits(Number(row.amount));
|
|
42
|
+
if (amountU > 0) {
|
|
43
|
+
const level = Number(String(row.attribution_path).replace(/^L/, '')) || 0;
|
|
44
|
+
const rate = Number(order.snapshot_commission_rate ?? 0.10);
|
|
45
|
+
let region = order.buyer_region || null;
|
|
46
|
+
if (!region)
|
|
47
|
+
region = db.prepare("SELECT region FROM users WHERE id = ?").get(order.buyer_id)?.region ?? 'global';
|
|
48
|
+
const srcType = commissionSourceType(db, order.product_id, row.recipient_user_id);
|
|
49
|
+
try {
|
|
50
|
+
db.prepare(`INSERT INTO commission_records (id, order_id, beneficiary_id, source_buyer_id, level, amount, rate, region, source, source_type) VALUES (?,?,?,?,?,?,?,?,?,?)`)
|
|
51
|
+
.run(generateId('comm'), row.order_id, row.recipient_user_id, order.buyer_id, level, toDecimal(amountU), rate, region, 'static', srcType);
|
|
52
|
+
}
|
|
53
|
+
catch (e) { /* UNIQUE — already recorded on a prior partial run */ }
|
|
54
|
+
applyWalletDelta(db, row.recipient_user_id, { balance: amountU, earned: amountU });
|
|
55
|
+
}
|
|
56
|
+
outcome = 'settled';
|
|
57
|
+
})();
|
|
58
|
+
return outcome;
|
|
59
|
+
}
|
|
60
|
+
export async function runClearingMatureSweep(deps) {
|
|
61
|
+
const { db } = deps;
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const rows = await dbAll(`
|
|
64
|
+
SELECT id, recipient_user_id, order_id, amount, attribution_path
|
|
65
|
+
FROM pending_commission_escrow
|
|
66
|
+
WHERE status = 'pending' AND matures_at IS NOT NULL AND matures_at <= ?
|
|
67
|
+
ORDER BY matures_at ASC
|
|
68
|
+
LIMIT 1000
|
|
69
|
+
`, [now]);
|
|
70
|
+
let settled = 0, held = 0, skipped = 0;
|
|
71
|
+
for (const r of rows) {
|
|
72
|
+
const o = matureClearingRow(db, r);
|
|
73
|
+
if (o === 'settled')
|
|
74
|
+
settled++;
|
|
75
|
+
else if (o === 'held')
|
|
76
|
+
held++;
|
|
77
|
+
else
|
|
78
|
+
skipped++;
|
|
79
|
+
}
|
|
80
|
+
return { scanned: rows.length, settled, held, skipped };
|
|
81
|
+
}
|
|
82
|
+
export function startClearingMatureCron(deps) {
|
|
83
|
+
const ms = 60 * 60 * 1000; // 1h — clearing window granularity is days
|
|
84
|
+
setInterval(async () => {
|
|
85
|
+
try {
|
|
86
|
+
const r = await runClearingMatureSweep(deps);
|
|
87
|
+
if (r.settled > 0 || r.held > 0) {
|
|
88
|
+
console.log(`[clearing-mature] scanned ${r.scanned}, settled ${r.settled}, held ${r.held}, skipped ${r.skipped}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
console.error('[clearing-mature-cron]', e);
|
|
93
|
+
}
|
|
94
|
+
}, ms);
|
|
95
|
+
console.log('⏳ RFC-018 commission clearing maturation cron 已启动 (每 1h, anchor=matures_at)');
|
|
96
|
+
}
|
|
@@ -3,17 +3,21 @@ import { dbAll } from '../../layer0-foundation/L0-1-database/db.js';
|
|
|
3
3
|
export async function runEscrowExpireSweep(deps) {
|
|
4
4
|
const { db, redirectToCommissionReserve } = deps;
|
|
5
5
|
const now = Date.now();
|
|
6
|
+
// RFC-018: pending_commission_escrow now holds TWO lifecycles, discriminated by matures_at:
|
|
7
|
+
// - matures_at IS NULL → opt-out escrow (this cron: expire → commission_reserve / pool).
|
|
8
|
+
// - matures_at NOT NULL → clearing rows (the SEPARATE rewards-clearing-mature cron: pay/reverse).
|
|
9
|
+
// We MUST exclude clearing rows here, or they'd be wrongly expired to the pool instead of paid.
|
|
6
10
|
const rows = await dbAll(`
|
|
7
11
|
SELECT id, recipient_user_id, order_id, amount, attribution_path, expires_at
|
|
8
12
|
FROM pending_commission_escrow
|
|
9
|
-
WHERE status = 'pending' AND expires_at <= ?
|
|
13
|
+
WHERE status = 'pending' AND matures_at IS NULL AND expires_at <= ?
|
|
10
14
|
ORDER BY expires_at ASC
|
|
11
15
|
LIMIT 1000
|
|
12
16
|
`, [now]);
|
|
13
17
|
const expired = [];
|
|
14
18
|
for (const r of rows) {
|
|
15
19
|
db.transaction(() => {
|
|
16
|
-
const upd = db.prepare(`UPDATE pending_commission_escrow SET status='expired', expired_to_charity_at=? WHERE id=? AND status='pending'`).run(now, r.id);
|
|
20
|
+
const upd = db.prepare(`UPDATE pending_commission_escrow SET status='expired', expired_to_charity_at=? WHERE id=? AND status='pending' AND matures_at IS NULL`).run(now, r.id);
|
|
17
21
|
if (upd.changes === 0)
|
|
18
22
|
return; // race lost — another sweep already took it
|
|
19
23
|
if (r.attribution_path === 'pv_pair') {
|
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
|
|
@@ -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,7 +74,7 @@ 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_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']);
|
|
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;
|
package/dist/pwa/server.js
CHANGED
|
@@ -22,12 +22,15 @@ import crypto from 'crypto';
|
|
|
22
22
|
import { initDatabase, generateId } from '../layer0-foundation/L0-1-database/schema.js';
|
|
23
23
|
import { setSeamDb } from '../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
24
24
|
import { initSystemUser, transition, getOrderStatus, checkTimeouts, settleFault } from '../layer0-foundation/L0-2-state-machine/engine.js';
|
|
25
|
+
import { genuineSalePredicate } from '../layer0-foundation/L0-2-state-machine/genuine-sale.js';
|
|
26
|
+
import { settleDirectPayFeeAtCompletion } from '../direct-pay-fee-ar.js'; // RFC-018 PR4 genuine-sale SSOT + Direct Pay Rail 1 平台费链下应收(完成时释放遗留模拟 stake + accrue)
|
|
25
27
|
import { endpointToAction, endpointToReadAction } from './endpoint-actions.js';
|
|
26
28
|
import { AGENT_RATE_PER_MIN_DEFAULTS, CROSS_USER_READ_DAILY_CAP, MASS_ACTION_TYPES, MASS_ACTION_DAILY_CAPS } from './limits.js';
|
|
27
29
|
// #420 P1-2/P1-3/P1-4 — 反滥用阈值单一真相源(governance-adjustable protocol_params)+ 纯决策函数
|
|
28
30
|
import { ANTI_ABUSE_PARAMS, readAntiAbuseThresholds, agentTrustLevel, agentSybilPenalty, agentStrikeSeverity, verifierOutlierBand } from './anti-abuse-thresholds.js';
|
|
31
|
+
import { DIRECT_PAY_CONTROL_PARAMS, DIRECT_PAY_AML_PARAMS } from '../direct-pay-controls.js'; // PR-4a 控制面 + PR-6D AML 监控 param 默认 seed(均 fail-closed / inert)
|
|
29
32
|
import { initOrderChainSchema, appendOrderEvent, getOrderChain, verifyOrderChain } from '../layer0-foundation/L0-2-state-machine/order-chain.js';
|
|
30
|
-
import { initVerifierWhitelistSchema, initMcpToolCallsSchema, initNotePhotoIndexSchema, initUserWishlistSchema, initProductQaSchema, initCouponsSchema, initAnnouncementsSchema, initProductWaitlistSchema, initFlashSalesSchema, initPublicIdeasSchema, initAuctionRemindersSchema, initEmailSubscriptionsSchema, initFeedbackTicketsSchema, initFeedbackMessagesSchema, initDisputeCasesSchema, initDisputeCommentsSchema, initDisputeCommentRepliesSchema, initShareableCommentsSchema, initDisputeFairnessVotesSchema, initOrderRatingsSchema, initBuyerRatingsSchema, initUserAddressesSchema, initP2pShopsSchema, initShareableLikesSchema, initShareableBookmarksSchema, initShareableTagsSchema, initManifestRegistrySchema, initPeerDirectorySchema, initSignalingQueueSchema, initConversationsSchema, initMessagesSchema, initChatReportsSchema, initQuotaIncreaseApplicationsSchema, initVerifierApplicationsSchema, initArbitratorReviewSchema, initVerifierAppealsSchema, initUserModerationSchema, initAdminAuditLogSchema, initVerificationCodesSchema, initAgentCallLogSchema, initAgentReputationSchema, initAgentDeclarationsSchema, initAgentAttestationsSchema, initAgentStrikesSchema, initAgentRevocationsSchema, initProductAliasesSchema, initRegionChangeLogSchema, initCartItemsSchema, initFollowsSchema, initPushSubscriptionsSchema, initUserSessionsSchema, initUserBlocklistSchema, initImportLogsSchema, initErrorLogSchema, initSecondhandItemsSchema, initProductTrialCampaignsSchema, initProductTrialClaimsSchema, initReturnRequestsSchema, initReturnMessagesSchema, initProductVariantsSchema, initEditorPicksSchema, initKycRecordsSchema, initWebauthnSchema, initClaimVerificationBaseSchema, initClaimVerifierSuspensionsSchema, initProductClaimSchema, initReviewClaimSchema, initSecondhandClaimSchema, initAuctionClaimSchema, initWishClaimSchema, initShareableClickLogSchema, initCommissionAuditLogSchema, initRegistrationAuditLogSchema, initProductExternalLinksBaseSchema, initLinkChallengesSchema, initVerifyTasksSchema, initVerifySubmissionsSchema, initVerifierStatsSchema, initRegisterListSearchColumns } from './server-schema.js';
|
|
33
|
+
import { initVerifierWhitelistSchema, initMcpToolCallsSchema, initNotePhotoIndexSchema, initUserWishlistSchema, initProductQaSchema, initCouponsSchema, initAnnouncementsSchema, initProductWaitlistSchema, initFlashSalesSchema, initPublicIdeasSchema, initAuctionRemindersSchema, initEmailSubscriptionsSchema, initFeedbackTicketsSchema, initFeedbackMessagesSchema, initDisputeCasesSchema, initDisputeCommentsSchema, initDisputeCommentRepliesSchema, initShareableCommentsSchema, initDisputeFairnessVotesSchema, initOrderRatingsSchema, initBuyerRatingsSchema, initUserAddressesSchema, initP2pShopsSchema, initShareableLikesSchema, initShareableBookmarksSchema, initShareableTagsSchema, initManifestRegistrySchema, initPeerDirectorySchema, initSignalingQueueSchema, initConversationsSchema, initMessagesSchema, initChatReportsSchema, initQuotaIncreaseApplicationsSchema, initVerifierApplicationsSchema, initArbitratorReviewSchema, initVerifierAppealsSchema, initUserModerationSchema, initAdminAuditLogSchema, initVerificationCodesSchema, initAgentCallLogSchema, initAgentReputationSchema, initAgentDeclarationsSchema, initAgentAttestationsSchema, initAgentStrikesSchema, initAgentRevocationsSchema, initProductAliasesSchema, initRegionChangeLogSchema, initCartItemsSchema, initFollowsSchema, initPushSubscriptionsSchema, initUserSessionsSchema, initUserBlocklistSchema, initImportLogsSchema, initErrorLogSchema, initSecondhandItemsSchema, initProductTrialCampaignsSchema, initProductTrialClaimsSchema, initReturnRequestsSchema, initReturnMessagesSchema, initProductVariantsSchema, initEditorPicksSchema, initKycRecordsSchema, initWebauthnSchema, initClaimVerificationBaseSchema, initClaimVerifierSuspensionsSchema, initProductClaimSchema, initReviewClaimSchema, initSecondhandClaimSchema, initAuctionClaimSchema, initWishClaimSchema, initShareableClickLogSchema, initCommissionAuditLogSchema, initRegistrationAuditLogSchema, initProductExternalLinksBaseSchema, initLinkChallengesSchema, initVerifyTasksSchema, initVerifySubmissionsSchema, initVerifierStatsSchema, initRegisterListSearchColumns, initPendingCommissionEscrowSchema } from './server-schema.js';
|
|
31
34
|
// RFC-014 PR4 — 正常成交结算走整数 base-units + allocate + 绝对值落库。
|
|
32
35
|
import { toUnits, toDecimal, mulRate, allocate } from '../money.js';
|
|
33
36
|
import { applyWalletDelta, creditColumns } from '../ledger.js';
|
|
@@ -84,6 +87,7 @@ import { registerClaimVerifyRoutes, processClaimTaskQueue, isEligibleClaimVerifi
|
|
|
84
87
|
import { registerFollowsRoutes } from './routes/follows.js';
|
|
85
88
|
// Leaderboard (#1013 Phase 11) — 单 endpoint 8 kinds(products/creators/buyers/sellers/value/agents/arbitrators/verifiers)
|
|
86
89
|
import { registerLeaderboardRoutes } from './routes/leaderboard.js';
|
|
90
|
+
import { registerFxRoutes } from './routes/fx.js'; // FX = display-only USDC→local rates
|
|
87
91
|
// Shareables 互动 (#1013 Phase 12) — click/like/comments/bookmark 8 endpoints
|
|
88
92
|
import { registerShareablesInteractionsRoutes } from './routes/shareables-interactions.js';
|
|
89
93
|
// Shareables CRUD (#1013 Phase 13) — 11 endpoints (notes-photo + create + me + creator-stats + by-* + feed + detail + PATCH + DELETE)
|
|
@@ -154,6 +158,8 @@ import { registerArbitratorRoutes } from './routes/arbitrator.js';
|
|
|
154
158
|
import { registerGovernanceOnboardingRoutes } from './routes/governance-onboarding.js';
|
|
155
159
|
import { startAutoDeactivateCron, runAutoDeactivateSweep } from './routes/governance-auto-deactivate.js';
|
|
156
160
|
import { startEscrowExpireCron } from './routes/rewards-escrow-expire.js';
|
|
161
|
+
import { startDirectPayTimeoutCron } from './routes/direct-pay-timeouts.js';
|
|
162
|
+
import { startClearingMatureCron } from './routes/rewards-clearing-mature.js';
|
|
157
163
|
import { startAutoDowngradeCron } from './routes/rewards-auto-downgrade.js';
|
|
158
164
|
import { registerRewardsApplyRoutes } from './routes/rewards-apply.js';
|
|
159
165
|
// 卖家配额 + 数据中心 (#1013 Phase 45) — 4 user + 3 admin
|
|
@@ -242,6 +248,11 @@ import { registerOrdersReadRoutes } from './routes/orders-read.js';
|
|
|
242
248
|
import { registerOrdersActionRoutes } from './routes/orders-action.js';
|
|
243
249
|
// Orders 下单端点 (#1013 Phase 85) — 1 endpoint (338-line POST /api/orders)
|
|
244
250
|
import { registerOrdersCreateRoutes } from './routes/orders-create.js';
|
|
251
|
+
import { registerDirectPayDisclosureAckRoutes } from './routes/direct-pay-disclosure-acks.js'; // PR-4d: Direct Pay 风险披露 ack 端点(薄 adapter)
|
|
252
|
+
import { registerDirectReceivePaymentInstructionRoutes } from './routes/direct-receive-payment-instructions.js';
|
|
253
|
+
import { registerDirectReceiveAccountsRoutes } from './routes/direct-receive-accounts.js'; // PR-4f-a instruction CRUD + Phase C1 multi-account+QR
|
|
254
|
+
import { registerDirectPayAvailabilityRoutes } from './routes/direct-pay-availability.js'; // PR-4a: Direct Pay 可用性只读(控制面 SSOT)
|
|
255
|
+
import { registerAdminDirectReceiveDepositsRoutes } from './routes/admin-direct-receive-deposits.js'; // PR-4b-3: ROOT 生产保证金 receipt 确认(fail-closed scaffold)
|
|
245
256
|
// Disputes 读端点 (#1013 Phase 86) — 5 endpoints (list/similar/detail/evidence-list/parties)
|
|
246
257
|
import { registerDisputesReadRoutes } from './routes/disputes-read.js';
|
|
247
258
|
// Disputes 写端点 (#1013 Phase 87) — 5 endpoints (respond/arbitrate/add-evidence/evidence-blob/request-evidence)
|
|
@@ -389,6 +400,7 @@ initSkillSchema(db);
|
|
|
389
400
|
initSkillMarketSchema(db);
|
|
390
401
|
initReputationSchema(db);
|
|
391
402
|
initOrderChainSchema(db);
|
|
403
|
+
initPendingCommissionEscrowSchema(db); // RFC-018 PR1: relocated from inline (was ~8250) so all escrow schema is built in this early batch — see helper. matures_at + reversed added (schema-only).
|
|
392
404
|
initBuildFeedbackSchema(db); // RFC-004 build_feedback
|
|
393
405
|
initBuildTasksSchema(db); // RFC-006 build_tasks(协调层)
|
|
394
406
|
initBuildTaskAgentMetadataSchema(db); // PR9B — agent-ready task metadata satellite(schema only;FUTURE-TASK-BOARD-V1-DESIGN #326)
|
|
@@ -917,6 +929,7 @@ const DEFAULT_PARAMS = [
|
|
|
917
929
|
// #420 P1-2/P1-3/P1-4:反滥用阈值(agent 信任公式 / strike 阶梯 / verifier outlier)→ 治理可调。
|
|
918
930
|
// 默认值 === 抽取前硬编码字面量(单一真相源在 anti-abuse-thresholds.ts;测试强制校验一致)。
|
|
919
931
|
...ANTI_ABUSE_PARAMS,
|
|
932
|
+
...DIRECT_PAY_CONTROL_PARAMS, ...DIRECT_PAY_AML_PARAMS,
|
|
920
933
|
];
|
|
921
934
|
for (const p of DEFAULT_PARAMS) {
|
|
922
935
|
try {
|
|
@@ -2957,7 +2970,7 @@ catch { }
|
|
|
2957
2970
|
const upd = db.prepare(`UPDATE products SET
|
|
2958
2971
|
last_sold_at = (SELECT MAX(COALESCE(updated_at, created_at)) FROM orders WHERE product_id = ? AND status = 'completed'),
|
|
2959
2972
|
first_sold_at = (SELECT MIN(COALESCE(updated_at, created_at)) FROM orders WHERE product_id = ? AND status = 'completed'),
|
|
2960
|
-
completion_count = (SELECT COUNT(1) FROM orders WHERE product_id = ? AND
|
|
2973
|
+
completion_count = (SELECT COUNT(1) FROM orders WHERE product_id = ? AND ${genuineSalePredicate('orders')}),
|
|
2961
2974
|
dispute_loss_count = (
|
|
2962
2975
|
SELECT COUNT(1) FROM disputes d JOIN orders o ON o.id = d.order_id
|
|
2963
2976
|
WHERE o.product_id = ? AND d.ruling_type IN ('refund_buyer','partial_refund')
|
|
@@ -3233,8 +3246,8 @@ function computeAgentTrust(apiKey) {
|
|
|
3233
3246
|
if (!user)
|
|
3234
3247
|
return null;
|
|
3235
3248
|
const ageDays = Math.max(0, (Date.now() - new Date(user.created_at.replace(' ', 'T') + 'Z').getTime()) / 86400_000);
|
|
3236
|
-
const completedBuyer = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND
|
|
3237
|
-
const completedSeller = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE seller_id = ? AND
|
|
3249
|
+
const completedBuyer = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND ${genuineSalePredicate('orders')}`).get(user.id).n;
|
|
3250
|
+
const completedSeller = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE seller_id = ? AND ${genuineSalePredicate('orders')}`).get(user.id).n;
|
|
3238
3251
|
const disputeLoss = db.prepare(`SELECT COUNT(*) as n FROM disputes WHERE defendant_id = ? AND ruling_type IN ('refund_buyer','partial_refund')`).get(user.id).n;
|
|
3239
3252
|
// 商品分享带来的真实成交(创作者贡献)
|
|
3240
3253
|
const shareConversions = db.prepare(`
|
|
@@ -4460,6 +4473,11 @@ registerAgentReputationRoutes(app, {
|
|
|
4460
4473
|
});
|
|
4461
4474
|
// #1013 Phase 47: 6 公开用户主页 endpoints 已迁出到 routes/users-public.ts
|
|
4462
4475
|
registerUsersPublicRoutes(app, { db, auth, noteAuthenticityBadges });
|
|
4476
|
+
registerDirectPayDisclosureAckRoutes(app, { db, auth, generateId, consumeGateToken }); // PR-4d
|
|
4477
|
+
registerDirectReceivePaymentInstructionRoutes(app, { db, auth, generateId });
|
|
4478
|
+
registerDirectReceiveAccountsRoutes(app, { db, auth, generateId, consumeGateToken }); // PR-4f-a + Phase C1
|
|
4479
|
+
registerDirectPayAvailabilityRoutes(app, { db, auth, getProtocolParam, generateId }); // PR-4a + 缓交 apply/status
|
|
4480
|
+
registerAdminDirectReceiveDepositsRoutes(app, { db, requireRootAdmin: (req, res) => requireRootAdmin(req, res), consumeGateToken, logAdminAction, getProtocolParam }); // PR-4b-3 + readiness
|
|
4463
4481
|
// RFC-004 build_feedback — agent-native "use → build" 反馈管道
|
|
4464
4482
|
registerBuildFeedbackRoutes(app, {
|
|
4465
4483
|
db, auth,
|
|
@@ -5652,7 +5670,7 @@ function checkVerifierEligibility(userId) {
|
|
|
5652
5670
|
const ageDays = Math.floor((Date.now() - new Date(user.created_at).getTime()) / 86400_000);
|
|
5653
5671
|
items.push({ key: 'age', label: '账户年龄 ≥ 60 天', current: ageDays, required: 60, ok: ageDays >= 60 });
|
|
5654
5672
|
items.push({ key: 'email', label: '邮箱已验证', current: user.email_verified ? '✓' : '✗', required: '✓', ok: !!user.email_verified });
|
|
5655
|
-
const orders = db.prepare(
|
|
5673
|
+
const orders = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE (buyer_id = ? OR seller_id = ?) AND ${genuineSalePredicate('orders')}`).get(userId, userId).n;
|
|
5656
5674
|
items.push({ key: 'orders', label: '完成订单 ≥ 20 笔', current: orders, required: 20, ok: orders >= 20 });
|
|
5657
5675
|
const disputeLost = db.prepare(`
|
|
5658
5676
|
SELECT COUNT(*) as n FROM disputes
|
|
@@ -5799,7 +5817,7 @@ function checkArbitratorEligibility(userId) {
|
|
|
5799
5817
|
const ageDays = Math.floor((Date.now() - new Date(user.created_at).getTime()) / 86400_000);
|
|
5800
5818
|
items.push({ key: 'age', label: '账户年龄 ≥ 90 天', current: ageDays, required: 90, ok: ageDays >= 90 });
|
|
5801
5819
|
items.push({ key: 'email', label: '邮箱已验证', current: user.email_verified ? '✓' : '✗', required: '✓', ok: !!user.email_verified });
|
|
5802
|
-
const orders = db.prepare(
|
|
5820
|
+
const orders = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE (buyer_id = ? OR seller_id = ?) AND ${genuineSalePredicate('orders')}`).get(userId, userId).n;
|
|
5803
5821
|
items.push({ key: 'orders', label: '完成订单 ≥ 50 笔', current: orders, required: 50, ok: orders >= 50 });
|
|
5804
5822
|
const disputeLost = db.prepare(`
|
|
5805
5823
|
SELECT COUNT(*) as n FROM disputes
|
|
@@ -6019,7 +6037,7 @@ registerOrdersReadRoutes(app, { db, auth, getOrderStatus, getOrderChain, verifyO
|
|
|
6019
6037
|
registerOrdersActionRoutes(app, {
|
|
6020
6038
|
db, auth, isTrustedRole, generateId, transition, notifyTransition,
|
|
6021
6039
|
settleOrder, settleFault, detectFraud, createDispute, checkTimeouts, recordViolationReputation,
|
|
6022
|
-
broadcastSystemEvent,
|
|
6040
|
+
broadcastSystemEvent, consumeGateToken,
|
|
6023
6041
|
});
|
|
6024
6042
|
// #1013 Phase 85: POST /api/orders 巨型事务已迁出
|
|
6025
6043
|
registerOrdersCreateRoutes(app, {
|
|
@@ -6134,8 +6152,8 @@ function awardBidAndCreateOrder(rfq, winner) {
|
|
|
6134
6152
|
db.prepare(`
|
|
6135
6153
|
INSERT INTO products (id, seller_id, title, description, price, stock, status, images,
|
|
6136
6154
|
ship_regions, handling_hours, commission_rate, category_id, stake_amount,
|
|
6137
|
-
listing_id, fulfillment_type, eta_hours, freshness_ts, cold_start_remaining)
|
|
6138
|
-
VALUES (?,?,?,?,?,?,'active','[]',?,?,?,?,0,?,?,?,datetime('now')
|
|
6155
|
+
listing_id, fulfillment_type, eta_hours, freshness_ts, cold_start_remaining, currency)
|
|
6156
|
+
VALUES (?,?,?,?,?,?,'active','[]',?,?,?,?,0,?,?,?,datetime('now'),?,'WAZ')
|
|
6139
6157
|
`).run(productId, sellerId, String(rfq.title), `[RFQ ${rfqId}] ` + (rfq.notes ? String(rfq.notes).slice(0, 200) : '协议撮合订单'), price, qty, String(rfq.region_required || '全国'), 24, 0, 'cat_default', lstId, fulfillmentType, etaHours, 30);
|
|
6140
6158
|
}
|
|
6141
6159
|
}
|
|
@@ -6589,8 +6607,8 @@ function settleAuctionInner(aucId) {
|
|
|
6589
6607
|
productId = generateId('p');
|
|
6590
6608
|
db.prepare(`
|
|
6591
6609
|
INSERT INTO products (id, seller_id, title, description, price, stock, status, images,
|
|
6592
|
-
ship_regions, handling_hours, commission_rate, category_id, stake_amount, listing_id, freshness_ts, cold_start_remaining)
|
|
6593
|
-
VALUES (?,?,?,?,?,?,'active','[]',?,?,?,?,0,?,datetime('now')
|
|
6610
|
+
ship_regions, handling_hours, commission_rate, category_id, stake_amount, listing_id, freshness_ts, cold_start_remaining, currency)
|
|
6611
|
+
VALUES (?,?,?,?,?,?,'active','[]',?,?,?,?,0,?,datetime('now'),?,'WAZ')
|
|
6594
6612
|
`).run(productId, sellerId, String(auc.title), `[AUC ${aucId}] ` + (auc.notes ? String(auc.notes).slice(0, 200) : '拍卖成交'), price, qty, '全国', 24, 0.10, 'cat_default', auc.listing_id ? String(auc.listing_id) : null, 30);
|
|
6595
6613
|
}
|
|
6596
6614
|
// 5) 截止期 — 拍卖订单走标准 17 天链(拍卖结束后再谈履约时效)
|
|
@@ -6718,7 +6736,7 @@ const TASK_DEFS = {
|
|
|
6718
6736
|
};
|
|
6719
6737
|
// 计算用户当前任务进度(不写库,纯读)
|
|
6720
6738
|
function computeTaskProgress(userId) {
|
|
6721
|
-
const completed = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND
|
|
6739
|
+
const completed = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND ${genuineSalePredicate('orders')}`).get(userId).n;
|
|
6722
6740
|
const ratingsGiven = db.prepare(`SELECT COUNT(*) as n FROM order_ratings WHERE buyer_id = ?`).get(userId).n;
|
|
6723
6741
|
const ratingsReceived = db.prepare(`SELECT COUNT(*) as n FROM order_ratings WHERE seller_id = ?`).get(userId).n;
|
|
6724
6742
|
const follows = db.prepare(`SELECT COUNT(*) as n FROM follows WHERE follower_id = ?`).get(userId).n;
|
|
@@ -6910,6 +6928,7 @@ registerShareablesInteractionsRoutes(app, {
|
|
|
6910
6928
|
parseMentions, notifyMentions,
|
|
6911
6929
|
});
|
|
6912
6930
|
registerLeaderboardRoutes(app, { db, internalAuditorId: INTERNAL_AUDITOR_ID, rateLimitOk });
|
|
6931
|
+
registerFxRoutes(app, { rateLimitOk });
|
|
6913
6932
|
// like-status / bookmark-status / bookmarked-shareables 已迁出 (#1013 Phase 12)
|
|
6914
6933
|
// ─── Manifest Registry API(原生 P2P 内容索引,零字节存储)─────
|
|
6915
6934
|
// ============================================================
|
|
@@ -7187,28 +7206,19 @@ function settleCommission(orderId, effectiveBase) {
|
|
|
7187
7206
|
{ level: 2, beneficiary: l2Uid },
|
|
7188
7207
|
{ level: 3, beneficiary: l3Uid },
|
|
7189
7208
|
];
|
|
7190
|
-
// #7 Commission source_type
|
|
7191
|
-
//
|
|
7192
|
-
function resolveSourceType(uid) {
|
|
7193
|
-
if (!uid)
|
|
7194
|
-
return 'sponsor';
|
|
7195
|
-
const attr = db.prepare(`
|
|
7196
|
-
SELECT shareable_id FROM product_share_attribution
|
|
7197
|
-
WHERE product_id = ? AND sharer_id = ? AND shareable_id IS NOT NULL
|
|
7198
|
-
ORDER BY created_at DESC LIMIT 1
|
|
7199
|
-
`).get(order.product_id, uid);
|
|
7200
|
-
if (!attr)
|
|
7201
|
-
return 'sponsor';
|
|
7202
|
-
const sh = db.prepare(`SELECT type FROM shareables WHERE id = ?`).get(attr.shareable_id);
|
|
7203
|
-
if (!sh)
|
|
7204
|
-
return 'sponsor';
|
|
7205
|
-
return sh.type === 'note' ? 'note' : 'link';
|
|
7206
|
-
}
|
|
7209
|
+
// #7 Commission source_type resolution moved to module-level commissionSourceType() — RFC-018
|
|
7210
|
+
// Option A writes commission_records at MATURATION (not here), so source_type is stamped there.
|
|
7207
7211
|
// 2026-06-04:所有兜底统一入 commission_reserve(三级公池,独立科目,只进不出)。
|
|
7208
7212
|
// commission 不再回流 global_fund(PV 资金)—— 三套科目解耦,redirected 始终 0。
|
|
7209
7213
|
let toCommissionReserve = 0; // → commission_reserve(仅作日志/返回信息用)
|
|
7210
7214
|
// 三级金额一次性 allocate(精确求和 ≡ poolU);各级再按 gate 路由到钱包/公池/escrow。
|
|
7211
7215
|
const levelAmtU = allocate(poolU, [LEVEL_RATES[1], LEVEL_RATES[2], LEVEL_RATES[3]]);
|
|
7216
|
+
// RFC-018 clearing window: opted-in commission accrues to pending_commission_escrow and matures
|
|
7217
|
+
// after the product's return window + buffer. Anchor = now (settleCommission runs at order→completed).
|
|
7218
|
+
const nowMsClearing = Date.now();
|
|
7219
|
+
const returnDaysClearing = Number(db.prepare("SELECT return_days FROM products WHERE id = ?").get(order.product_id)?.return_days ?? 7);
|
|
7220
|
+
const clearingBufferDays = Number(db.prepare("SELECT value FROM protocol_params WHERE key = 'settlement.clearing_buffer_days'").get()?.value ?? 2);
|
|
7221
|
+
const maturesAtClearing = nowMsClearing + Math.max(0, returnDaysClearing + clearingBufferDays) * 86400 * 1000;
|
|
7212
7222
|
for (const { level, beneficiary } of recipients) {
|
|
7213
7223
|
const amountU = levelAmtU[level - 1];
|
|
7214
7224
|
const amount = toDecimal(amountU);
|
|
@@ -7254,15 +7264,17 @@ function settleCommission(orderId, effectiveBase) {
|
|
|
7254
7264
|
catch (e) { /* UNIQUE 冲突 — settleCommission 重入幂等 */ }
|
|
7255
7265
|
continue;
|
|
7256
7266
|
}
|
|
7257
|
-
// ⑤
|
|
7267
|
+
// ⑤ RFC-018 clearing (Option A): opted-in commission ACCRUES to pending_commission_escrow with a
|
|
7268
|
+
// matures_at — it is NOT credited to the wallet now and writes NO commission_records yet. The
|
|
7269
|
+
// maturation cron (rewards-clearing-mature) re-validates the order is still genuinely closed, then
|
|
7270
|
+
// writes commission_records + credits the wallet. A return in-window reverses the pending row
|
|
7271
|
+
// (proportionally) — never paid, never clawed back. expires_at is a NOT-NULL filler = matures_at;
|
|
7272
|
+
// the escrow-expire cron ignores rows where matures_at IS NOT NULL (i.e. clearing rows).
|
|
7258
7273
|
try {
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
VALUES (?,?,?,?,?,?,?,?,?,?)`)
|
|
7262
|
-
.run(generateId('comm'), orderId, beneficiary, order.buyer_id, level, amount, rate, region, routeSource, srcType);
|
|
7263
|
-
applyWalletDelta(db, beneficiary, { balance: amountU, earned: amountU });
|
|
7274
|
+
db.prepare(`INSERT INTO pending_commission_escrow (recipient_user_id, order_id, amount, attribution_path, status, created_at, expires_at, matures_at) VALUES (?,?,?,?,'pending',?,?,?)`)
|
|
7275
|
+
.run(beneficiary, orderId, amount, `L${level}`, nowMsClearing, maturesAtClearing, maturesAtClearing);
|
|
7264
7276
|
}
|
|
7265
|
-
catch (e) { /* UNIQUE
|
|
7277
|
+
catch (e) { /* UNIQUE (recipient,order,path) — settleCommission re-entry idempotent */ }
|
|
7266
7278
|
}
|
|
7267
7279
|
db.prepare("UPDATE orders SET settled_commission_at = datetime('now') WHERE id = ?").run(orderId);
|
|
7268
7280
|
// redirected 恒为 0:commission 兜底全部入 commission_reserve,不再回流 global_fund(PV 资金)。
|
|
@@ -7378,6 +7390,10 @@ function settleOrder(orderId) {
|
|
|
7378
7390
|
// 内部 try/catch 用于非关键 hook(settlePinRewards / metrics 更新)失败不回滚资金主流程
|
|
7379
7391
|
db.transaction(() => {
|
|
7380
7392
|
const order = db.prepare('SELECT * FROM orders WHERE id = ?').get(orderId);
|
|
7393
|
+
if (order && order.payment_rail === 'direct_p2p') {
|
|
7394
|
+
settleDirectPayFeeAtCompletion(db, order, generateId('dpfr'));
|
|
7395
|
+
return;
|
|
7396
|
+
} // Rail1 直付:跳过 escrow 结算;平台费=链下应收(释放遗留模拟 stake + accrue,fail-closed 同原子边界)
|
|
7381
7397
|
const total = order.total_amount;
|
|
7382
7398
|
const isSecondhand = order.source === 'secondhand';
|
|
7383
7399
|
const isInPerson = order.fulfillment_mode === 'in_person';
|
|
@@ -8704,72 +8720,10 @@ try {
|
|
|
8704
8720
|
db.exec('CREATE INDEX IF NOT EXISTS idx_rewards_apps_action ON rewards_applications(user_id, action, created_at DESC)');
|
|
8705
8721
|
}
|
|
8706
8722
|
catch { }
|
|
8707
|
-
//
|
|
8708
|
-
//
|
|
8709
|
-
//
|
|
8710
|
-
//
|
|
8711
|
-
// in settleCommission (not by NOT NULL constraint).
|
|
8712
|
-
db.exec(`
|
|
8713
|
-
CREATE TABLE IF NOT EXISTS pending_commission_escrow (
|
|
8714
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8715
|
-
recipient_user_id TEXT NOT NULL,
|
|
8716
|
-
order_id TEXT, -- NULL for pv_pair (PR-1c-b)
|
|
8717
|
-
amount REAL NOT NULL, -- WAZ amount
|
|
8718
|
-
attribution_path TEXT NOT NULL, -- 'L1' | 'L2' | 'L3' | 'pv_pair' | etc.
|
|
8719
|
-
status TEXT NOT NULL DEFAULT 'pending', -- 'pending' | 'settled' | 'expired'
|
|
8720
|
-
created_at INTEGER NOT NULL,
|
|
8721
|
-
expires_at INTEGER NOT NULL,
|
|
8722
|
-
settled_at INTEGER,
|
|
8723
|
-
expired_to_charity_at INTEGER,
|
|
8724
|
-
FOREIGN KEY (recipient_user_id) REFERENCES users(id),
|
|
8725
|
-
FOREIGN KEY (order_id) REFERENCES orders(id)
|
|
8726
|
-
)
|
|
8727
|
-
`);
|
|
8728
|
-
(function migrateEscrowOrderIdNullable() {
|
|
8729
|
-
const cols = db.prepare("PRAGMA table_info(pending_commission_escrow)").all();
|
|
8730
|
-
const orderIdCol = cols.find(c => c.name === 'order_id');
|
|
8731
|
-
if (!orderIdCol || orderIdCol.notnull === 0)
|
|
8732
|
-
return; // already nullable (or table missing entirely)
|
|
8733
|
-
console.log('[pc-escrow-migrate] order_id is NOT NULL — recreating table to allow NULL for pv_pair');
|
|
8734
|
-
db.exec('PRAGMA foreign_keys = OFF');
|
|
8735
|
-
db.transaction(() => {
|
|
8736
|
-
db.exec(`
|
|
8737
|
-
CREATE TABLE pending_commission_escrow_new (
|
|
8738
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8739
|
-
recipient_user_id TEXT NOT NULL,
|
|
8740
|
-
order_id TEXT,
|
|
8741
|
-
amount REAL NOT NULL,
|
|
8742
|
-
attribution_path TEXT NOT NULL,
|
|
8743
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
8744
|
-
created_at INTEGER NOT NULL,
|
|
8745
|
-
expires_at INTEGER NOT NULL,
|
|
8746
|
-
settled_at INTEGER,
|
|
8747
|
-
expired_to_charity_at INTEGER,
|
|
8748
|
-
FOREIGN KEY (recipient_user_id) REFERENCES users(id),
|
|
8749
|
-
FOREIGN KEY (order_id) REFERENCES orders(id)
|
|
8750
|
-
)
|
|
8751
|
-
`);
|
|
8752
|
-
db.exec('INSERT INTO pending_commission_escrow_new SELECT * FROM pending_commission_escrow');
|
|
8753
|
-
db.exec('DROP TABLE pending_commission_escrow');
|
|
8754
|
-
db.exec('ALTER TABLE pending_commission_escrow_new RENAME TO pending_commission_escrow');
|
|
8755
|
-
})();
|
|
8756
|
-
db.exec('PRAGMA foreign_keys = ON');
|
|
8757
|
-
})();
|
|
8758
|
-
try {
|
|
8759
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_escrow_recipient ON pending_commission_escrow(recipient_user_id, status, expires_at)');
|
|
8760
|
-
}
|
|
8761
|
-
catch { }
|
|
8762
|
-
try {
|
|
8763
|
-
db.exec('CREATE INDEX IF NOT EXISTS idx_escrow_expiry ON pending_commission_escrow(status, expires_at)');
|
|
8764
|
-
}
|
|
8765
|
-
catch { }
|
|
8766
|
-
// PR-1c-a: UNIQUE (recipient, order, path) defends against double-insert if settleCommission ever retries
|
|
8767
|
-
// Note: NULL order_id (PR-1c-b pv_pair) is distinct in SQLite UNIQUE — idempotency for pv_pair relies
|
|
8768
|
-
// on binary_score_records.settled_at instead (source-side dedup).
|
|
8769
|
-
try {
|
|
8770
|
-
db.exec('CREATE UNIQUE INDEX IF NOT EXISTS uniq_escrow_recipient_order_path ON pending_commission_escrow(recipient_user_id, order_id, attribution_path)');
|
|
8771
|
-
}
|
|
8772
|
-
catch { }
|
|
8723
|
+
// pending_commission_escrow schema (table + migration + indexes) lives in
|
|
8724
|
+
// initPendingCommissionEscrowSchema (early helper batch above) — RFC-018 PR1 relocated it there so
|
|
8725
|
+
// all of its DDL is built once, never half-inline (fresh-DB silent-fail铁律). The backfill below
|
|
8726
|
+
// only READS the table; it still runs after the build (early batch precedes this line).
|
|
8773
8727
|
// Codex #69 P1:pv_escrow_reserve(#1106 隔离负债账)回填历史 pending pv_pair escrow —— 按 delta 对账,不全量转。
|
|
8774
8728
|
// 该列在 global_fund 上新增(line ~2319);#1106 之后新建的 pv_pair escrow 结算时【已】pv_escrow_reserve += wazAmount,
|
|
8775
8729
|
// 但加列【之前】产生的 pending pv_pair 从没进过 reserve。升级窗口里两者混存。
|
|
@@ -8819,6 +8773,7 @@ const RFC002_PARAMS = [
|
|
|
8819
8773
|
{ key: 'rewards_opt_in.min_completed_orders', value: '1', type: 'number', description: 'RFC-002 §3.2:申请 rewards opt-in 的最小已完成订单数', category: 'rewards', min: 0, max: 100, metaRuleLocked: false },
|
|
8820
8774
|
{ key: 'rewards_opt_in.require_passkey', value: '1', type: 'number', description: 'RFC-002 §3.3:申请 / 关闭是否需 Passkey(1=必须,0=允许 password)— META-RULE LOCKED,降低需 60d meta-rule track', category: 'rewards', min: 0, max: 1, metaRuleLocked: true },
|
|
8821
8775
|
{ key: 'rewards_opt_in.escrow_days', value: '30', type: 'number', description: 'RFC-002 §3.5b:pending commission escrow 过期天数(过期后流入 charity_fund)', category: 'rewards', min: 7, max: 180, metaRuleLocked: false },
|
|
8776
|
+
{ key: 'settlement.clearing_buffer_days', value: '2', type: 'number', description: 'RFC-018:佣金清算期 = 商品 return_days + 此 buffer(成熟才入钱包;窗内退货则冲销,永不 clawback)', category: 'rewards', min: 0, max: 30, metaRuleLocked: false },
|
|
8822
8777
|
{ key: 'rewards_opt_in.consent_delay_seconds', value: '8', type: 'number', description: 'RFC-002 §3.3:server-side 8s 反诱导延迟 — META-RULE LOCKED,降低需 60d meta-rule track', category: 'rewards', min: 0, max: 60, metaRuleLocked: true },
|
|
8823
8778
|
{ key: 'rewards_opt_in.reconfirm_grace_days', value: '14', type: 'number', description: 'RFC-002 §3.10:major consent 变更后用户重新确认 grace 期(过期 auto_downgrade)', category: 'rewards', min: 3, max: 90, metaRuleLocked: false },
|
|
8824
8779
|
];
|
|
@@ -8907,6 +8862,8 @@ app.listen(PORT, () => {
|
|
|
8907
8862
|
startAutoDeactivateCron({ db, generateId, getProtocolParam });
|
|
8908
8863
|
// #1090 RFC-002 PR-1c-a: escrow expire cron (every 1h)
|
|
8909
8864
|
startEscrowExpireCron({ db, redirectToCommissionReserve });
|
|
8865
|
+
startDirectPayTimeoutCron({ db });
|
|
8866
|
+
startClearingMatureCron({ db }); // RFC-018: pay/hold matured clearing commission (logic in the cron module)
|
|
8910
8867
|
// #1090 RFC-002 PR-3 slice 2: auto_downgrade cron (every 24h)
|
|
8911
8868
|
// Triggered when a new major consent text is published; opted-in users
|
|
8912
8869
|
// who don't reconfirm within reconfirm_grace_days (14d default) get
|