Patreon-php issues

I’ve been trying to integrate my website with my patreon page using patreon-php.
I’ve noticed I am not able to get any access tokens for users when they go through the login with patreon flow.
Getting refresh tokens work though, but it only works with the creator’s token and I want to fetch user campaign details instead.
I’ve scoured everywhere and I am not able to get any insight on why I keep getting the below error when I perform a get_tokens function call.

I’m using the API and OAuth from here.
The error I encounter is as below, this is from a var_dump of my $tokens variable in PHP.
patreon-API-error

Any help is appreciated.

An update from my own debugging which I’m performing right now.
I am getting access and refresh tokens from the get_tokens function call now, but I’m not sure why the return is considered an error as my var_dump will only display if the response is of ‘errors’.

Ok, now my problem is not with get_tokens function but with the API call to fetch_user().
I am getting 401: Unauthorized unless the user is a creator on Patreon.

This is kind of endless… the reason the API call failed is because even though I am getting the tokens, for some reason the API call is getting a 401 Unauthorized response.

Anybody familiar can confirm if the OAuth API endpoint is as below and the suffix(get_data function call) is correct?
These are hardcoded in patreon-php and I need to know if they are correct in order to continue.

// Set API endpoint to use. Its currently V2
	$this->api_endpoint = "https://www.patreon.com/api/oauth2/v2/";
	
	// Set default return format - this can be changed by the app using the lib by setting it 
	// after initialization of this class
	$this->api_return_format = 'array';
	
}

public function fetch_user() {
	// Fetches details of the current token user. 
	return $this->get_data('identity?include=memberships&fields'.urlencode('[user]').'=email,first_name,full_name,image_url,last_name,thumb_url,url,vanity,is_email_verified&fields'.urlencode('[member]').'=currently_entitled_amount_cents,lifetime_support_cents,last_charge_status,patron_status,last_charge_date,pledge_relationship_start');

I’m giving up, tweaking the code here and there doesn’t give me any more insight on what can be the problem. I only know for sure that the API call for fetch_user() will only work for creator. Regular users will encounter 401:Unauthorized. If anybody knows better do bump this thread.

Im going to look into this and update you with the result.

Are you using a v2 client for your app? And which PHP version?

Using a v2 client, i am not able to reproduce this issue in a test installation. I use a totally new patron account to pass through the login flow, after which i am logged in and my details are properly fetched from Patreon.

Can you post the exact example code which you are using for the login flow and to get user details?

@codebard

I am using a v2 client, PHP v7.1

My code as below.

        $oauth_client = new Patreon\OAuth($client_id, $client_secret);	
    	$tokens = $oauth_client->get_tokens($_POST['code'], $redirect_uri);
    	$access_token = $tokens['access_token'];
    	$refresh_token = $tokens['refresh_token'];
    	$api_client = new Patreon\API($access_token);
    	$api_client->api_return_format = 'array';
    	$patron_response = $api_client->fetch_user();
    	if ($patron_response['errors'])
    	{
    	    var_dump($api_client);
    	    //var_dump($patron_response);
    	}
    	else
    	    {
    	    $name = $patron_response['data']['attributes']['full_name'];
    	    echo $name;    
    	    }

My code for API to fetch user, there is no change in this from the patreon-php provided code.

	public function fetch_user() {
		// Fetches details of the current token user. 
		return $this->get_data("identity?include=memberships&fields".urlencode("[user]")."=email,first_name,full_name,image_url,last_name,thumb_url,url,vanity,is_email_verified&fields".urlencode("[member]")."=currently_entitled_amount_cents,lifetime_support_cents,last_charge_status,patron_status,last_charge_date,pledge_relationship_start");
	}
	public function get_data( $suffix, $args = array() ) {
				
		// Construct request:
		$api_request = $this->api_endpoint . $suffix;
		
		// This identifies a unique request
		
		$api_request_hash = md5( $this->access_token . $api_request );

		// Check if this request exists in the cache and if so, return it directly - avoids repeated requests to API in the same page run for same request string

		if ( !isset( $args['skip_read_from_cache'] ) ) {
			if ( isset( $this->request_cache[$api_request_hash] ) ) {
				return $this->request_cache[$api_request_hash];		
			}
		}
        
		// Request is new - actually perform the request 

		$ch = $this->__create_ch($api_request);
		$json_string = curl_exec($ch);
		$info = curl_getinfo($ch);
		curl_close($ch);

		// don't try to parse a 500-class error, as it's likely not JSON
		if ( $info['http_code'] >= 500 ) {
		  return $this->add_to_request_cache($api_request_hash, $json_string);
		}
		
		// don't try to parse a 400-class error, as it's likely not JSON
		if ( $info['http_code'] >= 400 ) {
		  return $this->add_to_request_cache($api_request_hash, $json_string);
		}

		// Parse the return according to the format set by api_return_format variable

		if( $this->api_return_format == 'array' ) {
		  $return = json_decode($json_string, true);
		}

		if( $this->api_return_format == 'object' ) {
		  $return = json_decode($json_string);
		}

		if( $this->api_return_format == 'json' ) {
		  $return = $json_string;
		}

		// Add this new request to the request cache and return it
		return $this->add_to_request_cache($api_request_hash, $return);

	}

	private function __create_ch($api_request) {

		// This function creates a cURL handler for a given URL. In our case, this includes entire API request, with endpoint and parameters

		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $api_request);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		
		if ( $this->api_request_method != 'GET' AND $this->curl_postfields ) {
			curl_setopt( $ch, CURLOPT_POSTFIELDS, $this->curl_postfields );
		}
		
		// Set the cURL request method - works for all of them
		
		curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $this->api_request_method );

		// Below line is for dev purposes - remove before release
		// curl_setopt($ch, CURLOPT_HEADER, 1);

		$headers = array(
			'Authorization: Bearer ' . $this->access_token,
			'User-Agent: Patreon-PHP, version 1.0.2, platform ' . php_uname('s') . '-' . php_uname( 'r' ),
		);

		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
		return $ch;

	}

What is the url which you use to send your user to Patreon login or pledge flow before applying that code?

Users are sent from https://foshan369.com/patintegration.html and that is my redirection URI as well.

Sorry, i meant what is the exact url which you are using to send your users to Patreon. There has to be various parameters, including a state variable.

What scope are you requesting initially? I needed to request identity and identity[email].
Depending on what type of information you are requesting you may also need to include campaigns.members

$scope_parameters = '&scope=campaigns.members%20identity%20identity'.urlencode('[email]');

When I follow your link, it looks like your scope is too limited for the information you are trying to collect…
scope=identity.memberships
Try adding the above scopes to your request like so…

$scope_parameters = '&scope=identity.memberships%20campaigns.members%20identity%20identity'.urlencode('[email]');

I also received Unauthroized responses before including additional scopes.

2 Likes

Indeed, what disconnected says can constitute a problem. Thats why i asked what exact urls are you using to send users to Patreon.

1 Like

Thanks to both of you, the additional scopes solved my problem.
It seems my understanding of the scopes is still lacking, grateful for the guidance.
Does it mean &identity and &identitiy.[email] are compulsory to avoid 401 error response?

1 Like

If you try to request something you do not have access to via scopes, yes, that would cause a not authorized. So they are needed.