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:
@@ -87,6 +87,26 @@ export const billingTransactions = pgTable("billing_transactions", {
|
||||
export type BillingTransaction = typeof billingTransactions.$inferSelect;
|
||||
export type InsertBillingTransaction = typeof billingTransactions.$inferInsert;
|
||||
|
||||
// Billing payments (self-service top-ups via payment provider)
|
||||
export const billingPayments = pgTable("billing_payments", {
|
||||
id: serial("id").primaryKey(),
|
||||
organizationId: integer("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
||||
provider: varchar("provider", { length: 30 }).notNull(),
|
||||
externalId: varchar("external_id", { length: 255 }).notNull().unique("billing_payments_external_id_key"),
|
||||
amount: numeric("amount", { precision: 10, scale: 2 }).notNull(),
|
||||
currency: varchar("currency", { length: 10 }).notNull().default("RUB"),
|
||||
status: varchar("status", { length: 20 }).notNull().default("pending"), // 'pending' | 'succeeded' | 'canceled'
|
||||
idempotencyKey: varchar("idempotency_key", { length: 255 }).notNull().unique("billing_payments_idempotency_key_key"),
|
||||
metadata: jsonb("metadata"),
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
updatedAt: timestamp("updated_at").defaultNow(),
|
||||
}, (table) => ({
|
||||
orgStatusIdx: index("billing_payments_org_status_idx").on(table.organizationId, table.status),
|
||||
}));
|
||||
|
||||
export type BillingPayment = typeof billingPayments.$inferSelect;
|
||||
export type InsertBillingPayment = typeof billingPayments.$inferInsert;
|
||||
|
||||
// Users table
|
||||
export const users = pgTable("users", {
|
||||
id: serial("id").primaryKey(),
|
||||
|
||||
Reference in New Issue
Block a user