146 lines
5.2 KiB
PL/PgSQL
146 lines
5.2 KiB
PL/PgSQL
\set ON_ERROR_STOP on
|
|
|
|
-- Regression: a second Payple checkout (stale tab, direct API call) must not
|
|
-- charge an active Payple subscriber again and restart the paid period.
|
|
-- Covers reserve_payment_provider_operation from
|
|
-- 20260928200000_payple_checkout_active_subscription_guard.sql.
|
|
|
|
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 (
|
|
'20000000-0000-4000-8000-000000000020', 'authenticated', 'authenticated',
|
|
'payple-active-guard@example.invalid', crypt('fixture-password', gen_salt('bf')), now(),
|
|
'{"provider":"email","providers":["email"]}'::jsonb, '{}'::jsonb, now(), now()
|
|
);
|
|
|
|
DO $$
|
|
DECLARE
|
|
c_user constant uuid := '20000000-0000-4000-8000-000000000020';
|
|
v_first jsonb;
|
|
v_apply jsonb;
|
|
v_second jsonb;
|
|
v_upgrade jsonb;
|
|
v_cancel jsonb;
|
|
v_lapsed jsonb;
|
|
v_period_end timestamptz;
|
|
v_before public.subscriptions%ROWTYPE;
|
|
v_after public.subscriptions%ROWTYPE;
|
|
v_operations integer;
|
|
BEGIN
|
|
-- First tab: a free user subscribes to Pro through Payple.
|
|
v_first := public.reserve_payment_provider_operation(
|
|
c_user, 'payple', 'checkout', 'pro',
|
|
'payple-checkout:active-guard-first', 'D3RO-20260928030000-guard01',
|
|
'payer-active-guard'
|
|
);
|
|
PERFORM pg_temp.assert_true((v_first->>'created')::boolean, 'free user can reserve checkout');
|
|
|
|
v_period_end := now() + interval '1 month';
|
|
v_apply := public.apply_payment_provider_event(
|
|
p_user_id => c_user,
|
|
p_provider => 'payple',
|
|
p_event_id => 'payple-payment:active-guard-first',
|
|
p_event_created_at => now() - interval '1 minute',
|
|
p_event_type => 'payment.completed',
|
|
p_payload_digest => repeat('a', 64),
|
|
p_provider_resource_id => 'payer-active-guard',
|
|
p_tier => 'pro',
|
|
p_status => 'active',
|
|
p_entitled => true,
|
|
p_current_period_start => now() - interval '1 minute',
|
|
p_current_period_end => v_period_end,
|
|
p_cancel_at => NULL,
|
|
p_auto_renewing => true,
|
|
p_provider_customer_id => 'payer-active-guard',
|
|
p_provider_order_id => 'D3RO-20260928030000-guard01',
|
|
p_operation_id => (v_first->>'operation_id')::uuid
|
|
);
|
|
PERFORM pg_temp.assert_true((v_apply->>'applied')::boolean, 'first checkout applied');
|
|
|
|
SELECT * INTO v_before FROM public.subscriptions WHERE user_id = c_user;
|
|
|
|
-- Second (stale) tab: fresh idempotency key, same provider, same tier.
|
|
v_second := public.reserve_payment_provider_operation(
|
|
c_user, 'payple', 'checkout', 'pro',
|
|
'payple-checkout:active-guard-second', 'D3RO-20260928030500-guard02',
|
|
'payer-active-guard'
|
|
);
|
|
PERFORM pg_temp.assert_true(
|
|
NOT coalesce((v_second->>'created')::boolean, false),
|
|
'active Payple subscriber must not reserve a second checkout'
|
|
);
|
|
PERFORM pg_temp.assert_true(
|
|
v_second->>'reason' = 'subscription_already_active',
|
|
'second checkout is rejected as subscription_already_active, got '
|
|
|| coalesce(v_second->>'reason', '<null>')
|
|
);
|
|
|
|
-- An unprorated full-price "upgrade" through checkout is rejected too.
|
|
v_upgrade := public.reserve_payment_provider_operation(
|
|
c_user, 'payple', 'checkout', 'pro_plus',
|
|
'payple-checkout:active-guard-upgrade', 'D3RO-20260928031000-guard03',
|
|
'payer-active-guard'
|
|
);
|
|
PERFORM pg_temp.assert_true(
|
|
v_upgrade->>'reason' = 'subscription_already_active',
|
|
'checkout cannot be used as an unprorated upgrade'
|
|
);
|
|
|
|
SELECT count(*) INTO v_operations
|
|
FROM public.payment_provider_operations
|
|
WHERE user_id = c_user AND operation_type = 'checkout';
|
|
PERFORM pg_temp.assert_true(v_operations = 1, 'rejected checkouts create no operation');
|
|
|
|
SELECT * INTO v_after FROM public.subscriptions WHERE user_id = c_user;
|
|
PERFORM pg_temp.assert_true(
|
|
v_after.current_period_start = v_before.current_period_start
|
|
AND v_after.current_period_end = v_before.current_period_end
|
|
AND v_after.current_period_end = v_period_end
|
|
AND v_after.tier = 'pro',
|
|
'paid period is preserved after the rejected checkout'
|
|
);
|
|
|
|
-- Cancellation of the live entitlement is still possible.
|
|
v_cancel := public.reserve_payment_provider_operation(
|
|
c_user, 'payple', 'cancellation', NULL,
|
|
'payple-manage:active-guard-cancel', NULL,
|
|
'payer-active-guard'
|
|
);
|
|
PERFORM pg_temp.assert_true((v_cancel->>'created')::boolean, 'active subscriber can still cancel');
|
|
PERFORM public.mark_payment_provider_operation(
|
|
(v_cancel->>'operation_id')::uuid, 'failed', NULL, 'fixture_released'
|
|
);
|
|
|
|
-- Once the paid period has lapsed, a new checkout is allowed again.
|
|
UPDATE public.subscriptions
|
|
SET current_period_end = now() - interval '1 second'
|
|
WHERE user_id = c_user;
|
|
v_lapsed := public.reserve_payment_provider_operation(
|
|
c_user, 'payple', 'checkout', 'pro',
|
|
'payple-checkout:active-guard-lapsed', 'D3RO-20260928031500-guard04',
|
|
'payer-active-guard'
|
|
);
|
|
PERFORM pg_temp.assert_true(
|
|
(v_lapsed->>'created')::boolean,
|
|
'checkout is allowed after the paid Payple period ended'
|
|
);
|
|
END;
|
|
$$;
|
|
|
|
ROLLBACK;
|
|
|
|
SELECT 'payple_checkout_active_subscription_ok' AS result;
|