import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { fileURLToPath } from "node:url"; import test from "node:test"; import { Client } from "pg"; import { startPostgresFixture } from "./helpers/postgres-fixture.ts"; const runnerPath = fileURLToPath( new URL("../scripts/db-migrate.mjs", import.meta.url), ); const ids = { billing: "71000000-0000-4000-8000-000000000001", support: "71000000-0000-4000-8000-000000000002", oneTimeUser: "72000000-0000-4000-8000-000000000001", retryUser: "72000000-0000-4000-8000-000000000002", compensateUser: "72000000-0000-4000-8000-000000000003", oneTimeProduct: "73000000-0000-4000-8000-000000000001", retryProduct: "73000000-0000-4000-8000-000000000002", compensateProduct: "73000000-0000-4000-8000-000000000003", }; const orderNo = (suffix: string) => `JYADJUST${suffix.padEnd(18, "0")}`; function errorText(error: unknown) { return error instanceof Error ? error.message : String(error); } test("billing order adjustments and redemption reasons are atomic and audited", async () => { const fixture = startPostgresFixture(); const admin = new Client({ connectionString: fixture.connectionUrl("postgres", "postgres-test-password"), }); const adminRuntime = new Client({ connectionString: fixture.connectionUrl( "admin_runtime", "admin-runtime-test-password", ), }); const serviceRuntime = new Client({ connectionString: fixture.connectionUrl( "service_runtime", "service-runtime-test-password", ), }); const sql = (statement: string) => fixture.psql(statement); try { const migration = spawnSync(process.execPath, [runnerPath], { encoding: "utf8", env: { ...process.env, SCHEMA_DATABASE_URL: fixture.connectionUrl( "schema_owner", "schema-owner-test-password", ), }, }); assert.equal(migration.status, 0, migration.stderr); await Promise.all([ admin.connect(), adminRuntime.connect(), serviceRuntime.connect(), ]); await serviceRuntime.query("set role service_role"); fixture.psqlAs( "identity_runtime", "identity-runtime-test-password", `insert into identity.users (id,name,email,email_verified,email_verified_at,role) values ('${ids.billing}','Billing Admin','billing-adjust@example.com',true,now(),'admin'), ('${ids.support}','Support Admin','support-adjust@example.com',true,now(),'admin'), ('${ids.oneTimeUser}','One Time User','one-time@example.com',true,now(),'user'), ('${ids.retryUser}','Retry User','retry@example.com',true,now(),'user'), ('${ids.compensateUser}','Compensate User','compensate@example.com',true,now(),'user')`, ); sql(` insert into public.admin_users(user_id,created_by) values ('${ids.billing}','${ids.billing}'),('${ids.support}','${ids.billing}'); insert into public.admin_user_roles(admin_user_id,role_id,assigned_by) select v.user_id,r.id,'${ids.billing}'::uuid from (values ('${ids.billing}'::uuid,'billing_admin'), ('${ids.support}'::uuid,'support') ) v(user_id,role_code) join public.admin_roles r on r.code=v.role_code; insert into public.billing_products( id,code,version,name,product_type,billing_period,interval_count, price_cents,enabled,status,effective_from,one_time_per_user ) values ('${ids.oneTimeProduct}','once_credits',1,'一次性积分','credit_pack','none',0,700,true,'published',now(),true), ('${ids.retryProduct}','retry_credits',1,'重试积分','credit_pack','none',0,500,true,'published',now(),false), ('${ids.compensateProduct}','compensate_credits',1,'补偿积分','credit_pack','none',0,400,true,'published',now(),false); insert into public.product_entitlements(product_id,feature_key,allowance_type,allowance_count,reset_period) values ('${ids.oneTimeProduct}','chat.standard','credits',7,'none'), ('${ids.retryProduct}','chat.standard','credits',5,'none'), ('${ids.compensateProduct}','chat.standard','credits',4,'none'); `); const insertOrder = async (input: { suffix: string; userId: string; productId: string; code: string; price: number; credits: number; oneTime: boolean; entitlements?: unknown[]; }) => { const entitlements = input.entitlements ?? [ { featureKey: "chat.standard", allowanceType: "credits", allowanceCount: input.credits, resetPeriod: "none", modelTier: null, fairUsePolicyId: null, metadata: {}, }, ]; const result = await admin.query<{ id: string }>( `insert into public.payment_orders ( order_no,user_id,package_id,product_id,product_code,product_version, product_snapshot,entitlement_snapshot,money_cents,currency,credits, grant_type,grant_status ) values ( $1,$2,null,$3,$4,1, jsonb_build_object( 'id',$3::uuid,'code',$4::text,'version',1,'name',$5::text,'description','', 'productType','credit_pack','billingPeriod','none','intervalCount',0, 'priceCents',$6::integer,'currency','CNY','oneTimePerUser',$7::boolean ), $8::jsonb,$6,'CNY',$9,'credits','pending' ) returning id`, [ orderNo(input.suffix), input.userId, input.productId, input.code, input.code, input.price, input.oneTime, JSON.stringify(entitlements), input.credits, ], ); return result.rows[0].id; }; const onceOrderA = await insertOrder({ suffix: "ONCEA", userId: ids.oneTimeUser, productId: ids.oneTimeProduct, code: "once_credits", price: 700, credits: 7, oneTime: true, }); await insertOrder({ suffix: "ONCEB", userId: ids.oneTimeUser, productId: ids.oneTimeProduct, code: "once_credits", price: 700, credits: 7, oneTime: true, }); const raceClients = [0, 1].map( () => new Client({ connectionString: fixture.connectionUrl( "service_runtime", "service-runtime-test-password", ), }), ); await Promise.all(raceClients.map((client) => client.connect())); await Promise.all( raceClients.map((client) => client.query("set role service_role")), ); try { const results = await Promise.all([ raceClients[0].query( "select * from public.settle_order($1,$2,$3,$4)", [orderNo("ONCEA"), "trade-once-a", 700, "hash-once-a"], ), raceClients[1].query( "select * from public.settle_order($1,$2,$3,$4)", [orderNo("ONCEB"), "trade-once-b", 700, "hash-once-b"], ), ]); assert.deepEqual( results.map((result) => result.rows[0].success).sort(), [false, true], ); assert.deepEqual( results.map((result) => result.rows[0].status).sort(), ["one_time_limit", "paid"], ); } finally { await Promise.all(raceClients.map((client) => client.end())); } assert.equal( sql(`select credits from public.profiles where id='${ids.oneTimeUser}'`), "7", ); assert.equal( sql(`select count(*) from public.user_product_redemptions where user_id='${ids.oneTimeUser}' and product_code='once_credits'`), "1", ); await assert.rejects( admin.query( "update public.payment_orders set product_snapshot=jsonb_set(product_snapshot,'{name}','\"tampered\"') where id=$1", [onceOrderA], ), (error) => /payment_order_snapshot_immutable/.test(errorText(error)), ); const retryOrder = await insertOrder({ suffix: "RETRY", userId: ids.retryUser, productId: ids.retryProduct, code: "retry_credits", price: 500, credits: 5, oneTime: false, }); await admin.query(` create function public.test_fail_retry_grant() returns trigger language plpgsql as $$ begin if new.request_id='${orderNo("RETRY")}' then raise exception 'forced_retry_grant_failure'; end if; return new; end $$; create trigger test_fail_retry_grant before insert on public.credit_transactions for each row execute function public.test_fail_retry_grant() `); const failedRetry = await serviceRuntime.query( "select * from public.settle_order($1,$2,$3,$4)", [orderNo("RETRY"), "trade-retry", 500, "hash-retry"], ); assert.equal(failedRetry.rows[0].success, false); assert.equal(failedRetry.rows[0].status, "grant_failed"); assert.equal( sql(`select credits from public.profiles where id='${ids.retryUser}'`), "0", ); await admin.query( "drop trigger test_fail_retry_grant on public.credit_transactions; drop function public.test_fail_retry_grant()", ); const retry = await adminRuntime.query( "select * from public.admin_adjust_order($1,$2,'retry_grant',0,$3,$4)", [ids.billing, retryOrder, "重试失败权益发放", "retry-adjustment-1"], ); assert.equal(retry.rows[0].action_success, true); assert.equal(retry.rows[0].adjustment_version, 1); assert.equal( sql(`select credits from public.profiles where id='${ids.retryUser}'`), "5", ); const retryAgain = await adminRuntime.query( "select * from public.admin_adjust_order($1,$2,'retry_grant',0,$3,$4)", [ids.billing, retryOrder, "同一请求幂等重放", "retry-adjustment-1"], ); assert.equal(retryAgain.rows[0].adjustment_version, 1); assert.equal( sql("select count(*) from audit.admin_audit_logs where request_id='retry-adjustment-1'"), "1", ); await assert.rejects( adminRuntime.query( "select * from public.admin_adjust_order($1,$2,'record_refund',0,$3,$4)", [ids.billing, retryOrder, "过期版本不得退款", "refund-stale"], ), (error) => (error as { code?: string }).code === "40001" && /admin_order_version_conflict/.test(errorText(error)), ); const compensateOrder = await insertOrder({ suffix: "COMPENSATE", userId: ids.compensateUser, productId: ids.compensateProduct, code: "compensate_credits", price: 400, credits: 4, oneTime: false, entitlements: [], }); const invalidGrant = await serviceRuntime.query( "select * from public.settle_order($1,$2,$3,$4)", [orderNo("COMPENSATE"), "trade-compensate", 400, "hash-compensate"], ); assert.equal(invalidGrant.rows[0].status, "invalid_snapshot"); await assert.rejects( adminRuntime.query( "select * from public.admin_adjust_order($1,$2,'compensate',0,'',$3)", [ids.billing, compensateOrder, "compensate-empty-reason"], ), (error) => /admin_reason_required/.test(errorText(error)), ); await assert.rejects( adminRuntime.query( "select * from public.admin_adjust_order($1,$2,'compensate',0,$3,$4)", [ids.support, compensateOrder, "无权人工补偿", "compensate-denied"], ), (error) => /admin_permission_denied/.test(errorText(error)), ); const compensated = await adminRuntime.query( "select * from public.admin_adjust_order($1,$2,'compensate',0,$3,$4)", [ids.billing, compensateOrder, "确认支付成功,人工补发积分", "compensate-1"], ); assert.equal(compensated.rows[0].grant_status, "granted"); assert.equal( sql(`select credits from public.profiles where id='${ids.compensateUser}'`), "4", ); assert.equal( sql(`select count(*) from public.credit_transactions where user_id='${ids.compensateUser}' and transaction_type='compensation'`), "1", ); const balanceBeforeRefund = sql( `select credits from public.profiles where id='${ids.retryUser}'`, ); const refunded = await adminRuntime.query( "select * from public.admin_adjust_order($1,$2,'record_refund',1,$3,$4)", [ids.billing, retryOrder, "线下已退款,仅登记账务状态", "refund-record-1"], ); assert.equal(refunded.rows[0].status, "refunded"); assert.equal(refunded.rows[0].refund_status, "recorded"); assert.equal( sql(`select credits from public.profiles where id='${ids.retryUser}'`), balanceBeforeRefund, ); assert.equal( sql(`select after_value->>'externalGatewayRefundAttempted' from audit.admin_audit_logs where request_id='refund-record-1'`), "f", ); assert.equal( sql(`select permission_used||':'||reason from audit.admin_audit_logs where request_id='compensate-1'`), "billing.adjustments.write:确认支付成功,人工补发积分", ); assert.equal( sql("select has_function_privilege('service_role','public.admin_create_redemption_codes(uuid,text,text,text,jsonb)','execute')"), "f", ); assert.equal( sql("select has_function_privilege('service_role','public.admin_update_redemption_code(uuid,text,text,text,uuid,boolean,text,boolean,timestamptz)','execute')"), "f", ); assert.equal( sql("select has_function_privilege('service_role','public.admin_revoke_redemption_code(uuid,text,text,text,uuid)','execute')"), "f", ); for (const role of ["service_role", "service_runtime"]) { assert.equal( sql(`select has_function_privilege('${role}','public.admin_adjust_order(uuid,uuid,text,integer,text,text)','execute')`), "f", ); assert.equal( sql(`select has_function_privilege('${role}','public.admin_create_redemption_codes(uuid,text,text,text,jsonb,text)','execute')`), "f", ); assert.equal( sql(`select has_function_privilege('${role}','public.admin_update_redemption_code(uuid,text,text,text,uuid,boolean,text,boolean,timestamptz,text)','execute')`), "f", ); assert.equal( sql(`select has_function_privilege('${role}','public.admin_revoke_redemption_code(uuid,text,text,text,uuid,text)','execute')`), "f", ); } assert.equal( sql("select pg_has_role('service_runtime','service_role','MEMBER')"), "t", ); assert.equal( sql("select has_function_privilege('service_role','public.settle_order(text,text,integer,text)','execute')"), "t", ); for (const signature of [ "public.admin_adjust_order(uuid,uuid,text,integer,text,text)", "public.admin_create_redemption_codes(uuid,text,text,text,jsonb,text)", "public.admin_update_redemption_code(uuid,text,text,text,uuid,boolean,text,boolean,timestamptz,text)", "public.admin_revoke_redemption_code(uuid,text,text,text,uuid,text)", ]) { assert.equal( sql(`select has_function_privilege('admin_runtime','${signature}','execute')`), "t", ); } await assert.rejects( serviceRuntime.query( "select * from public.admin_adjust_order($1,$2,'record_refund',1,$3,$4)", [ids.billing, retryOrder, "机器身份不得人工退款", "service-refund-denied"], ), (error) => (error as { code?: string }).code === "42501", ); await assert.rejects( serviceRuntime.query( "select * from public.admin_create_redemption_codes($1,$2,'admin',$3,$4::jsonb,$5)", [ids.billing, "billing-adjust@example.com", "service-code-denied", "[]", "机器身份不得创建兑换码"], ), (error) => (error as { code?: string }).code === "42501", ); await assert.rejects( adminRuntime.query( "select * from public.admin_create_redemption_codes($1,$2,'admin',$3,$4::jsonb,'')", [ ids.billing, "billing-adjust@example.com", "code-create-empty", JSON.stringify([ { codeHash: "a".repeat(64), codeMask: "JYOTISH-****-AAAA", credits: 3, expiresAt: null, note: "test", }, ]), ], ), (error) => /admin_reason_required/.test(errorText(error)), ); const createdCode = await adminRuntime.query<{ id: string }>( "select id from public.admin_create_redemption_codes($1,$2,'admin',$3,$4::jsonb,$5)", [ ids.billing, "billing-adjust@example.com", "code-create-1", JSON.stringify([ { codeHash: "b".repeat(64), codeMask: "JYOTISH-****-BBBB", credits: 3, expiresAt: null, note: "created", }, ]), "创建客服补偿码", ], ); const codeId = createdCode.rows[0].id; await adminRuntime.query( "select * from public.admin_update_redemption_code($1,$2,'admin',$3,$4,true,$5,false,null,$6)", [ ids.billing, "billing-adjust@example.com", "code-update-1", codeId, "updated", "修正兑换码备注", ], ); await adminRuntime.query( "select * from public.admin_revoke_redemption_code($1,$2,'admin',$3,$4,$5)", [ ids.billing, "billing-adjust@example.com", "code-revoke-1", codeId, "撤销未发放兑换码", ], ); assert.equal( sql(`select string_agg(action||':'||permission_used||':'||reason,'|' order by created_at) from audit.admin_audit_logs where target_id='${codeId}'`), "redemption_code.create:admin.access:创建客服补偿码|redemption_code.update:admin.access:修正兑换码备注|redemption_code.revoke:admin.access:撤销未发放兑换码", ); } finally { await Promise.all([ admin.end().catch(() => {}), adminRuntime.end().catch(() => {}), serviceRuntime.end().catch(() => {}), ]); fixture.stop(); } });