@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.
- package/LICENSE +21 -0
- package/dist/app/api/stripe/checkout/route.d.ts +19 -0
- package/dist/app/api/stripe/checkout/route.d.ts.map +1 -0
- package/dist/app/api/stripe/checkout/route.js +120 -0
- package/dist/app/api/stripe/checkout/route.mjs +118 -0
- package/dist/app/api/stripe/customer-portal/route.d.ts +11 -0
- package/dist/app/api/stripe/customer-portal/route.d.ts.map +1 -0
- package/dist/app/api/stripe/customer-portal/route.js +73 -0
- package/dist/app/api/stripe/customer-portal/route.mjs +71 -0
- package/dist/app/api/user/anonymous/init/route.d.ts +7 -0
- package/dist/app/api/user/anonymous/init/route.d.ts.map +1 -0
- package/dist/app/api/user/anonymous/init/route.js +210 -0
- package/dist/app/api/user/anonymous/init/route.mjs +208 -0
- package/dist/app/api/webhook/clerk/user/route.d.ts +7 -0
- package/dist/app/api/webhook/clerk/user/route.d.ts.map +1 -0
- package/dist/app/api/webhook/clerk/user/route.js +202 -0
- package/dist/app/api/webhook/clerk/user/route.mjs +200 -0
- package/dist/app/api/webhook/stripe/route.d.ts +8 -0
- package/dist/app/api/webhook/stripe/route.d.ts.map +1 -0
- package/dist/app/api/webhook/stripe/route.js +70 -0
- package/dist/app/api/webhook/stripe/route.mjs +67 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +83 -0
- package/dist/index.mjs +18 -0
- package/dist/lib/auth-utils.d.ts +46 -0
- package/dist/lib/auth-utils.d.ts.map +1 -0
- package/dist/lib/auth-utils.js +107 -0
- package/dist/lib/auth-utils.mjs +102 -0
- package/dist/lib/credit-init.d.ts +8 -0
- package/dist/lib/credit-init.d.ts.map +1 -0
- package/dist/lib/credit-init.js +16 -0
- package/dist/lib/credit-init.mjs +10 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +31 -0
- package/dist/lib/index.mjs +4 -0
- package/dist/lib/money-price-config.d.ts +51 -0
- package/dist/lib/money-price-config.d.ts.map +1 -0
- package/dist/lib/money-price-config.js +156 -0
- package/dist/lib/money-price-config.mjs +151 -0
- package/dist/lib/stripe-config.d.ts +31 -0
- package/dist/lib/stripe-config.d.ts.map +1 -0
- package/dist/lib/stripe-config.js +278 -0
- package/dist/lib/stripe-config.mjs +268 -0
- 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
- 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
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/errors.js +54 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/errors.mjs +51 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/iso.js +44 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/iso.mjs +35 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/parse.js +31 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/parse.mjs +18 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/schemas.js +587 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/schemas.mjs +527 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/api.js +447 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/api.mjs +399 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/checks.js +245 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/checks.mjs +232 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/core.js +68 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/core.mjs +62 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/doc.js +39 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/doc.mjs +37 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/errors.js +80 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/errors.mjs +75 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/parse.js +101 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/parse.mjs +86 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/regexes.js +102 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/regexes.mjs +76 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/registries.js +56 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/registries.mjs +52 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/schemas.js +1205 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/schemas.mjs +1157 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/util.js +407 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/util.mjs +374 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/versions.js +9 -0
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/versions.mjs +7 -0
- package/dist/prisma/client.d.ts +2 -0
- package/dist/prisma/client.d.ts.map +1 -0
- package/dist/prisma/client.js +12 -0
- package/dist/prisma/client.mjs +1 -0
- package/dist/prisma/index.d.ts +4 -0
- package/dist/prisma/index.d.ts.map +1 -0
- package/dist/prisma/index.js +10 -0
- package/dist/prisma/index.mjs +2 -0
- package/dist/prisma/prisma-transaction-util.d.ts +3 -0
- package/dist/prisma/prisma-transaction-util.d.ts.map +1 -0
- package/dist/prisma/prisma-transaction-util.js +29 -0
- package/dist/prisma/prisma-transaction-util.mjs +27 -0
- package/dist/prisma/prisma.d.ts +4 -0
- package/dist/prisma/prisma.d.ts.map +1 -0
- package/dist/prisma/prisma.js +109 -0
- package/dist/prisma/prisma.mjs +106 -0
- package/dist/services/aggregate/billing.aggregate.service.d.ts +83 -0
- package/dist/services/aggregate/billing.aggregate.service.d.ts.map +1 -0
- package/dist/services/aggregate/billing.aggregate.service.js +308 -0
- package/dist/services/aggregate/billing.aggregate.service.mjs +306 -0
- package/dist/services/aggregate/index.d.ts +3 -0
- package/dist/services/aggregate/index.d.ts.map +1 -0
- package/dist/services/aggregate/index.js +9 -0
- package/dist/services/aggregate/index.mjs +2 -0
- package/dist/services/aggregate/user.aggregate.service.d.ts +34 -0
- package/dist/services/aggregate/user.aggregate.service.d.ts.map +1 -0
- package/dist/services/aggregate/user.aggregate.service.js +136 -0
- package/dist/services/aggregate/user.aggregate.service.mjs +133 -0
- package/dist/services/context/index.d.ts +2 -0
- package/dist/services/context/index.d.ts.map +1 -0
- package/dist/services/context/index.js +13 -0
- package/dist/services/context/index.mjs +1 -0
- package/dist/services/context/user-context-service.d.ts +30 -0
- package/dist/services/context/user-context-service.d.ts.map +1 -0
- package/dist/services/context/user-context-service.js +170 -0
- package/dist/services/context/user-context-service.mjs +162 -0
- package/dist/services/database/apilog.service.d.ts +39 -0
- package/dist/services/database/apilog.service.d.ts.map +1 -0
- package/dist/services/database/apilog.service.js +174 -0
- package/dist/services/database/apilog.service.mjs +170 -0
- package/dist/services/database/constants.d.ts +73 -0
- package/dist/services/database/constants.d.ts.map +1 -0
- package/dist/services/database/constants.js +135 -0
- package/dist/services/database/constants.mjs +117 -0
- package/dist/services/database/credit.service.d.ts +107 -0
- package/dist/services/database/credit.service.d.ts.map +1 -0
- package/dist/services/database/credit.service.js +515 -0
- package/dist/services/database/credit.service.mjs +512 -0
- package/dist/services/database/creditAuditLog.service.d.ts +73 -0
- package/dist/services/database/creditAuditLog.service.d.ts.map +1 -0
- package/dist/services/database/creditAuditLog.service.js +305 -0
- package/dist/services/database/creditAuditLog.service.mjs +302 -0
- package/dist/services/database/index.d.ts +10 -0
- package/dist/services/database/index.d.ts.map +1 -0
- package/dist/services/database/index.js +38 -0
- package/dist/services/database/index.mjs +8 -0
- package/dist/services/database/prisma-model-type.d.ts +3 -0
- package/dist/services/database/prisma-model-type.d.ts.map +1 -0
- package/dist/services/database/subscription.service.d.ts +48 -0
- package/dist/services/database/subscription.service.d.ts.map +1 -0
- package/dist/services/database/subscription.service.js +267 -0
- package/dist/services/database/subscription.service.mjs +264 -0
- package/dist/services/database/transaction.service.d.ts +92 -0
- package/dist/services/database/transaction.service.d.ts.map +1 -0
- package/dist/services/database/transaction.service.js +326 -0
- package/dist/services/database/transaction.service.mjs +323 -0
- package/dist/services/database/user.service.d.ts +45 -0
- package/dist/services/database/user.service.d.ts.map +1 -0
- package/dist/services/database/user.service.js +180 -0
- package/dist/services/database/user.service.mjs +177 -0
- package/dist/services/database/userBackup.service.d.ts +45 -0
- package/dist/services/database/userBackup.service.d.ts.map +1 -0
- package/dist/services/database/userBackup.service.js +249 -0
- package/dist/services/database/userBackup.service.mjs +246 -0
- package/dist/services/stripe/index.d.ts +2 -0
- package/dist/services/stripe/index.d.ts.map +1 -0
- package/dist/services/stripe/index.js +7 -0
- package/dist/services/stripe/index.mjs +1 -0
- package/dist/services/stripe/webhook-handler.d.ts +6 -0
- package/dist/services/stripe/webhook-handler.d.ts.map +1 -0
- package/dist/services/stripe/webhook-handler.js +537 -0
- package/dist/services/stripe/webhook-handler.mjs +535 -0
- package/migrations/create.sql +176 -0
- package/migrations/db.init.sql +13 -0
- package/migrations/init-schema.sql +19 -0
- package/migrations/purge.sql +27 -0
- package/migrations/test-check.sql +167 -0
- package/package.json +123 -0
- package/prisma/schema.prisma +191 -0
- package/src/app/api/stripe/checkout/route.ts +145 -0
- package/src/app/api/stripe/customer-portal/route.ts +83 -0
- package/src/app/api/user/anonymous/init/route.ts +284 -0
- package/src/app/api/webhook/clerk/user/route.ts +249 -0
- package/src/app/api/webhook/stripe/route.ts +93 -0
- package/src/index.ts +6 -0
- package/src/lib/auth-utils.ts +101 -0
- package/src/lib/credit-init.ts +9 -0
- package/src/lib/index.ts +4 -0
- package/src/lib/money-price-config.ts +168 -0
- package/src/lib/stripe-config.ts +333 -0
- package/src/prisma/client.ts +2 -0
- package/src/prisma/index.ts +3 -0
- package/src/prisma/prisma-transaction-util.ts +24 -0
- package/src/prisma/prisma.ts +122 -0
- package/src/services/aggregate/billing.aggregate.service.ts +498 -0
- package/src/services/aggregate/index.ts +2 -0
- package/src/services/aggregate/user.aggregate.service.ts +168 -0
- package/src/services/context/index.ts +1 -0
- package/src/services/context/user-context-service.ts +200 -0
- package/src/services/database/apilog.service.ts +185 -0
- package/src/services/database/constants.ts +148 -0
- package/src/services/database/credit.service.ts +747 -0
- package/src/services/database/creditAuditLog.service.ts +402 -0
- package/src/services/database/index.ts +41 -0
- package/src/services/database/prisma-model-type.ts +13 -0
- package/src/services/database/subscription.service.ts +319 -0
- package/src/services/database/transaction.service.ts +447 -0
- package/src/services/database/user.service.ts +218 -0
- package/src/services/database/userBackup.service.ts +290 -0
- package/src/services/stripe/index.ts +1 -0
- 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,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
|
+
}
|