/var/www/html_de/wp-content/plugins/klaviyo/includes/blocks/StoreApi.php


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
<?php
/**
 * WooCommerceKlaviyo Blocks StoreApi
 *
 * StoreApi used to process consent at checkout checkboxes.
 *
 * @package     WooCommerceKlaviyo/Blocks
 * @since       3.2
 */

namespace WCK\Blocks;

/**
 * StoreApi
 */
class StoreApi {

    
/**
     * Plugin settings and API keys.
     *
     * @var array
     */
    
protected $settings;
    
/**
     * Klaviyo production API.
     *
     * @var string
     */
    
protected $api_url 'https://a.klaviyo.com/api/';

    
/**
     * Get options and hook into actions.
     */
    
public function __construct() {
        
$this->settings get_option'klaviyo_settings' );
        if ( ! 
$this->settings ) {
            return;
        }

        
/**
         * Compatibility issue separate from consent at checkout, see callback method docstring for more info. We are
         * using this hook based on its occurring specifically from the Checkout Block and corresponding to the older
         * woocommerce_checkout_order_processed hook and indicates and order is processed and ready for payment.
         */
        
add_action'woocommerce_store_api_checkout_order_processed', array( $this'update_order_created_at' ), 10);

        
// Only run the logic if we have one of the newsletter checkboxes enabled.
        
if (
            ( isset(
$this->settings['klaviyo_sms_subscribe_checkbox']) && $this->settings['klaviyo_sms_subscribe_checkbox'] )
            || ( isset(
$this->settings['klaviyo_subscribe_checkbox']) && $this->settings['klaviyo_subscribe_checkbox'] )
        ) {
            
// React to consent opt-in from Store API (Checkout block) and schedule an API call to Klaviyo.
            
add_action'woocommerce_store_api_checkout_update_order_from_request', array( $this'optin_customer_from_store_api' ), 10);
            
// Triggers an API call to Klaviyo.
            
add_action'klaviyo_schedule_consent_event', array( $this'send_consent_event' ), 10);
            
// Register Store API schema and validation.
            
$this->register_store_api_routes();
        }
    }

    
/**
     * We need to reset the order's created_at property when the order is placed to counteract Block Checkout creating
     * the shop_order record at the start of checkout as a draft. Legacy checkouts created a shop_order record when a
     * customer clicks "Place Order". The earlier created_at date causes Placed Order and Ordered Product events in
     * Klaviyo to eventually be recorded before Started Checkout events in a profile's activity feed. This can impact
     * Abandoned Cart flows negatively.
     *
     * @param \WC_Order $order Order object.
     * @return void
     */
    
public function update_order_created_at$order ) {
        
$unix_now time();
        
$order->set_date_created$unix_now );
        
// to ensure modified isn't before.
        
$order->set_date_modified$unix_now );
        
$order->save();
    }

    
/**
     * Register Store API schema and validation.
     * We're not returning anything to the Checkout request, that's why we only have schema_callback. We need to react to POST requests.
     *
     * @return void
     * @since 0.1.0
     * @see https://github.com/woocommerce/woocommerce-blocks/blob/trunk/docs/third-party-developers/extensibility/rest-api/extend-rest-api-add-data.md
     */
    
public function register_store_api_routes() {
        
$args = array(
            
'endpoint'        => \Automattic\WooCommerce\StoreApi\Schemas\V1\CheckoutSchema::IDENTIFIER,
            
'namespace'       => 'klaviyo',
            
'schema_callback' => function () {
                return array(
                    
'newsletter' => array(
                        
'description' => __'Subscribe to email newsletter.''klaviyo-checkout-block' ),
                        
'type'        => array( 'boolean''null' ),
                        
'context'     => array(),
                        
'arg_options' => $this->optional_boolean_arg_options(),
                    ),
                    
'sms'        => array(
                        
'description' => __'Subscribe to sms marketing newsletter.''klaviyo-checkout-block' ),
                        
'type'        => array( 'boolean''null' ),
                        
'context'     => array(),
                        
'arg_options' => $this->optional_boolean_arg_options(),
                    ),
                );
            },
        );

        
woocommerce_store_api_register_endpoint_data$args );
    }

    
/**
     * React to consent opt-in from Store API (Checkout block) and schedule an API call to Klaviyo.
     *
     * Request payload structure:
     * https://github.com/woocommerce/woocommerce-blocks/blob/trunk/src/StoreApi/docs/checkout.md#process-order-and-payment
     *
     * @param \WC_Order        $order Order object.
     * @param \WP_REST_Request $request Request object.
     * @return void
     * @since 0.1.0
     */
    
public function optin_customer_from_store_api$order$request ) {
        
$body            $request->get_json_params();
        
$billing_address $body['billing_address'] ?? array();
        
$request_email   $billing_address['email'] ?? null;
        
$request_phone   $billing_address['phone'] ?? null;
        
$request_country $billing_address['country'] ?? null;
        
$email           $order->get_billing_email() ?? $request_email;
        
$phone           $order->get_billing_phone() ?? $request_phone;
        
$country         $order->get_billing_country() ?? $request_country;

        
// Structure of extension data defined in schema_callback above.
        
$consent_to_sms        $request['extensions']['klaviyo']['sms'] ?? false;
        
$consent_to_newsletter $request['extensions']['klaviyo']['newsletter'] ?? false;

        if ( 
$consent_to_sms || $consent_to_newsletter ) {
            
as_enqueue_async_action'klaviyo_schedule_consent_event', array( $email$phone$country$consent_to_sms$consent_to_newsletter ), 'klaviyo' );
        }
    }

    
/**
     * Triggers an API call to Klaviyo.
     *
     * @param string  $email Customer email.
     * @param string  $phone Customer billing phone.
     * @param string  $country Customer billing country.
     * @param boolean $consent_to_sms If the customer consented to sms.
     * @param boolean $consent_to_newsletter If the customer consented to newsletter.
     * @return void
     * @since 0.1.0
     */
    
public function send_consent_event$email$phone$country$consent_to_sms$consent_to_newsletter ) {
        
$url  $this->api_url 'webhook/integration/woocommerce?c=' $this->settings['klaviyo_public_api_key'];
        
$body = array(
            
'data' => array(),
        );

        if ( 
$consent_to_sms ) {
            
array_push(
                
$body['data'],
                array(
                    
'customer'     => array(
                        
'email'   => $email,
                        
'country' => $country,
                        
'phone'   => $phone,
                    ),
                    
'consent'      => true,
                    
'updated_at'   => gmdateDATE_ATOMdate_timestamp_getdate_create() ) ),
                    
'consent_type' => 'sms',
                    
'group_id'     => $this->settings['klaviyo_sms_list_id'],
                )
            );
        }

        if ( 
$consent_to_newsletter ) {
            
array_push(
                
$body['data'],
                array(
                    
'customer'     => array(
                        
'email' => $email,
                        
'phone' => $phone,
                    ),
                    
'consent'      => true,
                    
'updated_at'   => gmdateDATE_ATOMdate_timestamp_getdate_create() ) ),
                    
'consent_type' => 'email',
                    
'group_id'     => $this->settings['klaviyo_newsletter_list_id'],
                )
            );
        }

        
wp_remote_post(
            
$url,
            array(
                
'method'      => 'POST',
                
'httpversion' => '1.0',
                
'blocking'    => false,
                
'headers'     => array(
                    
'X-WC-Webhook-Topic' => 'custom/consent',
                    
'Content-Type'       => 'application/json',
                ),
                
'body'        => wp_json_encode$body ),
                
'data_format' => 'body',
            )
        );
    }

    
/**
     * Validate if the value is a boolean or null. Make sure we accept null as false.
     *
     * @return array Array of options for the argument. See https://developer.wordpress.org/reference/functions/register_rest_route/#arguments
     * @since 0.1.0
     */
    
protected function optional_boolean_arg_options() {
        return array(
            
'validate_callback' => function ( $value ) {
                if ( ! 
is_null$value ) && ! is_bool$value ) ) {
                    return new 
\WP_Error'api-error''value of type ' gettype$value ) . ' was posted to the klaviyo opt-in callback' );
                }
                return 
true;
            },
            
'sanitize_callback' => function ( $value ) {
                if ( 
is_bool$value ) ) {
                    return 
$value;
                }
                return 
false;
            },
        );
    }
}