Пошаговое руководство по интеграции оплаты товаров и услуг через QR-коды СБП.
Ваш пользователь хочет оплатить покупку в магазине криптовалютой:
- Сканирует QR-код СБП на кассе
- Ваше приложение показывает сумму в USDT
- Пользователь подтверждает оплату
- Магазин получает рубли через СБП
Пользователь сканирует QR-код камерой. Вы получаете URL вида:
https://qr.nspk.ru/AS10003P3RCT8NTGH6LR6D3P815L3SHK?type=02&bank=100000000001&sum=50000&cur=RUB&crc=F4ABОтправьте QR-код в API:
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();Ответ:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"amountRub": 5000.00,
"amountUsdt": 52.63,
"rate": 95.00,
"expiresAt": "2026-03-10T12:00:30Z",
"isBalanceSufficient": true,
"merchantName": "ООО Кофейня"
}Отобразите информацию о платеже:
<PaymentConfirmation>
<MerchantName>{quote.merchantName}</MerchantName>
<Amount>{quote.amountRub} ₽</Amount>
<CryptoAmount>{quote.amountUsdt} USDT</CryptoAmount>
<Rate>Курс: {quote.rate} RUB/USDT</Rate>
<Timer expiresAt={quote.expiresAt} />
<ConfirmButton onClick={confirmPayment}>
Оплатить
</ConfirmButton>
</PaymentConfirmation>⚠️ Важно: Котировка действует 30 секунд. Показывайте таймер!
При нажатии "Оплатить":
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;
}После подтверждения ордер в статусе in_progress. Дождитесь webhook или polling:
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');
});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');
}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;
}
}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-окружение:
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 для успеха/ошибки