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