feat: YuKassa payment integration

- services/yukassa.js: createPayment, handleWebhook
  createPayment → создаёт платёж + сохраняет в payment_orders
  handleWebhook → activates plan + charges credits on payment.succeeded
- routes/billing.js: POST /checkout, POST /webhook (публичный)
- DB: payment_orders table
- index.js: /api/billing/webhook публичный (до auth middleware)
This commit is contained in:
Ник (Claude)
2026-06-11 18:44:20 +03:00
parent 2e60a6e316
commit 9baa0f0959
5 changed files with 218 additions and 1 deletions
+29 -1
View File
@@ -1,6 +1,7 @@
const express = require('express');
const router = express.Router();
const billing = require('../services/billing');
const billing = require('../services/billing');
const yukassa = require('../services/yukassa');
const { query } = require('../config/db');
function uid(req) { return req.headers['x-user-id'] ? parseInt(req.headers['x-user-id']) : null; }
@@ -80,3 +81,30 @@ router.get('/admin/users', async (req, res) => {
});
module.exports = router;
// POST /api/billing/checkout — создать платёж ЮKassa
router.post('/checkout', async (req, res) => {
const userId = uid(req);
if (!userId) return res.status(401).json({ error: 'x-user-id required' });
const { plan_code } = req.body;
if (!plan_code) return res.status(400).json({ error: 'plan_code required' });
try {
const { rows: [user] } = await query('SELECT email FROM users WHERE id=$1', [userId]);
const result = await yukassa.createPayment({ userId, planCode: plan_code, userEmail: user?.email });
res.json(result);
} catch (err) {
console.error('[billing] checkout:', err.message);
res.status(500).json({ error: err.message });
}
});
// POST /api/billing/webhook — вебхук ЮKassa (без auth, проверка по IP/подписи)
router.post('/webhook', express.json({ type: '*/*' }), async (req, res) => {
try {
const result = await yukassa.handleWebhook(req.body);
res.json({ ok: true, ...result });
} catch (err) {
console.error('[billing] webhook error:', err.message);
res.status(500).json({ error: err.message });
}
});