453 lines
16 KiB
PL/PgSQL
453 lines
16 KiB
PL/PgSQL
\set ON_ERROR_STOP on
|
|
|
|
BEGIN;
|
|
|
|
CREATE OR REPLACE FUNCTION pg_temp.assert_true(condition boolean, message text)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
IF condition IS NOT TRUE THEN
|
|
RAISE EXCEPTION 'assertion_failed: %', message;
|
|
END IF;
|
|
END;
|
|
$$;
|
|
|
|
INSERT INTO auth.users (
|
|
id, aud, role, email, encrypted_password, email_confirmed_at,
|
|
raw_app_meta_data, raw_user_meta_data, created_at, updated_at
|
|
) VALUES
|
|
(
|
|
'10000000-0000-4000-8000-000000000001', 'authenticated', 'authenticated',
|
|
'provider-one@example.invalid', crypt('fixture-password', gen_salt('bf')), now(),
|
|
'{"provider":"email","providers":["email"]}'::jsonb, '{}'::jsonb, now(), now()
|
|
),
|
|
(
|
|
'10000000-0000-4000-8000-000000000002', 'authenticated', 'authenticated',
|
|
'provider-two@example.invalid', crypt('fixture-password', gen_salt('bf')), now(),
|
|
'{"provider":"email","providers":["email"]}'::jsonb, '{}'::jsonb, now(), now()
|
|
);
|
|
|
|
SELECT pg_temp.assert_true(
|
|
NOT has_function_privilege(
|
|
'authenticated',
|
|
'public.apply_payment_provider_event(uuid,text,text,timestamptz,text,text,text,text,text,boolean,timestamptz,timestamptz,timestamptz,boolean,text,text,text,uuid,uuid)',
|
|
'EXECUTE'
|
|
),
|
|
'authenticated role must not execute entitlement RPC'
|
|
);
|
|
SELECT pg_temp.assert_true(
|
|
has_function_privilege(
|
|
'service_role',
|
|
'public.apply_payment_provider_event(uuid,text,text,timestamptz,text,text,text,text,text,boolean,timestamptz,timestamptz,timestamptz,boolean,text,text,text,uuid,uuid)',
|
|
'EXECUTE'
|
|
),
|
|
'service role must execute entitlement RPC'
|
|
);
|
|
SELECT pg_temp.assert_true(
|
|
NOT has_function_privilege(
|
|
'authenticated',
|
|
'public.reserve_payment_provider_operation(uuid,text,text,text,text,text,text)',
|
|
'EXECUTE'
|
|
),
|
|
'authenticated role must not reserve provider operations directly'
|
|
);
|
|
|
|
DO $$
|
|
DECLARE
|
|
v_payple_operation jsonb;
|
|
v_stripe_operation jsonb;
|
|
v_operation_id uuid;
|
|
v_apply jsonb;
|
|
v_duplicate jsonb;
|
|
v_stale jsonb;
|
|
v_wrong_cancel jsonb;
|
|
v_cancel jsonb;
|
|
v_subscription public.subscriptions%ROWTYPE;
|
|
v_profile_tier text;
|
|
BEGIN
|
|
v_payple_operation := public.reserve_payment_provider_operation(
|
|
'10000000-0000-4000-8000-000000000001',
|
|
'payple',
|
|
'checkout',
|
|
'pro',
|
|
'payple-checkout:fixture-one',
|
|
'D3RO-20260821120000-fixture1',
|
|
NULL
|
|
);
|
|
PERFORM pg_temp.assert_true((v_payple_operation->>'created')::boolean, 'Payple reservation created');
|
|
v_operation_id := (v_payple_operation->>'operation_id')::uuid;
|
|
|
|
v_stripe_operation := public.reserve_payment_provider_operation(
|
|
'10000000-0000-4000-8000-000000000001',
|
|
'stripe',
|
|
'checkout',
|
|
'pro_plus',
|
|
'stripe-checkout:fixture-race',
|
|
'STRIPE-00000000-0000-4000-8000-000000000001',
|
|
NULL
|
|
);
|
|
PERFORM pg_temp.assert_true(
|
|
v_stripe_operation->>'reason' = 'payment_operation_in_progress',
|
|
'concurrent Stripe checkout must be blocked before external work'
|
|
);
|
|
|
|
v_apply := public.apply_payment_provider_event(
|
|
p_user_id => '10000000-0000-4000-8000-000000000001',
|
|
p_provider => 'stripe',
|
|
p_event_id => 'evt_stripe_during_payple_reservation',
|
|
p_event_created_at => now() - interval '5 minutes',
|
|
p_event_type => 'customer.subscription.created',
|
|
p_payload_digest => repeat('1', 64),
|
|
p_provider_resource_id => 'sub_stripe_blocked',
|
|
p_tier => 'pro_plus',
|
|
p_status => 'active',
|
|
p_entitled => true,
|
|
p_current_period_start => now() - interval '5 minutes',
|
|
p_current_period_end => now() + interval '1 month',
|
|
p_cancel_at => NULL,
|
|
p_auto_renewing => true,
|
|
p_provider_customer_id => 'cus_stripe_blocked',
|
|
p_provider_order_id => NULL,
|
|
p_store_product_id => NULL,
|
|
p_store_purchase_id => NULL,
|
|
p_operation_id => NULL
|
|
);
|
|
PERFORM pg_temp.assert_true(
|
|
v_apply->>'reason' = 'other_provider_operation_in_progress',
|
|
'provider event must not overtake a reserved other-provider checkout'
|
|
);
|
|
|
|
v_apply := public.apply_payment_provider_event(
|
|
p_user_id => '10000000-0000-4000-8000-000000000001',
|
|
p_provider => 'payple',
|
|
p_event_id => 'payple-payment-fixture-one',
|
|
p_event_created_at => now() - interval '4 minutes',
|
|
p_event_type => 'payment.completed',
|
|
p_payload_digest => repeat('2', 64),
|
|
p_provider_resource_id => 'payer-fixture-one',
|
|
p_tier => 'pro',
|
|
p_status => 'active',
|
|
p_entitled => true,
|
|
p_current_period_start => now() - interval '4 minutes',
|
|
p_current_period_end => now() + interval '1 month',
|
|
p_cancel_at => NULL,
|
|
p_auto_renewing => true,
|
|
p_provider_customer_id => 'payer-fixture-one',
|
|
p_provider_order_id => 'D3RO-20260821120000-fixture1',
|
|
p_store_product_id => NULL,
|
|
p_store_purchase_id => NULL,
|
|
p_operation_id => v_operation_id
|
|
);
|
|
PERFORM pg_temp.assert_true((v_apply->>'applied')::boolean, 'Payple event applied');
|
|
|
|
SELECT * INTO v_subscription FROM public.subscriptions
|
|
WHERE user_id = '10000000-0000-4000-8000-000000000001';
|
|
SELECT tier INTO v_profile_tier FROM public.profiles
|
|
WHERE id = '10000000-0000-4000-8000-000000000001';
|
|
PERFORM pg_temp.assert_true(v_subscription.provider = 'payple', 'provider is entitlement SSOT');
|
|
PERFORM pg_temp.assert_true(v_subscription.payment_provider = 'payple', 'compatibility mirror follows provider');
|
|
PERFORM pg_temp.assert_true(v_subscription.tier = 'pro' AND v_profile_tier = 'pro', 'profile tier cannot drift');
|
|
|
|
BEGIN
|
|
PERFORM public.apply_verified_google_play_purchase(
|
|
'10000000-0000-4000-8000-000000000001',
|
|
'google_play',
|
|
'd3ro_voice_pro_plus_monthly',
|
|
'GPA.9999-8888-7777-66666',
|
|
repeat('f', 64),
|
|
NULL,
|
|
'fixture-conflicting-google-play-token',
|
|
'purchased',
|
|
now(),
|
|
now() + interval '1 month',
|
|
true,
|
|
true,
|
|
'pro_plus',
|
|
true,
|
|
'{"source":"provider-conflict"}'::jsonb
|
|
);
|
|
RAISE EXCEPTION 'expected active_subscription_other_provider';
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
IF SQLERRM NOT LIKE '%active_subscription_other_provider%' THEN RAISE; END IF;
|
|
END;
|
|
|
|
v_duplicate := public.apply_payment_provider_event(
|
|
p_user_id => '10000000-0000-4000-8000-000000000001',
|
|
p_provider => 'payple',
|
|
p_event_id => 'payple-payment-fixture-one',
|
|
-- The synchronous response and webhook can observe the same provider
|
|
-- charge at slightly different local times; the provider event identity,
|
|
-- not delivery time, makes the second path an exact replay.
|
|
p_event_created_at => now() - interval '3 minutes 30 seconds',
|
|
p_event_type => 'payment.completed',
|
|
p_payload_digest => repeat('2', 64),
|
|
p_provider_resource_id => 'payer-fixture-one',
|
|
p_tier => 'pro',
|
|
p_status => 'active',
|
|
p_entitled => true,
|
|
p_current_period_start => now() - interval '4 minutes',
|
|
p_current_period_end => now() + interval '1 month',
|
|
p_cancel_at => NULL,
|
|
p_auto_renewing => true,
|
|
p_provider_customer_id => 'payer-fixture-one',
|
|
p_provider_order_id => 'D3RO-20260821120000-fixture1',
|
|
p_store_product_id => NULL,
|
|
p_store_purchase_id => NULL,
|
|
p_operation_id => v_operation_id
|
|
);
|
|
PERFORM pg_temp.assert_true((v_duplicate->>'duplicate')::boolean, 'provider event replay is idempotent');
|
|
|
|
BEGIN
|
|
PERFORM public.apply_payment_provider_event(
|
|
p_user_id => '10000000-0000-4000-8000-000000000001',
|
|
p_provider => 'payple',
|
|
p_event_id => 'payple-payment-fixture-one',
|
|
p_event_created_at => now() - interval '4 minutes',
|
|
p_event_type => 'payment.completed',
|
|
p_payload_digest => repeat('3', 64),
|
|
p_provider_resource_id => 'payer-fixture-one',
|
|
p_tier => 'pro',
|
|
p_status => 'active',
|
|
p_entitled => true
|
|
);
|
|
RAISE EXCEPTION 'expected provider_event_payload_mismatch';
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
IF SQLERRM NOT LIKE '%provider_event_payload_mismatch%' THEN RAISE; END IF;
|
|
END;
|
|
|
|
BEGIN
|
|
PERFORM public.apply_payment_provider_event(
|
|
p_user_id => '10000000-0000-4000-8000-000000000001',
|
|
p_provider => 'payple',
|
|
p_event_id => 'payple-payment-fixture-one',
|
|
p_event_created_at => now() - interval '4 minutes',
|
|
p_event_type => 'payment.refunded',
|
|
p_payload_digest => repeat('2', 64),
|
|
p_provider_resource_id => 'payer-fixture-one',
|
|
p_tier => 'pro',
|
|
p_status => 'active',
|
|
p_entitled => true
|
|
);
|
|
RAISE EXCEPTION 'expected provider_event_payload_mismatch';
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
IF SQLERRM NOT LIKE '%provider_event_payload_mismatch%' THEN RAISE; END IF;
|
|
END;
|
|
|
|
v_stale := public.apply_payment_provider_event(
|
|
p_user_id => '10000000-0000-4000-8000-000000000001',
|
|
p_provider => 'payple',
|
|
p_event_id => 'payple-stale-fixture',
|
|
p_event_created_at => now() - interval '10 minutes',
|
|
p_event_type => 'subscription.updated',
|
|
p_payload_digest => repeat('4', 64),
|
|
p_provider_resource_id => 'payer-fixture-one',
|
|
p_tier => 'pro_plus',
|
|
p_status => 'active',
|
|
p_entitled => true
|
|
);
|
|
PERFORM pg_temp.assert_true(v_stale->>'reason' = 'stale_provider_event', 'older event rejected');
|
|
|
|
v_wrong_cancel := public.apply_payment_provider_event(
|
|
p_user_id => '10000000-0000-4000-8000-000000000001',
|
|
p_provider => 'payple',
|
|
p_event_id => 'payple-cancel-wrong-resource',
|
|
p_event_created_at => now() - interval '3 minutes',
|
|
p_event_type => 'subscription.canceled',
|
|
p_payload_digest => repeat('5', 64),
|
|
p_provider_resource_id => 'payer-attacker',
|
|
p_tier => 'free',
|
|
p_status => 'canceled',
|
|
p_entitled => false
|
|
);
|
|
PERFORM pg_temp.assert_true(
|
|
v_wrong_cancel->>'reason' = 'provider_resource_not_owner',
|
|
'foreign provider resource cannot revoke entitlement'
|
|
);
|
|
|
|
v_cancel := public.apply_payment_provider_event(
|
|
p_user_id => '10000000-0000-4000-8000-000000000001',
|
|
p_provider => 'payple',
|
|
p_event_id => 'payple-cancel-correct-resource',
|
|
p_event_created_at => now() - interval '2 minutes',
|
|
p_event_type => 'subscription.canceled',
|
|
p_payload_digest => repeat('6', 64),
|
|
p_provider_resource_id => 'payer-fixture-one',
|
|
p_tier => 'free',
|
|
p_status => 'canceled',
|
|
p_entitled => false,
|
|
p_current_period_end => now() - interval '2 minutes',
|
|
p_cancel_at => now() - interval '2 minutes',
|
|
p_auto_renewing => false
|
|
);
|
|
PERFORM pg_temp.assert_true((v_cancel->>'applied')::boolean, 'owner cancellation applied');
|
|
|
|
UPDATE public.subscriptions
|
|
SET payment_provider = 'stripe'
|
|
WHERE user_id = '10000000-0000-4000-8000-000000000001';
|
|
SELECT * INTO v_subscription FROM public.subscriptions
|
|
WHERE user_id = '10000000-0000-4000-8000-000000000001';
|
|
SELECT tier INTO v_profile_tier FROM public.profiles
|
|
WHERE id = '10000000-0000-4000-8000-000000000001';
|
|
PERFORM pg_temp.assert_true(
|
|
v_subscription.provider = 'none' AND v_subscription.payment_provider = 'none',
|
|
'payment_provider cannot diverge from provider'
|
|
);
|
|
PERFORM pg_temp.assert_true(v_subscription.tier = 'free' AND v_profile_tier = 'free', 'revocation syncs profile');
|
|
|
|
v_apply := public.apply_payment_provider_event(
|
|
p_user_id => '10000000-0000-4000-8000-000000000001',
|
|
p_provider => 'stripe',
|
|
p_event_id => 'evt_stripe_after_payple_release',
|
|
p_event_created_at => now() - interval '1 minute',
|
|
p_event_type => 'customer.subscription.updated',
|
|
p_payload_digest => repeat('7', 64),
|
|
p_provider_resource_id => 'sub_stripe_owner',
|
|
p_tier => 'pro_plus',
|
|
p_status => 'active',
|
|
p_entitled => true,
|
|
p_current_period_start => now() - interval '1 minute',
|
|
p_current_period_end => now() + interval '1 month',
|
|
p_auto_renewing => true,
|
|
p_provider_customer_id => 'cus_stripe_owner'
|
|
);
|
|
PERFORM pg_temp.assert_true((v_apply->>'applied')::boolean, 'newer Stripe event applies after release');
|
|
|
|
BEGIN
|
|
PERFORM public.apply_payment_provider_event(
|
|
p_user_id => '10000000-0000-4000-8000-000000000002',
|
|
p_provider => 'stripe',
|
|
p_event_id => 'evt_stripe_duplicate_resource_owner',
|
|
p_event_created_at => now(),
|
|
p_event_type => 'customer.subscription.created',
|
|
p_payload_digest => repeat('9', 64),
|
|
p_provider_resource_id => 'sub_stripe_owner',
|
|
p_tier => 'pro',
|
|
p_status => 'active',
|
|
p_entitled => true,
|
|
p_current_period_start => now(),
|
|
p_current_period_end => now() + interval '1 month',
|
|
p_auto_renewing => true,
|
|
p_provider_customer_id => 'cus_wrong_owner'
|
|
);
|
|
RAISE EXCEPTION 'expected unique provider resource owner violation';
|
|
EXCEPTION
|
|
WHEN unique_violation THEN NULL;
|
|
END;
|
|
|
|
v_apply := public.apply_payment_provider_event(
|
|
p_user_id => '10000000-0000-4000-8000-000000000001',
|
|
p_provider => 'payple',
|
|
p_event_id => 'payple-late-after-stripe',
|
|
p_event_created_at => now(),
|
|
p_event_type => 'subscription.updated',
|
|
p_payload_digest => repeat('8', 64),
|
|
p_provider_resource_id => 'payer-fixture-one',
|
|
p_tier => 'pro',
|
|
p_status => 'active',
|
|
p_entitled => true
|
|
);
|
|
PERFORM pg_temp.assert_true(
|
|
v_apply->>'reason' = 'active_subscription_other_provider',
|
|
'late Payple event cannot overwrite Stripe ownership'
|
|
);
|
|
END;
|
|
$$;
|
|
|
|
DO $$
|
|
DECLARE
|
|
v_result jsonb;
|
|
v_subscription public.subscriptions%ROWTYPE;
|
|
v_profile_tier text;
|
|
BEGIN
|
|
v_result := public.apply_verified_google_play_purchase(
|
|
'10000000-0000-4000-8000-000000000002',
|
|
'google_play',
|
|
'd3ro_voice_pro_monthly',
|
|
'GPA.1234-5678-9012-34567',
|
|
repeat('a', 64),
|
|
NULL,
|
|
'fixture-google-play-token',
|
|
'purchased',
|
|
now() - interval '1 day',
|
|
now() + interval '29 days',
|
|
true,
|
|
true,
|
|
'pro',
|
|
true,
|
|
'{"source":"integration"}'::jsonb
|
|
);
|
|
PERFORM pg_temp.assert_true((v_result->>'applied')::boolean, 'legacy Play wrapper grants entitlement');
|
|
SELECT * INTO v_subscription FROM public.subscriptions
|
|
WHERE user_id = '10000000-0000-4000-8000-000000000002';
|
|
SELECT tier INTO v_profile_tier FROM public.profiles
|
|
WHERE id = '10000000-0000-4000-8000-000000000002';
|
|
PERFORM pg_temp.assert_true(
|
|
v_subscription.provider = 'google_play'
|
|
AND v_subscription.payment_provider = 'google_play'
|
|
AND v_subscription.tier = 'pro'
|
|
AND v_profile_tier = 'pro',
|
|
'Play wrapper uses provider SSOT and profile sync'
|
|
);
|
|
|
|
v_result := public.reserve_payment_provider_operation(
|
|
'10000000-0000-4000-8000-000000000002',
|
|
'payple',
|
|
'checkout',
|
|
'pro_plus',
|
|
'payple-checkout:blocked-by-play',
|
|
'D3RO-20260821130000-fixture2',
|
|
NULL
|
|
);
|
|
PERFORM pg_temp.assert_true(
|
|
v_result->>'reason' = 'active_subscription_other_provider',
|
|
'Play ownership blocks Payple external checkout'
|
|
);
|
|
|
|
v_result := public.apply_verified_google_play_purchase(
|
|
'10000000-0000-4000-8000-000000000002',
|
|
'google_play',
|
|
'd3ro_voice_pro_monthly',
|
|
'GPA.1234-5678-9012-34567',
|
|
repeat('a', 64),
|
|
NULL,
|
|
'fixture-google-play-token',
|
|
'cancelled',
|
|
now() - interval '31 days',
|
|
now() - interval '1 day',
|
|
false,
|
|
true,
|
|
'pro',
|
|
false,
|
|
'{"source":"integration","state":"canceled"}'::jsonb
|
|
);
|
|
PERFORM pg_temp.assert_true((v_result->>'applied')::boolean, 'legacy Play wrapper revokes entitlement');
|
|
SELECT * INTO v_subscription FROM public.subscriptions
|
|
WHERE user_id = '10000000-0000-4000-8000-000000000002';
|
|
SELECT tier INTO v_profile_tier FROM public.profiles
|
|
WHERE id = '10000000-0000-4000-8000-000000000002';
|
|
PERFORM pg_temp.assert_true(
|
|
v_subscription.provider = 'none'
|
|
AND v_subscription.payment_provider = 'none'
|
|
AND v_subscription.tier = 'free'
|
|
AND v_profile_tier = 'free',
|
|
'Play revocation clears entitlement consistently'
|
|
);
|
|
END;
|
|
$$;
|
|
|
|
SELECT pg_temp.assert_true(
|
|
(SELECT count(*) FROM public.payment_provider_events) >= 9,
|
|
'provider events are durably recorded'
|
|
);
|
|
SELECT pg_temp.assert_true(
|
|
(SELECT count(*) FROM public.payment_provider_events WHERE disposition = 'rejected') >= 2,
|
|
'ownership conflicts remain auditable'
|
|
);
|
|
|
|
ROLLBACK;
|
|
|
|
SELECT 'payment_provider_integration_ok' AS result;
|