Apple push notifications from Magento

push_notifications

For everybody not so familiar with the title, Apple Push Notification Service (in further text: APNS) is basically the service that Apple provides for their mobile devices (iPhone, iPad …) that can be integrated within mobile application in order to receive specific messages on device pointed to specific listener application.

For proper usage of APNS, their possibilities need to be integrated inside mobile app, and also the certificate for sending the messages has to be generated on mobile application level with Apple.

I am not an iOS developer, so I am not competent to write about mobile side of the story, but, there is one more side that needs to be covered. I will try here to explain how we can use Magento’s side of the story to send the push notifications to targeted mobile devices.

Ideas can be different when Magento is used: we can send push notifications for example when new order is created, or customer is registered, or we can use them with our mobile Magento store to send some kind of newsletter or similar … etc.

This tutorial assuming that you enrolled in Apple developer program and registered for APNS usage and also that you already integrated APNS features within your mobile application.

There are few steps that we should do in order to start using APNS in Magento:

Prepare APNS certificate to be available from source code:

Log in to your Apple developer site and get certificate for using with APNS. Copy that certificate somewhere inside your extension’s file system. Also do not forget to write down a password for certificate file.

Create custom table in database for saving unique tokens and other needed parameters for APNS

Create install script in your extension’s sql folder that will create table for APNS tracking and usage. Also, create resource model and model for that table on native Magento way in order to make your new database table useful. I will not write about business logic that you need to implement for sending APNS on new customer registration or new order creation, or on some other event, but rather I want to provide the basic idea how the APNS can work with Magento.
For purpose of this article, let’s create just three columns for our “APNS” table inside install script: id, device_token, date_sent.

Create web service for storing device tokens and some other useful information in our APNS table

In our APNS table, we are going to save unique tokens of each mobile device that will receive our push notifications. This device token should be for example sent from mobile device when application is started. For purpose of saving device_token in database, we need to create separate Magento web service method that will be called each time when mobile application starts.

It is our responsibility to send APNS device token from mobile application to out apns_save web service method. Also in that web service method we should handle adding/updating device token string in same database row to avoid duplicates.

Here is example for that kind of web service API method:

<!--?php <br ?-->
/*
 * @category Inchoo
 * @package Inchoo_Mapy
 * @author Darko Goleš
 * @copyright Inchoo
 * @license http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
 
class Inchoo_Mapy_Model_Core_Apns_Api_V2 extends Mage_Api_Model_Resource_Abstract {
 
    public function setParams($token, $events, $appversion, $period) {
 
        $api_user = Mage::getSingleton('api/session')->getUser();
        $userExists = ($api_user && $api_user->getId()) ? true : false;
 
        if (!$userExists) {
            $this->_fault('not_exists', 'User does not exist!');
        }
        if (!$token) {
            $this->_fault('data_invalid', 'Device token not set!');
        }
        if ($token == '') {
            $this->_fault('data_invalid', 'Device token invalid!');
        }
 
        $collection = Mage::getSingleton('inchoo_mapy/core_apns')->getCollection();
 
        $collection->addFieldToFilter('user_id', $api_user->getId());
        $collection->addFieldToFilter('device_token', $token);
 
        $app = explode('-', $appversion);
        if (count($app) > 0) {
            $collection->addFieldToFilter('app_version',array('like'=>$app[0] . '%'));
        } else {
            $this->_fault('data_invalid', 'App version not supported!');
        }
        $param = $collection->getFirstItem();
 
        $currentTime = now();
 
        if ($period < 60) {             $period = 60;         }         $sess_id = Mage::getSingleton('api/session')->getSessionId();
 
        $param->setUserId($api_user->getId());
        $param->setAppVersion($appversion);
        $param->setDeviceToken($token);
        $param->setEvents($events);
        $param->setStatus('active');
        $param->setNotificationPeriod($period);
        $param->setUpdatedAt($currentTime);
        $param->setLastSession($sess_id);
 
        $param->save();
 
        return true;
    }

Create helper class that will be used for sending push messages.

When we have saved device tokens in our table, it’s up to us to make some business logic when, what and to what devices we will send messages from our Magento. Also it is developer’s responsibility to implement sending – interval logic in order to avoid spamming the devices with unnecessary amount of messages.

Helper for sending push notifications could look like this:

<!--?php <br ?-->
/*
 * @category Inchoo
 * @package Inchoo_Mapy
 * @author Darko Goleš
 * @copyright Inchoo
 * @license http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
 
class Inchoo_Mapy_Helper_ApnsLite extends Mage_Core_Helper_Abstract {
 
    const CONFIG_XML_PATH_APPLE_SSL = 'inchoo_mapy/settings/apnsssl';
    const CONFIG_XML_PATH_APPLE_FEEDBACK = 'inchoo_mapy/settings/apnsfeedback';
 
    private function _getCertificate() {
        return Mage::getBaseDir() . DS . 'app' . DS . 'code' . DS . 'community' . DS .
                'Inchoo' . DS . 'Mapy' . DS . 'lib' . DS . 'apple' .
                DS . 'certificate' . DS . 'ck_production_lite.pem';
    }
 
    public function sendPushNotifications() {
 
        $certificate = $this->_getCertificate();
        $devices = $this->_getDevicesForNotification();
 
        $messages = array();
        foreach ($devices as $device) {
 
            if ($device['received_at'] == null) {
                $now = Zend_Date::now();
                $now->sub(1, Zend_Date::HOUR);
 
                $from_time = $now;
            } else {
                $from_time = new Zend_Date($device['received_at'], Varien_Date::DATETIME_INTERNAL_FORMAT);
            }
 
            $data = array();
            if (strpos($device['events'], 'new_orders') !== false) {
                $result = $this->_getNewOrders($from_time);
                $data['orders_sum'] = $result['sales_total'];
                $data['orders_count'] = $result['num_orders'];
            }
            if (strpos($device['events'], 'new_customers') !== false) {
                $result = $this->_getNewCustomers($from_time);
                $data['customers_count'] = $result['new_customers'];
            }
 
            $message = $this->_prepareMessage($data);
 
            if (md5($message) == $device['last_msg_hash']) {
                $message = null;
            }
 
            $sess_id = $device['last_session'];
            $session = Mage::getSingleton('api/session');
 
            $user = Mage::getModel('api/user')->loadBySessId($sess_id);
 
            $user = Mage::getModel('api/user')->loadBySessId($sess_id);
            if ((!$session->isLoggedIn($sess_id))||(!$user->hasUserId())) {
                $message = null;
                $collection = Mage::getModel('inchoo_mapy/core_apns')->getCollection();
                $collection->addFieldToFilter('last_session', $sess_id);
 
                foreach ($collection as $val) {
                    if ($val->getId()) {
                        $val->setStatus('inactive');
                        $val->save();
                    }
                }
            }
 
            if ($message != null) {
                $messages[] = array(
                    'msg' => $message,
                    'device' => $device['device_token'],
                );
            }
        }
 
        if (count($messages) > 0) {
            $this->_pushMessage($messages, $certificate);
        }
    }
 
    private function _prepareMessage($data) {
 
        $helper = Mage::helper('inchoo_mapy');
 
        $message = '';
        $badge = 0;
        $mage_events = array();
 
        if (isset($data['orders_sum'])) {
            if ($data['orders_sum'] > 0) {
                $message.='Sales total: ' . $helper->currencyToBaseFormat($data['orders_sum']);
            }
        }
 
        if (isset($data['orders_count'])) {
            if ($data['orders_count'] > 0) {
                $message.=' Orders count: ' . $data['orders_count'];
                $badge+=$data['orders_count'];
                $mage_events[] = array('mage_event' => 'new_orders', 'count' => $data['orders_count']);
            }
        }
 
        if (isset($data['customers_count'])) {
            if ($data['customers_count'] > 0) {
                $message.=' New customers count: ' . $data['customers_count'];
                $badge+=$data['customers_count'];
                $mage_events[] = array('mage_event' => 'new_customers', 'count' => $data['customers_count']);
            }
        }
 
        if ($badge == 0) {
            return;
        }
 
        $body['aps'] = array(
            'sound' => 'tick.caf',
            'alert' => $message,
            'badge' => (int) $badge,
            'mage-events' => $mage_events
        );
 
        $payload = json_encode($body);
 
        return $payload;
    }
 
    private function _getDevicesForNotification() {
 
        $collection = Mage::getModel('inchoo_mapy/core_apns')->getCollection();
        $collection->addFieldToFilter('status', 'active');
 
        $where_expr = 'DATE_ADD(IFNULL(sent_at, DATE_SUB(UTC_TIMESTAMP(), INTERVAL 3600 SECOND )),INTERVAL notification_period SECOND)                 $param->save();
            }
        }
    }
 
}

If you reading the code, you will see that we have one public method for sending the notifications and inside that method we first loading devices for notification, then we loading and preparing data for message and sending it if all conditions are met.

_prepareMessage method is used to join our data in proper json message format for sending
_pushMessage is message sending method
_checkFeedback is used to get the list of devices from Apple that have turned off push notifications and we need to mark them as inactive in out table.

I hope that you will find this article usefull in own applications. Cheers.


5 comments

  1. Dear Sir
    I am trouble in calling this.
    $collection = Mage::getModel(‘inchoo_mapy/core_apns’)->getCollection();
    Where to put core_apns folder.
    If possible, could l get source full code.
    Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.