@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,333 @@
1
+ import Stripe from 'stripe';
2
+ import { Apilogger, userService, subscriptionService } from '../services/database/index';
3
+
4
+ // Stripe Configuration
5
+ export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
6
+ apiVersion: '2025-10-29.clover',
7
+ });
8
+
9
+ // Helper function to validate webhook signature
10
+ export const validateStripeWebhook = (
11
+ payload: string | Buffer,
12
+ signature: string,
13
+ secret: string
14
+ ): Stripe.Event => {
15
+ return stripe.webhooks.constructEvent(payload, signature, secret);
16
+ };
17
+
18
+ export interface BasicCheckoutSessionParams {
19
+ priceId: string;
20
+ customerId?: string;
21
+ clientReferenceId: string; // user_id
22
+ successUrl: string;
23
+ cancelUrl: string;
24
+ metadata?: Record<string, string>;
25
+ // ✅ New: Auto-determine mode based on interval
26
+ interval?: string; // 'month' | 'year' | 'onetime' | undefined
27
+ // ✅ New: Subscription metadata for webhook processing
28
+
29
+ }
30
+
31
+ // Helper function to create checkout session
32
+ export const createCheckoutSession = async (
33
+ params: BasicCheckoutSessionParams,
34
+ subscriptionData?: Stripe.Checkout.SessionCreateParams.SubscriptionData
35
+ ): Promise<Stripe.Checkout.Session> => {
36
+ const {
37
+ priceId,
38
+ customerId,
39
+ clientReferenceId,
40
+ successUrl,
41
+ cancelUrl,
42
+ metadata,
43
+ interval
44
+ } = params;
45
+
46
+ // ✅ Dynamic mode determination: subscription if interval is not 'onetime'
47
+ const mode: 'subscription' | 'payment' = interval && interval !== 'onetime' ? 'subscription' : 'payment';
48
+ const isSubscriptionMode = mode === 'subscription';
49
+
50
+ if (isSubscriptionMode) {
51
+ if (!subscriptionData) {
52
+ throw new Error('Subscription data is required for subscription mode');
53
+ }
54
+ const activeSubscription = await subscriptionService.getActiveSubscription(clientReferenceId);
55
+ if (activeSubscription) {
56
+ throw new ActiveSubscriptionExistsError();
57
+ }
58
+ }
59
+
60
+ const sessionParams: Stripe.Checkout.SessionCreateParams = {
61
+ mode, // ✅ Dynamic mode
62
+ payment_method_types: ['card'],
63
+ line_items: [
64
+ {
65
+ price: priceId,
66
+ quantity: 1,
67
+ },
68
+ ],
69
+ success_url: successUrl,
70
+ cancel_url: cancelUrl,
71
+ client_reference_id: clientReferenceId,
72
+ metadata: {
73
+ ...metadata,
74
+ mode, // Record mode for webhook processing
75
+ },
76
+ };
77
+
78
+ // Add customer if provided
79
+ if (customerId) {
80
+ sessionParams.customer = customerId;
81
+ }
82
+
83
+ // One-time payment specific configuration
84
+ if (isSubscriptionMode) {
85
+ // 在这里注入订单元数据,以保证后续事件处理能根据订单去匹配处理,只能在订阅模式里设置数据,否则Stripe报错
86
+ sessionParams.subscription_data = subscriptionData;
87
+ } else {
88
+ // One-time payments don't create invoices
89
+ sessionParams.invoice_creation = {
90
+ enabled: false,
91
+ };
92
+ }
93
+
94
+ // Create log record with request
95
+ const logId = await Apilogger.logStripeOutgoing('createCheckoutSession', params);
96
+
97
+ try {
98
+ const session = await stripe.checkout.sessions.create(sessionParams);
99
+
100
+ // Update log record with response
101
+ Apilogger.updateResponse(logId, {
102
+ session_id: session.id,
103
+ url: session.url,
104
+ mode: session.mode
105
+ });
106
+
107
+ return session;
108
+ } catch (error) {
109
+ // Update log record with error
110
+ Apilogger.updateResponse(logId, {
111
+ error: error instanceof Error ? error.message : 'Unknown error'
112
+ });
113
+ throw error;
114
+ }
115
+ };
116
+
117
+ // 根据发票ID去查支付ID
118
+ export const fetchPaymentId = async (invoiceId: string ): Promise<string> => {
119
+ const fullInvoice = await stripe.invoices.retrieve(invoiceId, {
120
+ expand: ['payments']
121
+ });
122
+ const payment = fullInvoice.payments?.data[0];
123
+ const paymentIntentInfo = payment?.payment?.payment_intent;
124
+ const paymentIntentId = typeof paymentIntentInfo === 'string' ? paymentIntentInfo : (paymentIntentInfo as Stripe.PaymentIntent)?.id;
125
+ return paymentIntentId;
126
+ }
127
+
128
+ // Helper function to create or retrieve customer
129
+ export const createOrGetCustomer = async (params: {
130
+ userId: string;
131
+ }): Promise<string> => {
132
+ const { userId } = params;
133
+
134
+ const user = await userService.findByUserId(userId);
135
+
136
+ if (!user) {
137
+ throw new Error(`User not found for userId: ${userId}`);
138
+ }
139
+
140
+ const setStripeCustomerId = async (stripeCusId: string | null) => {
141
+ try {
142
+ await userService.updateStripeCustomerId(userId, stripeCusId);
143
+ } catch (error) {
144
+ console.error('Failed to update stripe customer id', { userId, stripeCusId, error });
145
+ }
146
+ };
147
+
148
+ if (user.stripeCusId) {
149
+ try {
150
+ const customer = await stripe.customers.retrieve(user.stripeCusId);
151
+ if ('deleted' in customer) {
152
+ await setStripeCustomerId(null);
153
+ } else {
154
+ return customer.id;
155
+ }
156
+ } catch (error) {
157
+ await setStripeCustomerId(null);
158
+ console.warn('Failed to retrieve Stripe customer, fallback to lookup by email', {
159
+ userId,
160
+ stripeCusId: user.stripeCusId,
161
+ error,
162
+ });
163
+ }
164
+ }
165
+
166
+ if (user.email) {
167
+ const existingCustomers = await stripe.customers.list({
168
+ email: user.email,
169
+ limit: 1,
170
+ });
171
+
172
+ if (existingCustomers.data.length > 0) {
173
+ const stripeCustomer = existingCustomers.data[0];
174
+ if (!user.stripeCusId || user.stripeCusId !== stripeCustomer.id) {
175
+ await setStripeCustomerId(stripeCustomer.id);
176
+ }
177
+ return stripeCustomer.id;
178
+ }
179
+ }
180
+
181
+ // 创建新客户
182
+ const customerParams: Stripe.CustomerCreateParams = {
183
+ metadata: {
184
+ user_id: userId,
185
+ },
186
+ };
187
+
188
+ if (user.email) {
189
+ customerParams.email = user.email;
190
+ }
191
+ const derivedName = user.email ? user.email.split('@')[0] : undefined;
192
+ if (derivedName) {
193
+ customerParams.name = derivedName;
194
+ }
195
+
196
+ // Create log record with request
197
+ const logId = await Apilogger.logStripeOutgoing('createCustomer', {
198
+ userId,
199
+ email: customerParams.email,
200
+ name: customerParams.name,
201
+ });
202
+
203
+ try {
204
+ const customer = await stripe.customers.create(customerParams);
205
+ await setStripeCustomerId(customer.id);
206
+
207
+ // Update log record with response
208
+ Apilogger.updateResponse(logId, {
209
+ customer_id: customer.id,
210
+ email: customer.email
211
+ });
212
+
213
+ return customer.id;
214
+ } catch (error) {
215
+ // Update log record with error
216
+ Apilogger.updateResponse(logId, {
217
+ error: error instanceof Error ? error.message : 'Unknown error'
218
+ });
219
+ throw error;
220
+ }
221
+ };
222
+
223
+ // Helper function to update subscription
224
+ export const updateSubscription = async (params: {
225
+ subscriptionId: string;
226
+ priceId: string;
227
+ prorationBehavior?: 'create_prorations' | 'none' | 'always_invoice';
228
+ }): Promise<Stripe.Subscription> => {
229
+ const { subscriptionId, priceId, prorationBehavior = 'create_prorations' } = params;
230
+
231
+ const subscription = await stripe.subscriptions.retrieve(subscriptionId);
232
+
233
+ // Create log record with request
234
+ const logId = await Apilogger.logStripeOutgoing('updateSubscription', params);
235
+
236
+ try {
237
+ const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
238
+ items: [
239
+ {
240
+ id: subscription.items.data[0].id,
241
+ price: priceId,
242
+ },
243
+ ],
244
+ proration_behavior: prorationBehavior,
245
+ });
246
+
247
+ // Update log record with response
248
+ Apilogger.updateResponse(logId, {
249
+ subscription_id: updatedSubscription.id,
250
+ status: updatedSubscription.status
251
+ });
252
+
253
+ return updatedSubscription;
254
+ } catch (error) {
255
+ // Update log record with error
256
+ Apilogger.updateResponse(logId, {
257
+ error: error instanceof Error ? error.message : 'Unknown error'
258
+ });
259
+ throw error;
260
+ }
261
+ };
262
+
263
+ export const createCustomerPortalSession = async (params: {
264
+ customerId: string;
265
+ returnUrl: string;
266
+ }): Promise<Stripe.BillingPortal.Session> => {
267
+ const logId = await Apilogger.logStripeOutgoing('createCustomerPortalSession', params);
268
+
269
+ try {
270
+ const session = await stripe.billingPortal.sessions.create({
271
+ customer: params.customerId,
272
+ return_url: params.returnUrl,
273
+ });
274
+
275
+ Apilogger.updateResponse(logId, {
276
+ session_id: session.id,
277
+ url: session.url,
278
+ });
279
+
280
+ return session;
281
+ } catch (error) {
282
+ Apilogger.updateResponse(logId, {
283
+ error: error instanceof Error ? error.message : 'Unknown error'
284
+ });
285
+ throw error;
286
+ }
287
+ };
288
+
289
+ // Helper function to cancel subscription
290
+ export const cancelSubscription = async (
291
+ subscriptionId: string,
292
+ cancelAtPeriodEnd: boolean = true
293
+ ): Promise<Stripe.Subscription> => {
294
+ // Create log record with request
295
+ const logId = await Apilogger.logStripeOutgoing('cancelSubscription', {
296
+ subscriptionId,
297
+ cancelAtPeriodEnd
298
+ });
299
+
300
+ try {
301
+ let result: Stripe.Subscription;
302
+
303
+ if (cancelAtPeriodEnd) {
304
+ result = await stripe.subscriptions.update(subscriptionId, {
305
+ cancel_at_period_end: true,
306
+ });
307
+ } else {
308
+ result = await stripe.subscriptions.cancel(subscriptionId);
309
+ }
310
+
311
+ // Update log record with response
312
+ Apilogger.updateResponse(logId, {
313
+ subscription_id: result.id,
314
+ status: result.status,
315
+ cancel_at_period_end: result.cancel_at_period_end
316
+ });
317
+
318
+ return result;
319
+ } catch (error) {
320
+ // Update log record with error
321
+ Apilogger.updateResponse(logId, {
322
+ error: error instanceof Error ? error.message : 'Unknown error'
323
+ });
324
+ throw error;
325
+ }
326
+ };
327
+
328
+ export class ActiveSubscriptionExistsError extends Error {
329
+ constructor() {
330
+ super('ACTIVE_SUBSCRIPTION_EXISTS');
331
+ this.name = 'ActiveSubscriptionExistsError';
332
+ }
333
+ }
@@ -0,0 +1,2 @@
1
+ // Re-export generated Prisma client so consumers can import from "@windrun-huaiin/backend-core/prisma/client"
2
+ export * from '@prisma/client';
@@ -0,0 +1,3 @@
1
+ export { prisma, checkAndFallbackWithNonTCClient } from './prisma';
2
+ export { runInTransaction } from './prisma-transaction-util';
3
+ export type { Prisma } from '@prisma/client';
@@ -0,0 +1,24 @@
1
+ import { prisma } from './prisma';
2
+ import { Prisma } from '@prisma/client';
3
+
4
+ // 事务工具类,回滚时打印回滚日志
5
+ export async function runInTransaction<T>(
6
+ fn: (tx: Prisma.TransactionClient) => Promise<T>,
7
+ operationName?: string
8
+ ): Promise<T> {
9
+ const start = Date.now();
10
+ try {
11
+ return await prisma.$transaction(fn);
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ } catch (error: any) {
14
+ const duration = Date.now() - start;
15
+ console.error('='.repeat(60));
16
+ console.error('TRANSACTION ROLLBACK');
17
+ console.error(`Operation: ${operationName || 'unknown'}`);
18
+ console.error(`Duration: ${duration}ms`);
19
+ console.error(`Error: ${error.message}`);
20
+ if (error.code) console.error(`Code: ${error.code}`);
21
+ console.error('='.repeat(60));
22
+ throw error;
23
+ }
24
+ }
@@ -0,0 +1,122 @@
1
+ import { PrismaClient, Prisma } from '@prisma/client';
2
+
3
+ const globalForPrisma = globalThis as unknown as {
4
+ prisma?: PrismaClient;
5
+ __prisma_query_logger_registered?: boolean;
6
+ __prisma_query_logger_id?: string;
7
+ };
8
+
9
+ // ==================== 日志配置 ====================
10
+ const getLogConfig = () => {
11
+ const env = process.env.NODE_ENV || 'development';
12
+ switch (env) {
13
+ case 'development':
14
+ return [
15
+ { emit: 'event' as const, level: 'query' as const },
16
+ { emit: 'stdout' as const, level: 'info' as const },
17
+ { emit: 'stdout' as const, level: 'warn' as const },
18
+ { emit: 'stdout' as const, level: 'error' as const },
19
+ ];
20
+ case 'test':
21
+ return [
22
+ { emit: 'stdout' as const, level: 'warn' as const },
23
+ { emit: 'stdout' as const, level: 'error' as const },
24
+ ];
25
+ default:
26
+ return [{ emit: 'stdout' as const, level: 'error' as const }];
27
+ }
28
+ };
29
+
30
+ const logConfig = getLogConfig();
31
+
32
+ // ==================== 创建 Prisma 全局单例 ====================
33
+ export const prisma =
34
+ globalForPrisma.prisma ??
35
+ new PrismaClient<Prisma.PrismaClientOptions, 'query' | 'info' | 'warn' | 'error'>({
36
+ log: logConfig,
37
+ });
38
+
39
+ if (process.env.NODE_ENV !== 'production') {
40
+ globalForPrisma.prisma = prisma;
41
+ }
42
+
43
+ if (process.env.NODE_ENV === 'development') {
44
+ const REGISTERED_KEY = '__prisma_query_logger_registered';
45
+ const ID_KEY = '__prisma_query_logger_id';
46
+
47
+ if (globalForPrisma[REGISTERED_KEY]) {
48
+ console.log(`Prisma Query Logger Already Registered | ID: ${globalForPrisma[ID_KEY]}`);
49
+ } else {
50
+ const listenerId = `listener_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
51
+ globalForPrisma[ID_KEY] = listenerId;
52
+
53
+ // --- 自定义SQL拼接 ---
54
+ const interpolate = (query: string, params: string) => {
55
+ // 1. 【核心修改】:安全检查和参数解析
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ let parameters: any[] = [];
58
+ try {
59
+ // 尝试解析 params 字符串
60
+ // 如果 params 是空字符串 "",或者不是有效的 JSON,这里会捕获错误
61
+ parameters = params && params.length > 0 ? JSON.parse(params) : [];
62
+ // eslint-disable-next-line unused-imports/no-unused-vars
63
+ } catch (e) {
64
+ // 如果无法解析,则直接返回原始查询,跳过替换
65
+ return query;
66
+ }
67
+
68
+ // 确保 parameters 是一个数组
69
+ if (!Array.isArray(parameters)) {
70
+ console.warn('Prisma params解析结果不是数组,跳过参数替换。Result:', parameters);
71
+ return query;
72
+ }
73
+
74
+ // 如果没有参数,直接返回查询
75
+ if (parameters.length === 0) {
76
+ return query;
77
+ }
78
+
79
+ // 2. 将参数列表的值进行安全的字符串化处理
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ const safeValues = parameters.map((p: any) => {
82
+ if (p === null) return 'NULL';
83
+ // 对字符串类型的值加上单引号并转义(这是SQL安全的关键)
84
+ if (typeof p === 'string') return `'${p.replace(/'/g, "''")}'`;
85
+ return p; // 数字、布尔值等直接返回
86
+ });
87
+
88
+ // 3. 循环替换 $1, $2, ...
89
+ let sql = query;
90
+ for (let i = 0; i < safeValues.length; i++) {
91
+ const placeholder = new RegExp('\\$' + (i + 1) + '(?!\\d)', 'g');
92
+ sql = sql.replace(placeholder, safeValues[i]);
93
+ }
94
+ return sql;
95
+ };
96
+
97
+ const wrappedHandler = (event: Prisma.QueryEvent) => {
98
+ const ms = event.duration;
99
+ const slow = ms >= 200 ? '🐌 SLOW SQL ' : '🚀 SQL';
100
+
101
+ const interpolatedSql = interpolate(event.query, event.params);
102
+
103
+ const clean = interpolatedSql
104
+ .replace(/"[^"]+"\./g, '') // 去 "表".
105
+ .replace(/= '([^']+)'/g, `= '$1'`) // 已经替换成单引号,此处可以优化
106
+ .replace(/"/g, ''); // 彻底灭双引号
107
+
108
+ console.log('─'.repeat(60));
109
+ console.log(`${clean};`);
110
+ console.log(`⏰ 耗时: ${ms}ms, ${slow}`);
111
+ };
112
+ // 注册包装后的 handler
113
+ prisma.$on('query' as never, wrappedHandler);
114
+
115
+ globalForPrisma[REGISTERED_KEY] = true;
116
+ }
117
+ }
118
+
119
+ // ==================== 便捷方法, 入参事务客户端不存在或者不传, 就返回全局非事务客户端 ====================
120
+ export function checkAndFallbackWithNonTCClient(tx?: Prisma.TransactionClient): Prisma.TransactionClient | PrismaClient {
121
+ return tx ?? prisma;
122
+ }