Bug arreglado: Pedidos pagados con PayPal que dan ‘error de pedido’ en PrestaShop


bug-prestashop-modulo-paypal-redondeo-1200

Problema: Cuando un cliente paga con PayPal da ‘error de pedido’

Últimamente nos estamos encontrando con este problema con varios clientes. Tras realizar un pedido y pagar con PayPal el pedido en el back office se muestra como “error de pedido”, pero el pago se ha realizado. Tras investigar, hemos descubierto que todo se debe al redondeo.

Los errores de redondeo de PrestaShop ya son conocidos por los usuarios que han usado o usan la versión 1.5. Desde la versión 1.6.0.12 de PrestaShop se solucionó el problema de que los productos se redondeasen línea por línea. Es más, en PrestaMarketing desarrollamos en su momento un módulo gratuito para solucionar este problema de redondeo con PrestaShop.

Pues bien, PrestaShop cumplió y solucionó su parte, pero hemos visto que ahora el problema de redondeo lo tenemos con PayPal si tenemos configurada nuestra tienda para que redondee por totales en lugar de por productos.

Cuando PrestaShop envía a la plataforma de pago cualquier pedido, independientemente de cómo tengamos configurado el redondeo en PrestaShop, PayPal siempre redondea el precio de los productos línea por línea. Esto hace que el total no coincida con el importe que tenemos en PrestaShop y que se produzca un error en nuestra tienda online cuando el usuario vuelve de PayPal a nuestro comercio una vez realizado el pago.

Este error provoca que el pedido no se dé como válido, pero que el cobro desde PayPal sí que se realice. Obviamente, esto es un gran problema de confianza con los clientes de nuestra tienda online.

Ejemplo del problema de PayPal y PrestaShop con el redondeo

En mi tienda, el redondeo lo realizo sobre los totales en lugar de por producto.

Tengo un producto llamado Cerveza Artesana. Su precio de venta sin IVA es de 2,26€ y el tipo de IVA a aplicar es del 10%

Si compro 24 unidades de dicho producto, con un gasto de envío de 8,86€ (IVA no incluido 21%) los cálculos quedan así:

bug-paypal-01

  • 24 unidades X 2.26 = 54.24
  • Gastos de envio: 1 X 8.86 = 8.86

Cálculo del IVA:

  • Productos: 54.24 -> 10% = 5.42 = 59.66
  • Transporte: 8.86 -> 21% = 1.86 = 10.72

Total con IVA:

  • 59.66 + 10.72 = 70.38

Decido pagar este pedido con PayPal. Veamos lo que pasa:

bug-paypal-02

Como vemos, PayPal en su desglose indica que hay que cobrar 70.48€, cuando en realidad el precio del pedido es de 70.38€.
¿Dónde está el problema?

Simplemente, PayPal redondea línea por línea y calcula así:

  • Productos: 2.26 +10% IVA (0.226 -> 0.23) + 0.23 = 2.49 X 24 = 59.76€
  • Trasporte: 8.86 + 21% IVA (1.860 -> 1.86) + 1.86 = 10.72€
  • TOTAL: 10.72€ + 59.76€ = 70.48€

Al redondear el producto línea por línea, se produce un desajuste de 10 céntimos. Cuando volvemos a la tienda tras realizar el pago PrestaShop detecta la incoherencia entre el importe del pedido y el importe cobrado por PayPal y se produce un error que deja al pedido como no válido y a nuestro cliente desconcertado.

Solución al ‘error de pedido’ al pargar con PayPal

Pues bien, si el problema ocurre cuando PayPal desglosa nuestra compra al redondear cada línea de detalle (y nuestra tienda hace el redondeo en los totales), la solución pasa por que no haga dicha operación y que le pasemos la cantidad exacta del producto que ha de cobrar. Para ello tenemos que modificar el siguiente archivo dentro del módulo de PayPal:

  • paypal/express_checkout/procces.php

Tenemos que modificar dos funciones, y la primera de ellas es la función setProductsList:

private function setProductsList(&$fields, &$index, &$total) {
foreach ($this->product_list as $product) {
$fields['L_PAYMENTREQUEST_0_NUMBER'.++$index] = (int) $product['id_product'];
$fields['L_PAYMENTREQUEST_0_NAME'.$index] = $product['name'];
if (isset($product['attributes']) && (empty($product['attributes']) === false)) {
$fields['L_PAYMENTREQUEST_0_NAME'.$index] .= ' - '.$product['attributes'];
}
$fields['L_PAYMENTREQUEST_0_DESC'.$index] = Tools::substr(strip_tags($product['description_short']), 0, 50).'...';
$fields['L_PAYMENTREQUEST_0_AMT'.$index] = Tools::ps_round($product['price_wt'], $this->decimals);
$fields['L_PAYMENTREQUEST_0_QTY'.$index] = $product['quantity'];
$total = $total + ($fields['L_PAYMENTREQUEST_0_AMT'.$index] * $product['quantity']);
}
}

Esta función se encarga de enviar los detalles de los productos comprados a PayPal, por lo tanto su modificación no supondrá ninguna brecha de seguridad del módulo. La modificación a realizar es la siguiente:

private function setProductsList(&$fields, &$index, &$total)
{
foreach ($this->product_list as $product) {
$fields['L_PAYMENTREQUEST_0_NUMBER'.++$index] = 1;
$fields['L_PAYMENTREQUEST_0_NAME'.$index] = 'Total Productos '; // o el texto que deseemos
$fields['L_PAYMENTREQUEST_0_AMT'.$index] = $this->context->cart->getOrderTotal(true,Cart::ONLY_PRODUCTS);
//mandamos el total de los productos con IVA incluido
$fields['L_PAYMENTREQUEST_0_QTY'.$index] = 1;
$total = $total + ($fields['L_PAYMENTREQUEST_0_AMT'.$index] * 1);
break;
}
}

Dentro del mismo archivo, hemos de modificar también la función getTotalPaid:

public function getTotalPaid()
{
$total = 0.00;
foreach ($this->product_list as $product) {
$price = Tools::ps_round($product['price_wt'], $this->decimals);
$quantity = Tools::ps_round($product['quantity'], $this->decimals);
$total = Tools::ps_round($total + ($price * $quantity), $this->decimals);
}
if ($this->context->cart->gift == 1) {
$total = Tools::ps_round($total + $this->getGiftWrappingPrice(), $this->decimals);
}
if (version_compare(_PS_VERSION_, '1.5', '<')) { $discounts = $this->context->cart->getDiscounts();
$shipping_cost = $this->context->cart->getOrderShippingCost();
} else {
$discounts = $this->context->cart->getCartRules();
$shipping_cost = $this->context->cart->getTotalShippingCost();
}
if (count($discounts) > 0) {
foreach ($discounts as $product) {
$price = -1 * Tools::ps_round($product['value_real'], $this->decimals);
$total = Tools::ps_round($total + $price, $this->decimals);
}
}
return Tools::ps_round($shipping_cost, $this->decimals) + $total;
}

Por esta:

public function getTotalPaid()
{
$total = 0.00;
$total = $total + $this->context->cart->getOrderTotal(true,Cart::ONLY_PRODUCTS);
if ($this->context->cart->gift == 1) {
$total = Tools::ps_round($total + $this->getGiftWrappingPrice(), $this->decimals);
}
if (version_compare(_PS_VERSION_, '1.5', '<')) { $discounts = $this->context->cart->getDiscounts();
$shipping_cost = $this->context->cart->getOrderShippingCost();
} else {
$discounts = $this->context->cart->getCartRules();
$shipping_cost = $this->context->cart->getTotalShippingCost();
}
if (count($discounts) > 0) {
foreach ($discounts as $product) {
$price = -1 * Tools::ps_round($product['value_real'], $this->decimals);
$total = Tools::ps_round($total + $price, $this->decimals);
}
}
return Tools::ps_round($shipping_cost, $this->decimals) + $total;
}

Esta modificación es exactamente para lo mismo, para que aplique el total productos en vez del desglose una vez confirmado el pedido.

Con la modificación de estas dos funciones lo que hacemos es enviarle a PayPal el total de productos con el IVA ya incluido, así evitamos que genere el problema del redondeo al desglosar.

Inconveniente: no se muestra el desglose de productos a la hora de realizar el pago y es sustituido por un “Total Productos”. No obstante, consideramos que es un mal menor, ya que vemos peor que se muestre el desglose pero los totales no coincidan; eso puede crear confusión y falta de confianza en nuestros clientes.

Entonces, la vista de PayPal con el parche aplicado quedaría así:

bug-paypal-03

Como vemos ahora, el total a cobrar coincide y el problema queda resuelto.

Si actualizas el módulo de PayPal (algo que es más que recomendable), tendrás que acordarte de aplicar de nuevo este parche tal y como hemos descrito. Te recordamos que no supondrá ninguna brecha de seguridad en tu tienda ya que no afecta en absoluto al funcionamiento de la API de PayPal.

Esperamos que esta información te sirva de ayuda.

Y recuerda, si esto te resulta complicado, podemos ayudarte con nuestro servicio de Soporte Profesional para PrestaShop. Contacta con nosotros y nos ocupamos de solucionártelo.


Este artículo y más sobre la actualidad de PrestaShop y eCoommerce en PrestaMarketing®