@windrun-huaiin/backend-core 10.0.1

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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/dist/app/api/stripe/checkout/route.d.ts +19 -0
  3. package/dist/app/api/stripe/checkout/route.d.ts.map +1 -0
  4. package/dist/app/api/stripe/checkout/route.js +120 -0
  5. package/dist/app/api/stripe/checkout/route.mjs +118 -0
  6. package/dist/app/api/stripe/customer-portal/route.d.ts +11 -0
  7. package/dist/app/api/stripe/customer-portal/route.d.ts.map +1 -0
  8. package/dist/app/api/stripe/customer-portal/route.js +73 -0
  9. package/dist/app/api/stripe/customer-portal/route.mjs +71 -0
  10. package/dist/app/api/user/anonymous/init/route.d.ts +7 -0
  11. package/dist/app/api/user/anonymous/init/route.d.ts.map +1 -0
  12. package/dist/app/api/user/anonymous/init/route.js +210 -0
  13. package/dist/app/api/user/anonymous/init/route.mjs +208 -0
  14. package/dist/app/api/webhook/clerk/user/route.d.ts +7 -0
  15. package/dist/app/api/webhook/clerk/user/route.d.ts.map +1 -0
  16. package/dist/app/api/webhook/clerk/user/route.js +202 -0
  17. package/dist/app/api/webhook/clerk/user/route.mjs +200 -0
  18. package/dist/app/api/webhook/stripe/route.d.ts +8 -0
  19. package/dist/app/api/webhook/stripe/route.d.ts.map +1 -0
  20. package/dist/app/api/webhook/stripe/route.js +70 -0
  21. package/dist/app/api/webhook/stripe/route.mjs +67 -0
  22. package/dist/index.d.ts +7 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +83 -0
  25. package/dist/index.mjs +18 -0
  26. package/dist/lib/auth-utils.d.ts +46 -0
  27. package/dist/lib/auth-utils.d.ts.map +1 -0
  28. package/dist/lib/auth-utils.js +107 -0
  29. package/dist/lib/auth-utils.mjs +102 -0
  30. package/dist/lib/credit-init.d.ts +8 -0
  31. package/dist/lib/credit-init.d.ts.map +1 -0
  32. package/dist/lib/credit-init.js +16 -0
  33. package/dist/lib/credit-init.mjs +10 -0
  34. package/dist/lib/index.d.ts +5 -0
  35. package/dist/lib/index.d.ts.map +1 -0
  36. package/dist/lib/index.js +31 -0
  37. package/dist/lib/index.mjs +4 -0
  38. package/dist/lib/money-price-config.d.ts +51 -0
  39. package/dist/lib/money-price-config.d.ts.map +1 -0
  40. package/dist/lib/money-price-config.js +156 -0
  41. package/dist/lib/money-price-config.mjs +151 -0
  42. package/dist/lib/stripe-config.d.ts +31 -0
  43. package/dist/lib/stripe-config.d.ts.map +1 -0
  44. package/dist/lib/stripe-config.js +278 -0
  45. package/dist/lib/stripe-config.mjs +268 -0
  46. package/dist/node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js +48 -0
  47. package/dist/node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs +45 -0
  48. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/errors.js +54 -0
  49. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/errors.mjs +51 -0
  50. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/iso.js +44 -0
  51. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/iso.mjs +35 -0
  52. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/parse.js +31 -0
  53. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/parse.mjs +18 -0
  54. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/schemas.js +587 -0
  55. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/schemas.mjs +527 -0
  56. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/api.js +447 -0
  57. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/api.mjs +399 -0
  58. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/checks.js +245 -0
  59. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/checks.mjs +232 -0
  60. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/core.js +68 -0
  61. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/core.mjs +62 -0
  62. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/doc.js +39 -0
  63. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/doc.mjs +37 -0
  64. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/errors.js +80 -0
  65. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/errors.mjs +75 -0
  66. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/parse.js +101 -0
  67. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/parse.mjs +86 -0
  68. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/regexes.js +102 -0
  69. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/regexes.mjs +76 -0
  70. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/registries.js +56 -0
  71. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/registries.mjs +52 -0
  72. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/schemas.js +1205 -0
  73. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/schemas.mjs +1157 -0
  74. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/util.js +407 -0
  75. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/util.mjs +374 -0
  76. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/versions.js +9 -0
  77. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/versions.mjs +7 -0
  78. package/dist/prisma/client.d.ts +2 -0
  79. package/dist/prisma/client.d.ts.map +1 -0
  80. package/dist/prisma/client.js +12 -0
  81. package/dist/prisma/client.mjs +1 -0
  82. package/dist/prisma/index.d.ts +4 -0
  83. package/dist/prisma/index.d.ts.map +1 -0
  84. package/dist/prisma/index.js +10 -0
  85. package/dist/prisma/index.mjs +2 -0
  86. package/dist/prisma/prisma-transaction-util.d.ts +3 -0
  87. package/dist/prisma/prisma-transaction-util.d.ts.map +1 -0
  88. package/dist/prisma/prisma-transaction-util.js +29 -0
  89. package/dist/prisma/prisma-transaction-util.mjs +27 -0
  90. package/dist/prisma/prisma.d.ts +4 -0
  91. package/dist/prisma/prisma.d.ts.map +1 -0
  92. package/dist/prisma/prisma.js +109 -0
  93. package/dist/prisma/prisma.mjs +106 -0
  94. package/dist/services/aggregate/billing.aggregate.service.d.ts +83 -0
  95. package/dist/services/aggregate/billing.aggregate.service.d.ts.map +1 -0
  96. package/dist/services/aggregate/billing.aggregate.service.js +308 -0
  97. package/dist/services/aggregate/billing.aggregate.service.mjs +306 -0
  98. package/dist/services/aggregate/index.d.ts +3 -0
  99. package/dist/services/aggregate/index.d.ts.map +1 -0
  100. package/dist/services/aggregate/index.js +9 -0
  101. package/dist/services/aggregate/index.mjs +2 -0
  102. package/dist/services/aggregate/user.aggregate.service.d.ts +34 -0
  103. package/dist/services/aggregate/user.aggregate.service.d.ts.map +1 -0
  104. package/dist/services/aggregate/user.aggregate.service.js +136 -0
  105. package/dist/services/aggregate/user.aggregate.service.mjs +133 -0
  106. package/dist/services/context/index.d.ts +2 -0
  107. package/dist/services/context/index.d.ts.map +1 -0
  108. package/dist/services/context/index.js +13 -0
  109. package/dist/services/context/index.mjs +1 -0
  110. package/dist/services/context/user-context-service.d.ts +30 -0
  111. package/dist/services/context/user-context-service.d.ts.map +1 -0
  112. package/dist/services/context/user-context-service.js +170 -0
  113. package/dist/services/context/user-context-service.mjs +162 -0
  114. package/dist/services/database/apilog.service.d.ts +39 -0
  115. package/dist/services/database/apilog.service.d.ts.map +1 -0
  116. package/dist/services/database/apilog.service.js +174 -0
  117. package/dist/services/database/apilog.service.mjs +170 -0
  118. package/dist/services/database/constants.d.ts +73 -0
  119. package/dist/services/database/constants.d.ts.map +1 -0
  120. package/dist/services/database/constants.js +135 -0
  121. package/dist/services/database/constants.mjs +117 -0
  122. package/dist/services/database/credit.service.d.ts +107 -0
  123. package/dist/services/database/credit.service.d.ts.map +1 -0
  124. package/dist/services/database/credit.service.js +515 -0
  125. package/dist/services/database/credit.service.mjs +512 -0
  126. package/dist/services/database/creditAuditLog.service.d.ts +73 -0
  127. package/dist/services/database/creditAuditLog.service.d.ts.map +1 -0
  128. package/dist/services/database/creditAuditLog.service.js +305 -0
  129. package/dist/services/database/creditAuditLog.service.mjs +302 -0
  130. package/dist/services/database/index.d.ts +10 -0
  131. package/dist/services/database/index.d.ts.map +1 -0
  132. package/dist/services/database/index.js +38 -0
  133. package/dist/services/database/index.mjs +8 -0
  134. package/dist/services/database/prisma-model-type.d.ts +3 -0
  135. package/dist/services/database/prisma-model-type.d.ts.map +1 -0
  136. package/dist/services/database/subscription.service.d.ts +48 -0
  137. package/dist/services/database/subscription.service.d.ts.map +1 -0
  138. package/dist/services/database/subscription.service.js +267 -0
  139. package/dist/services/database/subscription.service.mjs +264 -0
  140. package/dist/services/database/transaction.service.d.ts +92 -0
  141. package/dist/services/database/transaction.service.d.ts.map +1 -0
  142. package/dist/services/database/transaction.service.js +326 -0
  143. package/dist/services/database/transaction.service.mjs +323 -0
  144. package/dist/services/database/user.service.d.ts +45 -0
  145. package/dist/services/database/user.service.d.ts.map +1 -0
  146. package/dist/services/database/user.service.js +180 -0
  147. package/dist/services/database/user.service.mjs +177 -0
  148. package/dist/services/database/userBackup.service.d.ts +45 -0
  149. package/dist/services/database/userBackup.service.d.ts.map +1 -0
  150. package/dist/services/database/userBackup.service.js +249 -0
  151. package/dist/services/database/userBackup.service.mjs +246 -0
  152. package/dist/services/stripe/index.d.ts +2 -0
  153. package/dist/services/stripe/index.d.ts.map +1 -0
  154. package/dist/services/stripe/index.js +7 -0
  155. package/dist/services/stripe/index.mjs +1 -0
  156. package/dist/services/stripe/webhook-handler.d.ts +6 -0
  157. package/dist/services/stripe/webhook-handler.d.ts.map +1 -0
  158. package/dist/services/stripe/webhook-handler.js +537 -0
  159. package/dist/services/stripe/webhook-handler.mjs +535 -0
  160. package/migrations/create.sql +176 -0
  161. package/migrations/db.init.sql +13 -0
  162. package/migrations/init-schema.sql +19 -0
  163. package/migrations/purge.sql +27 -0
  164. package/migrations/test-check.sql +167 -0
  165. package/package.json +123 -0
  166. package/prisma/schema.prisma +191 -0
  167. package/src/app/api/stripe/checkout/route.ts +145 -0
  168. package/src/app/api/stripe/customer-portal/route.ts +83 -0
  169. package/src/app/api/user/anonymous/init/route.ts +284 -0
  170. package/src/app/api/webhook/clerk/user/route.ts +249 -0
  171. package/src/app/api/webhook/stripe/route.ts +93 -0
  172. package/src/index.ts +6 -0
  173. package/src/lib/auth-utils.ts +101 -0
  174. package/src/lib/credit-init.ts +9 -0
  175. package/src/lib/index.ts +4 -0
  176. package/src/lib/money-price-config.ts +168 -0
  177. package/src/lib/stripe-config.ts +333 -0
  178. package/src/prisma/client.ts +2 -0
  179. package/src/prisma/index.ts +3 -0
  180. package/src/prisma/prisma-transaction-util.ts +24 -0
  181. package/src/prisma/prisma.ts +122 -0
  182. package/src/services/aggregate/billing.aggregate.service.ts +498 -0
  183. package/src/services/aggregate/index.ts +2 -0
  184. package/src/services/aggregate/user.aggregate.service.ts +168 -0
  185. package/src/services/context/index.ts +1 -0
  186. package/src/services/context/user-context-service.ts +200 -0
  187. package/src/services/database/apilog.service.ts +185 -0
  188. package/src/services/database/constants.ts +148 -0
  189. package/src/services/database/credit.service.ts +747 -0
  190. package/src/services/database/creditAuditLog.service.ts +402 -0
  191. package/src/services/database/index.ts +41 -0
  192. package/src/services/database/prisma-model-type.ts +13 -0
  193. package/src/services/database/subscription.service.ts +319 -0
  194. package/src/services/database/transaction.service.ts +447 -0
  195. package/src/services/database/user.service.ts +218 -0
  196. package/src/services/database/userBackup.service.ts +290 -0
  197. package/src/services/stripe/index.ts +1 -0
  198. package/src/services/stripe/webhook-handler.ts +648 -0
@@ -0,0 +1,512 @@
1
+ import { __awaiter } from '../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs';
2
+ import { Prisma } from '@prisma/client';
3
+ import { CreditType, OperationType } from './constants.mjs';
4
+ import { freeExpiredDays } from '../../lib/credit-init.mjs';
5
+ import { checkAndFallbackWithNonTCClient } from '../../prisma/prisma.mjs';
6
+ import { creditAuditLogService } from './creditAuditLog.service.mjs';
7
+
8
+ const CREDIT_PURGE_CONFIG = {
9
+ [CreditType.FREE]: {
10
+ amountKey: 'free',
11
+ balanceField: 'balanceFree',
12
+ limitField: 'totalFreeLimit',
13
+ startField: 'freeStart',
14
+ endField: 'freeEnd',
15
+ },
16
+ [CreditType.PAID]: {
17
+ amountKey: 'paid',
18
+ balanceField: 'balancePaid',
19
+ limitField: 'totalPaidLimit',
20
+ startField: 'paidStart',
21
+ endField: 'paidEnd',
22
+ },
23
+ [CreditType.ONE_TIME_PAID]: {
24
+ amountKey: 'oneTimePaid',
25
+ balanceField: 'balanceOneTimePaid',
26
+ limitField: 'totalOneTimePaidLimit',
27
+ startField: 'oneTimePaidStart',
28
+ endField: 'oneTimePaidEnd',
29
+ },
30
+ };
31
+ class CreditService {
32
+ normalizeAmounts(amounts) {
33
+ var _a, _b, _c;
34
+ return {
35
+ free: Math.trunc((_a = amounts === null || amounts === void 0 ? void 0 : amounts.free) !== null && _a !== void 0 ? _a : 0),
36
+ paid: Math.trunc((_b = amounts === null || amounts === void 0 ? void 0 : amounts.paid) !== null && _b !== void 0 ? _b : 0),
37
+ oneTimePaid: Math.trunc((_c = amounts === null || amounts === void 0 ? void 0 : amounts.oneTimePaid) !== null && _c !== void 0 ? _c : 0),
38
+ };
39
+ }
40
+ hasAnyChange(amounts) {
41
+ return amounts.free !== 0 || amounts.paid !== 0 || amounts.oneTimePaid !== 0;
42
+ }
43
+ ensureNonNegative(amounts, context) {
44
+ if (amounts.free < 0 || amounts.paid < 0 || amounts.oneTimePaid < 0) {
45
+ throw new Error(`${context}: negative credit adjustments are not allowed`);
46
+ }
47
+ }
48
+ ensureSufficientBalance(current, deduction) {
49
+ if (deduction.free > current.balanceFree) {
50
+ throw new Error('Insufficient free credits');
51
+ }
52
+ if (deduction.paid > current.balancePaid) {
53
+ throw new Error('Insufficient paid credits');
54
+ }
55
+ if (deduction.oneTimePaid > current.balanceOneTimePaid) {
56
+ throw new Error('Insufficient one-time paid credits');
57
+ }
58
+ }
59
+ ensureSufficientLimits(current, deduction) {
60
+ if (deduction.free > current.totalFreeLimit) {
61
+ throw new Error('Insufficient free credit limit');
62
+ }
63
+ if (deduction.paid > current.totalPaidLimit) {
64
+ throw new Error('Insufficient paid credit limit');
65
+ }
66
+ if (deduction.oneTimePaid > current.totalOneTimePaidLimit) {
67
+ throw new Error('Insufficient one-time paid credit limit');
68
+ }
69
+ }
70
+ buildIncrementData(amounts, limitAdjustments) {
71
+ const data = {};
72
+ if (amounts.free !== 0) {
73
+ data.balanceFree = { increment: amounts.free };
74
+ if (limitAdjustments && limitAdjustments.free !== 0) {
75
+ data.totalFreeLimit = { increment: limitAdjustments.free };
76
+ }
77
+ }
78
+ if (amounts.paid !== 0) {
79
+ data.balancePaid = { increment: amounts.paid };
80
+ if (limitAdjustments && limitAdjustments.paid !== 0) {
81
+ data.totalPaidLimit = { increment: limitAdjustments.paid };
82
+ }
83
+ }
84
+ if (amounts.oneTimePaid !== 0) {
85
+ data.balanceOneTimePaid = { increment: amounts.oneTimePaid };
86
+ if (limitAdjustments && limitAdjustments.oneTimePaid !== 0) {
87
+ data.totalOneTimePaidLimit = { increment: limitAdjustments.oneTimePaid };
88
+ }
89
+ }
90
+ return data;
91
+ }
92
+ buildDecrementData(amounts, limitAdjustments) {
93
+ const data = {};
94
+ if (amounts.free !== 0) {
95
+ data.balanceFree = { decrement: amounts.free };
96
+ if (limitAdjustments && limitAdjustments.free !== 0) {
97
+ data.totalFreeLimit = { decrement: limitAdjustments.free };
98
+ }
99
+ }
100
+ if (amounts.paid !== 0) {
101
+ data.balancePaid = { decrement: amounts.paid };
102
+ if (limitAdjustments && limitAdjustments.paid !== 0) {
103
+ data.totalPaidLimit = { decrement: limitAdjustments.paid };
104
+ }
105
+ }
106
+ if (amounts.oneTimePaid !== 0) {
107
+ data.balanceOneTimePaid = { decrement: amounts.oneTimePaid };
108
+ if (limitAdjustments && limitAdjustments.oneTimePaid !== 0) {
109
+ data.totalOneTimePaidLimit = { decrement: limitAdjustments.oneTimePaid };
110
+ }
111
+ }
112
+ return data;
113
+ }
114
+ executeCreditOperation(userId, amounts, options, tx) {
115
+ return __awaiter(this, void 0, void 0, function* () {
116
+ var _a;
117
+ const normalized = this.normalizeAmounts(amounts);
118
+ this.ensureNonNegative(normalized, options.context);
119
+ if (!this.hasAnyChange(normalized)) {
120
+ throw new Error(`${options.context}: no credit change specified`);
121
+ }
122
+ let normalizedLimitAdjustments;
123
+ if (options.limitAdjustments || options.defaultLimitAdjustmentsToAmounts) {
124
+ const raw = (_a = options.limitAdjustments) !== null && _a !== void 0 ? _a : amounts;
125
+ normalizedLimitAdjustments = this.normalizeAmounts(raw);
126
+ this.ensureNonNegative(normalizedLimitAdjustments, `${options.context} limitAdjustments`);
127
+ }
128
+ const client = checkAndFallbackWithNonTCClient(tx);
129
+ const currentCredit = yield client.credit.findUnique({
130
+ where: { userId },
131
+ });
132
+ if (!currentCredit) {
133
+ throw new Error('User credits not found');
134
+ }
135
+ if (options.ensureSufficientBalance) {
136
+ this.ensureSufficientBalance(currentCredit, normalized);
137
+ }
138
+ if (options.ensureSufficientLimits && normalizedLimitAdjustments) {
139
+ this.ensureSufficientLimits(currentCredit, normalizedLimitAdjustments);
140
+ }
141
+ const data = options.updateMode === 'increment'
142
+ ? this.buildIncrementData(normalized, normalizedLimitAdjustments)
143
+ : this.buildDecrementData(normalized, normalizedLimitAdjustments);
144
+ const credit = yield client.credit.update({
145
+ where: { userId },
146
+ data,
147
+ });
148
+ const usage = yield this.recordCreditAuditLog(client, userId, options.operationType, normalized, {
149
+ feature: options.feature,
150
+ operationReferId: options.operationReferId,
151
+ });
152
+ return { credit, usage };
153
+ });
154
+ }
155
+ recordCreditAuditLog(client, userId, operationType, amounts, options) {
156
+ return __awaiter(this, void 0, void 0, function* () {
157
+ const auditPayload = [];
158
+ if (amounts.free > 0) {
159
+ auditPayload.push({
160
+ userId,
161
+ feature: options.feature,
162
+ operationReferId: options.operationReferId,
163
+ creditType: CreditType.FREE,
164
+ operationType,
165
+ creditsChange: amounts.free,
166
+ });
167
+ }
168
+ if (amounts.paid > 0) {
169
+ auditPayload.push({
170
+ userId,
171
+ feature: options.feature,
172
+ operationReferId: options.operationReferId,
173
+ creditType: CreditType.PAID,
174
+ operationType,
175
+ creditsChange: amounts.paid,
176
+ });
177
+ }
178
+ if (amounts.oneTimePaid > 0) {
179
+ auditPayload.push({
180
+ userId,
181
+ feature: options.feature,
182
+ operationReferId: options.operationReferId,
183
+ creditType: CreditType.ONE_TIME_PAID,
184
+ operationType,
185
+ creditsChange: amounts.oneTimePaid,
186
+ });
187
+ }
188
+ if (auditPayload.length === 0) {
189
+ return [];
190
+ }
191
+ const audits = [];
192
+ for (const payload of auditPayload) {
193
+ const auditlog = yield client.creditAuditLog.create({ data: payload });
194
+ audits.push(auditlog);
195
+ }
196
+ return audits;
197
+ });
198
+ }
199
+ // Initialize User Credits, use upsert for easy handle anonymous upgrade to register
200
+ initializeCreditWithFree(init, tx) {
201
+ return __awaiter(this, void 0, void 0, function* () {
202
+ const now = new Date();
203
+ const freeStart = now;
204
+ const freeEnd = new Date(now);
205
+ freeEnd.setDate(freeEnd.getDate() + freeExpiredDays);
206
+ freeEnd.setHours(23, 59, 59, 999);
207
+ const normalized = this.normalizeAmounts({ free: init.creditsChange });
208
+ this.ensureNonNegative(normalized, 'initializeCredit');
209
+ const client = checkAndFallbackWithNonTCClient(tx);
210
+ // 这里使用upsert语义是为了代码复用,处理匿名初始化和匿名->注册的初始化
211
+ const credit = yield client.credit.upsert({
212
+ where: {
213
+ userId: init.userId
214
+ },
215
+ update: {
216
+ balanceFree: normalized.free,
217
+ totalFreeLimit: normalized.free,
218
+ freeStart: freeStart,
219
+ freeEnd: freeEnd,
220
+ },
221
+ create: {
222
+ userId: init.userId,
223
+ balanceFree: normalized.free,
224
+ totalFreeLimit: normalized.free,
225
+ freeStart: freeStart,
226
+ freeEnd: freeEnd,
227
+ },
228
+ });
229
+ yield creditAuditLogService.recordCreditOperation(init, tx);
230
+ return credit;
231
+ });
232
+ }
233
+ payFailedWatcher(data, tx) {
234
+ return __awaiter(this, void 0, void 0, function* () {
235
+ yield creditAuditLogService.recordAuditLog(data, tx);
236
+ console.warn('payFailedWatcher completed');
237
+ });
238
+ }
239
+ // Get User Credits
240
+ getCredit(userId, tx) {
241
+ return __awaiter(this, void 0, void 0, function* () {
242
+ const client = checkAndFallbackWithNonTCClient(tx);
243
+ const credit = yield client.credit.findUnique({
244
+ where: { userId },
245
+ });
246
+ if (!credit) {
247
+ return null;
248
+ }
249
+ // Guard query result: if a credit block has no end time or is already expired, treat its balance as 0
250
+ const now = new Date();
251
+ const protectedCredit = Object.assign({}, credit);
252
+ if (!credit.freeEnd || now >= credit.freeEnd) {
253
+ protectedCredit.balanceFree = 0;
254
+ }
255
+ if (!credit.paidEnd || now >= credit.paidEnd) {
256
+ protectedCredit.balancePaid = 0;
257
+ }
258
+ if (!credit.oneTimePaidEnd || now >= credit.oneTimePaidEnd) {
259
+ protectedCredit.balanceOneTimePaid = 0;
260
+ }
261
+ return protectedCredit;
262
+ });
263
+ }
264
+ // Get Total Credit Balance
265
+ getTotalBalance(userId, tx) {
266
+ return __awaiter(this, void 0, void 0, function* () {
267
+ const credits = yield this.getCredit(userId, tx);
268
+ if (!credits)
269
+ return 0;
270
+ return credits.balanceFree + credits.balancePaid + credits.balanceOneTimePaid;
271
+ });
272
+ }
273
+ // Recharge Credits (Transactional)
274
+ rechargeCredit(userId, amounts, options, tx) {
275
+ return __awaiter(this, void 0, void 0, function* () {
276
+ return this.executeCreditOperation(userId, amounts, {
277
+ context: 'rechargeCredit',
278
+ operationType: OperationType.RECHARGE,
279
+ updateMode: 'increment',
280
+ feature: options.feature,
281
+ operationReferId: options.operationReferId,
282
+ limitAdjustments: options.limitAdjustments,
283
+ defaultLimitAdjustmentsToAmounts: options.limitAdjustments === undefined,
284
+ }, tx);
285
+ });
286
+ }
287
+ // Consume Credits (Transactional)
288
+ consumeCredit(userId, amounts, options, tx) {
289
+ return __awaiter(this, void 0, void 0, function* () {
290
+ return this.executeCreditOperation(userId, amounts, {
291
+ context: 'consumeCredit',
292
+ operationType: OperationType.CONSUME,
293
+ updateMode: 'decrement',
294
+ feature: options.feature,
295
+ operationReferId: options.operationReferId,
296
+ ensureSufficientBalance: true,
297
+ }, tx);
298
+ });
299
+ }
300
+ // Freeze Credits
301
+ freezeCredit(userId, amounts, reason, operationReferId, tx) {
302
+ return __awaiter(this, void 0, void 0, function* () {
303
+ return this.executeCreditOperation(userId, amounts, {
304
+ context: 'freezeCredit',
305
+ operationType: OperationType.FREEZE,
306
+ operationReferId,
307
+ updateMode: 'decrement',
308
+ feature: reason,
309
+ ensureSufficientBalance: true,
310
+ }, tx);
311
+ });
312
+ }
313
+ // Unfreeze Credits
314
+ unfreezeCredit(userId, amounts, reason, operationReferId, tx) {
315
+ return __awaiter(this, void 0, void 0, function* () {
316
+ return this.executeCreditOperation(userId, amounts, {
317
+ context: 'unfreezeCredit',
318
+ operationType: OperationType.UNFREEZE,
319
+ operationReferId,
320
+ updateMode: 'increment',
321
+ feature: reason,
322
+ }, tx);
323
+ });
324
+ }
325
+ // Refund Credits
326
+ refundCredit(userId_1, amounts_1, operationReferId_1) {
327
+ return __awaiter(this, arguments, void 0, function* (userId, amounts, operationReferId, options = {}, tx) {
328
+ var _a;
329
+ return this.executeCreditOperation(userId, amounts, {
330
+ context: 'refundCredit',
331
+ operationType: OperationType.CONSUME,
332
+ updateMode: 'decrement',
333
+ feature: (_a = options.feature) !== null && _a !== void 0 ? _a : 'Refund',
334
+ operationReferId,
335
+ limitAdjustments: options.limitAdjustments,
336
+ defaultLimitAdjustmentsToAmounts: options.limitAdjustments === undefined,
337
+ ensureSufficientBalance: true,
338
+ ensureSufficientLimits: true,
339
+ }, tx);
340
+ });
341
+ }
342
+ // Batch Update Credits (Admin Operation)
343
+ adjustCredit(userId, operationReferId, adjustments, tx) {
344
+ return __awaiter(this, void 0, void 0, function* () {
345
+ var _a, _b, _c, _d, _e, _f;
346
+ const client = checkAndFallbackWithNonTCClient(tx);
347
+ const currentCredit = yield client.credit.findUnique({
348
+ where: { userId },
349
+ });
350
+ if (!currentCredit) {
351
+ throw new Error('User credits not found');
352
+ }
353
+ const nextBalanceFree = (_a = adjustments.balanceFree) !== null && _a !== void 0 ? _a : currentCredit.balanceFree;
354
+ const nextBalancePaid = (_b = adjustments.balancePaid) !== null && _b !== void 0 ? _b : currentCredit.balancePaid;
355
+ const nextBalanceOneTimePaid = (_c = adjustments.balanceOneTimePaid) !== null && _c !== void 0 ? _c : currentCredit.balanceOneTimePaid;
356
+ const nextTotalFreeLimit = (_d = adjustments.totalFreeLimit) !== null && _d !== void 0 ? _d : currentCredit.totalFreeLimit;
357
+ const nextTotalPaidLimit = (_e = adjustments.totalPaidLimit) !== null && _e !== void 0 ? _e : currentCredit.totalPaidLimit;
358
+ const nextTotalOneTimePaidLimit = (_f = adjustments.totalOneTimePaidLimit) !== null && _f !== void 0 ? _f : currentCredit.totalOneTimePaidLimit;
359
+ if (nextBalanceFree < 0 ||
360
+ nextBalancePaid < 0 ||
361
+ nextBalanceOneTimePaid < 0 ||
362
+ nextTotalFreeLimit < 0 ||
363
+ nextTotalPaidLimit < 0 ||
364
+ nextTotalOneTimePaidLimit < 0) {
365
+ throw new Error('adjustCredit: credit values cannot be negative');
366
+ }
367
+ const increaseDiff = this.normalizeAmounts({
368
+ free: Math.max(nextBalanceFree - currentCredit.balanceFree, 0),
369
+ paid: Math.max(nextBalancePaid - currentCredit.balancePaid, 0),
370
+ oneTimePaid: Math.max(nextBalanceOneTimePaid - currentCredit.balanceOneTimePaid, 0),
371
+ });
372
+ const decreaseDiff = this.normalizeAmounts({
373
+ free: Math.max(currentCredit.balanceFree - nextBalanceFree, 0),
374
+ paid: Math.max(currentCredit.balancePaid - nextBalancePaid, 0),
375
+ oneTimePaid: Math.max(currentCredit.balanceOneTimePaid - nextBalanceOneTimePaid, 0),
376
+ });
377
+ const credit = yield client.credit.update({
378
+ where: { userId },
379
+ data: {
380
+ balanceFree: nextBalanceFree,
381
+ balancePaid: nextBalancePaid,
382
+ balanceOneTimePaid: nextBalanceOneTimePaid,
383
+ totalFreeLimit: nextTotalFreeLimit,
384
+ totalPaidLimit: nextTotalPaidLimit,
385
+ totalOneTimePaidLimit: nextTotalOneTimePaidLimit,
386
+ },
387
+ });
388
+ if (this.hasAnyChange(increaseDiff)) {
389
+ yield this.recordCreditAuditLog(client, userId, OperationType.ADJUST_INCREASE, increaseDiff, {
390
+ feature: 'admin_adjust',
391
+ operationReferId
392
+ });
393
+ }
394
+ if (this.hasAnyChange(decreaseDiff)) {
395
+ yield this.recordCreditAuditLog(client, userId, OperationType.ADJUST_DECREASE, decreaseDiff, {
396
+ feature: 'admin_adjust',
397
+ operationReferId
398
+ });
399
+ }
400
+ return credit;
401
+ });
402
+ }
403
+ purgeCreditsByTypes(userId, reason, operationReferId, types, tx) {
404
+ return __awaiter(this, void 0, void 0, function* () {
405
+ const uniqueTypes = Array.from(new Set(types));
406
+ if (uniqueTypes.length === 0) {
407
+ throw new Error('purgeCreditsByTypes: no credit types specified');
408
+ }
409
+ const client = checkAndFallbackWithNonTCClient(tx);
410
+ const currentCredit = yield client.credit.findUnique({ where: { userId }, });
411
+ if (!currentCredit) {
412
+ throw new Error('User credits not found');
413
+ }
414
+ const deduction = {};
415
+ const updateData = {};
416
+ for (const type of uniqueTypes) {
417
+ const config = CREDIT_PURGE_CONFIG[type];
418
+ if (!config) {
419
+ throw new Error(`Unsupported credit type: ${type}`);
420
+ }
421
+ deduction[config.amountKey] = currentCredit[config.balanceField];
422
+ updateData[config.balanceField] = 0;
423
+ updateData[config.limitField] = 0;
424
+ updateData[config.startField] = null;
425
+ updateData[config.endField] = null;
426
+ }
427
+ const normalizedDeduction = this.normalizeAmounts(deduction);
428
+ const credit = yield client.credit.update({
429
+ where: { userId },
430
+ data: updateData,
431
+ });
432
+ // 强制留痕,即使是积分变化为0也记录,操作留痕
433
+ const usage = yield this.recordCreditAuditLog(client, userId, OperationType.PURGE, normalizedDeduction, { feature: reason, operationReferId });
434
+ return { credit, usage };
435
+ });
436
+ }
437
+ purgePaidCredit(userId, reason, operationReferId, tx) {
438
+ return __awaiter(this, void 0, void 0, function* () {
439
+ return this.purgeCreditsByTypes(userId, reason, operationReferId, [CreditType.PAID], tx);
440
+ });
441
+ }
442
+ purgeFreeCredit(userId, reason, operationReferId, tx) {
443
+ return __awaiter(this, void 0, void 0, function* () {
444
+ return this.purgeCreditsByTypes(userId, reason, operationReferId, [CreditType.FREE], tx);
445
+ });
446
+ }
447
+ purgeCredit(userId, reason, operationReferId, tx) {
448
+ return __awaiter(this, void 0, void 0, function* () {
449
+ return this.purgeCreditsByTypes(userId, reason, operationReferId, [CreditType.FREE, CreditType.PAID, CreditType.ONE_TIME_PAID], tx);
450
+ });
451
+ }
452
+ // Get Users with Low Credit Balance
453
+ getLowBalanceUsers() {
454
+ return __awaiter(this, arguments, void 0, function* (threshold = 10, tx) {
455
+ const client = checkAndFallbackWithNonTCClient(tx);
456
+ const query = Prisma.sql `
457
+ SELECT * FROM credits
458
+ WHERE (balance_free + balance_paid + balance_onetime_paid) < ${threshold}
459
+ ORDER BY (balance_free + balance_paid + balance_onetime_paid) ASC
460
+ `;
461
+ return yield client.$queryRaw(query);
462
+ });
463
+ }
464
+ // Get Credit Statistics
465
+ getCreditStats(tx) {
466
+ return __awaiter(this, void 0, void 0, function* () {
467
+ const client = checkAndFallbackWithNonTCClient(tx);
468
+ const stats = yield client.credit.aggregate({
469
+ _count: true,
470
+ _sum: {
471
+ balanceFree: true,
472
+ balancePaid: true,
473
+ balanceOneTimePaid: true,
474
+ },
475
+ _avg: {
476
+ balanceFree: true,
477
+ balancePaid: true,
478
+ balanceOneTimePaid: true,
479
+ },
480
+ });
481
+ const zeroBalanceUsers = yield client.credit.count({
482
+ where: {
483
+ AND: [
484
+ { balanceFree: 0 },
485
+ { balancePaid: 0 },
486
+ { balanceOneTimePaid: 0 },
487
+ ],
488
+ },
489
+ });
490
+ return {
491
+ totalUsers: stats._count,
492
+ totalFreeBalance: stats._sum.balanceFree || 0,
493
+ totalPaidBalance: stats._sum.balancePaid || 0,
494
+ totalOneTimePaidBalance: stats._sum.balanceOneTimePaid || 0,
495
+ avgFreeBalance: Math.round(stats._avg.balanceFree || 0),
496
+ avgPaidBalance: Math.round(stats._avg.balancePaid || 0),
497
+ avgOneTimePaidBalance: Math.round(stats._avg.balanceOneTimePaid || 0),
498
+ zeroBalanceUsers,
499
+ };
500
+ });
501
+ }
502
+ // Check if User has Enough Credits
503
+ hasEnoughCredits(userId, amount, tx) {
504
+ return __awaiter(this, void 0, void 0, function* () {
505
+ const totalBalance = yield this.getTotalBalance(userId, tx);
506
+ return totalBalance >= amount;
507
+ });
508
+ }
509
+ }
510
+ const creditService = new CreditService();
511
+
512
+ export { CreditService, creditService };
@@ -0,0 +1,73 @@
1
+ import { Prisma } from './prisma-model-type';
2
+ import type { CreditAuditLog } from './prisma-model-type';
3
+ export declare class CreditAuditLogService {
4
+ recordAuditLog(data: {
5
+ userId: string;
6
+ feature?: string;
7
+ operationReferId?: string;
8
+ creditType: string;
9
+ operationType: string;
10
+ creditsChange: number;
11
+ }, tx?: Prisma.TransactionClient): Promise<CreditAuditLog>;
12
+ recordCreditOperation(data: {
13
+ userId: string;
14
+ feature?: string;
15
+ operationReferId?: string;
16
+ creditType: string;
17
+ operationType: string;
18
+ creditsChange: number;
19
+ }, tx?: Prisma.TransactionClient): Promise<CreditAuditLog>;
20
+ recordBatchAudit(auditLogs: Prisma.CreditAuditLogCreateManyInput[], tx?: Prisma.TransactionClient): Promise<number>;
21
+ getUserCreditAuditHistory(userId: string, params?: {
22
+ creditType?: string;
23
+ operationType?: string;
24
+ feature?: string;
25
+ startDate?: Date;
26
+ endDate?: Date;
27
+ skip?: number;
28
+ take?: number;
29
+ orderBy?: Prisma.CreditAuditLogOrderByWithRelationInput;
30
+ }, tx?: Prisma.TransactionClient): Promise<{
31
+ creditAudit: CreditAuditLog[];
32
+ total: number;
33
+ }>;
34
+ getCreditAuditList(operationReferId: string, tx?: Prisma.TransactionClient): Promise<CreditAuditLog[]>;
35
+ getUserCreditAuditStats(userId: string, startDate?: Date, endDate?: Date, tx?: Prisma.TransactionClient): Promise<{
36
+ totalConsumed: number;
37
+ totalRecharged: number;
38
+ freeConsumed: number;
39
+ paidConsumed: number;
40
+ freeRecharged: number;
41
+ paidRecharged: number;
42
+ featureUsage: {
43
+ feature: string;
44
+ credits: number;
45
+ }[];
46
+ }>;
47
+ getPopularFeatures(limit?: number, startDate?: Date, endDate?: Date, tx?: Prisma.TransactionClient): Promise<{
48
+ feature: string | null;
49
+ totalCredits: number;
50
+ usageCount: number;
51
+ }[]>;
52
+ getDailyUsageTrend(days?: number, userId?: string, tx?: Prisma.TransactionClient): Promise<{
53
+ date: Date;
54
+ consumed: number;
55
+ recharged: number;
56
+ free_consumed: number;
57
+ paid_consumed: number;
58
+ unique_users: number;
59
+ }[]>;
60
+ getRecentOperations(userId: string, limit?: number, tx?: Prisma.TransactionClient): Promise<CreditAuditLog[]>;
61
+ deleteOldRecords(daysToKeep?: number, tx?: Prisma.TransactionClient): Promise<number>;
62
+ getSystemStats(tx?: Prisma.TransactionClient): Promise<{
63
+ totalUsers: number;
64
+ totalOperations: number;
65
+ totalConsumed: number;
66
+ totalRecharged: number;
67
+ avgDailyConsumption: number;
68
+ avgDailyRecharge: number;
69
+ }>;
70
+ isDuplicateOperation(userId: string, operationReferId: string, operationType: string, tx?: Prisma.TransactionClient): Promise<boolean>;
71
+ }
72
+ export declare const creditAuditLogService: CreditAuditLogService;
73
+ //# sourceMappingURL=creditAuditLog.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creditAuditLog.service.d.ts","sourceRoot":"","sources":["../../../src/services/database/creditAuditLog.service.ts"],"names":[],"mappings":"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"}