sequelize-game-bank 5.0.2 → 5.1.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/.cache_ggshield +1 -0
- package/config/setup-env.js +43 -24
- package/database/models/Credit.js +35 -22
- package/database/services/account/create-credit.js +70 -27
- package/database/services/bot/cron-schedule.js +155 -41
- package/package.json +3 -3
- package/public/credit.html +139 -81
- package/public/home.html +427 -344
- package/public/js/js.js +191 -88
- package/public/uploads/avatars/Pavel_1793516096895.png +0 -0
- package/public/uploads/avatars/root_1793520405521.png +0 -0
- package/server/server.js +304 -88
package/.cache_ggshield
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"last_found_secrets": [{"match": "34d9bf2fadeb3b27d67300951ca6eba96da51655834e1db45519bcbdd4bf9bee", "name": "Generic Password - /home/pl/Desktop/sequelize-game-bank/config/setup-env.js"}]}
|
package/config/setup-env.js
CHANGED
|
@@ -1,34 +1,53 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
const process = require(
|
|
4
|
-
const envPath = path.resolve(process.cwd(),
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const process = require("process");
|
|
4
|
+
const envPath = path.resolve(process.cwd(), ".env");
|
|
5
5
|
const logger = require("custom-color-logs").print;
|
|
6
6
|
const defaultEnvContent = `
|
|
7
7
|
# ==============================================================================
|
|
8
|
-
# sequelize-game-bank
|
|
8
|
+
# sequelize-game-bank v5.0.0
|
|
9
9
|
# ==============================================================================
|
|
10
|
+
|
|
11
|
+
# тег инициализации
|
|
10
12
|
SEQUELIZE_GAME_BANK_INITIALIZED = true
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
# токен который будет зашиваться в cooki
|
|
15
|
+
JWT_SECRET = "0000"
|
|
16
|
+
|
|
17
|
+
# пароль от аккаунта админестратора
|
|
18
|
+
ROOT_PASSWORD = "bank-root"
|
|
19
|
+
|
|
20
|
+
# почта админестратора
|
|
21
|
+
GMAIL = "example@gmail.com"
|
|
22
|
+
|
|
23
|
+
# пароль от приложения gmail
|
|
24
|
+
GOOGLE_APP_PASSWORD = "abcd efgh ijkl mnop"
|
|
25
|
+
|
|
26
|
+
# интервал проверки кредита
|
|
27
|
+
CRON_CREDIT_INTERVAL_MINUTES = 5
|
|
28
|
+
|
|
15
29
|
`;
|
|
16
30
|
function initializeEnvironment() {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
} catch (err) {
|
|
29
|
-
console.log(logger.ServerError('Не удалось проверить или обновить .env:', err.message));
|
|
31
|
+
try {
|
|
32
|
+
if (!fs.existsSync(envPath)) {
|
|
33
|
+
fs.writeFileSync(envPath, defaultEnvContent, "utf8");
|
|
34
|
+
} else {
|
|
35
|
+
const currentEnv = fs.readFileSync(envPath, "utf8");
|
|
36
|
+
if (currentEnv.includes("SEQUELIZE_GAME_BANK_INITIALIZED")) {
|
|
37
|
+
require("dotenv").config({ path: envPath });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
fs.appendFileSync(envPath, `\n\n${defaultEnvContent}`, "utf8");
|
|
30
41
|
}
|
|
31
|
-
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.log(
|
|
44
|
+
logger.ServerError(
|
|
45
|
+
"Не удалось проверить или обновить .env:",
|
|
46
|
+
err.message,
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
require("dotenv").config({ path: envPath });
|
|
32
51
|
}
|
|
33
52
|
initializeEnvironment();
|
|
34
|
-
module.exports = { envPath };
|
|
53
|
+
module.exports = { envPath };
|
|
@@ -1,37 +1,50 @@
|
|
|
1
|
-
const { DataTypes } = require(
|
|
1
|
+
const { DataTypes } = require("sequelize");
|
|
2
2
|
const path = require("path");
|
|
3
|
-
const sequelize = require(path.join(__dirname,
|
|
4
|
-
const Account = require(path.join(__dirname,
|
|
5
|
-
|
|
3
|
+
const sequelize = require(path.join(__dirname, "..", "config", "init-db.js"));
|
|
4
|
+
const Account = require(path.join(__dirname, "Account.js"));
|
|
5
|
+
|
|
6
|
+
const Credit = sequelize.define("credit", {
|
|
6
7
|
id: {
|
|
7
8
|
type: DataTypes.INTEGER,
|
|
8
9
|
autoIncrement: true,
|
|
9
|
-
primaryKey: true
|
|
10
|
+
primaryKey: true,
|
|
10
11
|
},
|
|
11
|
-
|
|
12
|
+
accountId: {
|
|
12
13
|
type: DataTypes.INTEGER,
|
|
13
14
|
allowNull: false,
|
|
14
|
-
references: { model: Account, key:
|
|
15
|
-
onDelete:
|
|
15
|
+
references: { model: Account, key: "id" },
|
|
16
|
+
onDelete: "CASCADE",
|
|
16
17
|
},
|
|
17
|
-
|
|
18
|
-
type: DataTypes.
|
|
19
|
-
allowNull: false
|
|
18
|
+
amount: {
|
|
19
|
+
type: DataTypes.DECIMAL(15, 2),
|
|
20
|
+
allowNull: false,
|
|
20
21
|
},
|
|
21
|
-
|
|
22
|
-
type: DataTypes.
|
|
23
|
-
allowNull: false
|
|
22
|
+
totalRepay: {
|
|
23
|
+
type: DataTypes.DECIMAL(15, 2),
|
|
24
|
+
allowNull: false,
|
|
24
25
|
},
|
|
25
|
-
|
|
26
|
+
remainingRepay: {
|
|
26
27
|
type: DataTypes.DECIMAL(15, 2),
|
|
27
|
-
allowNull: false
|
|
28
|
+
allowNull: false,
|
|
28
29
|
},
|
|
29
|
-
|
|
30
|
+
termMonths: {
|
|
31
|
+
type: DataTypes.INTEGER,
|
|
32
|
+
allowNull: false,
|
|
33
|
+
},
|
|
34
|
+
autoRepay: {
|
|
30
35
|
type: DataTypes.BOOLEAN,
|
|
31
36
|
allowNull: false,
|
|
32
|
-
defaultValue: false
|
|
33
|
-
}
|
|
37
|
+
defaultValue: false,
|
|
38
|
+
},
|
|
39
|
+
status: {
|
|
40
|
+
type: DataTypes.STRING,
|
|
41
|
+
allowNull: false,
|
|
42
|
+
defaultValue: "active",
|
|
43
|
+
},
|
|
34
44
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
|
|
46
|
+
// Настройка связей с учетом нового имени внешнего ключа accountId
|
|
47
|
+
Account.hasMany(Credit, { foreignKey: "accountId" });
|
|
48
|
+
Credit.belongsTo(Account, { foreignKey: "accountId", as: "linkedAccount" });
|
|
49
|
+
|
|
50
|
+
module.exports = Credit;
|
|
@@ -1,50 +1,92 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
-
const Account = require(
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
const Account = require(
|
|
3
|
+
path.join(__dirname, "..", "..", "models", "Account.js"),
|
|
4
|
+
);
|
|
5
|
+
const Credit = require(path.join(__dirname, "..", "..", "models", "Credit.js"));
|
|
6
|
+
const sequelize = require(
|
|
7
|
+
path.join(__dirname, "..", "..", "config", "init-db.js"),
|
|
8
|
+
);
|
|
9
|
+
const bcrypt = require("bcrypt");
|
|
10
|
+
// Функция теперь принимает аргументы по порядку, как они передаются из роутера
|
|
11
|
+
async function createCredit(
|
|
12
|
+
username,
|
|
13
|
+
accountId,
|
|
14
|
+
amount,
|
|
15
|
+
pinCode,
|
|
16
|
+
termMonths = 12,
|
|
17
|
+
autoRepay = false,
|
|
18
|
+
) {
|
|
6
19
|
const numericAmount = parseFloat(amount);
|
|
7
|
-
const numericRate = parseFloat(rate);
|
|
8
20
|
const numericTerm = parseInt(termMonths, 10);
|
|
21
|
+
const numericRate = 0.1;
|
|
9
22
|
if (!accountId || isNaN(numericAmount) || numericAmount <= 0) {
|
|
10
|
-
return {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
error: "Некорректные параметры для создания кредита",
|
|
26
|
+
};
|
|
11
27
|
}
|
|
12
|
-
if (isNaN(
|
|
13
|
-
return { success: false, error:
|
|
28
|
+
if (isNaN(numericTerm) || numericTerm <= 0) {
|
|
29
|
+
return { success: false, error: "Некорректный срок кредитования" };
|
|
14
30
|
}
|
|
31
|
+
if (!pinCode) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: "Укажите PIN-код для подтверждения операции",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
const transaction = await sequelize.transaction();
|
|
16
39
|
try {
|
|
17
40
|
const account = await Account.findOne({
|
|
18
41
|
where: { id: accountId },
|
|
19
42
|
lock: transaction.LOCK.UPDATE,
|
|
20
|
-
transaction
|
|
43
|
+
transaction,
|
|
21
44
|
});
|
|
22
|
-
|
|
23
|
-
if (account
|
|
45
|
+
|
|
46
|
+
if (!account) throw new Error("Аккаунт не найден");
|
|
47
|
+
|
|
48
|
+
if (account.user !== username)
|
|
49
|
+
throw new Error("Нет доступа к данному счету");
|
|
50
|
+
|
|
51
|
+
if (account.status === "blocked") throw new Error("Аккаунт заблокирован");
|
|
52
|
+
|
|
53
|
+
const isPinValid = await bcrypt.compare(
|
|
54
|
+
pinCode,
|
|
55
|
+
account.pincode || account.pin_code,
|
|
56
|
+
);
|
|
57
|
+
if (!isPinValid) throw new Error("Неверный PIN-код");
|
|
58
|
+
|
|
24
59
|
const existingCredit = await Credit.findOne({
|
|
25
|
-
where: { accountId: account.id, status:
|
|
60
|
+
where: { accountId: account.id, status: "active" },
|
|
26
61
|
transaction,
|
|
27
|
-
lock: transaction.LOCK.UPDATE
|
|
62
|
+
lock: transaction.LOCK.UPDATE,
|
|
28
63
|
});
|
|
29
64
|
if (existingCredit) {
|
|
30
|
-
throw new Error(
|
|
65
|
+
throw new Error("У этого аккаунта уже есть активный кредит");
|
|
31
66
|
}
|
|
67
|
+
|
|
32
68
|
const totalRepay = numericAmount * (1 + numericRate);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
69
|
+
|
|
70
|
+
const credit = await Credit.create(
|
|
71
|
+
{
|
|
72
|
+
accountId: account.id,
|
|
73
|
+
amount: numericAmount,
|
|
74
|
+
totalRepay: totalRepay,
|
|
75
|
+
remainingRepay: totalRepay,
|
|
76
|
+
status: "active",
|
|
77
|
+
autoRepay: !!autoRepay,
|
|
78
|
+
termMonths: numericTerm,
|
|
79
|
+
},
|
|
80
|
+
{ transaction },
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
await account.increment("manny", { by: numericAmount, transaction });
|
|
84
|
+
|
|
43
85
|
await transaction.commit();
|
|
44
86
|
return {
|
|
45
87
|
success: true,
|
|
46
|
-
message:
|
|
47
|
-
creditId: credit.id
|
|
88
|
+
message: "Кредит успешно оформлен",
|
|
89
|
+
creditId: credit.id,
|
|
48
90
|
};
|
|
49
91
|
} catch (error) {
|
|
50
92
|
if (transaction && !transaction.finished) {
|
|
@@ -53,4 +95,5 @@ async function createCredit({ accountId, amount, autoRepay = false, rate = 0.1,
|
|
|
53
95
|
return { success: false, error: error.message };
|
|
54
96
|
}
|
|
55
97
|
}
|
|
56
|
-
|
|
98
|
+
|
|
99
|
+
module.exports = createCredit;
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
const cron = require(
|
|
2
|
-
const nodemailer = require(
|
|
3
|
-
const { Op } = require(
|
|
1
|
+
const cron = require("node-cron");
|
|
2
|
+
const nodemailer = require("nodemailer");
|
|
3
|
+
const { Op } = require("sequelize");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
5
|
+
const sequelize = require(
|
|
6
|
+
path.join(__dirname, "..", "..", "config", "init-db"),
|
|
7
|
+
);
|
|
8
|
+
const Account = require(path.join(__dirname, "..", "..", "models", "Account"));
|
|
9
|
+
const Credit = require(path.join(__dirname, "..", "..", "models", "Credit"));
|
|
10
|
+
const User = require(path.join(__dirname, "..", "..", "models", "User"));
|
|
9
11
|
const logger = require("custom-color-logs").print;
|
|
12
|
+
if (!Account.associations["User"]) {
|
|
13
|
+
Account.belongsTo(User, { foreignKey: "user", as: "User" });
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
const transporter = nodemailer.createTransport({
|
|
11
|
-
service:
|
|
17
|
+
service: "gmail",
|
|
12
18
|
auth: {
|
|
13
19
|
user: process.env.GMAIL,
|
|
14
20
|
pass: process.env.GOOGLE_APP_PASSWORD,
|
|
@@ -16,8 +22,11 @@ const transporter = nodemailer.createTransport({
|
|
|
16
22
|
});
|
|
17
23
|
|
|
18
24
|
transporter.verify((err) => {
|
|
19
|
-
if (err)
|
|
20
|
-
|
|
25
|
+
if (err)
|
|
26
|
+
console.log(
|
|
27
|
+
logger.NodemailerError("Ошибка конфигурации почты:", err.message),
|
|
28
|
+
);
|
|
29
|
+
else console.log(logger.NodemailerInfo("Сервер готов к отправке писем"));
|
|
21
30
|
});
|
|
22
31
|
|
|
23
32
|
async function sendEmailNotification(userEmail, subject, htmlContent) {
|
|
@@ -29,63 +38,158 @@ async function sendEmailNotification(userEmail, subject, htmlContent) {
|
|
|
29
38
|
subject: subject,
|
|
30
39
|
html: htmlContent,
|
|
31
40
|
});
|
|
32
|
-
console.log(
|
|
41
|
+
console.log(
|
|
42
|
+
logger.ServerCreditNodemailerFunctionPositivePerformance(
|
|
43
|
+
`Письмо успешно отправлено на ${userEmail}`,
|
|
44
|
+
),
|
|
45
|
+
);
|
|
33
46
|
} catch (error) {
|
|
34
|
-
console.log(
|
|
47
|
+
console.log(
|
|
48
|
+
logger.ServerCreditNodemailerError(
|
|
49
|
+
`Не удалось отправить письмо на ${userEmail}:\n ${error.message}`,
|
|
50
|
+
),
|
|
51
|
+
);
|
|
35
52
|
}
|
|
36
53
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
const envMinutes = parseInt(process.env.CRON_CREDIT_INTERVAL_MINUTES, 10);
|
|
55
|
+
let cronExpression = "0 0 * * *";
|
|
56
|
+
|
|
57
|
+
if (!isNaN(envMinutes) && envMinutes > 0) {
|
|
58
|
+
cronExpression = `*/${envMinutes} * * * *`;
|
|
59
|
+
console.log(
|
|
60
|
+
logger.ServerCreditInfo(
|
|
61
|
+
`Установлен кастомный интервал проверки: каждые ${envMinutes} мин. (${cronExpression})`,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
} else {
|
|
65
|
+
console.log(
|
|
66
|
+
logger.ServerCreditInfo(
|
|
67
|
+
`Переменная CRON_CREDIT_INTERVAL_MINUTES не задана. Переход на режим: раз в сутки (${cronExpression})`,
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
cron.schedule(cronExpression, async () => {
|
|
72
|
+
console.log(
|
|
73
|
+
logger.ServerCreditInfo(`Проверка active кредитов и обработка платежей...`),
|
|
74
|
+
);
|
|
75
|
+
|
|
40
76
|
try {
|
|
41
77
|
const activeCredits = await Credit.findAll({
|
|
42
78
|
where: {
|
|
43
|
-
status:
|
|
44
|
-
remainingRepay: { [Op.gt]: 0 }
|
|
45
|
-
}
|
|
79
|
+
status: "active",
|
|
80
|
+
remainingRepay: { [Op.gt]: 0 },
|
|
81
|
+
},
|
|
46
82
|
});
|
|
83
|
+
|
|
84
|
+
if (activeCredits.length === 0) {
|
|
85
|
+
console.log(
|
|
86
|
+
logger.ServerCreditInfo(`Активных кредитов для обработки не найдено.`),
|
|
87
|
+
);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
47
91
|
for (const activeCreditData of activeCredits) {
|
|
48
92
|
let emailToSend = null;
|
|
49
|
-
let emailSubject =
|
|
50
|
-
let emailHtml =
|
|
93
|
+
let emailSubject = "";
|
|
94
|
+
let emailHtml = "";
|
|
95
|
+
|
|
96
|
+
const creditId =
|
|
97
|
+
activeCreditData.id ||
|
|
98
|
+
activeCreditData.get("id") ||
|
|
99
|
+
(activeCreditData.dataValues ? activeCreditData.dataValues.id : null);
|
|
100
|
+
if (!creditId) {
|
|
101
|
+
console.log(
|
|
102
|
+
logger.ServerCreditError(
|
|
103
|
+
`Не удалось определить ID записи кредита. Пропускаем.`,
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
51
109
|
const transaction = await sequelize.transaction();
|
|
110
|
+
|
|
52
111
|
try {
|
|
53
|
-
const credit = await Credit.findByPk(
|
|
112
|
+
const credit = await Credit.findByPk(creditId, {
|
|
54
113
|
transaction,
|
|
55
|
-
lock: transaction.LOCK.UPDATE
|
|
114
|
+
lock: transaction.LOCK.UPDATE,
|
|
56
115
|
});
|
|
57
|
-
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
!credit ||
|
|
119
|
+
credit.status !== "active" ||
|
|
120
|
+
credit.remainingRepay <= 0
|
|
121
|
+
) {
|
|
58
122
|
await transaction.rollback();
|
|
59
123
|
continue;
|
|
60
124
|
}
|
|
125
|
+
|
|
61
126
|
const calculatedPayment = credit.totalRepay / credit.termMonths;
|
|
62
|
-
const paymentAmount = Math.min(
|
|
127
|
+
const paymentAmount = Math.min(
|
|
128
|
+
calculatedPayment,
|
|
129
|
+
credit.remainingRepay,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const targetAccountId =
|
|
133
|
+
credit.accountId ||
|
|
134
|
+
credit.account_id ||
|
|
135
|
+
credit.get("accountId") ||
|
|
136
|
+
credit.get("account_id");
|
|
137
|
+
|
|
138
|
+
if (!targetAccountId) {
|
|
139
|
+
console.log(
|
|
140
|
+
logger.ServerCreditError(
|
|
141
|
+
`У кредита ID #${credit.id} не найден связанный ID аккаунта`,
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
await transaction.rollback();
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
63
148
|
const account = await Account.findOne({
|
|
64
|
-
where: { id:
|
|
65
|
-
include: [{ model: User, as:
|
|
149
|
+
where: { id: targetAccountId },
|
|
150
|
+
include: [{ model: User, as: "User" }],
|
|
66
151
|
transaction,
|
|
67
|
-
lock: transaction.LOCK.UPDATE
|
|
152
|
+
lock: transaction.LOCK.UPDATE,
|
|
68
153
|
});
|
|
69
|
-
|
|
70
|
-
|
|
154
|
+
|
|
155
|
+
if (!account || !account.User || !account.User.user_gmail) {
|
|
156
|
+
console.log(
|
|
157
|
+
logger.ServerCreditError(
|
|
158
|
+
`Не найден Email (user_gmail) или аккаунт для кредита ID #${credit.id}`,
|
|
159
|
+
),
|
|
160
|
+
);
|
|
71
161
|
await transaction.rollback();
|
|
72
162
|
continue;
|
|
73
163
|
}
|
|
74
|
-
|
|
164
|
+
|
|
165
|
+
const userEmail = account.User.user_gmail;
|
|
166
|
+
|
|
75
167
|
if (credit.autoRepay) {
|
|
76
|
-
if (account.status ===
|
|
77
|
-
throw new Error(
|
|
168
|
+
if (account.status === "blocked") {
|
|
169
|
+
throw new Error("Аккаунт заблокирован");
|
|
78
170
|
}
|
|
79
|
-
|
|
80
|
-
|
|
171
|
+
|
|
172
|
+
if (
|
|
173
|
+
account.balance >= paymentAmount ||
|
|
174
|
+
account.manny >= paymentAmount
|
|
175
|
+
) {
|
|
176
|
+
const balanceField =
|
|
177
|
+
account.manny !== undefined ? "manny" : "balance";
|
|
178
|
+
await account.decrement(balanceField, {
|
|
179
|
+
by: paymentAmount,
|
|
180
|
+
transaction,
|
|
181
|
+
});
|
|
182
|
+
|
|
81
183
|
credit.remainingRepay -= paymentAmount;
|
|
82
184
|
if (credit.remainingRepay <= 0) {
|
|
83
|
-
credit.status =
|
|
185
|
+
credit.status = "paid";
|
|
84
186
|
}
|
|
187
|
+
|
|
85
188
|
await credit.save({ transaction });
|
|
86
189
|
await transaction.commit();
|
|
190
|
+
|
|
87
191
|
emailToSend = userEmail;
|
|
88
|
-
emailSubject =
|
|
192
|
+
emailSubject = "Успешное автосписание по кредиту — Game Bank";
|
|
89
193
|
emailHtml = `<h3>Уважаемый клиент!</h3>
|
|
90
194
|
<p>С вашего баланса было автоматически списано <b>${paymentAmount.toFixed(2)} USD</b> в счет погашения кредита.</p>
|
|
91
195
|
<p>Остаток задолженности: <b>${credit.remainingRepay.toFixed(2)} USD</b>.</p>`;
|
|
@@ -93,7 +197,8 @@ cron.schedule('0 0 * * *', async () => {
|
|
|
93
197
|
await transaction.rollback();
|
|
94
198
|
|
|
95
199
|
emailToSend = userEmail;
|
|
96
|
-
emailSubject =
|
|
200
|
+
emailSubject =
|
|
201
|
+
"Ошибка автосписания: недостаточно средств — Game Bank";
|
|
97
202
|
emailHtml = `<h3>Внимание! Недостаточно средств</h3>
|
|
98
203
|
<p>Мы не смогли списать регулярный платеж по кредиту в размере <b>${paymentAmount.toFixed(2)} USD</b>.</p>
|
|
99
204
|
<p>Пожалуйста, пополните баланс вашего аккаунта, чтобы избежать просрочки.</p>`;
|
|
@@ -101,12 +206,13 @@ cron.schedule('0 0 * * *', async () => {
|
|
|
101
206
|
} else {
|
|
102
207
|
await transaction.rollback();
|
|
103
208
|
emailToSend = userEmail;
|
|
104
|
-
emailSubject =
|
|
209
|
+
emailSubject = "Напоминание о платеже по кредиту — Game Bank";
|
|
105
210
|
emailHtml = `<h3>Здравствуйте!</h3>
|
|
106
211
|
<p>Напоминаем, что вам необходимо внести обязательный платеж по кредиту в размере <b>${paymentAmount.toFixed(2)} USD</b>.</p>
|
|
107
212
|
<p>Текущий остаток по кредиту: <b>${credit.remainingRepay.toFixed(2)} USD</b>.</p>
|
|
108
213
|
<p>Вы можете оплатить его в личном кабинете.</p>`;
|
|
109
214
|
}
|
|
215
|
+
|
|
110
216
|
if (emailToSend) {
|
|
111
217
|
await sendEmailNotification(emailToSend, emailSubject, emailHtml);
|
|
112
218
|
}
|
|
@@ -114,10 +220,18 @@ cron.schedule('0 0 * * *', async () => {
|
|
|
114
220
|
if (transaction && !transaction.finished) {
|
|
115
221
|
await transaction.rollback();
|
|
116
222
|
}
|
|
117
|
-
console.log(
|
|
223
|
+
console.log(
|
|
224
|
+
logger.ServerCreditError(
|
|
225
|
+
`Ошибка обработки кредита ID ${activeCreditData.id}: ${err.message}`,
|
|
226
|
+
),
|
|
227
|
+
);
|
|
118
228
|
}
|
|
119
229
|
}
|
|
120
230
|
} catch (error) {
|
|
121
|
-
console.log(
|
|
231
|
+
console.log(
|
|
232
|
+
logger.ServerCreditError(
|
|
233
|
+
`Ошибка кредитного планировщика: ${error.message}`,
|
|
234
|
+
),
|
|
235
|
+
);
|
|
122
236
|
}
|
|
123
|
-
});
|
|
237
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequelize-game-bank",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.1",
|
|
4
4
|
"description": "A Sequelize-based gaming economy and banking system featuring user profiles, credits, admin management, and detailed transaction history.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"bcrypt": "^6.0.0",
|
|
13
|
-
"chalk-palette": "^
|
|
13
|
+
"chalk-palette": "^4.1.0",
|
|
14
14
|
"cookie-parser": "^1.4.7",
|
|
15
|
-
"custom-color-logs": "^3.2.
|
|
15
|
+
"custom-color-logs": "^3.2.2",
|
|
16
16
|
"dotenv": "^18.0.0",
|
|
17
17
|
"express": "^5.2.1",
|
|
18
18
|
"express-rate-limit": "^8.7.0",
|