Batch of red-team r3 fixes that were in the working tree before the 2026-09-28 design overhaul, committed as one unit with their tests. - desktop main: STT timeouts and sidecar, voice recording store, sync (credentials, audio, knowledge reindex, push gates), runtime provisioner, update policy, AltGr keybindings, voice-command policy, dictionary file codec/limits, meeting transcript condensing and a local recording ledger so interrupted-session recovery only closes meetings this device recorded (a phone's live meeting is left alone). - mobile: login CSRF via implicit token callbacks rejected, account deletion/retention, durable queue retention, knowledge realtime without unfiltered DELETE, meeting re-record failure paths, cloud STT client, preferences store/resync. - core: text chunking splits long unbroken transcripts to fit, template field policy, dictionary limits, meeting markdown inline handling. - server: payple webhook policy and cancellation order scope, meeting document generation quota, team RPC null-role guard, unified LLM quota in-flight accounting, knowledge chunk vector index, meeting re-record failure paths (migrations 20260929*). - ci: portable/runtime feed gates, update-policy schema, Forgejo file delete and alias planning. Four older tests are updated to the new contracts rather than the old behavior: token-pair auth callbacks are rejected, knowledge realtime no longer subscribes to DELETE, long transcript lines are split, and meeting recovery requires the local recording ledger for empty rows.
380 lines
14 KiB
PL/PgSQL
380 lines
14 KiB
PL/PgSQL
-- ============================================================================
|
|
-- Payple: a refund of an older order must not revoke the current period
|
|
--
|
|
-- apply_payment_provider_event checked a non-entitled (revoking) event only
|
|
-- against subscriptions.provider and provider_resource_id. For Payple that is
|
|
-- the billing-key payer id, shared by every order charged on the key, and
|
|
-- p_provider_order_id was ignored for revocations. A confirmed cancellation
|
|
-- webhook for last month's order (a console refund of a duplicate or courtesy
|
|
-- charge) therefore reset the whole subscription to free, dropping the period
|
|
-- paid by the current order and stopping renewals.
|
|
--
|
|
-- The payple-webhook edge function now ignores such cancellations itself
|
|
-- (webhook-policy.ts, reason 'canceled_order_not_current'). This migration
|
|
-- closes the remaining window where a renewal changes payple_pay_oid between
|
|
-- the edge function's read and this function's per-user advisory lock.
|
|
--
|
|
-- Only the new order-scope guard is added; the body is otherwise identical to
|
|
-- 20260821000003_payment_provider_serialization.sql. CREATE OR REPLACE keeps
|
|
-- the existing owner and grants (service_role only).
|
|
-- ============================================================================
|
|
|
|
BEGIN;
|
|
|
|
-- Apply one authoritative provider event. Provider ownership is strict: an
|
|
-- event can never overwrite another provider. A cancellation can affect only
|
|
-- the exact provider resource currently owning the entitlement.
|
|
CREATE OR REPLACE FUNCTION public.apply_payment_provider_event(
|
|
p_user_id uuid,
|
|
p_provider text,
|
|
p_event_id text,
|
|
p_event_created_at timestamptz,
|
|
p_event_type text,
|
|
p_payload_digest text,
|
|
p_provider_resource_id text,
|
|
p_tier text,
|
|
p_status text,
|
|
p_entitled boolean,
|
|
p_current_period_start timestamptz DEFAULT NULL,
|
|
p_current_period_end timestamptz DEFAULT NULL,
|
|
p_cancel_at timestamptz DEFAULT NULL,
|
|
p_auto_renewing boolean DEFAULT NULL,
|
|
p_provider_customer_id text DEFAULT NULL,
|
|
p_provider_order_id text DEFAULT NULL,
|
|
p_store_product_id text DEFAULT NULL,
|
|
p_store_purchase_id uuid DEFAULT NULL,
|
|
p_operation_id uuid DEFAULT NULL
|
|
) RETURNS jsonb
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public, pg_temp
|
|
AS $$
|
|
DECLARE
|
|
v_event public.payment_provider_events%ROWTYPE;
|
|
v_cursor public.payment_provider_cursors%ROWTYPE;
|
|
v_subscription public.subscriptions%ROWTYPE;
|
|
v_result jsonb;
|
|
BEGIN
|
|
IF p_user_id IS NULL OR NOT EXISTS (SELECT 1 FROM auth.users WHERE id = p_user_id) THEN
|
|
RAISE EXCEPTION 'unknown_user';
|
|
END IF;
|
|
IF p_provider NOT IN ('stripe', 'payple', 'google_play', 'app_store', 'admin') THEN
|
|
RAISE EXCEPTION 'invalid_provider';
|
|
END IF;
|
|
IF p_event_id IS NULL OR length(trim(p_event_id)) NOT BETWEEN 3 AND 255 THEN
|
|
RAISE EXCEPTION 'invalid_event_id';
|
|
END IF;
|
|
IF p_event_created_at IS NULL OR p_event_created_at > now() + interval '10 minutes' THEN
|
|
RAISE EXCEPTION 'invalid_event_created_at';
|
|
END IF;
|
|
IF p_event_type IS NULL OR length(trim(p_event_type)) NOT BETWEEN 1 AND 100 THEN
|
|
RAISE EXCEPTION 'invalid_event_type';
|
|
END IF;
|
|
IF p_payload_digest IS NULL OR p_payload_digest !~ '^[0-9a-f]{64}$' THEN
|
|
RAISE EXCEPTION 'invalid_payload_digest';
|
|
END IF;
|
|
IF p_provider_resource_id IS NULL
|
|
OR length(trim(p_provider_resource_id)) NOT BETWEEN 1 AND 255 THEN
|
|
RAISE EXCEPTION 'invalid_provider_resource_id';
|
|
END IF;
|
|
IF p_tier NOT IN ('free', 'pro', 'pro_plus') THEN
|
|
RAISE EXCEPTION 'invalid_tier';
|
|
END IF;
|
|
IF p_entitled AND p_tier = 'free' THEN
|
|
RAISE EXCEPTION 'entitled_tier_must_be_paid';
|
|
END IF;
|
|
IF p_status IS NULL OR p_status NOT IN (
|
|
'active', 'trialing', 'past_due', 'canceled', 'unpaid', 'incomplete',
|
|
'incomplete_expired', 'paused', 'on_hold', 'expired', 'refunded', 'pending'
|
|
) THEN
|
|
RAISE EXCEPTION 'invalid_status';
|
|
END IF;
|
|
IF p_current_period_start IS NOT NULL
|
|
AND p_current_period_end IS NOT NULL
|
|
AND p_current_period_end < p_current_period_start THEN
|
|
RAISE EXCEPTION 'invalid_subscription_period';
|
|
END IF;
|
|
IF p_provider_customer_id IS NOT NULL AND length(p_provider_customer_id) > 255 THEN
|
|
RAISE EXCEPTION 'invalid_provider_customer_id';
|
|
END IF;
|
|
IF p_provider_order_id IS NOT NULL AND length(p_provider_order_id) > 255 THEN
|
|
RAISE EXCEPTION 'invalid_provider_order_id';
|
|
END IF;
|
|
|
|
PERFORM pg_advisory_xact_lock(hashtextextended(p_user_id::text, 73031));
|
|
|
|
INSERT INTO public.payment_provider_events (
|
|
provider,
|
|
event_id,
|
|
user_id,
|
|
provider_resource_id,
|
|
event_type,
|
|
event_created_at,
|
|
payload_digest
|
|
) VALUES (
|
|
p_provider,
|
|
trim(p_event_id),
|
|
p_user_id,
|
|
trim(p_provider_resource_id),
|
|
trim(p_event_type),
|
|
p_event_created_at,
|
|
p_payload_digest
|
|
)
|
|
ON CONFLICT (provider, event_id) DO NOTHING
|
|
RETURNING * INTO v_event;
|
|
|
|
IF v_event.id IS NULL THEN
|
|
SELECT *
|
|
INTO v_event
|
|
FROM public.payment_provider_events
|
|
WHERE provider = p_provider
|
|
AND event_id = trim(p_event_id)
|
|
FOR UPDATE;
|
|
IF v_event.user_id <> p_user_id
|
|
OR v_event.provider_resource_id <> trim(p_provider_resource_id)
|
|
OR v_event.event_type <> trim(p_event_type)
|
|
OR v_event.payload_digest <> p_payload_digest THEN
|
|
RAISE EXCEPTION 'provider_event_payload_mismatch';
|
|
END IF;
|
|
RETURN coalesce(
|
|
v_event.result,
|
|
jsonb_build_object(
|
|
'applied', false,
|
|
'duplicate', true,
|
|
'reason', 'event_processing_in_progress'
|
|
)
|
|
) || jsonb_build_object('duplicate', true);
|
|
END IF;
|
|
|
|
SELECT *
|
|
INTO v_cursor
|
|
FROM public.payment_provider_cursors
|
|
WHERE user_id = p_user_id
|
|
AND provider = p_provider
|
|
FOR UPDATE;
|
|
|
|
IF v_cursor.user_id IS NOT NULL AND (
|
|
v_cursor.last_event_created_at > p_event_created_at
|
|
OR (
|
|
v_cursor.last_event_created_at = p_event_created_at
|
|
AND v_cursor.last_event_id >= trim(p_event_id)
|
|
)
|
|
) THEN
|
|
v_result := jsonb_build_object(
|
|
'applied', false,
|
|
'duplicate', false,
|
|
'reason', 'stale_provider_event'
|
|
);
|
|
UPDATE public.payment_provider_events
|
|
SET disposition = 'ignored', result = v_result, processed_at = now()
|
|
WHERE id = v_event.id;
|
|
RETURN v_result;
|
|
END IF;
|
|
|
|
-- A Payple revocation is order-scoped. Every order charged on one billing
|
|
-- key shares provider_resource_id (the payer id), so the resource-ownership
|
|
-- check below cannot tell last month's order from the current one. Only a
|
|
-- cancellation of the order that funds the current period
|
|
-- (subscriptions.payple_pay_oid) may revoke it. This runs before the cursor
|
|
-- advances so an ignored refund of an older order cannot make a later
|
|
-- legitimate event look stale. Callers that revoke without naming an order
|
|
-- (payple-renew scheduled expiry, payple-manage) are unaffected.
|
|
IF NOT p_entitled
|
|
AND p_provider = 'payple'
|
|
AND nullif(trim(p_provider_order_id), '') IS NOT NULL
|
|
AND EXISTS (
|
|
SELECT 1
|
|
FROM public.subscriptions
|
|
WHERE user_id = p_user_id
|
|
AND provider = 'payple'
|
|
AND payple_pay_oid IS DISTINCT FROM trim(p_provider_order_id)
|
|
) THEN
|
|
v_result := jsonb_build_object(
|
|
'applied', false,
|
|
'duplicate', false,
|
|
'reason', 'canceled_order_not_current'
|
|
);
|
|
UPDATE public.payment_provider_events
|
|
SET disposition = 'ignored', result = v_result, processed_at = now()
|
|
WHERE id = v_event.id;
|
|
RETURN v_result;
|
|
END IF;
|
|
|
|
INSERT INTO public.payment_provider_cursors (
|
|
user_id, provider, last_event_created_at, last_event_id
|
|
) VALUES (
|
|
p_user_id, p_provider, p_event_created_at, trim(p_event_id)
|
|
)
|
|
ON CONFLICT (user_id, provider) DO UPDATE
|
|
SET last_event_created_at = EXCLUDED.last_event_created_at,
|
|
last_event_id = EXCLUDED.last_event_id,
|
|
updated_at = now();
|
|
|
|
SELECT *
|
|
INTO v_subscription
|
|
FROM public.subscriptions
|
|
WHERE user_id = p_user_id
|
|
FOR UPDATE;
|
|
|
|
IF v_subscription.id IS NULL THEN
|
|
INSERT INTO public.subscriptions (user_id, tier, status, provider, payment_provider)
|
|
VALUES (p_user_id, 'free', 'active', 'none', 'none')
|
|
RETURNING * INTO v_subscription;
|
|
END IF;
|
|
|
|
IF p_operation_id IS NOT NULL AND NOT EXISTS (
|
|
SELECT 1
|
|
FROM public.payment_provider_operations
|
|
WHERE id = p_operation_id
|
|
AND user_id = p_user_id
|
|
AND provider = p_provider
|
|
) THEN
|
|
RAISE EXCEPTION 'invalid_payment_operation';
|
|
END IF;
|
|
|
|
IF p_entitled AND EXISTS (
|
|
SELECT 1
|
|
FROM public.payment_provider_operations
|
|
WHERE user_id = p_user_id
|
|
AND provider <> p_provider
|
|
AND state IN ('reserved', 'external_created', 'charged')
|
|
AND expires_at > now()
|
|
) THEN
|
|
v_result := jsonb_build_object(
|
|
'applied', false,
|
|
'duplicate', false,
|
|
'reason', 'other_provider_operation_in_progress'
|
|
);
|
|
UPDATE public.payment_provider_events
|
|
SET disposition = 'rejected', result = v_result, processed_at = now()
|
|
WHERE id = v_event.id;
|
|
RETURN v_result;
|
|
END IF;
|
|
|
|
IF p_entitled AND v_subscription.provider NOT IN ('none', p_provider) THEN
|
|
v_result := jsonb_build_object(
|
|
'applied', false,
|
|
'duplicate', false,
|
|
'reason', 'active_subscription_other_provider',
|
|
'owner_provider', v_subscription.provider
|
|
);
|
|
UPDATE public.payment_provider_events
|
|
SET disposition = 'rejected', result = v_result, processed_at = now()
|
|
WHERE id = v_event.id;
|
|
RETURN v_result;
|
|
END IF;
|
|
|
|
IF NOT p_entitled AND v_subscription.provider <> p_provider THEN
|
|
v_result := jsonb_build_object(
|
|
'applied', false,
|
|
'duplicate', false,
|
|
'reason', 'provider_not_owner',
|
|
'owner_provider', v_subscription.provider
|
|
);
|
|
UPDATE public.payment_provider_events
|
|
SET disposition = 'ignored', result = v_result, processed_at = now()
|
|
WHERE id = v_event.id;
|
|
RETURN v_result;
|
|
END IF;
|
|
|
|
IF NOT p_entitled
|
|
AND v_subscription.provider_resource_id IS DISTINCT FROM trim(p_provider_resource_id) THEN
|
|
v_result := jsonb_build_object(
|
|
'applied', false,
|
|
'duplicate', false,
|
|
'reason', 'provider_resource_not_owner'
|
|
);
|
|
UPDATE public.payment_provider_events
|
|
SET disposition = 'ignored', result = v_result, processed_at = now()
|
|
WHERE id = v_event.id;
|
|
RETURN v_result;
|
|
END IF;
|
|
|
|
IF p_entitled THEN
|
|
UPDATE public.subscriptions
|
|
SET tier = p_tier,
|
|
status = p_status,
|
|
current_period_start = p_current_period_start,
|
|
current_period_end = p_current_period_end,
|
|
cancel_at = p_cancel_at,
|
|
provider = p_provider,
|
|
provider_resource_id = trim(p_provider_resource_id),
|
|
provider_event_id = trim(p_event_id),
|
|
provider_event_created_at = p_event_created_at,
|
|
auto_renewing = p_auto_renewing,
|
|
stripe_customer_id = CASE
|
|
WHEN p_provider = 'stripe' THEN coalesce(nullif(trim(p_provider_customer_id), ''), stripe_customer_id)
|
|
ELSE stripe_customer_id
|
|
END,
|
|
stripe_subscription_id = CASE
|
|
WHEN p_provider = 'stripe' THEN trim(p_provider_resource_id)
|
|
ELSE stripe_subscription_id
|
|
END,
|
|
payple_payer_id = CASE
|
|
WHEN p_provider = 'payple' AND p_provider_customer_id = '' THEN NULL
|
|
WHEN p_provider = 'payple' AND p_provider_customer_id IS NOT NULL
|
|
THEN trim(p_provider_customer_id)
|
|
ELSE payple_payer_id
|
|
END,
|
|
payple_pay_oid = CASE
|
|
WHEN p_provider = 'payple' THEN coalesce(nullif(trim(p_provider_order_id), ''), payple_pay_oid)
|
|
ELSE payple_pay_oid
|
|
END,
|
|
store_product_id = CASE
|
|
WHEN p_provider IN ('google_play', 'app_store') THEN p_store_product_id
|
|
ELSE NULL
|
|
END,
|
|
store_purchase_id = CASE
|
|
WHEN p_provider IN ('google_play', 'app_store') THEN p_store_purchase_id
|
|
ELSE NULL
|
|
END,
|
|
renewal_failures = CASE WHEN p_provider = 'payple' THEN 0 ELSE renewal_failures END,
|
|
updated_at = now()
|
|
WHERE user_id = p_user_id;
|
|
ELSE
|
|
UPDATE public.subscriptions
|
|
SET tier = 'free',
|
|
status = p_status,
|
|
current_period_start = coalesce(p_current_period_start, current_period_start),
|
|
current_period_end = coalesce(p_current_period_end, current_period_end),
|
|
cancel_at = coalesce(p_cancel_at, p_current_period_end, now()),
|
|
provider = 'none',
|
|
provider_resource_id = NULL,
|
|
provider_event_id = trim(p_event_id),
|
|
provider_event_created_at = p_event_created_at,
|
|
auto_renewing = false,
|
|
store_product_id = NULL,
|
|
store_purchase_id = NULL,
|
|
updated_at = now()
|
|
WHERE user_id = p_user_id;
|
|
END IF;
|
|
|
|
IF p_operation_id IS NOT NULL THEN
|
|
UPDATE public.payment_provider_operations
|
|
SET state = 'applied',
|
|
external_reference = coalesce(
|
|
nullif(trim(p_provider_order_id), ''),
|
|
nullif(trim(p_provider_resource_id), ''),
|
|
external_reference
|
|
),
|
|
error_code = NULL,
|
|
updated_at = now()
|
|
WHERE id = p_operation_id;
|
|
END IF;
|
|
|
|
v_result := jsonb_build_object(
|
|
'applied', true,
|
|
'duplicate', false,
|
|
'provider', CASE WHEN p_entitled THEN p_provider ELSE 'none' END,
|
|
'tier', CASE WHEN p_entitled THEN p_tier ELSE 'free' END,
|
|
'status', p_status,
|
|
'entitled', p_entitled
|
|
);
|
|
UPDATE public.payment_provider_events
|
|
SET disposition = 'applied', result = v_result, processed_at = now()
|
|
WHERE id = v_event.id;
|
|
RETURN v_result;
|
|
END;
|
|
$$;
|
|
|
|
COMMIT;
|