How to Submit Post Requests to Xero API Inside Xero Webhook - php

I have a Webhook on my Xero Account for Invoices. When a new invoice is created I want to access the Xero Api using the data from the Webhook to Send the user the invoice email. I have all the code for this but my issue is that the Webhook expects a response of 200 and when I call the Xero Api code from within the webhook I get an error on Xero saying the response contained a body. This makes sense as I'm submitting and getting data from the Api.
So how can I make my requests to the Xero Api without interfering with the Xero Webhook response?
Webhook Code:
<?php
///////////////////////////////////////////////////////////////////////////
// WEBHOOK AUTHENTICATION - START
///////////////////////////////////////////////////////////////////////////
//hook section
$rawPayload = file_get_contents("php://input");
// Update your webhooks key here
$webhookKey = 'myWebhookKey';
// Compute the payload with HMACSHA256 with base64 encoding
$computedSignatureKey = base64_encode(
hash_hmac('sha256', $rawPayload, $webhookKey, true)
);
// Signature key from Xero request
$xeroSignatureKey = $_SERVER['HTTP_X_XERO_SIGNATURE'];
$isEqual = false;
if (hash_equals($computedSignatureKey, $xeroSignatureKey)) {
$isEqual = true;
http_response_code(200);
// getting and passing the data to the api functionality
$data = json_decode($rawPayload);
xero_api($data);
} else {
http_response_code(401);
}
///////////////////////////////////////////////////////////////////////////
// WEBHOOK AUTHENTICATION - END
///////////////////////////////////////////////////////////////////////////
?>
Api code:
<?php
///////////////////////////////////////////////////////////////////////////
// XERO API FUNCITONALITY - START
///////////////////////////////////////////////////////////////////////////
function xero_api($data) {
if ($data->events[0]->eventType === 'CREATE') {
$resourseId = $data->events[0]->resourceId;
///////////////////////////////////////////////////////////////////////////
// GET XERO CREDENTIALS - START
///////////////////////////////////////////////////////////////////////////
global $wpdb;
$xeroKeys = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}xero_keys WHERE ID = 0", ARRAY_A);
$clientId = $xeroKeys['client_id'];
$clientSecret = $xeroKeys['client_secret'];
$refreshToken = $xeroKeys['refresh_token'];
$tenantId = $xeroKeys['tenant_id'];
///////////////////////////////////////////////////////////////////////////
// GET XERO CREDENTIALS - END
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// GET ACCESS TOKEN AND GENERATE NEW REFRESH TOKEN - START
///////////////////////////////////////////////////////////////////////////
$args = array(
'headers' => array(
'grant_type' => 'refresh_token',
),
'body' => array(
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $clientId,
'client_secret' => $clientSecret
)
);
$refreshTokenRes = wp_remote_post('https://identity.xero.com/connect/token?=', $args);
$refreshTokenBody = json_decode($refreshTokenRes['body']);
if (isset($refreshTokenBody->refresh_token) && isset($refreshTokenBody->access_token)) {
$updateTokens = $wpdb->update(
$wpdb->prefix . 'xero_keys',
array(
'refresh_token' => $refreshTokenBody->refresh_token,
'access_token' => $refreshTokenBody->access_token
),
array('ID' => 0),
array('%s', '%s'),
array('%d')
);
}
///////////////////////////////////////////////////////////////////////////
// GET ACCESS TOKEN AND GENERATE NEW REFRESH TOKEN - End
///////////////////////////////////////////////////////////////////////////
$args = array(
'headers' => array(
'xero-tenant-id' => $tenantId,
'Authorization' => 'Bearer ' . $refreshTokenBody->access_token,
'Accept' => 'application/json',
'Content-Type' => 'application/json'
),
);
$response = wp_remote_post('https://api.xero.com/api.xro/2.0/Invoices/' . $resourseId . '/Email', $args);
}
}
///////////////////////////////////////////////////////////////////////////
// XERO API FUNCITONALITY - END
///////////////////////////////////////////////////////////////////////////
?>

The problem is that you need to separate these calls.
As the comment to your question said, you need to first deal with the webhook, then queue the job that will trigger your API. Something like this would do
<?php
///////////////////////////////////////////////////////////////////////////
// WEBHOOK AUTHENTICATION - START
///////////////////////////////////////////////////////////////////////////
//hook section
$rawPayload = file_get_contents("php://input");
// Update your webhooks key here
$webhookKey = 'myWebhookKey';
// Compute the payload with HMACSHA256 with base64 encoding
$computedSignatureKey = base64_encode(
hash_hmac('sha256', $rawPayload, $webhookKey, true)
);
// Signature key from Xero request
$xeroSignatureKey = $_SERVER['HTTP_X_XERO_SIGNATURE'];
$isEqual = false;
if (hash_equals($computedSignatureKey, $xeroSignatureKey)) {
$isEqual = true;
http_response_code(200);
// getting and passing the data to the api functionality
$data = json_decode($rawPayload);
wp_schedule_single_event(
time() + 10,
'send_xero_api_call',
['data' => $data]
);
} else {
http_response_code(401);
}
///////////////////////////////////////////////////////////////////////////
// WEBHOOK AUTHENTICATION - END
///////////////////////////////////////////////////////////////////////////
Then you need to register a WP Cron
add_action('send_xero_api_call', 'xero_api_cron', 10);
function xero_api_cron($data) {
if ($data->events[0]->eventType === 'CREATE') {
$resourseId = $data->events[0]->resourceId;
///////////////////////////////////////////////////////////////////////////
// GET XERO CREDENTIALS - START
///////////////////////////////////////////////////////////////////////////
global $wpdb;
$xeroKeys = $wpdb->get_row("SELECT * FROM {$wpdb->prefix}xero_keys WHERE ID = 0", ARRAY_A);
$clientId = $xeroKeys['client_id'];
$clientSecret = $xeroKeys['client_secret'];
$refreshToken = $xeroKeys['refresh_token'];
$tenantId = $xeroKeys['tenant_id'];
///////////////////////////////////////////////////////////////////////////
// GET XERO CREDENTIALS - END
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// GET ACCESS TOKEN AND GENERATE NEW REFRESH TOKEN - START
///////////////////////////////////////////////////////////////////////////
$args = array(
'headers' => array(
'grant_type' => 'refresh_token',
),
'body' => array(
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => $clientId,
'client_secret' => $clientSecret
)
);
$refreshTokenRes = wp_remote_post('https://identity.xero.com/connect/token?=', $args);
$refreshTokenBody = json_decode($refreshTokenRes['body']);
if (isset($refreshTokenBody->refresh_token) && isset($refreshTokenBody->access_token)) {
$updateTokens = $wpdb->update(
$wpdb->prefix . 'xero_keys',
array(
'refresh_token' => $refreshTokenBody->refresh_token,
'access_token' => $refreshTokenBody->access_token
),
array('ID' => 0),
array('%s', '%s'),
array('%d')
);
}
///////////////////////////////////////////////////////////////////////////
// GET ACCESS TOKEN AND GENERATE NEW REFRESH TOKEN - End
///////////////////////////////////////////////////////////////////////////
$args = array(
'headers' => array(
'xero-tenant-id' => $tenantId,
'Authorization' => 'Bearer ' . $refreshTokenBody->access_token,
'Accept' => 'application/json',
'Content-Type' => 'application/json'
),
);
$response = wp_remote_post('https://api.xero.com/api.xro/2.0/Invoices/' . $resourseId . '/Email', $args);
}
}
Something to this extent. The $data argument should contain the data you passed (not 100% sure how it looks for you). Also, you'll need to check the throttling by the API, so you might adjust the time of the execution of your job. And make sure you have somewhere stored if your background job finished successfully.
One thing I suggest, since you are storing sensitive info in your DB (access and refresh tokens), is to encrypt them when storing them in the DB, and decrypt when fetching them.
You can check how I implemented background jobs in my plugin
https://github.com/dingo-d/woo-solo-api/tree/develop/src/BackgroundJobs
https://github.com/dingo-d/woo-solo-api/blob/develop/src/Request/SoloApiRequest.php#L428-L438
You can use WP Queue lib from deliciousbrains: https://github.com/deliciousbrains/wp-queue/
It's a wrapper around WP Cron with custom DB tables for handling the queues, which will allow you to check if the jobs executed correctly or not.

Related

My callback_url endpoint in WooCommerce Rest API won't Fire

I am working on a php application that requires me to interact with each merchants store using the WooCommerce Rest API and i am trying to auto-generate the rest api keys like it was documented on their documentation but my callback_url endpoint won't fire and i don't receive the auto-generated keys sent to the callback endpoint.
Here is my code to Create an authentication endpoint URL
public function integrate()
{
$url = $this->input->post('url');
$title = $this->input->post('title');
$user_id = $this->session->userdata('client_id');
$save = $this->Store_model->save_store($user_id,$url, $title);
$genKeyEndpoint = '/wc-auth/v1/authorize';
$params = [
'app_name' => 'App Name',
'scope' => 'read_write',
'user_id' => $user_id,
'return_url' => base_url('stores/integrateForm'),
'callback_url' => base_url('stores/callback-endpoint')
];
$query_string = http_build_query( $params, null, '&', PHP_QUERY_RFC3986 );
$wooAuth = $url . $genKeyEndpoint . '?' . $query_string;
redirect($wooAuth);
}
and here is my code to retrieve the generated keys and store in my database
public function save_api_key() {
$post_data = json_decode(file_get_contents('php://input'), true);
$wooResponseData = [
'consumer_key' => $post_data['consumer_key'],
'consumer_secret' => $post_data['consumer_secret']
];
$this->Store_model->updateStoreKeys($this->session->userdata('client_id'), $wooResponseData);
}
My application is running on codeigniter.

Rest Authentication for woocommerce oauth

we are using the woocommerce Rest API. If there any ways to get client key and secret key in api response for oauth authentication. We referred the below link https://woocommerce.github.io/woocommerce-rest-api-docs/?php#rest-api-keys
$store_url = 'http://example.com';
$endpoint = '/wc-auth/v1/authorize';
$params = [
'app_name' => 'My App Name',
'scope' => 'write',
'user_id' => 123,
'return_url' => 'http://app.com',
'callback_url' => 'https://app.com'
];
$query_string = http_build_query( $params );
echo $store_url . $endpoint . '?' . $query_string;
But it Doesn't return any response. It only returns the post values only send by me.

WordPress REST API Basic Authentication

I am working on a project that requires some integration between different WordPress instances and I am in the process of creating a WordPress plugin that will provide that functionality through the REST API.
I have enabled the WP-API plugin and the Basic Authentication plugin and am able to make requests that do not require authentication but when I make a request that does require authentication, such as adding a new page, I am met with 401 - Sorry, you are not allowed to create new posts.
I realize basic authentication is not suitable for production needs but would like to get it working properly for development and have been spinning my wheels on this seemingly small problem. I am perfectly able to make these requests using Postman, so there is something wrong with my implementation. Here is the code in question:
function add_new_page($post) {
// Credentials for basic authentication.
$username = 'user';
$password = 'password';
// Request headers.
$headers = array(
'Authorization' => 'Basic ' . base64_encode( $username . ':' . $password ),
'Content-Type' => 'application/json'
);
// Request URL.
$url = "http://localhost/wp-json/wp/v2/pages";
// Request body.
$body = array(
'slug' => $post->post_name,
'status' => $post->post_status,
'type' => $post->post_type,
'title' => $post->post_title,
'content' => $post->post_content,
'excerpt' => $post->post_excerpt,
);
$body_json = json_encode($body);
// Request arguments.
$args = array(
'method' => 'POST',
'blocking' => true,
'headers' => $headers,
'cookies' => array(),
'body' => $body_json,
);
// Fire request.
$response = wp_remote_request($url, $args);
// Handle response.
if (is_wp_error($response)) {
$error_message = $response->get_error_message();
echo "Something went wrong: $error_message";
} else {
$response_body = json_decode(wp_remote_retrieve_body($response));
// Display response body.
echo '<pre>';
print_r($response_body);
echo '</pre>';
}
// Exit so we can read the response.
exit();
}
I would be really appreciative of any insights somebody out there could provide.

Setting post data with a Laravel request object

I'm trying to test a Laravel API endpoint and want to call it in code.
$request = Request::create( $path, $method );
$response = Route::dispatch( $request );
This snippet works fine for GET but I need to be able to set up POST calls too. Setting the $method to POST works as well, but I can't find documentation detailing how to attach post data.
Any advice?
As you mentioned in the comments, you could use $this->call() but you can actually do it with your current code too. If you take a look at the signature of the Request::create() function you can see that it takes $parameters as third argument:
public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null)
And the docblock says: The query (GET) or request (POST) parameters
So you can simply add the data to Request::create()
$data = array('foo' => 'bar');
$request = Request::create( $path, $method, $data );
$response = Route::dispatch( $request );
I've spent nearly a day trying to get this working myself for social authentication with passport and Angular front-end.
When I use the Restlet API Client to make the request I always get a successful response.
Restlet Client Request
Restlet client response
However using the following method of making internal requests always gave me an error.
$request = Request::create(
'/oauth/token',
'POST',
[
'grant_type' => 'social',
'client_id' => 'your_oauth_client_id',
'client_secret' => 'your_oauth_client_secret',
'provider' => 'social_auth_provider', // e.g facebook, google
'access_token' => 'access_token', // access token issued by specified provider
]
);
$response = Route::dispatch($request);
$content = json_decode($response->getContent(), true);
if (! $response->isSuccessful()) {
return response()->json($content, 401);
}
return response()->json([
'content' => $content,
'access_token' => $content['access_token'],
'refresh_token' => $content['refresh_token'],
'token_type' => $content['token_type'],
'expires_at' => Carbon::parse(
$content['expires_in']
)->toDateTimeString()
]);
This specific error:
{
error: "unsupported_grant_type",
error_description: "The authorization grant type is not supported by the
authorization server.",
hint: "Check that all required parameters have been provided",
message: "The authorization grant type is not supported by the authorization server."
}
I had the feeling it has to do with the way the form data is sent in the request, so while searching for a proper way to make such internal requests in laravel I came across this sample project with a working implementation: passport-social-grant-example.
In summary here's how to do it:
$proxy = Request::create(
'/oauth/token',
'POST',
[
'grant_type' => 'social',
'client_id' => 'your_oauth_client_id',
'client_secret' => 'your_oauth_client_secret',
'provider' => 'social_auth_provider', // e.g facebook, google
'access_token' => 'access_token', // access token issued by specified provider
]
);
return app()->handle($proxy);
Hope this helps.

Get Twitter Following Count

Im creating a widget for a Wordpress site and i am trying to get the twitter following count, I can get the followers count which is taken from http://www.wpbeginner.com/wp-tutorials/displaying-the-total-number-of-twitter-followers-as-text-on-wordpress/. Any help would be great.
thanks Pierce
Current code in functions.php:
// Twitter
function getTwitterFollowers($screenName = 'hellowWorld')
{
// some variables
$consumerKey = 'hidden';
$consumerSecret = 'hidden';
$token = get_option('cfTwitterToken');
// get follower count from cache
$numberOfFollowers = get_transient('cfTwitterFollowers');
// cache version does not exist or expired
if (false === $numberOfFollowers) {
// getting new auth bearer only if we don't have one
if(!$token) {
// preparing credentials
$credentials = $consumerKey . ':' . $consumerSecret;
$toSend = base64_encode($credentials);
// http post arguments
$args = array(
'method' => 'POST',
'httpversion' => '1.1',
'blocking' => true,
'headers' => array(
'Authorization' => 'Basic ' . $toSend,
'Content-Type' => 'application/x-www-form-urlencoded;charset=UTF-8'
),
'body' => array( 'grant_type' => 'client_credentials' )
);
add_filter('https_ssl_verify', '__return_false');
$response = wp_remote_post('https://api.twitter.com/oauth2/token', $args);
$keys = json_decode(wp_remote_retrieve_body($response));
if($keys) {
// saving token to wp_options table
update_option('cfTwitterToken', $keys->access_token);
$token = $keys->access_token;
}
}
// we have bearer token wether we obtained it from API or from options
$args = array(
'httpversion' => '1.1',
'blocking' => true,
'headers' => array(
'Authorization' => "Bearer $token"
)
);
add_filter('https_ssl_verify', '__return_false');
$api_url = "https://api.twitter.com/1.1/users/show.json?screen_name=$screenName";
$response = wp_remote_get($api_url, $args);
if (!is_wp_error($response)) {
$followers = json_decode(wp_remote_retrieve_body($response));
$numberOfFollowers = $followers->followers_count;
} else {
// get old value and break
$numberOfFollowers = get_option('cfNumberOfFollowers');
// uncomment below to debug
//die($response->get_error_message());
}
// cache for an hour
set_transient('cfTwitterFollowers', $numberOfFollowers, 1*60*60);
update_option('cfNumberOfFollowers', $numberOfFollowers);
}
return $numberOfFollowers;
}
It was pretty simple if I just read the documentation anyway...
Instead of followers_count i replaced it with friends_count as outlined in the API 1.1 documentation. :)

Categories