Как отправлять post-запросы в Xero API внутри Xero Webhook

#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 аргументов. Вы можете сделать то же самое в своем вызове. В примере я передал только один.