Files
incudal/server/prisma/migrations/20260111_add_billing_system/migration.sql
T
2026-06-20 14:26:29 +08:00

210 lines
8.1 KiB
SQL

-- CreateEnum: Balance Log Types
CREATE TYPE "BalanceLogType" AS ENUM ('recharge', 'consume', 'refund', 'admin_adjust', 'gift');
-- CreateEnum: Payment Provider Types
CREATE TYPE "PaymentProviderType" AS ENUM ('yipay', 'stripe', 'alipay_direct', 'wechat_direct', 'manual');
-- CreateEnum: Payment Provider Status
CREATE TYPE "PaymentProviderStatus" AS ENUM ('active', 'disabled', 'testing');
-- CreateEnum: Recharge Status
CREATE TYPE "RechargeStatus" AS ENUM ('pending', 'paid', 'completed', 'failed', 'cancelled', 'refunded');
-- CreateEnum: Billing Record Types
CREATE TYPE "BillingRecordType" AS ENUM ('newPurchase', 'renew', 'upgrade', 'downgrade', 'refund');
-- AlterTable: Add balance field to users
ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "balance" DECIMAL(10,2) NOT NULL DEFAULT 0;
-- AlterTable: Add billing fields to instances
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "package_plan_id" INTEGER;
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "billing_cycle" INTEGER;
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "auto_renew_attempts" INTEGER NOT NULL DEFAULT 0;
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "last_auto_renew_attempt_at" TIMESTAMP(3);
ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "version" INTEGER NOT NULL DEFAULT 0;
-- CreateTable: PaymentProvider
CREATE TABLE IF NOT EXISTS "payment_providers" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"type" "PaymentProviderType" NOT NULL,
"status" "PaymentProviderStatus" NOT NULL DEFAULT 'disabled',
"config" JSONB NOT NULL DEFAULT '{}',
"methods" JSONB NOT NULL DEFAULT '[]',
"fee_rate" DECIMAL(5,4) NOT NULL DEFAULT 0,
"fee_fixed" DECIMAL(10,2) NOT NULL DEFAULT 0,
"min_amount" DECIMAL(10,2) NOT NULL DEFAULT 1,
"max_amount" DECIMAL(10,2),
"sort_order" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "payment_providers_pkey" PRIMARY KEY ("id")
);
-- CreateTable: BalanceLog
CREATE TABLE IF NOT EXISTS "balance_logs" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"type" "BalanceLogType" NOT NULL,
"amount" DECIMAL(10,2) NOT NULL,
"balance_before" DECIMAL(10,2) NOT NULL,
"balance_after" DECIMAL(10,2) NOT NULL,
"order_id" TEXT,
"instance_id" INTEGER,
"remark" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "balance_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable: RechargeRecord
CREATE TABLE IF NOT EXISTS "recharge_records" (
"id" SERIAL NOT NULL,
"user_id" INTEGER NOT NULL,
"provider_id" INTEGER NOT NULL,
"order_no" TEXT NOT NULL,
"trade_no" TEXT,
"amount" DECIMAL(10,2) NOT NULL,
"actual_amount" DECIMAL(10,2),
"fee" DECIMAL(10,2) NOT NULL DEFAULT 0,
"payment_method" TEXT,
"status" "RechargeStatus" NOT NULL DEFAULT 'pending',
"callback_data" JSONB,
"callback_at" TIMESTAMP(3),
"fail_reason" TEXT,
"ip" TEXT,
"user_agent" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"expired_at" TIMESTAMP(3),
"completed_at" TIMESTAMP(3),
CONSTRAINT "recharge_records_pkey" PRIMARY KEY ("id")
);
-- CreateTable: PackagePlan
CREATE TABLE IF NOT EXISTS "package_plans" (
"id" SERIAL NOT NULL,
"package_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"cpu" INTEGER NOT NULL,
"memory" INTEGER NOT NULL,
"disk" INTEGER NOT NULL,
"port_limit" INTEGER NOT NULL,
"snapshot_limit" INTEGER NOT NULL,
"backup_limit" INTEGER NOT NULL,
"site_limit" INTEGER NOT NULL,
"traffic_limit" BIGINT NOT NULL,
"traffic_limit_speed" TEXT NOT NULL DEFAULT '1Mbit',
"price" DECIMAL(10,2) NOT NULL,
"billing_cycle" INTEGER NOT NULL DEFAULT 1,
"setup_fee" DECIMAL(10,2) NOT NULL DEFAULT 0,
"is_active" BOOLEAN NOT NULL DEFAULT true,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"stock_limit" INTEGER,
"stock_used" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "package_plans_pkey" PRIMARY KEY ("id")
);
-- CreateTable: InstanceBillingRecord
CREATE TABLE IF NOT EXISTS "instance_billing_records" (
"id" SERIAL NOT NULL,
"instance_id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"type" "BillingRecordType" NOT NULL,
"amount" DECIMAL(10,2) NOT NULL,
"months" INTEGER NOT NULL DEFAULT 1,
"period_start" TIMESTAMP(3) NOT NULL,
"period_end" TIMESTAMP(3) NOT NULL,
"balance_log_id" INTEGER,
"remark" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "instance_billing_records_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "recharge_records_order_no_key" ON "recharge_records"("order_no");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "balance_logs_user_id_created_at_idx" ON "balance_logs"("user_id", "created_at" DESC);
-- CreateIndex
CREATE INDEX IF NOT EXISTS "balance_logs_type_idx" ON "balance_logs"("type");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "recharge_records_user_id_status_idx" ON "recharge_records"("user_id", "status");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "recharge_records_status_created_at_idx" ON "recharge_records"("status", "created_at");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "recharge_records_order_no_idx" ON "recharge_records"("order_no");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "recharge_records_trade_no_idx" ON "recharge_records"("trade_no");
-- CreateIndex
CREATE UNIQUE INDEX IF NOT EXISTS "package_plans_package_id_name_key" ON "package_plans"("package_id", "name");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "package_plans_package_id_is_active_idx" ON "package_plans"("package_id", "is_active");
-- CreateIndex
CREATE INDEX IF NOT EXISTS "instance_billing_records_instance_id_created_at_idx" ON "instance_billing_records"("instance_id", "created_at" DESC);
-- CreateIndex
CREATE INDEX IF NOT EXISTS "instance_billing_records_user_id_created_at_idx" ON "instance_billing_records"("user_id", "created_at" DESC);
-- AddForeignKey (only if not exists - handle gracefully)
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'balance_logs_user_id_fkey') THEN
ALTER TABLE "balance_logs" ADD CONSTRAINT "balance_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'recharge_records_user_id_fkey') THEN
ALTER TABLE "recharge_records" ADD CONSTRAINT "recharge_records_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'recharge_records_provider_id_fkey') THEN
ALTER TABLE "recharge_records" ADD CONSTRAINT "recharge_records_provider_id_fkey" FOREIGN KEY ("provider_id") REFERENCES "payment_providers"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'package_plans_package_id_fkey') THEN
ALTER TABLE "package_plans" ADD CONSTRAINT "package_plans_package_id_fkey" FOREIGN KEY ("package_id") REFERENCES "packages"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'instances_package_plan_id_fkey') THEN
ALTER TABLE "instances" ADD CONSTRAINT "instances_package_plan_id_fkey" FOREIGN KEY ("package_plan_id") REFERENCES "package_plans"("id") ON DELETE SET NULL ON UPDATE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'instance_billing_records_instance_id_fkey') THEN
ALTER TABLE "instance_billing_records" ADD CONSTRAINT "instance_billing_records_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "instances"("id") ON DELETE CASCADE ON UPDATE CASCADE;
END IF;
END $$;
-- Remove old billing fields from packages (if exist)
ALTER TABLE "packages" DROP COLUMN IF EXISTS "price_monthly";
ALTER TABLE "packages" DROP COLUMN IF EXISTS "setup_fee";