@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,447 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import { Prisma } from './prisma-model-type';
4
+ import type { Transaction } from './prisma-model-type';
5
+ import { OrderStatus, TransactionType, PaySupplier, PaymentStatus } from './constants';
6
+ import { checkAndFallbackWithNonTCClient } from '../../prisma/index';
7
+
8
+ export class TransactionService {
9
+
10
+ // Create transaction order
11
+ async createTransaction(data: {
12
+ userId: string;
13
+ orderId: string;
14
+ orderStatus?: string;
15
+ paymentStatus?: string;
16
+ paySupplier?: PaySupplier;
17
+ payTransactionId?: string;
18
+ paySubscriptionId?: string;
19
+ subPeriodStart? : Date;
20
+ subPeriodEnd? : Date;
21
+ paySessionId?: string;
22
+ payInvoiceId?: string;
23
+ hostedInvoiceUrl?: string,
24
+ invoicePdf?: string,
25
+ billingReason?: string;
26
+ priceId?: string;
27
+ priceName?: string;
28
+ amount?: number;
29
+ currency?: string;
30
+ type?: string;
31
+ creditsGranted?: number;
32
+ orderDetail?: string;
33
+ orderExpiredAt?: Date;
34
+ paidAt?: Date;
35
+ paidEmail : string | null,
36
+ payUpdatedAt?: Date;
37
+ }, tx?: Prisma.TransactionClient): Promise<Transaction> {
38
+ const client = checkAndFallbackWithNonTCClient(tx);
39
+
40
+ return await client.transaction.create({
41
+ data: {
42
+ userId: data.userId,
43
+ orderId: data.orderId,
44
+ orderStatus: data.orderStatus || OrderStatus.CREATED,
45
+ paymentStatus: data.paymentStatus || PaymentStatus.UN_PAID,
46
+ orderExpiredAt: data.orderExpiredAt || new Date(Date.now() + 30 * 60 * 1000), // 默认30分钟过期
47
+ paySupplier: data.paySupplier,
48
+ payTransactionId: data.payTransactionId,
49
+ paySubscriptionId: data.paySubscriptionId,
50
+ paySessionId: data.paySessionId,
51
+ payInvoiceId: data.payInvoiceId,
52
+ hostedInvoiceUrl: data.hostedInvoiceUrl,
53
+ invoicePdf: data.invoicePdf,
54
+ billingReason: data.billingReason,
55
+ priceId: data.priceId,
56
+ priceName: data.priceName,
57
+ amount: data.amount,
58
+ currency: data.currency,
59
+ type: data.type,
60
+ creditsGranted: data.creditsGranted || 0,
61
+ orderDetail: data.orderDetail,
62
+ paidAt: data.paidAt,
63
+ paidEmail: data.paidEmail || undefined,
64
+ payUpdatedAt: data.payUpdatedAt,
65
+ },
66
+ });
67
+ }
68
+
69
+ // Find transaction by order ID
70
+ async findByOrderId(orderId: string, tx?: Prisma.TransactionClient): Promise<Transaction | null> {
71
+ const client = checkAndFallbackWithNonTCClient(tx);
72
+
73
+ return await client.transaction.findUnique({
74
+ where: { orderId, deleted: 0 }
75
+ });
76
+ }
77
+
78
+ // Find transaction by pay session ID
79
+ async findByPaySessionId(
80
+ paySessionId: string,
81
+ tx?: Prisma.TransactionClient
82
+ ): Promise<Transaction | null> {
83
+ const client = checkAndFallbackWithNonTCClient(tx);
84
+
85
+ return await client.transaction.findFirst({
86
+ where: { paySessionId, deleted: 0 }
87
+ });
88
+ }
89
+
90
+ // Find transaction by pay transaction ID
91
+ async findByPayTransactionId(
92
+ payTransactionId: string,
93
+ tx?: Prisma.TransactionClient
94
+ ): Promise<Transaction | null> {
95
+ const client = checkAndFallbackWithNonTCClient(tx);
96
+
97
+ return await client.transaction.findFirst({
98
+ where: { payTransactionId, deleted: 0 },
99
+ });
100
+ }
101
+
102
+ // Find transactions by user ID
103
+ async findByUserId(
104
+ userId: string,
105
+ params?: {
106
+ orderStatus?: string;
107
+ type?: string;
108
+ skip?: number;
109
+ take?: number;
110
+ orderBy?: Prisma.TransactionOrderByWithRelationInput;
111
+ },
112
+ tx?: Prisma.TransactionClient
113
+ ): Promise<{ transactions: Transaction[]; total: number }> {
114
+ const client = checkAndFallbackWithNonTCClient(tx);
115
+ const where: Prisma.TransactionWhereInput = { userId, deleted: 0 };
116
+
117
+ if (params?.orderStatus) {
118
+ where.orderStatus = params.orderStatus;
119
+ }
120
+
121
+ if (params?.type) {
122
+ where.type = params.type;
123
+ }
124
+
125
+ const [transactions, total] = await Promise.all([
126
+ client.transaction.findMany({
127
+ where,
128
+ skip: params?.skip || 0,
129
+ take: params?.take || 10,
130
+ orderBy: params?.orderBy || { orderCreatedAt: 'desc' },
131
+ }),
132
+ client.transaction.count({ where }),
133
+ ]);
134
+
135
+ return { transactions, total };
136
+ }
137
+
138
+ // Update transaction status
139
+ async updateStatus(
140
+ orderId: string,
141
+ orderStatus: string,
142
+ additionalData?: {
143
+ payTransactionId?: string;
144
+ paidAt?: Date;
145
+ paidEmail?: string;
146
+ paidDetail?: string;
147
+ payUpdatedAt?: Date;
148
+ paymentStatus?: string;
149
+ paySubscriptionId?: string;
150
+ paySessionId?: string;
151
+ payInvoiceId?: string;
152
+ hostedInvoiceUrl?: string;
153
+ invoicePdf?: string;
154
+ billingReason?: string;
155
+ orderDetail?: string;
156
+ },
157
+ tx?: Prisma.TransactionClient
158
+ ): Promise<Transaction> {
159
+ const updateData: Prisma.TransactionUpdateInput = {
160
+ orderStatus,
161
+ ...additionalData,
162
+ };
163
+
164
+ console.log(`orderId: ${orderId}\n updateData: ${JSON.stringify(updateData)}`)
165
+ const client = checkAndFallbackWithNonTCClient(tx);
166
+
167
+ return await client.transaction.update({
168
+ where: { orderId },
169
+ data: updateData,
170
+ });
171
+ }
172
+
173
+ async update(
174
+ orderId: string,
175
+ data: Prisma.TransactionUpdateInput,
176
+ tx?: Prisma.TransactionClient
177
+ ): Promise<Transaction> {
178
+ const client = checkAndFallbackWithNonTCClient(tx);
179
+
180
+ return await client.transaction.update({
181
+ where: { orderId },
182
+ data,
183
+ });
184
+ }
185
+
186
+ // Complete payment
187
+ async completePayment(
188
+ orderId: string,
189
+ paymentData: {
190
+ payTransactionId?: string;
191
+ paidAt: Date;
192
+ paidEmail?: string;
193
+ paidDetail?: string;
194
+ creditsGranted?: number;
195
+ payUpdatedAt?: Date;
196
+ paymentStatus?: string;
197
+ },
198
+ tx?: Prisma.TransactionClient
199
+ ): Promise<Transaction> {
200
+ const client = checkAndFallbackWithNonTCClient(tx);
201
+
202
+ return await client.transaction.update({
203
+ where: { orderId },
204
+ data: {
205
+ orderStatus: OrderStatus.SUCCESS,
206
+ payTransactionId: paymentData.payTransactionId,
207
+ paidAt: paymentData.paidAt,
208
+ paidEmail: paymentData.paidEmail,
209
+ paidDetail: paymentData.paidDetail,
210
+ creditsGranted: paymentData.creditsGranted,
211
+ payUpdatedAt: paymentData.payUpdatedAt,
212
+ paymentStatus: paymentData.paymentStatus || PaymentStatus.PAID
213
+ },
214
+ });
215
+ }
216
+
217
+ // Process refund
218
+ async processRefund(
219
+ orderId: string,
220
+ refundData: {
221
+ refundAmount?: number;
222
+ refundReason?: string;
223
+ refundedAt?: Date;
224
+ },
225
+ tx?: Prisma.TransactionClient
226
+ ): Promise<Transaction> {
227
+ const client = checkAndFallbackWithNonTCClient(tx);
228
+ const transaction = await this.findByOrderId(orderId, tx);
229
+ if (!transaction) {
230
+ throw new Error('Transaction not found');
231
+ }
232
+
233
+ return await client.transaction.update({
234
+ where: { orderId },
235
+ data: {
236
+ orderStatus: OrderStatus.REFUNDED,
237
+ orderDetail: JSON.stringify({
238
+ original: transaction.orderDetail,
239
+ refund: {
240
+ amount: refundData.refundAmount,
241
+ reason: refundData.refundReason,
242
+ refundedAt: refundData.refundedAt || new Date(),
243
+ },
244
+ }),
245
+ },
246
+ });
247
+ }
248
+
249
+ // Cancel order
250
+ async cancelOrder(orderId: string, reason?: string, tx?: Prisma.TransactionClient): Promise<Transaction> {
251
+ const client = checkAndFallbackWithNonTCClient(tx);
252
+
253
+ return await client.transaction.update({
254
+ where: { orderId },
255
+ data: {
256
+ orderStatus: OrderStatus.CANCELED,
257
+ orderDetail: reason || 'User canceled',
258
+ paymentStatus: PaymentStatus.UN_PAID,
259
+ },
260
+ });
261
+ }
262
+
263
+ // Get expired orders
264
+ async getExpiredOrders(tx?: Prisma.TransactionClient): Promise<Transaction[]> {
265
+ const client = checkAndFallbackWithNonTCClient(tx);
266
+
267
+ return await client.transaction.findMany({
268
+ where: {
269
+ orderStatus: OrderStatus.CREATED,
270
+ deleted: 0,
271
+ orderExpiredAt: {
272
+ lt: new Date(),
273
+ },
274
+ },
275
+ });
276
+ }
277
+
278
+ // Update expired orders status
279
+ async updateExpiredOrders(tx?: Prisma.TransactionClient): Promise<number> {
280
+ const client = checkAndFallbackWithNonTCClient(tx);
281
+
282
+ const result = await client.transaction.updateMany({
283
+ where: {
284
+ orderStatus: OrderStatus.CREATED,
285
+ deleted: 0,
286
+ orderExpiredAt: {
287
+ lt: new Date(),
288
+ },
289
+ },
290
+ data: {
291
+ orderStatus: OrderStatus.FAILED,
292
+ orderDetail: 'Order expired',
293
+ paymentStatus: PaymentStatus.UN_PAID,
294
+ },
295
+ });
296
+
297
+ return result.count;
298
+ }
299
+
300
+ // Get subscription related transactions
301
+ async getSubscriptionTransactions(
302
+ paySubscriptionId: string,
303
+ tx?: Prisma.TransactionClient
304
+ ): Promise<Transaction[]> {
305
+ const client = checkAndFallbackWithNonTCClient(tx);
306
+
307
+ return await client.transaction.findMany({
308
+ where: { paySubscriptionId, deleted: 0 },
309
+ orderBy: { orderCreatedAt: 'desc' },
310
+ });
311
+ }
312
+
313
+ // Get revenue statistics
314
+ async getRevenueStats(
315
+ startDate?: Date,
316
+ endDate?: Date,
317
+ tx?: Prisma.TransactionClient
318
+ ): Promise<{
319
+ totalRevenue: number;
320
+ totalTransactions: number;
321
+ averageOrderValue: number;
322
+ subscriptionRevenue: number;
323
+ oneTimeRevenue: number;
324
+ refundedAmount: number;
325
+ }> {
326
+ const client = checkAndFallbackWithNonTCClient(tx);
327
+ const where: Prisma.TransactionWhereInput = {
328
+ orderStatus: OrderStatus.SUCCESS,
329
+ deleted: 0,
330
+ };
331
+
332
+ if (startDate || endDate) {
333
+ where.paidAt = {};
334
+ if (startDate) where.paidAt.gte = startDate;
335
+ if (endDate) where.paidAt.lte = endDate;
336
+ }
337
+
338
+ // Get successful transactions
339
+ const successfulTransactions = await client.transaction.findMany({
340
+ where,
341
+ select: {
342
+ amount: true,
343
+ type: true,
344
+ },
345
+ });
346
+
347
+ // Get refund transactions
348
+ const refundWhere: Prisma.TransactionWhereInput = {
349
+ orderStatus: OrderStatus.REFUNDED,
350
+ deleted: 0,
351
+ };
352
+
353
+ if (startDate || endDate) {
354
+ refundWhere.orderUpdatedAt = {};
355
+ if (startDate) refundWhere.orderUpdatedAt.gte = startDate;
356
+ if (endDate) refundWhere.orderUpdatedAt.lte = endDate;
357
+ }
358
+
359
+ const refundedTransactions = await client.transaction.findMany({
360
+ where: refundWhere,
361
+ select: { amount: true },
362
+ });
363
+
364
+ // Calculate statistics
365
+ const totalRevenue = successfulTransactions.reduce(
366
+ (sum, t) => sum + (t.amount ? parseFloat(t.amount.toString()) : 0),
367
+ 0
368
+ );
369
+
370
+ const subscriptionRevenue = successfulTransactions
371
+ .filter(t => t.type === TransactionType.SUBSCRIPTION)
372
+ .reduce((sum, t) => sum + (t.amount ? parseFloat(t.amount.toString()) : 0), 0);
373
+
374
+ const oneTimeRevenue = successfulTransactions
375
+ .filter(t => t.type === TransactionType.ONE_TIME)
376
+ .reduce((sum, t) => sum + (t.amount ? parseFloat(t.amount.toString()) : 0), 0);
377
+
378
+ const refundedAmount = refundedTransactions.reduce(
379
+ (sum, t) => sum + (t.amount ? parseFloat(t.amount.toString()) : 0),
380
+ 0
381
+ );
382
+
383
+ return {
384
+ totalRevenue,
385
+ totalTransactions: successfulTransactions.length,
386
+ averageOrderValue: successfulTransactions.length > 0
387
+ ? totalRevenue / successfulTransactions.length
388
+ : 0,
389
+ subscriptionRevenue,
390
+ oneTimeRevenue,
391
+ refundedAmount,
392
+ };
393
+ }
394
+
395
+ // Get daily revenue
396
+ async getDailyRevenue(days: number = 30, tx?: Prisma.TransactionClient): Promise<any[]> {
397
+ const startDate = new Date();
398
+ startDate.setDate(startDate.getDate() - days);
399
+
400
+ const client = checkAndFallbackWithNonTCClient(tx);
401
+
402
+ const query = Prisma.sql`
403
+ SELECT
404
+ DATE(paid_at) as date,
405
+ COUNT(*) as transactions,
406
+ SUM(amount) as revenue,
407
+ SUM(CASE WHEN type = 'subscription' THEN amount ELSE 0 END) as subscription_revenue,
408
+ SUM(CASE WHEN type = 'one_time' THEN amount ELSE 0 END) as onetime_revenue
409
+ FROM transactions
410
+ WHERE order_status = 'success'
411
+ AND deleted = 0
412
+ AND paid_at >= ${startDate}
413
+ GROUP BY DATE(paid_at)
414
+ ORDER BY date DESC
415
+ `;
416
+
417
+ const result = await client.$queryRaw(query);
418
+
419
+ return result as any[];
420
+ }
421
+
422
+ // Soft Delete transaction
423
+ async deleteTransaction(orderId: string, tx?: Prisma.TransactionClient): Promise<void> {
424
+ const client = checkAndFallbackWithNonTCClient(tx);
425
+
426
+ await client.transaction.update({
427
+ where: { orderId },
428
+ data: { deleted: 1 },
429
+ });
430
+ }
431
+
432
+ // Create batch transactions
433
+ async createBatchTransactions(
434
+ transactions: Prisma.TransactionCreateManyInput[],
435
+ tx?: Prisma.TransactionClient
436
+ ): Promise<number> {
437
+ const client = checkAndFallbackWithNonTCClient(tx);
438
+ const result = await client.transaction.createMany({
439
+ data: transactions,
440
+ skipDuplicates: true,
441
+ });
442
+
443
+ return result.count;
444
+ }
445
+ }
446
+
447
+ export const transactionService = new TransactionService();
@@ -0,0 +1,218 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import type { Prisma } from './prisma-model-type';
4
+ import type { User } from './prisma-model-type';
5
+ import { UserStatus } from './constants';
6
+ import { checkAndFallbackWithNonTCClient } from '../../prisma/index';
7
+
8
+ export class UserService {
9
+
10
+ // Create user
11
+ async createUser(data: {
12
+ fingerprintId?: string;
13
+ sourceRef?: Prisma.InputJsonValue;
14
+ clerkUserId?: string;
15
+ stripeCusId?: string;
16
+ email?: string;
17
+ userName?: string;
18
+ status?: string;
19
+ }, tx?: Prisma.TransactionClient): Promise<User> {
20
+ const client = checkAndFallbackWithNonTCClient(tx);
21
+
22
+ return await client.user.create({
23
+ data: {
24
+ fingerprintId: data.fingerprintId,
25
+ sourceRef: data.sourceRef,
26
+ clerkUserId: data.clerkUserId,
27
+ stripeCusId: data.stripeCusId,
28
+ email: data.email,
29
+ userName: data.userName,
30
+ status: data.status || UserStatus.ANONYMOUS,
31
+ },
32
+ });
33
+ }
34
+
35
+ // Find user by ID
36
+ async findByUserId(userId: string, tx?: Prisma.TransactionClient): Promise<User | null> {
37
+ const client = checkAndFallbackWithNonTCClient(tx);
38
+
39
+ return await client.user.findUnique({
40
+ where: { userId },
41
+ });
42
+ }
43
+
44
+ // Find user by email
45
+ async findByEmail(email: string, tx?: Prisma.TransactionClient): Promise<User | null> {
46
+ const client = checkAndFallbackWithNonTCClient(tx);
47
+
48
+ return await client.user.findFirst({
49
+ where: { email },
50
+ });
51
+ }
52
+
53
+ // Find users by Fingerprint ID, fp_id can be used for multi user_ids
54
+ async findListByFingerprintId(fingerprintId: string, tx?: Prisma.TransactionClient): Promise<User[]> {
55
+ const client = checkAndFallbackWithNonTCClient(tx);
56
+
57
+ return await client.user.findMany({
58
+ where: {
59
+ fingerprintId,
60
+ status: {
61
+ not: UserStatus.DELETED
62
+ }
63
+ },
64
+ orderBy: { createdAt: 'desc' },
65
+ });
66
+ }
67
+
68
+ // Find user by Clerk user ID
69
+ async findByClerkUserId(clerkUserId: string, tx?: Prisma.TransactionClient): Promise<User | null> {
70
+ const client = checkAndFallbackWithNonTCClient(tx);
71
+
72
+ // DB的部分索引与这里的状态查询相对应,因而可以使用findUnique
73
+ return await client.user.findUnique({
74
+ where: {
75
+ clerkUserId,
76
+ status: {
77
+ not: UserStatus.DELETED
78
+ }
79
+ }
80
+ });
81
+ }
82
+
83
+ // Update user
84
+ async updateUser(
85
+ userId: string,
86
+ data: Prisma.UserUpdateInput,
87
+ tx?: Prisma.TransactionClient
88
+ ): Promise<User> {
89
+ const client = checkAndFallbackWithNonTCClient(tx);
90
+
91
+ return await client.user.update({
92
+ where: { userId },
93
+ data,
94
+ });
95
+ }
96
+
97
+ async updateStripeCustomerId(
98
+ userId: string,
99
+ stripeCusId: string | null,
100
+ tx?: Prisma.TransactionClient
101
+ ): Promise<User> {
102
+ const client = checkAndFallbackWithNonTCClient(tx);
103
+
104
+ return await client.user.update({
105
+ where: { userId },
106
+ data: { stripeCusId },
107
+ });
108
+ }
109
+
110
+ // Upgrade anonymous user to registered user
111
+ async upgradeToRegistered(
112
+ userId: string,
113
+ data: {
114
+ email: string;
115
+ clerkUserId: string;
116
+ userName?: string;
117
+ },
118
+ tx?: Prisma.TransactionClient
119
+ ): Promise<User> {
120
+ const client = checkAndFallbackWithNonTCClient(tx);
121
+
122
+ return await client.user.update({
123
+ where: { userId },
124
+ data: {
125
+ email: data.email,
126
+ clerkUserId: data.clerkUserId,
127
+ userName: data.userName || undefined,
128
+ status: UserStatus.REGISTERED,
129
+ },
130
+ });
131
+ }
132
+
133
+ async unregister(userId: string, tx?: Prisma.TransactionClient): Promise<User> {
134
+ const client = checkAndFallbackWithNonTCClient(tx);
135
+
136
+ return await client.user.update({
137
+ where: { userId },
138
+ data: {
139
+ status: UserStatus.DELETED,
140
+ },
141
+ });
142
+ }
143
+
144
+ // Get user list
145
+ async listUsers(params: {
146
+ skip?: number;
147
+ take?: number;
148
+ status?: string;
149
+ orderBy?: Prisma.UserOrderByWithRelationInput;
150
+ }, tx?: Prisma.TransactionClient): Promise<{ users: User[]; total: number }> {
151
+ const client = checkAndFallbackWithNonTCClient(tx);
152
+ const { skip = 0, take = 10, status, orderBy = { createdAt: 'desc' } } = params;
153
+
154
+ const where: Prisma.UserWhereInput = status ? { status } : {};
155
+
156
+ const [users, total] = await Promise.all([
157
+ client.user.findMany({
158
+ where,
159
+ skip,
160
+ take,
161
+ orderBy,
162
+ }),
163
+ client.user.count({ where }),
164
+ ]);
165
+
166
+ return { users, total };
167
+ }
168
+
169
+ // 批量创建匿名用户
170
+ async createBatchAnonymousUsers(
171
+ fingerprintIds: string[],
172
+ tx?: Prisma.TransactionClient
173
+ ): Promise<number> {
174
+ const client = checkAndFallbackWithNonTCClient(tx);
175
+ const data = fingerprintIds.map((fingerprintId) => ({
176
+ fingerprintId,
177
+ status: UserStatus.ANONYMOUS,
178
+ }));
179
+
180
+ const result = await client.user.createMany({
181
+ data,
182
+ skipDuplicates: true,
183
+ });
184
+
185
+ return result.count;
186
+ }
187
+
188
+ // Check if user exists
189
+ async exists(userId: string, tx?: Prisma.TransactionClient): Promise<boolean> {
190
+ const client = checkAndFallbackWithNonTCClient(tx);
191
+ const count = await client.user.count({
192
+ where: { userId },
193
+ });
194
+ return count > 0;
195
+ }
196
+
197
+ // Get user statistics
198
+ async getUserStats(tx?: Prisma.TransactionClient): Promise<{
199
+ total: number;
200
+ anonymous: number;
201
+ registered: number;
202
+ frozen: number;
203
+ deleted: number;
204
+ }> {
205
+ const client = checkAndFallbackWithNonTCClient(tx);
206
+ const [total, anonymous, registered, frozen, deleted] = await Promise.all([
207
+ client.user.count(),
208
+ client.user.count({ where: { status: UserStatus.ANONYMOUS } }),
209
+ client.user.count({ where: { status: UserStatus.REGISTERED } }),
210
+ client.user.count({ where: { status: UserStatus.FROZEN } }),
211
+ client.user.count({ where: { status: UserStatus.DELETED } }),
212
+ ]);
213
+
214
+ return { total, anonymous, registered, frozen, deleted };
215
+ }
216
+ }
217
+
218
+ export const userService = new UserService();