@windrun-huaiin/backend-core 30.0.0 → 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/dist/prisma/prisma.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const globalForPrisma = globalThis;
|
|
2
|
-
// ====================
|
|
2
|
+
// ==================== Logging Configuration ====================
|
|
3
3
|
const getLogConfig = () => {
|
|
4
4
|
if (process.env.PRISMA_DEBUG === 'true') {
|
|
5
5
|
return [
|
|
@@ -65,41 +65,40 @@ function registerDevelopmentQueryLogger(prismaClient, instanceId) {
|
|
|
65
65
|
const listenerId = `listener_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
|
|
66
66
|
globalForPrisma[ID_KEY] = listenerId;
|
|
67
67
|
console.log(`Prisma Query Logger Registered | Listener ID: ${listenerId} | Instance ID: ${instanceId}`);
|
|
68
|
-
// ---
|
|
68
|
+
// --- Custom SQL interpolation ---
|
|
69
69
|
const interpolate = (query, params) => {
|
|
70
|
-
// 1.
|
|
70
|
+
// 1. Validate and parse parameters safely.
|
|
71
71
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
72
|
let parameters = [];
|
|
73
73
|
try {
|
|
74
|
-
//
|
|
75
|
-
// 如果 params 是空字符串 "",或者不是有效的 JSON,这里会捕获错误
|
|
74
|
+
// Parse the params string. Empty strings or invalid JSON are handled by the catch block.
|
|
76
75
|
parameters = params && params.length > 0 ? JSON.parse(params) : [];
|
|
77
76
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
78
77
|
}
|
|
79
78
|
catch (e) {
|
|
80
|
-
//
|
|
79
|
+
// If parsing fails, return the original query without interpolation.
|
|
81
80
|
return query;
|
|
82
81
|
}
|
|
83
|
-
//
|
|
82
|
+
// Ensure parameters is an array.
|
|
84
83
|
if (!Array.isArray(parameters)) {
|
|
85
|
-
console.warn('Prisma params
|
|
84
|
+
console.warn('Prisma params did not parse to an array; skipping parameter interpolation. Result:', parameters);
|
|
86
85
|
return query;
|
|
87
86
|
}
|
|
88
|
-
//
|
|
87
|
+
// If there are no parameters, return the query as-is.
|
|
89
88
|
if (parameters.length === 0) {
|
|
90
89
|
return query;
|
|
91
90
|
}
|
|
92
|
-
// 2.
|
|
91
|
+
// 2. Safely stringify parameter values.
|
|
93
92
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
94
93
|
const safeValues = parameters.map((p) => {
|
|
95
94
|
if (p === null)
|
|
96
95
|
return 'NULL';
|
|
97
|
-
//
|
|
96
|
+
// Quote and escape string values for readable SQL logging.
|
|
98
97
|
if (typeof p === 'string')
|
|
99
98
|
return `'${p.replace(/'/g, "''")}'`;
|
|
100
|
-
return p; //
|
|
99
|
+
return p; // Numbers, booleans, and similar values can be returned directly.
|
|
101
100
|
});
|
|
102
|
-
// 3.
|
|
101
|
+
// 3. Replace $1, $2, ... placeholders.
|
|
103
102
|
let sql = query;
|
|
104
103
|
for (let i = 0; i < safeValues.length; i++) {
|
|
105
104
|
const placeholder = new RegExp('\\$' + (i + 1) + '(?!\\d)', 'g');
|
|
@@ -112,20 +111,20 @@ function registerDevelopmentQueryLogger(prismaClient, instanceId) {
|
|
|
112
111
|
const slow = ms >= 200 ? '🐌 SLOW SQL ' : '🚀 SQL';
|
|
113
112
|
const interpolatedSql = interpolate(event.query, event.params);
|
|
114
113
|
const clean = interpolatedSql
|
|
115
|
-
.replace(/"[^"]+"\./g, '') //
|
|
116
|
-
.replace(/= '([^']+)'/g, `= '$1'`) //
|
|
117
|
-
.replace(/"/g, ''); //
|
|
114
|
+
.replace(/"[^"]+"\./g, '') // Remove "table". prefixes.
|
|
115
|
+
.replace(/= '([^']+)'/g, `= '$1'`) // Keep normalized quoted values.
|
|
116
|
+
.replace(/"/g, ''); // Remove remaining double quotes.
|
|
118
117
|
console.log('─'.repeat(60));
|
|
119
118
|
console.log(`Prisma Instance ID: ${instanceId} | Listener ID: ${listenerId}`);
|
|
120
119
|
console.log(`${clean};`);
|
|
121
|
-
console.log(
|
|
120
|
+
console.log(`Duration: ${ms}ms, ${slow}`);
|
|
122
121
|
};
|
|
123
|
-
//
|
|
122
|
+
// Register the wrapped handler.
|
|
124
123
|
(_a = prismaClient.$on) === null || _a === void 0 ? void 0 : _a.call(prismaClient, 'query', wrappedHandler);
|
|
125
124
|
globalForPrisma[REGISTERED_KEY] = true;
|
|
126
125
|
}
|
|
127
126
|
}
|
|
128
|
-
// ====================
|
|
127
|
+
// ==================== Client Helper: fall back to the global non-transaction client when no transaction client is provided ====================
|
|
129
128
|
function checkAndFallbackWithNonTCClient(tx) {
|
|
130
129
|
return tx !== null && tx !== void 0 ? tx : getBackendCorePrisma();
|
|
131
130
|
}
|
|
@@ -14,7 +14,7 @@ class BillingAggregateService {
|
|
|
14
14
|
yield prismaTransactionUtil.runInTransaction((tx) => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
15
15
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
16
16
|
const now = new Date();
|
|
17
|
-
//
|
|
17
|
+
// Set subscription expiry to the last second of the expiration date
|
|
18
18
|
const originSubPeriodEnd = context.periodEnd;
|
|
19
19
|
const specialEnd = originSubPeriodEnd ? new Date(originSubPeriodEnd.setHours(23, 59, 59, 999)) : originSubPeriodEnd;
|
|
20
20
|
const updatedSubscription = yield subscription_service.subscriptionService.updateSubscription(context.subIdKey, {
|
|
@@ -132,7 +132,7 @@ class BillingAggregateService {
|
|
|
132
132
|
paidEmail: context.paidEmail,
|
|
133
133
|
payUpdatedAt: new Date(),
|
|
134
134
|
}, tx);
|
|
135
|
-
//
|
|
135
|
+
// Set subscription expiry to the last second of the expiration date
|
|
136
136
|
const originSubPeriodEnd = context.periodEnd;
|
|
137
137
|
const specialEnd = originSubPeriodEnd ? new Date(originSubPeriodEnd.setHours(23, 59, 59, 999)) : originSubPeriodEnd;
|
|
138
138
|
yield subscription_service.subscriptionService.updateSubscription(context.subIdKey, {
|
|
@@ -218,7 +218,7 @@ class BillingAggregateService {
|
|
|
218
218
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
219
219
|
yield prismaTransactionUtil.runInTransaction((tx) => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
220
220
|
if (params.isUserCancel) {
|
|
221
|
-
//
|
|
221
|
+
// Record the time when the user unsubscribes
|
|
222
222
|
yield transaction_service.transactionService.update(params.orderId, {
|
|
223
223
|
subLastTryCancelAt: new Date(),
|
|
224
224
|
}, tx);
|
|
@@ -236,14 +236,14 @@ class BillingAggregateService {
|
|
|
236
236
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
237
237
|
yield prismaTransactionUtil.runInTransaction((tx) => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
238
238
|
var _a;
|
|
239
|
-
//
|
|
239
|
+
// Update the order and log cancellation details
|
|
240
240
|
yield transaction_service.transactionService.update(context.orderId, {
|
|
241
241
|
subPeriodCanceledAt: context.canceledAt,
|
|
242
242
|
subCancellationDetail: (_a = context.cancellationDetail) !== null && _a !== void 0 ? _a : undefined
|
|
243
243
|
}, tx);
|
|
244
|
-
//
|
|
244
|
+
// Update subscription information
|
|
245
245
|
yield subscription_service.subscriptionService.updateStatus(context.subIdKey, constants.SubscriptionStatus.CANCELED, tx);
|
|
246
|
-
//
|
|
246
|
+
// Clear credits and keep audit trail
|
|
247
247
|
yield credit_service.creditService.purgePaidCredit(context.userId, 'cancel_subscription_purge', context.orderId, tx);
|
|
248
248
|
}));
|
|
249
249
|
});
|
|
@@ -12,7 +12,7 @@ class BillingAggregateService {
|
|
|
12
12
|
yield runInTransaction((tx) => __awaiter(this, void 0, void 0, function* () {
|
|
13
13
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
14
14
|
const now = new Date();
|
|
15
|
-
//
|
|
15
|
+
// Set subscription expiry to the last second of the expiration date
|
|
16
16
|
const originSubPeriodEnd = context.periodEnd;
|
|
17
17
|
const specialEnd = originSubPeriodEnd ? new Date(originSubPeriodEnd.setHours(23, 59, 59, 999)) : originSubPeriodEnd;
|
|
18
18
|
const updatedSubscription = yield subscriptionService.updateSubscription(context.subIdKey, {
|
|
@@ -130,7 +130,7 @@ class BillingAggregateService {
|
|
|
130
130
|
paidEmail: context.paidEmail,
|
|
131
131
|
payUpdatedAt: new Date(),
|
|
132
132
|
}, tx);
|
|
133
|
-
//
|
|
133
|
+
// Set subscription expiry to the last second of the expiration date
|
|
134
134
|
const originSubPeriodEnd = context.periodEnd;
|
|
135
135
|
const specialEnd = originSubPeriodEnd ? new Date(originSubPeriodEnd.setHours(23, 59, 59, 999)) : originSubPeriodEnd;
|
|
136
136
|
yield subscriptionService.updateSubscription(context.subIdKey, {
|
|
@@ -216,7 +216,7 @@ class BillingAggregateService {
|
|
|
216
216
|
return __awaiter(this, void 0, void 0, function* () {
|
|
217
217
|
yield runInTransaction((tx) => __awaiter(this, void 0, void 0, function* () {
|
|
218
218
|
if (params.isUserCancel) {
|
|
219
|
-
//
|
|
219
|
+
// Record the time when the user unsubscribes
|
|
220
220
|
yield transactionService.update(params.orderId, {
|
|
221
221
|
subLastTryCancelAt: new Date(),
|
|
222
222
|
}, tx);
|
|
@@ -234,14 +234,14 @@ class BillingAggregateService {
|
|
|
234
234
|
return __awaiter(this, void 0, void 0, function* () {
|
|
235
235
|
yield runInTransaction((tx) => __awaiter(this, void 0, void 0, function* () {
|
|
236
236
|
var _a;
|
|
237
|
-
//
|
|
237
|
+
// Update the order and log cancellation details
|
|
238
238
|
yield transactionService.update(context.orderId, {
|
|
239
239
|
subPeriodCanceledAt: context.canceledAt,
|
|
240
240
|
subCancellationDetail: (_a = context.cancellationDetail) !== null && _a !== void 0 ? _a : undefined
|
|
241
241
|
}, tx);
|
|
242
|
-
//
|
|
242
|
+
// Update subscription information
|
|
243
243
|
yield subscriptionService.updateStatus(context.subIdKey, SubscriptionStatus.CANCELED, tx);
|
|
244
|
-
//
|
|
244
|
+
// Clear credits and keep audit trail
|
|
245
245
|
yield creditService.purgePaidCredit(context.userId, 'cancel_subscription_purge', context.orderId, tx);
|
|
246
246
|
}));
|
|
247
247
|
});
|
|
@@ -7,17 +7,17 @@ export declare class UserAggregateService {
|
|
|
7
7
|
credit: Credit;
|
|
8
8
|
}>;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Create a new registered user
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* 1.
|
|
14
|
-
* 2.
|
|
15
|
-
* 3.
|
|
16
|
-
* 4.
|
|
12
|
+
* Initialization steps (parallel to credit):
|
|
13
|
+
* 1. Create User record
|
|
14
|
+
* 2. Initialize Credit record (free credits)
|
|
15
|
+
* 3. Initialize Subscription record (placeholder, status=INCOMPLETE)
|
|
16
|
+
* 4. Record CreditUsage (audit)
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* - session.completed
|
|
20
|
-
* -
|
|
18
|
+
* When the user subscribes via Stripe later:
|
|
19
|
+
* - session.completed or invoice.paid will UPDATE the subscription record
|
|
20
|
+
* - No CREATE needed, only UPDATE to ensure logical consistency
|
|
21
21
|
*/
|
|
22
22
|
createNewRegisteredUser(clerkUserId: string, email?: string, fingerprintId?: string, userName?: string, sourceRef?: CoreJsonValue): Promise<{
|
|
23
23
|
newUser: User;
|
|
@@ -32,17 +32,17 @@ class UserAggregateService {
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* Create a new registered user
|
|
36
36
|
*
|
|
37
|
-
*
|
|
38
|
-
* 1.
|
|
39
|
-
* 2.
|
|
40
|
-
* 3.
|
|
41
|
-
* 4.
|
|
37
|
+
* Initialization steps (parallel to credit):
|
|
38
|
+
* 1. Create User record
|
|
39
|
+
* 2. Initialize Credit record (free credits)
|
|
40
|
+
* 3. Initialize Subscription record (placeholder, status=INCOMPLETE)
|
|
41
|
+
* 4. Record CreditUsage (audit)
|
|
42
42
|
*
|
|
43
|
-
*
|
|
44
|
-
* - session.completed
|
|
45
|
-
* -
|
|
43
|
+
* When the user subscribes via Stripe later:
|
|
44
|
+
* - session.completed or invoice.paid will UPDATE the subscription record
|
|
45
|
+
* - No CREATE needed, only UPDATE to ensure logical consistency
|
|
46
46
|
*/
|
|
47
47
|
createNewRegisteredUser(clerkUserId, email, fingerprintId, userName, sourceRef) {
|
|
48
48
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -68,7 +68,7 @@ class UserAggregateService {
|
|
|
68
68
|
}));
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
|
-
//
|
|
71
|
+
// Note: Handle credit review logs
|
|
72
72
|
upgradeToRegistered(userId, email, clerkUserId, userName) {
|
|
73
73
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
74
74
|
return prismaTransactionUtil.runInTransaction((tx) => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -77,9 +77,9 @@ class UserAggregateService {
|
|
|
77
77
|
clerkUserId,
|
|
78
78
|
userName
|
|
79
79
|
}, tx);
|
|
80
|
-
//
|
|
80
|
+
// Clear anonymous credits first and audit for traceability
|
|
81
81
|
yield credit_service.creditService.purgeFreeCredit(userId, 'user_registered_purge', userId, tx);
|
|
82
|
-
//
|
|
82
|
+
// Then initialize free credits upon successful registration
|
|
83
83
|
const credit = yield credit_service.creditService.initializeCreditWithFree({
|
|
84
84
|
userId: updateUser.userId,
|
|
85
85
|
feature: 'user_registration_init',
|
|
@@ -95,20 +95,20 @@ class UserAggregateService {
|
|
|
95
95
|
handleUserUnregister(clerkUserId) {
|
|
96
96
|
return tslib.__awaiter(this, void 0, void 0, function* () {
|
|
97
97
|
return prismaTransactionUtil.runInTransaction((tx) => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
98
|
-
//
|
|
98
|
+
// query DB user
|
|
99
99
|
const user = yield user_service.userService.findByClerkUserId(clerkUserId, tx);
|
|
100
100
|
if (!user) {
|
|
101
101
|
console.log(`User with clerkUserId ${clerkUserId} not found`);
|
|
102
102
|
return null;
|
|
103
103
|
}
|
|
104
104
|
const userId = user.userId;
|
|
105
|
-
//
|
|
105
|
+
// Update user status and retain user info (especially FingerprintId) to prevent repeated registration abuse
|
|
106
106
|
yield user_service.userService.unregister(user.userId, tx);
|
|
107
|
-
//
|
|
107
|
+
// Clear credits
|
|
108
108
|
yield credit_service.creditService.purgeCredit(userId, 'soft_delete_user', userId, tx);
|
|
109
109
|
const subscription = yield subscription_service.subscriptionService.getActiveSubscription(userId, tx);
|
|
110
110
|
if (subscription) {
|
|
111
|
-
//
|
|
111
|
+
// Update subscription info if it exists
|
|
112
112
|
yield subscription_service.subscriptionService.cancelSubscription(subscription.id, true, tx);
|
|
113
113
|
}
|
|
114
114
|
return user.userId;
|
|
@@ -30,17 +30,17 @@ class UserAggregateService {
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* Create a new registered user
|
|
34
34
|
*
|
|
35
|
-
*
|
|
36
|
-
* 1.
|
|
37
|
-
* 2.
|
|
38
|
-
* 3.
|
|
39
|
-
* 4.
|
|
35
|
+
* Initialization steps (parallel to credit):
|
|
36
|
+
* 1. Create User record
|
|
37
|
+
* 2. Initialize Credit record (free credits)
|
|
38
|
+
* 3. Initialize Subscription record (placeholder, status=INCOMPLETE)
|
|
39
|
+
* 4. Record CreditUsage (audit)
|
|
40
40
|
*
|
|
41
|
-
*
|
|
42
|
-
* - session.completed
|
|
43
|
-
* -
|
|
41
|
+
* When the user subscribes via Stripe later:
|
|
42
|
+
* - session.completed or invoice.paid will UPDATE the subscription record
|
|
43
|
+
* - No CREATE needed, only UPDATE to ensure logical consistency
|
|
44
44
|
*/
|
|
45
45
|
createNewRegisteredUser(clerkUserId, email, fingerprintId, userName, sourceRef) {
|
|
46
46
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -66,7 +66,7 @@ class UserAggregateService {
|
|
|
66
66
|
}));
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
|
-
//
|
|
69
|
+
// Note: Handle credit review logs
|
|
70
70
|
upgradeToRegistered(userId, email, clerkUserId, userName) {
|
|
71
71
|
return __awaiter(this, void 0, void 0, function* () {
|
|
72
72
|
return runInTransaction((tx) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -75,9 +75,9 @@ class UserAggregateService {
|
|
|
75
75
|
clerkUserId,
|
|
76
76
|
userName
|
|
77
77
|
}, tx);
|
|
78
|
-
//
|
|
78
|
+
// Clear anonymous credits first and audit for traceability
|
|
79
79
|
yield creditService.purgeFreeCredit(userId, 'user_registered_purge', userId, tx);
|
|
80
|
-
//
|
|
80
|
+
// Then initialize free credits upon successful registration
|
|
81
81
|
const credit = yield creditService.initializeCreditWithFree({
|
|
82
82
|
userId: updateUser.userId,
|
|
83
83
|
feature: 'user_registration_init',
|
|
@@ -93,20 +93,20 @@ class UserAggregateService {
|
|
|
93
93
|
handleUserUnregister(clerkUserId) {
|
|
94
94
|
return __awaiter(this, void 0, void 0, function* () {
|
|
95
95
|
return runInTransaction((tx) => __awaiter(this, void 0, void 0, function* () {
|
|
96
|
-
//
|
|
96
|
+
// query DB user
|
|
97
97
|
const user = yield userService.findByClerkUserId(clerkUserId, tx);
|
|
98
98
|
if (!user) {
|
|
99
99
|
console.log(`User with clerkUserId ${clerkUserId} not found`);
|
|
100
100
|
return null;
|
|
101
101
|
}
|
|
102
102
|
const userId = user.userId;
|
|
103
|
-
//
|
|
103
|
+
// Update user status and retain user info (especially FingerprintId) to prevent repeated registration abuse
|
|
104
104
|
yield userService.unregister(user.userId, tx);
|
|
105
|
-
//
|
|
105
|
+
// Clear credits
|
|
106
106
|
yield creditService.purgeCredit(userId, 'soft_delete_user', userId, tx);
|
|
107
107
|
const subscription = yield subscriptionService.getActiveSubscription(userId, tx);
|
|
108
108
|
if (subscription) {
|
|
109
|
-
//
|
|
109
|
+
// Update subscription info if it exists
|
|
110
110
|
yield subscriptionService.cancelSubscription(subscription.id, true, tx);
|
|
111
111
|
}
|
|
112
112
|
return user.userId;
|
|
@@ -3,91 +3,91 @@
|
|
|
3
3
|
// Database Field Enums
|
|
4
4
|
// Keep in sync with DB CHECK constraints
|
|
5
5
|
const UserStatus = {
|
|
6
|
-
//
|
|
6
|
+
// Anonymous user
|
|
7
7
|
ANONYMOUS: 'anonymous',
|
|
8
|
-
//
|
|
8
|
+
// Registered user
|
|
9
9
|
REGISTERED: 'registered',
|
|
10
|
-
//
|
|
10
|
+
// Frozen by admin intervention
|
|
11
11
|
FROZEN: 'frozen',
|
|
12
|
-
//
|
|
12
|
+
// Soft-deleted user data that must not be reused
|
|
13
13
|
DELETED: 'deleted',
|
|
14
14
|
};
|
|
15
15
|
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
|
};
|
|
27
27
|
const OrderStatus = {
|
|
28
|
-
//
|
|
28
|
+
// Initial state
|
|
29
29
|
CREATED: 'created',
|
|
30
|
-
//
|
|
30
|
+
// Intermediate state, awaiting payment; may be triggered by a payment failure event
|
|
31
31
|
PENDING_UNPAID: 'pending_unpaid',
|
|
32
|
-
//
|
|
32
|
+
// Intermediate or final state; payment succeeded and may later become refunded or canceled
|
|
33
33
|
SUCCESS: 'success',
|
|
34
|
-
//
|
|
34
|
+
// Intermediate or final state; checkout or payment failed and may later become refunded or canceled
|
|
35
35
|
FAILED: 'failed',
|
|
36
|
-
//
|
|
36
|
+
// Final state, refunded
|
|
37
37
|
REFUNDED: 'refunded',
|
|
38
|
-
//
|
|
38
|
+
// Final state, canceled
|
|
39
39
|
CANCELED: 'canceled',
|
|
40
40
|
};
|
|
41
41
|
const TransactionType = {
|
|
42
|
-
//
|
|
42
|
+
// Subscription order
|
|
43
43
|
SUBSCRIPTION: 'subscription',
|
|
44
|
-
//
|
|
44
|
+
// One-time payment order
|
|
45
45
|
ONE_TIME: 'one_time',
|
|
46
46
|
};
|
|
47
47
|
const CreditType = {
|
|
48
|
-
//
|
|
48
|
+
// Subscription credits
|
|
49
49
|
PAID: 'paid',
|
|
50
|
-
//
|
|
50
|
+
// One-time paid credits
|
|
51
51
|
ONE_TIME_PAID: 'one_time_paid',
|
|
52
|
-
//
|
|
52
|
+
// Free credits
|
|
53
53
|
FREE: 'free',
|
|
54
54
|
};
|
|
55
55
|
const OperationType = {
|
|
56
|
-
//
|
|
56
|
+
// System-granted credits
|
|
57
57
|
SYS_GIFT: 'system_gift',
|
|
58
|
-
//
|
|
58
|
+
// User credit consumption
|
|
59
59
|
CONSUME: 'consume',
|
|
60
|
-
//
|
|
60
|
+
// User credit recharge
|
|
61
61
|
RECHARGE: 'recharge',
|
|
62
|
-
//
|
|
62
|
+
// Admin credit freeze
|
|
63
63
|
FREEZE: 'freeze',
|
|
64
|
-
//
|
|
64
|
+
// Admin credit unfreeze
|
|
65
65
|
UNFREEZE: 'unfreeze',
|
|
66
|
-
//
|
|
66
|
+
// Admin credit increase
|
|
67
67
|
ADJUST_INCREASE: 'adjust_increase',
|
|
68
|
-
//
|
|
68
|
+
// Admin credit decrease
|
|
69
69
|
ADJUST_DECREASE: 'adjust_decrease',
|
|
70
|
-
//
|
|
70
|
+
// Credit purge triggered by an event or expiration
|
|
71
71
|
PURGE: 'purge',
|
|
72
72
|
};
|
|
73
|
-
//
|
|
73
|
+
// Payment provider types
|
|
74
74
|
const PaySupplier = {
|
|
75
75
|
STRIPE: 'Stripe',
|
|
76
76
|
APPLE: 'Apple',
|
|
77
77
|
PAYPAL: 'Paypal',
|
|
78
78
|
};
|
|
79
79
|
const BillingReason = {
|
|
80
|
-
//
|
|
80
|
+
// Initial subscription
|
|
81
81
|
SUBSCRIPTION_CREATE: 'subscription_create',
|
|
82
|
-
//
|
|
82
|
+
// Subscription renewal
|
|
83
83
|
SUBSCRIPTION_CYCLE: 'subscription_cycle',
|
|
84
84
|
};
|
|
85
85
|
const PaymentStatus = {
|
|
86
|
-
//
|
|
86
|
+
// Paid
|
|
87
87
|
PAID: 'paid',
|
|
88
|
-
//
|
|
88
|
+
// Pending payment
|
|
89
89
|
UN_PAID: 'un_paid',
|
|
90
|
-
//
|
|
90
|
+
// No payment required
|
|
91
91
|
NO_PAYMENT_REQUIRED: 'no_payment_required',
|
|
92
92
|
};
|
|
93
93
|
// Validation Functions
|
|
@@ -1,91 +1,91 @@
|
|
|
1
1
|
// Database Field Enums
|
|
2
2
|
// Keep in sync with DB CHECK constraints
|
|
3
3
|
const UserStatus = {
|
|
4
|
-
//
|
|
4
|
+
// Anonymous user
|
|
5
5
|
ANONYMOUS: 'anonymous',
|
|
6
|
-
//
|
|
6
|
+
// Registered user
|
|
7
7
|
REGISTERED: 'registered',
|
|
8
|
-
//
|
|
8
|
+
// Frozen by admin intervention
|
|
9
9
|
FROZEN: 'frozen',
|
|
10
|
-
//
|
|
10
|
+
// Soft-deleted user data that must not be reused
|
|
11
11
|
DELETED: 'deleted',
|
|
12
12
|
};
|
|
13
13
|
const SubscriptionStatus = {
|
|
14
|
-
//
|
|
14
|
+
// Initial or post-cancellation state
|
|
15
15
|
INCOMPLETE: 'incomplete',
|
|
16
|
-
//
|
|
16
|
+
// Trial subscription period
|
|
17
17
|
TRIALING: 'trialing',
|
|
18
|
-
//
|
|
18
|
+
// Active subscription
|
|
19
19
|
ACTIVE: 'active',
|
|
20
|
-
//
|
|
20
|
+
// Past-due subscription
|
|
21
21
|
PAST_DUE: 'past_due',
|
|
22
|
-
//
|
|
22
|
+
// Canceled subscription
|
|
23
23
|
CANCELED: 'canceled',
|
|
24
24
|
};
|
|
25
25
|
const OrderStatus = {
|
|
26
|
-
//
|
|
26
|
+
// Initial state
|
|
27
27
|
CREATED: 'created',
|
|
28
|
-
//
|
|
28
|
+
// Intermediate state, awaiting payment; may be triggered by a payment failure event
|
|
29
29
|
PENDING_UNPAID: 'pending_unpaid',
|
|
30
|
-
//
|
|
30
|
+
// Intermediate or final state; payment succeeded and may later become refunded or canceled
|
|
31
31
|
SUCCESS: 'success',
|
|
32
|
-
//
|
|
32
|
+
// Intermediate or final state; checkout or payment failed and may later become refunded or canceled
|
|
33
33
|
FAILED: 'failed',
|
|
34
|
-
//
|
|
34
|
+
// Final state, refunded
|
|
35
35
|
REFUNDED: 'refunded',
|
|
36
|
-
//
|
|
36
|
+
// Final state, canceled
|
|
37
37
|
CANCELED: 'canceled',
|
|
38
38
|
};
|
|
39
39
|
const TransactionType = {
|
|
40
|
-
//
|
|
40
|
+
// Subscription order
|
|
41
41
|
SUBSCRIPTION: 'subscription',
|
|
42
|
-
//
|
|
42
|
+
// One-time payment order
|
|
43
43
|
ONE_TIME: 'one_time',
|
|
44
44
|
};
|
|
45
45
|
const CreditType = {
|
|
46
|
-
//
|
|
46
|
+
// Subscription credits
|
|
47
47
|
PAID: 'paid',
|
|
48
|
-
//
|
|
48
|
+
// One-time paid credits
|
|
49
49
|
ONE_TIME_PAID: 'one_time_paid',
|
|
50
|
-
//
|
|
50
|
+
// Free credits
|
|
51
51
|
FREE: 'free',
|
|
52
52
|
};
|
|
53
53
|
const OperationType = {
|
|
54
|
-
//
|
|
54
|
+
// System-granted credits
|
|
55
55
|
SYS_GIFT: 'system_gift',
|
|
56
|
-
//
|
|
56
|
+
// User credit consumption
|
|
57
57
|
CONSUME: 'consume',
|
|
58
|
-
//
|
|
58
|
+
// User credit recharge
|
|
59
59
|
RECHARGE: 'recharge',
|
|
60
|
-
//
|
|
60
|
+
// Admin credit freeze
|
|
61
61
|
FREEZE: 'freeze',
|
|
62
|
-
//
|
|
62
|
+
// Admin credit unfreeze
|
|
63
63
|
UNFREEZE: 'unfreeze',
|
|
64
|
-
//
|
|
64
|
+
// Admin credit increase
|
|
65
65
|
ADJUST_INCREASE: 'adjust_increase',
|
|
66
|
-
//
|
|
66
|
+
// Admin credit decrease
|
|
67
67
|
ADJUST_DECREASE: 'adjust_decrease',
|
|
68
|
-
//
|
|
68
|
+
// Credit purge triggered by an event or expiration
|
|
69
69
|
PURGE: 'purge',
|
|
70
70
|
};
|
|
71
|
-
//
|
|
71
|
+
// Payment provider types
|
|
72
72
|
const PaySupplier = {
|
|
73
73
|
STRIPE: 'Stripe',
|
|
74
74
|
APPLE: 'Apple',
|
|
75
75
|
PAYPAL: 'Paypal',
|
|
76
76
|
};
|
|
77
77
|
const BillingReason = {
|
|
78
|
-
//
|
|
78
|
+
// Initial subscription
|
|
79
79
|
SUBSCRIPTION_CREATE: 'subscription_create',
|
|
80
|
-
//
|
|
80
|
+
// Subscription renewal
|
|
81
81
|
SUBSCRIPTION_CYCLE: 'subscription_cycle',
|
|
82
82
|
};
|
|
83
83
|
const PaymentStatus = {
|
|
84
|
-
//
|
|
84
|
+
// Paid
|
|
85
85
|
PAID: 'paid',
|
|
86
|
-
//
|
|
86
|
+
// Pending payment
|
|
87
87
|
UN_PAID: 'un_paid',
|
|
88
|
-
//
|
|
88
|
+
// No payment required
|
|
89
89
|
NO_PAYMENT_REQUIRED: 'no_payment_required',
|
|
90
90
|
};
|
|
91
91
|
// Validation Functions
|
|
@@ -208,7 +208,7 @@ class CreditService {
|
|
|
208
208
|
const normalized = this.normalizeAmounts({ free: init.creditsChange });
|
|
209
209
|
this.ensureNonNegative(normalized, 'initializeCredit');
|
|
210
210
|
const client = prisma.checkAndFallbackWithNonTCClient(tx);
|
|
211
|
-
//
|
|
211
|
+
// Use upsert semantics to share initialization logic for anonymous users and anonymous-to-registered upgrades.
|
|
212
212
|
const credit = yield client.credit.upsert({
|
|
213
213
|
where: {
|
|
214
214
|
userId: init.userId
|
|
@@ -430,7 +430,7 @@ class CreditService {
|
|
|
430
430
|
where: { userId },
|
|
431
431
|
data: updateData,
|
|
432
432
|
});
|
|
433
|
-
//
|
|
433
|
+
// Always write an audit entry, even when the credit change is zero.
|
|
434
434
|
const usage = yield this.recordCreditAuditLog(client, userId, constants.OperationType.PURGE, normalizedDeduction, { feature: reason, operationReferId });
|
|
435
435
|
return { credit, usage };
|
|
436
436
|
});
|
|
@@ -206,7 +206,7 @@ class CreditService {
|
|
|
206
206
|
const normalized = this.normalizeAmounts({ free: init.creditsChange });
|
|
207
207
|
this.ensureNonNegative(normalized, 'initializeCredit');
|
|
208
208
|
const client = checkAndFallbackWithNonTCClient(tx);
|
|
209
|
-
//
|
|
209
|
+
// Use upsert semantics to share initialization logic for anonymous users and anonymous-to-registered upgrades.
|
|
210
210
|
const credit = yield client.credit.upsert({
|
|
211
211
|
where: {
|
|
212
212
|
userId: init.userId
|
|
@@ -428,7 +428,7 @@ class CreditService {
|
|
|
428
428
|
where: { userId },
|
|
429
429
|
data: updateData,
|
|
430
430
|
});
|
|
431
|
-
//
|
|
431
|
+
// Always write an audit entry, even when the credit change is zero.
|
|
432
432
|
const usage = yield this.recordCreditAuditLog(client, userId, OperationType.PURGE, normalizedDeduction, { feature: reason, operationReferId });
|
|
433
433
|
return { credit, usage };
|
|
434
434
|
});
|