Compare commits
2 Commits
d96ea91815
...
f0634c2e49
| Author | SHA1 | Date |
|---|---|---|
|
|
f0634c2e49 | |
|
|
df3b1a6ec6 |
|
|
@ -1,4 +1,4 @@
|
||||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
export const InvoiceItemType = {
|
export const InvoiceItemType = {
|
||||||
SUBSCRIPTION: 'subscription',
|
SUBSCRIPTION: 'subscription',
|
||||||
|
|
@ -8,7 +8,7 @@ export const InvoiceItemType = {
|
||||||
} as const;
|
} as const;
|
||||||
export type InvoiceItemType = typeof InvoiceItemType[keyof typeof InvoiceItemType];
|
export type InvoiceItemType = typeof InvoiceItemType[keyof typeof InvoiceItemType];
|
||||||
|
|
||||||
@Entity({ name: 'invoice_items', schema: 'public' })
|
@Entity({ name: 'billing_invoice_items', schema: 'public' })
|
||||||
export class InvoiceItem {
|
export class InvoiceItem {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
@ -22,8 +22,8 @@ export class InvoiceItem {
|
||||||
@Column({ name: 'item_type', type: 'varchar', length: 30 })
|
@Column({ name: 'item_type', type: 'varchar', length: 30 })
|
||||||
itemType!: InvoiceItemType; // 'subscription' | 'overage' | 'credit' | 'adjustment'
|
itemType!: InvoiceItemType; // 'subscription' | 'overage' | 'credit' | 'adjustment'
|
||||||
|
|
||||||
@Column({ type: 'numeric', precision: 12, scale: 4 })
|
@Column({ type: 'bigint', default: 1 })
|
||||||
quantity!: number; // 1 for subscription, 2.5 for 2.5M tokens overage
|
quantity!: number; // 1 for subscription, token count for overage
|
||||||
|
|
||||||
@Column({ name: 'unit_price', type: 'int' })
|
@Column({ name: 'unit_price', type: 'int' })
|
||||||
unitPrice!: number; // in cents/fen
|
unitPrice!: number; // in cents/fen
|
||||||
|
|
@ -33,4 +33,7 @@ export class InvoiceItem {
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 3, default: 'USD' })
|
@Column({ type: 'varchar', length: 3, default: 'USD' })
|
||||||
currency!: string;
|
currency!: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt!: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, OneToMany } from 'typeorm';
|
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
|
||||||
import { InvoiceItem } from './invoice-item.entity';
|
|
||||||
|
|
||||||
export const InvoiceStatus = {
|
export const InvoiceStatus = {
|
||||||
DRAFT: 'draft',
|
DRAFT: 'draft',
|
||||||
|
|
@ -17,7 +16,7 @@ export const InvoiceCurrency = {
|
||||||
} as const;
|
} as const;
|
||||||
export type InvoiceCurrency = typeof InvoiceCurrency[keyof typeof InvoiceCurrency];
|
export type InvoiceCurrency = typeof InvoiceCurrency[keyof typeof InvoiceCurrency];
|
||||||
|
|
||||||
@Entity({ name: 'invoices', schema: 'public' })
|
@Entity({ name: 'billing_invoices', schema: 'public' })
|
||||||
export class Invoice {
|
export class Invoice {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
@ -61,9 +60,9 @@ export class Invoice {
|
||||||
@Column({ name: 'paid_at', type: 'timestamptz', nullable: true })
|
@Column({ name: 'paid_at', type: 'timestamptz', nullable: true })
|
||||||
paidAt?: Date;
|
paidAt?: Date;
|
||||||
|
|
||||||
@OneToMany(() => InvoiceItem, (item) => item.invoiceId, { eager: false })
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
items?: InvoiceItem[];
|
|
||||||
|
|
||||||
@CreateDateColumn({ type: 'timestamptz' })
|
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@Column({ name: 'updated_at', type: 'timestamptz', nullable: true })
|
||||||
|
updatedAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity({ name: 'payment_methods', schema: 'public' })
|
@Entity({ name: 'billing_payment_methods', schema: 'public' })
|
||||||
export class PaymentMethod {
|
export class PaymentMethod {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
@Column({ name: 'tenant_id', type: 'varchar', length: 100 })
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
tenantId!: string;
|
tenantId!: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 20 })
|
@Column({ type: 'varchar', length: 20 })
|
||||||
|
|
@ -14,24 +14,21 @@ export class PaymentMethod {
|
||||||
@Column({ name: 'display_name', type: 'varchar', length: 200, default: '' })
|
@Column({ name: 'display_name', type: 'varchar', length: 200, default: '' })
|
||||||
displayName!: string; // e.g. 'Visa *4242', 'Alipay xxx@qq.com'
|
displayName!: string; // e.g. 'Visa *4242', 'Alipay xxx@qq.com'
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 30, nullable: true })
|
|
||||||
type?: string; // 'card' | 'alipay_account' | 'wechat_openid' | 'crypto_wallet'
|
|
||||||
|
|
||||||
@Column({ name: 'is_default', type: 'boolean', default: false })
|
@Column({ name: 'is_default', type: 'boolean', default: false })
|
||||||
isDefault!: boolean;
|
isDefault!: boolean;
|
||||||
|
|
||||||
@Column({ type: 'jsonb', default: '{}' })
|
@Column({ type: 'jsonb', nullable: true })
|
||||||
details!: Record<string, any>; // { last4: '4242', brand: 'visa' } — NEVER store full card numbers
|
details?: Record<string, any>; // { last4: '4242', brand: 'visa' } — NEVER store full card numbers
|
||||||
|
|
||||||
@Column({ name: 'provider_customer_id', type: 'varchar', length: 200, nullable: true })
|
@Column({ name: 'provider_customer_id', type: 'varchar', length: 255, nullable: true })
|
||||||
providerCustomerId?: string;
|
providerCustomerId?: string;
|
||||||
|
|
||||||
@Column({ name: 'expires_at', type: 'timestamptz', nullable: true })
|
@Column({ name: 'expires_at', type: 'timestamptz', nullable: true })
|
||||||
expiresAt?: Date;
|
expiresAt?: Date;
|
||||||
|
|
||||||
@CreateDateColumn({ type: 'timestamptz' })
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
||||||
@UpdateDateColumn({ type: 'timestamptz' })
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,16 @@ export const PaymentStatus = {
|
||||||
} as const;
|
} as const;
|
||||||
export type PaymentStatus = typeof PaymentStatus[keyof typeof PaymentStatus];
|
export type PaymentStatus = typeof PaymentStatus[keyof typeof PaymentStatus];
|
||||||
|
|
||||||
@Entity({ name: 'payments', schema: 'public' })
|
@Entity({ name: 'billing_payments', schema: 'public' })
|
||||||
export class Payment {
|
export class Payment {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 100 })
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
tenantId!: string;
|
tenantId!: string;
|
||||||
|
|
||||||
@Column({ type: 'uuid', nullable: true })
|
@Column({ name: 'invoice_id', type: 'uuid' })
|
||||||
invoiceId?: string;
|
invoiceId!: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 20 })
|
@Column({ type: 'varchar', length: 20 })
|
||||||
provider!: string; // 'stripe' | 'alipay' | 'wechat_pay' | 'crypto'
|
provider!: string; // 'stripe' | 'alipay' | 'wechat_pay' | 'crypto'
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,49 @@
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity({ name: 'plans', schema: 'public' })
|
@Entity({ name: 'billing_plans', schema: 'public' })
|
||||||
export class Plan {
|
export class Plan {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 30, unique: true })
|
@Column({ type: 'varchar', length: 50, unique: true })
|
||||||
name!: string; // 'free' | 'pro' | 'enterprise'
|
name!: string; // 'free' | 'pro' | 'enterprise'
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 100 })
|
@Column({ name: 'display_name', type: 'varchar', length: 100 })
|
||||||
displayName!: string;
|
displayName!: string;
|
||||||
|
|
||||||
@Column({ type: 'int', default: 0 })
|
@Column({ name: 'monthly_price_usd_cents', type: 'int', default: 0 })
|
||||||
monthlyPriceCentsUsd!: number; // e.g. 4999 = $49.99
|
monthlyPriceCentsUsd!: number; // e.g. 4999 = $49.99
|
||||||
|
|
||||||
@Column({ type: 'int', default: 0 })
|
@Column({ name: 'monthly_price_cny', type: 'int', default: 0 })
|
||||||
monthlyPriceFenCny!: number; // e.g. 34900 = ¥349.00
|
monthlyPriceFenCny!: number; // e.g. 34900 = ¥349.00
|
||||||
|
|
||||||
@Column({ type: 'bigint', default: 100000 })
|
@Column({ name: 'included_tokens_per_month', type: 'bigint', default: 100000 })
|
||||||
includedTokens!: number; // tokens per month included in plan
|
includedTokens!: number; // tokens per month included in plan
|
||||||
|
|
||||||
@Column({ type: 'int', default: 0 })
|
@Column({ name: 'overage_rate_cents_per_m_token', type: 'int', default: 0 })
|
||||||
overageRateCentsPerMTokenUsd!: number; // price per 1M overage tokens in cents
|
overageRateCentsPerMTokenUsd!: number; // price per 1M overage tokens in cents
|
||||||
|
|
||||||
@Column({ type: 'int', default: 5 })
|
@Column({ name: 'max_servers', type: 'int', default: 5 })
|
||||||
maxServers!: number; // -1 = unlimited
|
maxServers!: number; // -1 = unlimited
|
||||||
|
|
||||||
@Column({ type: 'int', default: 3 })
|
@Column({ name: 'max_users', type: 'int', default: 3 })
|
||||||
maxUsers!: number; // -1 = unlimited
|
maxUsers!: number; // -1 = unlimited
|
||||||
|
|
||||||
@Column({ type: 'int', default: 10 })
|
@Column({ name: 'max_standing_orders', type: 'int', default: 10 })
|
||||||
maxStandingOrders!: number; // -1 = unlimited
|
maxStandingOrders!: number; // -1 = unlimited
|
||||||
|
|
||||||
@Column({ type: 'int', default: 100 })
|
@Column({ name: 'hard_limit_percent', type: 'int', default: 100 })
|
||||||
hardLimitPercent!: number; // 100 = block at 100%, 150 = block at 150%, 0 = no limit
|
hardLimitPercent!: number; // 100 = block at 100%, 150 = block at 150%, 0 = no limit
|
||||||
|
|
||||||
@Column({ type: 'jsonb', default: '{}' })
|
@Column({ name: 'trial_days', type: 'int', default: 0 })
|
||||||
features!: Record<string, boolean>;
|
|
||||||
|
|
||||||
@Column({ type: 'boolean', default: true })
|
|
||||||
isActive!: boolean;
|
|
||||||
|
|
||||||
@Column({ type: 'int', default: 0 })
|
|
||||||
sortOrder!: number;
|
|
||||||
|
|
||||||
@Column({ type: 'int', default: 14 })
|
|
||||||
trialDays!: number;
|
trialDays!: number;
|
||||||
|
|
||||||
@CreateDateColumn({ type: 'timestamptz' })
|
@Column({ name: 'is_active', type: 'boolean', default: true })
|
||||||
|
isActive!: boolean;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
||||||
@UpdateDateColumn({ type: 'timestamptz' })
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,41 +9,41 @@ export const SubscriptionStatus = {
|
||||||
} as const;
|
} as const;
|
||||||
export type SubscriptionStatus = typeof SubscriptionStatus[keyof typeof SubscriptionStatus];
|
export type SubscriptionStatus = typeof SubscriptionStatus[keyof typeof SubscriptionStatus];
|
||||||
|
|
||||||
@Entity({ name: 'subscriptions', schema: 'public' })
|
@Entity({ name: 'billing_subscriptions', schema: 'public' })
|
||||||
export class Subscription {
|
export class Subscription {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 100 })
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
tenantId!: string;
|
tenantId!: string;
|
||||||
|
|
||||||
@Column({ type: 'uuid' })
|
@Column({ name: 'plan_id', type: 'uuid' })
|
||||||
planId!: string;
|
planId!: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 20, default: 'trialing' })
|
@Column({ type: 'varchar', length: 20, default: 'trialing' })
|
||||||
status!: SubscriptionStatus;
|
status!: SubscriptionStatus;
|
||||||
|
|
||||||
@Column({ type: 'timestamptz' })
|
@Column({ name: 'current_period_start', type: 'timestamptz' })
|
||||||
currentPeriodStart!: Date;
|
currentPeriodStart!: Date;
|
||||||
|
|
||||||
@Column({ type: 'timestamptz' })
|
@Column({ name: 'current_period_end', type: 'timestamptz' })
|
||||||
currentPeriodEnd!: Date;
|
currentPeriodEnd!: Date;
|
||||||
|
|
||||||
@Column({ type: 'timestamptz', nullable: true })
|
@Column({ name: 'trial_ends_at', type: 'timestamptz', nullable: true })
|
||||||
trialEndsAt?: Date;
|
trialEndsAt?: Date;
|
||||||
|
|
||||||
@Column({ type: 'timestamptz', nullable: true })
|
@Column({ name: 'cancelled_at', type: 'timestamptz', nullable: true })
|
||||||
cancelledAt?: Date;
|
cancelledAt?: Date;
|
||||||
|
|
||||||
@Column({ type: 'boolean', default: false })
|
@Column({ name: 'cancel_at_period_end', type: 'boolean', default: false })
|
||||||
cancelAtPeriodEnd!: boolean;
|
cancelAtPeriodEnd!: boolean;
|
||||||
|
|
||||||
@Column({ type: 'uuid', nullable: true })
|
@Column({ name: 'next_plan_id', type: 'uuid', nullable: true })
|
||||||
nextPlanId?: string; // for scheduled downgrades
|
nextPlanId?: string; // for scheduled downgrades
|
||||||
|
|
||||||
@CreateDateColumn({ type: 'timestamptz' })
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
||||||
@UpdateDateColumn({ type: 'timestamptz' })
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { Entity, PrimaryGeneratedColumn, Column, UpdateDateColumn } from 'typeorm';
|
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity({ name: 'usage_aggregates', schema: 'public' })
|
@Entity({ name: 'billing_usage_aggregates', schema: 'public' })
|
||||||
export class UsageAggregate {
|
export class UsageAggregate {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
@Column({ name: 'tenant_id', type: 'varchar', length: 100 })
|
@Column({ name: 'tenant_id', type: 'uuid' })
|
||||||
tenantId!: string;
|
tenantId!: string;
|
||||||
|
|
||||||
@Column({ type: 'int' })
|
@Column({ type: 'int' })
|
||||||
|
|
@ -29,12 +29,15 @@ export class UsageAggregate {
|
||||||
@Column({ name: 'total_tokens', type: 'bigint', default: 0 })
|
@Column({ name: 'total_tokens', type: 'bigint', default: 0 })
|
||||||
totalTokens!: number;
|
totalTokens!: number;
|
||||||
|
|
||||||
@Column({ name: 'total_cost_usd', type: 'numeric', precision: 14, scale: 6, default: 0 })
|
@Column({ name: 'total_cost_usd', type: 'numeric', precision: 12, scale: 6, default: 0 })
|
||||||
totalCostUsd!: number;
|
totalCostUsd!: number;
|
||||||
|
|
||||||
@Column({ name: 'task_count', type: 'int', default: 0 })
|
@Column({ name: 'task_count', type: 'int', default: 0 })
|
||||||
taskCount!: number;
|
taskCount!: number;
|
||||||
|
|
||||||
@UpdateDateColumn({ type: 'timestamptz' })
|
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
|
||||||
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ const SEED_PLANS = [
|
||||||
{
|
{
|
||||||
name: 'free',
|
name: 'free',
|
||||||
displayName: 'Free',
|
displayName: 'Free',
|
||||||
monthlyPriceUsdCents: 0,
|
monthlyPriceCentsUsd: 0,
|
||||||
monthlyPriceCny: 0,
|
monthlyPriceFenCny: 0,
|
||||||
includedTokensPerMonth: 100_000,
|
includedTokens: 100_000,
|
||||||
overageRateCentsPerMToken: 0,
|
overageRateCentsPerMTokenUsd: 0,
|
||||||
maxServers: 5,
|
maxServers: 5,
|
||||||
maxUsers: 3,
|
maxUsers: 3,
|
||||||
maxStandingOrders: 10,
|
maxStandingOrders: 10,
|
||||||
|
|
@ -19,10 +19,10 @@ const SEED_PLANS = [
|
||||||
{
|
{
|
||||||
name: 'pro',
|
name: 'pro',
|
||||||
displayName: 'Pro',
|
displayName: 'Pro',
|
||||||
monthlyPriceUsdCents: 4999, // $49.99
|
monthlyPriceCentsUsd: 4999, // $49.99
|
||||||
monthlyPriceCny: 34900, // ¥349.00
|
monthlyPriceFenCny: 34900, // ¥349.00
|
||||||
includedTokensPerMonth: 1_000_000,
|
includedTokens: 1_000_000,
|
||||||
overageRateCentsPerMToken: 800, // $8.00 per MTok
|
overageRateCentsPerMTokenUsd: 800, // $8.00 per MTok
|
||||||
maxServers: 50,
|
maxServers: 50,
|
||||||
maxUsers: 20,
|
maxUsers: 20,
|
||||||
maxStandingOrders: 100,
|
maxStandingOrders: 100,
|
||||||
|
|
@ -33,10 +33,10 @@ const SEED_PLANS = [
|
||||||
{
|
{
|
||||||
name: 'enterprise',
|
name: 'enterprise',
|
||||||
displayName: 'Enterprise',
|
displayName: 'Enterprise',
|
||||||
monthlyPriceUsdCents: 19999, // $199.99
|
monthlyPriceCentsUsd: 19999, // $199.99
|
||||||
monthlyPriceCny: 139900, // ¥1399.00
|
monthlyPriceFenCny: 139900, // ¥1399.00
|
||||||
includedTokensPerMonth: 10_000_000,
|
includedTokens: 10_000_000,
|
||||||
overageRateCentsPerMToken: 500, // $5.00 per MTok
|
overageRateCentsPerMTokenUsd: 500, // $5.00 per MTok
|
||||||
maxServers: -1,
|
maxServers: -1,
|
||||||
maxUsers: -1,
|
maxUsers: -1,
|
||||||
maxStandingOrders: -1,
|
maxStandingOrders: -1,
|
||||||
|
|
|
||||||
|
|
@ -62,14 +62,7 @@ export class InvoiceController {
|
||||||
periodEnd: invoice.periodEnd,
|
periodEnd: invoice.periodEnd,
|
||||||
dueDate: invoice.dueDate,
|
dueDate: invoice.dueDate,
|
||||||
paidAt: invoice.paidAt,
|
paidAt: invoice.paidAt,
|
||||||
items: (invoice.items ?? []).map((item) => ({
|
items: [],
|
||||||
id: item.id,
|
|
||||||
itemType: item.itemType,
|
|
||||||
description: item.description,
|
|
||||||
quantity: item.quantity,
|
|
||||||
unitPrice: item.unitPrice / 100,
|
|
||||||
amount: item.amount / 100,
|
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue