/** * `billing.js` —— `createBilling`:装配各能力 + 暴露 `asBillingPort`(§3 唯一编排入口)。 * * 只做装配:注入 `billingRepo` → 组出 ledger(deduct/grant)、paywall、products、port。 * 装配时即自检(少注入 billingRepo 当场 assert,不拖到运行某行——架构 §10.3)。 * 不读 env、不建连接。 * * @typedef {import('./repo/index.js').BillingRepo} BillingRepo * @typedef {import('./port.js').BillingPort} BillingPort */ import { createLedger } from './ledger.js' import { createActionPolicies } from './action-policies.js' import { createPaywall } from './paywall.js' import { createProducts } from './products.js' import { asBillingPort } from './port.js' import { createModelBilling } from './model-billing.js' import { createDailyClaim } from './daily-claim.js' import { createCommerceOperations } from './commerce-operations.js' import { createSignupReward } from './signup-reward.js' /** * 工厂:唯一入口(§3)。 * @param {object} [deps] * @param {BillingRepo} [deps.billingRepo] 注入真实数据库仓库(createBillingRepo) * @returns {{ * deduct:Function, grant:Function, reversePayment:Function, balance:Function, ledger:Function, findLedgerByIdempotencyKey:Function, * settleModelBilling:Function, * paywall:{ check:Function, unlock:Function, listUnlocked:Function }, * products:{ list:Function, get:Function, save:Function }, * withTransaction?:Function, * asBillingPort:() => BillingPort * }} */ export function createBilling({ billingRepo, onDeductCommitted = null } = {}) { if (!billingRepo || typeof billingRepo.applyDelta !== 'function') { throw new Error('[billing] createBilling 需要注入 billingRepo') } /** @type {BillingRepo} */ const repo = billingRepo const ledgerOperations = createLedger(repo) async function deduct(input) { const result = await ledgerOperations.deduct(input) if (!result.replayed && onDeductCommitted !== null) { repo.afterCommit(() => onDeductCommitted({ user_id: input.user_id, balance_after: result.balance_after, ledger_id: result.ledger_id, })) } return result } const { grant, reversePayment } = ledgerOperations const actionPolicies = createActionPolicies(repo) const settleModelBilling = createModelBilling({ deduct }) const paywall = createPaywall({ billingRepo: repo, deduct }) const products = createProducts(repo) /** * 当前余额。 * @param {{ user_id:string }} p * @returns {Promise<{ points:number }>} */ async function balance({ user_id }) { const points = await repo.getBalance(user_id) return { points: Number(points) } } /** * 可审计流水(按时间倒序,支持类型/时间过滤与分页)。 * @param {object} p * @param {string} p.user_id * @param {{ type?:string, since?:string, until?:string, limit?:number, cursor?:string }} [p.filter] * @returns {Promise<{ items:any[], next_cursor:string|null }>} */ async function ledger({ user_id, filter }) { return repo.listLedger(user_id, filter ?? {}) } async function findLedgerByIdempotencyKey(key) { return repo.findLedgerByIdempotencyKey(key) } function withTransaction(trx) { if (typeof repo.forTransaction !== 'function') { throw new Error('[billing] 当前 billingRepo 不支持事务绑定') } return createBilling({ billingRepo: repo.forTransaction(trx), onDeductCommitted }) } const billing = { deduct, grant, reversePayment, balance, ledger, findLedgerByIdempotencyKey, settleModelBilling, paywall, products, actionPolicies, withTransaction, asBillingPort: () => asBillingPort({ deduct }), } billing.dailyClaim = createDailyClaim(billing) billing.signupReward = createSignupReward(billing) billing.commerce = createCommerceOperations(billing) return billing }