Matthew Barraud Posted July 9, 2023 Share Posted July 9, 2023 (edited) Hi All New here, had a quick search and I believe that my issue is related to encoding but really not sure, feels like I've tried so much now. I have an app created and the beginnings of a Sage Class. When calling authorizeSageAccountingApp() from a PHP page, it redirects to authorise my app, I am redirected to a page with handleCallback() and then receiving the following: '{"$severity":"error","$dataCode":"DataParsingError","$message":"The data you sent could not be processed.","$source":"Proxy"}' I am clearly doing something wrong but I may not have a good understanding of what's happening here. Any pointers are greatly received. Eventually, I would like to code the ability to create invoices in our Sage Accounting account rather than using Stripe. <?php class Sage { public function __construct() { $this->clientId = 'XXXX'; $this->clientSecret = 'XXXX'; } function authorizeSageAccountingApp() { $authorizationUrl = 'https://www.sageone.com/oauth2/auth/central?filter=apiv3.1&country=gb&locale=en_GB'; $clientId = $this->clientId; $redirectUri = 'http://localhost/sailadventure-new/tests/callback'; $scopes = 'full_access'; // or 'readonly' if applicable $authorizationUrl .= '&response_type=code'; $authorizationUrl .= '&client_id=' . urlencode($clientId); $authorizationUrl .= '&redirect_uri=' . urlencode($redirectUri); $authorizationUrl .= '&scope=' . urlencode($scopes); header('Location: ' . $authorizationUrl); exit(); } function handleCallback() { if (isset($_GET['code'])) { $authorizationCode = $_GET['code']; $clientId = $this->clientId; $clientSecret = $this->clientSecret; $redirectUri = 'http://localhost/sailadventure-new/tests/continue'; $tokenUrl = 'https://oauth.accounting.sage.com/token'; $header = array('Accept: application/json','Content-Type: application/x-www-form-urlencoded'); $content = "client_id=$clientId&client_secret=$clientSecret&grant_type=authorization_code"; $curl = curl_init(); curl_setopt_array($curl, array( CURLOPT_URL => $tokenUrl, CURLOPT_HTTPHEADER => $header, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $content )); $response = curl_exec($curl); curl_close($curl); echo '<pre>' . var_export($response, true) . '</pre>'; } else { echo "Authorization code not found."; } } } Edited July 9, 2023 by Matthew Barraud Link to comment Share on other sites More sharing options...
Matthew Barraud Posted July 9, 2023 Author Share Posted July 9, 2023 (edited) I just tidied the code a little, and I am still getting the same response. <?php class Sage { private $clientId; private $clientSecret; private $accessToken; public function __construct() { $this->clientId = '37d7XXXXXXXXXXXXXe064ec9'; $this->clientSecret = 'eieXXXXXXXXXXXIz'; } function authorizeSageAccountingApp() { $authorizationUrl = 'https://www.sageone.com/oauth2/auth/central?filter=apiv3.1&country=gb&locale=en_GB'; $clientId = $this->clientId; $redirectUri = 'http://localhost/sailadventure-new/tests/callback'; $scopes = 'full_access'; // or 'readonly' if applicable $authorizationUrl .= '&response_type=code'; $authorizationUrl .= '&client_id=' . urlencode($clientId); $authorizationUrl .= '&redirect_uri=' . urlencode($redirectUri); $authorizationUrl .= '&scope=' . urlencode($scopes); header('Location: ' . $authorizationUrl); exit(); } function handleCallback() { if (isset($_GET['code'])) { // Initialize cURL $curl = curl_init(); // Create the URL $url = 'https://oauth.accounting.sage.com/token'; $clientSecret = $this->clientSecret; $authCode = urlencode($_GET['code']); $data = array( 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $authCode, 'grant_type' => 'authorization_code', 'redirect_uri' => 'http://localhost/sailadventure-new/tests/continue' ); $jsonData = json_encode($data); curl_setopt($curl, CURLOPT_POSTFIELDS, $jsonData); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json' ]); // Execute the request $response = curl_exec($curl); if (curl_errno($curl)) { $error = curl_error($curl); return $error; } curl_close($curl); echo '<pre>' . var_export($response, true) . '</pre>'; } } } Edited July 9, 2023 by Matthew Barraud Link to comment Share on other sites More sharing options...
Steel, Mark Posted July 10, 2023 Share Posted July 10, 2023 Hi Matthew, welcome to the forum. Could you clarify you have the following: You've read the Authentication Guide. Your application has been registered in the developer self service portal and the correct callback url's have been added. **Note** - callback url's are case sensitive. A Sage Business Cloud Accounting instance has been created to test the oAuth flow https://developer.sage.com/accounting/quick-start/set-up-the-basics/. You're able to sign into the Sage Business Cloud Accounting instance and view the welcome dashboard. The parameters passed in the auth url match those registered in the developer portal and the country filter(optional) passed matches the country the business is registered for. For example if GB is passed and the business is registered for the US the auth will fail. If you have the above it may be worthwhile completing the Auth flow in POSTMAN before moving on to you app. Looking at your code I can't see how you're constructing the auth url with the required params. It should look similar to the below. request_uri = auth_url + "&response_type=" + response_type + "&client_id=" + client_id + "&redirect_uri=" + redirect_uri + "&scope=" + scope + "&state=" + state Also, your code for exchanging the code once it's returned doesn't look to be using the correct header params def exchange_code(code: str): headers = { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json", } data = { "client_id": config.client_id, "client_secret": config.client_secret, "code": code, "grant_type": "authorization_code", "redirect_uri": config.redirect_uri, } response = requests.post(config.token_url, headers=headers, data=data) Thanks Mark Link to comment Share on other sites More sharing options...
Matthew Barraud Posted July 10, 2023 Author Share Posted July 10, 2023 Hi Mark Thanks for replying 🙂 In response ... You've read the Authentication Guide. Yep, loads, he he Your application has been registered in the developer self service portal and the correct callback url's have been added. **Note** - callback url's are case sensitive.I believe so., see pic below. A Sage Business Cloud Accounting instance has been created to test the oAuth flow https://developer.sage.com/accounting/quick-start/set-up-the-basics/.This one, I'm not sure, I already have a Sage Accounting account for my business. Do I need to setup a trial account as well? You're able to sign into the Sage Business Cloud Accounting instance and view the welcome dashboard.Yep, on my business account, see above ref, developer. The parameters passed in the auth url match those registered in the developer portal and the country filter(optional) passed matches the country the business is registered for. For example if GB is passed and the business is registered for the US the auth will fail.Understood all GB used. ----- When I visit http://localhost/sailadventure-new/tests/ it opens a new instance of my class and runs the authorizeSageAccountingApp() function. This redirects the page to https://www.sageone.com/oauth2/auth/central?filter=apiv3.1&country=gb&locale=en_GB&response_type=code&client_id=XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX&redirect_uri=http%3A%2F%2Flocalhost%2Fsailadventure-new%2Ftests%2Fcallback&scope=full_access&state=159786325 it then prompts me with the following clicking allow then returns '{"$severity":"error","$dataCode":"DataParsingError","$message":"The data you sent could not be processed.","$source":"Proxy"}' I've been updating the code and working through issues as I learn/understand them. This is the class currently in PHP <?php class Sage { private $clientId; private $clientSecret; private $accessToken; public function __construct() { $this->clientId = 'XXXX'; $this->clientSecret = 'XXXX'; } function authorizeSageAccountingApp() { $authorizationUrl = 'https://www.sageone.com/oauth2/auth/central?filter=apiv3.1&country=gb&locale=en_GB'; $clientId = $this->clientId; $redirectUri = 'http://localhost/sailadventure-new/tests/callback'; $scopes = 'full_access'; // or 'readonly' if applicable $authorizationUrl .= '&response_type=code'; $authorizationUrl .= '&client_id=' . urlencode($clientId); $authorizationUrl .= '&redirect_uri=' . urlencode($redirectUri); $authorizationUrl .= '&scope=' . urlencode($scopes); $authorizationUrl .= '&state=159786325'; header('Location: ' . $authorizationUrl); exit(); } function handleCallback() { if (isset($_GET['code'])) { // Initialize cURL $curl = curl_init(); // Create the URL $url = 'https://oauth.accounting.sage.com/token'; $clientSecret = $this->clientSecret; $authCode = $_GET['code']; $data = array( 'grant_type' => 'authorization_code', 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $authCode, 'redirect_uri' => 'http://localhost/sailadventure-new/tests/callback' ); http_build_query($data); curl_setopt($curl, CURLOPT_POSTFIELDS, $data); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HTTPHEADER, [ 'Content-Type: application/x-www-form-urlencoded', 'Accept: application/json', ]); // Execute the request $response = curl_exec($curl); if (curl_errno($curl)) { $error = curl_error($curl); return $error; } curl_close($curl); echo '<pre>' . var_export($response, true) . '</pre>'; } } } Link to comment Share on other sites More sharing options...
Matthew Barraud Posted July 10, 2023 Author Share Posted July 10, 2023 Sorry, forgot to add I downloaded Postman and created a flow through there and it gave me a token, so obviously something is wrong in my code somewhere. Link to comment Share on other sites More sharing options...
Matthew Barraud Posted July 10, 2023 Author Share Posted July 10, 2023 (edited) I think I have it! This was an encoding / decoding issue in the URL. With the edition of the following to the $authCode variable I was able to get a token returned. function handleCallback() { if (isset($_GET['code'])) { // Initialize cURL $curl = curl_init(); // Create the URL $url = 'https://oauth.accounting.sage.com/token'; $clientSecret = $this->clientSecret; $authCode = $_GET['code']; $authCode = preg_replace("/%u([0-9a-f]{3,4})/i","&#x\\1;",urldecode($authCode)); $authCode = html_entity_decode($authCode,null,'UTF-8'); $data = array( 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'code' => $authCode, 'grant_type' => 'authorization_code', 'redirect_uri' => 'http://localhost/sailadventure-new/tests/callback' ); curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HTTPHEADER, [ 'Content-Type: application/x-www-form-urlencoded', 'Accept: application/json', ]); // Execute the request $response = curl_exec($curl); if (curl_errno($curl)) { $error = curl_error($curl); return $error; } curl_close($curl); echo '<pre>' . var_export($response, true) . '</pre>'; } } Edited July 10, 2023 by Matthew Barraud Link to comment Share on other sites More sharing options...
Steel, Mark Posted July 11, 2023 Share Posted July 11, 2023 Thanks for the update Matthew, I'm pleased you've been able to find a solution to your issue. Thanks Mark Link to comment Share on other sites More sharing options...
Recommended Posts
Please sign in to comment
You will be able to leave a comment after signing in
Sign In Now