#php #wordpress #webhooks #xero-api #xero
#php #wordpress #webhooks #xero-api #xero
Вопрос:
В моей учетной записи Xero есть Webhook для счетов-фактур. Когда создается новый счет-фактура, я хочу получить доступ к Xero Api, используя данные из Webhook для отправки пользователю счета по электронной почте. У меня есть весь код для этого, но моя проблема в том, что Webhook ожидает ответа 200, и когда я вызываю код Xero Api из webhook, я получаю сообщение об ошибке Xero, в котором говорится, что ответ содержит тело. Это имеет смысл, поскольку я отправляю и получаю данные из Api.
Итак, как я могу отправлять свои запросы в Xero Api, не вмешиваясь в ответ Xero Webhook?
Код Webhook:
<?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:
<?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) amp;amp; 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
///////////////////////////////////////////////////////////////////////////
?>
Комментарии:
1. Не могу конкретно ссылаться на ваш код, но наилучшей практикой для работы с webhooks было бы: 1) Получить webhook 2) Проверить полезную нагрузку webhook с помощью
x-xero-signature
заголовка 3) Сохранить полезную нагрузку webhook в db / queue 4) Ответить кодом состояния 200… и иметь отдельный процесс для запуска через вашу базу данных / очередь для выполнения работы с полученной полезной нагрузкой. Пытаться выполнить работу до того, как вы ответили на webhook, рискованно, потому что вы не можете гарантировать, что Xero API ответит в течение 5-секундного срока webhook.2. @rustyskates Спасибо за совет. Но когда вы говорите «иметь отдельный процесс для запуска через вашу БД / очередь», означает ли это использование задания cron или есть другой способ для этого? Кстати, я использую WordPress.
Ответ №1:
Проблема в том, что вам нужно разделить эти вызовы.
Как сказано в комментарии к вашему вопросу, вам нужно сначала разобраться с webhook, а затем поставить в очередь задание, которое запустит ваш API. Что-то вроде этого сделало бы
<?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
///////////////////////////////////////////////////////////////////////////
Затем вам нужно зарегистрировать 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) amp;amp; 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);
}
}
Что-то в этом роде. Аргумент $data должен содержать переданные вами данные (не уверен на 100%, как это выглядит для вас). Кроме того, вам нужно будет проверить регулирование API, чтобы вы могли настроить время выполнения вашей работы. И убедитесь, что у вас есть где-то сохраненный, если ваша фоновая работа успешно завершена.
Поскольку вы храните конфиденциальную информацию в своей БД (токены доступа и обновления), я предлагаю одну вещь: шифровать их при хранении в БД и расшифровывать при их извлечении.
Вы можете проверить, как я реализовал фоновые задания в своем плагине
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
Вы можете использовать WP Queue lib от deliciousbrains: https://github.com/deliciousbrains/wp-queue /
Это оболочка вокруг WP Cron с пользовательскими таблицами БД для обработки очередей, которая позволит вам проверить, правильно ли выполняются задания или нет.
Комментарии:
1. Привет, спасибо за ответ. Я попробую это завтра и дам вам знать, как это происходит.
2. Это отлично работает, не мешая работе моего webhook спасибо!!! Мне просто нужно немного поработать над моим кодом api, и все должно быть хорошо
3. Потрясающе, рад, что смог помочь 🙂
4. Я думаю, вы пропустили определение количества параметров, которые функция может иметь внутри add_action. Я попытался отредактировать, но редактирование слишком мало, чтобы быть принятым
5. Это зависит от того, сколько параметров вы хотите передать. Например, здесь я распространяю
$args
аргумент. Я передал здесь 5 аргументов. Вы можете сделать то же самое в своем вызове. В примере я передал только один.