fix(payple): reject checkout while a paid Payple period is still active
This commit is contained in:
parent
f456d737c4
commit
e69fe0335d
5 changed files with 717 additions and 71 deletions
|
|
@ -0,0 +1,146 @@
|
|||
\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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue