Files
iistwin/server/billing/payment-provider.ts
Ильяс Султанов 701af06290 feat(billing): PaymentProvider + Mock, self-service пополнение баланса
Шаг 0.13 плана production-готовности:
- интерфейс PaymentProvider (createPayment/verifyWebhook/getPaymentStatus)
- MockPaymentProvider с настоящим webhook (sha256-подпись, идемпотентность)
- applySucceededPayment: транзакция с guard, credit, авто-снятие billingBlocked
- роуты payments + confirm-test (только mock) + публичный webhook
- Billing.tsx: Пополнить/Подтвердить (тест), фикс setLocation в рендере
- миграция 0080 billing_payments, 13 новых тестов (78/78)
2026-09-07 22:45:30 +03:00

57 lines
1.7 KiB
TypeScript

import { MockPaymentProvider } from "./mock-provider";
// Абстракция платёжного провайдера биллинга.
// Подключение реального провайдера (ЮKassa/CloudPayments) = новый класс,
// реализующий PaymentProvider, + case в getPaymentProvider(). Роуты и сервис не меняются.
export type PaymentStatus = 'pending' | 'succeeded' | 'canceled';
export interface CreatePaymentParams {
organizationId: number;
amount: number; // в основной валюте (рубли, не копейки)
currency: string;
idempotencyKey: string;
description?: string;
returnUrl?: string;
metadata?: Record<string, unknown>;
}
export interface PaymentCreated {
externalId: string;
confirmationUrl?: string;
status: PaymentStatus;
}
export interface WebhookVerification {
valid: boolean;
externalId?: string;
status?: PaymentStatus;
idempotencyKey?: string;
error?: string;
}
export interface PaymentStatusInfo {
externalId: string;
status: PaymentStatus;
}
export interface PaymentProvider {
name: string;
createPayment(params: CreatePaymentParams): Promise<PaymentCreated>;
verifyWebhook(headers: Record<string, unknown>, body: unknown): WebhookVerification;
getPaymentStatus(externalId: string): Promise<PaymentStatusInfo>;
}
export function getPaymentProviderName(): string {
return (process.env.PAYMENT_PROVIDER || 'mock').trim().toLowerCase();
}
export function getPaymentProvider(name: string = getPaymentProviderName()): PaymentProvider {
switch (name) {
case 'mock':
return new MockPaymentProvider();
default:
throw new Error(`Неизвестный платёжный провайдер: ${name}`);
}
}