CORE-POS/IS4C

View on GitHub
pos/is4c-nf/plugins/Paycards/gui/PaycardEmvWic.php

Summary

Maintainability
F
3 days
Test Coverage
<?php

/**
 * This page does not really have a UI and just exists
 * to run javascript and process responses. It flows like this:
 *
 * 1. First page load
 *      a. Generate Balance transaction XML
 *      b. Submit XML to driver via javascript
 *      c. POST driver response XML back to this page
 * 2. Second page load
 *      a. Bail out on error
 *      b. Generate Sale transaction XML
 *      b. Submit XML to driver via javascript
 *      c. POST driver response XML back to this page
 * 3. Third page load
 *      a. Bail out on error
 *      b. Examine response and apply tender
 */

use COREPOS\pos\lib\Database;
use COREPOS\pos\lib\DisplayLib;
use COREPOS\pos\lib\FormLib;
use COREPOS\pos\lib\LaneLogger;
use COREPOS\pos\lib\MiscLib;
use COREPOS\pos\lib\ReceiptLib;
use COREPOS\pos\lib\TransRecord;
use COREPOS\pos\lib\UdpComm;
use COREPOS\pos\plugins\Paycards\xml\BetterXmlData;
if (!class_exists('AutoLoader')) include_once(dirname(__FILE__).'/../../../lib/AutoLoader.php');

class PaycardEmvWic extends PaycardProcessPage 
{
    private $runTransaction = false;

    function preprocess()
    {
        // I haven't tracked down the cause but some mechanisms for getting here
        // do NOT unset msgrepeat which leads to an approval immediately triggering
        // into another ewic transaction
        $this->conf->set('msgrepeat', 0);
        if (FormLib::get('xml-resp', false) !== false) {
            $xml = FormLib::get('xml-resp');
            $log = new LaneLogger();
            $log->debug("WIC RESPONSE: " . $xml);
            if ($this->conf->get('EWicStep') == 0) {
                $e2e = new MercuryDC();
                $this->conf->set('EWicLast4', false);
                $success = $e2e->handleResponseDataCapBalance($xml);
                if ($success === PaycardLib::PAYCARD_ERR_OK) {
                    $this->conf->set('EWicStep', 1);
                    $cur = $this->session->get('receiptToggle');
                    $this->conf->set('receiptToggle', 1);
                    $receipt = ReceiptLib::printReceipt('wicSlip', ReceiptLib::receiptNumber());
                    $this->conf->set('receiptToggle', $cur);
                    ReceiptLib::writeLine($receipt);
                    UdpComm::udpSend('termYesNoContinue eWIC sale?');
                } else {
                    $this->cleanup();
                    $this->change_page(MiscLib::baseURL() . 'gui-modules/boxMsg2.php');
                    return false;
                }
            } elseif ($this->conf->get('EWicStep') == 1) {
                $e2e = new MercuryDC();
                $success = $e2e->handleResponseDataCap($xml);
                if ($success === PaycardLib::PAYCARD_ERR_OK) {
                    $this->tenderResponse($xml);
                    $this->change_page(MiscLib::baseURL() . 'gui-modules/pos2.php?reginput=TO&repeat=1');
                } else {
                    $this->change_page(MiscLib::baseURL() . 'gui-modules/boxMsg2.php');
                }
                $this->cleanup();
                return false;
            }
        } elseif (FormLib::get('reginput', false) === false) {
            $this->cleanup();
            $this->runTransaction = true;
            $this->addOnloadCommand('balanceSubmit();');
        } else {
            $input = FormLib::get('reginput', false);
            if (strtoupper($input) == 'CL') {
                $this->cleanup();
                $this->change_page(MiscLib::baseURL() . 'gui-modules/pos2.php');
                return false;
            } elseif (strtoupper($input) == 'RP') {
                $cur = $this->session->get('receiptToggle');
                $this->conf->set('receiptToggle', 1);
                $receipt = ReceiptLib::printReceipt('wicSlip', ReceiptLib::receiptNumber());
                $this->conf->set('receiptToggle', $cur);
                ReceiptLib::writeLine($receipt);
                return true;
            } elseif ($input === '' && $this->conf->get('EWicStep') == 1) {
                $this->runTransaction = true;
                $this->addOnloadCommand('emvSubmit();');
            }
        }

        return true;
    }

    private function cleanup()
    {
        $this->conf->reset();
        $this->conf->set('EWicStep', 0);
        $this->conf->set('EWicLast4', false);
    }

    /**
     * Adjust item pricing on anything approved for a lower
     * amount and then add the tender record
     */
    private function tenderResponse($xml)
    {
        $dbc = Database::tDataConnect();
        $setUnit = $dbc->prepare("UPDATE localtemptrans SET unitPrice=?, charflag='WO' WHERE upc=?");
        $setTotal = $dbc->prepare("UPDATE localtemptrans SET total=unitPrice*quantity WHERE upc=?");
        $translateP = $dbc->prepare("SELECT * FROM " . $this->conf->get('pDatabase') . $dbc->sep() . "EWicItems WHERE upcCheck=?");
        $aliasP = $dbc->prepare("SELECT * FROM " . $this->conf->get('pDatabase') . $dbc->sep() . "EWicItems WHERE alias=?");
        $better = new BetterXmlData($xml);
        $i = 1;
        /**
          This currently does not behave as spec'd. When an item
          is approved for a lower price that status does not say
          "approved for a lower price"; it just says "approved"
          like any other item. The authorized amount in the response
          also does not factor in price reductions so on net it
          works out OK. Not adjusting the prices and tendering
          the authorized amount in effect pays for the appropriate
          items. If the authorized amount in the reponse changes
          to the sum of the approved price then there'll have to
          be an adjustment along the lines of this loop.
        */
        /*
        while (true) {
            $status = $better->query('/RStream/TranResponse/ItemData/ItemStatus' . $i);
            $status = strtolower($status);
            if ($status === 'approved for lower price') {
                $price = $better->query('/RStream/TranResponse/ItemData/ItemPrice' . $i);
                $qty = $better->query('/RStream/TranResponse/ItemData/ItemQty' . $i);
                $wicUPC = $better->query('/RStream/TranResponse/ItemData/UPCItem' . $i);
                $wicPLU = $better->query('/RStream/TranResponse/ItemData/PLUItem' . $i);
                if ($wicPLU) {
                    $wicPLU = '1' . str_pad($wicPLU, 16, '0', STR_PAD_LEFT);
                }
                if (!$wicUPC && $wicPLU) {
                    $wicUPC = $wicPLU;
                }
                $unit = $price / $qty;
                $ours = $dbc->getRow($translateP, array($wicUPC));
                if ($ours === false) {
                    $ours = $dbc->getRow($aliasP, array($wicUPC));
                }
                $dbc->execute($setUnit, array($unit, $ours['upc']));
                $dbc->execute($setTotal, array($ours['upc']));
            } elseif ($status === 'approved for lower qty') {
                // this creates kind of a mess if items are rung with *
                // so a single line item might be partly approved
            }

            $i++;
            if ($i > 1000) break;
        }
        */

        $amount = $better->query('/RStream/TranResponse/Amount/Authorize');
        $amount = "" . (-1*$amount);
        $ptP = $dbc->prepare('SELECT MAX(paycardTransactionID) FROM PaycardTransactions');
        $ptID = $dbc->getValue($ptP);
        TransRecord::addFlaggedTender('EWIC', 'EW', $amount, $ptID, 'PT');
    }

    /**
     * Get item data XML for eligible items
     * @param $arr [array] EWic Balance Data
     * @return [string] XML
     */
    private function getItemData($arr)
    {
        $ret = '';
        /**
          Convert balance to a more useful format.
          Key is either:
           - categoryID:subCategoryID
           - categoryID

          Value is the quantity avaliable
        */
        $categories = array();
        foreach ($arr as $balanceRecord) {
            if (isset($balanceRecord['subcat']) && $balanceRecord['qty'] > 0) {
                $key = $balanceRecord['cat']['eWicCategoryID']
                    . ':' . $balanceRecord['subcat']['eWicSubCategoryID'];
                $categories[$key] = $balanceRecord['qty'];
            } elseif ($balanceRecord['qty'] > 0) {
                $key = $balanceRecord['cat']['eWicCategoryID'];
                $categories[$key] = $balanceRecord['qty'];
            }
        }

        $dbc = Database::tDataConnect();
        $appliedP = $dbc->prepare("
            SELECT c.trans_id
            FROM couponApplied AS c
                INNER JOIN localtemptrans AS t ON c.trans_id=t.trans_id
            WHERE t.upc=?");
        $couponP = $dbc->prepare("SELECT SUM(total) FROM localtemptrans WHERE upc LIKE ?");
        $res = $dbc->query('SELECT SUM(t.quantity) AS qty, SUM(t.total) AS ttl,
            CASE WHEN e.alias IS NOT NULL THEN e.alias ELSE e.upc END AS upc,
            e.upcCheck, e.eWicCategoryID, e.eWicSubCategoryID, e.broadband, e.multiplier,
            MAX(discountable) AS discountable, MAX(percentDiscount) AS percentDiscount
            FROM localtemptrans AS t
                INNER JOIN ' . $this->conf->get('pDatabase') . $dbc->sep() . 'EWicItems AS e ON t.upc=e.upc
            GROUP BY
            CASE WHEN e.alias IS NOT NULL THEN e.alias ELSE e.upc END,
            e.upcCheck, e.eWicCategoryID, e.eWicSubCategoryID, e.broadband, e.multiplier
            ORDER BY e.broadband, e.multiplier DESC');
        $i = 1;
        $total = 0;
        $couponCache = array();
        while ($row = $dbc->fetchRow($res)) {
            $upc = $row['upcCheck'];
            $tag = 'UPCItem';
            if (strlen($upc) > 16) {
                $upc = substr($upc, -5);
                $tag = 'PLUItem';
            }
            $manu = substr($row['upc'], 3, 5);
            if ($manu != '00000' && !isset($couponCache[$manu])) {
                $couponApplied = $dbc->getValue($appliedP, array($row['upc']));
                if ($couponApplied) {
                    $couponValue = $dbc->getValue($couponP, array('005' . $manu . '%'));
                    $row['ttl'] += $couponValue; // coupons are negative
                    if ($row['ttl'] < 0.005) { // coupon made item free
                        continue;
                    }
                }
                $couponCache[$manu] = true;
            }
            if ($row['discountable'] && $row['percentDiscount']) {
                $row['ttl'] *= (1 - ($row['percentDiscount'] / 100));
            }
            if ($row['ttl'] < 0.005) {
                // item was voided, free w/ coupon, or otherwise
                // had cost drop to $0
                continue;
            }
            /**
                Check if the whole category is available. If not
                check whether the specific subcategory is availble.
                Keep $key to later decrement the appropriate quantity
            */
            $add = false;
            $key = $row['eWicCategoryID'];
            if ($row['broadband'] && isset($categories[$key]) && $categories[$key] > 0) {
                $add = true;
            } else {
                $key = $row['eWicCategoryID'] . ':' . $row['eWicSubCategoryID'];
                if (isset($categories[$key]) && $categories[$key] > 0) {
                    $add = true;
                }
            }
            if ($add && $row['eWicCategoryID'] == 19) {
                if ($row['ttl'] > $categories[$key]) {
                    $row['ttl'] = $categories[$key];
                }
                $ret .= "<{$tag}{$i}>{$upc}</{$tag}{$i}>";
                $ret .= "<ItemQty{$i}>" . sprintf('%.2f', $row['ttl']) . "</ItemQty{$i}>";
                $ret .= "<ItemPrice{$i}>" . sprintf('%.2f', 1) . "</ItemPrice{$i}>";
                $i++;
                $total += $row['ttl'];
                $categories[$key] -= $row['ttl'];
            } elseif ($add) {
                while ($row['qty'] * $row['multiplier'] > $categories[$key]) {
                    $price = $row['ttl'] / $row['qty'];
                    $row['qty'] -= 1;
                    $row['ttl'] -= $price;
                }
                // package size exceeds remaing quantity
                if ($row['qty'] <= 0) {
                    continue;
                }
                $ret .= "<{$tag}{$i}>{$upc}</{$tag}{$i}>";
                $ret .= "<ItemQty{$i}>" . sprintf('%.2f', $row['qty']) . "</ItemQty{$i}>";
                $ret .= "<ItemPrice{$i}>" . sprintf('%.2f', $row['ttl'] / $row['qty']) . "</ItemPrice{$i}>";
                $i++;
                $total += $row['ttl'];
                $categories[$key] -= $row['qty'] * $row['multiplier'];
            }
        }
        $this->conf->set('paycard_amount', $total);

        return $ret;
    }

    function head_content()
    {
        $url = MiscLib::baseURL();
        echo '<script type="text/javascript" src="' . $url . '/js/singleSubmit.js"></script>';
        echo '<script type="text/javascript" src="../js/emv.js?date=20210325"></script>';
        if (!$this->runTransaction) {
            echo <<<HTML
<script type="text/javascript">
function parseWrapper(str) {
    if (str.toUpperCase() == 'TERMYES') {
        $('#reginput').val('');
        $('#formlocal').submit();
    }  else if (str.toUpperCase() == 'TERMNO') {
        $('#reginput').val('CL');
        $('#formlocal').submit();
    }
}
</script>
HTML;
            return '';
        }
        $e2e = new MercuryDC($this->conf->get('PaycardsDatacapName'));
        $manual = FormLib::get('manual') ? true : false;
        $xml = '';
        if ($this->conf->get('EWicStep') == 0) {
            $this->conf->set('paycard_id', $this->conf->get('LastID')+1);
            $this->conf->set('EWicAcAcq', false);
            $xml = $e2e->prepareDataCapBalance('EWICVAL', $manual);
        } elseif ($this->conf->get('EWicStep') == 1) {
            $this->conf->set('paycard_id', $this->conf->get('LastID')+1);
            $xml = $e2e->prepareDataCapWic($this->getItemData($this->conf->get('EWicBalance')), 'PreAuthCapture', $this->conf->get('EWicLast4'));
        }
        $log = new LaneLogger();
        $log->debug("WIC REQUEST: " . $xml);
        ?>
<script type="text/javascript">
function balanceSubmit() {
    emv.setWaitingMsg('Getting balance');
    emv.showProcessing('div.baseHeight');
    var xmlData = '<?php echo json_encode($xml); ?>';
    <?php if ($this->conf->Get('training') == 1) { ?>
    emv.setURL('../ajax/AjaxPaycardTest.php');
    <?php } ?>
    emv.submit(xmlData);
    $(document).keyup(checkForCancel);
}
function emvSubmit() {
    emv.setWaitingMsg('Authorizing purchase');
    emv.showProcessing('div.baseHeight');
    var xmlData = '<?php echo json_encode($xml); ?>';
    // POST XML request to driver using AJAX
    if (xmlData == '"Error"') { // failed to save request info in database
        location = '<?php echo MiscLib::baseURL(); ?>gui-modules/boxMsg2.php';
        return false;
    }
    <?php if ($this->conf->Get('training') == 1) { ?>
    emv.setURL('../ajax/AjaxPaycardTest.php');
    <?php } ?>
    emv.submit(xmlData);
    $(document).keyup(checkForCancel);
}
var ccKey1;
var ccKey2;
function checkForCancel(ev) {
    var jsKey = ev.which ? ev.which : ev.keyCode;
    if (jsKey == 13 && (ccKey2 == 99 || ccKey2 == 67) && (ccKey1 == 108 || ccKey1 == 76)) {
        $.ajax({
            url: 'PaycardEmvPage.php',
            data: 'cancel=1'
        }).done(function(){
        });
    }
    ccKey2 = ccKey1;
    ccKey1 = jsKey;
}
</script>
        <?php
    }

    public function getFooter()
    {
        $ret = "<div id=\"footer\">"
            . DisplayLib::printfooter()
            . "</div>\n"
            . "</div>\n";
        ob_start();
        $this->scale_box();
        $this->scanner_scale_polling(true);
        $ret .= ob_get_clean();

        return $ret;
    }

    function body_content()
    {
        echo '<div class="baseHeight">';
        if ($this->conf->get('EWicStep') == 1) {
            $msg = '<div style="font-size: 85%">';
            foreach ($this->conf->get('EWicBalance') as $line) {
                $msg .= $line['qty'] . ' ' . $line['cat']['name'] . '<br />';
            }
            $msg .= '</div>';
            echo PaycardLib::paycardMsgBox('Current Balance', $msg, 'wait for customer to continue, [clear] to cancel');
        }
        echo '</div>';
    }
}

AutoLoader::dispatch();