spuzz99.github.io



spuzz99.github.io

0 0


spuzz99.github.io

My Git Hub Pages Repo

On Github spuzz99 / spuzz99.github.io

eStore High Level Design
Current eStore EFT flow
New eStore EFT flow with Litle PayPage
Checkout form logical flow with Litle

Drupal estore.module

Routing path to function

/wfmcom/docroot/sites/all/modules /custom/estore/estore.module

function estore_menu() {

    $items['shop/%/checkout'] = array(
        'access callback' => true,
        'page callback' => 'estore_checkoutpage',
        'page arguments' => array(1)
    );

    $items['shop/%'] = array(
        'access callback' => true,
        'page callback' => 'estore_storepage',
        'page arguments' => array(1)
    );

    return $items;
}
                        

Drupal estore.module

Page callback function to Handlebars theme file

function estore_checkoutpage($store) {
    estore_forcessl();
    estore_nocache();
    estore_start();

    global $user;
    global $_POST;

    $estore = estore_new_estore();
    $wfm = estore_new_wfm();
    $hbs = estore_new_handlebars();
    $cartID = estore_getUserCart($store);

    // ...

    $html_body = $hbs->render(
        'checkout',
    array(
        'user' => $user_data,
        'stored_payments' => $stored_payments,
        'cart' => $items,
        'usstates' => $usstates,
        'ukstates' => $ukstates,
        'castates' => $castates,
        'countries' => $countries,
        'items' => $cart,
        'count' => $count,
        'store' => $store_info,
        'shipping_methods' => $shipping_methods,
        'json_data' => json_encode($items),
    )
);
                        

Handlebars

Angular.js controller set by ng-controller attribute.

/wfmcom/docroot/sites/all/modules /custom/estore/dev/partials/*.handlebars

{{> header_secure}}

<div  ng-controller="checkout">

    <form id="checkoutForm"
        name="form"
        checkout-form
        class="ng-class: {attempted_submit: form.attempted_submit}"
        action="/shop/{{store.id}}/actions/submit"
        method="post">

        <div class="contained cart-body" ng-show="step==2">

            {{#if user}}
                <fieldset>
                    <h3>Welcome back, {{user.name}}</h3>
                    <label>Email *</label>
                    <input
                        ng-model="email"
                        required
                        type="email"
                        id="email"
                        name="email"
                        value="{{user.email}}"
                        ng-maxlength="100"
                    >

                    <div ng-show="form.email.$dirty && form.email.$invalid">
                        <span ng-show="form.email.$error.email">This is not a valid email.</span>
                        <span ng-show="form.email.$error.maxlength">This field must be less than 100 characters.</span>
                    </div>
                </fieldset>
            {{else}}
                <fieldset>
                    <p>
                    <label>Guest Email *</label>
                    <input
                        ng-model="email"
                        required
                        type="email"
                        id="email"
                        name="email"
                        ng-maxlength="100"
                        >

                    <div ng-show="form.email.$dirty && form.email.$invalid">
                        <span ng-show="form.email.$error.email">This is not a valid email.</span>
                        <span ng-show="form.email.$error.maxlength">This field must be less than 100 characters.</span>
                    </div>
                    </p>
                </fieldset>
            {{/if}}


            <!-- PayPage Request Input Fields -->
            <fieldset>
                <input type="hidden" id="request$paypageId" name="request$paypageId" value="abc123"/>
                <input type="hidden" id="request$orderId" name="request$orderId" value="order_123"/>
                <input type="hidden" id="request$timeout" name="request$timeout" value="15000"/>
            </fieldset>

            <!-- PayPage Response Output Fields -->
            <fieldset>
                <input type="hidden" id="response$paypageRegistrationId" name="response$paypageRegistrationId" readonly="true" value=""/>
                <input type="hidden" id="response$litleTxnId" name="response$litleTxnId" readonly="true"/>
                <input type="hidden" id="response$firstSix" name="response$firstSix" readonly="true"/>
                <input type="hidden" id="response$lastFour" name="response$lastFour" readonly="true"/>
                <input type="hidden" id="response$expMonth" name="response$expMonth" readonly="readonly"/>
                <input type="hidden" id="response$expYear" name="response$expYear" readonly="true"/>
                <input type="hidden" id="response$code" name="response$code" readonly="true"/>
                <input type="hidden" id="response$message" name="response$message" readonly="true"/>
            </fieldset>
                        

Angular.js

/wfmcom/docroot/sites/all/modules /custom/estore/dev/js/app.js

_estore.controller('checkout', ['$scope','$http', '$sce','Estore','$rootScope','$timeout','$q','DataLayer',
    function($scope, $http, $sce, Estore, $rootScope, $timeout, $q, DataLayer){

        $scope.reviewOrder = function(form) {
            Drupal.settings.WholeFoods = Drupal.settings.WholeFoods || {};
            Drupal.settings.WholeFoods.Litle = Drupal.settings.WholeFoods.Litle || {};
            var wfmLitle = Drupal.settings.WholeFoods.Litle;

            // First validate non-credit card form elements
            if ($scope.validateCheckout(form) == false) {
                // Form is dirty, modal error should have launched, do not continue with reviewOrder.
                console.log('Non credit card form elements are NOT valid according to $scope.validateCheckout(form)');
                return;
            } else {
                console.log('Non credit card form elements are valid according to $scope.validateCheckout(form)');
            }
                        

How does the Litle widget work?

The checkout page loads the litle script (checkout.handlebars):

<script src="https://request-prelive.np-securepaypage-litle.com/LitlePayPage/js/payframe-client.min.js"></script>
                        

How does the Litle widget work?

We configure and instantiate (app.js :: checkout controller):

jQuery(document).ready(function() {
    Drupal.settings.WholeFoods.Litle = Drupal.settings.WholeFoods.Litle || {};
    var wfmLitle = Drupal.settings.WholeFoods.Litle;

    wfmLitle.payframeClientCallback = function(response) {
        $scope.payframeCallback(response);
    };

    wfmLitle.paypageRegIdConfigure = {
        "id"  : json_data.id,
        "orderId" : json_data.id
    };

    wfmLitle.litleConfigure = {
        "paypageId" : document.getElementById("request$paypageId").value,
        "style" : "WFMdefaultStyle", // StyleSheet file name, WFMdefaultStyle or empty
        "div" : "payframe",
        "callback"  : $scope.payframeCallback,
    };

    if (typeof LitlePayframeClient === 'undefined') {
        // TODO: HANDLE LITLE LIBRARY NOT LOADED ERR
    } else {
        payframeClient = new LitlePayframeClient(wfmLitle.litleConfigure);
    }
}
                        

How does the Litle widget work?

"REVIEW ORDER" onClick event handler calls Angular.js 'checkout' controller reviewOrder() function (checkout.handlebars):

<input type="submit" class="button secure-btn"
    onClick="return false;"
    ng-click="reviewOrder(form)"
    value="REVIEW ORDER" />
                        

How does the Litle widget work?

Angular.js 'checkout' controller reviewOrder() (app.js):

$scope.reviewOrder = function(form) {
    Drupal.settings.WholeFoods = Drupal.settings.WholeFoods || {};
    Drupal.settings.WholeFoods.Litle = Drupal.settings.WholeFoods.Litle || {};
    var wfmLitle = Drupal.settings.WholeFoods.Litle;

    // First validate non-credit card form elements
    if ($scope.validateCheckout(form) == false) {
        // Form is dirty, modal error should have launched, do not continue with reviewOrder.
        console.log('Non credit card form elements are NOT valid according to $scope.validateCheckout(form)');
        return;
    } else {
        console.log('Non credit card form elements are valid according to $scope.validateCheckout(form)');
    }

    var typeofPayframeClient = typeof payframeClient;
    if (typeofPayframeClient == 'object' && jQuery("#vantiv-payframe").length > 0) {
        payframeClient.getPaypageRegistrationId(wfmLitle.paypageRegIdConfigure);
        return;
    } else {
        // Payframe not in use, proceed to the next step
        $scope.prepForSubmit(form);
    }
}
                        

How does the Litle widget work?

Angular.js 'checkout' controller reviewOrder() (app.js):

$scope.payframeCallback = function(response) {
    Drupal.settings.WholeFoods = Drupal.settings.WholeFoods || {};
    Drupal.settings.WholeFoods.Litle = Drupal.settings.WholeFoods.Litle || {};
    var wfmLitle = Drupal.settings.WholeFoods.Litle;
    var successful = false;
    var errMsg = '';

    document.getElementById('response$code').value = response.response;
    document.getElementById('response$responseMessage').value = response.message;
    document.getElementById('response$litleTxnId').value = response.litleTxnId;
    document.getElementById('response$lastFour').value = response.lastFour;
    document.getElementById('response$firstSix').value = response.firstSix;
    document.getElementById('response$paypageRegistrationId').value = response.paypageRegistrationId;

    if (response.response === '870') {
        successful = true;
    }
    else if (
        response.response === '871' ||
        response.response === '872' ||
        response.response === '873' ||
        response.response === '874' ||
        response.response === '876'
    ) {
        // Recoverable error caused by user mis-typing their card info.
        errMsg = 'Please check and re-enter your credit card number and try again.';
    }
    else if (
        response.response === '881' ||
        response.response === '882' ||
        response.response === '883'
    ) {
        // Recoverable error caused by user mis-typing their card info.
        errMsg = 'Please check and re-enter your card validation number and try again.';
    }
    else if (response.response === '884') {
        // Litle PayPage frame failed to load so payment can't proceed.
        // Log the IP, user agent, time, paypageId and style that failed for debugging.
        // Hide the frame to remove unsightly browser errs
        jQuery('#payframe').hide();
        $('#submitButton').attr('disabled','disabled');
    }
    else if (response.response === '885') {
        // CSS failed to load so page will look unsightly but will function
        // Continue with order
        $('#submitButton').removeAttr('disabled');

        // Log IP, user agent, time and style that failed to load for debugging
    }
    else {
        // Non-recoverable or unknown error code.
        errMsg = 'We are experiencing technical difficulties. Please reload this page and try again or call us to complete your order.';
    }

    if (successful == true) {
        $scope.prepForSubmit($scope.form);
    } else {
        DataLayer.validationErrorEvent('billing', 'missing required field(s)');
        $scope.openDialog(errMsg,
            {onclose: function(){
                $scope.focusToCCInput();
            }
        });

        $scope.$apply();

    }
}
                        

Dir Structure

Angular.js app.js:

/wfmcom/docroot/sites/all/modules/custom/estore/dev/js/app.js
                        

Handlebars themes:

/wfmcom/docroot/sites/all/modules/custom/estore/dev/partials/*.handlebars
                        

CSS:

/wfmcom/docroot/sites/all/modules/custom/estore/dev/css/components/*.scss
                        

PHP Object Classes for APIs, caching and business logic:

/wfmcom/docroot/sites/all/modules/custom/estore/libs/bcapi/*.inc
                        

Prod Files:

/wfmcom/docroot/sites/all/modules/custom/estore/public/*
                        

Grunt

After editing app.js, handlebars or CSS run grunt before edits are activated.

$ cd /wfmcom/docroot/sites/all/modules/custom/estore/
$ grunt
                        

Grunt prod deployments also minify.

$ cd /wfmcom/docroot/sites/all/modules/custom/estore/
$ grunt prod
                        

SAGE Class Object

Add xdebug breakpoints or error_log() in the function getAPI() for raw requests and responses from the Spinutech or SAGE APIs.

class SAGE {
    // ...
    public function getAPI($url, $params, $type = 'GET') {
        // Debug Spinutech API call here. Look at our request $url and $params values here.

        if ($type == 'GET') {
            // if we have a SAGE OAuth token at all, use it.
            // don't try to generate one here, because we may actually in the process
            // of trying to generate one...
            if ($token = $this->estore_cache->get('sage_token')) {
                $params['access_token'] = $token;
            } else {
                $params['client_id'] = $this->getParams('client_id');
                $params['client_secret'] = $this->getParams('client_secret');
            }

            $post_data ='';
            foreach ($params as $field=>$val) {
                $post_data = $post_data . urlencode($field) . "=" . urlencode($val) . "&";
            }
            $url = $url . '?' . $post_data;
            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        }

        if($type == 'POST'){
            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_POST, TRUE);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
            curl_setopt($ch, CURLOPT_HTTPHEADER, Array("Content-Type: text/xml"));
        }

        $result = curl_exec($ch);
        // Debug $result here for raw response from Spinutech API

        curl_close($ch);
        return $result;
    }