# Оплата QR СБП Пошаговое руководство по интеграции оплаты товаров и услуг через QR-коды СБП. ## Сценарий Ваш пользователь хочет оплатить покупку в магазине криптовалютой: 1. Сканирует QR-код СБП на кассе 2. Ваше приложение показывает сумму в USDT 3. Пользователь подтверждает оплату 4. Магазин получает рубли через СБП ## Шаг 1: Получение QR-кода Пользователь сканирует QR-код камерой. Вы получаете URL вида: ``` https://qr.nspk.ru/AS10003P3RCT8NTGH6LR6D3P815L3SHK?type=02&bank=100000000001&sum=50000&cur=RUB&crc=F4AB ``` ## Шаг 2: Создание котировки Отправьте QR-код в API: ```javascript const response = await fetch('https://b2b.lumowallet.io/orders/prepare', { method: 'POST', headers: { 'X-API-Key': process.env.LUMO_API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ qrCode: qrCodeUrl, externalOrderId: `order-${Date.now()}` }) }); const quote = await response.json(); ``` Ответ: ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "amountRub": 5000.00, "amountUsdt": 52.63, "rate": 95.00, "expiresAt": "2026-03-10T12:00:30Z", "isBalanceSufficient": true, "merchantName": "ООО Кофейня" } ``` ## Шаг 3: Показ пользователю Отобразите информацию о платеже: ```jsx {quote.merchantName} {quote.amountRub} ₽ {quote.amountUsdt} USDT Курс: {quote.rate} RUB/USDT Оплатить ``` > ⚠️ **Важно:** Котировка действует 30 секунд. Показывайте таймер! ## Шаг 4: Подтверждение оплаты При нажатии "Оплатить": ```javascript async function confirmPayment(quoteId) { const response = await fetch( `https://b2b.lumowallet.io/orders/accept/${quoteId}`, { method: 'POST', headers: { 'X-API-Key': process.env.LUMO_API_KEY, 'Idempotency-Key': `confirm-${quoteId}` } } ); const order = await response.json(); return order; } ``` ## Шаг 5: Ожидание результата После подтверждения ордер в статусе `in_progress`. Дождитесь webhook или polling: ### Вариант A: Webhook (рекомендуется) ```javascript app.post('/webhooks/lumo', (req, res) => { const { event, orderId, status } = req.body; if (event === 'order.status_changed') { if (status === 'success') { notifyUser(orderId, 'Платёж успешен!'); } else if (status === 'failed') { notifyUser(orderId, 'Платёж не удался'); } } res.status(200).send('OK'); }); ``` ### Вариант B: Polling ```javascript async function waitForCompletion(orderId, maxAttempts = 30) { for (let i = 0; i < maxAttempts; i++) { const response = await fetch( `https://b2b.lumowallet.io/orders/${orderId}`, { headers: { 'X-API-Key': process.env.LUMO_API_KEY } } ); const order = await response.json(); if (['success', 'failed', 'expired_no_taker'].includes(order.status)) { return order; } await new Promise(r => setTimeout(r, 2000)); } throw new Error('Timeout waiting for order completion'); } ``` ## Шаг 6: Обработка результата ```javascript function handleOrderResult(order) { switch (order.status) { case 'success': showSuccess({ message: 'Оплата прошла успешно!', merchant: order.merchantName, amount: order.amountRub }); break; case 'failed': showError({ message: 'Платёж не удался', reason: order.failureReason, action: 'Попробуйте снова' }); break; case 'expired_no_taker': showError({ message: 'Время ожидания истекло', action: 'Создайте новый платёж' }); break; } } ``` ## Полный пример ```javascript class LumoPaymentService { constructor(apiKey) { this.apiKey = apiKey; this.baseUrl = 'https://b2b.lumowallet.io'; } async createQuote(qrCode) { const response = await fetch(`${this.baseUrl}/orders/prepare`, { method: 'POST', headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ qrCode }) }); if (!response.ok) { throw new Error(`Quote failed: ${response.status}`); } return response.json(); } async confirmQuote(quoteId) { const response = await fetch(`${this.baseUrl}/orders/accept/${quoteId}`, { method: 'POST', headers: { 'X-API-Key': this.apiKey, 'Idempotency-Key': `confirm-${quoteId}-${Date.now()}` } }); if (!response.ok) { throw new Error(`Accept failed: ${response.status}`); } return response.json(); } async getOrder(orderId) { const response = await fetch(`${this.baseUrl}/orders/${orderId}`, { headers: { 'X-API-Key': this.apiKey } }); return response.json(); } } // Использование const lumo = new LumoPaymentService(process.env.LUMO_API_KEY); async function processPayment(qrCode) { // 1. Создаём котировку const quote = await lumo.createQuote(qrCode); // 2. Проверяем баланс if (!quote.isBalanceSufficient) { throw new Error('Недостаточно средств'); } // 3. Показываем пользователю (ждём подтверждения) const confirmed = await showConfirmationUI(quote); if (!confirmed) { return { cancelled: true }; } // 4. Подтверждаем const order = await lumo.confirmQuote(quote.id); // 5. Ждём результат return await waitForCompletion(order.id); } ``` ## Тестирование Используйте staging-окружение: ```javascript const lumo = new LumoPaymentService(process.env.LUMO_API_KEY); lumo.baseUrl = 'https://b2b-staging.lumowallet.io'; ``` ## Чек-лист интеграции - [ ] Получение и парсинг QR-кода - [ ] Создание котировки с обработкой ошибок - [ ] Таймер 30 секунд для котировки - [ ] Проверка `isBalanceSufficient` - [ ] Idempotency-Key при подтверждении - [ ] Обработка webhook'ов - [ ] Fallback на polling - [ ] Обработка всех статусов ордера - [ ] UI для успеха/ошибки