@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.
Files changed (111) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +7 -6
  3. package/dist/currency.js +16 -0
  4. package/dist/deposit-rails.js +52 -0
  5. package/dist/direct-pay-aml-monitor.js +67 -0
  6. package/dist/direct-pay-aml-review.js +40 -0
  7. package/dist/direct-pay-base-bond-entry.js +5 -0
  8. package/dist/direct-pay-bond-rail-clearance.js +94 -0
  9. package/dist/direct-pay-compliance-ingress.js +145 -0
  10. package/dist/direct-pay-controls.js +136 -0
  11. package/dist/direct-pay-create.js +162 -0
  12. package/dist/direct-pay-deferral-quota.js +43 -0
  13. package/dist/direct-pay-disclosures.js +44 -0
  14. package/dist/direct-pay-eligibility.js +46 -0
  15. package/dist/direct-pay-fee-ar.js +241 -0
  16. package/dist/direct-pay-launch-readiness.js +108 -0
  17. package/dist/direct-pay-launch-summary.js +68 -0
  18. package/dist/direct-pay-ledger.js +94 -0
  19. package/dist/direct-receive-account-qr.js +77 -0
  20. package/dist/direct-receive-accounts.js +82 -0
  21. package/dist/direct-receive-deferral.js +142 -0
  22. package/dist/direct-receive-deposits.js +260 -0
  23. package/dist/direct-receive-payment-instruction.js +26 -0
  24. package/dist/fx-rates.js +71 -0
  25. package/dist/layer0-foundation/L0-1-database/schema.js +531 -1
  26. package/dist/layer0-foundation/L0-2-state-machine/genuine-sale.js +15 -5
  27. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +41 -0
  28. package/dist/layer0-foundation/L0-5-manifest/manifest.js +1 -1
  29. package/dist/layer1-agent/L1-1-mcp-server/cli.js +64 -0
  30. package/dist/layer1-agent/L1-1-mcp-server/network-mode.js +11 -0
  31. package/dist/layer1-agent/L1-1-mcp-server/server.js +18 -16
  32. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +4 -4
  33. package/dist/ledger.js +12 -3
  34. package/dist/mcp.js +23 -4
  35. package/dist/merchant-bond-domain.js +64 -0
  36. package/dist/merchant-bond-exposure.js +78 -0
  37. package/dist/merchant-bond-watcher.js +26 -0
  38. package/dist/payment-rails.js +29 -0
  39. package/dist/product-verification.js +93 -0
  40. package/dist/pwa/acp-feed.js +3 -2
  41. package/dist/pwa/contract-fingerprint.js +3 -0
  42. package/dist/pwa/direct-pay-guards.js +20 -0
  43. package/dist/pwa/direct-pay-order-redaction.js +24 -0
  44. package/dist/pwa/endpoint-actions.js +3 -0
  45. package/dist/pwa/public/app-ai.js +10 -10
  46. package/dist/pwa/public/app-chat-poll.js +29 -0
  47. package/dist/pwa/public/app-create-kinds.js +17 -0
  48. package/dist/pwa/public/app-direct-pay-accounts.js +141 -0
  49. package/dist/pwa/public/app-direct-pay-buyer.js +72 -0
  50. package/dist/pwa/public/app-direct-pay-compliance.js +67 -0
  51. package/dist/pwa/public/app-direct-pay-deferral-admin.js +72 -0
  52. package/dist/pwa/public/app-direct-pay-deferral.js +61 -0
  53. package/dist/pwa/public/app-direct-pay-fee-center.js +33 -0
  54. package/dist/pwa/public/app-direct-pay-fee-ops.js +112 -0
  55. package/dist/pwa/public/app-direct-pay-product-verify.js +103 -0
  56. package/dist/pwa/public/app-direct-pay-readiness.js +38 -0
  57. package/dist/pwa/public/app-direct-pay-store-verify.js +100 -0
  58. package/dist/pwa/public/app-direct-pay.js +227 -0
  59. package/dist/pwa/public/app-discover.js +20 -20
  60. package/dist/pwa/public/app-external-links.js +32 -0
  61. package/dist/pwa/public/app-listings.js +4 -4
  62. package/dist/pwa/public/app-prelaunch-waz.js +39 -0
  63. package/dist/pwa/public/app-price.js +55 -0
  64. package/dist/pwa/public/app-product-media.js +15 -0
  65. package/dist/pwa/public/app-profile.js +6 -6
  66. package/dist/pwa/public/app-seller.js +5 -5
  67. package/dist/pwa/public/app-shop.js +19 -19
  68. package/dist/pwa/public/app.js +142 -146
  69. package/dist/pwa/public/i18n.js +398 -197
  70. package/dist/pwa/public/index.html +17 -0
  71. package/dist/pwa/public/openapi.json +495 -1
  72. package/dist/pwa/routes/admin-analytics.js +4 -2
  73. package/dist/pwa/routes/admin-direct-receive-deposits.js +321 -0
  74. package/dist/pwa/routes/buyer-feeds.js +2 -1
  75. package/dist/pwa/routes/chat.js +6 -1
  76. package/dist/pwa/routes/dashboards.js +2 -1
  77. package/dist/pwa/routes/direct-pay-availability.js +196 -0
  78. package/dist/pwa/routes/direct-pay-disclosure-acks.js +74 -0
  79. package/dist/pwa/routes/direct-pay-timeouts.js +71 -0
  80. package/dist/pwa/routes/direct-receive-accounts.js +155 -0
  81. package/dist/pwa/routes/direct-receive-payment-instructions.js +45 -0
  82. package/dist/pwa/routes/fx.js +12 -0
  83. package/dist/pwa/routes/leaderboard.js +47 -9
  84. package/dist/pwa/routes/listings.js +2 -2
  85. package/dist/pwa/routes/logistics.js +4 -0
  86. package/dist/pwa/routes/manifests.js +38 -0
  87. package/dist/pwa/routes/me-data.js +5 -2
  88. package/dist/pwa/routes/orders-action.js +117 -9
  89. package/dist/pwa/routes/orders-create.js +4 -0
  90. package/dist/pwa/routes/orders-read.js +36 -0
  91. package/dist/pwa/routes/p2p-products.js +2 -2
  92. package/dist/pwa/routes/products-create.js +5 -3
  93. package/dist/pwa/routes/products-links.js +34 -0
  94. package/dist/pwa/routes/products-list.js +2 -1
  95. package/dist/pwa/routes/products-update.js +22 -2
  96. package/dist/pwa/routes/promoter.js +3 -0
  97. package/dist/pwa/routes/referral.js +4 -0
  98. package/dist/pwa/routes/returns.js +26 -1
  99. package/dist/pwa/routes/rewards-apply.js +10 -5
  100. package/dist/pwa/routes/rewards-clearing-mature.js +96 -0
  101. package/dist/pwa/routes/rewards-escrow-expire.js +6 -2
  102. package/dist/pwa/routes/shops.js +2 -1
  103. package/dist/pwa/routes/url-claim.js +2 -2
  104. package/dist/pwa/routes/users-public.js +8 -0
  105. package/dist/pwa/routes/wallet-read.js +3 -0
  106. package/dist/pwa/routes/webauthn.js +1 -1
  107. package/dist/pwa/server.js +59 -102
  108. package/dist/runtime/webaz-schema-helpers.js +104 -0
  109. package/dist/store-verification.js +77 -0
  110. package/dist/version.js +1 -1
  111. package/package.json +71 -2
@@ -41,7 +41,7 @@ export function initDatabase() {
41
41
  title TEXT NOT NULL,
42
42
  description TEXT NOT NULL,
43
43
  price REAL NOT NULL,
44
- currency TEXT DEFAULT 'DCP', -- 协议内部模拟货币
44
+ currency TEXT DEFAULT 'WAZ', -- 协议内部模拟单位(WAZ);旧默认 'DCP' 已翻转 + 存量回填(见迁移段),existing-DB 建单路径亦显式写 WAZ
45
45
  stock INTEGER DEFAULT 1,
46
46
  category TEXT,
47
47
  images TEXT DEFAULT '[]', -- JSON 数组,存图片路径
@@ -93,6 +93,15 @@ export function initDatabase() {
93
93
  shipping_address TEXT, -- 收货地址(JSON)
94
94
  notes TEXT, -- 买家备注
95
95
 
96
+ -- PR-5b-0: direct_p2p 建单时的【入口控制 policy 快照】(冻结当时的 cfg + 判定码;5b wiring 才写入,本 PR 仅建列)。
97
+ -- 全部 additive nullable;布尔以 0/1 存,cap 以整数 base-units 存。仅 direct_p2p 单写,其它 rail 恒 NULL。
98
+ direct_pay_enabled_snapshot INTEGER, -- 建单时 direct_pay.enabled 快照(0/1)
99
+ direct_pay_rail_breaker_snapshot INTEGER, -- 建单时 rail_breaker_tripped 快照(0/1)
100
+ direct_pay_region_snapshot TEXT, -- 建单时 region 快照
101
+ direct_pay_region_allowlist_snapshot TEXT, -- 建单时 region_allowlist 快照(CSV/JSON 文本)
102
+ direct_pay_per_tx_cap_units_snapshot INTEGER, -- 建单时单笔上限快照(整数 policy base-units;WebAZ 记录订单总额天花板,非场外实付控制)
103
+ direct_pay_seller_breaker_snapshot INTEGER, -- 建单时 sellerBreakerTripped 快照(0/1)
104
+ direct_pay_decision_code TEXT, -- 建单时控制面判定码快照(成功通常 NULL / 'OK';拒绝码见 DirectPayControlReason)
96
105
  created_at TEXT DEFAULT (datetime('now')),
97
106
  updated_at TEXT DEFAULT (datetime('now'))
98
107
  );
@@ -172,6 +181,436 @@ export function initDatabase() {
172
181
  created_at TEXT DEFAULT (datetime('now'))
173
182
  );
174
183
 
184
+ /* ──────────────────────────────────────────
185
+ 直接支付(Direct Pay)Rail 1 · 非托管撮合 + 信誉轨
186
+ 本金(货款)不经协议;以下表仅记录【资格/担保物/费用质押/风控/AML/披露】元数据。
187
+ 设计稿: docs/modules/DIRECT-PAYMENT-MODULE-DESIGN.INTERNAL.md (Rev 2026-06-27e)
188
+ ────────────────────────────────────────── */
189
+
190
+ -- 卖家"直接收款"资格(一真人一资格;档位 T0/T1/T2 决定配额)
191
+ CREATE TABLE IF NOT EXISTS direct_receive_privileges (
192
+ user_id TEXT PRIMARY KEY REFERENCES users(id),
193
+ status TEXT NOT NULL DEFAULT 'none', -- none | active | suspended
194
+ tier TEXT NOT NULL DEFAULT 'T0', -- T0 | T1 | T2
195
+ suspended_reason TEXT,
196
+ granted_at TEXT,
197
+ updated_at TEXT DEFAULT (datetime('now'))
198
+ );
199
+
200
+ -- base bond = 履约担保物(merchant performance security deposit);法律/会计上独立于买家货款。
201
+ -- currency 必须【显式传入】,只允许 usdc | fiat(外部真实资产,生产收款经 deposit-rail 网关 legal-review gated;本 PR/4b 仅非生产确认)。
202
+ -- WAZ【未启用】为 base-bond currency —— 无默认值 + CHECK 双重拦截;domain helper openDeposit 亦拒(4c 前路由不得 raw-insert 绕过)。
203
+ CREATE TABLE IF NOT EXISTS direct_receive_deposits (
204
+ id TEXT PRIMARY KEY,
205
+ user_id TEXT NOT NULL REFERENCES users(id),
206
+ tier TEXT NOT NULL DEFAULT 'T0',
207
+ required_amount REAL NOT NULL, -- 该档要求的 bond
208
+ amount REAL NOT NULL DEFAULT 0, -- 实际到位
209
+ currency TEXT NOT NULL CHECK (currency IN ('usdc','fiat')), -- 显式传入,无默认;只允许 usdc|fiat;WAZ 未启用(生产收款 legal-review gated)
210
+ deposit_rail TEXT NOT NULL DEFAULT 'manual', -- manual(仅 test/非生产) | usdc_onchain | fiat_psp (后两者 GATED)
211
+ external_ref TEXT, -- 链上 tx / PSP 凭证引用
212
+ status TEXT NOT NULL DEFAULT 'pending',-- pending|confirmed|locked|insufficient|expired|refunding|refunded|slashed
213
+ confirmed_at TEXT,
214
+ locked_at TEXT,
215
+ released_at TEXT,
216
+ production_receipt_confirmed_at TEXT, -- 仅【真实生产收款】(USDC 链上 / 法币 PSP)确认时置;manual/非生产恒 NULL。生产 go-live 必须要求非 NULL,杜绝 manual rail 冒充 base bond 到位。
217
+ -- PR-4b-1 生产收款 provenance 快照(仅在 production_receipt_confirmed_at 一并写;manual/非生产恒 NULL)。
218
+ -- 刻意 production_ 前缀以区别于运营列 currency/deposit_rail/external_ref(后三者 manual 轨也用)。本 PR 只加列,无写入方。
219
+ production_receipt_ref TEXT, -- 生产收款凭证引用(链上 tx / PSP receipt id);独立于 external_ref(manual 也写)
220
+ production_rail_id TEXT, -- 确认时的 legal-cleared 生产轨 id 快照(usdc_onchain / fiat_psp)
221
+ production_jurisdiction TEXT, -- 法务管辖区快照(担保物定性所适用法域)
222
+ production_policy_version TEXT, -- 确认时生效的 base-bond/合规 policy 版本快照(审计可追溯)
223
+ created_at TEXT DEFAULT (datetime('now')),
224
+ updated_at TEXT DEFAULT (datetime('now'))
225
+ );
226
+
227
+ -- 卖家自填【收款说明】(展示给买家的纯文本/handle/label)。这【不是】payment rail / escrow / PSP / 币种路由:
228
+ -- WebAZ 只存储 + 下单时快照,绝不验证/路由/托管/判断币种/做 allowlist。下单读取卖家当前 active 一条,快照进 order。
229
+ CREATE TABLE IF NOT EXISTS direct_receive_payment_instructions (
230
+ id TEXT PRIMARY KEY,
231
+ seller_id TEXT NOT NULL REFERENCES users(id),
232
+ instruction TEXT NOT NULL, -- 卖家自填、展示给买家的收款说明(场外结算用;WebAZ 不解析)
233
+ label TEXT, -- 可选短标签(如 "PayNow" / "Bank")
234
+ status TEXT NOT NULL DEFAULT 'active',-- active | inactive
235
+ created_at TEXT DEFAULT (datetime('now')),
236
+ updated_at TEXT DEFAULT (datetime('now'))
237
+ );
238
+
239
+ -- 直付多收款账号(direct_receive_accounts,Phase B):卖家可维护【多个】收款方式,每个自带币种 + 可选二维码图。
240
+ -- ⚠️ 与单条 payment_instruction 同性质:WebAZ 只【存储 + 展示】卖家自填内容,【绝不】验证/路由/托管/判断币种,
241
+ -- 也不解析二维码。currency 仅供买家侧换算展示(FX 支持才显 ≈本地);qr_image_ref 指向硬化图片端点(Phase C)。
242
+ -- 多行模型:一个 seller 可有【多个】active(买家下单自选其一),与单 instruction 的"至多一条 active"不同。
243
+ CREATE TABLE IF NOT EXISTS direct_receive_accounts (
244
+ id TEXT PRIMARY KEY,
245
+ seller_id TEXT NOT NULL REFERENCES users(id),
246
+ method TEXT, -- 收款方式名(卖家自填,如 PayNow / GCash / PromptPay / Bank)
247
+ currency TEXT, -- 该账号结算币种(卖家声明;买家侧按此换算,FX 支持则显 ≈本地)
248
+ instruction TEXT NOT NULL, -- 展示给买家的收款明细(账号 / 钱包 ID / 链接;WebAZ 不解析)
249
+ label TEXT, -- 可选短标签
250
+ qr_image_ref TEXT, -- 可选收款二维码图片引用(Phase C 填;硬化端点服务,绝不解析)
251
+ status TEXT NOT NULL DEFAULT 'active',-- active | inactive
252
+ created_at TEXT DEFAULT (datetime('now')),
253
+ updated_at TEXT DEFAULT (datetime('now'))
254
+ );
255
+
256
+ -- 直付收款二维码图(Phase C):卖家收款码原始字节。【不可变、按内容寻址(per-seller)】—— ref = sha256(bytes),
257
+ -- replace = 插新行,旧行永不改/删(触发器强制),这样订单可在建单时快照 (ref, seller_id) 并在 D1/D2 ack 后取回【当时那一版】QR。
258
+ -- ⚠️ 主键 = (ref, seller_id):内容寻址【按卖家隔离】。两个卖家上传【相同】字节时各得一行(否则 INSERT OR IGNORE
259
+ -- 只留首个卖家的行,第二个卖家 owner-scoped 读 = 命中不到 → "上传成功但预览 404")。dedup 只在同一卖家内发生。
260
+ -- WebAZ 只存字节、经硬化端点转发,绝不解析二维码含义 / 不验证收款方 / 不路由资金。仅 png|webp、解码 ≤64KB。
261
+ CREATE TABLE IF NOT EXISTS direct_receive_account_qr_images (
262
+ ref TEXT NOT NULL, -- = sha256(decoded bytes),内容寻址、不可变
263
+ account_id TEXT NOT NULL REFERENCES direct_receive_accounts(id),
264
+ seller_id TEXT NOT NULL REFERENCES users(id),
265
+ mime TEXT NOT NULL, -- 'image/png' | 'image/webp'(仅此二者)
266
+ data_b64 TEXT NOT NULL, -- base64 原始字节(硬化端点 forced content-type + nosniff + no-store 转发)
267
+ byte_len INTEGER NOT NULL, -- 解码后字节数(≤ 65536)
268
+ sha256 TEXT NOT NULL, -- = ref(冗余留证)
269
+ created_at TEXT DEFAULT (datetime('now')),
270
+ PRIMARY KEY (ref, seller_id) -- per-seller 内容寻址:同字节跨卖家各存一行
271
+ );
272
+ CREATE INDEX IF NOT EXISTS idx_dr_qr_account ON direct_receive_account_qr_images(account_id);
273
+ CREATE TRIGGER IF NOT EXISTS trg_dr_qr_no_update BEFORE UPDATE ON direct_receive_account_qr_images BEGIN SELECT RAISE(ABORT, 'direct_receive_account_qr_images is content-addressed & immutable (UPDATE forbidden)'); END;
274
+ CREATE TRIGGER IF NOT EXISTS trg_dr_qr_no_delete BEFORE DELETE ON direct_receive_account_qr_images BEGIN SELECT RAISE(ABORT, 'direct_receive_account_qr_images is immutable (DELETE forbidden — keep for order snapshots)'); END;
275
+
276
+ -- 直付收款账号审计(Phase C):append-only,记事件+account/qr ref,【绝不】写 raw instruction / raw QR。
277
+ CREATE TABLE IF NOT EXISTS direct_receive_account_events (
278
+ id TEXT PRIMARY KEY,
279
+ account_id TEXT NOT NULL,
280
+ seller_id TEXT NOT NULL,
281
+ event_type TEXT NOT NULL, -- account_added | account_updated | account_deactivated | qr_uploaded
282
+ qr_ref TEXT, -- qr 事件时的 ref(= sha256);非 qr 事件为 NULL。绝无 raw 内容
283
+ created_at TEXT DEFAULT (datetime('now'))
284
+ );
285
+ CREATE INDEX IF NOT EXISTS idx_dr_acct_events_account ON direct_receive_account_events(account_id);
286
+ CREATE TRIGGER IF NOT EXISTS trg_dr_acct_events_no_update BEFORE UPDATE ON direct_receive_account_events BEGIN SELECT RAISE(ABORT, 'direct_receive_account_events is append-only (UPDATE forbidden)'); END;
287
+ CREATE TRIGGER IF NOT EXISTS trg_dr_acct_events_no_delete BEFORE DELETE ON direct_receive_account_events BEGIN SELECT RAISE(ABORT, 'direct_receive_account_events is append-only (DELETE forbidden)'); END;
288
+
289
+ -- 缓交(deferred-deposit)申请;审批=真人(RISK),绝不自动;缓交期配额压低,绝不零威慑。
290
+ CREATE TABLE IF NOT EXISTS direct_receive_deferrals (
291
+ id TEXT PRIMARY KEY,
292
+ user_id TEXT NOT NULL REFERENCES users(id),
293
+ reason TEXT,
294
+ period_days INTEGER NOT NULL,
295
+ reduced_quota_factor REAL NOT NULL DEFAULT 0.5, -- 缓交期配额压低系数(<1,带下限)
296
+ status TEXT NOT NULL DEFAULT 'pending', -- pending|granted|rejected|expired
297
+ approved_by TEXT REFERENCES users(id), -- 真人 admin
298
+ approved_at TEXT,
299
+ expires_at TEXT,
300
+ grace_until TEXT,
301
+ created_at TEXT DEFAULT (datetime('now'))
302
+ );
303
+
304
+ -- 【按产品】外部平台商品认证(per-product verification)。降低作弊:一次验证【绝不】默认放行该卖家所有产品 ——
305
+ -- 每个要走直付收款的产品都必须【单独】被真人 admin 手动核验通过(硬门:未验证产品 direct-pay 不可用,退回托管)。
306
+ -- 诚实边界:WebAZ【绝不】抓取 external_url(无 SSRF、无"WebAZ 已核验该商品/店铺真实性"超claim)。机制 = 卖家为【该产品】
307
+ -- 申领 code → 展示在其外部平台商品页 → 提交该产品链接 → 真人 admin 手动打开核对 → attest。记录的最弱准确事实 =
308
+ -- "admin <id> 于 <时间> 手动确认产品 <product_id> 在 <url> 展示了验证码 <code>"。状态:issued→submitted→verified|rejected。
309
+ CREATE TABLE IF NOT EXISTS product_verifications (
310
+ id TEXT PRIMARY KEY,
311
+ product_id TEXT NOT NULL REFERENCES products(id),
312
+ seller_id TEXT NOT NULL REFERENCES users(id),
313
+ code TEXT NOT NULL, -- WebAZ 签发、卖家需展示在【该产品】外部页的验证码
314
+ platform TEXT, -- 卖家自填的平台名(展示用,不校验)
315
+ external_url TEXT, -- 卖家提交的该产品外部链接(仅存,WebAZ 不抓取)
316
+ status TEXT NOT NULL DEFAULT 'issued', -- issued|submitted|verified|rejected
317
+ reviewed_by TEXT REFERENCES users(id), -- 真人 admin(手动核对者)
318
+ reviewed_at TEXT,
319
+ notes TEXT,
320
+ created_at TEXT DEFAULT (datetime('now')),
321
+ updated_at TEXT DEFAULT (datetime('now'))
322
+ );
323
+ CREATE INDEX IF NOT EXISTS idx_product_verifications_product ON product_verifications(product_id, status);
324
+ CREATE INDEX IF NOT EXISTS idx_product_verifications_seller ON product_verifications(seller_id, status);
325
+
326
+ -- 【按卖家】店铺认证(store verification)= 逐品验证的【豁免】路径。卖家申请一次店铺(发码→贴外部店铺页→提交店铺链接),
327
+ -- 真人 admin 手动核对时【勾选 per_product_exempt】:置 1 = 该卖家所有商品免逐品验证、可直付;置 0(默认)= 仍需逐品验证。
328
+ -- 诚实边界同 product_verifications:WebAZ 绝不抓取 external_url(无 SSRF/无超claim),只存链接+码+真人 attest。单一活跃 per seller。
329
+ CREATE TABLE IF NOT EXISTS store_verifications (
330
+ id TEXT PRIMARY KEY,
331
+ user_id TEXT NOT NULL REFERENCES users(id),
332
+ code TEXT NOT NULL,
333
+ platform TEXT,
334
+ external_url TEXT,
335
+ status TEXT NOT NULL DEFAULT 'issued', -- issued|submitted|verified|rejected
336
+ per_product_exempt INTEGER NOT NULL DEFAULT 0, -- 1 = 该卖家免逐品验证(admin 核店铺时勾选);0 = 仍需逐品
337
+ reviewed_by TEXT REFERENCES users(id),
338
+ reviewed_at TEXT,
339
+ notes TEXT,
340
+ created_at TEXT DEFAULT (datetime('now')),
341
+ updated_at TEXT DEFAULT (datetime('now'))
342
+ );
343
+ CREATE INDEX IF NOT EXISTS idx_store_verifications_user ON store_verifications(user_id, status);
344
+
345
+ -- 逐单费用质押(fee-stake = 平台应收费用,非买家保障)。锁在现有 WAZ 账本。
346
+ CREATE TABLE IF NOT EXISTS direct_pay_fee_stakes (
347
+ id TEXT PRIMARY KEY,
348
+ order_id TEXT NOT NULL REFERENCES orders(id),
349
+ seller_id TEXT NOT NULL REFERENCES users(id),
350
+ amount REAL NOT NULL, -- 锁定额(= 该单平台费)
351
+ status TEXT NOT NULL DEFAULT 'locked', -- locked|fee_taken|released|slashed
352
+ created_at TEXT DEFAULT (datetime('now')),
353
+ settled_at TEXT,
354
+ UNIQUE(order_id)
355
+ );
356
+
357
+ -- ─────────────────────────────────────────────────────────────────────────
358
+ -- Direct Pay 平台费【链下应收(AR)】(设计稿 DIRECT-PAY-FEE-RECEIVABLE-DESIGN.INTERNAL.md)
359
+ -- 替代逐单 WAZ 质押:完成时记真实 USDC 应收,月结开票/人工收/欠费暂停资格。
360
+ -- v1 单一计价币 = USDC。append-only:原始事实行不改,变更走 adjustments / events。
361
+ -- PR-1 = 纯建表 + 读 helper,零行为接线(建单门/accrue/cron/UI 在 PR-2+)。
362
+ -- ─────────────────────────────────────────────────────────────────────────
363
+
364
+ -- 月结发票(有 status;每次状态变更必写 invoice_events)。【先于 receivables 建:后者 invoice_id 引用它】。
365
+ CREATE TABLE IF NOT EXISTS direct_pay_fee_invoices (
366
+ id TEXT PRIMARY KEY,
367
+ seller_id TEXT NOT NULL REFERENCES users(id),
368
+ period_start TEXT NOT NULL,
369
+ period_end TEXT NOT NULL,
370
+ total_amount REAL NOT NULL CHECK (total_amount >= 0),
371
+ currency TEXT NOT NULL DEFAULT 'usdc' CHECK (currency = 'usdc'),
372
+ due_date TEXT NOT NULL, -- = period_end + net 15d(D9)
373
+ status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','paid','overdue','void')),
374
+ created_at TEXT DEFAULT (datetime('now')),
375
+ created_by TEXT, -- 'cron' 或 admin user_id
376
+ paid_at TEXT
377
+ );
378
+ CREATE INDEX IF NOT EXISTS idx_dp_fee_invoices_seller ON direct_pay_fee_invoices(seller_id, status);
379
+
380
+ -- 逐单应收 = 完成时赚的一笔平台费(原始 accrual 事实行,IMMUTABLE:行一旦写入永不改)。
381
+ -- ⚠️ 不含 invoice_id:入票关联走独立 append-only 表 direct_pay_fee_invoice_items(避免回填 UPDATE 破坏不可变)。
382
+ CREATE TABLE IF NOT EXISTS direct_pay_fee_receivables (
383
+ id TEXT PRIMARY KEY,
384
+ order_id TEXT NOT NULL REFERENCES orders(id),
385
+ seller_id TEXT NOT NULL REFERENCES users(id),
386
+ amount REAL NOT NULL CHECK (amount >= 0), -- 该单平台费(USDC 计价小数;units 经 money.ts 边界)
387
+ currency TEXT NOT NULL DEFAULT 'usdc' CHECK (currency = 'usdc'), -- v1 单币;v2 放开
388
+ accrued_at TEXT DEFAULT (datetime('now')),
389
+ UNIQUE(order_id)
390
+ );
391
+ CREATE INDEX IF NOT EXISTS idx_dp_fee_receivables_seller ON direct_pay_fee_receivables(seller_id);
392
+
393
+ -- 应收 ↔ 发票分摊(append-only 关联行;月结开票时写,不回填/不改 receivables)。UNIQUE(receivable_id) 防重复入票。
394
+ CREATE TABLE IF NOT EXISTS direct_pay_fee_invoice_items (
395
+ id TEXT PRIMARY KEY,
396
+ invoice_id TEXT NOT NULL REFERENCES direct_pay_fee_invoices(id),
397
+ receivable_id TEXT NOT NULL REFERENCES direct_pay_fee_receivables(id),
398
+ amount REAL NOT NULL CHECK (amount >= 0), -- 入票金额(= 该 receivable 计入本发票的额)
399
+ created_at TEXT DEFAULT (datetime('now')),
400
+ UNIQUE(receivable_id)
401
+ );
402
+ CREATE INDEX IF NOT EXISTS idx_dp_fee_invoice_items_invoice ON direct_pay_fee_invoice_items(invoice_id);
403
+
404
+ -- 冲销 / 坏账核销 / 手工冲正(独立 append-only 行;原始 accrual 行不动)。
405
+ CREATE TABLE IF NOT EXISTS direct_pay_fee_adjustments (
406
+ id TEXT PRIMARY KEY,
407
+ receivable_id TEXT REFERENCES direct_pay_fee_receivables(id), -- nullable(整体冲正可不挂单)
408
+ seller_id TEXT NOT NULL REFERENCES users(id),
409
+ delta_amount REAL NOT NULL, -- 带符号(退货冲销=负)
410
+ currency TEXT NOT NULL DEFAULT 'usdc' CHECK (currency = 'usdc'),
411
+ kind TEXT NOT NULL CHECK (kind IN ('reversal','write_off','correction')),
412
+ reason TEXT,
413
+ created_at TEXT DEFAULT (datetime('now')),
414
+ created_by TEXT REFERENCES users(id) -- admin(人工铁律)
415
+ );
416
+ CREATE INDEX IF NOT EXISTS idx_dp_fee_adjustments_seller ON direct_pay_fee_adjustments(seller_id);
417
+
418
+ -- 发票状态变更审计(append-only)。
419
+ CREATE TABLE IF NOT EXISTS direct_pay_fee_invoice_events (
420
+ id TEXT PRIMARY KEY,
421
+ invoice_id TEXT NOT NULL REFERENCES direct_pay_fee_invoices(id),
422
+ from_status TEXT,
423
+ to_status TEXT NOT NULL,
424
+ actor TEXT, -- 'cron' 或 admin user_id
425
+ reason TEXT,
426
+ created_at TEXT DEFAULT (datetime('now'))
427
+ );
428
+ CREATE INDEX IF NOT EXISTS idx_dp_fee_invoice_events_invoice ON direct_pay_fee_invoice_events(invoice_id);
429
+
430
+ -- 商家平台服务费【收款/预付款】事实行(append-only,IMMUTABLE)。当前模型 = 首单宽限 + 预充值续用:
431
+ -- 每行 = admin 记录的一笔商家平台服务费预付款(USDC/法币);invoice_id IS NULL = 【未分配预充值】=
432
+ -- 计入 available_prepay。(invoice_id 非空属早先月结模型预留,本模型不生成发票,故恒 NULL。)
433
+ -- ⚠️ 是【商家平台服务费预付款】,非买家 escrow / 非保证金 / 非 penalty。本轮无"余额退款"实功能。
434
+ CREATE TABLE IF NOT EXISTS direct_pay_fee_payments (
435
+ id TEXT PRIMARY KEY,
436
+ seller_id TEXT NOT NULL REFERENCES users(id),
437
+ invoice_id TEXT REFERENCES direct_pay_fee_invoices(id),
438
+ amount REAL NOT NULL CHECK (amount >= 0),
439
+ currency TEXT NOT NULL DEFAULT 'usdc' CHECK (currency = 'usdc'),
440
+ method TEXT NOT NULL CHECK (method IN ('usdc','fiat')),
441
+ received_at TEXT DEFAULT (datetime('now')),
442
+ recorded_by TEXT REFERENCES users(id), -- admin(Passkey + purpose-bound)
443
+ evidence_ref TEXT,
444
+ note TEXT
445
+ );
446
+ CREATE INDEX IF NOT EXISTS idx_dp_fee_payments_seller ON direct_pay_fee_payments(seller_id);
447
+
448
+ -- ⏸ DORMANT(早先"AR 信用上限"模型预留;当前 = 首单宽限 + 预充值续用,额度即商家实际预付余额,无固定上限)。
449
+ -- 本表与 ceiling_requests/invoices/invoice_items/invoice_events 当前【不写不读】;保留待将来需要时复用或清理。
450
+ CREATE TABLE IF NOT EXISTS direct_pay_fee_ar_seller_overrides (
451
+ seller_id TEXT PRIMARY KEY REFERENCES users(id),
452
+ ceiling_units INTEGER NOT NULL CHECK (ceiling_units >= 0), -- 该商家未付 AR 上限(整数 base-units;0=封锁)
453
+ updated_by TEXT REFERENCES users(id), -- admin
454
+ updated_at TEXT DEFAULT (datetime('now'))
455
+ );
456
+
457
+ -- 商家【申请】调高未付 AR 上限 → ROOT/admin 真人 Passkey 审批(approve 即写 override)。
458
+ -- 商家不能自调,只能申请;append-only 审计。模式同仓内 build-task 配额申请。
459
+ CREATE TABLE IF NOT EXISTS direct_pay_fee_ceiling_requests (
460
+ id TEXT PRIMARY KEY,
461
+ seller_id TEXT NOT NULL REFERENCES users(id),
462
+ requested_units INTEGER NOT NULL CHECK (requested_units >= 0), -- 申请目标上限(整数 base-units)
463
+ effective_units_at_request INTEGER CHECK (effective_units_at_request >= 0), -- 申请时生效上限快照(审计用)
464
+ reason TEXT,
465
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','approved','rejected')),
466
+ created_at TEXT DEFAULT (datetime('now')),
467
+ reviewed_by TEXT REFERENCES users(id), -- admin
468
+ reviewed_at TEXT,
469
+ decision_note TEXT
470
+ );
471
+ CREATE INDEX IF NOT EXISTS idx_dp_fee_ceiling_requests_seller ON direct_pay_fee_ceiling_requests(seller_id, status);
472
+
473
+ -- 商家平台服务费【预充值余额退款】事实行(append-only,IMMUTABLE)。= WebAZ 把已预付未消耗的平台服务费余额
474
+ -- 退还给商家(链下真实退款,记 evidence_ref)。available_prepay 减去 Σ refunds。amount>0(退款额,正)。
475
+ -- ⚠️ 退的是【商家平台服务费预付款】,非买家货款/escrow/保证金/penalty。与 adjustments.correction(账务更正)区分:
476
+ -- refund = 真实退钱;correction = 记账更正(不一定动真钱)。退款额 ≤ 当前 available(不可退已被费用消耗的部分,由 helper 校验)。
477
+ CREATE TABLE IF NOT EXISTS direct_pay_fee_prepay_refunds (
478
+ id TEXT PRIMARY KEY,
479
+ seller_id TEXT NOT NULL REFERENCES users(id),
480
+ amount REAL NOT NULL CHECK (amount >= 0),
481
+ currency TEXT NOT NULL DEFAULT 'usdc' CHECK (currency = 'usdc'),
482
+ method TEXT NOT NULL CHECK (method IN ('usdc','fiat')),
483
+ evidence_ref TEXT,
484
+ reason TEXT,
485
+ recorded_by TEXT REFERENCES users(id), -- admin(Passkey + purpose-bound)
486
+ created_at TEXT DEFAULT (datetime('now'))
487
+ );
488
+ CREATE INDEX IF NOT EXISTS idx_dp_fee_prepay_refunds_seller ON direct_pay_fee_prepay_refunds(seller_id);
489
+
490
+ -- ── APPEND-ONLY 硬强制(DB 级,非仅注释)── money-adjacent fee 账本的事实/事件行不可改/删。
491
+ -- 6 张:receivables(immutable accrual)/ invoice_items(写一次)/ adjustments / invoice_events / payments(immutable)/ prepay_refunds(immutable)。
492
+ -- invoices/overrides/ceiling_requests 刻意【不】锁(有合法状态/值变更)。PG 等价 plpgsql guard 由 gen-pg-schema 的 APPEND_ONLY_TABLES 生成。
493
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_receivables_no_update BEFORE UPDATE ON direct_pay_fee_receivables BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_receivables is append-only (UPDATE forbidden)'); END;
494
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_receivables_no_delete BEFORE DELETE ON direct_pay_fee_receivables BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_receivables is append-only (DELETE forbidden)'); END;
495
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_invoice_items_no_update BEFORE UPDATE ON direct_pay_fee_invoice_items BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_invoice_items is append-only (UPDATE forbidden)'); END;
496
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_invoice_items_no_delete BEFORE DELETE ON direct_pay_fee_invoice_items BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_invoice_items is append-only (DELETE forbidden)'); END;
497
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_adjustments_no_update BEFORE UPDATE ON direct_pay_fee_adjustments BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_adjustments is append-only (UPDATE forbidden)'); END;
498
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_adjustments_no_delete BEFORE DELETE ON direct_pay_fee_adjustments BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_adjustments is append-only (DELETE forbidden)'); END;
499
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_invoice_events_no_update BEFORE UPDATE ON direct_pay_fee_invoice_events BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_invoice_events is append-only (UPDATE forbidden)'); END;
500
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_invoice_events_no_delete BEFORE DELETE ON direct_pay_fee_invoice_events BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_invoice_events is append-only (DELETE forbidden)'); END;
501
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_payments_no_update BEFORE UPDATE ON direct_pay_fee_payments BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_payments is append-only (UPDATE forbidden)'); END;
502
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_payments_no_delete BEFORE DELETE ON direct_pay_fee_payments BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_payments is append-only (DELETE forbidden)'); END;
503
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_prepay_refunds_no_update BEFORE UPDATE ON direct_pay_fee_prepay_refunds BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_prepay_refunds is append-only (UPDATE forbidden)'); END;
504
+ CREATE TRIGGER IF NOT EXISTS trg_dp_fee_prepay_refunds_no_delete BEFORE DELETE ON direct_pay_fee_prepay_refunds BEGIN SELECT RAISE(ABORT, 'direct_pay_fee_prepay_refunds is append-only (DELETE forbidden)'); END;
505
+
506
+ -- penalty 科目(独立、物理隔离、只进不出)。出账【无代码路径】= append-only 硬保证。
507
+ -- 三条出账红线(永不可破):①不退买家 ②不计 WebAZ 利润 ③不按个案裁决结果流向裁决者。
508
+ CREATE TABLE IF NOT EXISTS penalty_fund (
509
+ id TEXT PRIMARY KEY, -- 恒为 'main'
510
+ balance REAL DEFAULT 0,
511
+ total_fee_stake_slash REAL DEFAULT 0,
512
+ total_base_bond_slash REAL DEFAULT 0,
513
+ updated_at TEXT
514
+ );
515
+ CREATE TABLE IF NOT EXISTS penalty_fund_txns (
516
+ id TEXT PRIMARY KEY,
517
+ kind TEXT NOT NULL, -- fee_stake_slash | base_bond_slash
518
+ source TEXT NOT NULL, -- fee_stake | base_bond (provenance)
519
+ from_user_id TEXT,
520
+ amount REAL NOT NULL,
521
+ related_order_id TEXT,
522
+ reason TEXT,
523
+ created_at TEXT DEFAULT (datetime('now'))
524
+ );
525
+
526
+ -- 制裁/合规筛查(本仓原无;direct-pay 含加密=最重洗钱形态,硬要求)
527
+ CREATE TABLE IF NOT EXISTS sanctions_screening (
528
+ id TEXT PRIMARY KEY,
529
+ user_id TEXT NOT NULL REFERENCES users(id),
530
+ status TEXT NOT NULL DEFAULT 'clear', -- clear | flagged | blocked
531
+ source TEXT,
532
+ reason TEXT,
533
+ screened_at TEXT DEFAULT (datetime('now')),
534
+ created_at TEXT DEFAULT (datetime('now'))
535
+ -- PR-6A: expires_at 由 ALTER 补(见迁移段);制裁结论有有效期,过期视作未通过(fail-closed)。
536
+ );
537
+
538
+ -- PR-6A: Direct Pay KYB(商户尽调)复核台账。Direct Pay AML/KYB fail-closed runtime —— 无第三方 vendor、无真实
539
+ -- API 调用,仅记录【真人/合规复核结论】。fail-closed:missing/pending/rejected/revoked/expired 一律不通过,
540
+ -- 只有 approved 且未过期(且无 rejected/revoked)才算 KYB 通过。本表无生产写入方 → 真实卖家天然 fail-closed。
541
+ CREATE TABLE IF NOT EXISTS direct_receive_kyb_reviews (
542
+ id TEXT PRIMARY KEY,
543
+ user_id TEXT NOT NULL REFERENCES users(id),
544
+ status TEXT NOT NULL DEFAULT 'pending', -- pending | approved | rejected | revoked
545
+ reviewed_by TEXT, -- 复核人(真人/合规);无第三方集成
546
+ reviewed_at TEXT,
547
+ expires_at TEXT, -- 复核有效期;过期视作未通过
548
+ reason TEXT,
549
+ created_at TEXT DEFAULT (datetime('now')),
550
+ updated_at TEXT DEFAULT (datetime('now'))
551
+ );
552
+
553
+ -- AML 监控 flag(进【独立复核队列】,与配额节流分开);AML 能力 = INVARIANT。
554
+ CREATE TABLE IF NOT EXISTS aml_flags (
555
+ id TEXT PRIMARY KEY,
556
+ subject_user_id TEXT NOT NULL REFERENCES users(id),
557
+ related_order_id TEXT,
558
+ rule TEXT NOT NULL, -- structuring|concentration|cumulative|crypto|velocity
559
+ severity TEXT NOT NULL DEFAULT 'low',-- low|medium|high
560
+ detail TEXT, -- JSON
561
+ status TEXT NOT NULL DEFAULT 'open',-- open|reviewing|cleared|escalated|str_filed
562
+ disposition TEXT, -- review_queue|downgrade|suspend (非资金手段)
563
+ reviewed_by TEXT REFERENCES users(id),
564
+ reviewed_at TEXT,
565
+ created_at TEXT DEFAULT (datetime('now'))
566
+ );
567
+ -- STR 申报(具名 compliance 责任人);留存默认 5 年(legal 可调)。
568
+ CREATE TABLE IF NOT EXISTS aml_str_filings (
569
+ id TEXT PRIMARY KEY,
570
+ flag_id TEXT REFERENCES aml_flags(id),
571
+ filed_by TEXT NOT NULL, -- 具名 compliance officer
572
+ filing_ref TEXT,
573
+ narrative TEXT,
574
+ filed_at TEXT DEFAULT (datetime('now')),
575
+ retention_until TEXT NOT NULL -- filed_at + 留存年限
576
+ );
577
+
578
+ -- 披露契约层凭证(append-only 事件):两次风险提醒各一行 —— stage='pre_select'(展示/选择直付前)
579
+ -- 与 stage='pre_confirm'(最终下单/确认付款前)。每行记 notice_version + acked_at,可证两次【分别】发生、
580
+ -- 第二次在最终确认前。最终确认逻辑只在【两 stage 都 ack】时放行(见 direct-pay-disclosures.ts)。
581
+ -- 这是 Direct Pay「无经济保障、风险自担」边界的证据层 —— 不可压成单次 ack。
582
+ CREATE TABLE IF NOT EXISTS direct_pay_disclosure_acks (
583
+ id TEXT PRIMARY KEY,
584
+ order_id TEXT NOT NULL REFERENCES orders(id),
585
+ buyer_id TEXT NOT NULL REFERENCES users(id),
586
+ stage TEXT NOT NULL, -- pre_select | pre_confirm
587
+ notice_version TEXT NOT NULL, -- 该次提醒的披露文案版本
588
+ acked_at TEXT DEFAULT (datetime('now')),
589
+ UNIQUE(order_id, stage) -- 每单每阶段一次;两阶段=两行
590
+ );
591
+
592
+ -- ── Merchant Base-Bond (v1 collateral-only) — PR1 testnet/dev scaffold。链上为真相,这两张表只是【DB 镜像/缓存】(见 docs/modules/MERCHANT-BASE-BOND-DESIGN.INTERNAL.md §4.1)。
593
+ -- v1 默认关闭、不接 mainnet、不收真钱、live 路径不读写 → 对现有 Direct Pay 零影响。
594
+ CREATE TABLE IF NOT EXISTS merchant_bond_wallets (
595
+ seller_id TEXT PRIMARY KEY REFERENCES users(id),
596
+ wallet TEXT NOT NULL, -- registeredBondWallet(链上地址);seller_id ↔ wallet 唯一
597
+ chain_id INTEGER NOT NULL, -- v1 = Base
598
+ rotated_at TEXT,
599
+ created_at TEXT DEFAULT (datetime('now')),
600
+ UNIQUE(wallet)
601
+ );
602
+ CREATE TABLE IF NOT EXISTS merchant_bond_deposits (
603
+ id TEXT PRIMARY KEY,
604
+ seller_id TEXT NOT NULL REFERENCES users(id),
605
+ wallet TEXT NOT NULL,
606
+ tx_hash TEXT,
607
+ collateral_units TEXT NOT NULL DEFAULT '0', -- USDC integer units(字符串,big-int 安全)
608
+ status TEXT NOT NULL DEFAULT 'pending_confirmations', -- none|pending_confirmations|active|cooling|withdrawable|withdrawn|slashed_below_min(§4 状态机)
609
+ confirmations INTEGER NOT NULL DEFAULT 0,
610
+ created_at TEXT DEFAULT (datetime('now')),
611
+ updated_at TEXT DEFAULT (datetime('now'))
612
+ );
613
+
175
614
  `);
176
615
  // 迁移:为已有数据库添加 roles 列
177
616
  try {
@@ -179,6 +618,97 @@ export function initDatabase() {
179
618
  }
180
619
  catch { /* 列已存在 */ }
181
620
  db.exec(`UPDATE users SET roles = json_array(role) WHERE roles = '[]' OR roles IS NULL`);
621
+ // 迁移(Direct Pay Rail 1): 既有库补列。ADD COLUMN(非 rebuild),fresh-DB 与 existing-DB 两路均生效。
622
+ try {
623
+ db.exec(`ALTER TABLE orders ADD COLUMN payment_rail TEXT DEFAULT 'escrow'`);
624
+ }
625
+ catch { /* 已存在 */ }
626
+ try {
627
+ db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_window_deadline TEXT`);
628
+ }
629
+ catch { /* 已存在 */ }
630
+ try {
631
+ db.exec(`ALTER TABLE orders ADD COLUMN direct_grace_deadline TEXT`);
632
+ }
633
+ catch { /* 已存在 */ } // Rail1 paid-but-timeout 宽限期:系统在此之前绝不关单(买家 →disputed 窗口)
634
+ try {
635
+ db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_instruction_snapshot TEXT`);
636
+ }
637
+ catch { /* 已存在 */ } // Rail1 4c:下单时快照卖家收款说明(冻结买家所见;卖家事后改/停用不影响)
638
+ try {
639
+ db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_account_snapshot TEXT`);
640
+ }
641
+ catch { /* 已存在 */ } // Rail1 D2:买家所选收款账号快照 JSON {account_id,method,currency,label,qr_ref}(非敏感元数据;instruction 原文仍在 direct_pay_instruction_snapshot 受披露门;qr_ref 的图字节走 ack 门端点)
642
+ // PR-5b-0: direct_p2p 入口控制 policy 快照列(既有库补列;additive nullable,本 PR 无写入方,5b wiring 才写)。
643
+ try {
644
+ db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_enabled_snapshot INTEGER`);
645
+ }
646
+ catch { /* 已存在 */ }
647
+ try {
648
+ db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_rail_breaker_snapshot INTEGER`);
649
+ }
650
+ catch { /* 已存在 */ }
651
+ try {
652
+ db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_region_snapshot TEXT`);
653
+ }
654
+ catch { /* 已存在 */ }
655
+ try {
656
+ db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_region_allowlist_snapshot TEXT`);
657
+ }
658
+ catch { /* 已存在 */ }
659
+ try {
660
+ db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_per_tx_cap_units_snapshot INTEGER`);
661
+ }
662
+ catch { /* 已存在 */ }
663
+ try {
664
+ db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_seller_breaker_snapshot INTEGER`);
665
+ }
666
+ catch { /* 已存在 */ }
667
+ try {
668
+ db.exec(`ALTER TABLE orders ADD COLUMN direct_pay_decision_code TEXT`);
669
+ }
670
+ catch { /* 已存在 */ }
671
+ try {
672
+ db.exec(`ALTER TABLE wallets ADD COLUMN fee_staked REAL DEFAULT 0`);
673
+ }
674
+ catch { /* 已存在 */ }
675
+ // PR-4b-1: direct_receive_deposits 生产收款 provenance 快照列(既有库补列;additive nullable,无写入方,无 flow 启用)。
676
+ try {
677
+ db.exec(`ALTER TABLE direct_receive_deposits ADD COLUMN production_receipt_ref TEXT`);
678
+ }
679
+ catch { /* 已存在 */ }
680
+ try {
681
+ db.exec(`ALTER TABLE direct_receive_deposits ADD COLUMN production_rail_id TEXT`);
682
+ }
683
+ catch { /* 已存在 */ }
684
+ try {
685
+ db.exec(`ALTER TABLE direct_receive_deposits ADD COLUMN production_jurisdiction TEXT`);
686
+ }
687
+ catch { /* 已存在 */ }
688
+ try {
689
+ db.exec(`ALTER TABLE direct_receive_deposits ADD COLUMN production_policy_version TEXT`);
690
+ }
691
+ catch { /* 已存在 */ }
692
+ // PR-6A: sanctions 结论有有效期(过期 → fail-closed)。additive nullable;NULL = 无期限(不过期)。
693
+ try {
694
+ db.exec(`ALTER TABLE sanctions_screening ADD COLUMN expires_at TEXT`);
695
+ }
696
+ catch { /* 已存在 */ }
697
+ // PR-6D: 支撑 #108 AML 监控的窗口查询(seller_id + payment_rail='direct_p2p' + created_at 范围)。
698
+ // 纯只读索引;不改行为、不打开任何规则。命名随既有 idx_orders_* 风格。
699
+ try {
700
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_orders_direct_pay_seller_window ON orders(seller_id, payment_rail, created_at)`);
701
+ }
702
+ catch { /* 已存在 */ }
703
+ // 币种回填(Claim 5 gated):把存量遗留内部代号 'DCP' 一次性刷成 'WAZ'。幂等、可重跑(每次 init 都跑,已是 WAZ 则 0 行);
704
+ // DCP 与 WAZ 是【同一模拟单位纯改名】,金额不变、无汇率换算;且全仓无 `currency='DCP'` 的筛选逻辑,不破坏任何查询。
705
+ // fresh-DB 默认已是 WAZ;existing-DB 新建单路径亦显式写 WAZ(见 products-create / MCP list_product / RFQ·auction fulfillment)。
706
+ try {
707
+ db.exec(`UPDATE products SET currency = 'WAZ' WHERE currency = 'DCP'`);
708
+ }
709
+ catch { /* products 表尚未建则跳过 */ }
710
+ // penalty 科目单行种子(只进不出;无出账代码路径)
711
+ db.exec(`INSERT OR IGNORE INTO penalty_fund (id, balance, total_fee_stake_slash, total_base_bond_slash, updated_at) VALUES ('main', 0, 0, 0, datetime('now'))`);
182
712
  console.error('✅ L0-1 数据库初始化完成:', DB_PATH);
183
713
  return db;
184
714
  }
@@ -6,16 +6,26 @@
6
6
  * 这些**退款 / 违约 / 争议**处置终态。所以裸 `status='completed'` ≠ 有效成交 —— 被退款的失败交易也是
7
7
  * completed。任何「有效成交」语义的资格门 / 信任信号都不能裸用 status。
8
8
  *
9
- * 真实成交 = 该订单曾进入过 `confirmed`(买家确认收货,或送达 72h 自动确认)。仅 happy path 经过 confirmed;
10
- * 所有 fault/退款/争议终态都不经过。据 `order_state_history`(transition() 每次状态变更写入)判定。
9
+ * 真实成交 = 该订单曾进入过 `confirmed`(买家确认收货,或送达 72h 自动确认)**且未被全额退货**。仅 happy path
10
+ * 经过 confirmed;所有 fault/退款/争议终态都不经过。据 `order_state_history`(transition() 每次状态变更写入)判定。
11
+ *
12
+ * RFC-018 PR4:退货发生在 `confirmed→completed` **之后**(returns 只作用于 completed 单),所以仅靠 confirmed
13
+ * 判据会把【已全额退货】的单仍算成真实成交 —— 退货虚增资格门 / completion_count / sales_count。故再排除
14
+ * 【累计退款达到订单总额】的单:`SUM(return_requests.refund_amount WHERE status='refunded') >= 订单总额`。
15
+ * 用累计 SUM 而非单行 `refund_amount >= total` —— 同一单可先后多次退货(已退款的旧 return 不挡新 return),
16
+ * 多次部分退款累计到全额也应排除。累计 < total 仍算真实成交(确曾成交,只是部分退款)。
11
17
  *
12
18
  * 用法:把查询里的 `status='completed'` 条件替换为本谓词。
13
19
  * 单表:`... WHERE buyer_id = ? AND ${genuineSalePredicate()}` // 默认别名 orders
14
20
  * 相关子查询:`... FROM orders o WHERE ... AND ${genuineSalePredicate('o')}`
15
21
  *
16
- * 注:这是纯 SQL 片段( DB 依赖),保证所有消费方用同一定义。若将来规模下相关子查询成性能瓶颈,
17
- * 再升级为 settleOrder 写入的 `orders.settled_ok_at` 列(届时只改本文件的实现)。
22
+ * Direct Pay (Rail 1) 排除:`payment_rail='direct_p2p'` 是场外直付,付款不可观测、本档无资金保障,
23
+ * 不计真实成交/佣金/PV(设计稿 §3「Commission/PV/genuine-sale on Rail 1」)。escrow 及将来可观测的
24
+ * Rail 2(链上全额质押)仍计入。NULL → 视为 escrow(老行)。
25
+ *
26
+ * 注:这是纯 SQL 片段(无 DB 依赖),保证所有消费方用同一定义。整体加括号防 OR 上下文优先级。
27
+ * 若将来规模下相关子查询成性能瓶颈,再升级为 settleOrder 写入的 `orders.settled_ok_at` 列(届时只改本文件)。
18
28
  */
19
29
  export function genuineSalePredicate(ordersAlias = 'orders') {
20
- return `EXISTS (SELECT 1 FROM order_state_history osh WHERE osh.order_id = ${ordersAlias}.id AND osh.to_status = 'confirmed')`;
30
+ return `(EXISTS (SELECT 1 FROM order_state_history osh WHERE osh.order_id = ${ordersAlias}.id AND osh.to_status = 'confirmed') AND (SELECT COALESCE(SUM(rr.refund_amount), 0) FROM return_requests rr WHERE rr.order_id = ${ordersAlias}.id AND rr.status = 'refunded') < ${ordersAlias}.total_amount AND COALESCE(${ordersAlias}.payment_rail, 'escrow') != 'direct_p2p')`;
21
31
  }