-- Redemption-code management is an ordinary admin-console capability. -- Keep actor verification, trusted request IDs and server-owned audit markers, -- but do not reuse the narrower billing adjustment permission. create or replace function public.require_admin_redemption_reason() returns trigger language plpgsql set search_path = '' as $$ declare v_reason text := nullif(btrim(current_setting('app.admin_redemption_reason', true)), ''); begin if new.action in ('redemption_code.create','redemption_code.update','redemption_code.revoke') then if v_reason is null or char_length(v_reason) > 500 then raise exception 'admin_reason_required' using errcode='22023'; end if; new.permission_used := 'admin.access'; new.reason := v_reason; end if; return new; end; $$; revoke all on function public.require_admin_redemption_reason() from public, anon, authenticated, service_role; create or replace function public.admin_create_redemption_codes( p_actor_user_id uuid,p_actor_email text,p_actor_role text,p_request_id text,p_codes jsonb,p_reason text ) returns table(id uuid,code_mask text,credits integer,expires_at timestamptz,note text,created_at timestamptz, redeemed_by uuid,redeemed_email text,redeemed_at timestamptz,revoked_by uuid,revoked_at timestamptz) language plpgsql security definer set search_path = '' as $$ begin if not public.admin_has_permission(p_actor_user_id,'admin.access') then raise exception 'admin_permission_denied' using errcode='42501'; end if; if char_length(btrim(coalesce(p_reason,''))) not between 1 and 500 then raise exception 'admin_reason_required' using errcode='22023'; end if; perform set_config('app.admin_redemption_reason',btrim(p_reason),true); return query select * from public.admin_create_redemption_codes( p_actor_user_id,p_actor_email,p_actor_role,p_request_id,p_codes ); end; $$; create or replace function public.admin_update_redemption_code( p_actor_user_id uuid,p_actor_email text,p_actor_role text,p_request_id text,p_code_id uuid, p_set_note boolean,p_note text,p_set_expires_at boolean,p_expires_at timestamptz,p_reason text ) returns table(id uuid,code_mask text,credits integer,expires_at timestamptz,note text,created_at timestamptz, redeemed_by uuid,redeemed_email text,redeemed_at timestamptz,revoked_by uuid,revoked_at timestamptz) language plpgsql security definer set search_path = '' as $$ begin if not public.admin_has_permission(p_actor_user_id,'admin.access') then raise exception 'admin_permission_denied' using errcode='42501'; end if; if char_length(btrim(coalesce(p_reason,''))) not between 1 and 500 then raise exception 'admin_reason_required' using errcode='22023'; end if; perform set_config('app.admin_redemption_reason',btrim(p_reason),true); return query select * from public.admin_update_redemption_code( p_actor_user_id,p_actor_email,p_actor_role,p_request_id,p_code_id, p_set_note,p_note,p_set_expires_at,p_expires_at ); end; $$; create or replace function public.admin_revoke_redemption_code( p_actor_user_id uuid,p_actor_email text,p_actor_role text,p_request_id text,p_code_id uuid,p_reason text ) returns table(id uuid,code_mask text,credits integer,expires_at timestamptz,note text,created_at timestamptz, redeemed_by uuid,redeemed_email text,redeemed_at timestamptz,revoked_by uuid,revoked_at timestamptz) language plpgsql security definer set search_path = '' as $$ begin if not public.admin_has_permission(p_actor_user_id,'admin.access') then raise exception 'admin_permission_denied' using errcode='42501'; end if; if char_length(btrim(coalesce(p_reason,''))) not between 1 and 500 then raise exception 'admin_reason_required' using errcode='22023'; end if; perform set_config('app.admin_redemption_reason',btrim(p_reason),true); return query select * from public.admin_revoke_redemption_code( p_actor_user_id,p_actor_email,p_actor_role,p_request_id,p_code_id ); end; $$; revoke all on function public.admin_create_redemption_codes(uuid,text,text,text,jsonb,text), public.admin_update_redemption_code(uuid,text,text,text,uuid,boolean,text,boolean,timestamptz,text), public.admin_revoke_redemption_code(uuid,text,text,text,uuid,text) from public, anon, authenticated, service_role; do $$ begin if exists(select 1 from pg_roles where rolname='admin_runtime') then grant execute on function public.admin_create_redemption_codes(uuid,text,text,text,jsonb,text), public.admin_update_redemption_code(uuid,text,text,text,uuid,boolean,text,boolean,timestamptz,text), public.admin_revoke_redemption_code(uuid,text,text,text,uuid,text) to admin_runtime; end if; end $$;