Пошаговое руководство для продуктов, которым нужно выдавать пользователям виртуальные VISA-карты без прохождения KYC.
Вы — криптокошелёк, необанк или любой потребительский сервис. Ваши пользователи хотят платить виртуальной картой за подписки и онлайн- покупки, но вы не хотите (и не можете быстро) настроить KYC-процесс.
С Lumo B2B ваш пользователь получает активную VISA с полным номером и CVC через ~2 секунды после нажатия кнопки — без паспорта, без селфи, без ожидания подтверждения.
- Ноль документов. KYC не требуется ни от вас, ни от пользователя.
- Мгновенно. Карта выдаётся из нашего стока — один HTTP-запрос.
- 3DS OTP через API. Мы отдаём код SMS-подтверждения по запросу. SIM-карта не нужна.
- Пополнение с USDT. Списываем с баланса компании в USDT — пользователь вообще не касается блокчейна.
- Полный PAN и CVC. Не только маска — реальные данные для ввода в интернет-магазине.
Пользователь ──► Ваш продукт ──► Lumo B2B API ──► Виртуальная VISA
│ │
│ Интернет-магазин
│ │
└────── USDT баланс ◄────── оплата / 3DS-кодДеньги списываются с USDT-баланса вашей компании в Lumo. Вы берёте с пользователя (фиат или крипта) столько, сколько хотите — разница ваша маржа.
Перед первым вызовом убедитесь, что у компании в Lumo включена программа карт. Проверить можно через GET /auth/company/profile:
{
"cards": {
"enabled": true,
"openingFeeUsdt": 5,
"commissionBps": 80,
"minInitialDepositUsdt": 10,
"testMode": false
}
}Если enabled: false — напишите в Telegram, мы активируем.
import { v4 as uuid } from 'uuid';
async function issueCard(userId) {
const idempotencyKey = uuid();
const res = await fetch('https://b2b.lumowallet.io/cards/request', {
method: 'POST',
headers: {
'X-API-Key': process.env.LUMO_API_KEY,
'Idempotency-Key': idempotencyKey,
},
});
const body = await res.json();
if (body.status === 'assigned') {
await db.cards.create({
userId,
lumoCardId: body.card.id,
maskedPan: body.card.maskedPan,
});
return body.card;
}
// status === 'pending' — карт в стоке нет, ждём webhook card.assigned
await db.cardRequests.create({
userId,
lumoRequestId: body.request.id,
});
return { status: 'queued' };
}При status: 'assigned' карта уже работает. Комиссия за выпуск (openingFeeUsdt) списана с баланса компании автоматически.
Когда пользователь хочет вбить данные в онлайн-магазине, запросите полный номер и CVC через отдельный endpoint:
async function revealCard(lumoCardId) {
const res = await fetch(
`https://b2b.lumowallet.io/cards/${lumoCardId}/sensitive`,
{ headers: { 'X-API-Key': process.env.LUMO_API_KEY } },
);
return res.json();
}Ответ:
{
"cardId": "8c7f1823-...",
"cardNumberFull": "4466123412341234",
"cardCvv": "123",
"expiry": "03/29"
}⚠️ Безопасность. На вашей стороне никогда не храните
cardNumberFullиcardCvvв БД. Запрашивайте в момент показа, держите в памяти на время сессии, очищайте при закрытии экрана. Lumo ничего не кэширует — каждый запрос идёт напрямую к эквайеру.
Рекомендуем auto-hide через 30 секунд:
function CardDetails({ cardId }) {
const [data, setData] = useState(null);
const [seconds, setSeconds] = useState(0);
const show = async () => {
setData(await revealCard(cardId));
setSeconds(30);
};
useEffect(() => {
if (seconds === 0) { setData(null); return; }
const t = setTimeout(() => setSeconds(s => s - 1), 1000);
return () => clearTimeout(t);
}, [seconds]);
if (!data) return <button onClick={show}>Показать карту</button>;
return (
<div>
<div>{data.cardNumberFull.replace(/(.{4})/g, '$1 ').trim()}</div>
<div>CVC: {data.cardCvv}</div>
<div>Скрыто через {seconds}s</div>
</div>
);
}Когда пользователь делает покупку, эквайер присылает SMS c 6-значным кодом. У нас нет вашей SIM — мы получаем код напрямую из почты и отдаём через API.
Вариант A — поллинг (если не хотите webhook):
async function waitForOtp(lumoCardId, timeoutMs = 90_000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const res = await fetch(
`https://b2b.lumowallet.io/cards/${lumoCardId}/otp/latest`,
{ headers: { 'X-API-Key': process.env.LUMO_API_KEY } },
);
const otp = await res.json();
if (otp && new Date(otp.expiresAt) > new Date()) return otp;
await sleep(5000);
}
throw new Error('OTP not received');
}Вариант B — webhook (рекомендуется):
Подпишитесь на card.otp_received в профиле компании:
{
"event": "card.otp_received",
"data": {
"cardId": "8c7f1823-...",
"code": "582130",
"expiresAt": "2026-04-24T12:02:10Z"
}
}Моментально проталкивайте код пользователю в интерфейс, пуш-уведомлением или Telegram-ботом.
Если пользователю нужен не только выпуск, но и возможность пополнять карту USDT — используйте двухфазный recharge:
async function rechargeCard(lumoCardId, usdAmount) {
// 1. Посчитать сколько USDT будет списано
const quote = await fetch('https://b2b.lumowallet.io/cards/recharge/quote', {
method: 'POST',
headers: {
'X-API-Key': process.env.LUMO_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ cardId: lumoCardId, targetAmount: usdAmount }),
}).then(r => r.json());
console.log(`Будет списано ${quote.totalDebitUsdt} USDT (включая ${quote.commissionUsdt} наша комиссия)`);
// 2. Подтвердить
const recharge = await fetch('https://b2b.lumowallet.io/cards/recharge', {
method: 'POST',
headers: {
'X-API-Key': process.env.LUMO_API_KEY,
'Idempotency-Key': uuid(),
'Content-Type': 'application/json',
},
body: JSON.stringify({ cardId: lumoCardId, targetAmount: usdAmount }),
}).then(r => r.json());
return recharge;
}Финальный статус (succeeded / failed / refunded) приходит webhook'ом card.recharge.* или обновляется в GET /cards/recharges.
Пользователь хочет видеть свои покупки — отдавайте через:
async function listUserTransactions(lumoCardId, before) {
const params = new URLSearchParams({ limit: '50' });
if (before) params.set('before', before);
const res = await fetch(
`https://b2b.lumowallet.io/cards/${lumoCardId}/transactions?${params}`,
{ headers: { 'X-API-Key': process.env.LUMO_API_KEY } },
);
return res.json();
}Для real-time — подписывайтесь на card.transaction.created webhook.
| Параметр | Значение (пример) |
|---|---|
openingFeeUsdt | 5 USDT |
minInitialDepositUsdt | 10 USDT |
commissionBps на recharge | 80 (0.8%) |
На каждом пополнении вы получаете commissionBps * сумма. На выпуске — openingFeeUsdt. Добавляйте свою маржу сверху — стандартная практика в таких продуктах 1–3%.
- Обзор программы карт — полные параметры и жизненный цикл.
- Webhooks по картам — все события и формат.
- Тестируйте с
cardsTestMode = true— все операции возвращают стабовые данные без расхода реального стока (запросите включение у поддержки).