-- ============================================================================ -- Mobile monetization hardening -- Atomically revoke Google Play linked purchase tokens before applying a new -- verified purchase. This prevents upgrade/downgrade and resubscribe chains -- from retaining two entitlements. -- ============================================================================ BEGIN; CREATE OR REPLACE FUNCTION public.apply_verified_google_play_purchase( p_user_id uuid, p_platform text, p_product_id text, p_store_transaction_id text, p_token_hash text, p_linked_token_hash text, p_purchase_token text, p_purchase_state text, p_purchase_at timestamptz, p_expires_at timestamptz, p_auto_renewing boolean, p_acknowledged boolean, p_tier text, p_entitled boolean, p_verification jsonb ) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path = public, pg_temp AS $$ DECLARE v_linked_purchase_id uuid; v_linked_user_id uuid; v_linked_revoked boolean := false; v_result jsonb; BEGIN IF p_platform <> 'google_play' THEN RAISE EXCEPTION 'invalid_platform'; END IF; IF p_linked_token_hash IS NOT NULL THEN IF p_linked_token_hash !~ '^[0-9a-f]{64}$' OR p_linked_token_hash = p_token_hash THEN RAISE EXCEPTION 'invalid_linked_purchase_token'; END IF; SELECT id, user_id INTO v_linked_purchase_id, v_linked_user_id FROM public.iap_purchases WHERE platform = 'google_play' AND token_hash = p_linked_token_hash FOR UPDATE; IF v_linked_user_id IS NOT NULL AND v_linked_user_id <> p_user_id THEN RAISE EXCEPTION 'linked_purchase_owned_by_other_user'; END IF; IF v_linked_purchase_id IS NOT NULL THEN UPDATE public.iap_purchases SET purchase_state = 'expired', expires_at = least(coalesce(expires_at, now()), now()), auto_renewing = false, verified_at = now(), updated_at = now() WHERE id = v_linked_purchase_id; UPDATE public.subscriptions SET tier = 'free', status = 'expired', current_period_end = now(), cancel_at = now(), provider = 'none', payment_provider = 'none', store_product_id = NULL, store_purchase_id = NULL, auto_renewing = false, updated_at = now() WHERE user_id = p_user_id AND store_purchase_id = v_linked_purchase_id; IF FOUND THEN UPDATE public.profiles SET tier = 'free', updated_at = now() WHERE id = p_user_id; END IF; v_linked_revoked := true; END IF; END IF; v_result := public.apply_verified_store_purchase( p_user_id, p_platform, p_product_id, p_store_transaction_id, p_token_hash, p_purchase_token, p_purchase_state, p_purchase_at, p_expires_at, p_auto_renewing, p_acknowledged, p_tier, p_entitled, p_verification ); RETURN v_result || jsonb_build_object('linked_purchase_revoked', v_linked_revoked); END; $$; REVOKE ALL ON FUNCTION public.apply_verified_google_play_purchase( uuid, text, text, text, text, text, text, text, timestamptz, timestamptz, boolean, boolean, text, boolean, jsonb ) FROM PUBLIC, anon, authenticated; GRANT EXECUTE ON FUNCTION public.apply_verified_google_play_purchase( uuid, text, text, text, text, text, text, text, timestamptz, timestamptz, boolean, boolean, text, boolean, jsonb ) TO service_role; COMMIT;