Skip to content
Last updated

No-KYC виртуальные карты

Пошаговое руководство для продуктов, которым нужно выдавать пользователям виртуальные 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. Вы берёте с пользователя (фиат или крипта) столько, сколько хотите — разница ваша маржа.

Шаг 1: Подключение

Перед первым вызовом убедитесь, что у компании в Lumo включена программа карт. Проверить можно через GET /auth/company/profile:

{
  "cards": {
    "enabled": true,
    "openingFeeUsdt": 5,
    "commissionBps": 80,
    "minInitialDepositUsdt": 10,
    "testMode": false
  }
}

Если enabled: false — напишите в Telegram, мы активируем.

Шаг 2: Выпуск карты

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) списана с баланса компании автоматически.

Шаг 3: Показ PAN и CVC пользователю

Когда пользователь хочет вбить данные в онлайн-магазине, запросите полный номер и 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>
  );
}

Шаг 4: 3DS OTP

Когда пользователь делает покупку, эквайер присылает 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-ботом.

Шаг 5: Пополнение карты

Если пользователю нужен не только выпуск, но и возможность пополнять карту 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.

Шаг 6: История транзакций

Пользователь хочет видеть свои покупки — отдавайте через:

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.

Юнит-экономика

ПараметрЗначение (пример)
openingFeeUsdt5 USDT
minInitialDepositUsdt10 USDT
commissionBps на recharge80 (0.8%)

На каждом пополнении вы получаете commissionBps * сумма. На выпуске — openingFeeUsdt. Добавляйте свою маржу сверху — стандартная практика в таких продуктах 1–3%.

Что дальше

  • Обзор программы карт — полные параметры и жизненный цикл.
  • Webhooks по картам — все события и формат.
  • Тестируйте с cardsTestMode = true — все операции возвращают стабовые данные без расхода реального стока (запросите включение у поддержки).