From 6899d85685c0829a48f42698b679b08d165af1b4 Mon Sep 17 00:00:00 2001 From: Jordan Leslie Date: Fri, 15 Sep 2023 15:08:41 -0500 Subject: [PATCH 01/29] WIP --- Api/KlaviyoApiWrapper.php | 65 ++ Helper/Data.php | 24 +- KlaviyoV3/Exception/KlaviyoApiException.php | 7 + .../KlaviyoAuthenticationException.php | 7 + KlaviyoV3/Exception/KlaviyoException.php | 7 + .../Exception/KlaviyoRateLimitException.php | 7 + .../KlaviyoResourceNotFoundException.php | 7 + KlaviyoV3/KlaviyoV3.php | 49 ++ KlaviyoV3/KlaviyoV3Api.php | 623 ++++++++++++++++++ 9 files changed, 773 insertions(+), 23 deletions(-) create mode 100644 Api/KlaviyoApiWrapper.php create mode 100644 KlaviyoV3/Exception/KlaviyoApiException.php create mode 100644 KlaviyoV3/Exception/KlaviyoAuthenticationException.php create mode 100644 KlaviyoV3/Exception/KlaviyoException.php create mode 100644 KlaviyoV3/Exception/KlaviyoRateLimitException.php create mode 100644 KlaviyoV3/Exception/KlaviyoResourceNotFoundException.php create mode 100644 KlaviyoV3/KlaviyoV3.php create mode 100644 KlaviyoV3/KlaviyoV3Api.php diff --git a/Api/KlaviyoApiWrapper.php b/Api/KlaviyoApiWrapper.php new file mode 100644 index 0000000..cc1815e --- /dev/null +++ b/Api/KlaviyoApiWrapper.php @@ -0,0 +1,65 @@ +use Configuration; +use KlaviyoV3Sdk\Exception\KlaviyoApiException; +use KlaviyoV3Sdk\Klaviyo; +use KlaviyoV3Sdk\KlaviyoV3Api; +use KlaviyoV3Sdk\Exception; + +class KlaviyoApiWrapper +{ + /** @var Klaviyo Client for Klaviyo's Api. */ + protected $client; + + public function __construct() + { + $this->client = new KlaviyoV3Api(Configuration::get('KLAVIYO_PRIVATE_API'), Configuration::get('KLAVIYO_PUBLIC_API')); + } + + /** + * Get all lists for specific Klaviyo account. + * + * @return mixed + */ + public function getLists() + { + return $this->client->getLists(); + } + + /** + * Subscribe email to the Subscriber List selected on configuration page (if selected). + * + * @param string $email + * @throws KlaviyoApiException + */ + public function subscribeCustomer($email, $customProperties = []) + { + $profile = array( + 'type' => 'profile', + 'attributes' => array( + 'email' => $email, + 'subscriptions' => array( + 'email' => [ + 'MARKETING' + ] + ) + ) + ); + + $listId = Configuration::get('KLAVIYO_SUBSCRIBER_LIST'); + + if ($listId) { + $this->client->subscribeMembersToList($listId, array($profile)); + } + } + + /** + * Send event to Klaviyo using the Track endpoint. + * + * @param array $event + * @return bool + * @throws KlaviyoApiException + */ + public function trackEvent(array $eventConfig) + { + return (bool) $this->client->track($eventConfig); + } +} \ No newline at end of file diff --git a/Helper/Data.php b/Helper/Data.php index fed0076..fad8347 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -5,6 +5,7 @@ use Klaviyo\Reclaim\Helper\ScopeSetting; use Magento\Framework\App\Helper\Context; use Klaviyo\Reclaim\Helper\Logger; +use KlaviyoV3Sdk\KlaviyoV3Api; class Data extends \Magento\Framework\App\Helper\AbstractHelper { @@ -184,29 +185,6 @@ public function klaviyoTrackEvent($event, $customer_properties = [], $properties return $this->make_request('api/track', $params); } - protected function make_request($path, $params) - { - $url = self::KLAVIYO_HOST . $path; - - $dataString = json_encode($params); - $options = array( - 'http' => array( - 'header' => "Content-type: application/json\r\n", - 'method' => 'POST', - 'content' => $dataString, - ), - ); - - $context = stream_context_create($options); - $response = file_get_contents($url, false, $context); - - if ($response == '0') { - $this->_klaviyoLogger->log("Unable to send event to Track API with data: $dataString"); - } - - return $response == '1'; - } - /** * @param string $path * @param array $params diff --git a/KlaviyoV3/Exception/KlaviyoApiException.php b/KlaviyoV3/Exception/KlaviyoApiException.php new file mode 100644 index 0000000..46b6f86 --- /dev/null +++ b/KlaviyoV3/Exception/KlaviyoApiException.php @@ -0,0 +1,7 @@ +private_key = $private_key; + $this->public_key = $public_key; + } + + /** + * @return string + */ + public function getPrivateKey(): string + { + return $this->private_key; + } + + /** + * @return string + */ + public function getPublicKey(): string + { + return $this->public_key; + } +} \ No newline at end of file diff --git a/KlaviyoV3/KlaviyoV3Api.php b/KlaviyoV3/KlaviyoV3Api.php new file mode 100644 index 0000000..9ff9adc --- /dev/null +++ b/KlaviyoV3/KlaviyoV3Api.php @@ -0,0 +1,623 @@ +public_key = $public_key; + $this->private_key = $private_key; + } + + /** + * Build headers for the Klaviyo all event + * + * @param $clientEvent + * @return array|array[] + */ + public function getHeaders($clientEvent) + { + $klaviyops = new KlaviyoPs(); + + $headers = array( + CURLOPT_HTTPHEADER => [ + self::REVISION_KEY_HEADER . ': ' . self::KLAVIYO_V3_REVISION, + self::ACCEPT_KEY_HEADER . ': ' . self::APPLICATION_JSON_HEADER_VALUE, + self::KLAVIYO_USER_AGENT_KEY . ': ' . 'magento2-klaviyo/' . $klaviyops->version . 'Magento2/' . _PS_VERSION_ . 'PHP/' . phpversion() + ] + ); + + $headers[CURLOPT_HTTPHEADER][] = $clientEvent ? self::CONTENT_TYPE_KEY_HEADER . ' ' . self::APPLICATION_JSON_HEADER_VALUE : self::AUTHORIZATION_KEY_HEADER . ': ' . self::KLAVIYO_API_KEY . ' ' . $this->private_key; + + return $headers; + } + + + /** + * Query for all available lists in Klaviyo + * + * @return array + */ + public function getLists() + { + return $this->make_request('api/lists/', false); + } + + /** + * Record an event for a customer on their Klaviyo profile + * https://developers.klaviyo.com/en/reference/create_client_event + * + * @param $config + * @return array + */ + public function track($config) + { + $body = array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::EVENT_VALUE_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => + $this->buildEventProperties($config['properties'], $config['time'], $config['metric']) + + $this->buildCustomerProperties($config['customer_properties']) + ) + ); + + return $this->make_request('/client/events/?company_id=' . $this->public_key, true, $body); + } + + /** + * Subscribe members to a Klaviyo list + * https://developers.klaviyo.com/en/reference/create_list_relationships + * + * @param $listId + * @param $profiles + * @return array + */ + public function subscribeMembersToList($listId, $profiles) + { + $body = array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::PROFILE_SUBSCRIPTION_BULK_CREATE_JOB_PAYLOAD_KEY, + self::ATTRIBUTE_KEY_PAYLOAD => array( + self::CUSTOM_SOURCE_PAYLOAD_KEY => 'Prestashop', + self::PROFILES_PAYLOAD_KEY => $profiles, + self::RELATIONSHIPS_PAYLOAD_KEY => array( + self::LIST_PAYLOAD_KEY => array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::LIST_PAYLOAD_KEY, + self::ID_KEY_PAYLOAD => $listId + ) + ) + ) + ) + ) + ); + + return $this->make_request('/api/profile-subscription-bulk-create-jobs/', false, $body); + } + + /** + * Request method used by all API methods to make calls + * + * @param $path + * @param $clientEvent + * @param $body + * @return array + */ + protected function make_request($path, $clientEvent, $body = null) + { + $curl = curl_init(); + $options = array( + CURLOPT_URL => self::BASE_URL . $path, + ) + $this->getHeaders($clientEvent) + $this->getDefaultCurlOptions(); + + if ($body !== null) { + $options[CURLOPT_POSTFIELDS][] = $body; + } + + curl_setopt_array($curl, $options); + + $response = curl_exec($curl); + $phpVersionHttpCode = version_compare(phpversion(), '5.5.0', '>') ? CURLINFO_RESPONSE_CODE : CURLINFO_HTTP_CODE; + $statusCode = curl_getinfo($curl, $phpVersionHttpCode); + curl_close($curl); + + return $this->handleAPIpublic_key = $public_key; + $this->private_key = $private_key; + } + + /** + * Build headers for the Klaviyo all event + * + * @param $clientEvent + * @return array|array[] + */ + public function getHeaders($clientEvent) + { + $klaviyops = new KlaviyoPs(); + + $headers = array( + CURLOPT_HTTPHEADER => [ + self::REVISION_KEY_HEADER . ': ' . self::KLAVIYO_V3_REVISION, + self::ACCEPT_KEY_HEADER . ': ' . self::APPLICATION_JSON_HEADER_VALUE, + self::KLAVIYO_USER_AGENT_KEY . ': ' . 'magento2-klaviyo/' . $klaviyops->version . 'Magento2/' . _PS_VERSION_ . 'PHP/' . phpversion() + ] + ); + + $headers[CURLOPT_HTTPHEADER][] = $clientEvent ? self::CONTENT_TYPE_KEY_HEADER . ' ' . self::APPLICATION_JSON_HEADER_VALUE : self::AUTHORIZATION_KEY_HEADER . ': ' . self::KLAVIYO_API_KEY . ' ' . $this->private_key; + + return $headers; + } + + + /** + * Query for all available lists in Klaviyo + * + * @return array + */ + public function getLists() + { + return $this->make_request('api/lists/', false); + } + + /** + * Record an event for a customer on their Klaviyo profile + * https://developers.klaviyo.com/en/reference/create_client_event + * + * @param $config + * @return array + */ + public function track($config) + { + $body = array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::EVENT_VALUE_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => + $this->buildEventProperties($config['properties'], $config['time'], $config['metric']) + + $this->buildCustomerProperties($config['customer_properties']) + ) + ); + + return $this->make_request('/client/events/?company_id=' . $this->public_key, true, $body); + } + + /** + * Subscribe members to a Klaviyo list + * https://developers.klaviyo.com/en/reference/create_list_relationships + * + * @param $listId + * @param $profiles + * @return array + */ + public function subscribeMembersToList($listId, $profiles) + { + $body = array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::PROFILE_SUBSCRIPTION_BULK_CREATE_JOB_PAYLOAD_KEY, + self::ATTRIBUTE_KEY_PAYLOAD => array( + self::CUSTOM_SOURCE_PAYLOAD_KEY => 'Prestashop', + self::PROFILES_PAYLOAD_KEY => $profiles, + self::RELATIONSHIPS_PAYLOAD_KEY => array( + self::LIST_PAYLOAD_KEY => array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::LIST_PAYLOAD_KEY, + self::ID_KEY_PAYLOAD => $listId + ) + ) + ) + ) + ) + ); + + return $this->make_request('/api/profile-subscription-bulk-create-jobs/', false, $body); + } + + /** + * Request method used by all API methods to make calls + * + * @param $path + * @param $clientEvent + * @param $body + * @return array + */ + protected function make_request($path, $clientEvent, $body = null) + { + $url = self::KLAVIYO_HOST . $path; + + $options = array( + 'http' => array( + 'header' => "Content-type: application/json\r\n", + 'method' => 'POST', + 'content' => $dataString, + ), + ); + + $options = array( + CURLOPT_URL => self::BASE_URL . $path, + ) + $this->getHeaders($clientEvent) + $this->getDefaultCurlOptions(); + + $context = stream_context_create($options); + $response = file_get_contents($url, false, $context); + + if ($response == '0') { + $this->_klaviyoLogger->log("Unable to send event to Track API with data: $dataString"); + } + + return $response == '1'; + } + + /** + * Build Event Properties for the Client/Events endpoint + * + * @param $eventProperties + * @param $time + * @param $metric + * @return array + */ + public function buildEventProperties($eventProperties, $time, $metric): array + { + $event_time = new DateTime(); + $event_time->setTimestamp($time ?: time()); + + return array( + self::PROPERTIES_KEY_PAYLOAD => $eventProperties, + self::TIME_KEY_PAYLOAD => $event_time, + self::VALUE_KEY_PAYLOAD => $eventProperties[self::VALUE_KEY_PAYLOAD], + self::METRIC_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::METRIC_KEY_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => array( + self::NAME_KEY_PAYLOAD => $metric + ) + )); + } + + /** + * Build customer properties for the Client/Events endpoint + * + * @param $customerProperties + * @return \array[][] + */ + public function buildCustomerProperties($customerProperties): array + { + $kl_properties = array( + 'email' => $customerProperties['$email'], + 'first_name' => $customerProperties['firstname'], + 'last_name' => $customerProperties['lastname'] + ); + + unset($customerProperties['email']); + unset($customerProperties['firstname']); + unset($customerProperties['lastname']); + + return array( + self::PROFILE_KEY_PAYLOAD => array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::PROFILE_KEY_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => $kl_properties, + self::PROPERTIES => $customerProperties, + ) + ) + ); + } + + /** + * Get base options array for curl request. + * + * @return array + */ + #[\ReturnTypeWillChange] + protected function getDefaultCurlOptions() + { + return array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_CUSTOMREQUEST => self::HTTP_POST, + ); + } + + /** + * Handle the API response and return the parsed data. + * + * @param string $response The raw API response. + * @param int $statusCode The HTTP status code of the response. + * @param bool $clientEvent + * @return array| string |null An array containing the parsed data or null on error. + * @throws KlaviyoApiException + * @throws KlaviyoAuthenticationException + * @throws KlaviyoRateLimitException + */ + protected function handleAPIResponse($response, $statusCode, $clientEvent = false) + { + $decoded_response = $this->decodeJsonResponse($response); + if ($statusCode == 403) { + throw new KlaviyoAuthenticationException(self::ERROR_INVALID_API_KEY, $statusCode); + } + + if ($statusCode == 401) { + throw new KlaviyoAuthenticationException(self::ERROR_EXPIRED_API_KEY, $statusCode); + } + + if ($status_code === 429) { + throw new KlaviyoAuthenticationException(self::ERROR_UNVERIFIABLE_API_KEY, $statusCode); + } + + if ($statusCode == 429) { + throw new KlaviyoRateLimitException( + $this->returnRateLimit($decoded_response) + ); + } + + if ($statusCode < 200 || $statusCode >= 300) { + throw new KlaviyoApiException(isset($decoded_response['detail']) ? $decoded_response['detail'] : sprintf(self::ERROR_NON_200_STATUS, $statusCode), $statusCode); + } + + if ($clientEvent) { + return $response; + } + + return $decoded_response; + } + + /** + * Return decoded JSON response as associative or empty array. + * Certain Klaviyo endpoints (such as Delete) return an empty string on success + * and so PHP versions >= 7 will throw a JSON_ERROR_SYNTAX when trying to decode it + * + * @param string $response + * @return mixed + */ + private function decodeJsonResponse($response) + { + if (!empty($response)) { + return json_decode($response, true); + } + return json_decode('{}', true); + } +}Response($response, $statusCode, $clientEvent); + } + + /** + * Build Event Properties for the Client/Events endpoint + * + * @param $eventProperties + * @param $time + * @param $metric + * @return array + */ + public function buildEventProperties($eventProperties, $time, $metric): array + { + $event_time = new DateTime(); + $event_time->setTimestamp($time ?: time()); + + return array( + self::PROPERTIES_KEY_PAYLOAD => $eventProperties, + self::TIME_KEY_PAYLOAD => $event_time, + self::VALUE_KEY_PAYLOAD => $eventProperties[self::VALUE_KEY_PAYLOAD], + self::METRIC_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::METRIC_KEY_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => array( + self::NAME_KEY_PAYLOAD => $metric + ) + )); + } + + /** + * Build customer properties for the Client/Events endpoint + * + * @param $customerProperties + * @return \array[][] + */ + public function buildCustomerProperties($customerProperties): array + { + $kl_properties = array( + 'email' => $customerProperties['$email'], + 'first_name' => $customerProperties['firstname'], + 'last_name' => $customerProperties['lastname'] + ); + + unset($customerProperties['email']); + unset($customerProperties['firstname']); + unset($customerProperties['lastname']); + + return array( + self::PROFILE_KEY_PAYLOAD => array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::PROFILE_KEY_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => $kl_properties, + self::PROPERTIES => $customerProperties, + ) + ) + ); + } + + /** + * Get base options array for curl request. + * + * @return array + */ + #[\ReturnTypeWillChange] + protected function getDefaultCurlOptions() + { + return array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_CUSTOMREQUEST => self::HTTP_POST, + ); + } + + /** + * Return decoded JSON response as associative or empty array. + * Certain Klaviyo endpoints (such as Delete) return an empty string on success + * and so PHP versions >= 7 will throw a JSON_ERROR_SYNTAX when trying to decode it + * + * @param string $response + * @return mixed + */ + private function decodeJsonResponse($response) + { + if (!empty($response)) { + return json_decode($response, true); + } + return json_decode('{}', true); + } +} \ No newline at end of file From 92375ee6fd818e8a5c09a0e1cf74916063c12070 Mon Sep 17 00:00:00 2001 From: Jordan Leslie Date: Mon, 18 Sep 2023 14:58:47 -0500 Subject: [PATCH 02/29] WIP --- Helper/Data.php | 85 +-- Helper/ScopeSetting.php | 4 +- KlaviyoV3/KlaviyoV3Api.php | 623 ------------------ .../Exception/KlaviyoApiException.php | 0 .../KlaviyoAuthenticationException.php | 2 +- .../Exception/KlaviyoException.php | 0 .../Exception/KlaviyoRateLimitException.php | 0 .../KlaviyoResourceNotFoundException.php | 0 KlaviyoV3Sdk/Exception/index.php | 9 + {KlaviyoV3 => KlaviyoV3Sdk}/KlaviyoV3.php | 0 KlaviyoV3Sdk/KlaviyoV3Api.php | 356 ++++++++++ 11 files changed, 387 insertions(+), 692 deletions(-) delete mode 100644 KlaviyoV3/KlaviyoV3Api.php rename {KlaviyoV3 => KlaviyoV3Sdk}/Exception/KlaviyoApiException.php (100%) rename KlaviyoV3/Exception/KlaviyoAuthenticationException.php => KlaviyoV3Sdk/Exception/KlaviyoAuthenticationException.php (63%) rename {KlaviyoV3 => KlaviyoV3Sdk}/Exception/KlaviyoException.php (100%) rename {KlaviyoV3 => KlaviyoV3Sdk}/Exception/KlaviyoRateLimitException.php (100%) rename KlaviyoV3/Exception/KlaviyoResourceNotFoundException.php => KlaviyoV3Sdk/Exception/KlaviyoResourceNotFoundException.php (100%) create mode 100644 KlaviyoV3Sdk/Exception/index.php rename {KlaviyoV3 => KlaviyoV3Sdk}/KlaviyoV3.php (100%) create mode 100644 KlaviyoV3Sdk/KlaviyoV3Api.php diff --git a/Helper/Data.php b/Helper/Data.php index fad8347..fe4c303 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -7,11 +7,11 @@ use Klaviyo\Reclaim\Helper\Logger; use KlaviyoV3Sdk\KlaviyoV3Api; -class Data extends \Magento\Framework\App\Helper\AbstractHelper +class Data extends KlaviyoV3Api { const USER_AGENT = 'Klaviyo/1.0'; const KLAVIYO_HOST = 'https://a.klaviyo.com/'; - const LIST_V2_API = 'api/v2/list/'; + const LIST_V3_API = 'api/list'; /** * Klaviyo logger helper @@ -62,9 +62,9 @@ public function getKlaviyoLists($api_key = null) if (!$api_key) { $api_key = $this->_klaviyoScopeSetting->getPrivateApiKey(); } - + $v3_list_api = self::KLAVIYO_HOST . self::LIST_V3_API . '?api_key=' . $api_key; $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, 'https://a.klaviyo.com/api/v2/lists?api_key=' . $api_key); + curl_setopt($ch, CURLOPT_URL, $v3_list_api); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); @@ -74,11 +74,11 @@ public function getKlaviyoLists($api_key = null) if ($statusCode !== 200) { if ($statusCode === 403) { - $reason = 'The Private Klaviyo API Key you have set is invalid.'; + $reason = self::ERROR_INVALID_API_KEY; } elseif ($statusCode === 401) { - $reason = 'The Private Klaviyo API key you have set is no longer valid.'; + $reason = self::ERROR_INVALID_API_KEY; } else { - $reason = 'Unable to verify Klaviyo Private API Key.'; + $reason = self::ERROR_NON_200_STATUS; } $result = [ @@ -104,7 +104,7 @@ public function getKlaviyoLists($api_key = null) * @param string|null $firstName * @param string|null $lastName * @param string|null $source - * @return bool|string + * @return array|false|null|string */ public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName = null, $source = null) { @@ -128,10 +128,13 @@ public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName $propertiesVal = ['profiles' => $properties]; - $path = self::LIST_V2_API . $listId . $optInSetting; - + if ($optInSetting == ScopeSetting::API_SUBSCRIBE) { + $path = self::LIST_V3_API . $listId . $optInSetting; + } else { + $path = self::LIST_V3_API . $optInSetting; + } try { - $response = $this->sendApiRequest($path, $propertiesVal, 'POST'); + $response = $this->subscribeMembersToList($path, $listId, $propertiesVal); } catch (\Exception $e) { $this->_klaviyoLogger->log(sprintf('Unable to subscribe %s to list %s: %s', $email, $listId, $e)); $response = false; @@ -142,19 +145,13 @@ public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName /** * @param string $email - * @return bool|string + * @return array|string|null */ - public function unsubscribeEmailFromKlaviyoList($email) + public function unsubscribeEmailFromKlaviyoList($email): array|string|null { $listId = $this->_klaviyoScopeSetting->getNewsletter(); - - $path = self::LIST_V2_API . $listId . ScopeSetting::API_SUBSCRIBE; - $fields = [ - 'emails' => [(string)$email], - ]; - try { - $response = $this->sendApiRequest($path, $fields, 'DELETE'); + $response = $this->unsubscribeEmailFromKlaviyoList($email); } catch (\Exception $e) { $this->_klaviyoLogger->log(sprintf('Unable to unsubscribe %s from list %s: %s', $email, $listId, $e)); $response = false; @@ -173,8 +170,7 @@ public function klaviyoTrackEvent($event, $customer_properties = [], $properties return 'You must identify a user by email or ID.'; } $params = array( - 'token' => $this->_klaviyoScopeSetting->getPublicApiKey($storeId), - 'event' => $event, + 'metric' => $event, 'properties' => $properties, 'customer_properties' => $customer_properties ); @@ -182,49 +178,6 @@ public function klaviyoTrackEvent($event, $customer_properties = [], $properties if (!is_null($timestamp)) { $params['time'] = $timestamp; } - return $this->make_request('api/track', $params); - } - - /** - * @param string $path - * @param array $params - * @param string $method - * @return mixed[] - * @throws \Exception - */ - private function sendApiRequest(string $path, array $params, string $method = null) - { - $url = self::KLAVIYO_HOST . $path; - - //Add API Key to params - $params['api_key'] = $this->_klaviyoScopeSetting->getPrivateApiKey(); - - $curl = curl_init(); - $encodedParams = json_encode($params); - - curl_setopt_array($curl, [ - CURLOPT_URL => $url, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_CUSTOMREQUEST => (!empty($method)) ? $method : 'POST', - CURLOPT_POSTFIELDS => $encodedParams, - CURLOPT_USERAGENT => self::USER_AGENT, - CURLOPT_HTTPHEADER => [ - 'Content-Type: application/json', - 'Content-Length: ' . strlen($encodedParams) - ], - ]); - - // Submit the request - $response = curl_exec($curl); - $err = curl_errno($curl); - - if ($err) { - throw new \Exception(curl_error($curl)); - } - - // Close cURL session handle - curl_close($curl); - - return $response; + return $this->track($params); } } diff --git a/Helper/ScopeSetting.php b/Helper/ScopeSetting.php index 20f24be..ed36d05 100755 --- a/Helper/ScopeSetting.php +++ b/Helper/ScopeSetting.php @@ -13,8 +13,8 @@ class ScopeSetting extends \Magento\Framework\App\Helper\AbstractHelper const NEWSLETTER = 'klaviyo_reclaim_newsletter/newsletter/newsletter'; const USING_KLAVIYO_LIST_OPT_IN = 'klaviyo_reclaim_newsletter/newsletter/using_klaviyo_list_opt_in'; - const API_MEMBERS = '/members'; - const API_SUBSCRIBE = '/subscribe'; + const API_MEMBERS = '/relationships/profiles/'; + const API_SUBSCRIBE = '/profile-subscription-bulk-create-jobs'; const CONSENT_AT_CHECKOUT_EMAIL_IS_ACTIVE = 'klaviyo_reclaim_consent_at_checkout/email_consent/is_active'; const CONSENT_AT_CHECKOUT_EMAIL_LIST_ID = 'klaviyo_reclaim_consent_at_checkout/email_consent/list_id'; diff --git a/KlaviyoV3/KlaviyoV3Api.php b/KlaviyoV3/KlaviyoV3Api.php deleted file mode 100644 index 9ff9adc..0000000 --- a/KlaviyoV3/KlaviyoV3Api.php +++ /dev/null @@ -1,623 +0,0 @@ -public_key = $public_key; - $this->private_key = $private_key; - } - - /** - * Build headers for the Klaviyo all event - * - * @param $clientEvent - * @return array|array[] - */ - public function getHeaders($clientEvent) - { - $klaviyops = new KlaviyoPs(); - - $headers = array( - CURLOPT_HTTPHEADER => [ - self::REVISION_KEY_HEADER . ': ' . self::KLAVIYO_V3_REVISION, - self::ACCEPT_KEY_HEADER . ': ' . self::APPLICATION_JSON_HEADER_VALUE, - self::KLAVIYO_USER_AGENT_KEY . ': ' . 'magento2-klaviyo/' . $klaviyops->version . 'Magento2/' . _PS_VERSION_ . 'PHP/' . phpversion() - ] - ); - - $headers[CURLOPT_HTTPHEADER][] = $clientEvent ? self::CONTENT_TYPE_KEY_HEADER . ' ' . self::APPLICATION_JSON_HEADER_VALUE : self::AUTHORIZATION_KEY_HEADER . ': ' . self::KLAVIYO_API_KEY . ' ' . $this->private_key; - - return $headers; - } - - - /** - * Query for all available lists in Klaviyo - * - * @return array - */ - public function getLists() - { - return $this->make_request('api/lists/', false); - } - - /** - * Record an event for a customer on their Klaviyo profile - * https://developers.klaviyo.com/en/reference/create_client_event - * - * @param $config - * @return array - */ - public function track($config) - { - $body = array( - self::DATA_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::EVENT_VALUE_PAYLOAD, - self::ATTRIBUTE_KEY_PAYLOAD => - $this->buildEventProperties($config['properties'], $config['time'], $config['metric']) + - $this->buildCustomerProperties($config['customer_properties']) - ) - ); - - return $this->make_request('/client/events/?company_id=' . $this->public_key, true, $body); - } - - /** - * Subscribe members to a Klaviyo list - * https://developers.klaviyo.com/en/reference/create_list_relationships - * - * @param $listId - * @param $profiles - * @return array - */ - public function subscribeMembersToList($listId, $profiles) - { - $body = array( - self::DATA_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::PROFILE_SUBSCRIPTION_BULK_CREATE_JOB_PAYLOAD_KEY, - self::ATTRIBUTE_KEY_PAYLOAD => array( - self::CUSTOM_SOURCE_PAYLOAD_KEY => 'Prestashop', - self::PROFILES_PAYLOAD_KEY => $profiles, - self::RELATIONSHIPS_PAYLOAD_KEY => array( - self::LIST_PAYLOAD_KEY => array( - self::DATA_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::LIST_PAYLOAD_KEY, - self::ID_KEY_PAYLOAD => $listId - ) - ) - ) - ) - ) - ); - - return $this->make_request('/api/profile-subscription-bulk-create-jobs/', false, $body); - } - - /** - * Request method used by all API methods to make calls - * - * @param $path - * @param $clientEvent - * @param $body - * @return array - */ - protected function make_request($path, $clientEvent, $body = null) - { - $curl = curl_init(); - $options = array( - CURLOPT_URL => self::BASE_URL . $path, - ) + $this->getHeaders($clientEvent) + $this->getDefaultCurlOptions(); - - if ($body !== null) { - $options[CURLOPT_POSTFIELDS][] = $body; - } - - curl_setopt_array($curl, $options); - - $response = curl_exec($curl); - $phpVersionHttpCode = version_compare(phpversion(), '5.5.0', '>') ? CURLINFO_RESPONSE_CODE : CURLINFO_HTTP_CODE; - $statusCode = curl_getinfo($curl, $phpVersionHttpCode); - curl_close($curl); - - return $this->handleAPIpublic_key = $public_key; - $this->private_key = $private_key; - } - - /** - * Build headers for the Klaviyo all event - * - * @param $clientEvent - * @return array|array[] - */ - public function getHeaders($clientEvent) - { - $klaviyops = new KlaviyoPs(); - - $headers = array( - CURLOPT_HTTPHEADER => [ - self::REVISION_KEY_HEADER . ': ' . self::KLAVIYO_V3_REVISION, - self::ACCEPT_KEY_HEADER . ': ' . self::APPLICATION_JSON_HEADER_VALUE, - self::KLAVIYO_USER_AGENT_KEY . ': ' . 'magento2-klaviyo/' . $klaviyops->version . 'Magento2/' . _PS_VERSION_ . 'PHP/' . phpversion() - ] - ); - - $headers[CURLOPT_HTTPHEADER][] = $clientEvent ? self::CONTENT_TYPE_KEY_HEADER . ' ' . self::APPLICATION_JSON_HEADER_VALUE : self::AUTHORIZATION_KEY_HEADER . ': ' . self::KLAVIYO_API_KEY . ' ' . $this->private_key; - - return $headers; - } - - - /** - * Query for all available lists in Klaviyo - * - * @return array - */ - public function getLists() - { - return $this->make_request('api/lists/', false); - } - - /** - * Record an event for a customer on their Klaviyo profile - * https://developers.klaviyo.com/en/reference/create_client_event - * - * @param $config - * @return array - */ - public function track($config) - { - $body = array( - self::DATA_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::EVENT_VALUE_PAYLOAD, - self::ATTRIBUTE_KEY_PAYLOAD => - $this->buildEventProperties($config['properties'], $config['time'], $config['metric']) + - $this->buildCustomerProperties($config['customer_properties']) - ) - ); - - return $this->make_request('/client/events/?company_id=' . $this->public_key, true, $body); - } - - /** - * Subscribe members to a Klaviyo list - * https://developers.klaviyo.com/en/reference/create_list_relationships - * - * @param $listId - * @param $profiles - * @return array - */ - public function subscribeMembersToList($listId, $profiles) - { - $body = array( - self::DATA_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::PROFILE_SUBSCRIPTION_BULK_CREATE_JOB_PAYLOAD_KEY, - self::ATTRIBUTE_KEY_PAYLOAD => array( - self::CUSTOM_SOURCE_PAYLOAD_KEY => 'Prestashop', - self::PROFILES_PAYLOAD_KEY => $profiles, - self::RELATIONSHIPS_PAYLOAD_KEY => array( - self::LIST_PAYLOAD_KEY => array( - self::DATA_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::LIST_PAYLOAD_KEY, - self::ID_KEY_PAYLOAD => $listId - ) - ) - ) - ) - ) - ); - - return $this->make_request('/api/profile-subscription-bulk-create-jobs/', false, $body); - } - - /** - * Request method used by all API methods to make calls - * - * @param $path - * @param $clientEvent - * @param $body - * @return array - */ - protected function make_request($path, $clientEvent, $body = null) - { - $url = self::KLAVIYO_HOST . $path; - - $options = array( - 'http' => array( - 'header' => "Content-type: application/json\r\n", - 'method' => 'POST', - 'content' => $dataString, - ), - ); - - $options = array( - CURLOPT_URL => self::BASE_URL . $path, - ) + $this->getHeaders($clientEvent) + $this->getDefaultCurlOptions(); - - $context = stream_context_create($options); - $response = file_get_contents($url, false, $context); - - if ($response == '0') { - $this->_klaviyoLogger->log("Unable to send event to Track API with data: $dataString"); - } - - return $response == '1'; - } - - /** - * Build Event Properties for the Client/Events endpoint - * - * @param $eventProperties - * @param $time - * @param $metric - * @return array - */ - public function buildEventProperties($eventProperties, $time, $metric): array - { - $event_time = new DateTime(); - $event_time->setTimestamp($time ?: time()); - - return array( - self::PROPERTIES_KEY_PAYLOAD => $eventProperties, - self::TIME_KEY_PAYLOAD => $event_time, - self::VALUE_KEY_PAYLOAD => $eventProperties[self::VALUE_KEY_PAYLOAD], - self::METRIC_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::METRIC_KEY_PAYLOAD, - self::ATTRIBUTE_KEY_PAYLOAD => array( - self::NAME_KEY_PAYLOAD => $metric - ) - )); - } - - /** - * Build customer properties for the Client/Events endpoint - * - * @param $customerProperties - * @return \array[][] - */ - public function buildCustomerProperties($customerProperties): array - { - $kl_properties = array( - 'email' => $customerProperties['$email'], - 'first_name' => $customerProperties['firstname'], - 'last_name' => $customerProperties['lastname'] - ); - - unset($customerProperties['email']); - unset($customerProperties['firstname']); - unset($customerProperties['lastname']); - - return array( - self::PROFILE_KEY_PAYLOAD => array( - self::DATA_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::PROFILE_KEY_PAYLOAD, - self::ATTRIBUTE_KEY_PAYLOAD => $kl_properties, - self::PROPERTIES => $customerProperties, - ) - ) - ); - } - - /** - * Get base options array for curl request. - * - * @return array - */ - #[\ReturnTypeWillChange] - protected function getDefaultCurlOptions() - { - return array( - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => "", - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 30, - CURLOPT_CUSTOMREQUEST => self::HTTP_POST, - ); - } - - /** - * Handle the API response and return the parsed data. - * - * @param string $response The raw API response. - * @param int $statusCode The HTTP status code of the response. - * @param bool $clientEvent - * @return array| string |null An array containing the parsed data or null on error. - * @throws KlaviyoApiException - * @throws KlaviyoAuthenticationException - * @throws KlaviyoRateLimitException - */ - protected function handleAPIResponse($response, $statusCode, $clientEvent = false) - { - $decoded_response = $this->decodeJsonResponse($response); - if ($statusCode == 403) { - throw new KlaviyoAuthenticationException(self::ERROR_INVALID_API_KEY, $statusCode); - } - - if ($statusCode == 401) { - throw new KlaviyoAuthenticationException(self::ERROR_EXPIRED_API_KEY, $statusCode); - } - - if ($status_code === 429) { - throw new KlaviyoAuthenticationException(self::ERROR_UNVERIFIABLE_API_KEY, $statusCode); - } - - if ($statusCode == 429) { - throw new KlaviyoRateLimitException( - $this->returnRateLimit($decoded_response) - ); - } - - if ($statusCode < 200 || $statusCode >= 300) { - throw new KlaviyoApiException(isset($decoded_response['detail']) ? $decoded_response['detail'] : sprintf(self::ERROR_NON_200_STATUS, $statusCode), $statusCode); - } - - if ($clientEvent) { - return $response; - } - - return $decoded_response; - } - - /** - * Return decoded JSON response as associative or empty array. - * Certain Klaviyo endpoints (such as Delete) return an empty string on success - * and so PHP versions >= 7 will throw a JSON_ERROR_SYNTAX when trying to decode it - * - * @param string $response - * @return mixed - */ - private function decodeJsonResponse($response) - { - if (!empty($response)) { - return json_decode($response, true); - } - return json_decode('{}', true); - } -}Response($response, $statusCode, $clientEvent); - } - - /** - * Build Event Properties for the Client/Events endpoint - * - * @param $eventProperties - * @param $time - * @param $metric - * @return array - */ - public function buildEventProperties($eventProperties, $time, $metric): array - { - $event_time = new DateTime(); - $event_time->setTimestamp($time ?: time()); - - return array( - self::PROPERTIES_KEY_PAYLOAD => $eventProperties, - self::TIME_KEY_PAYLOAD => $event_time, - self::VALUE_KEY_PAYLOAD => $eventProperties[self::VALUE_KEY_PAYLOAD], - self::METRIC_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::METRIC_KEY_PAYLOAD, - self::ATTRIBUTE_KEY_PAYLOAD => array( - self::NAME_KEY_PAYLOAD => $metric - ) - )); - } - - /** - * Build customer properties for the Client/Events endpoint - * - * @param $customerProperties - * @return \array[][] - */ - public function buildCustomerProperties($customerProperties): array - { - $kl_properties = array( - 'email' => $customerProperties['$email'], - 'first_name' => $customerProperties['firstname'], - 'last_name' => $customerProperties['lastname'] - ); - - unset($customerProperties['email']); - unset($customerProperties['firstname']); - unset($customerProperties['lastname']); - - return array( - self::PROFILE_KEY_PAYLOAD => array( - self::DATA_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::PROFILE_KEY_PAYLOAD, - self::ATTRIBUTE_KEY_PAYLOAD => $kl_properties, - self::PROPERTIES => $customerProperties, - ) - ) - ); - } - - /** - * Get base options array for curl request. - * - * @return array - */ - #[\ReturnTypeWillChange] - protected function getDefaultCurlOptions() - { - return array( - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => "", - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 30, - CURLOPT_CUSTOMREQUEST => self::HTTP_POST, - ); - } - - /** - * Return decoded JSON response as associative or empty array. - * Certain Klaviyo endpoints (such as Delete) return an empty string on success - * and so PHP versions >= 7 will throw a JSON_ERROR_SYNTAX when trying to decode it - * - * @param string $response - * @return mixed - */ - private function decodeJsonResponse($response) - { - if (!empty($response)) { - return json_decode($response, true); - } - return json_decode('{}', true); - } -} \ No newline at end of file diff --git a/KlaviyoV3/Exception/KlaviyoApiException.php b/KlaviyoV3Sdk/Exception/KlaviyoApiException.php similarity index 100% rename from KlaviyoV3/Exception/KlaviyoApiException.php rename to KlaviyoV3Sdk/Exception/KlaviyoApiException.php diff --git a/KlaviyoV3/Exception/KlaviyoAuthenticationException.php b/KlaviyoV3Sdk/Exception/KlaviyoAuthenticationException.php similarity index 63% rename from KlaviyoV3/Exception/KlaviyoAuthenticationException.php rename to KlaviyoV3Sdk/Exception/KlaviyoAuthenticationException.php index 2aeb2ad..cddf2f5 100644 --- a/KlaviyoV3/Exception/KlaviyoAuthenticationException.php +++ b/KlaviyoV3Sdk/Exception/KlaviyoAuthenticationException.php @@ -1,6 +1,6 @@ public_key = $public_key; + $this->private_key = $private_key; + } + + /** + * Build headers for the Klaviyo all event + * + * @param $clientEvent + * @return array|array[] + */ + public function getHeaders($clientEvent) + { + $klaviyops = new KlaviyoPs(); + + $headers = array( + CURLOPT_HTTPHEADER => [ + self::REVISION_KEY_HEADER . ': ' . self::KLAVIYO_V3_REVISION, + self::ACCEPT_KEY_HEADER . ': ' . self::APPLICATION_JSON_HEADER_VALUE, + self::KLAVIYO_USER_AGENT_KEY . ': ' . 'prestashop-klaviyo/' . $klaviyops->version . 'Prestashop/' . _PS_VERSION_ . 'PHP/' . phpversion() + ] + ); + + $headers[CURLOPT_HTTPHEADER][] = $clientEvent ? self::CONTENT_TYPE_KEY_HEADER . ' ' . self::APPLICATION_JSON_HEADER_VALUE : self::AUTHORIZATION_KEY_HEADER . ': ' . self::KLAVIYO_API_KEY . ' ' . $this->private_key; + + return $headers; + } + + + /** + * Query for all available lists in Klaviyo + * + * @return array + */ + public function getLists($api_key) + { + + } + + /** + * Record an event for a customer on their Klaviyo profile + * https://developers.klaviyo.com/en/reference/create_client_event + * + * @param $config + * @return array + */ + public function track($config): array + { + $body = array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::EVENT_VALUE_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => + $this->buildEventProperties($config['properties'], $config['time'], $config['metric']) + + $this->buildCustomerProperties($config['customer_properties']) + ) + ); + + return $this->make_request('/client/events/?company_id=' . $this->public_key, true, $body); + } + + /** + * Subscribe members to a Klaviyo list + * https://developers.klaviyo.com/en/reference/create_list_relationships + * + * @param $listId + * @param $profiles + * @return array + */ + public function subscribeMembersToList($path, $listId, $profiles) + { + $body = array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::PROFILE_SUBSCRIPTION_BULK_CREATE_JOB_PAYLOAD_KEY, + self::ATTRIBUTE_KEY_PAYLOAD => array( + self::CUSTOM_SOURCE_PAYLOAD_KEY => 'Magento 2', + self::PROFILES_PAYLOAD_KEY => $profiles, + self::RELATIONSHIPS_PAYLOAD_KEY => array( + self::LIST_PAYLOAD_KEY => array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::LIST_PAYLOAD_KEY, + self::ID_KEY_PAYLOAD => $listId + ) + ) + ) + ) + ) + ); + + return $this->sendApiRequest($path, false, $body); + } + + + /** + * @param string $email + * @return array|null|string + */ + public function unsubscribeEmailFromKlaviyoList($email): array|string|null + { + $path = self::LIST_V3_API . ScopeSetting::API_SUBSCRIBE; + $fields = [ + 'emails' => [(string)$email], + ]; + + return $this->sendApiRequest($path, $fields, 'POST'); + } + + /** + * Request method used by all API methods to make calls + * + * @param $path + * @param $clientEvent + * @param $method + * @param $body + * @return array + */ + + protected function sendApiRequest($path, $clientEvent, $method = null, $body = null) + { + $url = self::KLAVIYO_HOST . $path; + + //Add API Key to params + $params['api_key'] = $this->_klaviyoScopeSetting->getPrivateApiKey(); + + $curl = curl_init(); + $options = array( + CURLOPT_URL => self::KLAVIYO_HOST . $path, + ) + $this->getHeaders($clientEvent) + $this->getDefaultCurlOptions(); + + if ($body !== null) { + $options[CURLOPT_POSTFIELDS][] = $body; + } + + curl_setopt_array($curl, $options); + + $response = curl_exec($curl); + $phpVersionHttpCode = version_compare(phpversion(), '5.5.0', '>') ? CURLINFO_RESPONSE_CODE : CURLINFO_HTTP_CODE; + $statusCode = curl_getinfo($curl, $phpVersionHttpCode); + curl_close($curl); + + return $this->handleAPIResponse($response, $statusCode, $clientEvent); + } + + /** + * Build Event Properties for the Client/Events endpoint + * + * @param $eventProperties + * @param $time + * @param $metric + * @return array + */ + public function buildEventProperties($eventProperties, $time, $metric): array + { + $event_time = new DateTime(); + $event_time->setTimestamp($time ?: time()); + + return array( + self::PROPERTIES_KEY_PAYLOAD => $eventProperties, + self::TIME_KEY_PAYLOAD => $event_time, + self::VALUE_KEY_PAYLOAD => $eventProperties[self::VALUE_KEY_PAYLOAD], + self::METRIC_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::METRIC_KEY_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => array( + self::NAME_KEY_PAYLOAD => $metric + ) + )); + } + + /** + * Build customer properties for the Client/Events endpoint + * + * @param $customerProperties + * @return \array[][] + */ + public function buildCustomerProperties($customerProperties): array + { + $kl_properties = array( + 'email' => $customerProperties['$email'], + 'first_name' => $customerProperties['firstname'], + 'last_name' => $customerProperties['lastname'] + ); + + unset($customerProperties['email']); + unset($customerProperties['firstname']); + unset($customerProperties['lastname']); + + return array( + self::PROFILE_KEY_PAYLOAD => array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::PROFILE_KEY_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => $kl_properties, + self::PROPERTIES => $customerProperties, + ) + ) + ); + } + + /** + * Get base options array for curl request. + * + * @return array + */ + #[\ReturnTypeWillChange] + protected function getDefaultCurlOptions($method = null) + { + return array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_CUSTOMREQUEST => (!empty($method)) ? $method : 'POST',, + CURLOPT_USERAGENT => self::USER_AGENT, + ); + } + + /** + * Handle the API response and return the parsed data. + * + * @param string $response The raw API response. + * @param int $statusCode The HTTP status code of the response. + * @param bool $clientEvent + * @return array| string |null An array containing the parsed data or null on error. + * @throws KlaviyoApiException + * @throws KlaviyoAuthenticationException + * @throws KlaviyoRateLimitException + */ + protected + function handleAPIResponse($response, $statusCode, $clientEvent = false) + { + $decoded_response = $this->decodeJsonResponse($response); + if ($statusCode == 403) { + throw new KlaviyoAuthenticationException(self::ERROR_INVALID_API_KEY, $statusCode); + } + + if ($statusCode == 401) { + throw new KlaviyoAuthenticationException(self::ERROR_EXPIRED_API_KEY, $statusCode); + } + + if ($statusCode === 429) { + throw new KlaviyoAuthenticationException(self::ERROR_UNVERIFIABLE_API_KEY, $statusCode); + } + + if ($statusCode == 429) { + throw new KlaviyoRateLimitException( + $this->returnRateLimit($decoded_response) + ); + } + + if ($statusCode < 200 || $statusCode >= 300) { + throw new KlaviyoApiException(isset($decoded_response['detail']) ? $decoded_response['detail'] : sprintf(self::ERROR_NON_200_STATUS, $statusCode), $statusCode); + } + + if ($clientEvent) { + return $response; + } + + return $decoded_response; + } + + /** + * Return decoded JSON response as associative or empty array. + * Certain Klaviyo endpoints (such as Delete) return an empty string on success + * and so PHP versions >= 7 will throw a JSON_ERROR_SYNTAX when trying to decode it + * + * @param string $response + * @return mixed + */ + private function decodeJsonResponse($response) + { + if (!empty($response)) { + return json_decode($response, true); + } + return json_decode('{}', true); + } +} \ No newline at end of file From bac8e6d8bb355cf42f9116eed866ef81d875a73d Mon Sep 17 00:00:00 2001 From: Jordan Leslie Date: Tue, 19 Sep 2023 14:52:42 -0500 Subject: [PATCH 03/29] Move V3 logic to api wrapper --- Helper/Data.php | 46 +++---------- .../Exception/KlaviyoApiException.php | 2 +- .../KlaviyoAuthenticationException.php | 2 +- KlaviyoV3Sdk/Exception/KlaviyoException.php | 4 +- .../Exception/KlaviyoRateLimitException.php | 2 +- .../KlaviyoResourceNotFoundException.php | 2 +- KlaviyoV3Sdk/KlaviyoV3Api.php | 69 +++++++++---------- 7 files changed, 49 insertions(+), 78 deletions(-) diff --git a/Helper/Data.php b/Helper/Data.php index fe4c303..2b2b19f 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -59,44 +59,16 @@ public function unsetObserverAtcPayload() public function getKlaviyoLists($api_key = null) { - if (!$api_key) { - $api_key = $this->_klaviyoScopeSetting->getPrivateApiKey(); - } - $v3_list_api = self::KLAVIYO_HOST . self::LIST_V3_API . '?api_key=' . $api_key; - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $v3_list_api); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - - - $output = json_decode(curl_exec($ch)); - $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); - curl_close($ch); - - if ($statusCode !== 200) { - if ($statusCode === 403) { - $reason = self::ERROR_INVALID_API_KEY; - } elseif ($statusCode === 401) { - $reason = self::ERROR_INVALID_API_KEY; - } else { - $reason = self::ERROR_NON_200_STATUS; - } - - $result = [ - 'success' => false, - 'reason' => $reason - ]; - } else { - usort($output, function ($a, $b) { - return strtolower($a->list_name) > strtolower($b->list_name) ? 1 : -1; - }); - - $result = [ - 'success' => true, - 'lists' => $output - ]; - } + $response = $this->getLists(); + + usort($response, function ($a, $b) { + return strtolower($a->list_name) > strtolower($b->list_name) ? 1 : -1; + }); - return $result; + return [ + 'success' => true, + 'lists' => $response + ]; } /** diff --git a/KlaviyoV3Sdk/Exception/KlaviyoApiException.php b/KlaviyoV3Sdk/Exception/KlaviyoApiException.php index 46b6f86..4a14ece 100644 --- a/KlaviyoV3Sdk/Exception/KlaviyoApiException.php +++ b/KlaviyoV3Sdk/Exception/KlaviyoApiException.php @@ -1,6 +1,6 @@ _klaviyoScopeSetting->getVersion(); + + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $productMetadata = $objectManager->get('Magento\Framework\App\ProductMetadataInterface'); + $m2Version = $productMetadata->getVersion(); $headers = array( CURLOPT_HTTPHEADER => [ self::REVISION_KEY_HEADER . ': ' . self::KLAVIYO_V3_REVISION, self::ACCEPT_KEY_HEADER . ': ' . self::APPLICATION_JSON_HEADER_VALUE, - self::KLAVIYO_USER_AGENT_KEY . ': ' . 'prestashop-klaviyo/' . $klaviyops->version . 'Prestashop/' . _PS_VERSION_ . 'PHP/' . phpversion() + self::KLAVIYO_USER_AGENT_KEY . ': ' . 'prestashop-klaviyo/' . $klVersion() . 'Magento2/' . $m2Version . 'PHP/' . phpversion() ] ); @@ -114,9 +121,9 @@ public function getHeaders($clientEvent) * * @return array */ - public function getLists($api_key) + public function getKlaviyoLists() { - + $this->sendApiRequest(self::LIST_V3_API, false, 'GET'); } /** @@ -137,7 +144,7 @@ public function track($config): array ) ); - return $this->make_request('/client/events/?company_id=' . $this->public_key, true, $body); + return $this->sendApiRequest(self::EVENT_V3_API . '?company_id=' . $this->public_key, true, $body); } /** @@ -183,7 +190,7 @@ public function unsubscribeEmailFromKlaviyoList($email): array|string|null 'emails' => [(string)$email], ]; - return $this->sendApiRequest($path, $fields, 'POST'); + return $this->sendApiRequest($path, false, 'POST', $fields); } /** @@ -206,7 +213,7 @@ protected function sendApiRequest($path, $clientEvent, $method = null, $body = n $curl = curl_init(); $options = array( CURLOPT_URL => self::KLAVIYO_HOST . $path, - ) + $this->getHeaders($clientEvent) + $this->getDefaultCurlOptions(); + ) + $this->getHeaders($clientEvent) + $this->getDefaultCurlOptions($method); if ($body !== null) { $options[CURLOPT_POSTFIELDS][] = $body; @@ -289,7 +296,7 @@ protected function getDefaultCurlOptions($method = null) CURLOPT_ENCODING => "", CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, - CURLOPT_CUSTOMREQUEST => (!empty($method)) ? $method : 'POST',, + CURLOPT_CUSTOMREQUEST => (!empty($method)) ? $method : 'POST', CURLOPT_USERAGENT => self::USER_AGENT, ); } @@ -306,29 +313,21 @@ protected function getDefaultCurlOptions($method = null) * @throws KlaviyoRateLimitException */ protected - function handleAPIResponse($response, $statusCode, $clientEvent = false) + function handleAPIResponse($response, $statusCode, $clientEvent = false): array|string|null { - $decoded_response = $this->decodeJsonResponse($response); - if ($statusCode == 403) { - throw new KlaviyoAuthenticationException(self::ERROR_INVALID_API_KEY, $statusCode); - } - - if ($statusCode == 401) { - throw new KlaviyoAuthenticationException(self::ERROR_EXPIRED_API_KEY, $statusCode); - } - - if ($statusCode === 429) { - throw new KlaviyoAuthenticationException(self::ERROR_UNVERIFIABLE_API_KEY, $statusCode); - } - - if ($statusCode == 429) { - throw new KlaviyoRateLimitException( - $this->returnRateLimit($decoded_response) - ); - } - - if ($statusCode < 200 || $statusCode >= 300) { - throw new KlaviyoApiException(isset($decoded_response['detail']) ? $decoded_response['detail'] : sprintf(self::ERROR_NON_200_STATUS, $statusCode), $statusCode); + try { + $decoded_response = $this->decodeJsonResponse($response); + } catch (\Exception $error) { + switch ($statusCode) { + case 403: + case 401: + throw new KlaviyoAuthenticationException($error['detail'], $statusCode); + case 429: + throw new KlaviyoRateLimitException($error['detail'], $statusCode); + default: + $errorMessage = isset($decoded_response['detail']) ? $decoded_response['detail'] : sprintf(self::ERROR_NON_200_STATUS, $statusCode); + throw new KlaviyoApiException($errorMessage, $statusCode); + } } if ($clientEvent) { @@ -353,4 +352,4 @@ private function decodeJsonResponse($response) } return json_decode('{}', true); } -} \ No newline at end of file +} From 44a2f625908934f2fa6e93e606c50696be13202a Mon Sep 17 00:00:00 2001 From: Jordan Leslie Date: Fri, 22 Sep 2023 13:10:39 -0500 Subject: [PATCH 04/29] Cleanup and add newline --- KlaviyoV3Sdk/KlaviyoV3Api.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/KlaviyoV3Sdk/KlaviyoV3Api.php b/KlaviyoV3Sdk/KlaviyoV3Api.php index 496433d..e7a21c0 100644 --- a/KlaviyoV3Sdk/KlaviyoV3Api.php +++ b/KlaviyoV3Sdk/KlaviyoV3Api.php @@ -183,7 +183,7 @@ public function subscribeMembersToList($path, $listId, $profiles) * @param string $email * @return array|null|string */ - public function unsubscribeEmailFromKlaviyoList($email): array|string|null + public function unsubscribeEmailFromKlaviyoList($email) { $path = self::LIST_V3_API . ScopeSetting::API_SUBSCRIBE; $fields = [ @@ -312,8 +312,8 @@ protected function getDefaultCurlOptions($method = null) * @throws KlaviyoAuthenticationException * @throws KlaviyoRateLimitException */ - protected - function handleAPIResponse($response, $statusCode, $clientEvent = false): array|string|null + + protected function handleAPIResponse($response, $statusCode, $clientEvent = false) { try { $decoded_response = $this->decodeJsonResponse($response); From 58d14290a4412c86b769ace30806a37d86972c19 Mon Sep 17 00:00:00 2001 From: Jordan Leslie Date: Fri, 22 Sep 2023 13:13:34 -0500 Subject: [PATCH 05/29] Newlines --- KlaviyoV3Sdk/Exception/KlaviyoApiException.php | 2 +- KlaviyoV3Sdk/Exception/KlaviyoAuthenticationException.php | 2 +- KlaviyoV3Sdk/Exception/KlaviyoException.php | 2 +- KlaviyoV3Sdk/Exception/KlaviyoRateLimitException.php | 2 +- KlaviyoV3Sdk/Exception/KlaviyoResourceNotFoundException.php | 2 +- KlaviyoV3Sdk/KlaviyoV3.php | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/KlaviyoV3Sdk/Exception/KlaviyoApiException.php b/KlaviyoV3Sdk/Exception/KlaviyoApiException.php index 4a14ece..197f7f7 100644 --- a/KlaviyoV3Sdk/Exception/KlaviyoApiException.php +++ b/KlaviyoV3Sdk/Exception/KlaviyoApiException.php @@ -4,4 +4,4 @@ class KlaviyoApiException extends KlaviyoException { -} \ No newline at end of file +} diff --git a/KlaviyoV3Sdk/Exception/KlaviyoAuthenticationException.php b/KlaviyoV3Sdk/Exception/KlaviyoAuthenticationException.php index 28312f0..e16bc6c 100644 --- a/KlaviyoV3Sdk/Exception/KlaviyoAuthenticationException.php +++ b/KlaviyoV3Sdk/Exception/KlaviyoAuthenticationException.php @@ -4,4 +4,4 @@ class KlaviyoAuthenticationException extends KlaviyoApiException { -} \ No newline at end of file +} diff --git a/KlaviyoV3Sdk/Exception/KlaviyoException.php b/KlaviyoV3Sdk/Exception/KlaviyoException.php index 8f23a81..bc7eea4 100644 --- a/KlaviyoV3Sdk/Exception/KlaviyoException.php +++ b/KlaviyoV3Sdk/Exception/KlaviyoException.php @@ -4,4 +4,4 @@ class KlaviyoException extends \Exception { -} \ No newline at end of file +} diff --git a/KlaviyoV3Sdk/Exception/KlaviyoRateLimitException.php b/KlaviyoV3Sdk/Exception/KlaviyoRateLimitException.php index 26168fa..b252aa6 100644 --- a/KlaviyoV3Sdk/Exception/KlaviyoRateLimitException.php +++ b/KlaviyoV3Sdk/Exception/KlaviyoRateLimitException.php @@ -4,4 +4,4 @@ class KlaviyoRateLimitException extends KlaviyoApiException { -} \ No newline at end of file +} diff --git a/KlaviyoV3Sdk/Exception/KlaviyoResourceNotFoundException.php b/KlaviyoV3Sdk/Exception/KlaviyoResourceNotFoundException.php index fb25b19..8e2624d 100644 --- a/KlaviyoV3Sdk/Exception/KlaviyoResourceNotFoundException.php +++ b/KlaviyoV3Sdk/Exception/KlaviyoResourceNotFoundException.php @@ -4,4 +4,4 @@ class KlaviyoResourceNotFoundException extends KlaviyoApiException { -} \ No newline at end of file +} diff --git a/KlaviyoV3Sdk/KlaviyoV3.php b/KlaviyoV3Sdk/KlaviyoV3.php index 773ad81..7dbb49b 100644 --- a/KlaviyoV3Sdk/KlaviyoV3.php +++ b/KlaviyoV3Sdk/KlaviyoV3.php @@ -46,4 +46,4 @@ public function getPublicKey(): string { return $this->public_key; } -} \ No newline at end of file +} From 5ce38cf6177972500e53d5f4717d480b9a15d7d8 Mon Sep 17 00:00:00 2001 From: Jordan Leslie Date: Fri, 22 Sep 2023 13:30:04 -0500 Subject: [PATCH 06/29] Remove unused class, newline --- Api/KlaviyoApiWrapper.php | 65 -------------------------------- KlaviyoV3Sdk/Exception/index.php | 2 +- 2 files changed, 1 insertion(+), 66 deletions(-) delete mode 100644 Api/KlaviyoApiWrapper.php diff --git a/Api/KlaviyoApiWrapper.php b/Api/KlaviyoApiWrapper.php deleted file mode 100644 index cc1815e..0000000 --- a/Api/KlaviyoApiWrapper.php +++ /dev/null @@ -1,65 +0,0 @@ -use Configuration; -use KlaviyoV3Sdk\Exception\KlaviyoApiException; -use KlaviyoV3Sdk\Klaviyo; -use KlaviyoV3Sdk\KlaviyoV3Api; -use KlaviyoV3Sdk\Exception; - -class KlaviyoApiWrapper -{ - /** @var Klaviyo Client for Klaviyo's Api. */ - protected $client; - - public function __construct() - { - $this->client = new KlaviyoV3Api(Configuration::get('KLAVIYO_PRIVATE_API'), Configuration::get('KLAVIYO_PUBLIC_API')); - } - - /** - * Get all lists for specific Klaviyo account. - * - * @return mixed - */ - public function getLists() - { - return $this->client->getLists(); - } - - /** - * Subscribe email to the Subscriber List selected on configuration page (if selected). - * - * @param string $email - * @throws KlaviyoApiException - */ - public function subscribeCustomer($email, $customProperties = []) - { - $profile = array( - 'type' => 'profile', - 'attributes' => array( - 'email' => $email, - 'subscriptions' => array( - 'email' => [ - 'MARKETING' - ] - ) - ) - ); - - $listId = Configuration::get('KLAVIYO_SUBSCRIBER_LIST'); - - if ($listId) { - $this->client->subscribeMembersToList($listId, array($profile)); - } - } - - /** - * Send event to Klaviyo using the Track endpoint. - * - * @param array $event - * @return bool - * @throws KlaviyoApiException - */ - public function trackEvent(array $eventConfig) - { - return (bool) $this->client->track($eventConfig); - } -} \ No newline at end of file diff --git a/KlaviyoV3Sdk/Exception/index.php b/KlaviyoV3Sdk/Exception/index.php index 5298563..63eec81 100644 --- a/KlaviyoV3Sdk/Exception/index.php +++ b/KlaviyoV3Sdk/Exception/index.php @@ -6,4 +6,4 @@ header('Cache-Control: post-check=0, pre-check=0', false); header('Pragma: no-cache'); header('Location: ../'); -exit; \ No newline at end of file +exit; From 1fbcfccb9836d9f8f2666a8afa9d0523be72307b Mon Sep 17 00:00:00 2001 From: Jordan Leslie Date: Fri, 22 Sep 2023 13:48:53 -0500 Subject: [PATCH 07/29] Change getKlaviyoList to getLists in api wrapper --- Helper/Data.php | 4 +--- KlaviyoV3Sdk/KlaviyoV3Api.php | 11 ++++------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Helper/Data.php b/Helper/Data.php index 2b2b19f..4de469d 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -2,10 +2,8 @@ namespace Klaviyo\Reclaim\Helper; -use Klaviyo\Reclaim\Helper\ScopeSetting; +use Klaviyo\Reclaim\KlaviyoV3Sdk\KlaviyoV3Api; use Magento\Framework\App\Helper\Context; -use Klaviyo\Reclaim\Helper\Logger; -use KlaviyoV3Sdk\KlaviyoV3Api; class Data extends KlaviyoV3Api { diff --git a/KlaviyoV3Sdk/KlaviyoV3Api.php b/KlaviyoV3Sdk/KlaviyoV3Api.php index e7a21c0..d28d63e 100644 --- a/KlaviyoV3Sdk/KlaviyoV3Api.php +++ b/KlaviyoV3Sdk/KlaviyoV3Api.php @@ -1,15 +1,12 @@ sendApiRequest(self::LIST_V3_API, false, 'GET'); } From 287812c65bc3ddc785e41ada321bb0ec20614c02 Mon Sep 17 00:00:00 2001 From: Jordan Leslie Date: Fri, 22 Sep 2023 13:59:43 -0500 Subject: [PATCH 08/29] Remove union type from function --- Helper/Data.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Helper/Data.php b/Helper/Data.php index 4de469d..813ae74 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -117,7 +117,7 @@ public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName * @param string $email * @return array|string|null */ - public function unsubscribeEmailFromKlaviyoList($email): array|string|null + public function unsubscribeEmailFromKlaviyoList($email) { $listId = $this->_klaviyoScopeSetting->getNewsletter(); try { From cfa59f53caa44399e5484f8061fce83a6730a1b6 Mon Sep 17 00:00:00 2001 From: Jordan Leslie Date: Fri, 22 Sep 2023 14:35:31 -0500 Subject: [PATCH 09/29] Remove unused constructor --- Helper/Data.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Helper/Data.php b/Helper/Data.php index 813ae74..e386d5a 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -3,7 +3,6 @@ namespace Klaviyo\Reclaim\Helper; use Klaviyo\Reclaim\KlaviyoV3Sdk\KlaviyoV3Api; -use Magento\Framework\App\Helper\Context; class Data extends KlaviyoV3Api { @@ -29,16 +28,6 @@ class Data extends KlaviyoV3Api */ private $observerAtcPayload; - public function __construct( - Context $context, - Logger $klaviyoLogger, - ScopeSetting $klaviyoScopeSetting - ) { - parent::__construct($context); - $this->_klaviyoLogger = $klaviyoLogger; - $this->_klaviyoScopeSetting = $klaviyoScopeSetting; - $this->observerAtcPayload = null; - } public function getObserverAtcPayload() { From 9898ddad58afc2f9c20a21889f03b7a5964faf95 Mon Sep 17 00:00:00 2001 From: siddwarkhedkar Date: Sun, 24 Sep 2023 20:09:22 -0400 Subject: [PATCH 10/29] V3 API patch latest --- Helper/Data.php | 75 +++-- KlaviyoV3Sdk/Exception/KlaviyoException.php | 3 +- KlaviyoV3Sdk/KlaviyoV3.php | 49 ---- KlaviyoV3Sdk/KlaviyoV3Api.php | 293 ++++++++++++++------ Observer/NewsletterSubscribeObserver.php | 3 +- 5 files changed, 260 insertions(+), 163 deletions(-) delete mode 100644 KlaviyoV3Sdk/KlaviyoV3.php diff --git a/Helper/Data.php b/Helper/Data.php index e386d5a..c3bc633 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -4,7 +4,7 @@ use Klaviyo\Reclaim\KlaviyoV3Sdk\KlaviyoV3Api; -class Data extends KlaviyoV3Api +class Data extends \Magento\Framework\App\Helper\AbstractHelper { const USER_AGENT = 'Klaviyo/1.0'; const KLAVIYO_HOST = 'https://a.klaviyo.com/'; @@ -28,6 +28,23 @@ class Data extends KlaviyoV3Api */ private $observerAtcPayload; + /** + * V3 API Wrapper + * @var KlaviyoV3Api $api + */ + protected $api; + + public function __construct( + Context $context, + Logger $klaviyoLogger, + ScopeSetting $klaviyoScopeSetting + ) { + parent::__construct($context); + $this->_klaviyoLogger = $klaviyoLogger; + $this->_klaviyoScopeSetting = $klaviyoScopeSetting; + $this->observerAtcPayload = null; + $this->api = new KlaviyoV3Api($this->_klaviyoScopeSetting->getPublicApiKey(), $this->_klaviyoScopeSetting->getPrivateApiKey()); + } public function getObserverAtcPayload() { @@ -65,7 +82,7 @@ public function getKlaviyoLists($api_key = null) * @param string|null $source * @return array|false|null|string */ - public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName = null, $source = null) + public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName = null) { $listId = $this->_klaviyoScopeSetting->getNewsletter(); $optInSetting = $this->_klaviyoScopeSetting->getOptInSetting(); @@ -73,27 +90,43 @@ public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName $properties = []; $properties['email'] = $email; if ($firstName) { - $properties['$first_name'] = $firstName; + $properties['first_name'] = $firstName; } if ($lastName) { - $properties['$last_name'] = $lastName; - } - if ($source) { - $properties['$source'] = $source; - } - if ($optInSetting == ScopeSetting::API_SUBSCRIBE) { - $properties['$consent'] = ['email']; + $properties['last_name'] = $lastName; } - $propertiesVal = ['profiles' => $properties]; - - if ($optInSetting == ScopeSetting::API_SUBSCRIBE) { - $path = self::LIST_V3_API . $listId . $optInSetting; - } else { - $path = self::LIST_V3_API . $optInSetting; - } try { - $response = $this->subscribeMembersToList($path, $listId, $propertiesVal); + if ($optInSetting == ScopeSetting::API_SUBSCRIBE) { + // Subscribe profile using the profile creation endpoint for lists + $consent_profile_object = array( + 'type' => 'profile', + 'attributes' => array( + 'email' => $email, + 'subscriptions' => array( + 'email' => [ + 'MARKETING' + ] + ) + ) + ); + + $response = $this->api->subscribeMembersToList($listId, array($consent_profile_object)); + } else { + // Search for profile by email using the api/profiles endpoint + $profile_id = $this->api->searchProfileByEmail($email); + + // If the profile exists, use the ID to add to a list + // If the profile does not exist, create + if ($profile_id) { + $this->api->addProfileToList($listId, $profile_id); + } else { + $this->api->createProfile($properties); + } + } + + + } catch (\Exception $e) { $this->_klaviyoLogger->log(sprintf('Unable to subscribe %s to list %s: %s', $email, $listId, $e)); $response = false; @@ -110,7 +143,7 @@ public function unsubscribeEmailFromKlaviyoList($email) { $listId = $this->_klaviyoScopeSetting->getNewsletter(); try { - $response = $this->unsubscribeEmailFromKlaviyoList($email); + $response = $this->api->unsubscribeEmailFromKlaviyoList($email, $listId); } catch (\Exception $e) { $this->_klaviyoLogger->log(sprintf('Unable to unsubscribe %s from list %s: %s', $email, $listId, $e)); $response = false; @@ -129,7 +162,7 @@ public function klaviyoTrackEvent($event, $customer_properties = [], $properties return 'You must identify a user by email or ID.'; } $params = array( - 'metric' => $event, + 'event' => $event, 'properties' => $properties, 'customer_properties' => $customer_properties ); @@ -137,6 +170,6 @@ public function klaviyoTrackEvent($event, $customer_properties = [], $properties if (!is_null($timestamp)) { $params['time'] = $timestamp; } - return $this->track($params); + return $this->api->track($params); } } diff --git a/KlaviyoV3Sdk/Exception/KlaviyoException.php b/KlaviyoV3Sdk/Exception/KlaviyoException.php index bc7eea4..0801cf6 100644 --- a/KlaviyoV3Sdk/Exception/KlaviyoException.php +++ b/KlaviyoV3Sdk/Exception/KlaviyoException.php @@ -2,6 +2,7 @@ namespace Klaviyo\Reclaim\KlaviyoV3Sdk\Exception; -class KlaviyoException extends \Exception +use Exception; +class KlaviyoException extends Exception { } diff --git a/KlaviyoV3Sdk/KlaviyoV3.php b/KlaviyoV3Sdk/KlaviyoV3.php deleted file mode 100644 index 7dbb49b..0000000 --- a/KlaviyoV3Sdk/KlaviyoV3.php +++ /dev/null @@ -1,49 +0,0 @@ -private_key = $private_key; - $this->public_key = $public_key; - } - - /** - * @return string - */ - public function getPrivateKey(): string - { - return $this->private_key; - } - - /** - * @return string - */ - public function getPublicKey(): string - { - return $this->public_key; - } -} diff --git a/KlaviyoV3Sdk/KlaviyoV3Api.php b/KlaviyoV3Sdk/KlaviyoV3Api.php index d28d63e..7b733a4 100644 --- a/KlaviyoV3Sdk/KlaviyoV3Api.php +++ b/KlaviyoV3Sdk/KlaviyoV3Api.php @@ -15,27 +15,26 @@ class KlaviyoV3Api */ const KLAVIYO_HOST = 'https://a.klaviyo.com/'; const KLAVIYO_V3_REVISION = '2023-08-15'; - const USER_AGENT = 'Klaviyo/1.0'; - const LIST_V3_API = 'api/list/'; - const EVENT_V3_API = 'client/event/'; + /** * Request methods */ + const HTTP_GET = 'GET'; const HTTP_POST = 'POST'; /** * Error messages */ const ERROR_INVALID_API_KEY = 'The Private Klaviyo API Key you have set is invalid.'; - const ERROR_EXPIRED_API_KEY = 'The Private Klaviyo API key you have set is no longer valid.'; - const ERROR_UNVERIFIABLE_API_KEY = 'Unable to verify Klaviyo Private API Key.'; const ERROR_NON_200_STATUS = 'Request Failed with HTTP Status Code: %s'; + const ERROR_API_CALL_FAILED = 'Request could be completed at this time, API call failed'; + const ERROR_MALFORMED_RESPONSE_BODY = 'Response from API could not be decoded from JSON, check response body'; const ERROR_RATE_LIMIT_EXCEEDED = 'Rate limit exceeded'; /** * Request options */ const ACCEPT_KEY_HEADER = 'accept'; - const CONTENT_TYPE_KEY_HEADER = 'content-type'; + const CONTENT_TYPE_KEY_HEADER = 'Content-type'; const REVISION_KEY_HEADER = 'revision'; const AUTHORIZATION_KEY_HEADER = 'Authorization'; const KLAVIYO_API_KEY = 'Klaviyo-API-Key'; @@ -46,22 +45,28 @@ class KlaviyoV3Api /** * Payload options */ + const CUSTOMER_PROPERTIES_MAP = ['$email' => 'email', 'firstname' => 'first_name', 'lastname' => 'last_name']; const DATA_KEY_PAYLOAD = 'data'; const TYPE_KEY_PAYLOAD = 'type'; const ATTRIBUTE_KEY_PAYLOAD = 'attributes'; const PROPERTIES_KEY_PAYLOAD = 'properties'; const TIME_KEY_PAYLOAD = 'time'; const VALUE_KEY_PAYLOAD = 'value'; + const VALUE_KEY_PAYLOAD_OLD = '$value'; const METRIC_KEY_PAYLOAD = 'metric'; const PROFILE_KEY_PAYLOAD = 'profile'; const NAME_KEY_PAYLOAD = 'name'; const EVENT_VALUE_PAYLOAD = 'event'; const ID_KEY_PAYLOAD = 'id'; - const PROFILE_SUBSCRIPTION_BULK_CREATE_JOB_PAYLOAD_KEY = 'profile-subscription-bulk-create-job'; + const PROFILE_SUBSCRIPTION_BULK_CREATE_JOB_PAYLOAD_KEY = 'profile-subscription-bulk-create-jobs'; + const PROFILE_SUBSCRIPTION_BULK_DELETE_JOB_PAYLOAD_KEY = 'profile-subscription-bulk-delete-jobs'; const LIST_PAYLOAD_KEY = 'list'; const RELATIONSHIPS_PAYLOAD_KEY = 'relationships'; const PROFILES_PAYLOAD_KEY = 'profiles'; const CUSTOM_SOURCE_PAYLOAD_KEY = 'custom_source'; + const MAGENTO_TWO_PAYLOAD_VALUE = 'Magento Two'; + const MAGENTO_TWO_INTEGRATION_SERVICE_KEY = 'magentotwo'; + const SERVICE_PAYLOAD_KEY = 'service'; /** * @var string @@ -91,7 +96,7 @@ public function __construct($public_key, $private_key) * @param $clientEvent * @return array|array[] */ - public function getHeaders($clientEvent) + public function getHeaders() { $klVersion = $this->_klaviyoScopeSetting->getVersion(); @@ -102,25 +107,90 @@ public function getHeaders($clientEvent) $headers = array( CURLOPT_HTTPHEADER => [ self::REVISION_KEY_HEADER . ': ' . self::KLAVIYO_V3_REVISION, + self::CONTENT_TYPE_KEY_HEADER . ': ' . self::APPLICATION_JSON_HEADER_VALUE, self::ACCEPT_KEY_HEADER . ': ' . self::APPLICATION_JSON_HEADER_VALUE, - self::KLAVIYO_USER_AGENT_KEY . ': ' . 'prestashop-klaviyo/' . $klVersion() . 'Magento2/' . $m2Version . 'PHP/' . phpversion() + self::KLAVIYO_USER_AGENT_KEY . ': ' . 'magento2-klaviyo/' . $klVersion() . 'Magento2/' . $m2Version . 'PHP/' . phpversion(), + self::AUTHORIZATION_KEY_HEADER . ': ' . self::KLAVIYO_API_KEY . ' ' . $this->private_key ] ); - $headers[CURLOPT_HTTPHEADER][] = $clientEvent ? self::CONTENT_TYPE_KEY_HEADER . ' ' . self::APPLICATION_JSON_HEADER_VALUE : self::AUTHORIZATION_KEY_HEADER . ': ' . self::KLAVIYO_API_KEY . ' ' . $this->private_key; - return $headers; } /** * Query for all available lists in Klaviyo + * https://developers.klaviyo.com/en/v2023-08-15/reference/get_lists * * @return array + * @throws KlaviyoApiException + * @throws KlaviyoAuthenticationException + * @throws KlaviyoRateLimitException */ public function getLists() { - $this->sendApiRequest(self::LIST_V3_API, false, 'GET'); + $response_body = $this->requestV3('api/lists/', self::HTTP_GET); + + return $response_body[self::DATA_KEY_PAYLOAD]; + } + + /** + * Search for profile by Email + * https://developers.klaviyo.com/en/v2023-08-15/reference/get_profiles + * + * @param $email + * @return false|mixed + */ + public function searchProfileByEmail($email) + { + $response_body = $this->requestV3("api/profiles/?filter=equals(email,'$email')", self::HTTP_GET); + + if (empty($response_body[self::DATA_KEY_PAYLOAD])){ + return false; + } else { + return $response_body[self::DATA_KEY_PAYLOAD][0][self::ID_KEY_PAYLOAD]; + } + } + + /** + * Add a Profile to a list using profile id + * https://developers.klaviyo.com/en/v2023-08-15/reference/create_list_relationships + * + * @param $list_id + * @param $profile_id + * @return array|string|null + * @throws KlaviyoApiException + */ + public function addProfileToList($list_id, $profile_id) + { + $body = array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::PROFILE_KEY_PAYLOAD, + self::ID_KEY_PAYLOAD => $profile_id + ) + ); + + return $this->requestV3("api/lists/$list_id/relationships/profiles/",self::HTTP_POST, $body); + } + + /** + * Create a new Profile in Klaviyo + * https://developers.klaviyo.com/en/v2023-08-15/reference/create_profile + * + * @param $profile_properties + * @return array|string|null + * @throws KlaviyoApiException + */ + public function createProfile($profile_properties) + { + $body = array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::PROFILE_KEY_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => $profile_properties + ) + ); + + return $this->requestV3('api/profiles/', self::HTTP_POST, $body); } /** @@ -130,49 +200,67 @@ public function getLists() * @param $config * @return array */ - public function track($config): array + /** + * Record an event for a customer on their Klaviyo profile + * https://developers.klaviyo.com/en/v2023-08-15/reference/create_event + * + * @param $config + * @return array + * @throws KlaviyoApiException + * @throws KlaviyoAuthenticationException + * @throws KlaviyoRateLimitException + */ + public function track($config) { + $event_time = new DateTime(); + $event_time->setTimestamp($config['time'] ?? time()); + $body = array( self::DATA_KEY_PAYLOAD => array( self::TYPE_KEY_PAYLOAD => self::EVENT_VALUE_PAYLOAD, self::ATTRIBUTE_KEY_PAYLOAD => - $this->buildEventProperties($config['properties'], $config['time'], $config['metric']) + + $this->buildEventProperties($config['properties'], $event_time->format('Y-m-d\TH:i:s'), $config['event']) + $this->buildCustomerProperties($config['customer_properties']) ) ); - return $this->sendApiRequest(self::EVENT_V3_API . '?company_id=' . $this->public_key, true, $body); + return $this->requestV3('/api/events/', self::HTTP_POST, $body); } /** * Subscribe members to a Klaviyo list - * https://developers.klaviyo.com/en/reference/create_list_relationships + * https://developers.klaviyo.com/en/reference/subscribe_profiles * * @param $listId * @param $profiles * @return array + * @throws KlaviyoApiException + * @throws KlaviyoAuthenticationException + * @throws KlaviyoRateLimitException */ - public function subscribeMembersToList($path, $listId, $profiles) + public function subscribeMembersToList($listId, $profiles) { $body = array( self::DATA_KEY_PAYLOAD => array( self::TYPE_KEY_PAYLOAD => self::PROFILE_SUBSCRIPTION_BULK_CREATE_JOB_PAYLOAD_KEY, self::ATTRIBUTE_KEY_PAYLOAD => array( - self::CUSTOM_SOURCE_PAYLOAD_KEY => 'Magento 2', - self::PROFILES_PAYLOAD_KEY => $profiles, - self::RELATIONSHIPS_PAYLOAD_KEY => array( - self::LIST_PAYLOAD_KEY => array( - self::DATA_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::LIST_PAYLOAD_KEY, - self::ID_KEY_PAYLOAD => $listId - ) + self::CUSTOM_SOURCE_PAYLOAD_KEY => self::MAGENTO_TWO_PAYLOAD_VALUE, + self::PROFILES_PAYLOAD_KEY => array( + self::DATA_KEY_PAYLOAD => $profiles + ) + ), + self::RELATIONSHIPS_PAYLOAD_KEY => array( + self::LIST_PAYLOAD_KEY => array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::LIST_PAYLOAD_KEY, + self::ID_KEY_PAYLOAD => $listId ) ) ) ) ); - return $this->sendApiRequest($path, false, $body); + return $this->requestV3('/api/profile-subscription-bulk-create-jobs/', self::HTTP_POST, $body); } @@ -180,94 +268,124 @@ public function subscribeMembersToList($path, $listId, $profiles) * @param string $email * @return array|null|string */ - public function unsubscribeEmailFromKlaviyoList($email) + public function unsubscribeEmailFromKlaviyoList($email, $listId) { - $path = self::LIST_V3_API . ScopeSetting::API_SUBSCRIBE; - $fields = [ - 'emails' => [(string)$email], - ]; + $body = array( + self::TYPE_KEY_PAYLOAD => self::PROFILE_SUBSCRIPTION_BULK_DELETE_JOB_PAYLOAD_KEY, + self::ATTRIBUTE_KEY_PAYLOAD => array( + self::PROFILES_PAYLOAD_KEY => array( + self::DATA_KEY_PAYLOAD => array( + array( + self::TYPE_KEY_PAYLOAD => self::PROFILE_KEY_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => array( + 'email' => $email + ) + ) + ) + ) + ), + self::RELATIONSHIPS_PAYLOAD_KEY => array( + self::LIST_PAYLOAD_KEY => array( + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::LIST_PAYLOAD_KEY, + self::ID_KEY_PAYLOAD => $listId + ) + ) + ) + ); - return $this->sendApiRequest($path, false, 'POST', $fields); + return $this->requestV3('/api/profile-subscription-bulk-delete-jobs/', self::HTTP_POST, $body); } /** * Request method used by all API methods to make calls * * @param $path - * @param $clientEvent * @param $method * @param $body - * @return array + * @param $attempt + * @return array|string|null + * @throws KlaviyoApiException + * @throws KlaviyoAuthenticationException + * @throws KlaviyoRateLimitException */ - - protected function sendApiRequest($path, $clientEvent, $method = null, $body = null) + protected function requestV3($path, $method = null, $body = null, $attempt = 0) { $url = self::KLAVIYO_HOST . $path; - //Add API Key to params - $params['api_key'] = $this->_klaviyoScopeSetting->getPrivateApiKey(); - $curl = curl_init(); $options = array( CURLOPT_URL => self::KLAVIYO_HOST . $path, - ) + $this->getHeaders($clientEvent) + $this->getDefaultCurlOptions($method); + ) + $this->getHeaders() + $this->getDefaultCurlOptions($method); + + curl_setopt_array($curl, $options); if ($body !== null) { - $options[CURLOPT_POSTFIELDS][] = $body; + curl_setopt($curl, CURLOPT_POSTFIELDS, $body); } - curl_setopt_array($curl, $options); - $response = curl_exec($curl); + + // In the event that the curl_exec fails for whatever reason, it responds with `false`, + // Implementing a timeout and retry mechanism which will attempt the API call 3 times at 5 second intervals + if (!$response){ + if($attempt < 3) { + sleep(5); + $this->requestV3($path, $method, $body, $attempt+1); + } else { + throw new KlaviyoApiException(self::ERROR_API_CALL_FAILED); + } + } + $phpVersionHttpCode = version_compare(phpversion(), '5.5.0', '>') ? CURLINFO_RESPONSE_CODE : CURLINFO_HTTP_CODE; $statusCode = curl_getinfo($curl, $phpVersionHttpCode); curl_close($curl); - return $this->handleAPIResponse($response, $statusCode, $clientEvent); + return $this->handleAPIResponse($response, $statusCode); } /** - * Build Event Properties for the Client/Events endpoint + * Build Event Properties for the api/events endpoint * * @param $eventProperties * @param $time * @param $metric * @return array */ - public function buildEventProperties($eventProperties, $time, $metric): array + public function buildEventProperties($eventProperties, $time, $metric) { - $event_time = new DateTime(); - $event_time->setTimestamp($time ?: time()); - return array( self::PROPERTIES_KEY_PAYLOAD => $eventProperties, - self::TIME_KEY_PAYLOAD => $event_time, - self::VALUE_KEY_PAYLOAD => $eventProperties[self::VALUE_KEY_PAYLOAD], + self::TIME_KEY_PAYLOAD => $time, + self::VALUE_KEY_PAYLOAD => $eventProperties[self::VALUE_KEY_PAYLOAD_OLD], self::METRIC_KEY_PAYLOAD => array( - self::TYPE_KEY_PAYLOAD => self::METRIC_KEY_PAYLOAD, - self::ATTRIBUTE_KEY_PAYLOAD => array( - self::NAME_KEY_PAYLOAD => $metric + self::DATA_KEY_PAYLOAD => array( + self::TYPE_KEY_PAYLOAD => self::METRIC_KEY_PAYLOAD, + self::ATTRIBUTE_KEY_PAYLOAD => array( + self::NAME_KEY_PAYLOAD => $metric, + self::SERVICE_PAYLOAD_KEY => self::MAGENTO_TWO_INTEGRATION_SERVICE_KEY + ) ) - )); + ) + ); } /** - * Build customer properties for the Client/Events endpoint + * Build customer properties for the api/events endpoint * * @param $customerProperties - * @return \array[][] + * @return array[][] */ - public function buildCustomerProperties($customerProperties): array + public function buildCustomerProperties($customerProperties) { - $kl_properties = array( - 'email' => $customerProperties['$email'], - 'first_name' => $customerProperties['firstname'], - 'last_name' => $customerProperties['lastname'] - ); + $kl_properties = []; - unset($customerProperties['email']); - unset($customerProperties['firstname']); - unset($customerProperties['lastname']); + foreach(array_keys(self::CUSTOMER_PROPERTIES_MAP) as $property_name){ + if (isset($customerProperties[$property_name])) { + $kl_properties[self::CUSTOMER_PROPERTIES_MAP[$property_name]] = $customerProperties[$property_name]; + unset($customerProperties[$property_name]); + } + } return array( self::PROFILE_KEY_PAYLOAD => array( @@ -286,15 +404,14 @@ public function buildCustomerProperties($customerProperties): array * @return array */ #[\ReturnTypeWillChange] - protected function getDefaultCurlOptions($method = null) + protected function getDefaultCurlOptions($method) { return array( CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => "", CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, - CURLOPT_CUSTOMREQUEST => (!empty($method)) ? $method : 'POST', - CURLOPT_USERAGENT => self::USER_AGENT, + CURLOPT_CUSTOMREQUEST => $method, ); } @@ -303,32 +420,22 @@ protected function getDefaultCurlOptions($method = null) * * @param string $response The raw API response. * @param int $statusCode The HTTP status code of the response. - * @param bool $clientEvent * @return array| string |null An array containing the parsed data or null on error. * @throws KlaviyoApiException * @throws KlaviyoAuthenticationException * @throws KlaviyoRateLimitException */ - - protected function handleAPIResponse($response, $statusCode, $clientEvent = false) + protected function handleAPIResponse($response, $statusCode) { - try { - $decoded_response = $this->decodeJsonResponse($response); - } catch (\Exception $error) { - switch ($statusCode) { - case 403: - case 401: - throw new KlaviyoAuthenticationException($error['detail'], $statusCode); - case 429: - throw new KlaviyoRateLimitException($error['detail'], $statusCode); - default: - $errorMessage = isset($decoded_response['detail']) ? $decoded_response['detail'] : sprintf(self::ERROR_NON_200_STATUS, $statusCode); - throw new KlaviyoApiException($errorMessage, $statusCode); - } - } - - if ($clientEvent) { - return $response; + $decoded_response = $this->decodeJsonResponse($response); + if ($statusCode == 403) { + throw new KlaviyoAuthenticationException(self::ERROR_INVALID_API_KEY, $statusCode); + } elseif ($statusCode == 429) { + throw new KlaviyoRateLimitException( + self::ERROR_RATE_LIMIT_EXCEEDED + ); + } elseif ($statusCode < 200 || $statusCode >= 300) { + throw new KlaviyoApiException(isset($decoded_response['detail']) ? $decoded_response['detail'] : sprintf(self::ERROR_NON_200_STATUS, $statusCode), $statusCode); } return $decoded_response; @@ -341,12 +448,18 @@ protected function handleAPIResponse($response, $statusCode, $clientEvent = fals * * @param string $response * @return mixed + * @throws KlaviyoException */ private function decodeJsonResponse($response) { if (!empty($response)) { - return json_decode($response, true); + try { + return json_decode($response, true); + } catch (Exception $e) { + throw new KlaviyoException(self::ERROR_MALFORMED_RESPONSE_BODY); + } } + return json_decode('{}', true); } } diff --git a/Observer/NewsletterSubscribeObserver.php b/Observer/NewsletterSubscribeObserver.php index b682fcd..1cdfac9 100644 --- a/Observer/NewsletterSubscribeObserver.php +++ b/Observer/NewsletterSubscribeObserver.php @@ -55,8 +55,7 @@ public function execute(Observer $observer) $this->helper->subscribeEmailToKlaviyoList( $customer ? $customer->getEmail() : $subscriber->getEmail(), $customer ? $customer->getFirstname() : $subscriber->getFirstname(), - $customer ? $customer->getLastname() : $subscriber->getLastname(), - self::SOURCE_ID_MAGENTO2 + $customer ? $customer->getLastname() : $subscriber->getLastname() ); } From 37bcd47538e97184ed009b784e995b7212076149 Mon Sep 17 00:00:00 2001 From: siddwarkhedkar Date: Sun, 24 Sep 2023 20:14:15 -0400 Subject: [PATCH 11/29] codesniff but manual --- KlaviyoV3Sdk/KlaviyoV3Api.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/KlaviyoV3Sdk/KlaviyoV3Api.php b/KlaviyoV3Sdk/KlaviyoV3Api.php index 7b733a4..667d435 100644 --- a/KlaviyoV3Sdk/KlaviyoV3Api.php +++ b/KlaviyoV3Sdk/KlaviyoV3Api.php @@ -145,7 +145,7 @@ public function searchProfileByEmail($email) { $response_body = $this->requestV3("api/profiles/?filter=equals(email,'$email')", self::HTTP_GET); - if (empty($response_body[self::DATA_KEY_PAYLOAD])){ + if (empty($response_body[self::DATA_KEY_PAYLOAD])) { return false; } else { return $response_body[self::DATA_KEY_PAYLOAD][0][self::ID_KEY_PAYLOAD]; @@ -170,7 +170,7 @@ public function addProfileToList($list_id, $profile_id) ) ); - return $this->requestV3("api/lists/$list_id/relationships/profiles/",self::HTTP_POST, $body); + return $this->requestV3("api/lists/$list_id/relationships/profiles/", self::HTTP_POST, $body); } /** @@ -329,9 +329,9 @@ protected function requestV3($path, $method = null, $body = null, $attempt = 0) // In the event that the curl_exec fails for whatever reason, it responds with `false`, // Implementing a timeout and retry mechanism which will attempt the API call 3 times at 5 second intervals if (!$response){ - if($attempt < 3) { - sleep(5); - $this->requestV3($path, $method, $body, $attempt+1); + if ($attempt < 3) { + sleep(1); + $this->requestV3($path, $method, $body, $attempt + 1); } else { throw new KlaviyoApiException(self::ERROR_API_CALL_FAILED); } @@ -380,7 +380,7 @@ public function buildCustomerProperties($customerProperties) { $kl_properties = []; - foreach(array_keys(self::CUSTOMER_PROPERTIES_MAP) as $property_name){ + foreach (array_keys(self::CUSTOMER_PROPERTIES_MAP) as $property_name) { if (isset($customerProperties[$property_name])) { $kl_properties[self::CUSTOMER_PROPERTIES_MAP[$property_name]] = $customerProperties[$property_name]; unset($customerProperties[$property_name]); From dbdc062241f8d5e2d2a064d0f5716634792286a4 Mon Sep 17 00:00:00 2001 From: siddwarkhedkar Date: Sun, 24 Sep 2023 20:14:57 -0400 Subject: [PATCH 12/29] codesniff but manual 2 --- Helper/Data.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/Helper/Data.php b/Helper/Data.php index c3bc633..27ebb3d 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -124,9 +124,6 @@ public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName $this->api->createProfile($properties); } } - - - } catch (\Exception $e) { $this->_klaviyoLogger->log(sprintf('Unable to subscribe %s to list %s: %s', $email, $listId, $e)); $response = false; From bb7631e5b2f7be2e8c115b577e3d691c50136d91 Mon Sep 17 00:00:00 2001 From: siddwarkhedkar Date: Sun, 24 Sep 2023 20:17:03 -0400 Subject: [PATCH 13/29] codesniff but manual 3 --- KlaviyoV3Sdk/Exception/KlaviyoException.php | 1 + KlaviyoV3Sdk/KlaviyoV3Api.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/KlaviyoV3Sdk/Exception/KlaviyoException.php b/KlaviyoV3Sdk/Exception/KlaviyoException.php index 0801cf6..99f5331 100644 --- a/KlaviyoV3Sdk/Exception/KlaviyoException.php +++ b/KlaviyoV3Sdk/Exception/KlaviyoException.php @@ -3,6 +3,7 @@ namespace Klaviyo\Reclaim\KlaviyoV3Sdk\Exception; use Exception; + class KlaviyoException extends Exception { } diff --git a/KlaviyoV3Sdk/KlaviyoV3Api.php b/KlaviyoV3Sdk/KlaviyoV3Api.php index 667d435..ff9bd7b 100644 --- a/KlaviyoV3Sdk/KlaviyoV3Api.php +++ b/KlaviyoV3Sdk/KlaviyoV3Api.php @@ -328,7 +328,7 @@ protected function requestV3($path, $method = null, $body = null, $attempt = 0) // In the event that the curl_exec fails for whatever reason, it responds with `false`, // Implementing a timeout and retry mechanism which will attempt the API call 3 times at 5 second intervals - if (!$response){ + if (!$response) { if ($attempt < 3) { sleep(1); $this->requestV3($path, $method, $body, $attempt + 1); From 14c2ce57f75dd164c982606949520f6d9b06d095 Mon Sep 17 00:00:00 2001 From: Jordan Leslie Date: Mon, 25 Sep 2023 12:29:02 -0500 Subject: [PATCH 14/29] return response data and id from get/create profile --- Helper/Data.php | 7 ++++--- KlaviyoV3Sdk/KlaviyoV3Api.php | 13 +++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Helper/Data.php b/Helper/Data.php index 27ebb3d..a3cab98 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -114,14 +114,15 @@ public function subscribeEmailToKlaviyoList($email, $firstName = null, $lastName $response = $this->api->subscribeMembersToList($listId, array($consent_profile_object)); } else { // Search for profile by email using the api/profiles endpoint - $profile_id = $this->api->searchProfileByEmail($email); - + $response = $this->api->searchProfileByEmail($email); + $profile_id = $response["profile_id"]; // If the profile exists, use the ID to add to a list // If the profile does not exist, create if ($profile_id) { $this->api->addProfileToList($listId, $profile_id); } else { - $this->api->createProfile($properties); + $new_profile = $this->api->createProfile($properties); + $this->api->addProfileToList($listId, $new_profile["profile_id"]); } } } catch (\Exception $e) { diff --git a/KlaviyoV3Sdk/KlaviyoV3Api.php b/KlaviyoV3Sdk/KlaviyoV3Api.php index ff9bd7b..0d6bfca 100644 --- a/KlaviyoV3Sdk/KlaviyoV3Api.php +++ b/KlaviyoV3Sdk/KlaviyoV3Api.php @@ -148,7 +148,11 @@ public function searchProfileByEmail($email) if (empty($response_body[self::DATA_KEY_PAYLOAD])) { return false; } else { - return $response_body[self::DATA_KEY_PAYLOAD][0][self::ID_KEY_PAYLOAD]; + $id = $response_body[self::DATA_KEY_PAYLOAD][0][self::ID_KEY_PAYLOAD]; + return [ + 'response' => $response_body, + 'profile_id' => $id + ]; } } @@ -190,7 +194,12 @@ public function createProfile($profile_properties) ) ); - return $this->requestV3('api/profiles/', self::HTTP_POST, $body); + $response_body = $this->requestV3('api/profiles/', self::HTTP_POST, $body); + $id = $response_body[self::DATA_KEY_PAYLOAD][0][self::ID_KEY_PAYLOAD]; + return [ + 'data' => $response_body, + 'profile_id' => $id + ]; } /** From d4d315367ab2eca4c19dbd9290127c70aea68f15 Mon Sep 17 00:00:00 2001 From: Jordan Leslie Date: Mon, 25 Sep 2023 12:31:25 -0500 Subject: [PATCH 15/29] Revert changes to API:SUBSCRIBE --- Helper/ScopeSetting.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Helper/ScopeSetting.php b/Helper/ScopeSetting.php index ed36d05..20f24be 100755 --- a/Helper/ScopeSetting.php +++ b/Helper/ScopeSetting.php @@ -13,8 +13,8 @@ class ScopeSetting extends \Magento\Framework\App\Helper\AbstractHelper const NEWSLETTER = 'klaviyo_reclaim_newsletter/newsletter/newsletter'; const USING_KLAVIYO_LIST_OPT_IN = 'klaviyo_reclaim_newsletter/newsletter/using_klaviyo_list_opt_in'; - const API_MEMBERS = '/relationships/profiles/'; - const API_SUBSCRIBE = '/profile-subscription-bulk-create-jobs'; + const API_MEMBERS = '/members'; + const API_SUBSCRIBE = '/subscribe'; const CONSENT_AT_CHECKOUT_EMAIL_IS_ACTIVE = 'klaviyo_reclaim_consent_at_checkout/email_consent/is_active'; const CONSENT_AT_CHECKOUT_EMAIL_LIST_ID = 'klaviyo_reclaim_consent_at_checkout/email_consent/list_id'; From eb933f3096418189afc315f541feab29f3090f28 Mon Sep 17 00:00:00 2001 From: siddwarkhedkar Date: Mon, 25 Sep 2023 23:13:55 -0400 Subject: [PATCH 16/29] Update to use Klaviyo object --- Helper/Data.php | 16 +++++++++++----- Model/Config/Source/ListOptions.php | 2 +- view/frontend/templates/product/viewed.phtml | 8 +++++--- view/frontend/web/js/customer.js | 9 ++++++--- view/frontend/web/js/view/checkout/email.js | 9 ++++++--- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/Helper/Data.php b/Helper/Data.php index a3cab98..81ed205 100644 --- a/Helper/Data.php +++ b/Helper/Data.php @@ -3,6 +3,7 @@ namespace Klaviyo\Reclaim\Helper; use Klaviyo\Reclaim\KlaviyoV3Sdk\KlaviyoV3Api; +use Magento\Framework\App\Helper\Context; class Data extends \Magento\Framework\App\Helper\AbstractHelper { @@ -63,15 +64,20 @@ public function unsetObserverAtcPayload() public function getKlaviyoLists($api_key = null) { - $response = $this->getLists(); + $lists_response = $this->api->getLists(); - usort($response, function ($a, $b) { - return strtolower($a->list_name) > strtolower($b->list_name) ? 1 : -1; - }); + $lists = array(); + + foreach ($lists_response as $list) { + $lists[] = array( + 'id' => $list['id'], + 'name' => $list['attributes']['name'] + ); + } return [ 'success' => true, - 'lists' => $response + 'lists' => $lists ]; } diff --git a/Model/Config/Source/ListOptions.php b/Model/Config/Source/ListOptions.php index 0a73bab..e52e616 100644 --- a/Model/Config/Source/ListOptions.php +++ b/Model/Config/Source/ListOptions.php @@ -64,7 +64,7 @@ public function toOptionArray() } $options = array_map(function ($list) { - return [self::LABEL => $list->list_name, self::VALUE => $list->list_id]; + return [self::LABEL => $list['name'], self::VALUE => $list['id']]; }, $result['lists']); $default_value = [ diff --git a/view/frontend/templates/product/viewed.phtml b/view/frontend/templates/product/viewed.phtml index e601334..a2abe01 100644 --- a/view/frontend/templates/product/viewed.phtml +++ b/view/frontend/templates/product/viewed.phtml @@ -4,9 +4,11 @@ diff --git a/view/frontend/web/js/customer.js b/view/frontend/web/js/customer.js index 7543534..1a7f054 100644 --- a/view/frontend/web/js/customer.js +++ b/view/frontend/web/js/customer.js @@ -4,13 +4,16 @@ define([ 'domReady!' ], function (_, customerData) { 'use strict'; - var _learnq = window._learnq || []; + + !function(){if(!window.klaviyo){window._klOnsite=window._klOnsite||[];try{window.klaviyo=new Proxy({},{get:function(n,i){return"push"===i?function(){var n;(n=window._klOnsite).push.apply(n,arguments)}:function(){for(var n=arguments.length,o=new Array(n),w=0;w Date: Mon, 25 Sep 2023 23:18:24 -0400 Subject: [PATCH 17/29] Codesniff commit --- view/frontend/templates/product/viewed.phtml | 3 ++- view/frontend/web/js/customer.js | 4 ++-- view/frontend/web/js/view/checkout/email.js | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/view/frontend/templates/product/viewed.phtml b/view/frontend/templates/product/viewed.phtml index a2abe01..959552b 100644 --- a/view/frontend/templates/product/viewed.phtml +++ b/view/frontend/templates/product/viewed.phtml @@ -3,8 +3,9 @@ ?>