@windrun-huaiin/backend-core 29.0.3 → 31.0.0
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/README.md +95 -0
- package/dist/app/api/user/anonymous/init/route.d.ts +1 -1
- package/dist/app/api/user/anonymous/init/route.d.ts.map +1 -1
- package/dist/app/api/user/anonymous/init/route.js +18 -19
- package/dist/app/api/user/anonymous/init/route.mjs +18 -19
- package/dist/app/api/webhook/clerk/user/route.js +16 -16
- package/dist/app/api/webhook/clerk/user/route.mjs +16 -16
- package/dist/auth/auth-utils.d.ts +8 -23
- package/dist/auth/auth-utils.d.ts.map +1 -1
- package/dist/auth/auth-utils.js +8 -20
- package/dist/auth/auth-utils.mjs +8 -20
- package/dist/lib/money-price-config.d.ts +28 -28
- package/dist/lib/money-price-config.js +31 -31
- package/dist/lib/money-price-config.mjs +31 -31
- package/dist/lib/stripe-config.js +3 -3
- package/dist/lib/stripe-config.mjs +3 -3
- package/dist/prisma/prisma-transaction-util.js +1 -1
- package/dist/prisma/prisma-transaction-util.mjs +1 -1
- package/dist/prisma/prisma.d.ts.map +1 -1
- package/dist/prisma/prisma.js +18 -19
- package/dist/prisma/prisma.mjs +18 -19
- package/dist/services/aggregate/billing.aggregate.service.js +6 -6
- package/dist/services/aggregate/billing.aggregate.service.mjs +6 -6
- package/dist/services/aggregate/user.aggregate.service.d.ts +9 -9
- package/dist/services/aggregate/user.aggregate.service.js +16 -16
- package/dist/services/aggregate/user.aggregate.service.mjs +16 -16
- package/dist/services/database/constants.js +34 -34
- package/dist/services/database/constants.mjs +34 -34
- package/dist/services/database/credit.service.js +2 -2
- package/dist/services/database/credit.service.mjs +2 -2
- package/dist/services/database/transaction.service.js +1 -1
- package/dist/services/database/transaction.service.mjs +1 -1
- package/dist/services/database/user.service.js +2 -2
- package/dist/services/database/user.service.mjs +2 -2
- package/dist/services/stripe/webhook-handler.js +5 -5
- package/dist/services/stripe/webhook-handler.mjs +5 -5
- package/package.json +13 -6
- package/src/app/api/user/anonymous/init/route.ts +21 -22
- package/src/app/api/webhook/clerk/user/route.ts +17 -17
- package/src/auth/auth-utils.ts +8 -23
- package/src/lib/money-price-config.ts +31 -32
- package/src/lib/stripe-config.ts +3 -3
- package/src/prisma/prisma-transaction-util.ts +1 -1
- package/src/prisma/prisma.ts +18 -19
- package/src/services/aggregate/billing.aggregate.service.ts +7 -7
- package/src/services/aggregate/user.aggregate.service.ts +16 -16
- package/src/services/database/constants.ts +34 -34
- package/src/services/database/credit.service.ts +2 -2
- package/src/services/database/transaction.service.ts +1 -1
- package/src/services/database/user.service.ts +2 -2
- package/src/services/stripe/webhook-handler.ts +5 -5
package/src/lib/stripe-config.ts
CHANGED
|
@@ -97,7 +97,7 @@ export const createCheckoutSession = async (
|
|
|
97
97
|
|
|
98
98
|
// One-time payment specific configuration
|
|
99
99
|
if (isSubscriptionMode) {
|
|
100
|
-
//
|
|
100
|
+
// Attach order metadata for later event matching. Stripe only allows this in subscription mode.
|
|
101
101
|
sessionParams.subscription_data = subscriptionData;
|
|
102
102
|
} else {
|
|
103
103
|
// One-time payments don't create invoices
|
|
@@ -129,7 +129,7 @@ export const createCheckoutSession = async (
|
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
-
//
|
|
132
|
+
// Resolve the payment ID from an invoice ID.
|
|
133
133
|
export const fetchPaymentId = async (invoiceId: string ): Promise<string> => {
|
|
134
134
|
const fullInvoice = await getStripe().invoices.retrieve(invoiceId, {
|
|
135
135
|
expand: ['payments']
|
|
@@ -193,7 +193,7 @@ export const createOrGetCustomer = async (params: {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
//
|
|
196
|
+
// Create a new customer.
|
|
197
197
|
const customerParams: Stripe.CustomerCreateParams = {
|
|
198
198
|
metadata: {
|
|
199
199
|
user_id: userId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getBackendCorePrisma } from './prisma';
|
|
2
2
|
import type { Prisma } from '@core/db/prisma-model-type';
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// Transaction helper that logs rollback details on failure.
|
|
5
5
|
export async function runInTransaction<T>(
|
|
6
6
|
fn: (tx: Prisma.TransactionClient) => Promise<T>,
|
|
7
7
|
operationName?: string
|
package/src/prisma/prisma.ts
CHANGED
|
@@ -41,7 +41,7 @@ const globalForPrisma = globalThis as unknown as {
|
|
|
41
41
|
__prisma_ssl_warning_logged?: boolean;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
// ====================
|
|
44
|
+
// ==================== Logging Configuration ====================
|
|
45
45
|
const getLogConfig = () => {
|
|
46
46
|
if (process.env.PRISMA_DEBUG === 'true') {
|
|
47
47
|
return [
|
|
@@ -125,42 +125,41 @@ function registerDevelopmentQueryLogger(prismaClient: BackendCoreHostPrismaClien
|
|
|
125
125
|
globalForPrisma[ID_KEY] = listenerId;
|
|
126
126
|
console.log(`Prisma Query Logger Registered | Listener ID: ${listenerId} | Instance ID: ${instanceId}`);
|
|
127
127
|
|
|
128
|
-
// ---
|
|
128
|
+
// --- Custom SQL interpolation ---
|
|
129
129
|
const interpolate = (query: string, params: string) => {
|
|
130
|
-
// 1.
|
|
130
|
+
// 1. Validate and parse parameters safely.
|
|
131
131
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
132
132
|
let parameters: any[] = [];
|
|
133
133
|
try {
|
|
134
|
-
//
|
|
135
|
-
// 如果 params 是空字符串 "",或者不是有效的 JSON,这里会捕获错误
|
|
134
|
+
// Parse the params string. Empty strings or invalid JSON are handled by the catch block.
|
|
136
135
|
parameters = params && params.length > 0 ? JSON.parse(params) : [];
|
|
137
136
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
138
137
|
} catch (e) {
|
|
139
|
-
//
|
|
138
|
+
// If parsing fails, return the original query without interpolation.
|
|
140
139
|
return query;
|
|
141
140
|
}
|
|
142
141
|
|
|
143
|
-
//
|
|
142
|
+
// Ensure parameters is an array.
|
|
144
143
|
if (!Array.isArray(parameters)) {
|
|
145
|
-
console.warn('Prisma params
|
|
144
|
+
console.warn('Prisma params did not parse to an array; skipping parameter interpolation. Result:', parameters);
|
|
146
145
|
return query;
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
//
|
|
148
|
+
// If there are no parameters, return the query as-is.
|
|
150
149
|
if (parameters.length === 0) {
|
|
151
150
|
return query;
|
|
152
151
|
}
|
|
153
152
|
|
|
154
|
-
// 2.
|
|
153
|
+
// 2. Safely stringify parameter values.
|
|
155
154
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
155
|
const safeValues = parameters.map((p: any) => {
|
|
157
156
|
if (p === null) return 'NULL';
|
|
158
|
-
//
|
|
157
|
+
// Quote and escape string values for readable SQL logging.
|
|
159
158
|
if (typeof p === 'string') return `'${p.replace(/'/g, "''")}'`;
|
|
160
|
-
return p; //
|
|
159
|
+
return p; // Numbers, booleans, and similar values can be returned directly.
|
|
161
160
|
});
|
|
162
161
|
|
|
163
|
-
// 3.
|
|
162
|
+
// 3. Replace $1, $2, ... placeholders.
|
|
164
163
|
let sql = query;
|
|
165
164
|
for (let i = 0; i < safeValues.length; i++) {
|
|
166
165
|
const placeholder = new RegExp('\\$' + (i + 1) + '(?!\\d)', 'g');
|
|
@@ -176,23 +175,23 @@ function registerDevelopmentQueryLogger(prismaClient: BackendCoreHostPrismaClien
|
|
|
176
175
|
const interpolatedSql = interpolate(event.query, event.params);
|
|
177
176
|
|
|
178
177
|
const clean = interpolatedSql
|
|
179
|
-
.replace(/"[^"]+"\./g, '') //
|
|
180
|
-
.replace(/= '([^']+)'/g, `= '$1'`) //
|
|
181
|
-
.replace(/"/g, ''); //
|
|
178
|
+
.replace(/"[^"]+"\./g, '') // Remove "table". prefixes.
|
|
179
|
+
.replace(/= '([^']+)'/g, `= '$1'`) // Keep normalized quoted values.
|
|
180
|
+
.replace(/"/g, ''); // Remove remaining double quotes.
|
|
182
181
|
|
|
183
182
|
console.log('─'.repeat(60));
|
|
184
183
|
console.log(`Prisma Instance ID: ${instanceId} | Listener ID: ${listenerId}`);
|
|
185
184
|
console.log(`${clean};`);
|
|
186
|
-
console.log(
|
|
185
|
+
console.log(`Duration: ${ms}ms, ${slow}`);
|
|
187
186
|
};
|
|
188
|
-
//
|
|
187
|
+
// Register the wrapped handler.
|
|
189
188
|
prismaClient.$on?.('query' as never, wrappedHandler);
|
|
190
189
|
|
|
191
190
|
globalForPrisma[REGISTERED_KEY] = true;
|
|
192
191
|
}
|
|
193
192
|
}
|
|
194
193
|
|
|
195
|
-
// ====================
|
|
194
|
+
// ==================== Client Helper: fall back to the global non-transaction client when no transaction client is provided ====================
|
|
196
195
|
export function checkAndFallbackWithNonTCClient(tx?: Prisma.TransactionClient): Prisma.TransactionClient | BackendCorePrismaClient {
|
|
197
196
|
return tx ?? getBackendCorePrisma();
|
|
198
197
|
}
|
|
@@ -35,7 +35,7 @@ type BasicOrderContext = {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
type RenewalOrderContext = BasicOrderContext & {
|
|
38
|
-
//
|
|
38
|
+
// Use Stripe invoice price as the source of truth for renewal
|
|
39
39
|
amountPaidCents: number;
|
|
40
40
|
currency: string;
|
|
41
41
|
}
|
|
@@ -64,7 +64,7 @@ class BillingAggregateService {
|
|
|
64
64
|
): Promise<void> {
|
|
65
65
|
await runInTransaction(async (tx) => {
|
|
66
66
|
const now = new Date();
|
|
67
|
-
|
|
67
|
+
// Set subscription expiry to the last second of the expiration date
|
|
68
68
|
const originSubPeriodEnd = context.periodEnd;
|
|
69
69
|
const specialEnd = originSubPeriodEnd ? new Date(originSubPeriodEnd.setHours(23, 59, 59, 999)) : originSubPeriodEnd;
|
|
70
70
|
const updatedSubscription = await subscriptionService.updateSubscription(
|
|
@@ -237,7 +237,7 @@ class BillingAggregateService {
|
|
|
237
237
|
},
|
|
238
238
|
tx
|
|
239
239
|
);
|
|
240
|
-
//
|
|
240
|
+
// Set subscription expiry to the last second of the expiration date
|
|
241
241
|
const originSubPeriodEnd = context.periodEnd;
|
|
242
242
|
const specialEnd = originSubPeriodEnd ? new Date(originSubPeriodEnd.setHours(23, 59, 59, 999)) : originSubPeriodEnd;
|
|
243
243
|
|
|
@@ -365,7 +365,7 @@ class BillingAggregateService {
|
|
|
365
365
|
): Promise<void> {
|
|
366
366
|
await runInTransaction(async (tx) => {
|
|
367
367
|
if (params.isUserCancel) {
|
|
368
|
-
//
|
|
368
|
+
// Record the time when the user unsubscribes
|
|
369
369
|
await transactionService.update(
|
|
370
370
|
params.orderId,
|
|
371
371
|
{
|
|
@@ -391,7 +391,7 @@ class BillingAggregateService {
|
|
|
391
391
|
context: SubscriptionCancelContext
|
|
392
392
|
): Promise<void> {
|
|
393
393
|
await runInTransaction(async (tx) => {
|
|
394
|
-
//
|
|
394
|
+
// Update the order and log cancellation details
|
|
395
395
|
await transactionService.update(
|
|
396
396
|
context.orderId,
|
|
397
397
|
{
|
|
@@ -400,10 +400,10 @@ class BillingAggregateService {
|
|
|
400
400
|
},
|
|
401
401
|
tx
|
|
402
402
|
)
|
|
403
|
-
//
|
|
403
|
+
// Update subscription information
|
|
404
404
|
await subscriptionService.updateStatus(context.subIdKey, SubscriptionStatus.CANCELED, tx);
|
|
405
405
|
|
|
406
|
-
//
|
|
406
|
+
// Clear credits and keep audit trail
|
|
407
407
|
await creditService.purgePaidCredit(context.userId, 'cancel_subscription_purge', context.orderId, tx);
|
|
408
408
|
})
|
|
409
409
|
}
|
|
@@ -40,17 +40,17 @@ export class UserAggregateService {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* Create a new registered user
|
|
44
44
|
*
|
|
45
|
-
*
|
|
46
|
-
* 1.
|
|
47
|
-
* 2.
|
|
48
|
-
* 3.
|
|
49
|
-
* 4.
|
|
45
|
+
* Initialization steps (parallel to credit):
|
|
46
|
+
* 1. Create User record
|
|
47
|
+
* 2. Initialize Credit record (free credits)
|
|
48
|
+
* 3. Initialize Subscription record (placeholder, status=INCOMPLETE)
|
|
49
|
+
* 4. Record CreditUsage (audit)
|
|
50
50
|
*
|
|
51
|
-
*
|
|
52
|
-
* - session.completed
|
|
53
|
-
* -
|
|
51
|
+
* When the user subscribes via Stripe later:
|
|
52
|
+
* - session.completed or invoice.paid will UPDATE the subscription record
|
|
53
|
+
* - No CREATE needed, only UPDATE to ensure logical consistency
|
|
54
54
|
*/
|
|
55
55
|
async createNewRegisteredUser(
|
|
56
56
|
clerkUserId: string,
|
|
@@ -89,7 +89,7 @@ export class UserAggregateService {
|
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
//
|
|
92
|
+
// Note: Handle credit review logs
|
|
93
93
|
async upgradeToRegistered(
|
|
94
94
|
userId: string,
|
|
95
95
|
email: string,
|
|
@@ -107,9 +107,9 @@ export class UserAggregateService {
|
|
|
107
107
|
tx
|
|
108
108
|
);
|
|
109
109
|
|
|
110
|
-
//
|
|
110
|
+
// Clear anonymous credits first and audit for traceability
|
|
111
111
|
await creditService.purgeFreeCredit(userId, 'user_registered_purge', userId, tx);
|
|
112
|
-
//
|
|
112
|
+
// Then initialize free credits upon successful registration
|
|
113
113
|
const credit = await creditService.initializeCreditWithFree(
|
|
114
114
|
{
|
|
115
115
|
userId: updateUser.userId,
|
|
@@ -128,21 +128,21 @@ export class UserAggregateService {
|
|
|
128
128
|
|
|
129
129
|
async handleUserUnregister(clerkUserId: string): Promise<string | null> {
|
|
130
130
|
return runInTransaction(async (tx) => {
|
|
131
|
-
//
|
|
131
|
+
// query DB user
|
|
132
132
|
const user = await userService.findByClerkUserId(clerkUserId, tx);
|
|
133
133
|
if (!user) {
|
|
134
134
|
console.log(`User with clerkUserId ${clerkUserId} not found`);
|
|
135
135
|
return null;
|
|
136
136
|
}
|
|
137
137
|
const userId = user.userId;
|
|
138
|
-
//
|
|
138
|
+
// Update user status and retain user info (especially FingerprintId) to prevent repeated registration abuse
|
|
139
139
|
await userService.unregister(user.userId, tx);
|
|
140
|
-
//
|
|
140
|
+
// Clear credits
|
|
141
141
|
await creditService.purgeCredit(userId, 'soft_delete_user', userId, tx);
|
|
142
142
|
|
|
143
143
|
const subscription = await subscriptionService.getActiveSubscription(userId, tx);
|
|
144
144
|
if (subscription) {
|
|
145
|
-
//
|
|
145
|
+
// Update subscription info if it exists
|
|
146
146
|
await subscriptionService.cancelSubscription(subscription.id, true, tx);
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -2,80 +2,80 @@
|
|
|
2
2
|
// Keep in sync with DB CHECK constraints
|
|
3
3
|
|
|
4
4
|
export const UserStatus = {
|
|
5
|
-
//
|
|
5
|
+
// Anonymous user
|
|
6
6
|
ANONYMOUS: 'anonymous',
|
|
7
|
-
//
|
|
7
|
+
// Registered user
|
|
8
8
|
REGISTERED: 'registered',
|
|
9
|
-
//
|
|
9
|
+
// Frozen by admin intervention
|
|
10
10
|
FROZEN: 'frozen',
|
|
11
|
-
//
|
|
11
|
+
// Soft-deleted user data that must not be reused
|
|
12
12
|
DELETED: 'deleted',
|
|
13
13
|
} as const;
|
|
14
14
|
|
|
15
15
|
export const SubscriptionStatus = {
|
|
16
|
-
//
|
|
16
|
+
// Initial or post-cancellation state
|
|
17
17
|
INCOMPLETE: 'incomplete',
|
|
18
|
-
//
|
|
18
|
+
// Trial subscription period
|
|
19
19
|
TRIALING: 'trialing',
|
|
20
|
-
//
|
|
20
|
+
// Active subscription
|
|
21
21
|
ACTIVE: 'active',
|
|
22
|
-
//
|
|
22
|
+
// Past-due subscription
|
|
23
23
|
PAST_DUE: 'past_due',
|
|
24
|
-
//
|
|
24
|
+
// Canceled subscription
|
|
25
25
|
CANCELED: 'canceled',
|
|
26
26
|
} as const;
|
|
27
27
|
|
|
28
28
|
export const OrderStatus = {
|
|
29
|
-
//
|
|
29
|
+
// Initial state
|
|
30
30
|
CREATED: 'created',
|
|
31
|
-
//
|
|
31
|
+
// Intermediate state, awaiting payment; may be triggered by a payment failure event
|
|
32
32
|
PENDING_UNPAID: 'pending_unpaid',
|
|
33
|
-
//
|
|
33
|
+
// Intermediate or final state; payment succeeded and may later become refunded or canceled
|
|
34
34
|
SUCCESS: 'success',
|
|
35
|
-
//
|
|
35
|
+
// Intermediate or final state; checkout or payment failed and may later become refunded or canceled
|
|
36
36
|
FAILED: 'failed',
|
|
37
|
-
//
|
|
37
|
+
// Final state, refunded
|
|
38
38
|
REFUNDED: 'refunded',
|
|
39
|
-
//
|
|
39
|
+
// Final state, canceled
|
|
40
40
|
CANCELED: 'canceled',
|
|
41
41
|
} as const;
|
|
42
42
|
|
|
43
43
|
export const TransactionType = {
|
|
44
|
-
//
|
|
44
|
+
// Subscription order
|
|
45
45
|
SUBSCRIPTION: 'subscription',
|
|
46
|
-
//
|
|
46
|
+
// One-time payment order
|
|
47
47
|
ONE_TIME: 'one_time',
|
|
48
48
|
} as const;
|
|
49
49
|
|
|
50
50
|
export const CreditType = {
|
|
51
|
-
//
|
|
51
|
+
// Subscription credits
|
|
52
52
|
PAID: 'paid',
|
|
53
|
-
//
|
|
53
|
+
// One-time paid credits
|
|
54
54
|
ONE_TIME_PAID: 'one_time_paid',
|
|
55
|
-
//
|
|
55
|
+
// Free credits
|
|
56
56
|
FREE: 'free',
|
|
57
57
|
} as const;
|
|
58
58
|
|
|
59
59
|
export const OperationType = {
|
|
60
|
-
//
|
|
60
|
+
// System-granted credits
|
|
61
61
|
SYS_GIFT: 'system_gift',
|
|
62
|
-
//
|
|
62
|
+
// User credit consumption
|
|
63
63
|
CONSUME: 'consume',
|
|
64
|
-
//
|
|
64
|
+
// User credit recharge
|
|
65
65
|
RECHARGE: 'recharge',
|
|
66
|
-
//
|
|
66
|
+
// Admin credit freeze
|
|
67
67
|
FREEZE: 'freeze',
|
|
68
|
-
//
|
|
68
|
+
// Admin credit unfreeze
|
|
69
69
|
UNFREEZE: 'unfreeze',
|
|
70
|
-
//
|
|
70
|
+
// Admin credit increase
|
|
71
71
|
ADJUST_INCREASE: 'adjust_increase',
|
|
72
|
-
//
|
|
72
|
+
// Admin credit decrease
|
|
73
73
|
ADJUST_DECREASE: 'adjust_decrease',
|
|
74
|
-
//
|
|
74
|
+
// Credit purge triggered by an event or expiration
|
|
75
75
|
PURGE: 'purge',
|
|
76
76
|
} as const;
|
|
77
77
|
|
|
78
|
-
//
|
|
78
|
+
// Payment provider types
|
|
79
79
|
export const PaySupplier = {
|
|
80
80
|
STRIPE: 'Stripe',
|
|
81
81
|
APPLE: 'Apple',
|
|
@@ -83,18 +83,18 @@ export const PaySupplier = {
|
|
|
83
83
|
} as const;
|
|
84
84
|
|
|
85
85
|
export const BillingReason = {
|
|
86
|
-
//
|
|
86
|
+
// Initial subscription
|
|
87
87
|
SUBSCRIPTION_CREATE: 'subscription_create',
|
|
88
|
-
//
|
|
88
|
+
// Subscription renewal
|
|
89
89
|
SUBSCRIPTION_CYCLE: 'subscription_cycle',
|
|
90
90
|
} as const;
|
|
91
91
|
|
|
92
92
|
export const PaymentStatus = {
|
|
93
|
-
//
|
|
93
|
+
// Paid
|
|
94
94
|
PAID: 'paid',
|
|
95
|
-
//
|
|
95
|
+
// Pending payment
|
|
96
96
|
UN_PAID: 'un_paid',
|
|
97
|
-
//
|
|
97
|
+
// No payment required
|
|
98
98
|
NO_PAYMENT_REQUIRED: 'no_payment_required',
|
|
99
99
|
} as const;
|
|
100
100
|
|
|
@@ -300,7 +300,7 @@ export class CreditService {
|
|
|
300
300
|
this.ensureNonNegative(normalized, 'initializeCredit');
|
|
301
301
|
const client = checkAndFallbackWithNonTCClient(tx);
|
|
302
302
|
|
|
303
|
-
//
|
|
303
|
+
// Use upsert semantics to share initialization logic for anonymous users and anonymous-to-registered upgrades.
|
|
304
304
|
const credit = await client.credit.upsert({
|
|
305
305
|
where: {
|
|
306
306
|
userId: init.userId
|
|
@@ -636,7 +636,7 @@ export class CreditService {
|
|
|
636
636
|
data: updateData as Prisma.CreditUpdateInput,
|
|
637
637
|
});
|
|
638
638
|
|
|
639
|
-
//
|
|
639
|
+
// Always write an audit entry, even when the credit change is zero.
|
|
640
640
|
const usage = await this.recordCreditAuditLog(client, userId, OperationType.PURGE, normalizedDeduction, { feature: reason, operationReferId })
|
|
641
641
|
|
|
642
642
|
return { credit, usage };
|
|
@@ -43,7 +43,7 @@ export class TransactionService {
|
|
|
43
43
|
orderId: data.orderId,
|
|
44
44
|
orderStatus: data.orderStatus || OrderStatus.CREATED,
|
|
45
45
|
paymentStatus: data.paymentStatus || PaymentStatus.UN_PAID,
|
|
46
|
-
orderExpiredAt: data.orderExpiredAt || new Date(Date.now() + 30 * 60 * 1000), //
|
|
46
|
+
orderExpiredAt: data.orderExpiredAt || new Date(Date.now() + 30 * 60 * 1000), // Default expiration: 30 minutes
|
|
47
47
|
paySupplier: data.paySupplier,
|
|
48
48
|
payTransactionId: data.payTransactionId,
|
|
49
49
|
paySubscriptionId: data.paySubscriptionId,
|
|
@@ -83,7 +83,7 @@ export class UserService {
|
|
|
83
83
|
async findByClerkUserId(clerkUserId: string, tx?: Prisma.TransactionClient): Promise<User | null> {
|
|
84
84
|
const client = checkAndFallbackWithNonTCClient(tx);
|
|
85
85
|
|
|
86
|
-
// DB
|
|
86
|
+
// Partial DB indexes match this status filter, so findUnique is valid here.
|
|
87
87
|
return await client.user.findUnique({
|
|
88
88
|
where: {
|
|
89
89
|
clerkUserId,
|
|
@@ -183,7 +183,7 @@ export class UserService {
|
|
|
183
183
|
return { users, total };
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
//
|
|
186
|
+
// Create anonymous users in bulk
|
|
187
187
|
async createBatchAnonymousUsers(
|
|
188
188
|
fingerprintIds: string[],
|
|
189
189
|
tx?: Prisma.TransactionClient
|
|
@@ -292,7 +292,7 @@ async function handleInvoicePaid(invoice: Stripe.Invoice) {
|
|
|
292
292
|
});
|
|
293
293
|
|
|
294
294
|
if (isInitialPayment) {
|
|
295
|
-
//
|
|
295
|
+
// check
|
|
296
296
|
const nonActiveSubscription = await subscriptionService.getNonActiveSubscription(userId);
|
|
297
297
|
if (!nonActiveSubscription) {
|
|
298
298
|
throw new Error(`Subscription status is ACTIVE for user ${userId}, forbidden to re-active!`);
|
|
@@ -324,7 +324,7 @@ async function handleInvoicePaid(invoice: Stripe.Invoice) {
|
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
if (isRenewal) {
|
|
327
|
-
//
|
|
327
|
+
// must query it's subscription db record
|
|
328
328
|
const subscription = await subscriptionService.findByPaySubscriptionId(subscriptionId);
|
|
329
329
|
if (!subscription) {
|
|
330
330
|
throw new Error(`Subscription not found for renewal: ${subscriptionId}`);
|
|
@@ -337,8 +337,8 @@ async function handleInvoicePaid(invoice: Stripe.Invoice) {
|
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
// Get credits from current price configuration (handles plan upgrades/downgrades)
|
|
340
|
-
//
|
|
341
|
-
//
|
|
340
|
+
// Prefer values from configuration; fall back to the previous cycle value if unavailable. Manual remediation will be applied later if issues occur, prioritizing functional availability
|
|
341
|
+
// No error occurs here as long as the configuration is correct!
|
|
342
342
|
const creditsForRenewal = subscription.priceId
|
|
343
343
|
? getCreditsFromPriceId(subscription.priceId)
|
|
344
344
|
: subscription.creditsAllocated;
|
|
@@ -467,7 +467,7 @@ async function handleInvoicePaymentFailed(invoice: Stripe.Invoice) {
|
|
|
467
467
|
const subscriptionId = parentDetails.subscription;
|
|
468
468
|
const subscriptionMetadata = parentDetails.metadata || {};
|
|
469
469
|
|
|
470
|
-
//
|
|
470
|
+
// PaymentIntentId
|
|
471
471
|
const paymentIntentId = await fetchPaymentId(invoice.id)
|
|
472
472
|
|
|
473
473
|
console.log('Invoice payment failed event key-info:', {
|