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 });
|
||||
}
|
||||
});
|
||||
|
||||
// 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 billing = require('./billing');
|
||||
|
||||
function getClient() {
|
||||
const shopId = process.env.YUKASSA_SHOP_ID;
|
||||
const secret = process.env.YUKASSA_SECRET;
|
||||
if (!shopId || !secret) throw new Error('YUKASSA_SHOP_ID и YUKASSA_SECRET не настроены');
|
||||
async function getConfig() {
|
||||
const { rows } = await query(
|
||||
"SELECT key, value FROM app_settings WHERE key IN ('YUKASSA_SHOP_ID','YUKASSA_SECRET','YUKASSA_RETURN_URL')"
|
||||
);
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -31,14 +41,14 @@ async function createPayment({ userId, planCode, userEmail }) {
|
||||
if (!plan) throw new Error(`План "${planCode}" не найден`);
|
||||
if (plan.price_rub === 0) throw new Error('Free план не требует оплаты');
|
||||
|
||||
const returnUrl = process.env.YUKASSA_RETURN_URL || 'https://app.zeropost.ru/billing?paid=1';
|
||||
const client = getClient();
|
||||
const cfg = await getConfig();
|
||||
const client = getClient(cfg.shopId, cfg.secret);
|
||||
|
||||
const idempotenceKey = `${userId}-${planCode}-${Date.now()}`;
|
||||
const payment = await client.createPayment({
|
||||
amount: { value: String(plan.price_rub) + '.00', currency: 'RUB' },
|
||||
payment_method_data: { type: 'bank_card' },
|
||||
confirmation: { type: 'redirect', return_url: returnUrl },
|
||||
confirmation: { type: 'redirect', return_url: cfg.returnUrl },
|
||||
capture: true,
|
||||
description: `${plan.name} — ZeroPost`,
|
||||
metadata: { user_id: String(userId), plan_code: planCode },
|
||||
|
||||
Reference in New Issue
Block a user