From 36f0d52290520beaf247dec224e0b9a9c6e1c506 Mon Sep 17 00:00:00 2001 From: Pavel Date: Mon, 25 Nov 2024 20:27:27 +0200 Subject: [PATCH] Added Order paid completed webhook --- README.txt | 5 +- assets/js/admin-javascript.js | 7 +- bootstrap.php | 1 + gtm-server-side.php | 3 +- includes/class-gtm-server-side-admin-ajax.php | 123 ++++++++++-------- .../class-gtm-server-side-admin-settings.php | 28 +++- includes/class-gtm-server-side-wc-helpers.php | 8 ++ ...lass-gtm-server-side-webhook-completed.php | 86 ++++++++++++ ...ass-gtm-server-side-webhook-processing.php | 2 +- .../class-gtm-server-side-webhook-refund.php | 2 +- 10 files changed, 205 insertions(+), 60 deletions(-) create mode 100644 includes/class-gtm-server-side-webhook-completed.php diff --git a/README.txt b/README.txt index dfedf11..69e4718 100644 --- a/README.txt +++ b/README.txt @@ -3,7 +3,7 @@ Contributors: gtmserver,bukashk0zzz Tags: google tag manager, google tag manager server side, gtm, gtm server side, tag manager, tagmanager, analytics, google, serverside, server-side, gtag Requires at least: 5.2.0 Tested up to: 6.7.0 -Stable tag: 2.1.23 +Stable tag: 2.1.24 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -67,6 +67,9 @@ Yes. How t == Changelog == += 2.1.24 = +* Added Order paid completed webhook + = 2.1.23 = * Fix purchase event diff --git a/assets/js/admin-javascript.js b/assets/js/admin-javascript.js index 871bcaa..115a574 100644 --- a/assets/js/admin-javascript.js +++ b/assets/js/admin-javascript.js @@ -54,12 +54,13 @@ jQuery( document ).ready( } var isPurchaseChecked = jQuery( '#gtm_server_side_webhooks_purchase' ).is( ':checked' ); - var isRefundChecked = jQuery( '#gtm_server_side_webhooks_refund' ).is( ':checked' ); var isProcessingChecked = jQuery( '#gtm_server_side_webhooks_processing' ).is( ':checked' ); + var isCompletedChecked = jQuery( '#gtm_server_side_webhooks_completed' ).is( ':checked' ); + var isRefundChecked = jQuery( '#gtm_server_side_webhooks_refund' ).is( ':checked' ); - return isPurchaseChecked || isRefundChecked || isProcessingChecked; + return isPurchaseChecked || isProcessingChecked || isCompletedChecked || isRefundChecked; }, - 'Select purchase and/or refund webhook' + 'Select one or more webhooks' ); // Tab "General". diff --git a/bootstrap.php b/bootstrap.php index 965a2c2..a38552b 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -32,6 +32,7 @@ define( 'GTM_SERVER_SIDE_FIELD_WEBHOOKS_CONTAINER_URL', 'gtm_server_side_webhooks_container_url' ); define( 'GTM_SERVER_SIDE_FIELD_WEBHOOKS_PURCHASE', 'gtm_server_side_webhooks_purchase' ); define( 'GTM_SERVER_SIDE_FIELD_WEBHOOKS_PROCESSING', 'gtm_server_side_webhooks_processing' ); +define( 'GTM_SERVER_SIDE_FIELD_WEBHOOKS_COMPLETED', 'gtm_server_side_webhooks_completed' ); define( 'GTM_SERVER_SIDE_FIELD_WEBHOOKS_REFUND', 'gtm_server_side_webhooks_refund' ); define( 'GTM_SERVER_SIDE_FIELD_PLACEMENT_VALUE_CODE', 'code' ); diff --git a/gtm-server-side.php b/gtm-server-side.php index 7138e2e..6f05819 100644 --- a/gtm-server-side.php +++ b/gtm-server-side.php @@ -10,7 +10,7 @@ * Plugin Name: GTM Server Side * Plugin URI: https://wordpress.org/plugins/gtm-server-side/ * Description: Enhance conversion tracking by implementing server-side tagging using server Google Tag Manager container. Effortlessly configure data layer events in web GTM, send webhooks, set up custom loader, and extend cookie lifetime. - * Version: 2.1.23 + * Version: 2.1.24 * Author: Stape * Author URI: https://stape.io * License: GPL-2.0+ @@ -32,6 +32,7 @@ add_action( 'gtm_server_side', array( GTM_Server_Side_I18n::class, 'instance' ) ); add_action( 'gtm_server_side', array( GTM_Server_Side_Webhook_Purchase::class, 'instance' ) ); add_action( 'gtm_server_side', array( GTM_Server_Side_Webhook_Processing::class, 'instance' ) ); +add_action( 'gtm_server_side', array( GTM_Server_Side_Webhook_Completed::class, 'instance' ) ); add_action( 'gtm_server_side', array( GTM_Server_Side_Webhook_Refund::class, 'instance' ) ); add_action( 'gtm_server_side_admin', array( GTM_Server_Side_Admin_Settings::class, 'instance' ) ); add_action( 'gtm_server_side_admin', array( GTM_Server_Side_Admin_Ajax::class, 'instance' ) ); diff --git a/includes/class-gtm-server-side-admin-ajax.php b/includes/class-gtm-server-side-admin-ajax.php index 6e91f63..03492fe 100644 --- a/includes/class-gtm-server-side-admin-ajax.php +++ b/includes/class-gtm-server-side-admin-ajax.php @@ -54,13 +54,19 @@ public function gtm_server_side_webhook_test() { ); } - $is_refund = GTM_Server_Side_Helpers::get_option( GTM_SERVER_SIDE_FIELD_WEBHOOKS_REFUND ); $is_purchase = GTM_Server_Side_Helpers::get_option( GTM_SERVER_SIDE_FIELD_WEBHOOKS_PURCHASE ); $is_processing = GTM_Server_Side_Helpers::get_option( GTM_SERVER_SIDE_FIELD_WEBHOOKS_PROCESSING ); - if ( empty( $is_purchase ) && empty( $is_refund ) && empty( $is_processing ) ) { + $is_completed = GTM_Server_Side_Helpers::get_option( GTM_SERVER_SIDE_FIELD_WEBHOOKS_COMPLETED ); + $is_refund = GTM_Server_Side_Helpers::get_option( GTM_SERVER_SIDE_FIELD_WEBHOOKS_REFUND ); + if ( + empty( $is_purchase ) && + empty( $is_processing ) && + empty( $is_completed ) && + empty( $is_refund ) + ) { wp_send_json_error( array( - 'message' => __( 'Purchase or order paid or refund webhook is required.', 'gtm-server-side' ), + 'message' => __( 'Purchase or order paid processing or order paid completed or refund webhook is required.', 'gtm-server-side' ), ) ); } @@ -74,6 +80,10 @@ public function gtm_server_side_webhook_test() { $answer[] = $this->send_webhook_processing(); } + if ( ! empty( $is_completed ) ) { + $answer[] = $this->send_webhook_completed(); + } + if ( ! empty( $is_refund ) ) { $answer[] = $this->send_webhook_refund(); } @@ -102,28 +112,7 @@ public function gtm_server_side_webhook_test() { private function send_webhook_purchase() { $request = array( 'event' => 'purchase', - 'ecommerce' => array( - 'transaction_id' => '358', - 'affiliation' => 'test', - 'value' => 18.00, - 'tax' => 0, - 'shipping' => 0, - 'currency' => 'USD', - 'coupon' => 'test_coupon', - 'items' => array( - array( - 'item_name' => 'Beanie', - 'item_brand' => 'Stape', - 'item_id' => '15', - 'item_sku' => 'woo-beanie', - 'price' => 18.00, - 'item_category' => 'Clothing', - 'item_category2' => 'Accessories', - 'quantity' => 1, - 'index' => 1, - ), - ), - ), + 'ecommerce' => $this->get_ecommerce_data(), ); $result = $this->send_request( $request ); @@ -139,47 +128,49 @@ private function send_webhook_purchase() { } /** - * Send webhooks processing (order paid). + * Send webhooks processing (order paid processing). * * @return string */ private function send_webhook_processing() { $request = array( 'event' => 'order_paid', - 'ecommerce' => array( - 'transaction_id' => '358', - 'affiliation' => 'test', - 'value' => 18.00, - 'tax' => 0, - 'shipping' => 0, - 'currency' => 'USD', - 'coupon' => 'test_coupon', - 'items' => array( - array( - 'item_name' => 'Beanie', - 'item_brand' => 'Stape', - 'item_id' => '15', - 'item_sku' => 'woo-beanie', - 'price' => 18.00, - 'item_category' => 'Clothing', - 'item_category2' => 'Accessories', - 'quantity' => 1, - 'index' => 1, - ), - ), - ), + 'ecommerce' => $this->get_ecommerce_data(), ); $result = $this->send_request( $request ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( - 'message' => __( 'Some problem with Purchase webhook.', 'gtm-server-side' ), + 'message' => __( 'Some problem with order paid processing webhook.', 'gtm-server-side' ), + ) + ); + } + + return __( 'Order paid processing webhook sent.', 'gtm-server-side' ); + } + + /** + * Send webhooks completed (order paid completed). + * + * @return string + */ + private function send_webhook_completed() { + $request = array( + 'event' => 'order_completed', + 'ecommerce' => $this->get_ecommerce_data(), + ); + + $result = $this->send_request( $request ); + if ( is_wp_error( $result ) ) { + wp_send_json_error( + array( + 'message' => __( 'Some problem with order paid completed webhook.', 'gtm-server-side' ), ) ); } - return __( 'Order paid webhook sent.', 'gtm-server-side' ); + return __( 'Order paid completed webhook sent.', 'gtm-server-side' ); } /** @@ -244,7 +235,7 @@ private function send_request( $body ) { } /** - * Return user request test data + * Return user request test data. * * @return array */ @@ -275,4 +266,34 @@ private function get_request_user_data() { 'new_customer' => 'false', ); } + + /** + * Return ecommerce test data. + * + * @return array + */ + private function get_ecommerce_data() { + return array( + 'transaction_id' => '358', + 'affiliation' => 'test', + 'value' => 18.00, + 'tax' => 0, + 'shipping' => 0, + 'currency' => 'USD', + 'coupon' => 'test_coupon', + 'items' => array( + array( + 'item_name' => 'Beanie', + 'item_brand' => 'Stape', + 'item_id' => '15', + 'item_sku' => 'woo-beanie', + 'price' => 18.00, + 'item_category' => 'Clothing', + 'item_category2' => 'Accessories', + 'quantity' => 1, + 'index' => 1, + ), + ), + ); + } } diff --git a/includes/class-gtm-server-side-admin-settings.php b/includes/class-gtm-server-side-admin-settings.php index cd5c648..39e1448 100644 --- a/includes/class-gtm-server-side-admin-settings.php +++ b/includes/class-gtm-server-side-admin-settings.php @@ -441,7 +441,7 @@ function() { ); add_settings_field( GTM_SERVER_SIDE_FIELD_WEBHOOKS_PROCESSING, - __( 'Order paid webhook', 'gtm-server-side' ), + __( 'Order paid webhook - processing', 'gtm-server-side' ), function() { echo ''; echo '
'; - printf( __( 'Order paid event will be sent whenever an order is paid (has "Processing" status as per
Woocommerce documentation).', 'gtm-server-side' ), 'https://woocommerce.com/document/managing-orders/order-statuses/' ); // phpcs:ignore + printf( __( 'order_paid event will be sent whenever an order is paid (has "Processing" status as per Woocommerce documentation).', 'gtm-server-side' ), 'https://woocommerce.com/document/managing-orders/order-statuses/' ); // phpcs:ignore + }, + GTM_SERVER_SIDE_ADMIN_SLUG, + GTM_SERVER_SIDE_ADMIN_GROUP_WEBHOOKS + ); + + register_setting( + GTM_SERVER_SIDE_ADMIN_GROUP, + GTM_SERVER_SIDE_FIELD_WEBHOOKS_COMPLETED, + array( + 'sanitize_callback' => 'GTM_Server_Side_Helpers::sanitize_bool', + ) + ); + add_settings_field( + GTM_SERVER_SIDE_FIELD_WEBHOOKS_COMPLETED, + __( 'Order paid webhook - completed', 'gtm-server-side' ), + function() { + echo ''; + echo '
'; + printf( __( 'order_completed event will be sent whenever order status becomes completed (has "Completed" status as per Woocommerce documentation).', 'gtm-server-side' ), 'https://woocommerce.com/document/managing-orders/order-statuses/' ); // phpcs:ignore }, GTM_SERVER_SIDE_ADMIN_SLUG, GTM_SERVER_SIDE_ADMIN_GROUP_WEBHOOKS diff --git a/includes/class-gtm-server-side-wc-helpers.php b/includes/class-gtm-server-side-wc-helpers.php index 34a5b67..44ece2f 100644 --- a/includes/class-gtm-server-side-wc-helpers.php +++ b/includes/class-gtm-server-side-wc-helpers.php @@ -96,6 +96,10 @@ public function get_order_data_layer_items( $items ) { foreach ( $items as $item_loop ) { $product = $item_loop->get_product(); + if ( ! ( $product instanceof WC_Product ) ) { + continue; + } + $array = $this->get_data_layer_item( $product ); $array['quantity'] = intval( $item_loop->get_quantity() ); $array['index'] = $index++; @@ -118,6 +122,10 @@ public function get_cart_data_layer_items( $cart ) { foreach ( $cart as $product_loop ) { $product = $product_loop['data']; + if ( ! ( $product instanceof WC_Product ) ) { + continue; + } + $array = $this->get_data_layer_item( $product ); $array['quantity'] = intval( $product_loop['quantity'] ); $array['index'] = $index++; diff --git a/includes/class-gtm-server-side-webhook-completed.php b/includes/class-gtm-server-side-webhook-completed.php new file mode 100644 index 0000000..db9ddad --- /dev/null +++ b/includes/class-gtm-server-side-webhook-completed.php @@ -0,0 +1,86 @@ + 'order_completed', + 'ecommerce' => array( + 'transaction_id' => esc_attr( $order->get_order_number() ), + 'affiliation' => '', + 'value' => GTM_Server_Side_WC_Helpers::instance()->formatted_price( $order->get_total() ), + 'tax' => GTM_Server_Side_WC_Helpers::instance()->formatted_price( $order->get_total_tax() ), + 'shipping' => GTM_Server_Side_WC_Helpers::instance()->formatted_price( $order->get_shipping_total() ), + 'currency' => esc_attr( $order->get_currency() ), + 'coupon' => esc_attr( join( ',', $order->get_coupon_codes() ) ), + 'items' => GTM_Server_Side_WC_Helpers::instance()->get_order_data_layer_items( $order->get_items() ), + ), + 'user_data' => GTM_Server_Side_WC_Helpers::instance()->get_order_user_data( $order ), + ); + + $request_cookies = GTM_Server_Side_Helpers::get_request_cookies(); + + if ( ! empty( $request_cookies ) ) { + $request['cookies'] = $request_cookies; + + if ( isset( $request_cookies['_dcid'] ) ) { + $request['client_id'] = $request_cookies['_dcid']; + } + } + + /** + * Allows modification of processing order webhook payload. + * + * @param array $request Webhook payload data. + * @param object $order WC_Order instance. + */ + $request = apply_filters( 'gtm_server_side_processing_webhook_payload', $request, $order ); + + GTM_Server_Side_Helpers::send_webhook_request( $request ); + } +} diff --git a/includes/class-gtm-server-side-webhook-processing.php b/includes/class-gtm-server-side-webhook-processing.php index 907d3f9..eed7c17 100644 --- a/includes/class-gtm-server-side-webhook-processing.php +++ b/includes/class-gtm-server-side-webhook-processing.php @@ -29,7 +29,7 @@ public function init() { } /** - * Order change status to processing (Order paid). + * Order change status to processing. * * @param int $order_id Order id. * @return void diff --git a/includes/class-gtm-server-side-webhook-refund.php b/includes/class-gtm-server-side-webhook-refund.php index 8bd700e..d204f26 100644 --- a/includes/class-gtm-server-side-webhook-refund.php +++ b/includes/class-gtm-server-side-webhook-refund.php @@ -52,7 +52,7 @@ public function woocommerce_order_refunded( $order_id, $refund_id ) { $request = array( 'event' => 'refund', 'ecommerce' => array( - 'transaction_id' => $refund_id, + 'transaction_id' => esc_attr( $order->get_order_number() ), 'value' => GTM_Server_Side_WC_Helpers::instance()->formatted_price( $order->get_total() ), 'currency' => esc_attr( $order->get_currency() ), 'items' => GTM_Server_Side_WC_Helpers::instance()->get_order_data_layer_items( $order->get_items() ),