Patreon does not appear to be syncing on our instance

We’re on a hosted instance on discourse.group. We have the Patreon plug-in configured to update a group called Patrons, and anyone of any tier should be included in that group. Right now, despite over 1000 active patrons on Patreon, this group has only ~70 members, which seems like it’s far too small, especially considering that except for a few people given direct invites we’ve only supported people signing in with Patreon.

It has been two days since the sync has run according to the plug-ins panel in admin, contrary to documentation that suggests it should run every 6 hours, and that last run coincided with several people being dropped from this group, and a failure in the log (that appears to be a response from CloudFlare and not api.patreon.com itself.) We see similar failures at roughly two week intervals.

Clicking the “Update Patreon Data & Sync Groups” button in the plugin’s admin panel appears to do nothing (we have verbose logging enabled for the plug-in, and nothing is logged.)

I do not personally have admin access to the Patreon account we’re tied to, but if I use the creator token set up for the plugin against the API endpoints (with curl) it does work and I am able to get valid responses, page through pledges, etc. There was an earlier thread that suggested that the request limits for that instance needed to be increased; I did that on our instance but it appears to have had no effect.

I’ve looked at a few relevant threads:

But from what I can see it just looks like synchronization isn’t happening either manually or on a schedule, or at least not on the 4x daily schedule the documentation implies, and as we’re not self-hosting I think I’m at the limit of what I can inspect myself.

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?