feat: add payment gateway and billing plans

This commit is contained in:
Entropy.Xu
2026-05-13 01:18:38 +08:00
parent 0fa97595bf
commit 10285c5eb9
97 changed files with 14797 additions and 404 deletions

View File

@@ -80,6 +80,13 @@ CREATE TABLE IF NOT EXISTS payment_orders (
`refunded_amount_usd` DOUBLE NOT NULL DEFAULT 0,
`refundable_amount_usd` DOUBLE NOT NULL DEFAULT 0,
`payment_method` VARCHAR(64) NOT NULL,
`payment_provider` VARCHAR(64),
`payment_channel` VARCHAR(64),
`order_kind` VARCHAR(64) NOT NULL DEFAULT 'wallet_recharge',
`product_id` VARCHAR(64),
`product_snapshot` JSON,
`fulfillment_status` VARCHAR(64) NOT NULL DEFAULT 'pending',
`fulfillment_error` LONGTEXT,
`gateway_order_id` VARCHAR(128),
`gateway_response` JSON,
`status` VARCHAR(64) NOT NULL DEFAULT 'pending',
@@ -92,7 +99,25 @@ CREATE TABLE IF NOT EXISTS payment_orders (
KEY idx_payment_orders_wallet_created (`wallet_id`, `created_at`),
KEY idx_payment_orders_user_created (`user_id`, `created_at`),
KEY idx_payment_orders_status (`status`),
KEY idx_payment_orders_gateway_order_id (`gateway_order_id`)
KEY idx_payment_orders_gateway_order_id (`gateway_order_id`),
KEY idx_payment_orders_kind_status (`order_kind`, `status`),
KEY idx_payment_orders_product (`product_id`)
);
CREATE TABLE IF NOT EXISTS payment_gateway_configs (
`provider` VARCHAR(64) NOT NULL,
`enabled` TINYINT(1) NOT NULL DEFAULT 0,
`endpoint_url` VARCHAR(512) NOT NULL,
`callback_base_url` VARCHAR(512),
`merchant_id` VARCHAR(128) NOT NULL,
`merchant_key_encrypted` LONGTEXT,
`pay_currency` VARCHAR(16) NOT NULL DEFAULT 'CNY',
`usd_exchange_rate` DOUBLE NOT NULL DEFAULT 7.2,
`min_recharge_usd` DOUBLE NOT NULL DEFAULT 1,
`channels_json` JSON,
`created_at` BIGINT NOT NULL,
`updated_at` BIGINT NOT NULL,
PRIMARY KEY (`provider`)
);
CREATE TABLE IF NOT EXISTS payment_callbacks (
@@ -117,6 +142,56 @@ CREATE TABLE IF NOT EXISTS payment_callbacks (
KEY ix_payment_callbacks_payment_order_id (`payment_order_id`)
);
CREATE TABLE IF NOT EXISTS billing_plans (
`id` VARCHAR(64) NOT NULL,
`title` VARCHAR(128) NOT NULL,
`description` LONGTEXT,
`price_amount` DOUBLE NOT NULL,
`price_currency` VARCHAR(16) NOT NULL DEFAULT 'CNY',
`duration_unit` VARCHAR(32) NOT NULL,
`duration_value` BIGINT NOT NULL,
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
`sort_order` BIGINT NOT NULL DEFAULT 0,
`max_active_per_user` BIGINT NOT NULL DEFAULT 1,
`purchase_limit_scope` VARCHAR(32) NOT NULL DEFAULT 'active_period',
`entitlements_json` JSON NOT NULL,
`created_at` BIGINT NOT NULL,
`updated_at` BIGINT NOT NULL,
PRIMARY KEY (`id`),
KEY idx_billing_plans_enabled_sort (`enabled`, `sort_order`)
);
CREATE TABLE IF NOT EXISTS user_plan_entitlements (
`id` VARCHAR(64) NOT NULL,
`user_id` VARCHAR(64) NOT NULL,
`plan_id` VARCHAR(64) NOT NULL,
`payment_order_id` VARCHAR(64) NOT NULL,
`status` VARCHAR(64) NOT NULL DEFAULT 'active',
`starts_at` BIGINT NOT NULL,
`expires_at` BIGINT NOT NULL,
`entitlements_snapshot` JSON NOT NULL,
`created_at` BIGINT NOT NULL,
`updated_at` BIGINT NOT NULL,
PRIMARY KEY (`id`),
KEY idx_user_plan_entitlements_user_active (`user_id`, `status`, `expires_at`),
KEY idx_user_plan_entitlements_order (`payment_order_id`)
);
CREATE TABLE IF NOT EXISTS entitlement_usage_ledgers (
`id` VARCHAR(64) NOT NULL,
`user_entitlement_id` VARCHAR(64) NOT NULL,
`user_id` VARCHAR(64) NOT NULL,
`request_id` VARCHAR(128) NOT NULL,
`amount_usd` DOUBLE NOT NULL,
`balance_before` DOUBLE NOT NULL,
`balance_after` DOUBLE NOT NULL,
`usage_date` VARCHAR(16) NOT NULL,
`created_at` BIGINT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY uq_entitlement_usage_request (`user_entitlement_id`, `request_id`),
KEY idx_entitlement_usage_user_date (`user_id`, `usage_date`)
);
CREATE TABLE IF NOT EXISTS refund_requests (
`id` VARCHAR(64) NOT NULL,
`refund_no` VARCHAR(128) NOT NULL,

View File

@@ -83,6 +83,13 @@ CREATE TABLE IF NOT EXISTS public.payment_orders (
refunded_amount_usd double precision DEFAULT 0 NOT NULL,
refundable_amount_usd double precision DEFAULT 0 NOT NULL,
payment_method character varying(64) NOT NULL,
payment_provider character varying(64),
payment_channel character varying(64),
order_kind character varying(64) DEFAULT 'wallet_recharge' NOT NULL,
product_id character varying(64),
product_snapshot jsonb,
fulfillment_status character varying(64) DEFAULT 'pending' NOT NULL,
fulfillment_error text,
gateway_order_id character varying(128),
gateway_response jsonb,
status character varying(64) DEFAULT 'pending' NOT NULL,
@@ -98,6 +105,25 @@ CREATE INDEX IF NOT EXISTS idx_payment_orders_wallet_created ON public.payment_o
CREATE INDEX IF NOT EXISTS idx_payment_orders_user_created ON public.payment_orders USING btree (user_id, created_at);
CREATE INDEX IF NOT EXISTS idx_payment_orders_status ON public.payment_orders USING btree (status);
CREATE INDEX IF NOT EXISTS idx_payment_orders_gateway_order_id ON public.payment_orders USING btree (gateway_order_id);
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) NOT NULL,
enabled boolean DEFAULT false NOT NULL,
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) DEFAULT 'CNY' NOT NULL,
usd_exchange_rate double precision DEFAULT 7.2 NOT NULL,
min_recharge_usd double precision DEFAULT 1 NOT NULL,
channels_json jsonb,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
ALTER TABLE ONLY public.payment_gateway_configs ADD CONSTRAINT payment_gateway_configs_pkey PRIMARY KEY (provider);
CREATE TABLE IF NOT EXISTS public.payment_callbacks (
id character varying(64) NOT NULL,
@@ -122,6 +148,59 @@ CREATE INDEX IF NOT EXISTS idx_payment_callbacks_gateway_order ON public.payment
CREATE INDEX IF NOT EXISTS idx_payment_callbacks_created ON public.payment_callbacks USING btree (created_at);
CREATE INDEX IF NOT EXISTS ix_payment_callbacks_payment_order_id ON public.payment_callbacks USING btree (payment_order_id);
CREATE TABLE IF NOT EXISTS public.billing_plans (
id character varying(64) NOT NULL,
title character varying(128) NOT NULL,
description text,
price_amount double precision NOT NULL,
price_currency character varying(16) DEFAULT 'CNY' NOT NULL,
duration_unit character varying(32) NOT NULL,
duration_value bigint NOT NULL,
enabled boolean DEFAULT true NOT NULL,
sort_order bigint DEFAULT 0 NOT NULL,
max_active_per_user bigint DEFAULT 1 NOT NULL,
purchase_limit_scope character varying(32) DEFAULT 'active_period' NOT NULL,
entitlements_json jsonb NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
ALTER TABLE ONLY public.billing_plans ADD CONSTRAINT billing_plans_pkey PRIMARY KEY (id);
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) NOT NULL,
user_id character varying(64) NOT NULL,
plan_id character varying(64) NOT NULL,
payment_order_id character varying(64) NOT NULL,
status character varying(64) DEFAULT 'active' NOT NULL,
starts_at bigint NOT NULL,
expires_at bigint NOT NULL,
entitlements_snapshot jsonb NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL
);
ALTER TABLE ONLY public.user_plan_entitlements ADD CONSTRAINT user_plan_entitlements_pkey PRIMARY KEY (id);
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) NOT NULL,
user_entitlement_id character varying(64) NOT NULL,
user_id character varying(64) NOT NULL,
request_id character varying(128) NOT NULL,
amount_usd double precision NOT NULL,
balance_before double precision NOT NULL,
balance_after double precision NOT NULL,
usage_date character varying(16) NOT NULL,
created_at bigint NOT NULL
);
ALTER TABLE ONLY public.entitlement_usage_ledgers ADD CONSTRAINT entitlement_usage_ledgers_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.entitlement_usage_ledgers ADD 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);
CREATE TABLE IF NOT EXISTS public.refund_requests (
id character varying(64) NOT NULL,
refund_no character varying(128) NOT NULL,

View File

@@ -77,6 +77,13 @@ CREATE TABLE IF NOT EXISTS payment_orders (
refunded_amount_usd REAL NOT NULL DEFAULT 0,
refundable_amount_usd REAL NOT NULL DEFAULT 0,
payment_method TEXT NOT NULL,
payment_provider TEXT,
payment_channel TEXT,
order_kind TEXT NOT NULL DEFAULT 'wallet_recharge',
product_id TEXT,
product_snapshot TEXT,
fulfillment_status TEXT NOT NULL DEFAULT 'pending',
fulfillment_error TEXT,
gateway_order_id TEXT,
gateway_response TEXT,
status TEXT NOT NULL DEFAULT 'pending',
@@ -90,6 +97,23 @@ CREATE INDEX IF NOT EXISTS idx_payment_orders_wallet_created ON payment_orders (
CREATE INDEX IF NOT EXISTS idx_payment_orders_user_created ON payment_orders (user_id, created_at);
CREATE INDEX IF NOT EXISTS idx_payment_orders_status ON payment_orders (status);
CREATE INDEX IF NOT EXISTS idx_payment_orders_gateway_order_id ON payment_orders (gateway_order_id);
CREATE INDEX IF NOT EXISTS idx_payment_orders_kind_status ON payment_orders (order_kind, status);
CREATE INDEX IF NOT EXISTS idx_payment_orders_product ON payment_orders (product_id);
CREATE TABLE IF NOT EXISTS payment_gateway_configs (
provider TEXT PRIMARY KEY NOT NULL,
enabled INTEGER NOT NULL DEFAULT 0,
endpoint_url TEXT NOT NULL,
callback_base_url TEXT,
merchant_id TEXT NOT NULL,
merchant_key_encrypted TEXT,
pay_currency TEXT NOT NULL DEFAULT 'CNY',
usd_exchange_rate REAL NOT NULL DEFAULT 7.2,
min_recharge_usd REAL NOT NULL DEFAULT 1,
channels_json TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS payment_callbacks (
id TEXT PRIMARY KEY NOT NULL,
@@ -112,6 +136,53 @@ CREATE INDEX IF NOT EXISTS idx_payment_callbacks_gateway_order ON payment_callba
CREATE INDEX IF NOT EXISTS idx_payment_callbacks_created ON payment_callbacks (created_at);
CREATE INDEX IF NOT EXISTS ix_payment_callbacks_payment_order_id ON payment_callbacks (payment_order_id);
CREATE TABLE IF NOT EXISTS billing_plans (
id TEXT PRIMARY KEY NOT NULL,
title TEXT NOT NULL,
description TEXT,
price_amount REAL NOT NULL,
price_currency TEXT NOT NULL DEFAULT 'CNY',
duration_unit TEXT NOT NULL,
duration_value INTEGER NOT NULL,
enabled INTEGER NOT NULL DEFAULT 1,
sort_order INTEGER NOT NULL DEFAULT 0,
max_active_per_user INTEGER NOT NULL DEFAULT 1,
purchase_limit_scope TEXT NOT NULL DEFAULT 'active_period',
entitlements_json TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_billing_plans_enabled_sort ON billing_plans (enabled, sort_order);
CREATE TABLE IF NOT EXISTS user_plan_entitlements (
id TEXT PRIMARY KEY NOT NULL,
user_id TEXT NOT NULL,
plan_id TEXT NOT NULL,
payment_order_id TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
starts_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL,
entitlements_snapshot TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_user_plan_entitlements_user_active ON user_plan_entitlements (user_id, status, expires_at);
CREATE INDEX IF NOT EXISTS idx_user_plan_entitlements_order ON user_plan_entitlements (payment_order_id);
CREATE TABLE IF NOT EXISTS entitlement_usage_ledgers (
id TEXT PRIMARY KEY NOT NULL,
user_entitlement_id TEXT NOT NULL,
user_id TEXT NOT NULL,
request_id TEXT NOT NULL,
amount_usd REAL NOT NULL,
balance_before REAL NOT NULL,
balance_after REAL NOT NULL,
usage_date TEXT NOT NULL,
created_at INTEGER NOT NULL,
UNIQUE (user_entitlement_id, request_id)
);
CREATE INDEX IF NOT EXISTS idx_entitlement_usage_user_date ON entitlement_usage_ledgers (user_id, usage_date);
CREATE TABLE IF NOT EXISTS refund_requests (
id TEXT PRIMARY KEY NOT NULL,
refund_no TEXT NOT NULL,