billing.js
3.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
* `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
}