Разработка системы конвертации крипто в фиат при оплате картой
Пользователь держит USDC, хочет заплатить картой в обычном магазине. В момент транзакции по карте: USDC списывается с крипто-баланса, конвертируется в USD/EUR, и процессинг карты проходит как обычная фиатная оплата. С точки зрения терминала — обычная Visa/Mastercard транзакция. Это не теория, это работающая архитектура за такими продуктами как Crypto.com Card, Binance Card, Coinbase Card.
Построить такую систему с нуля — значит решить несколько независимых инженерных задач: эмиссия карт, real-time конвертация, BIN спонсорство и лицензирование, compliance. Каждая из них — отдельный трек работы.
Архитектурные компоненты
BIN спонсорство и эмиссия карт
BIN (Bank Identification Number) — первые 6–8 цифр карты, определяют эмитента. Без собственной банковской лицензии работают через BIN sponsor: лицензированный банк-эмитент, который выпускает карты под своим BIN, но по вашей логике. Вы — program manager.
Основные BIN sponsors и card issuing платформы:
| Провайдер | Сети | Регионы | API |
|---|---|---|---|
| Marqeta | Visa, Mastercard | US, EU, UK | REST + Webhooks |
| Galileo | Visa | US, LATAM | REST |
| Stripe Issuing | Visa, Mastercard | US, EU | REST |
| Moorwand | Mastercard | EU/EEA | REST |
| Railsbank (Railsr) | Visa, Mastercard | EU, UK | REST |
Marqeta — наиболее распространён в crypto-card проектах (Coinbase Card использует Marqeta). Just-In-Time (JIT) funding — ключевая фича: Marqeta вызывает ваш webhook в момент авторизации, вы подтверждаете или отклоняете транзакцию с конвертацией в реальном времени.
Just-In-Time funding: логика в реальном времени
JIT — сердце архитектуры. Схема:
Карточный терминал → Visa/MC network → Marqeta → ваш JIT webhook (< 2 сек) →
[вы конвертируете крипто → фиат] → ответ approve/decline → Marqeta → терминал
Время ответа на JIT webhook: строго менее 2 секунд. Это аппаратный таймаут карточных сетей. Пропустил — транзакция автоматически отклоняется.
// JIT webhook handler
import { FastifyRequest, FastifyReply } from 'fastify';
import { MarqetaJITPayload } from './types';
export async function handleJITFunding(
req: FastifyRequest<{ Body: MarqetaJITPayload }>,
reply: FastifyReply,
) {
const startTime = Date.now();
const { transaction, card_token } = req.body;
try {
// 1. Идентификация пользователя по card_token
const user = await userService.findByCardToken(card_token);
if (!user) return reply.send({ result: 'DECLINED', memo: 'USER_NOT_FOUND' });
// 2. Получаем сумму в фиат валюте
const { currency_code, amount } = transaction;
// 3. Рассчитываем крипто-сумму для списания
const cryptoAmount = await pricingService.fiatToCrypto({
fiatAmount: amount,
fiatCurrency: currency_code,
cryptoCurrency: user.primaryAsset, // USDC, BTC, ETH
});
// 4. Проверяем баланс
const balance = await walletService.getBalance(user.id, user.primaryAsset);
if (balance < cryptoAmount.amountWithFee) {
return reply.send({ result: 'DECLINED', memo: 'INSUFFICIENT_FUNDS' });
}
// 5. Резервируем крипто (hold, не списываем сразу)
const holdId = await walletService.createHold({
userId: user.id,
asset: user.primaryAsset,
amount: cryptoAmount.amountWithFee,
expiresAt: new Date(Date.now() + 30 * 60 * 1000), // 30 мин
transactionRef: transaction.token,
});
// Проверяем что укладываемся в таймаут
if (Date.now() - startTime > 1500) {
await walletService.releaseHold(holdId);
return reply.send({ result: 'DECLINED', memo: 'TIMEOUT' });
}
return reply.send({
result: 'APPROVED',
funding: { amount, currency_code },
});
} catch (error) {
logger.error({ error, transaction }, 'JIT funding error');
return reply.send({ result: 'DECLINED', memo: 'INTERNAL_ERROR' });
}
}
Hold механизм критически важен. Авторизация карты и фактическое списание (clearing) — разные события. Между ними может пройти до 5 дней (особенно для оффлайн транзакций). Hold резервирует крипто-баланс, реальная конвертация происходит при clearing.
Конвертация крипто → фиат
В момент clearing нужно реально конвертировать криптовалюту в фиат для пополнения карточного счёта. Два подхода:
Pre-funded fiat float. Вы держите фиатный баланс на счету BIN-спонсора. При clearing — списываете из float, параллельно конвертируете крипто и пополняете float. Преимущество: декоуплинг от скорости крипто-конвертации. Недостаток: нужен significant рабочий капитал (float).
Real-time liquidation. При clearing немедленно продаёте крипто через CEX или liquidity провайдера. Деньги идут напрямую на карточный счёт.
// Clearing handler (вызывается webhook от Marqeta при settlement)
export async function handleClearing(clearingEvent: MarqetaClearingEvent) {
const { original_jit_funding_token, clearing_amount, currency_code } = clearingEvent;
// Найти original hold по JIT token
const hold = await holdRepository.findByTransactionRef(original_jit_funding_token);
// Рассчитать финальную крипто-сумму (курс мог измениться)
const finalCryptoAmount = await pricingService.fiatToCrypto({
fiatAmount: clearing_amount,
fiatCurrency: currency_code,
cryptoCurrency: hold.asset,
slippage: 0.005, // 0.5% slippage tolerance
});
// Выполнить конвертацию через liquidity провайдера
const conversion = await liquidityService.convert({
fromAsset: hold.asset,
toFiat: currency_code,
amount: finalCryptoAmount.amount,
destinationAccount: BIN_SPONSOR_ACCOUNT_ID,
});
// Финальное списание с крипто-баланса пользователя
await walletService.finalizeConversion({
userId: hold.userId,
holdId: hold.id,
cryptoAmount: finalCryptoAmount.amount,
fiatAmount: clearing_amount,
conversionRate: conversion.rate,
fee: finalCryptoAmount.fee,
});
// Уведомление пользователю
await notificationService.sendPushNotification(hold.userId, {
type: 'card_payment_settled',
amount: clearing_amount,
currency: currency_code,
cryptoSpent: finalCryptoAmount.amount,
cryptoAsset: hold.asset,
merchantName: clearingEvent.merchant_name,
});
}
Liquidity providers и FX
Для конвертации крипто → фиат в автоматическом режиме:
CEX через API: Binance, Coinbase Prime, Kraken. Подходит для средних объёмов. Риск: API latency, биржа может быть недоступна. Нужен failover на второй provider.
OTC / Prime Brokers: Galaxy Digital, FalconX, Cumberland. Для крупных объёмов ($100k+) дают лучшие ставки, работают через API или RFQ (Request for Quote). Минимальная сделка обычно $10k–$50k — не подходит для розничных транзакций напрямую, но хорошо для пополнения float.
Embedded liquidity: интеграция с Fireblocks Settlement Network или B2C2. Программный доступ, фиксированные спреды, enterprise SLA.
// Абстракция для multi-provider liquidity
interface LiquidityProvider {
getQuote(params: QuoteParams): Promise<Quote>;
executeConversion(quoteId: string): Promise<ConversionResult>;
getBalance(currency: string): Promise<Decimal>;
}
class LiquidityRouter implements LiquidityProvider {
private providers: LiquidityProvider[];
async getQuote(params: QuoteParams): Promise<Quote> {
// Запрашиваем котировки у всех провайдеров параллельно
const quotes = await Promise.allSettled(
this.providers.map(p => p.getQuote(params))
);
// Выбираем лучшую котировку (по rate с учётом fee)
const validQuotes = quotes
.filter(q => q.status === 'fulfilled')
.map(q => (q as PromiseFulfilledResult<Quote>).value);
return validQuotes.sort((a, b) => b.netRate - a.netRate)[0];
}
}
Compliance и KYC/AML
Обязательные компоненты
KYC (Know Your Customer). Обязателен для карточного продукта. Минимум: проверка личности (паспорт/ID), proof of address, OFAC screening. Провайдеры: Sumsub, Jumio, Onfido. Уровни верификации влияют на лимиты (базовый: $500/день, enhanced: $10,000/день).
Transaction monitoring. AML требования: мониторинг подозрительных паттернов, structuring detection (дробление на малые суммы), unusual merchant categories. Провайдеры: Chainalysis, Elliptic для крипто-стороны; ComplyAdvantage для фиат-стороны.
Лицензирование. В EU: EMI лицензия (Electronic Money Institution) или партнёрство с лицензированным EMI. В US: Money Transmitter Licence в каждом штате, или партнёрство с licensed issuer. Получение лицензии: 6–18 месяцев, $100k–$500k. Альтернатива: работа под лицензией BIN-спонсора (быстрее, но ограниченнее).
Налоговые последствия для пользователей
Конвертация крипто при оплате картой — taxable event во многих юрисдикциях (US, UK, большинство EU). Система должна:
// Генерация tax-lot записей для каждой конвертации
interface TaxLot {
userId: string;
asset: string;
acquiredDate: Date;
acquiredCostBasis: Decimal; // цена покупки в фиат
disposedDate: Date;
disposalProceeds: Decimal; // сумма транзакции по карте
gainLoss: Decimal; // прибыль/убыток
transactionRef: string;
}
Предоставление tax report (1099 в US, аналогичное в EU) — не опционально для лицензированных операций.
Карточные механики: физические и виртуальные
Виртуальные карты — выпускаются мгновенно через Marqeta API, используются для Apple Pay / Google Pay. Приоритет для быстрого MVP.
Физические карты — нужен card personalisation bureau (Matica, Entrust). Срок производства: 5–14 дней. Delivery tracking интеграция.
// Marqeta: создание виртуальной карты
const card = await marqeta.cards.create({
user_token: marqetaUserId,
card_product_token: CARD_PRODUCT_TOKEN,
fulfillment: { shipping: { method: 'GROUND' } }, // для физических
});
// Provisioning в Apple Pay / Google Pay
const tokenizationData = await marqeta.digitalWallets.provisionApplePay({
card_token: card.token,
provisioning_payload: applePayProvisioningRequest,
certificates: [...],
});
Freeze/unfreeze — пользователь должен мочь мгновенно заморозить карту через приложение. Marqeta API: cards/{token}/transitions с state: SUSPENDED.
Этапы и сроки разработки
| Фаза | Содержание | Срок |
|---|---|---|
| Setup & licensing | Выбор BIN-спонсора, юридическая структура, KYC провайдер | 4–8 нед |
| Core wallet | Крипто-кошелёк, балансы, holds | 3–4 нед |
| JIT funding | Webhook, pricing engine, hold механизм | 3–4 нед |
| Liquidity | FX интеграция, конвертация при clearing | 2–3 нед |
| Card management | Виртуальные карты, Marqeta integration | 3–4 нед |
| Compliance | AML мониторинг, tax reporting | 3–4 нед |
| Physical cards | Производство, delivery | 2–3 нед |
| Testing & launch | End-to-end, UAT, soft launch | 3–4 нед |
Реалистичный срок от нуля до рабочего продукта с виртуальными картами: 6–9 месяцев. Основные блокеры — legal/compliance onboarding с BIN-спонсором и KYC провайдером, а не разработка.







