如何在 Xero Webhook 内部向 Xero API 提交 Post 请求

How to Submit Post Requests to Xero API Inside Xero Webhook

我的 Xero 发票帐户上有一个 Webhook。创建新发票时,我想使用 Webhook 中的数据访问 Xero Api 以向用户发送发票电子邮件。我有这方面的所有代码,但我的问题是 Webhook 期望响应为 200,当我从 Webhook 中调用 Xero Api 代码时,我在 Xero 上收到一条错误消息,说响应包含一个正文。这是有道理的,因为我正在从 Api.

提交和获取数据

那么我如何才能在不干扰 Xero Webhook 响应的情况下向 Xero Api 发出请求?

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) && 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
///////////////////////////////////////////////////////////////////////////
?>

问题是你需要把这些调用分开。

正如对您问题的评论所说,您需要首先处理 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) && 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

您可以使用 deliciousbrains 的 WP Queue 库:https://github.com/deliciousbrains/wp-queue/

它是 WP Cron 的包装器,带有用于处理队列的自定义数据库表,可让您检查作业是否正确执行。