Платежи и СБП
Интеграция с банковской системой РФ
Платежи и СБП
Система быстрых платежей (СБП)
Интеграция с СБП — основной канал ввода/вывода ликвидности для розничных клиентов.
Пользовательские сценарии
-
C2B (Пополнение кошелька):
- Клиент выбирает сумму в приложении.
- Получает DeepLink на банковское приложение.
- Подтверждает платеж в банке.
- Деньги зачисляются мгновенно (SLA < 15 сек).
-
C2B (Оплата покупок):
- Клиент сканирует QR-код на кассе магазина приложением "Манитон".
- Система списывает CFA-RUB.
- Система отправляет рубли мерчанту через банк-агент.
Диаграмма потока (QR-платеж)
Loading diagram...
Сверки и казначейство
Для обеспечения финансовой устойчивости используется модель ежедневного сведения (Daily Reconcile):
- End-of-Day Check: В 23:59:59 остаток на номинальном счете в банке сверяется с объемом выпущенных токенов
TotalSupply(CFA-RUB). - Расхождения: При обнаружении разницы (например, платеж прошел в банке, но не дошел вебхук) запускается процедура ручного разбора (Manual Adjustment), которая создает корректирующую проводку.
2) API (gRPC Connect)
CreatePaymentIntent
Создание намерения платежа для пополнения баланса.
message CreatePaymentIntentRequest {
RequestContext context = 1;
string user_id = 2;
Money amount = 3;
PaymentMethod method = 4;
}
message CreatePaymentIntentResponse {
PaymentIntent intent = 1;
}HandlePaymentWebhook
Обработка вебхука от банка о статусе платежа.
message HandlePaymentWebhookRequest {
RequestContext context = 1;
PaymentWebhook webhook = 2;
}
message PaymentWebhook {
string payment_id = 1;
PaymentStatus status = 2;
Money amount = 3;
string provider_payload = 4;
google.protobuf.Timestamp received_at = 5;
}InitiatePayout
Инициация вывода средств пользователю.
message InitiatePayoutRequest {
RequestContext context = 1;
PayoutRequest payout = 2;
}
message PayoutRequest {
string payout_id = 1;
string user_id = 2;
Money amount = 3;
string bank_account = 4;
}Reconcile
Сверка балансов банковского счета и выпущенных CFA-RUB.
message ReconcileRequest {
RequestContext context = 1;
google.protobuf.Timestamp business_date = 2;
}
message ReconcileResponse {
ReconcileReport report = 1;
}
message ReconcileReport {
string report_id = 1;
Money bank_balance = 2;
Money token_supply = 3;
bool balanced = 4;
string notes = 5;
}3) Use Cases
CreatePaymentIntentUseCase
Создание намерения платежа и генерация QR-кода.
async execute(request: CreatePaymentIntentRequest): Promise<CreatePaymentIntentResponse> {
const { context, userId, amount, method } = request;
// 1. Проверка лимитов
const limitsCheck = await this.identityClient.checkLimits(
context,
userId,
LimitType.DEPOSIT,
amount,
OperationType.DEPOSIT
);
if (!limitsCheck.allowed) {
throw new Error(limitsCheck.reason);
}
// 2. Создание PaymentIntent
const paymentIntent = new PaymentIntent(
crypto.randomUUID(),
userId,
amount,
method,
PaymentStatus.PENDING
);
// 3. Генерация QR-кода
if (method === PaymentMethod.SBP_QR) {
const qr = await this.sbpClient.createQR(
Number(amount.amount.value),
'Пополнение баланса'
);
paymentIntent.qrPayload = qr.qrPayload;
paymentIntent.paymentUrl = qr.paymentUrl;
}
// 4. Сохранение
await this.paymentIntentRepository.save(paymentIntent);
// 5. Публикация события
await this.eventPublisher.publish({
type: 'PaymentIntentCreated',
payload: paymentIntent,
});
return { intent: paymentIntent };
}HandlePaymentWebhookUseCase
Обработка вебхука от банка.
async execute(request: HandlePaymentWebhookRequest): Promise<HandlePaymentWebhookResponse> {
const { context, webhook } = request;
// 1. Валидация подписи
if (!this.validateSignature(webhook)) {
throw new Error('Invalid signature');
}
// 2. Проверка суммы
const paymentIntent = await this.paymentIntentRepository.findById(webhook.paymentId);
if (!paymentIntent) {
throw new Error('Payment intent not found');
}
if (webhook.amount.amount.value !== paymentIntent.amount.amount.value) {
throw new Error('Amount mismatch');
}
// 3. Обновление статуса
paymentIntent.status = webhook.status;
await this.paymentIntentRepository.save(paymentIntent);
// 4. Публикация события
if (webhook.status === PaymentStatus.SUCCESS) {
await this.eventPublisher.publish({
type: 'FiatDeposited',
payload: {
userId: paymentIntent.userId,
amount: paymentIntent.amount,
paymentId: paymentIntent.paymentId,
},
});
}
return { intent: paymentIntent };
}ReconcileUseCase
Сверка балансов.
async execute(request: ReconcileRequest): Promise<ReconcileResponse> {
const { context, businessDate } = request;
// 1. Получение баланса банка
const bankBalance = await this.bankClient.getBalance();
// 2. Получение supply CFA-RUB
const cfaRubSupply = await this.cfaClient.getSupply('CFA-RUB');
// 3. Сравнение
const balanced = bankBalance.amount.value === cfaRubSupply.amount.value;
// 4. Создание отчета
const report = {
reportId: crypto.randomUUID(),
businessDate,
bankBalance,
tokenSupply: cfaRubSupply,
balanced,
notes: balanced ? 'Balanced' : 'Discrepancy detected',
};
// 5. Сохранение отчета
await this.reconciliationReportRepository.save(report);
// 6. Алерт если не совпадает
if (!balanced) {
await this.alertService.sendAlert({
severity: 'critical',
message: 'Reconciliation failed',
details: report,
});
}
return { report };
}4) Мониторинг
Метрики
export const paymentMetrics = {
paymentIntentsCreatedTotal: new Counter({
name: 'payment_intents_created_total',
help: 'Total number of payment intents created',
labelNames: ['method'],
}),
paymentWebhooksReceivedTotal: new Counter({
name: 'payment_webhooks_received_total',
help: 'Total number of payment webhooks received',
labelNames: ['status'],
}),
paymentProcessingDuration: new Histogram({
name: 'payment_processing_duration_seconds',
help: 'Time spent processing payments',
labelNames: ['method'],
buckets: [0.1, 0.5, 1, 2, 5, 10, 30, 60],
}),
reconciliationDuration: new Histogram({
name: 'reconciliation_duration_seconds',
help: 'Time spent on reconciliation',
buckets: [1, 5, 10, 30, 60],
}),
};5) Troubleshooting
Проблема: Вебхук не приходит
Диагностика:
# Проверка логов сервиса
kubectl logs -n maniton payments-service
# Проверка доступности endpoint
curl -X POST https://payments-service/webhook \
-H "Content-Type: application/json" \
-d '{"test": "data"}'
# Проверка конфигурации вебхука
kubectl get configmap payments-service -o yamlРешение:
- Проверьте, что endpoint доступен извне
- Проверьте конфигурацию CORS
- Проверьте логи безопасности (WAF)
Проблема: Сверка не проходит
Диагностика:
# Проверка баланса банка
curl https://bank-api.example.com/balance
# Проверка supply CFA-RUB
curl https://cfa-core.example.com/supply?instrument=CFA-RUB
# Проверка отчета сверки
curl https://payments-service.example.com/reconciliation/latestРешение:
- Проверьте синхронизацию времени
- Проверьте транзакции, которые не прошли
- Ручной разбор расхождений