feat(db): payments 拆为 subscriptions 与 topups
订阅权益与充值入账分表;topups 对齐上游充值单(无 quota_granted), paid_at=complete_time;quota_derived 查询层派生。
This commit is contained in:
+71
-23
@@ -4,17 +4,19 @@ import {
|
||||
auditLogs,
|
||||
engineStatus,
|
||||
members,
|
||||
payments,
|
||||
subscriptions,
|
||||
qpuHistory,
|
||||
sessions,
|
||||
topups,
|
||||
usageLogs,
|
||||
users,
|
||||
type ApiKeyRow,
|
||||
type AuditLogRow,
|
||||
type EngineStatusRow,
|
||||
type MemberRow,
|
||||
type PaymentRow,
|
||||
type SubscriptionRow,
|
||||
type SessionRow,
|
||||
type TopupRow,
|
||||
type UsageLogRow,
|
||||
type UserRow,
|
||||
} from "./schema.ts";
|
||||
@@ -384,41 +386,87 @@ export function createRepos(db: Db) {
|
||||
},
|
||||
},
|
||||
|
||||
payments: {
|
||||
async listAll(): Promise<PaymentRow[]> {
|
||||
return db.select().from(payments).orderBy(desc(payments.paid_at));
|
||||
subscriptions: {
|
||||
async listAll(): Promise<SubscriptionRow[]> {
|
||||
return db
|
||||
.select()
|
||||
.from(subscriptions)
|
||||
.orderBy(desc(subscriptions.period_start));
|
||||
},
|
||||
async findById(id: number): Promise<PaymentRow | null> {
|
||||
async findById(id: number): Promise<SubscriptionRow | null> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(payments)
|
||||
.where(eq(payments.id, id))
|
||||
.from(subscriptions)
|
||||
.where(eq(subscriptions.id, id))
|
||||
.limit(1);
|
||||
return rows[0] ?? null;
|
||||
},
|
||||
async insert(values: {
|
||||
kind: string;
|
||||
paid_at: Date;
|
||||
paid_amount: number;
|
||||
remark: string;
|
||||
period_start: Date | null;
|
||||
period_end: Date | null;
|
||||
subscription_id: number | null;
|
||||
quota_granted: number | null;
|
||||
}): Promise<PaymentRow> {
|
||||
const [row] = await db.insert(payments).values(values).returning();
|
||||
async findBySubscriptionId(
|
||||
subscriptionId: number,
|
||||
): Promise<SubscriptionRow | null> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(subscriptions)
|
||||
.where(eq(subscriptions.subscription_id, subscriptionId))
|
||||
.limit(1);
|
||||
return rows[0] ?? null;
|
||||
},
|
||||
async insert(
|
||||
values: typeof subscriptions.$inferInsert,
|
||||
): Promise<SubscriptionRow> {
|
||||
const [row] = await db.insert(subscriptions).values(values).returning();
|
||||
return row!;
|
||||
},
|
||||
async update(id: number, patch: Partial<typeof payments.$inferInsert>) {
|
||||
async update(
|
||||
id: number,
|
||||
patch: Partial<typeof subscriptions.$inferInsert>,
|
||||
) {
|
||||
const [row] = await db
|
||||
.update(payments)
|
||||
.update(subscriptions)
|
||||
.set(patch)
|
||||
.where(eq(payments.id, id))
|
||||
.where(eq(subscriptions.id, id))
|
||||
.returning();
|
||||
return row!;
|
||||
},
|
||||
async remove(id: number): Promise<void> {
|
||||
await db.delete(payments).where(eq(payments.id, id));
|
||||
await db.delete(subscriptions).where(eq(subscriptions.id, id));
|
||||
},
|
||||
},
|
||||
|
||||
topups: {
|
||||
async listAll(): Promise<TopupRow[]> {
|
||||
return db.select().from(topups).orderBy(desc(topups.paid_at));
|
||||
},
|
||||
async findById(id: number): Promise<TopupRow | null> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(topups)
|
||||
.where(eq(topups.id, id))
|
||||
.limit(1);
|
||||
return rows[0] ?? null;
|
||||
},
|
||||
async findByUpstreamId(upstreamId: number): Promise<TopupRow | null> {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(topups)
|
||||
.where(eq(topups.upstream_id, upstreamId))
|
||||
.limit(1);
|
||||
return rows[0] ?? null;
|
||||
},
|
||||
async insert(values: typeof topups.$inferInsert): Promise<TopupRow> {
|
||||
const [row] = await db.insert(topups).values(values).returning();
|
||||
return row!;
|
||||
},
|
||||
async update(id: number, patch: Partial<typeof topups.$inferInsert>) {
|
||||
const [row] = await db
|
||||
.update(topups)
|
||||
.set(patch)
|
||||
.where(eq(topups.id, id))
|
||||
.returning();
|
||||
return row!;
|
||||
},
|
||||
async remove(id: number): Promise<void> {
|
||||
await db.delete(topups).where(eq(topups.id, id));
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -117,20 +117,57 @@ export const usageLogs = pgTable(
|
||||
],
|
||||
);
|
||||
|
||||
export const payments = pgTable("payments", {
|
||||
/** 订阅权益登记(原 payments.kind=subscription) */
|
||||
export const subscriptions = pgTable(
|
||||
"subscriptions",
|
||||
{
|
||||
id: bigint("id", { mode: "number" }).primaryKey().generatedAlwaysAsIdentity(),
|
||||
subscription_id: bigint("subscription_id", { mode: "number" }).notNull().unique(),
|
||||
period_start: timestamp("period_start", {
|
||||
withTimezone: true,
|
||||
mode: "date",
|
||||
}).notNull(),
|
||||
period_end: timestamp("period_end", { withTimezone: true, mode: "date" }),
|
||||
quota_granted: bigint("quota_granted", { mode: "number" }).notNull(),
|
||||
paid_amount: numeric("paid_amount", {
|
||||
precision: 18,
|
||||
scale: 2,
|
||||
mode: "number",
|
||||
}),
|
||||
paid_at: timestamp("paid_at", { withTimezone: true, mode: "date" }),
|
||||
remark: text("remark").notNull().default(""),
|
||||
created_at: timestamp("created_at", {
|
||||
withTimezone: true,
|
||||
mode: "date",
|
||||
})
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
},
|
||||
(t) => [index("subscriptions_period_idx").on(t.period_start, t.period_end)],
|
||||
);
|
||||
|
||||
/** 充值入账事件(原 payments.kind=topup;无 quota_granted 列) */
|
||||
export const topups = pgTable("topups", {
|
||||
id: bigint("id", { mode: "number" }).primaryKey().generatedAlwaysAsIdentity(),
|
||||
kind: text("kind").notNull(),
|
||||
paid_at: timestamp("paid_at", { withTimezone: true, mode: "date" }).notNull(),
|
||||
paid_amount: numeric("paid_amount", {
|
||||
upstream_id: bigint("upstream_id", { mode: "number" }),
|
||||
amount: numeric("amount", { precision: 18, scale: 6, mode: "number" }).notNull(),
|
||||
money: numeric("money", {
|
||||
precision: 18,
|
||||
scale: 2,
|
||||
mode: "number",
|
||||
}).notNull(),
|
||||
trade_no: text("trade_no"),
|
||||
payment_method: text("payment_method"),
|
||||
payment_provider: text("payment_provider"),
|
||||
status: text("status"),
|
||||
paid_at: timestamp("paid_at", { withTimezone: true, mode: "date" }).notNull(),
|
||||
remark: text("remark").notNull().default(""),
|
||||
period_start: timestamp("period_start", { withTimezone: true, mode: "date" }),
|
||||
period_end: timestamp("period_end", { withTimezone: true, mode: "date" }),
|
||||
subscription_id: bigint("subscription_id", { mode: "number" }),
|
||||
quota_granted: bigint("quota_granted", { mode: "number" }),
|
||||
created_at: timestamp("created_at", {
|
||||
withTimezone: true,
|
||||
mode: "date",
|
||||
})
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
});
|
||||
|
||||
export const qpuHistory = pgTable("qpu_history", {
|
||||
@@ -207,7 +244,8 @@ export const engineStatus = pgTable("engine_status", {
|
||||
export type MemberRow = typeof members.$inferSelect;
|
||||
export type ApiKeyRow = typeof apiKeys.$inferSelect;
|
||||
export type UsageLogRow = typeof usageLogs.$inferSelect;
|
||||
export type PaymentRow = typeof payments.$inferSelect;
|
||||
export type SubscriptionRow = typeof subscriptions.$inferSelect;
|
||||
export type TopupRow = typeof topups.$inferSelect;
|
||||
export type QpuHistoryRow = typeof qpuHistory.$inferSelect;
|
||||
export type UserRow = typeof users.$inferSelect;
|
||||
export type SessionRow = typeof sessions.$inferSelect;
|
||||
|
||||
Reference in New Issue
Block a user