Merge remote-tracking branch 'entropy-xu/payment-billing-plans'

# Conflicts:
#	crates/aether-data/src/lifecycle/bootstrap/postgres.rs
#	crates/aether-data/src/lifecycle/migrate/tests.rs
#	frontend/src/views/admin/Users.vue
This commit is contained in:
fawney19
2026-05-15 16:37:17 +08:00
97 changed files with 14795 additions and 402 deletions

View File

@@ -0,0 +1,81 @@
ALTER TABLE public.payment_orders
ADD COLUMN payment_provider character varying(64),
ADD COLUMN payment_channel character varying(64),
ADD COLUMN order_kind character varying(64) NOT NULL DEFAULT 'wallet_recharge',
ADD COLUMN product_id character varying(64),
ADD COLUMN product_snapshot jsonb,
ADD COLUMN fulfillment_status character varying(64) NOT NULL DEFAULT 'pending',
ADD COLUMN fulfillment_error text;
CREATE INDEX IF NOT EXISTS idx_payment_orders_kind_status
ON public.payment_orders USING btree (order_kind, status);
CREATE INDEX IF NOT EXISTS idx_payment_orders_product
ON public.payment_orders USING btree (product_id);
CREATE TABLE IF NOT EXISTS public.payment_gateway_configs (
provider character varying(64) PRIMARY KEY,
enabled boolean NOT NULL DEFAULT false,
endpoint_url character varying(512) NOT NULL,
callback_base_url character varying(512),
merchant_id character varying(128) NOT NULL,
merchant_key_encrypted text,
pay_currency character varying(16) NOT NULL DEFAULT 'CNY',
usd_exchange_rate numeric(18,8) NOT NULL DEFAULT 7.2,
min_recharge_usd numeric(20,8) NOT NULL DEFAULT 1,
channels_json jsonb,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE TABLE IF NOT EXISTS public.billing_plans (
id character varying(64) PRIMARY KEY,
title character varying(128) NOT NULL,
description text,
price_amount numeric(20,8) NOT NULL,
price_currency character varying(16) NOT NULL DEFAULT 'CNY',
duration_unit character varying(32) NOT NULL,
duration_value bigint NOT NULL,
enabled boolean NOT NULL DEFAULT true,
sort_order bigint NOT NULL DEFAULT 0,
max_active_per_user bigint NOT NULL DEFAULT 1,
entitlements_json jsonb NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_billing_plans_enabled_sort
ON public.billing_plans USING btree (enabled, sort_order);
CREATE TABLE IF NOT EXISTS public.user_plan_entitlements (
id character varying(64) PRIMARY KEY,
user_id character varying(64) NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
plan_id character varying(64) NOT NULL REFERENCES public.billing_plans(id) ON DELETE RESTRICT,
payment_order_id character varying(64) NOT NULL REFERENCES public.payment_orders(id) ON DELETE RESTRICT,
status character varying(64) NOT NULL DEFAULT 'active',
starts_at timestamp with time zone NOT NULL,
expires_at timestamp with time zone NOT NULL,
entitlements_snapshot jsonb NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_plan_entitlements_user_active
ON public.user_plan_entitlements USING btree (user_id, status, expires_at);
CREATE INDEX IF NOT EXISTS idx_user_plan_entitlements_order
ON public.user_plan_entitlements USING btree (payment_order_id);
CREATE TABLE IF NOT EXISTS public.entitlement_usage_ledgers (
id character varying(64) PRIMARY KEY,
user_entitlement_id character varying(64) NOT NULL REFERENCES public.user_plan_entitlements(id) ON DELETE CASCADE,
user_id character varying(64) NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
request_id character varying(128) NOT NULL,
amount_usd numeric(20,8) NOT NULL,
balance_before numeric(20,8) NOT NULL,
balance_after numeric(20,8) NOT NULL,
usage_date character varying(16) NOT NULL,
created_at timestamp with time zone NOT NULL,
CONSTRAINT uq_entitlement_usage_request UNIQUE (user_entitlement_id, request_id)
);
CREATE INDEX IF NOT EXISTS idx_entitlement_usage_user_date
ON public.entitlement_usage_ledgers USING btree (user_id, usage_date);

View File

@@ -0,0 +1,2 @@
ALTER TABLE public.billing_plans
ADD COLUMN IF NOT EXISTS purchase_limit_scope character varying(32) NOT NULL DEFAULT 'active_period';