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)
This commit is contained in:
56
server/billing/payment-provider.ts
Normal file
56
server/billing/payment-provider.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user