Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 3m55s
新增收款商户、商户池轮询、微信授权配置独立管理;三类新支付 (C端套餐购买、C端资产钱包充值、代理在线预存款充值)无条件 经商户池选择并冻结路由,无旧综合配置回退。merchant_id 为空 历史支付继续按 payment_config_id 双读。凭证版本化加载与 ID+版本缓存保证轮换一致性。删除商户池新支付创建开关及全部 引用。
238 lines
13 KiB
SQL
238 lines
13 KiB
SQL
-- 商户池支付路由:收款商户、商户池、微信授权、成功统计事实及支付快照。
|
||
-- 不使用数据库外键;关联以 ID 保存并由应用层显式校验。
|
||
|
||
CREATE TABLE tb_payment_merchant (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
name VARCHAR(100) NOT NULL,
|
||
payment_method VARCHAR(20) NOT NULL,
|
||
provider_type VARCHAR(30) NOT NULL,
|
||
merchant_identity VARCHAR(100) NOT NULL,
|
||
credentials JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||
credential_version BIGINT NOT NULL DEFAULT 1,
|
||
status SMALLINT NOT NULL DEFAULT 0,
|
||
remark TEXT NOT NULL DEFAULT '',
|
||
creator BIGINT NOT NULL DEFAULT 0,
|
||
updater BIGINT NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
deleted_at TIMESTAMPTZ,
|
||
CONSTRAINT chk_payment_merchant_method CHECK (payment_method IN ('wechat', 'alipay')),
|
||
CONSTRAINT chk_payment_merchant_status CHECK (status IN (0, 1)),
|
||
CONSTRAINT chk_payment_merchant_credential_version CHECK (credential_version >= 1)
|
||
);
|
||
|
||
CREATE UNIQUE INDEX uk_payment_merchant_method_identity_active
|
||
ON tb_payment_merchant (payment_method, merchant_identity)
|
||
WHERE deleted_at IS NULL;
|
||
CREATE INDEX idx_payment_merchant_method_status
|
||
ON tb_payment_merchant (payment_method, status, id)
|
||
WHERE deleted_at IS NULL;
|
||
|
||
CREATE TABLE tb_payment_merchant_pool (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
name VARCHAR(100) NOT NULL,
|
||
payment_method VARCHAR(20) NOT NULL,
|
||
status SMALLINT NOT NULL DEFAULT 0,
|
||
strategy VARCHAR(20) NOT NULL,
|
||
threshold_amount BIGINT,
|
||
threshold_count BIGINT,
|
||
statistic_cycle VARCHAR(20),
|
||
time_period_value BIGINT,
|
||
time_period_unit VARCHAR(10),
|
||
time_period_started_at TIMESTAMPTZ,
|
||
routing_epoch BIGINT NOT NULL DEFAULT 1,
|
||
remark TEXT NOT NULL DEFAULT '',
|
||
creator BIGINT NOT NULL DEFAULT 0,
|
||
updater BIGINT NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
deleted_at TIMESTAMPTZ,
|
||
CONSTRAINT chk_payment_merchant_pool_method CHECK (payment_method IN ('wechat', 'alipay')),
|
||
CONSTRAINT chk_payment_merchant_pool_status CHECK (status IN (0, 1)),
|
||
CONSTRAINT chk_payment_merchant_pool_strategy CHECK (strategy IN ('amount', 'count', 'time')),
|
||
CONSTRAINT chk_payment_merchant_pool_epoch CHECK (routing_epoch >= 1),
|
||
CONSTRAINT chk_payment_merchant_pool_statistic_cycle CHECK (
|
||
statistic_cycle IS NULL OR statistic_cycle IN ('round', 'day', 'month')
|
||
),
|
||
CONSTRAINT chk_payment_merchant_pool_time_unit CHECK (
|
||
time_period_unit IS NULL OR time_period_unit IN ('minute', 'hour', 'day')
|
||
),
|
||
CONSTRAINT chk_payment_merchant_pool_strategy_parameters CHECK (
|
||
(strategy = 'amount' AND threshold_amount IS NOT NULL AND threshold_amount > 0
|
||
AND threshold_count IS NULL AND statistic_cycle IS NOT NULL
|
||
AND time_period_value IS NULL AND time_period_unit IS NULL AND time_period_started_at IS NULL)
|
||
OR
|
||
(strategy = 'count' AND threshold_count IS NOT NULL AND threshold_count > 0
|
||
AND threshold_amount IS NULL AND statistic_cycle IS NOT NULL
|
||
AND time_period_value IS NULL AND time_period_unit IS NULL AND time_period_started_at IS NULL)
|
||
OR
|
||
(strategy = 'time' AND time_period_value IS NOT NULL AND time_period_value >= 1
|
||
AND time_period_unit IS NOT NULL AND time_period_started_at IS NOT NULL
|
||
AND threshold_amount IS NULL AND threshold_count IS NULL AND statistic_cycle IS NULL)
|
||
)
|
||
);
|
||
|
||
CREATE UNIQUE INDEX uk_payment_merchant_pool_name_active
|
||
ON tb_payment_merchant_pool (name)
|
||
WHERE deleted_at IS NULL;
|
||
CREATE UNIQUE INDEX uk_payment_merchant_pool_enabled_method
|
||
ON tb_payment_merchant_pool (payment_method)
|
||
WHERE status = 1 AND deleted_at IS NULL;
|
||
CREATE INDEX idx_payment_merchant_pool_method_status
|
||
ON tb_payment_merchant_pool (payment_method, status, id)
|
||
WHERE deleted_at IS NULL;
|
||
|
||
CREATE TABLE tb_payment_merchant_pool_member (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
pool_id BIGINT NOT NULL,
|
||
merchant_id BIGINT NOT NULL,
|
||
sort_order BIGINT NOT NULL,
|
||
creator BIGINT NOT NULL DEFAULT 0,
|
||
updater BIGINT NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
deleted_at TIMESTAMPTZ,
|
||
CONSTRAINT chk_payment_merchant_pool_member_order CHECK (sort_order >= 0)
|
||
);
|
||
|
||
CREATE UNIQUE INDEX uk_payment_merchant_pool_member_active
|
||
ON tb_payment_merchant_pool_member (pool_id, merchant_id)
|
||
WHERE deleted_at IS NULL;
|
||
CREATE UNIQUE INDEX uk_payment_merchant_pool_member_order_active
|
||
ON tb_payment_merchant_pool_member (pool_id, sort_order)
|
||
WHERE deleted_at IS NULL;
|
||
CREATE INDEX idx_payment_merchant_pool_member_merchant
|
||
ON tb_payment_merchant_pool_member (merchant_id, pool_id)
|
||
WHERE deleted_at IS NULL;
|
||
|
||
CREATE TABLE tb_wechat_authorization (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
oa_app_id VARCHAR(100) NOT NULL DEFAULT '',
|
||
oa_app_secret VARCHAR(200) NOT NULL DEFAULT '',
|
||
oa_token VARCHAR(200) NOT NULL DEFAULT '',
|
||
oa_aes_key VARCHAR(200) NOT NULL DEFAULT '',
|
||
oa_oauth_redirect_url VARCHAR(500) NOT NULL DEFAULT '',
|
||
miniapp_app_id VARCHAR(100) NOT NULL DEFAULT '',
|
||
miniapp_app_secret VARCHAR(200) NOT NULL DEFAULT '',
|
||
credential_version BIGINT NOT NULL DEFAULT 1,
|
||
status SMALLINT NOT NULL DEFAULT 0,
|
||
creator BIGINT NOT NULL DEFAULT 0,
|
||
updater BIGINT NOT NULL DEFAULT 0,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
deleted_at TIMESTAMPTZ,
|
||
CONSTRAINT chk_wechat_authorization_status CHECK (status IN (0, 1)),
|
||
CONSTRAINT chk_wechat_authorization_credential_version CHECK (credential_version >= 1)
|
||
);
|
||
|
||
CREATE UNIQUE INDEX uk_wechat_authorization_enabled
|
||
ON tb_wechat_authorization ((status))
|
||
WHERE status = 1 AND deleted_at IS NULL;
|
||
|
||
CREATE TABLE tb_payment_merchant_routing_success (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
payment_id BIGINT NOT NULL,
|
||
merchant_id BIGINT NOT NULL,
|
||
pool_id BIGINT NOT NULL,
|
||
routing_epoch BIGINT NOT NULL,
|
||
amount BIGINT NOT NULL,
|
||
paid_at TIMESTAMPTZ NOT NULL,
|
||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
CONSTRAINT uk_payment_merchant_routing_success_payment UNIQUE (payment_id),
|
||
CONSTRAINT chk_payment_merchant_routing_success_epoch CHECK (routing_epoch >= 1),
|
||
CONSTRAINT chk_payment_merchant_routing_success_amount CHECK (amount > 0)
|
||
);
|
||
|
||
CREATE INDEX idx_payment_merchant_routing_success_select
|
||
ON tb_payment_merchant_routing_success (pool_id, routing_epoch, merchant_id, paid_at, id);
|
||
|
||
ALTER TABLE tb_payment
|
||
ADD COLUMN merchant_id BIGINT,
|
||
ADD COLUMN merchant_pool_id BIGINT,
|
||
ADD COLUMN merchant_name_snapshot VARCHAR(100),
|
||
ADD COLUMN merchant_payment_method_snapshot VARCHAR(20),
|
||
ADD COLUMN merchant_provider_type_snapshot VARCHAR(30),
|
||
ADD COLUMN merchant_pool_name_snapshot VARCHAR(100),
|
||
ADD COLUMN routing_strategy_snapshot VARCHAR(20),
|
||
ADD COLUMN routing_epoch BIGINT;
|
||
|
||
CREATE INDEX idx_payment_merchant_route
|
||
ON tb_payment (merchant_id, routing_epoch, status, id)
|
||
WHERE deleted_at IS NULL AND merchant_id IS NOT NULL;
|
||
CREATE INDEX idx_payment_merchant_pool_route
|
||
ON tb_payment (merchant_pool_id, routing_epoch, id)
|
||
WHERE deleted_at IS NULL AND merchant_pool_id IS NOT NULL;
|
||
|
||
-- 从唯一生效综合配置复制完整凭证。单成员池使用一分钟时间轮询,
|
||
-- 对单成员没有选择差异;后续成员或策略调整由管理配置显式产生新世代。
|
||
DO $$
|
||
DECLARE
|
||
active_count BIGINT;
|
||
legacy tb_wechat_config%ROWTYPE;
|
||
copied_merchant_id BIGINT;
|
||
copied_pool_id BIGINT;
|
||
BEGIN
|
||
SELECT COUNT(*) INTO active_count
|
||
FROM tb_wechat_config
|
||
WHERE is_active = TRUE AND deleted_at IS NULL;
|
||
|
||
IF active_count > 1 THEN
|
||
RAISE EXCEPTION '存在多条生效综合支付配置,无法安全迁移商户池';
|
||
END IF;
|
||
IF active_count = 0 THEN
|
||
RETURN;
|
||
END IF;
|
||
|
||
SELECT * INTO legacy
|
||
FROM tb_wechat_config
|
||
WHERE is_active = TRUE AND deleted_at IS NULL;
|
||
|
||
IF legacy.provider_type IN ('wechat', 'wechat_v2')
|
||
AND legacy.wx_mch_id <> ''
|
||
AND ((legacy.provider_type = 'wechat' AND legacy.wx_api_v3_key <> '' AND legacy.wx_cert_content <> '' AND legacy.wx_key_content <> '' AND legacy.wx_serial_no <> '' AND legacy.wx_notify_url <> '')
|
||
OR (legacy.provider_type = 'wechat_v2' AND legacy.wx_api_v2_key <> '' AND legacy.wx_notify_url <> '')) THEN
|
||
INSERT INTO tb_payment_merchant (name, payment_method, provider_type, merchant_identity, credentials, credential_version, status)
|
||
VALUES (legacy.name || '-微信商户', 'wechat', legacy.provider_type, legacy.wx_mch_id,
|
||
jsonb_build_object('wx_mch_id', legacy.wx_mch_id, 'wx_api_v3_key', legacy.wx_api_v3_key, 'wx_api_v2_key', legacy.wx_api_v2_key, 'wx_cert_content', legacy.wx_cert_content, 'wx_key_content', legacy.wx_key_content, 'wx_serial_no', legacy.wx_serial_no, 'wx_notify_url', legacy.wx_notify_url), 1, 1)
|
||
RETURNING id INTO copied_merchant_id;
|
||
ELSIF legacy.provider_type = 'fuiou'
|
||
AND legacy.fy_ins_cd <> '' AND legacy.fy_mchnt_cd <> '' AND legacy.fy_term_id <> ''
|
||
AND legacy.fy_private_key <> '' AND legacy.fy_public_key <> '' AND legacy.fy_api_url <> '' AND legacy.fy_notify_url <> '' THEN
|
||
INSERT INTO tb_payment_merchant (name, payment_method, provider_type, merchant_identity, credentials, credential_version, status)
|
||
VALUES (legacy.name || '-富友商户', 'wechat', 'fuiou', legacy.fy_mchnt_cd,
|
||
jsonb_build_object('fy_ins_cd', legacy.fy_ins_cd, 'fy_mchnt_cd', legacy.fy_mchnt_cd, 'fy_term_id', legacy.fy_term_id, 'fy_private_key', legacy.fy_private_key, 'fy_public_key', legacy.fy_public_key, 'fy_api_url', legacy.fy_api_url, 'fy_notify_url', legacy.fy_notify_url), 1, 1)
|
||
RETURNING id INTO copied_merchant_id;
|
||
END IF;
|
||
IF copied_merchant_id IS NOT NULL THEN
|
||
INSERT INTO tb_payment_merchant_pool (name, payment_method, status, strategy, time_period_value, time_period_unit, time_period_started_at, routing_epoch)
|
||
VALUES ('迁移微信商户池', 'wechat', 1, 'time', 1, 'minute', NOW(), 1)
|
||
RETURNING id INTO copied_pool_id;
|
||
INSERT INTO tb_payment_merchant_pool_member (pool_id, merchant_id, sort_order) VALUES (copied_pool_id, copied_merchant_id, 0);
|
||
END IF;
|
||
|
||
copied_merchant_id := NULL;
|
||
IF legacy.ali_app_id <> '' AND legacy.ali_private_key <> '' AND legacy.ali_public_key <> '' AND legacy.ali_notify_url <> '' AND legacy.ali_return_url <> '' THEN
|
||
INSERT INTO tb_payment_merchant (name, payment_method, provider_type, merchant_identity, credentials, credential_version, status)
|
||
VALUES (legacy.name || '-支付宝商户', 'alipay', 'alipay', legacy.ali_app_id,
|
||
jsonb_build_object('ali_app_id', legacy.ali_app_id, 'ali_private_key', legacy.ali_private_key, 'ali_public_key', legacy.ali_public_key, 'ali_notify_url', legacy.ali_notify_url, 'ali_return_url', legacy.ali_return_url, 'ali_production', legacy.ali_production, 'ali_pay_expire_minutes', legacy.ali_pay_expire_minutes), 1, 1)
|
||
RETURNING id INTO copied_merchant_id;
|
||
INSERT INTO tb_payment_merchant_pool (name, payment_method, status, strategy, time_period_value, time_period_unit, time_period_started_at, routing_epoch)
|
||
VALUES ('迁移支付宝商户池', 'alipay', 1, 'time', 1, 'minute', NOW(), 1)
|
||
RETURNING id INTO copied_pool_id;
|
||
INSERT INTO tb_payment_merchant_pool_member (pool_id, merchant_id, sort_order) VALUES (copied_pool_id, copied_merchant_id, 0);
|
||
END IF;
|
||
|
||
IF legacy.oa_app_id <> '' AND legacy.oa_app_secret <> ''
|
||
AND legacy.miniapp_app_id <> '' AND legacy.miniapp_app_secret <> '' THEN
|
||
INSERT INTO tb_wechat_authorization (oa_app_id, oa_app_secret, oa_token, oa_aes_key, oa_oauth_redirect_url, miniapp_app_id, miniapp_app_secret, credential_version, status)
|
||
VALUES (legacy.oa_app_id, legacy.oa_app_secret, legacy.oa_token, legacy.oa_aes_key, legacy.oa_oauth_redirect_url, legacy.miniapp_app_id, legacy.miniapp_app_secret, 1, 1);
|
||
END IF;
|
||
END $$;
|
||
|
||
COMMENT ON TABLE tb_payment_merchant IS '实际收款商户,凭证仅在受控字段保存';
|
||
COMMENT ON TABLE tb_payment_merchant_pool IS '按支付方式轮询实际收款商户的商户池';
|
||
COMMENT ON TABLE tb_payment_merchant_pool_member IS '商户池有序成员;支付方式一致性由应用事务校验';
|
||
COMMENT ON TABLE tb_wechat_authorization IS 'C端微信授权配置,不保存收款商户凭证';
|
||
COMMENT ON TABLE tb_payment_merchant_routing_success IS '支付首次成功的路由累计唯一事实';
|
||
COMMENT ON COLUMN tb_payment.routing_epoch IS '支付创建时冻结的商户池统计世代';
|