forked from admin/zeropost-engine
feat: yukassa reads keys from app_settings + monthly reset endpoint
- yukassa.js: getConfig() читает YUKASSA_SHOP_ID/SECRET/RETURN_URL из app_settings (fallback env) - routes/billing.js: POST /monthly-reset — запускает processMonthlyResets() - app_settings: категория 'payments' с ключами ЮKassa - cron: 0 6 * * * zeropost-billing-reset.sh
This commit is contained in:
@@ -108,3 +108,15 @@ router.post('/webhook', express.json({ type: '*/*' }), async (req, res) => {
|
|||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// POST /api/billing/monthly-reset — ежемесячный сброс кредитов (вызывается cron)
|
||||||
|
router.post('/monthly-reset', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const processed = await billing.processMonthlyResets();
|
||||||
|
console.log(`[Billing] monthly reset: ${processed} users processed`);
|
||||||
|
res.json({ ok: true, processed });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Billing] monthly-reset error:', err.message);
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
+17
-7
@@ -15,10 +15,20 @@ const { YooCheckout } = require('@a2seven/yoo-checkout');
|
|||||||
const { query } = require('../config/db');
|
const { query } = require('../config/db');
|
||||||
const billing = require('./billing');
|
const billing = require('./billing');
|
||||||
|
|
||||||
function getClient() {
|
async function getConfig() {
|
||||||
const shopId = process.env.YUKASSA_SHOP_ID;
|
const { rows } = await query(
|
||||||
const secret = process.env.YUKASSA_SECRET;
|
"SELECT key, value FROM app_settings WHERE key IN ('YUKASSA_SHOP_ID','YUKASSA_SECRET','YUKASSA_RETURN_URL')"
|
||||||
if (!shopId || !secret) throw new Error('YUKASSA_SHOP_ID и YUKASSA_SECRET не настроены');
|
);
|
||||||
|
const s = Object.fromEntries(rows.map(r => [r.key, r.value?.trim()]));
|
||||||
|
return {
|
||||||
|
shopId: s.YUKASSA_SHOP_ID || process.env.YUKASSA_SHOP_ID || '',
|
||||||
|
secret: s.YUKASSA_SECRET || process.env.YUKASSA_SECRET || '',
|
||||||
|
returnUrl: s.YUKASSA_RETURN_URL || process.env.YUKASSA_RETURN_URL || 'https://app.zeropost.ru/billing?paid=1',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClient(shopId, secret) {
|
||||||
|
if (!shopId || !secret) throw new Error('YUKASSA_SHOP_ID и YUKASSA_SECRET не настроены в app_settings');
|
||||||
return new YooCheckout({ shopId, secretKey: secret });
|
return new YooCheckout({ shopId, secretKey: secret });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,14 +41,14 @@ async function createPayment({ userId, planCode, userEmail }) {
|
|||||||
if (!plan) throw new Error(`План "${planCode}" не найден`);
|
if (!plan) throw new Error(`План "${planCode}" не найден`);
|
||||||
if (plan.price_rub === 0) throw new Error('Free план не требует оплаты');
|
if (plan.price_rub === 0) throw new Error('Free план не требует оплаты');
|
||||||
|
|
||||||
const returnUrl = process.env.YUKASSA_RETURN_URL || 'https://app.zeropost.ru/billing?paid=1';
|
const cfg = await getConfig();
|
||||||
const client = getClient();
|
const client = getClient(cfg.shopId, cfg.secret);
|
||||||
|
|
||||||
const idempotenceKey = `${userId}-${planCode}-${Date.now()}`;
|
const idempotenceKey = `${userId}-${planCode}-${Date.now()}`;
|
||||||
const payment = await client.createPayment({
|
const payment = await client.createPayment({
|
||||||
amount: { value: String(plan.price_rub) + '.00', currency: 'RUB' },
|
amount: { value: String(plan.price_rub) + '.00', currency: 'RUB' },
|
||||||
payment_method_data: { type: 'bank_card' },
|
payment_method_data: { type: 'bank_card' },
|
||||||
confirmation: { type: 'redirect', return_url: returnUrl },
|
confirmation: { type: 'redirect', return_url: cfg.returnUrl },
|
||||||
capture: true,
|
capture: true,
|
||||||
description: `${plan.name} — ZeroPost`,
|
description: `${plan.name} — ZeroPost`,
|
||||||
metadata: { user_id: String(userId), plan_code: planCode },
|
metadata: { user_id: String(userId), plan_code: planCode },
|
||||||
|
|||||||
Reference in New Issue
Block a user