wp_transition_post_status does not change the status of the post

This function contains do_action() calls for post status transition
action hooks. The order of the words in the function name might be
confusing – it does not change the status of posts, it only calls
actions that can be hooked into by plugin developers.

https://codex.wordpress.org/Function_Reference/wp_transition_post_status

What you want is wp_update_post, you’re also using WP_Query incorrectly:

“`
$notpayed_posts = new WP_Query( array(
‘meta_key’ => ‘_payment’,
‘meta_value’ => ‘0’
) );

while ($notpayed_posts->have_posts()) {
    $notpayed_posts->the_post();
    wp_update_post( array(
        'ID' => get_the_ID(),
        'post_status' => 'draft'
    ));
}
wp_reset_postdata();

Also, keep in mind that your query is an expensive/very slow post meta query, post meta isn’t built for searching or queries where you’re looking for posts. Consider a taxonomy term.

You also do nothing to change the meta afterwards, so if there are 10 posts that match, then every page load will try to update 10 posts, even if they’ve already been updated! Either change the meta value with update_post_meta or check the post status in the query, e.g.

$notpayed_posts = new WP_Query( array(
    'post_status'               =>  'publish',
    'meta_key'                  =>  '_payment',
    'meta_value'                =>  '0'
) );

Additionally, use a cron job, don’t do it on every page load by using the init hook, it’s very bad for performance and incompatible with page caching