\set ON_ERROR_STOP on -- Regression: a confirmed refund of an older Payple order (same billing key) -- must not revoke the period funded by the current order. -- Covers apply_payment_provider_event from -- 20260929100011_payple_cancellation_order_scope.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-000000000031', 'authenticated', 'authenticated', 'payple-order-scope@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-000000000031'; c_payer constant text := 'payer-order-scope'; c_oid1 constant text := 'D3RO-20260801090000-scope01'; c_oid2 constant text := 'D3RO-20260901090000-scope02'; v_result jsonb; v_period_end timestamptz; v_cursor_before timestamptz; v_cursor_after timestamptz; v_sub public.subscriptions%ROWTYPE; v_disposition text; BEGIN -- Month 1: checkout order oid1. v_result := public.apply_payment_provider_event( p_user_id => c_user, p_provider => 'payple', p_event_id => 'payment:' || c_oid1, p_event_created_at => now() - interval '40 days', p_event_type => 'payment.completed', p_payload_digest => repeat('a', 64), p_provider_resource_id => c_payer, p_tier => 'pro', p_status => 'active', p_entitled => true, p_current_period_start => now() - interval '40 days', p_current_period_end => now() - interval '10 days', p_auto_renewing => true, p_provider_customer_id => c_payer, p_provider_order_id => c_oid1 ); PERFORM pg_temp.assert_true((v_result->>'applied')::boolean, 'month 1 applied'); -- Month 2: renewal order oid2 on the same billing key. v_period_end := now() + interval '20 days'; v_result := public.apply_payment_provider_event( p_user_id => c_user, p_provider => 'payple', p_event_id => 'payment:' || c_oid2, p_event_created_at => now() - interval '10 days', p_event_type => 'payment.completed', p_payload_digest => repeat('b', 64), p_provider_resource_id => c_payer, p_tier => 'pro', p_status => 'active', p_entitled => true, p_current_period_start => now() - interval '10 days', p_current_period_end => v_period_end, p_auto_renewing => true, p_provider_customer_id => c_payer, p_provider_order_id => c_oid2 ); PERFORM pg_temp.assert_true((v_result->>'applied')::boolean, 'month 2 applied'); SELECT last_event_created_at INTO v_cursor_before FROM public.payment_provider_cursors WHERE user_id = c_user AND provider = 'payple'; -- Support refunds oid1; Payple confirms the cancellation webhook. v_result := public.apply_payment_provider_event( p_user_id => c_user, p_provider => 'payple', p_event_id => 'cancel:' || c_oid1 || ':승인취소완료', p_event_created_at => now(), p_event_type => 'webhook.payment_canceled', p_payload_digest => repeat('c', 64), p_provider_resource_id => c_payer, p_tier => 'free', p_status => 'canceled', p_entitled => false, p_current_period_start => NULL, p_current_period_end => now(), p_cancel_at => now(), p_auto_renewing => false, p_provider_customer_id => c_payer, p_provider_order_id => c_oid1 ); PERFORM pg_temp.assert_true( NOT (v_result->>'applied')::boolean AND v_result->>'reason' = 'canceled_order_not_current', 'refund of an older order is ignored, got ' || v_result::text ); SELECT * INTO v_sub FROM public.subscriptions WHERE user_id = c_user; PERFORM pg_temp.assert_true( v_sub.tier = 'pro' AND v_sub.provider = 'payple' AND v_sub.auto_renewing AND v_sub.payple_pay_oid = c_oid2 AND v_sub.current_period_end = v_period_end AND v_sub.provider_resource_id = c_payer, 'current paid period and renewals are preserved' ); SELECT disposition INTO v_disposition FROM public.payment_provider_events WHERE provider = 'payple' AND event_id = 'cancel:' || c_oid1 || ':승인취소완료'; PERFORM pg_temp.assert_true(v_disposition = 'ignored', 'ignored event is recorded'); SELECT last_event_created_at INTO v_cursor_after FROM public.payment_provider_cursors WHERE user_id = c_user AND provider = 'payple'; PERFORM pg_temp.assert_true( v_cursor_after = v_cursor_before, 'ignored refund does not advance the ordering cursor' ); -- Refunding the current order still revokes entitlement. v_result := public.apply_payment_provider_event( p_user_id => c_user, p_provider => 'payple', p_event_id => 'cancel:' || c_oid2 || ':승인취소완료', p_event_created_at => now(), p_event_type => 'webhook.payment_canceled', p_payload_digest => repeat('d', 64), p_provider_resource_id => c_payer, p_tier => 'free', p_status => 'canceled', p_entitled => false, p_current_period_start => NULL, p_current_period_end => now(), p_cancel_at => now(), p_auto_renewing => false, p_provider_customer_id => c_payer, p_provider_order_id => c_oid2 ); PERFORM pg_temp.assert_true( (v_result->>'applied')::boolean, 'refund of the current order is applied, got ' || v_result::text ); SELECT * INTO v_sub FROM public.subscriptions WHERE user_id = c_user; PERFORM pg_temp.assert_true( v_sub.tier = 'free' AND v_sub.provider = 'none' AND NOT v_sub.auto_renewing, 'current order refund revokes the subscription' ); -- Revocations that name no order (scheduled expiry, manage) are unaffected. v_result := public.apply_payment_provider_event( p_user_id => c_user, p_provider => 'payple', p_event_id => 'payment:D3RO-20260910090000-scope03', p_event_created_at => now() + interval '1 second', p_event_type => 'payment.completed', p_payload_digest => repeat('e', 64), p_provider_resource_id => c_payer, 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 => false, p_provider_customer_id => c_payer, p_provider_order_id => 'D3RO-20260910090000-scope03' ); PERFORM pg_temp.assert_true((v_result->>'applied')::boolean, 're-entitled'); v_result := public.apply_payment_provider_event( p_user_id => c_user, p_provider => 'payple', p_event_id => 'scheduled-expire:order-scope', p_event_created_at => now() + interval '2 seconds', p_event_type => 'subscription.scheduled_expiry', p_payload_digest => repeat('f', 64), p_provider_resource_id => c_payer, p_tier => 'free', p_status => 'expired', p_entitled => false, p_provider_order_id => NULL ); PERFORM pg_temp.assert_true( (v_result->>'applied')::boolean, 'order-less revocation still applies, got ' || v_result::text ); END; $$; ROLLBACK; SELECT 'payple_cancellation_order_scope_ok' AS result;