Search code examples
phpopencart2.3

Multi store multi domain one checkout, single customer login into all stores?


I am running Opencart v2.3.0.2 multi store on several domains (not sub-domains) and want to Checkout on main site, which has all the products from all the stores.

Since it is hard to share sessions on multiple domains, think it is best to force user login before the Checkout process (login credentials are same for all stores and Cart items are shared). But, by default, users have to login into each store separately - how can I create single login into/for all stores? Once logged in, I can send them to main site to checkout.

If you think there is a better way for single checkout, please suggest.


Solution

  • Opencart 2 sets two HTTPOnly cookies PHPSESSID and default to identify the customer. Therefore, I decided to share/sync them over the stores.

    1. Get the list of stores for Javascript

    Maybe not best, but in /catalog/controller/common/header.php I assigned a variable:

    $this->load->language('setting/store');
    $this->load->model('setting/store');
    $this->load->model('setting/setting');
    
    $data['multi_stores'] = array();
    
    $data['multi_stores'][] = array(
        'store_id'      => 0,
        'name'          => 'Main Site',
        'url'           => 'https://mysite.co.uk/',
        'is_current'    => stripos('https://mysite.co.uk/', $_SERVER['SERVER_NAME']) !== false
    );
    
    $results = $this->model_setting_store->getStores();
    foreach ($results as $result) {
        $data['multi_stores'][] = array(
            'store_id'      => $result['store_id'],
            'name'          => $result['name'],
            'url'           => $result['url'],
            'is_current'    => stripos($result['url'], $_SERVER['SERVER_NAME']) !== false
        );
    }
    

    2. Than in template I used it:

    <script type="text/javascript">
    var multiStores = <?php echo json_encode($multi_stores); ?>;
    </script>
    

    3. Created two PHP scripts to set and get cookies:

    • Please note, to set PHP HTTPOnly cookie, the 7th parameter must be true.
    • Another note, to get HTTPOnly cookie we have to request it from server, it is not accessible via Javascript (which is its purpose in first place).

    getCookies.php:

    $cookies = array(
        'PHPSESSID' => $_COOKIE['PHPSESSID'],
        'default' => $_COOKIE['default'],
        'currency' => $_COOKIE['currency'],
        'language' => $_COOKIE['language']
    );
    
    header('Content-Type: application/json');
    echo json_encode( $cookies );
    

    setCookies.php:

    $response = array(
        'status' => 'ok'
    );
    
    /* Format: [cookie name] => [expire days] */
    $cookies_to_sync = array(
        'PHPSESSID' => '',
        'default' => '',
        'currency' => 30,
        'language'=> 30
    );
    
    /* If no expire was set, than set session HTTPOnly cookie (last, 7th parameter 'true') */
    foreach( $cookies_to_sync as $cname=>$cexpire ) {
        if( $_POST[$cname] ) {
            if( $cexpire ) {
                /* 86400 seconds per day */
                setcookie($cname, $_POST[$cname], time() + (86400 * $cexpire), '/', null, null, false);
            } else {
                setcookie($cname, $_POST[$cname], null, '/', null, null, true);
            };
        };
    };
    
    /* Browser requests a JSON, cross-origin enabled, with OPTIONS enabled to set cookies */
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: POST, OPTIONS');
    header('Access-Control-Max-Age: 1000');
    header('Content-Type: application/json');
    echo json_encode( $response );
    

    4. The Javascript part: - Please note, in order to send/set cookies cross-domain, we set OPTIONS in PHP header and for jQuery we add $.ajax xhrFields option withCredentials: true. Having that in mind, I created my method to sync website cookies:

    this.syncWebsites = function() {
        if( typeof multiStores!='undefined' ) {
            that.stores = multiStores;
            that.synced = that.readCookie('synced');
            if( !that.synced ) {
                /* First get cookies */
                $.getJSON( "catalog/view/theme/mytheme/javascript/getCookies.php", function( data ) {
                    /* Send to other sites */
                    $.each(that.stores, function(i, store) {
                        if( !store.is_current ) {
                            /* Send to other sites, MUST use xhrFields->withCredentials: true, to set cookies */
                            $.ajax({
                                url: store.url.replace('http://', '//') + "catalog/view/theme/mytheme/javascript/setCookies.php",
                                xhrFields: {
                                    withCredentials: true
                                },
                                type: "post",
                                crossDomain: true,
                                data: data,
                                dataType: "json",
                                success:function(result){
                                    that.echo(JSON.stringify(result));
                                },
                                error:function(xhr, status, error){
                                    that.echo(status);
                                }
                            });
                        };
                    });
                    that.createCookie('synced', 'Yes', '');
                });
            };
        };
    };
    

    Please note: I created the synced cookie, so this requests happen only once during the session.

    Final result: We have all the Opencart 2 customers synced on all websites.

    Security considerations: All websites are using SSL encription. The danger of stealing the info is same as one would visit all of those websites.