From d81ef13a838e71f93c3dafdcf7077c1556d57408 Mon Sep 17 00:00:00 2001 From: rsbowers Date: Thu, 10 Sep 2020 18:48:07 -0500 Subject: [PATCH 01/10] chore: add new v3 zendesk endpoints --- .gitignore | 2 + README.md | 2 +- .../community/Zendesk/Zendesk/Helper/Data.php | 376 ++++++++++++++++++ .../Zendesk/controllers/ApiController.php | 232 ++++++++++- .../community/Zendesk/Zendesk/etc/config.xml | 2 +- .../community/Zendesk/Zendesk/etc/system.xml | 1 + 6 files changed, 612 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 61ead866..4b091440 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ +**/.DS_Store + /vendor diff --git a/README.md b/README.md index 55cf0639..4b7cb6b5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The latest stable version of the extension can be installed via the [Magento Con ### General Notes -The extension provides its own custom RESTful API, which is intended to be used by the [Magento Zendesk App](https://github.com/zendesk/magento_app). The custom API allows for a consistent interface across all Magento versions, regardless of whether they support XML-RPC, SOAP or REST interfaces, and provides exactly the data that the app requires. +The extension provides its own custom RESTful API, which is intended to be used by the [agnoStack app](https://www.zendesk.com/apps/support/agnostack-commerce---by-particular/). The custom API allows for a consistent interface across all Magento versions, regardless of whether they support XML-RPC, SOAP or REST interfaces, and provides exactly the data that the app requires. The base URL of the API is `http://your_site_base_url/zendesk/api/`. diff --git a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php index 1587a929..0aeb2126 100644 --- a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php +++ b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php @@ -442,4 +442,380 @@ public function isAdmin() (Mage::app()->getStore()->isAdmin() || Mage::getDesign()->getArea() == 'adminhtml') ); } + + #region version 3.0 or later + + protected function formatPrice($price, $currency) + { + return array( + 'amount' => $price * 100, + 'currency' => $currency + ); + } + + protected function formatCustomer($order) + { + $isGuest = (bool)$order->getCustomerIsGuest(); + $id = $order->getCustomerId(); + $email = $order->getCustomerEmail(); + $customer = array(); + + if ($isGuest){ + $customer['type'] = 'guest'; + } else { + $customer['type'] = 'customer'; + } + + if (!empty($id)) { + $customer['id'] = $id; + } + if (!empty($email)) { + $customer['email'] = $email; + } + // TODO: can we get customer URL or timestamps?? + + return $customer; + } + + protected function formatAddress($address) + { + $addressData = array( + 'type' => 'address', + 'first_name' => $address->getFirstname(), + 'last_name' => $address->getLastname(), + 'city' => $address->getCity(), + 'county' => $address->getRegion(), + 'postcode' => $address->getPostcode(), + 'country' => $address->getCountryId(), + 'phone' => $address->getTelephone() + ); + + $entityId = $address->getEntityId(); + $addressId = $address->getCustomerAddressId(); + $addressData['id'] = $addressId ?: $entityId; + + $street = $address->getStreet(); + $addressData['line_1'] = $street[0] ?: ''; + $addressData['line_2'] = $street[1] ?: ''; + + return $addressData; + } + + public function getShipments($order) + { + $shipments = array(); + $orderStatus = $order->getStatus(); + + foreach($order->getShipmentsCollection() as $shipment) { + $shipmentId = $shipment->getEntityId(); + $shippingAddress = $shipment->getShippingAddress(); + $serviceCode = $order->getShippingDescription(); + } + + if ($shipmentId) { + $tracks = $order->getTracksCollection(); + if (count($tracks) > 0) { + foreach($tracks as $track) { + if ($shipmentId == $track->getParentId()) { + $shipments[] = array( + 'id' => $track->getEntityId(), + 'carrier' => $track->getTitle(), + 'carrier_code' => $track->getCarrierCode(), + 'service_code' => $serviceCode, + 'shipping_description' => $track->getDescription() ?: '', + 'created_at' => $track->getCreatedAt(), + 'updated_at' => $track->getUpdatedAt(), + 'tracking_number' => $track->getTrackNumber(), + 'shipping_address' => $this->formatAddress($shippingAddress), + 'order_status' => $orderStatus, + ); + } + } + } else { + $shipments[] = array( + 'service_code' => $serviceCode, + 'carrier_code' => $order->getShippingMethod(), + 'shipping_address' => $this->formatAddress($shippingAddress), + 'order_status' => $orderStatus, + ); + } + } + + return $shipments; + } + + public function getOrderDetailBasic($order) + { + // if the admin site has a custom URL, use it + $urlModel = Mage::getModel('adminhtml/url')->setStore('admin'); + + $currency = $order->getOrderCurrencyCode(); + $shippingAddress = $order->getShippingAddress(); + $shippingWithTax = $order->getShippingInclTax(); + + $orderInfo = array( + 'id' => $order->getIncrementId(), + 'url' => $urlModel->getUrl('adminhtml/sales_order/view', array('order_id' => $order->getId())), + 'transaction_id' => $order->getIncrementId(), + 'status' => $order->getStatus(), + 'billing_address' => $this->formatAddress($order->getBillingAddress()), + 'meta' => array( + 'store_info' => array( + 'type' => 'store_info', + 'name' => $order->getStoreName() + ), + 'display_price' => array( + 'with_tax' => $this->formatPrice($order->getGrandTotal(), $currency), + 'without_tax' => $this->formatPrice($order->getGrandTotal() - $order->getTaxAmount(), $currency), // TODO: get without tax + 'tax' => $this->formatPrice($order->getTaxAmount(), $currency) // TODO: get tax + ), + 'timestamps' => array( + 'created_at' => $order->getCreatedAt(), + 'updated_at' => $order->getUpdatedAt(), + ) + ), + 'relationships' => array( + 'customer' => array( + 'data' => $this->formatCustomer($order) + ), + 'items' => array( + 'data' => array() + ), + ), + 'shipments' => array(), + ); + + foreach($order->getItemsCollection(array(), true) as $item) { + $itemWithTax = $item->getRowTotal(); + $itemTax = $item->getTaxAmount(); + $orderInfo['relationships']['items']['data'][] = array( + 'type' => 'order_item', + 'id' => $item->getItemId(), + 'product_id' => $item->getProductId(), + 'name' => $item->getName(), + 'sku' => $item->getSku(), + 'quantity' => intval($item->getQtyOrdered()), + 'refunded' => intval($item->getQtyRefunded()), + 'meta' => array( + 'display_price' => array( + 'with_tax' => $this->formatPrice($itemWithTax, $currency), + 'without_tax' => $this->formatPrice($itemWithTax - $itemTax, $currency), + 'tax' => $this->formatPrice($iitemTax, $currency) + ), + 'timestamps' => array( + 'created_at' => $item->getCreatedAt(), + 'updated_at' => $item->getUpdatedAt(), + ) + ) + ); + } + + if ($shippingWithTax) { + $shippingTax = $order->getShippingTaxAmount(); + $shippingItem = array( + 'type' => 'custom_item', + 'id' => 'shipping--'.$order->getEntityId(), + 'product_id' => $order->getEntityId(), + 'name' => 'shipping--'.$order->getShippingDescription(), + 'sku' => $order->getShippingMethod(), + 'quantity' => 1, + 'refunded' => 0, + 'meta' => array( + 'display_price' => array( + 'with_tax' => $this->formatPrice($shippingWithTax, $currency), + 'without_tax' => $this->formatPrice($shippingWithTax - $shippingTax, $currency), + 'tax' => $this->formatPrice($shippingTax, $currency) + ), + 'timestamps' => array( + 'created_at' => $order->getCreatedAt(), + 'updated_at' => $order->getUpdatedAt(), + ) + ) + ); + array_push($orderInfo['relationships']['items']['data'], $shippingItem); + } + + $orderInfo['shipments'] = $this->getShipments($order); + + return $orderInfo; + } + + public function getOrderDetailExtended($order) + { + // if the admin site has a custom URL, use it + $urlModel = Mage::getModel('adminhtml/url')->setStore('admin'); + + $currency = $order->getOrderCurrencyCode(); + + $orderInfo = $this->getOrderDetailBasic($order); + + foreach($order->getPaymentsCollection() as $payment) { + $transactionAmount = $payment->getAmountAuthorized() ?: $payment->getAmountOrdered(); + $gateway = $payment->getMethod(); + + $lastTransId = $payment->getLastTransId(); + + if (!$lastTransId && $gateway == 'authorizenet') { + $additionalInformation = $payment->getAdditionalInformation(); + $authorizeCards = $additionalInformation['authorize_cards']; + $authorizeCardKeys = array_keys($authorizeCards); + $authorizeCard = $authorizeCards[$authorizeCardKeys[0]]; + $lastTransId = $authorizeCard['last_trans_id']; + } + + if (null != $lastTransId) { + $transaction = $payment->lookupTransaction($lastTransId, 'capture'); // TODO grab authorization as well + } + + if (!empty($transaction)) { + $transactionData = $transaction->getData(); + } + + $orderInfo['relationships']['transactions']['data'][] = array( + // 'DATA' => $payment->getData(), // TEMP + // 'METHODS' => get_class_methods($payment), // TEMP + 'id' => $transactionData['transaction_id'], //TODO validate this is the correct value + 'type' => $transactionData['txn_type'], //TODO is this only always payment? or can this be refund? + 'reference' => $transactionData['txn_id'], //TODO validate this is the correct value + 'gateway' => $gateway, //TODO validate this is the correct value + 'status' => $payment->getCcCidStatus(), //TODO validate this is the correct value + 'meta' => array( + 'display_price' => $this->formatPrice($transactionAmount, $currency), + 'timestamps' => array( + 'created_at' => $order->getCreatedAt(), + 'updated_at' => $order->getUpdatedAt(), + ) + ), + 'relationships' => array( + 'charges' => array( + 'data' => [] + ) + ) + ); + } + + // NOTE this is invoices + // $orderInfo['invoices'] = array(); + // foreach($order->getInvoiceCollection() as $invoice) { + // $orderInfo['invoices'][] = $invoice->getData(); + // } + + // NOTE this is refunds + // $orderInfo['creditmemos'] = array(); + // foreach($order->getCreditmemosCollection() as $creditmemo) { + // $orderInfo['creditmemos'][] = $creditmemo->getData(); + // } + + return $orderInfo; + } + + public function getOrderNotes($order) + { + $notesInfo = array(); + + foreach($order->getStatusHistoryCollection() as $note) { + $noteInfo = array( + 'id' => $note->getEntityId(), + 'type' => 'order_note', + 'source' => 'magento', + 'status' => $note->getStatus() ?: '', + 'created_at' => $note->getCreatedAt(), + 'entity_name' => $note->getEntityName() + ); + + $comment = $note->getComment(); + if ($comment) { + $noteInfo['messages'][] = $comment; + } + + $notesInfo[] = $noteInfo; + } + + return $notesInfo; + } + + public function getCustomer($customer) + { + $customerId = $customer->getEntityId(); + $urlModel = Mage::getModel('adminhtml/url')->setStore('admin'); + $adminUrl = $urlModel->getUrl('adminhtml/zendesk/redirect', array('id' => $customerId, 'type' => 'customer')); + + return array( + 'id' => $customerId, + 'type' => 'customer', + 'url' => $adminUrl, + 'email' => $customer->getEmail(), + 'first_name' => $customer->getFirstname(), + 'last_name' => $customer->getLastname(), + 'created_at' => $customer->getCreatedAt(), + 'updated_at' => $customer->getUpdatedAt(), + ); + } + + public function getFilteredOrders($customerFilters, $generalFilters) + { + // Get a list of all orders for the given email address + // This is used to determine if a missing customer is a guest or if they really aren't a customer at all + $orderCollection = Mage::getModel('sales/order')->getCollection(); + + foreach($customerFilters as $customerKey => $customerValue) { + $orderCollection->addFieldToFilter('customer_'.$customerKey, $customerValue); + } + + foreach($generalFilters as $generalKey => $generalValue) { + $orderCollection->addFieldToFilter($generalKey, $generalValue); + } + + $orders = array(); + + if($orderCollection->getSize()) { + foreach($orderCollection as $order) { + $orders[] = $this->getOrderDetailBasic($order); + } + } + + return $orders; + } + + public function getFilteredOrdersByProduct($customerFilters, $productFilters) + { + $emailKey = 'email'; + $email = $customerFilters->$emailKey; + + $orderItemCollection = Mage::getResourceModel('sales/order_item_collection'); + + foreach($productFilters as $key => $value) { + $orderItemCollection->addAttributeToFilter($key, $value); + } + $orderItemCollection->load(); + + $ordersData = array(); + + foreach($orderItemCollection as $orderItem) { + $orderId = $orderItem->getOrderId(); + $ordersData[$orderId] = array( + 'email' => $orderItem->getOrder()->getCustomerEmail(), + 'order' => $orderItem->getOrder() + ); + } + + if($email) { + $filteredOrdersData = array_filter(array_values($ordersData), function ($orderData) use ($email) { + return ($orderData['email'] == $email); + }); + } else { + $filteredOrdersData = $ordersData; + } + + $orders = array(); + + foreach($filteredOrdersData as $filteredData) { + $orders[] = $this->getOrderDetailBasic($filteredData['order']); + } + + return $orders; + } + + #endregion version 3.0 or later } + \ No newline at end of file diff --git a/src/app/code/community/Zendesk/Zendesk/controllers/ApiController.php b/src/app/code/community/Zendesk/Zendesk/controllers/ApiController.php index 726dfe92..5875aab6 100644 --- a/src/app/code/community/Zendesk/Zendesk/controllers/ApiController.php +++ b/src/app/code/community/Zendesk/Zendesk/controllers/ApiController.php @@ -18,6 +18,15 @@ class Zendesk_Zendesk_ApiController extends Mage_Core_Controller_Front_Action { + public function preDispatch() { + parent::preDispatch(); + // TODO!!!!!!!: read version from config.xml + // $configSettings = Mage::getSingleton('Zendesk_Zendesk/config'); + // Mage::log(json_encode($configSettings), null, 'zendesk.log'); + $this->getResponse()->setHeader('X-Extension-Version', '3.0.0'); + return $this; + } + public function _authorise() { // Perform some basic checks before running any of the API methods @@ -130,7 +139,6 @@ public function ordersAction() return $this; } - public function customersAction() { if(!$this->_authorise()) { @@ -392,4 +400,226 @@ public function finaliseAction() ->setHeader('Content-type', 'application/json', true); return $this; } + + #region V2 + + // TODO: rename to searchOrdersAction? + public function searchOrdersAction() + { + if(!$this->_authorise()) { + return $this; + } + + $req = $this->getRequest(); + $isPost = $req->isPost(); + + if (!$isPost) { + $this->getResponse() + ->setHttpResponseCode(405) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + $productKey = 'product'; + $customerKey = 'customer'; + + $filters = json_decode($req->getRawBody()); + $generalFilters = array(); + + foreach($filters as $key => $val) { + if($key == $productKey) { + $productFilters = $val; + } else if($key == $customerKey) { + $customerFilters = $val; + } else { + $generalFilters[$key] = $val; + } + } + + if($productFilters) { + $orders = Mage::helper('zendesk')->getFilteredOrdersByProduct($customerFilters, $productFilters); + } else { + $orders = Mage::helper('zendesk')->getFilteredOrders($customerFilters, $generalFilters); + } + + $this->getResponse() + ->setBody(json_encode($orders)) + ->setHttpResponseCode(200) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + public function orderAction() + { + if(!$this->_authorise()) { + return $this; + } + + $sections = explode('/', trim($this->getRequest()->getPathInfo(), '/')); + $orderId = $sections[3]; + + $order = Mage::getModel('sales/order')->loadByIncrementId($orderId); + + if(!$order && !$order->getId()) { + $this->getResponse() + ->setBody(json_encode(array('success' => false, 'message' => 'Order does not exist'))) + ->setHttpResponseCode(404) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + // TODO!!: implement this method + $info = Mage::helper('zendesk')->getOrderDetailExtended($order); + + $this->getResponse() + ->setBody(json_encode($info)) + ->setHttpResponseCode(200) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + public function shippingAction() + { + if(!$this->_authorise()) { + return $this; + } + + $sections = explode('/', trim($this->getRequest()->getPathInfo(), '/')); + $orderId = $sections[3]; + + $order = Mage::getModel('sales/order')->loadByIncrementId($orderId); + + if(!$order && !$order->getId()) { + $this->getResponse() + ->setBody(json_encode(array('success' => false, 'message' => 'Order does not exist'))) + ->setHttpResponseCode(404) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + $shipments = Mage::helper('zendesk')->getShipments($order); + + $this->getResponse() + ->setBody(json_encode($shipments)) + ->setHttpResponseCode(200) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + public function notesAction() + { + if(!$this->_authorise()) { + return $this; + } + + $sections = explode('/', trim($this->getRequest()->getPathInfo(), '/')); + $orderId = $sections[3]; + + $order = Mage::getModel('sales/order')->loadByIncrementId($orderId); + + if(!$order && !$order->getId()) { + $this->getResponse() + ->setBody(json_encode(array('success' => false, 'message' => 'Order does not exist'))) + ->setHttpResponseCode(404) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + $info = Mage::helper('zendesk')->getOrderNotes($order); + + $this->getResponse() + ->setBody(json_encode($info)) + ->setHttpResponseCode(200) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + public function customerAction() + { + if(!$this->_authorise()) { + return $this; + } + + $sections = explode('/', trim($this->getRequest()->getPathInfo(), '/')); + $customerId = $sections[3]; + + $customer = Mage::getModel('customer/customer')->load($customerId); + + if(!$customer->getEntityId()) { + $this->getResponse() + ->setBody(json_encode(null)) + ->setHttpResponseCode(200) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + $customerData = Mage::helper('zendesk')->getCustomer($customer); + + $this->getResponse() + ->setBody(json_encode($customerData)) + ->setHttpResponseCode(200) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + public function searchCustomersAction() + { + if(!$this->_authorise()) { + return $this; + } + + $req = $this->getRequest(); + $isPost = $req->isPost(); + + if (!$isPost) { + $this->getResponse() + ->setHttpResponseCode(405) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + $customerKey = 'customer'; + + $filters = json_decode($req->getRawBody()); + $generalFilters = array(); + + foreach($filters as $key => $val) { + if($key == $customerKey) { + $customerFilters = $val; + } else { + $generalFilters[$key] = $val; + } + } + + $customerCollection = Mage::getModel('customer/customer')->getCollection(); + + // TODO does this bring back guest cutsomers + foreach($customerFilters as $customerKey => $customerValue) { + $customerCollection->addFieldToFilter($customerKey, $customerValue); + } + + foreach($generalFilters as $generalKey => $generalValue) { + $customerCollection->addFieldToFilter($generalKey, $generalValue); + } + + $customerCollection->load(); + + $urlModel = Mage::getModel('adminhtml/url')->setStore('admin'); + + $customers = array(); + + foreach($customerCollection as $customer) { + $id = $customer->getId(); + $customerDetails = Mage::getModel('customer/customer')->load($id); + $customers[] = Mage::helper('zendesk')->getCustomer($customerDetails); + } + + $this->getResponse() + ->setBody(json_encode($customers)) + ->setHttpResponseCode(200) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + #endregion V2 } diff --git a/src/app/code/community/Zendesk/Zendesk/etc/config.xml b/src/app/code/community/Zendesk/Zendesk/etc/config.xml index 86509501..9f1e211e 100644 --- a/src/app/code/community/Zendesk/Zendesk/etc/config.xml +++ b/src/app/code/community/Zendesk/Zendesk/etc/config.xml @@ -19,7 +19,7 @@ - 2.2.0 + 3.0.0 diff --git a/src/app/code/community/Zendesk/Zendesk/etc/system.xml b/src/app/code/community/Zendesk/Zendesk/etc/system.xml index cedecc1a..e09f5a6f 100644 --- a/src/app/code/community/Zendesk/Zendesk/etc/system.xml +++ b/src/app/code/community/Zendesk/Zendesk/etc/system.xml @@ -24,6 +24,7 @@ + zendesk-section From 3c9b180de6b3cb363a2f2013faf1d424c497a4bd Mon Sep 17 00:00:00 2001 From: Adam Grohs Date: Fri, 11 Sep 2020 13:42:13 -0500 Subject: [PATCH 02/10] chore: cleanup order of methods in v2 --- .../community/Zendesk/Zendesk/Helper/Data.php | 8 +- .../Zendesk/controllers/ApiController.php | 181 +++++++++--------- 2 files changed, 94 insertions(+), 95 deletions(-) diff --git a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php index 0aeb2126..448ba77c 100644 --- a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php +++ b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php @@ -456,7 +456,7 @@ protected function formatPrice($price, $currency) protected function formatCustomer($order) { $isGuest = (bool)$order->getCustomerIsGuest(); - $id = $order->getCustomerId(); + $id = $order->getCustomerId(); // TODO: should this be customerId or entity id?? $email = $order->getCustomerEmail(); $customer = array(); @@ -752,7 +752,7 @@ public function getCustomer($customer) ); } - public function getFilteredOrders($customerFilters, $generalFilters) + public function getFilteredOrders($customerFilters, $genericFilters) { // Get a list of all orders for the given email address // This is used to determine if a missing customer is a guest or if they really aren't a customer at all @@ -762,8 +762,8 @@ public function getFilteredOrders($customerFilters, $generalFilters) $orderCollection->addFieldToFilter('customer_'.$customerKey, $customerValue); } - foreach($generalFilters as $generalKey => $generalValue) { - $orderCollection->addFieldToFilter($generalKey, $generalValue); + foreach($genericFilters as $genericKey => $genericValue) { + $orderCollection->addFieldToFilter($genericKey, $genericValue); } $orders = array(); diff --git a/src/app/code/community/Zendesk/Zendesk/controllers/ApiController.php b/src/app/code/community/Zendesk/Zendesk/controllers/ApiController.php index 5875aab6..d0a255cd 100644 --- a/src/app/code/community/Zendesk/Zendesk/controllers/ApiController.php +++ b/src/app/code/community/Zendesk/Zendesk/controllers/ApiController.php @@ -403,7 +403,93 @@ public function finaliseAction() #region V2 - // TODO: rename to searchOrdersAction? + public function customerAction() + { + if(!$this->_authorise()) { + return $this; + } + + $sections = explode('/', trim($this->getRequest()->getPathInfo(), '/')); + $customerId = $sections[3]; + + $customer = Mage::getModel('customer/customer')->load($customerId); + + if(!$customer->getEntityId()) { + $this->getResponse() + ->setBody(json_encode(null)) + ->setHttpResponseCode(200) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + $customerData = Mage::helper('zendesk')->getCustomer($customer); + + $this->getResponse() + ->setBody(json_encode($customerData)) + ->setHttpResponseCode(200) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + public function searchCustomersAction() + { + if(!$this->_authorise()) { + return $this; + } + + $req = $this->getRequest(); + $isPost = $req->isPost(); + + if (!$isPost) { + $this->getResponse() + ->setHttpResponseCode(405) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + + $customerKey = 'customer'; + + $filters = json_decode($req->getRawBody()); + $genericFilters = array(); + + foreach($filters as $key => $val) { + if($key == $customerKey) { + $customerFilters = $val; + } else { + $genericFilters[$key] = $val; + } + } + + $customerCollection = Mage::getModel('customer/customer')->getCollection(); + + // TODO does this bring back guest cutsomers + foreach($customerFilters as $customerKey => $customerValue) { + $customerCollection->addFieldToFilter($customerKey, $customerValue); + } + + foreach($genericFilters as $genericKey => $genericValue) { + $customerCollection->addFieldToFilter($genericKey, $genericValue); + } + + $customerCollection->load(); + + $urlModel = Mage::getModel('adminhtml/url')->setStore('admin'); + + $customers = array(); + + foreach($customerCollection as $customer) { + $id = $customer->getId(); + $customerDetails = Mage::getModel('customer/customer')->load($id); + $customers[] = Mage::helper('zendesk')->getCustomer($customerDetails); + } + + $this->getResponse() + ->setBody(json_encode($customers)) + ->setHttpResponseCode(200) + ->setHeader('Content-type', 'application/json', true); + return $this; + } + public function searchOrdersAction() { if(!$this->_authorise()) { @@ -424,7 +510,7 @@ public function searchOrdersAction() $customerKey = 'customer'; $filters = json_decode($req->getRawBody()); - $generalFilters = array(); + $genericFilters = array(); foreach($filters as $key => $val) { if($key == $productKey) { @@ -432,14 +518,14 @@ public function searchOrdersAction() } else if($key == $customerKey) { $customerFilters = $val; } else { - $generalFilters[$key] = $val; + $genericFilters[$key] = $val; } } if($productFilters) { $orders = Mage::helper('zendesk')->getFilteredOrdersByProduct($customerFilters, $productFilters); } else { - $orders = Mage::helper('zendesk')->getFilteredOrders($customerFilters, $generalFilters); + $orders = Mage::helper('zendesk')->getFilteredOrders($customerFilters, $genericFilters); } $this->getResponse() @@ -534,92 +620,5 @@ public function notesAction() return $this; } - public function customerAction() - { - if(!$this->_authorise()) { - return $this; - } - - $sections = explode('/', trim($this->getRequest()->getPathInfo(), '/')); - $customerId = $sections[3]; - - $customer = Mage::getModel('customer/customer')->load($customerId); - - if(!$customer->getEntityId()) { - $this->getResponse() - ->setBody(json_encode(null)) - ->setHttpResponseCode(200) - ->setHeader('Content-type', 'application/json', true); - return $this; - } - - $customerData = Mage::helper('zendesk')->getCustomer($customer); - - $this->getResponse() - ->setBody(json_encode($customerData)) - ->setHttpResponseCode(200) - ->setHeader('Content-type', 'application/json', true); - return $this; - } - - public function searchCustomersAction() - { - if(!$this->_authorise()) { - return $this; - } - - $req = $this->getRequest(); - $isPost = $req->isPost(); - - if (!$isPost) { - $this->getResponse() - ->setHttpResponseCode(405) - ->setHeader('Content-type', 'application/json', true); - return $this; - } - - $customerKey = 'customer'; - - $filters = json_decode($req->getRawBody()); - $generalFilters = array(); - - foreach($filters as $key => $val) { - if($key == $customerKey) { - $customerFilters = $val; - } else { - $generalFilters[$key] = $val; - } - } - - $customerCollection = Mage::getModel('customer/customer')->getCollection(); - - // TODO does this bring back guest cutsomers - foreach($customerFilters as $customerKey => $customerValue) { - $customerCollection->addFieldToFilter($customerKey, $customerValue); - } - - foreach($generalFilters as $generalKey => $generalValue) { - $customerCollection->addFieldToFilter($generalKey, $generalValue); - } - - $customerCollection->load(); - - $urlModel = Mage::getModel('adminhtml/url')->setStore('admin'); - - $customers = array(); - - foreach($customerCollection as $customer) { - $id = $customer->getId(); - $customerDetails = Mage::getModel('customer/customer')->load($id); - $customers[] = Mage::helper('zendesk')->getCustomer($customerDetails); - } - - $this->getResponse() - ->setBody(json_encode($customers)) - ->setHttpResponseCode(200) - ->setHeader('Content-type', 'application/json', true); - return $this; - } - #endregion V2 } From 7270a84d313d912212758d8ae1ad249b6d5f0a55 Mon Sep 17 00:00:00 2001 From: Adam Grohs Date: Tue, 15 Sep 2020 15:01:13 -0500 Subject: [PATCH 03/10] fix: updated to check if any shippingMethod --- src/app/code/community/Zendesk/Zendesk/Helper/Data.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php index 448ba77c..b9761168 100644 --- a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php +++ b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php @@ -552,6 +552,7 @@ public function getOrderDetailBasic($order) $currency = $order->getOrderCurrencyCode(); $shippingAddress = $order->getShippingAddress(); $shippingWithTax = $order->getShippingInclTax(); + $shippingMethod = $order->getShippingMethod(); $orderInfo = array( 'id' => $order->getIncrementId(), @@ -610,14 +611,14 @@ public function getOrderDetailBasic($order) ); } - if ($shippingWithTax) { + if ($shippingWithTax && $shippingMethod) { $shippingTax = $order->getShippingTaxAmount(); $shippingItem = array( 'type' => 'custom_item', 'id' => 'shipping--'.$order->getEntityId(), 'product_id' => $order->getEntityId(), 'name' => 'shipping--'.$order->getShippingDescription(), - 'sku' => $order->getShippingMethod(), + 'sku' => $shippingMethod, 'quantity' => 1, 'refunded' => 0, 'meta' => array( From 9dbabdff1a8e69324943924a4f88c5dac57ca24d Mon Sep 17 00:00:00 2001 From: rsbowers Date: Tue, 15 Sep 2020 21:12:13 -0500 Subject: [PATCH 04/10] chore: add customer addresses onto getCustomer --- src/app/code/community/Zendesk/Zendesk/Helper/Data.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php index 0aeb2126..3cd14c90 100644 --- a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php +++ b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php @@ -740,7 +740,7 @@ public function getCustomer($customer) $urlModel = Mage::getModel('adminhtml/url')->setStore('admin'); $adminUrl = $urlModel->getUrl('adminhtml/zendesk/redirect', array('id' => $customerId, 'type' => 'customer')); - return array( + $info = array( 'id' => $customerId, 'type' => 'customer', 'url' => $adminUrl, @@ -749,7 +749,14 @@ public function getCustomer($customer) 'last_name' => $customer->getLastname(), 'created_at' => $customer->getCreatedAt(), 'updated_at' => $customer->getUpdatedAt(), + 'addresses' => array() ); + + foreach($customer->getAddressesCollection() as $address) { + $info['addresses'][] = $this->formatAddress($address); + } + + return $info; } public function getFilteredOrders($customerFilters, $generalFilters) From 8fa721a019042b841da838c041ef8e299ecd36c5 Mon Sep 17 00:00:00 2001 From: rsbowers Date: Tue, 15 Sep 2020 21:42:03 -0500 Subject: [PATCH 05/10] chore: add extension version to left menu --- .../default/default/template/zendesk/left-menu.phtml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/design/adminhtml/default/default/template/zendesk/left-menu.phtml b/src/app/design/adminhtml/default/default/template/zendesk/left-menu.phtml index 2dbb8e1c..0cc5f141 100644 --- a/src/app/design/adminhtml/default/default/template/zendesk/left-menu.phtml +++ b/src/app/design/adminhtml/default/default/template/zendesk/left-menu.phtml @@ -53,4 +53,8 @@ +
+
+

Extension Version: 3.0.0

+
From 10247cdab8193359ef17c6df7a7e4fddf8e635c8 Mon Sep 17 00:00:00 2001 From: rsbowers Date: Wed, 16 Sep 2020 10:22:02 -0500 Subject: [PATCH 06/10] chore: bring in admin url and product meta onto items collection --- .../code/community/Zendesk/Zendesk/Helper/Data.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php index 1df53024..dc2ff250 100644 --- a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php +++ b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php @@ -589,12 +589,19 @@ public function getOrderDetailBasic($order) foreach($order->getItemsCollection(array(), true) as $item) { $itemWithTax = $item->getRowTotal(); $itemTax = $item->getTaxAmount(); + + $productId = $item->getProductId(); + $product = Mage::getModel('catalog/product')->load($productId); + + $adminUrl = $urlModel->getUrl('adminhtml/zendesk/redirect', array('id' => $productId, 'type' => 'product')); + $orderInfo['relationships']['items']['data'][] = array( 'type' => 'order_item', 'id' => $item->getItemId(), 'product_id' => $item->getProductId(), 'name' => $item->getName(), 'sku' => $item->getSku(), + 'url' => $adminUrl, 'quantity' => intval($item->getQtyOrdered()), 'refunded' => intval($item->getQtyRefunded()), 'meta' => array( @@ -606,6 +613,13 @@ public function getOrderDetailBasic($order) 'timestamps' => array( 'created_at' => $item->getCreatedAt(), 'updated_at' => $item->getUpdatedAt(), + ), + 'product' => array( + 'status' => $product->getStatus(), + 'type' => $product->getTypeId(), + 'url' => $product->getUrlPath(), + 'image' => $product->getThumbnail(), + 'description' => $product->getDescription(), ) ) ); From e80f306e71b9204fe5fa2225056d7d3c41716cae Mon Sep 17 00:00:00 2001 From: rsbowers Date: Wed, 16 Sep 2020 14:54:24 -0500 Subject: [PATCH 07/10] chore: ensure shipments returns an address even if not yet shipped --- src/app/code/community/Zendesk/Zendesk/Helper/Data.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php index dc2ff250..9dbfee3a 100644 --- a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php +++ b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php @@ -539,6 +539,11 @@ public function getShipments($order) 'order_status' => $orderStatus, ); } + } else { + $shippingAddress = $order->getShippingAddress(); + $shipments[] = array( + 'shipping_address' => $this->formatAddress($shippingAddress), + ); } return $shipments; From 89bacb66ea9accb144495bc9e97422dfce1f85fc Mon Sep 17 00:00:00 2001 From: rsbowers Date: Wed, 16 Sep 2020 15:03:31 -0500 Subject: [PATCH 08/10] chore: update orderInfo schema --- src/app/code/community/Zendesk/Zendesk/Helper/Data.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php index 9dbfee3a..c3044899 100644 --- a/src/app/code/community/Zendesk/Zendesk/Helper/Data.php +++ b/src/app/code/community/Zendesk/Zendesk/Helper/Data.php @@ -606,7 +606,6 @@ public function getOrderDetailBasic($order) 'product_id' => $item->getProductId(), 'name' => $item->getName(), 'sku' => $item->getSku(), - 'url' => $adminUrl, 'quantity' => intval($item->getQtyOrdered()), 'refunded' => intval($item->getQtyRefunded()), 'meta' => array( @@ -622,7 +621,7 @@ public function getOrderDetailBasic($order) 'product' => array( 'status' => $product->getStatus(), 'type' => $product->getTypeId(), - 'url' => $product->getUrlPath(), + 'path' => $product->getUrlPath(), 'image' => $product->getThumbnail(), 'description' => $product->getDescription(), ) From 04b4c998f19b687e9b8a72f499e4a8e3903f2ff3 Mon Sep 17 00:00:00 2001 From: rsbowers Date: Wed, 23 Sep 2020 11:51:52 -0500 Subject: [PATCH 09/10] chore: update plugin description --- src/app/code/community/Zendesk/Zendesk/etc/system.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/code/community/Zendesk/Zendesk/etc/system.xml b/src/app/code/community/Zendesk/Zendesk/etc/system.xml index e09f5a6f..cee95d98 100644 --- a/src/app/code/community/Zendesk/Zendesk/etc/system.xml +++ b/src/app/code/community/Zendesk/Zendesk/etc/system.xml @@ -92,14 +92,14 @@ - + select adminhtml/system_config_source_yesno 6 1 1 1 - + From 047a2c8fb9d1308472326a6b4eff0f427a8a4a23 Mon Sep 17 00:00:00 2001 From: rsbowers Date: Wed, 23 Sep 2020 11:52:21 -0500 Subject: [PATCH 10/10] chore: update readme --- README.md | 291 +++++++++++++----------------------------------------- 1 file changed, 67 insertions(+), 224 deletions(-) diff --git a/README.md b/README.md index 4b7cb6b5..6af94980 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ +# ANNOUNCING VERSION 3.0 :mega: + +The new 3.0 version of the Extension provides all new endpoints, with the ability to retrieve additional information about customers and orders within your Magento store. + +* Perform advanced Customer Search +* View real-time Shipping Status +* Access detailed Payment Status +* Enable direct Order Search by ID +* Access Order Messages and Notes + + # Zendesk Extension for Magento This extension makes Zendesk work seamlessly with Magento to enable stores to deliver great customer support. **Features include:** @@ -62,239 +73,71 @@ Will return customer information for the customer with the provided email addres Note that Magento allows scoping customers either globally or per website. If set to be scoped per website then this method will return the first customer which matches the email address, regardless of the website they belong to. -**Response Format** - -Guest customers only have the `guest` and `orders` keys returned. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeComment
guestbooleanWhether the customer is a guest (no customer record) or has a full customer record
idintegerInternal Magento ID for the customer
namestringCustomer's full name
emailstringCustomer's email address
activebooleanWhether the customer is marked as active in Magento
admin_urlstringURL to access the customer detail in the Magento admin panel
createdstringDate and time the customer record was created
dobstringDate of birth
addressesarrayList of addresses recorded on the customer account
ordersarrayList of orders placed by the customer (see the `orders` method for details)
- #### GET /orders/`` Will return details of an individual order based on the Magento order increment ID, which usually takes the form 100000321. -**Response Format** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeComment
idstringThe order ID displayed to the customer
statusstringCurrent order status (e.g. Pending, Processing, Complete)
createdstringDate and time the order was created
updatedstringDate and time the order was last updated
customerobjectHas the keys: -
-
name
-
Customer's name
-
email
-
Customer's email address
-
ip
-
IP address the order was placed from
-
guest
-
Whether the customer placed the order as a guest
-
-
storestringMagento store that the order was placed in
totalstringTotal value of the order
currencystringCurrency code (e.g. AUD, USD)
itemsarrayList of items on the order; each item has the keys: -
-
sku
-
Product's unique SKU
-
name
-
Product name
-
-
admin_urlstringURL to access the order in the Magento admin panel
#### GET /users/`` Will return either a single Magento admin user, or a list of users if the `user_id` argument is left out. Admin user accounts have access to the admin panel and are different to customer accounts. -**Parameters** - - - - - - - - - - - - - - - - - - - - - - - - - - -
ArgumentDefaultComment
page_size100Number of results to be returned
offset0Page number to return, based on `page_size`
sortgiven_nameAttribute to sort by
- -**Response Format** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeComment
idstringInternal ID for the user
given_namestringUser's first name
family_namestringUser's surname
usernamestringUsername used to log in to the Magento admin panel
emailstringUser's email address
activebooleanWhether the user is enabled and can log in
rolestringUser's role; used for ACLs in Magento
+### Additional Available Methods (v3 and later) + +#### GET /customer/`` + +Will return customer information for the customer with the provided customer ID. If no customer record exists, this will return `null`. + +#### POST /searchCustomers + +Will return customer information for the customers with the provided customer atrribute payload. + +Example payload: + +``` +{ + "customer": { + "email": "janedoe@example.com", + "firstname": "Jane", + "lastname": "Doe" + } +} +``` + +#### POST /searchOrders + +Will return order details for orders which match the provided order attribute payload. + +Example payloads: + +``` +// Lookup by customer and product +{ + "customer": { + "email": "janedoe@example.com" + }, + "product": { + "sku": "123" + } +} + +// Lookup by status +{ + "status": "complete" +} +``` + +#### GET /order/`` + +Will return details of an individual order based on the Magento order increment ID, which usually takes the form 100000321. This endpoint will return enhanced data on top of what is prodived by the v2.x endpoint. + +#### GET /shipping/`` + +Will return shipping related details of an individual order based on the Magento order increment ID, which usually takes the form 100000321. + +#### GET /notes/`` + +Will return status history details of an individual order based on the Magento order increment ID, which usually takes the form 100000321. ## Local Development