Patreon does not appear to be syncing on our instance

Hi,

I think I may have found the cause of this issue, or at least a closely related Patreon API v2 sync issue.

I have a Discourse instance using the Patreon integration with API v2. Existing Patreon users continue to be assigned correctly to the patrons group, but a new paying member was not added to the group, even after manually running Update Patreon Data. The sync completed successfully and the email address in Patreon exactly matched the user’s Discourse email.

With patreon_verbose_log enabled, the sync reports:

Patreon sync: found 4 rewards/tiers across 1 campaigns
Patreon sync complete: 1968 pledges, 0 users synced

I then inspected the API response used by ApiVersion::V2.

For the first page of 1,000 members:

Members in data: 1000
Included total: 1004
Included users: 1000
Keys for first included user: ["full_name"]
Included users with email: 0

However, the member objects themselves contain the email:

Members: 1000
Keys for first member:
["currently_entitled_amount_cents", "email", "full_name",
 "last_charge_date", "last_charge_status", "patron_status"]

Members with email: 928

Looking at plugins/discourse-patreon/lib/api_version/v2.rb, MEMBER_FIELDS explicitly requests email, but V2.extract only populates the users hash from the related user objects in included:

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

It does not appear to use entry["attributes"]["email"] from the member objects, even though that is where Patreon is returning the email in my API response.

This seems to explain why the sync successfully retrieves all 1,968 pledges but reports 0 users synced.

It also explains why existing Patreon/Discourse associations can continue working while new members cannot be matched by email: existing users may already have their patreon_id stored in user_custom_fields, whereas a new member needs the Patreon ID ↔ email association to be established.

Would it make sense for V2.extract to populate users directly while processing each member, for example:

patron_id = entry["relationships"]["user"]["data"]["id"]
attrs = entry["attributes"]

if attrs["email"].present?
  users[patron_id] = attrs["email"].downcase
end

while retaining the current included processing as a fallback?