La synchro Patreon v2 affiche « N pledges, 0 users synced » — l’adresse e-mail du membre est lue depuis la mauvaise ressource lors de l’extraction

Avertissement : oui, j’ai utilisé Claude pour m’aider à résoudre ce problème :

Exécution du cœur de Discourse sur le commit 5ee8e24 (2026-08-19). La synchronisation Patreon se termine sans erreur, mais ne synchronise aucun utilisateur :

Patreon sync complete: 63 pledges, 0 users synced

Les réponses de l’API sont toutes status=200, donc les requêtes elles-mêmes sont correctes. En traçant le problème dans la console rails, la requête des membres construite par le plugin est correcte et demande bien l’adresse e-mail sur la ressource membre :

fields[member]=full_name,last_charge_date,last_charge_status,currently_entitled_amount_cents,patron_status,email

En appelant directement ce point de terminaison avec un jeton de créateur correctement scoppé (campaigns.members[email] accordé), l’adresse e-mail est présente sur l’objet member, et non sur l’objet user dans included. Une récupération d’un seul membre le confirme :

data.attributes.email        => "patron@example.com"   # présent
included[].attributes.email  => (absent)               # les objets user ne portent que full_name

Ainsi, dans la liste des membres de la campagne, member.attributes.email est renseigné (46 de mes 63 membres ; le reste a un partage restreint ou sont des abonnés gratuits), tandis que les entrées utilisateur included n’ont tout simplement pas de champ e-mail.

Le problème se trouve dans Patreon::ApiVersion::V2.extract (lib/api_version/v2.rb). Il construit la carte users à partir des objets utilisateur included :

ruby

(member_data["included"] || []).each do |entry|
  if entry["type"] == "user" && entry["attributes"]["email"].present?
    users[entry["id"]] = entry["attributes"]["email"].downcase
  end
end

Comme ces objets utilisateur ne portent jamais d’adresse e-mail, la carte ressort vide et chaque membre échoue à la correspondance d’e-mail, d’où 0 synchronisés.

Lire l’adresse e-mail depuis l’entrée membre plutôt que d’ailleurs (clé par l’identifiant utilisateur déjà disponible dans member.relationships.user.data.id, qui est le même patron_id utilisé pour les engagements) a corrigé le problème sur mon installation : 0 est devenu 46 users synced. Le changement que j’ai appliqué :

diff

+            users[patron_id] = attrs["email"].downcase if attrs["email"].present?
             pledges[patron_id] = attrs["currently_entitled_amount_cents"]
             declines[patron_id] = attrs["last_charge_date"] if attrs["last_charge_status"] == "Declined"
           end
-
-          (member_data["included"] || []).each do |entry|
-            if entry["type"] == "user" && entry["attributes"]["email"].present?
-              users[entry["id"]] = entry["attributes"]["email"].downcase
-            end
-          end

Je ne suis pas certain que la suppression complète de la boucle included soit la bonne décision dans tous les cas — si certaines configurations reçoivent l’adresse e-mail sur l’objet utilisateur, vous voudrez peut-être la conserver en tant que solution de repli plutôt que de la remplacer. Je publie le comportement observé et le changement minimal qui a fonctionné ici, au cas où cela serait utile ou indiquerait le bon correctif.