d3ro-voice/server/supabase/tests/payment-provider.integration.sql
2026-08-29 18:33:45 +09:00

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;