Why does this only work in PHP5.6 not PHP7.1?

Hey guys,

I would like to know why this code fails on the totalAmountCheck function in PHP7.1 but not PHP5.6

<?php
/**
* 2007-2014 PrestaShop
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
* @author    PrestaShop SA <contact@prestashop.com>
* @copyright 2007-2014 PrestaShop SA
* @license   http://addons.prestashop.com/en/content/12-terms-and-conditions-of-use
* International Registered Trademark & Property of PrestaShop SA
*/

include(dirname(__FILE__).'/../../config/config.inc.php');
include(dirname(__FILE__).'/../../init.php');
include(dirname(__FILE__).'/npaypalpro.php');

$paypalpro = new NpaypalPro();

function fillShipping($shipping_address)
{
	$shipping = array();
	$shipping['recipient_name'] = $shipping_address->address1;
	$shipping['line1'] = $shipping_address->address1;
	$shipping['city'] = $shipping_address->city;
	$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('SELECT iso_code FROM '
	._DB_PREFIX_.'country WHERE id_country = '.$shipping_address->id_country);

	$shipping['country_code'] = $result['iso_code'];
	$shipping['postal_code'] = $shipping_address->postcode;
	return $shipping;
}

if ($paypalpro->active)
{
	$context = Context::getContext();

	/* Loading current billing address from PrestaShop */
	if (!isset($context->cart->id)
	|| empty($context->cart->id)
	|| !isset($context->cart->id_address_invoice)
	|| empty($context->cart->id_address_invoice))
		die('No active shopping cart');
	$billing_address = new Address((int)$context->cart->id_address_invoice, false);
	$shipping_address = new Address((int)$context->cart->id_address_delivery, false);

	if (isset($billing_address) && isset($shipping_address) && isset($context->customer->email))
	{
		$shipping = fillShipping($shipping_address);

		$currency = $context->currency->iso_code;

		$transaction = array();
		$transaction['intent'] = 'sale';
		$transaction['payer'] = array();
		$transaction['payer']['payment_method'] = 'credit_card';
		$transaction['payer']['payer_info'] = array();
		$transaction['payer']['payer_info']['email'] = $context->customer->email;
		$transaction['payer']['payer_info']['shipping_address'] = $shipping;

		/* Retrieving Country and State ISO codes */
		$billing_country = new Country((int)$billing_address->id_country);
		if (isset($billing_country))
		{
			if ($billing_address->id_state)
				$billing_state = new State((int)$billing_address->id_state);
			if ($shipping_address->id_state)
				$shipping_state = new State((int)$shipping_address->id_state);
		}

		$cart = new Cart((int)$context->cart->id);

		if (isset($cart))
		{
            $items = array();
			$tab = $cart->getProducts();
            $discounts = $cart->getDiscounts();

			/* Update shipping price */
			if (preg_match('/\.(\d{2})$|^\d*$/', $cart->getTotalShippingCost()))
				$shipping_price = $cart->getTotalShippingCost();
			else
				$shipping_price = $cart->getTotalShippingCost().'0';

			$shipping_item = array(
				'quantity' => 1,
				'name' => 'Shipping',
				'price' => $shipping_price,
				'currency' => $currency
			);

			if ($cart->gift)
			{
				/* Update gift price */
				if (preg_match('/\.(\d{2})$|^\d*$/', $cart->getGiftWrappingPrice()))
					$gift_price = $cart->getGiftWrappingPrice();
				else
					$gift_price = $cart->getGiftWrappingPrice().'0';

				$gift = array(
					'quantity' => 1,
					'name' => 'Gift',
					'price' => $gift_price,
					'currency' => $currency
				);
			}

			if (isset($tab))
			{
				foreach ($tab as $product)
				{
					$item = array();
					$item['quantity'] = $product['quantity'];
					$item['name'] = $product['name'];
                    $price = round($product['price_wt'], 2);
					if (preg_match('/\.(\d{2})$|^\d*$/', $price))
						$item_price = $price;
					else
						$item_price = $price.'0';
					$item['price'] = $item_price;
					$item['currency'] = $currency;
					array_push($items, $item);
				}
				array_push($items, $shipping_item);
				if ($cart->gift)
					array_push($items, $gift);
			}

            /* get Discount Price */
            if (!empty($discounts))
            {
                foreach ($discounts as $discount)
                {
                    /* Update discount price */
                    $formDiscount = number_format($discount['value_tax_exc'], 2);
                    if (preg_match('/\.(\d{2})$|^\d*$/', $formDiscount))
                        $discount_price = '-'.$formDiscount;
                    else
                        $discount_price = '-'.$formDiscount.'0';

                    $disc = array(
                        'quantity' => 1,
                        'name' => $discount['name'],
                        'price' => $discount_price,
                        'currency' => $currency
                    );

                }
                array_push($items, $disc);
            }

			$item_list = array();
			$item_list['items'] = $items;
			$item_list['shipping_address'] = $shipping;

			$credit_card = array();
			$credit_card['number'] = preg_replace('/\s+/', '', Tools::getValue('paypal_pro_cc_number'));
			$credit_card['type'] = Tools::strtolower(Tools::getValue('paypal_pro_cc_type'));
			$credit_card['expire_month'] = (int)Tools::getValue('paypal_pro_cc_exp_month');
			$credit_card['expire_year'] = (int)Tools::getValue('paypal_pro_cc_year');
			$credit_card['cvv2'] = Tools::getValue('paypal_pro_cc_cvv');
			$credit_card['first_name'] = Tools::substr(Tools::getValue('paypal_pro_cc_firstname'), 0, 25);
			$credit_card['last_name'] = Tools::substr(Tools::getValue('paypal_pro_cc_lastname'), 0, 25);
			$credit_card['billing_address'] = array();
			$credit_card['billing_address']['line1'] = $billing_address->address1;
			if (isset($billing_address->address2) && !empty($billing_address->address2))
				$credit_card['billing_address']['line2'] = $billing_address->address2;
			$credit_card['billing_address']['city'] = $billing_address->city;
			if ($billing_address->id_state && isset($billing_state->iso_code))
				$credit_card['billing_address']['state'] = Tools::strtoupper(Tools::substr($billing_state->iso_code, 0, 2));
			$credit_card['billing_address']['postal_code'] = $billing_address->postcode;
			$credit_card['billing_address']['country_code'] = Tools::strtoupper(Tools::substr($billing_country->iso_code, 0, 2));
			$transaction['payer']['funding_instruments'] = array(array('credit_card' => $credit_card));

			$amount = array();
			if (preg_match('/\.(\d{2})$|^\d*$/', $context->cart->getOrderTotal(true)))
				$amount['total'] = $context->cart->getOrderTotal(true);
			else
				$amount['total'] = $context->cart->getOrderTotal(true).'0';
			$amount['currency'] = $context->currency->iso_code;

            /* Total Amount check */
            $diff = totalAmountCheck($amount['total'], $item_list['items']); // this verification is made because of the US taxes

            /* If amountcheck != $amount */
            if ($diff != 0)
            {
                $price = $diff > 0 ? '-'.$diff : abs($diff);
                $item = array();
                $item['quantity'] = 1;
                $item['name'] = 'Voucher taxes';
                $item['price'] = $price;
                $item['currency'] = $currency;
                array_push($item_list['items'], $item);
            }

			$transaction['transactions'] = array(array('amount' => $amount, 'description' => 'PrestaShop - Customer: '
			.$context->customer->email.' | Shopping Cart ID: '.(int)$context->cart->id, 'item_list' => $item_list));

			/* Make payment */
			$paypalpro->apiRequest('v1/payments/payment', $transaction);
		}
	}
}

function totalAmountCheck($amount, $items)
{
    $amountCheck = $diff = 0;

    foreach ($items as $item)
        $amountCheck += number_format((int) $item['quantity'] * (float) $item['price'], 2);

    if ($amountCheck != $amount)
        $diff = number_format($amountCheck - $amount, 2);

    return $diff;

}

Not sure if the LOGs help debug it

Field: transactions[0].amount.total Issue: Value exceeds max length of 10. — Field: transactions[0].amount.total Issue: Currency amount must be non-negative number and can optionally contain exactly two decimal places separated by '.', optional thousands separator ',', limited to seven digits before the decimal point. — cart id: 5104

Is PHP giving any errors?

Got this one

30-Oct-2018 10:37:19 Australia/Sydney] PHP Notice: A non well formed numeric value encountered in /home/public_html/modules/npaypalpro/ajax.php on line 214

Line 214 is

$amountCheck += number_format($item['quantity'] * $item['price'], 2);

but tried to change it to

$amountCheck += number_format((int) $item['quantity'] * (float) $item['price'], 2);

and still no good

My guess is it isn’t the type casting that’s causing the non well formed but passing an expression. (doesn’t seem likely, but maybe)

Any better if you put that inside parentheses? eg.

$amountCheck += number_format( ($item['quantity'] * $item['price']), 2);

No still throws up an error :frowning: has me stumped because if I add a PHP5.6 handler to the directory it works fine

Payment declined. Unknown error, please use another card or contact us.

Here is a link to the whole PHP module
https://1drv.ms/u/s!ArreVlBudIUVqiSpVOc-eLNnt_Ol

Sorry, but I can’t download and open zip files.

It might be because number_format can no longer return negative

http://php.net/manual/en/function.number-format.php

7.2.0 number_format() was changed to not being able to return -0 , previously -0 could be returned for cases like where number would be -0.01 .

Check the values of those variables to see if that is the problem. A quick test could be to try this to force to positive

$amountCheck += number_format( abs($item['quantity']) * abs($item['price']), 2);

I thought that as well but didn’t have any luck with it

Active support for PHP 7.1 ends today, so it would be wise to target PHP 7.2 instead of 7.1.

See https://secure.php.net/supported-versions.php

1 Like

Brutal, not sure if Prestashop 1.6.1.23 supports PHP 7.2

The PHP Manual states that the first parameter expected is a float:

string number_format ( float $number [, int $decimals = 0 ] )

Try this:

$amountCheck += number_format( (float) $item['quantity'] * $item['price'], 2);

// if the above still fails try this:
$fVal = (float) $item['quantity'] * $item['price'];
var_dump($fVal); // hopefully it is a float
$amountCheck += number_format( $fVal , 2);

Hi John,

Thank you for trying to assist with this one.

Tried the first code

$amountCheck += number_format( (float) $item['quantity'] * $item['price'], 2);

Result = Unknown Error occurred on the front end

Tried the second code and the transaction hangs on “Transaction in progress, please wait.”

Try this which has been tested locally and not just typed without trying on my tablet:

  echo '<br>line ==> ' .__line__;  
  echo '<br>$amountCheck ==> ',  
    $amountCheck = 0;

  echo '<br>$item["quantity"] ==> ',  
    $item["quantity"] = 2;
 
  echo '<br>$item["price"] ==> ',  
    $item["price"] = 1.23;

  echo '<br>$amountCheck ==> ',
    $amountCheck += number_format( (float) $item['quantity'] * $item['price'], 2);

// if the above still fails try this:
  $fVal = (float) $item['quantity'] * $item['price'];
  echo '<br>var_dump($fVal) ==> ';  
    var_dump($fVal); // hopefully it is a float
  
  echo '<br>$amountCheck ==> ',  
    $amountCheck += number_format( $fVal , 2);
  
  echo '<br>var_dump($amountCheck) ==> ';  
  var_dump($amountCheck); // hopefully it is a float

  echo '<br>line ==> ' .__line__;  
  die; // halts further execution


Output

line ==> 38
$amountCheck ==> 0
$item["quantity"] ==> 2
$item["price"] ==> 1.23
$amountCheck ==> 2.46
var_dump($fVal) ==> float(2.46)
$amountCheck ==> 4.92
var_dump($amountCheck) ==> float(4.92)
line ==> 62

Looks as though the script I supplied is correct and “Transaction in progress, please wait” problem occurs later.

Try inserting the following line and gradually move the line deeper into your script until the error is pinpointed.

echo 'line ==>''  .__line__;  die;

Edit:
I forgot to mention about error reporting. Try adding these lines to the top of the script:

<?php 
 // PHP7 specific - fails fast, file-wide
  declare( strict_types=1); 

// applies to this file and all included/required files
  error_reporting( -1 ); // maximum error reporting 
  ini_set( 'display_errors' , 'true'); // display errors and warnings on the screen

// remaining script

Tried adding these lines to the top of the script

<?php 
 // PHP7 specific - fails fast, file-wide
  declare( strict_types=1); 

// applies to this file and all included/required files
  error_reporting( -1 ); // maximum error reporting 
  ini_set( 'display_errors' , 'true'); // display errors and warnings on the screen

// remaining script

Then inserted

function totalAmountCheck($amount, $items)
{
    $amountCheck = $diff = 0;

    foreach ($items as $item)
         echo '<br>line ==> ' .__line__;  
  echo '<br>$amountCheck ==> ',  
    $amountCheck = 0;

  echo '<br>$item["quantity"] ==> ',  
    $item["quantity"] = 2;
 
  echo '<br>$item["price"] ==> ',  
    $item["price"] = 1.23;

  echo '<br>$amountCheck ==> ',
    $amountCheck += number_format( (float) $item['quantity'] * $item['price'], 2);
        
    if ($amountCheck != $amount)
        $diff = number_format($amountCheck - $amount, 2);

    return $diff;

}

Result error_log

[02-Dec-2018 15:27:38 Australia/Sydney] PHP Fatal error:  Uncaught TypeError: preg_match() expects parameter 2 to be string, integer given in /home/public_html/DEV/modules/npaypalpro/ajax.php:93
Stack trace:
#0 /home/public_html/DEV/modules/npaypalpro/ajax.php(93): preg_match('/\\.(\\d{2})$|^\\d...', 0)
#1 {main}
  thrown in /home/public_html/DEV/modules/npaypalpro/ajax.php on line 93

It looks as though your original question has been solved.

I just noticed the script states: 2007-2014 PrestaShop! Perhaps there is a later version which is PHP7 friendly.

Your Post #14 errors states there is a problem on line 93 in the following file:

/home/public_html/DEV/modules/npaypalpro/ajax.php

Try inserting the following line and gradually move the line deeper into your script until the error is pinpointed.

echo 'line ==> '  .__line__;  die; // SEE EDIT BELOW

Please raise another Topic once you have pinpointed the problem which makes the errors appear.

Edit:
Removed extra single quote;

Hi John,

So you just need to add

echo 'line ==>''  .__line__;  die;

in the ajax.php code on every line until error reveals itself?

Do you need to modify this code at all?

echo 'line ==>''  .__line__;  die;

Must be this line that it doesn’t like, Line 89

if (preg_match('/\.(\d{2})$|^\d*$/', $cart->getTotalShippingCost()))
				$shipping_price = $cart->getTotalShippingCost();

Yes unfortunately I had an too many single quotes.

This is the correct syntax:

echo 'line ==> '  .__line__;  die;
PHP Fatal error:  Uncaught TypeError: preg_match() expects parameter 2 to be string, integer given in /home/public_html/DEV/modules/npaypalpro/ajax.php:89

It expects it to be a string but it’s being given an integer

Sorry I cannot help because I avoid preg_match(…) mostly because it is far too cryptic and the same result can be achieved using verbose script which is easier to debug. I believe the processing speed difference is negligible.

The error:

preg_match() expects parameter 2 to be string, integer given in /home/public_html/DEV/modules/npaypalpro/ajax.php:89

$tmp1 = $cart->getTotalShippingCost();
var_dump($tmp1); // LOOKS LIKE IT IS AN INTEGER AND NOT A STRING

$tmp1 = (string) $tmp1;

if ( preg_match('/\.(\d{2})$|^\d*$/',   $tmp1 ) ) 
{
  $shipping_price = $cart->getTotalShippingCost();
}

Isolate the function result and use var_dump( $result ); to view whether it is an integer or a string;