Шаг 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)
57 lines
1.7 KiB
TypeScript
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}`);
|
|
}
|
|
}
|