# 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`:


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

Если `enabled: false` — напишите в [Telegram](https://t.me/lumo_support_bot),
мы активируем.

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


```javascript
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:


```javascript
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();
}
```

Ответ:


```json
{
  "cardId": "8c7f1823-...",
  "cardNumberFull": "4466123412341234",
  "cardCvv": "123",
  "expiry": "03/29"
}
```

> ⚠️ **Безопасность.** На вашей стороне никогда не храните `cardNumberFull`
и `cardCvv` в БД. Запрашивайте в момент показа, держите в памяти на
время сессии, очищайте при закрытии экрана. Lumo ничего не кэширует —
каждый запрос идёт напрямую к эквайеру.


Рекомендуем auto-hide через 30 секунд:


```jsx
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):


```javascript
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` в профиле компании:


```json
{
  "event": "card.otp_received",
  "data": {
    "cardId": "8c7f1823-...",
    "code": "582130",
    "expiresAt": "2026-04-24T12:02:10Z"
  }
}
```

Моментально проталкивайте код пользователю в интерфейс, пуш-уведомлением
или Telegram-ботом.

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

Если пользователю нужен не только выпуск, но и возможность пополнять
карту USDT — используйте двухфазный recharge:


```javascript
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: История транзакций

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


```javascript
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%.

## Что дальше

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