import { createHash } from 'node:crypto' import { mkdir, open, readFile, rename, unlink } from 'node:fs/promises' import { dirname } from 'node:path' // Internal stage only. No credentials, fixture generation, CLI or environment fallback. const ROOT = '/merchant/v1/orders/first-leg' const assert = (condition, message) => { if (!condition) throw new Error(message) } const longId = value => typeof value === 'string' && /^[1-9]\d*$/.test(value) const canonical = value => Array.isArray(value) ? value.map(canonical) : value && typeof value === 'object' ? Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])])) : value export const fingerprint = value => createHash('sha256').update(JSON.stringify(canonical(value))).digest('hex') export function validateSubmitCheckpoint(state) { if (['SUBMIT_INTENT', 'SUBMITTED', 'APPROVE_INTENT', 'APPROVED'].includes(state.phase)) { assert(Number.isSafeInteger(state.submitCommand?.body?.expectedVersion) && state.submitCommand.body.expectedVersion >= 0 && typeof state.submitCommand.idempotencyKey === 'string' && state.submitCommand.idempotencyKey.length > 0, '提交检查点缺少首次版本或原幂等键,禁止续跑时补取新版') } } export function validateOrderInput(input) { const { batch, customer, origin, destination, payload, operator } = input assert(/^[A-Za-z0-9][A-Za-z0-9_-]{7,47}$/.test(batch || ''), '批次须为 8~48 位字母数字或连字符') assert(longId(customer?.id) && customer.name && customer.code, '缺少固定客户身份') assert(operator?.merchant && operator?.admin, '缺少幂等操作者身份') assert(origin?.code === 'CN-YIWU-01' && longId(origin.id), '仅允许义乌始发') assert(destination?.type === 'FBA' && longId(destination.id) && destination.code, '当前订单阶段仅支持显式 FBA 目的仓') assert(payload?.customerName === customer.name && (payload.customerId == null || payload.customerId === customer.id), '下单客户与固定客户不符') assert(payload.receivingWarehouseId === origin.id && payload.destinationWarehouseId === destination.id && payload.amazonWarehouseCode === destination.code, '下单仓库与已核对主档不符') assert(payload.transportMode === 'SEA' && payload.destinationType === 'FBA' && payload.fulfillmentType === 'STANDARD' && payload.containerMode === 'LCL' && payload.pickupMethod === 'SELF_EXPRESS', '仅支持标准海运 FBA 自送散货链路') assert(payload.customerRemark === `RELEASE-ACCEPTANCE:${batch}`, '批次标记必须精确匹配') assert(typeof payload.channelCode === 'string' && payload.channelCode.length > 0, '缺少明确渠道') assert(payload.inboundCode == null && payload.senderAddressId == null && payload.privateDestinationAddressId == null, '不允许手工入仓码或地址副作用') assert(Array.isArray(payload.cargoes) && payload.cargoes.length === 1, '专属冒烟固定一个 SKU') const cargo = payload.cargoes[0] assert(Number.isInteger(cargo.packageCount) && cargo.packageCount >= 1 && cargo.packageCount <= 2, '冒烟仅允许 1~2 箱') assert(cargo.woodenProduct === false && cargo.dangerousGoods === false && !cargo.packingGroupNo, '仅支持非木制非危险单 SKU 货物') assert(longId(cargo.productImageFileId), '需要客户已上传产品图片,不生成虚假证明文件') assert(cargo.skuNo && Number.isInteger(cargo.unitsPerPackage) && cargo.unitsPerPackage > 0 && cargo.totalQuantity === cargo.packageCount * cargo.unitsPerPackage, 'SKU 或件数不守恒') for (const field of ['lengthCm', 'widthCm', 'heightCm', 'grossWeightKg', 'volumeCbm']) { assert(typeof cargo[field] === 'number' && Number.isFinite(cargo[field]) && cargo[field] > 0, `货物 ${field} 无效`) } const volume = cargo.lengthCm * cargo.widthCm * cargo.heightCm * cargo.packageCount / 1_000_000 assert(Math.abs(volume - cargo.volumeCbm) < 0.000001, '箱规与总体积不守恒') return input } function binding(input) { return fingerprint({ batch: input.batch, customer: input.customer, origin: input.origin, destination: input.destination, payload: input.payload, operator: input.operator, environment: input.environment }) } // Atomic snapshots plus an exclusive process lock. A crashed process leaves the lock for manual review. // The caller must retain this file between invocations. Never generate a new batch to retry a failed one. export async function withOrderJournal(path, input, action) { validateOrderInput(input) assert(typeof input.environment === 'string' && input.environment.startsWith('https://'), '缺少环境绑定') await mkdir(dirname(path), { recursive: true }) const lock = await open(`${path}.lock`, 'wx', 0o600) const temp = `${path}.next` let tempOwned = false try { let state try { state = JSON.parse(await readFile(path, 'utf8')) } catch (error) { if (error.code !== 'ENOENT') throw new Error('检查点损坏,禁止重新创建订单') state = { version: 1, binding: binding(input), phase: 'NEW', orderId: null } } assert(state.version === 1 && state.binding === binding(input), '批次配置、环境、客户、账号或载荷已变化') assert(['NEW', 'CREATE_INTENT', 'CREATED', 'SUBMIT_INTENT', 'SUBMITTED', 'APPROVE_INTENT', 'APPROVED'].includes(state.phase), '检查点阶段无效') assert(['NEW', 'CREATE_INTENT'].includes(state.phase) ? state.orderId === null : longId(state.orderId), '检查点订单身份无效') validateSubmitCheckpoint(state) const save = async patch => { const next = { ...state, ...patch } const file = await open(temp, 'wx', 0o600) tempOwned = true try { await file.writeFile(JSON.stringify(next, null, 2)); await file.sync() } finally { await file.close() } await rename(temp, path) tempOwned = false Object.assign(state, next) } if (state.phase === 'NEW') await save({}) return await action(state, save) } finally { // Only delete files created by this invocation; never remove an existing crash checkpoint. if (tempOwned) await unlink(temp).catch(() => {}) await lock.close() await unlink(`${path}.lock`) } } export function assertOrderDetail(detail, input, orderId) { const order = detail?.order assert(order?.id === orderId && order.tenantId === input.customer.id && order.customerId === input.customer.id, '订单归属与本批次不符') for (const field of ['receivingWarehouseId', 'destinationWarehouseId', 'channelCode', 'destinationType', 'fulfillmentType', 'customerRemark', 'pickupMethod', 'containerMode', 'pricingTaxMode']) { assert(order[field] === input.payload[field], `订单 ${field} 与批次不符`) } assert(typeof order.orderNo === 'string' && order.orderNo.length > 0, '缺少业务订单号') assert(['DRAFT', 'SUBMITTED', 'APPROVED'].includes(order.status), '订单已偏离当前验收阶段,停止自动推进') assert(Array.isArray(detail.cargoes) && detail.cargoes.length === 1, '订单货物行发生变化') const cargo = detail.cargoes[0] for (const key of ['skuNo', 'packageCount', 'totalQuantity', 'unitsPerPackage', 'productImageFileId']) { assert(cargo[key] === input.payload.cargoes[0][key], `订单货物 ${key} 与批次不符`) } for (const key of ['lengthCm', 'widthCm', 'heightCm', 'grossWeightKg', 'volumeCbm']) { assert(Number(cargo[key]) === input.payload.cargoes[0][key], `订单货物 ${key} 与批次不符`) } return order } /** * transport is intentionally scoped to this module's five fixed endpoints, not the old test context. * authorize performs fresh client/admin identity and route/quote checks, including on explicit resume. * An uncertain non-idempotent approval is never replayed based only on SUBMITTED being observed. */ export async function runOrderStage({ input, state, save, transport, authorize, resume = false }) { validateOrderInput(input) assert(state.binding === binding(input), '检查点绑定不符') validateSubmitCheckpoint(state) await authorize(input) const key = op => `prod-release-${fingerprint({ binding: state.binding, op }).slice(0, 48)}` if (state.submitCommand) assert(state.submitCommand.idempotencyKey === key('submit'), '提交原幂等键与批次不符') const request = (role, method, path, body, idempotencyKey) => transport({ role, method, path, body, idempotencyKey }) if (state.phase === 'CREATE_INTENT') assert(resume, '创建结果待核对;需要显式续跑') if (['NEW', 'CREATE_INTENT'].includes(state.phase)) { const policy = await request('MERCHANT', 'GET', `${ROOT}/inbound-code-policy`) assert(policy?.mode === 'AUTO' && policy.manualEntryRequired === false, '当前专属链只支持自动入仓码') await save({ phase: 'CREATE_INTENT' }) const orderId = await request('MERCHANT', 'POST', ROOT, input.payload, key('create')) assert(longId(orderId), '创建返回订单 ID 无效,保留创建意图待核对') await save({ phase: 'CREATED', orderId }) } assert(longId(state.orderId), '缺少精确订单 ID') const path = `${ROOT}/${state.orderId}` const read = async () => assertOrderDetail(await request('MERCHANT', 'GET', path), input, state.orderId) let order = await read() if (state.phase === 'CREATED') { assert(order.status === 'DRAFT', '创建后状态被外部修改') assert(Number.isSafeInteger(order.version) && order.version >= 0, '提交前详情缺少有效版本') await save({ phase: 'SUBMIT_INTENT', submitCommand: { body: { expectedVersion: order.version }, idempotencyKey: key('submit') } }) await request('MERCHANT', 'POST', `${path}/submit`, state.submitCommand.body, state.submitCommand.idempotencyKey) order = await read() assert(order.status === 'SUBMITTED', '提交后未进入待审核') await save({ phase: 'SUBMITTED', orderNo: order.orderNo, inboundCode: order.inboundCode }) } else if (state.phase === 'SUBMIT_INTENT') { assert(resume, '提交结果待核对;需要显式续跑') if (order.status === 'DRAFT') { await request('MERCHANT', 'POST', `${path}/submit`, state.submitCommand.body, state.submitCommand.idempotencyKey) order = await read() } assert(order.status === 'SUBMITTED', '提交结果无法安全核对') await save({ phase: 'SUBMITTED', orderNo: order.orderNo, inboundCode: order.inboundCode }) } if (state.phase === 'SUBMITTED') { order = await read() assert(order.status === 'SUBMITTED', '审核前状态被外部修改') await save({ phase: 'APPROVE_INTENT' }) await request('ADMIN', 'POST', `/platform/v1/orders/first-leg/${state.orderId}/approve?tenantId=${input.customer.id}`) order = await read() assert(order.status === 'APPROVED', '审核后未进入已审核,保留意图待核对') await save({ phase: 'APPROVED' }) } else if (state.phase === 'APPROVE_INTENT') { assert(resume && order.status === 'APPROVED', '审核结果不确定;禁止重发审核,需人工核对') await save({ phase: 'APPROVED' }) } order = await read() assert(state.phase === 'APPROVED' && order.status === 'APPROVED', '订单阶段没有完成') assert(typeof order.inboundCode === 'string' && order.inboundCode.length > 0, '审核订单缺少入仓码') return { status: 'ORDER_APPROVED', orderId: state.orderId, orderNo: order.orderNo, inboundCode: order.inboundCode, fullFlowVerified: false, next: 'YIWU_MEASUREMENT' } }