feat(billing): 完成异步结算与请求执行闭环
统一任务成功、Attempt 与结算 Outbox 的事务边界,增加多实例安全结算、人工复核、请求幂等与执行租约。钱包决策使用九位精确金额,并通过审计保护约束保留流水事实;同时补充管理接口、指标与 PostgreSQL 集成测试。
This commit is contained in:
@@ -1,16 +1,24 @@
|
||||
ALTER TABLE gateway_tasks
|
||||
ADD COLUMN IF NOT EXISTS billing_version text NOT NULL DEFAULT 'effective-pricing-v2',
|
||||
ADD COLUMN IF NOT EXISTS billing_status text NOT NULL DEFAULT 'not_started',
|
||||
ADD COLUMN IF NOT EXISTS billing_currency text NOT NULL DEFAULT 'resource',
|
||||
ADD COLUMN IF NOT EXISTS pricing_snapshot jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS billing_version text DEFAULT 'effective-pricing-v2',
|
||||
ADD COLUMN IF NOT EXISTS billing_status text DEFAULT 'not_started',
|
||||
ADD COLUMN IF NOT EXISTS billing_currency text DEFAULT 'resource',
|
||||
ADD COLUMN IF NOT EXISTS pricing_snapshot jsonb DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS request_fingerprint text,
|
||||
ADD COLUMN IF NOT EXISTS idempotency_key_hash text,
|
||||
ADD COLUMN IF NOT EXISTS reservation_amount numeric(38, 9) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS idempotency_request_hash text,
|
||||
ADD COLUMN IF NOT EXISTS reservation_amount numeric(38, 9) DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS execution_token uuid,
|
||||
ADD COLUMN IF NOT EXISTS execution_lease_expires_at timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS billing_updated_at timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS billing_settled_at timestamptz;
|
||||
|
||||
UPDATE gateway_tasks
|
||||
SET billing_version = COALESCE(billing_version, 'effective-pricing-v2'),
|
||||
billing_status = COALESCE(billing_status, 'not_started'),
|
||||
billing_currency = COALESCE(billing_currency, 'resource'),
|
||||
pricing_snapshot = COALESCE(pricing_snapshot, '{}'::jsonb),
|
||||
reservation_amount = COALESCE(reservation_amount, 0);
|
||||
|
||||
ALTER TABLE gateway_tasks
|
||||
ADD CONSTRAINT gateway_tasks_billing_status_v2_check CHECK (
|
||||
billing_status IN (
|
||||
@@ -32,28 +40,40 @@ CREATE INDEX IF NOT EXISTS idx_gateway_tasks_billing_status_v2
|
||||
ON gateway_tasks(billing_status, billing_updated_at, created_at);
|
||||
|
||||
ALTER TABLE gateway_task_attempts
|
||||
ADD COLUMN IF NOT EXISTS pricing_snapshot jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS pricing_snapshot jsonb DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS request_fingerprint text,
|
||||
ADD COLUMN IF NOT EXISTS upstream_submission_status text NOT NULL DEFAULT 'not_submitted',
|
||||
ADD COLUMN IF NOT EXISTS upstream_submission_status text DEFAULT 'not_submitted',
|
||||
ADD COLUMN IF NOT EXISTS upstream_submission_updated_at timestamptz;
|
||||
|
||||
UPDATE gateway_task_attempts
|
||||
SET pricing_snapshot = COALESCE(pricing_snapshot, '{}'::jsonb),
|
||||
upstream_submission_status = COALESCE(upstream_submission_status, 'not_submitted');
|
||||
|
||||
ALTER TABLE gateway_task_attempts
|
||||
ADD CONSTRAINT gateway_task_attempts_submission_v2_check CHECK (
|
||||
upstream_submission_status IN ('not_submitted', 'submitting', 'response_received')
|
||||
) NOT VALID;
|
||||
|
||||
ALTER TABLE settlement_outbox
|
||||
ADD COLUMN IF NOT EXISTS action text NOT NULL DEFAULT 'settle',
|
||||
ADD COLUMN IF NOT EXISTS amount numeric(38, 9) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS currency text NOT NULL DEFAULT 'resource',
|
||||
ADD COLUMN IF NOT EXISTS pricing_snapshot jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS action text DEFAULT 'settle',
|
||||
ADD COLUMN IF NOT EXISTS amount numeric(38, 9) DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS currency text DEFAULT 'resource',
|
||||
ADD COLUMN IF NOT EXISTS pricing_snapshot jsonb DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS locked_by text,
|
||||
ADD COLUMN IF NOT EXISTS lock_token uuid,
|
||||
ADD COLUMN IF NOT EXISTS locked_at timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS last_error_code text,
|
||||
ADD COLUMN IF NOT EXISTS last_error_message text,
|
||||
ADD COLUMN IF NOT EXISTS completed_at timestamptz,
|
||||
ADD COLUMN IF NOT EXISTS manual_review_reason text;
|
||||
ADD COLUMN IF NOT EXISTS manual_review_reason text,
|
||||
ADD COLUMN IF NOT EXISTS retry_idempotency_key_hash text,
|
||||
ADD COLUMN IF NOT EXISTS retry_requested_at timestamptz;
|
||||
|
||||
UPDATE settlement_outbox
|
||||
SET action = COALESCE(action, 'settle'),
|
||||
amount = COALESCE(amount, 0),
|
||||
currency = COALESCE(currency, 'resource'),
|
||||
pricing_snapshot = COALESCE(pricing_snapshot, '{}'::jsonb);
|
||||
|
||||
ALTER TABLE settlement_outbox
|
||||
ADD CONSTRAINT settlement_outbox_action_v2_check CHECK (action IN ('settle', 'release')) NOT VALID,
|
||||
@@ -71,7 +91,9 @@ CREATE INDEX IF NOT EXISTS idx_settlement_outbox_stale_v2
|
||||
WHERE status = 'processing';
|
||||
|
||||
ALTER TABLE model_pricing_rules
|
||||
ADD COLUMN IF NOT EXISTS is_free boolean NOT NULL DEFAULT false;
|
||||
ADD COLUMN IF NOT EXISTS is_free boolean DEFAULT false;
|
||||
|
||||
UPDATE model_pricing_rules SET is_free = false WHERE is_free IS NULL;
|
||||
|
||||
ALTER TABLE model_pricing_rules
|
||||
ADD CONSTRAINT model_pricing_rules_explicit_free_v2_check CHECK (
|
||||
@@ -82,9 +104,18 @@ ALTER TABLE gateway_wallet_accounts
|
||||
ADD CONSTRAINT gateway_wallet_nonnegative_frozen_v2_check CHECK (frozen_balance >= 0) NOT VALID,
|
||||
ADD CONSTRAINT gateway_wallet_balance_covers_frozen_v2_check CHECK (balance >= frozen_balance) NOT VALID;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS gateway_wallet_account_audit_guards (
|
||||
account_id uuid PRIMARY KEY REFERENCES gateway_wallet_accounts(id) ON DELETE RESTRICT,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
INSERT INTO gateway_wallet_account_audit_guards (account_id)
|
||||
SELECT id FROM gateway_wallet_accounts
|
||||
ON CONFLICT (account_id) DO NOTHING;
|
||||
|
||||
ALTER TABLE gateway_wallet_transactions
|
||||
ADD CONSTRAINT gateway_wallet_transactions_account_restrict_v2
|
||||
FOREIGN KEY (account_id) REFERENCES gateway_wallet_accounts(id) ON DELETE RESTRICT NOT VALID;
|
||||
ADD CONSTRAINT gateway_wallet_transactions_audit_guard_v2
|
||||
FOREIGN KEY (account_id) REFERENCES gateway_wallet_account_audit_guards(account_id) ON DELETE RESTRICT NOT VALID;
|
||||
|
||||
UPDATE gateway_tasks task
|
||||
SET billing_status = 'not_required',
|
||||
|
||||
Reference in New Issue
Block a user