mage2pro / klarna

Klarna integration with Magento 2
https://upwork.com/fl/mage2pro
3 stars 5 forks source link

Klarna is not working with Custom line totals #8

Open fme-tahir opened 5 years ago

fme-tahir commented 5 years ago

Hi, we are using Klarna on site where it is failing to work with module that plays with order line totals, we recently faced an issue where a module adds a certain amount like extra charges and also subtracts a certain amount (based on some business logic) . The module works perfectly with Paypal, amazon and couple of other payment methods used on site.

Customers should see the checkout page and place order with Klarna payment method, like other payment methods do, with updated Order Grand total.

Customers see a blank Checkout page. whereas in logs it throws an error as following: _Order line totals do not total orderamount - xxxx != xxx as soon i disable the klarna payment method, site is back on track.

There should be a way like Paypal, to allow system to inject custom line totals, with Total Label, Total code and (+/-) amount Or atleast it should work with updated totals.

thanks,

mauveine commented 5 years ago

I have the same issue, but while using a coupon code. The error appears when I go on checkout page and I get a blank.

fme-tahir commented 5 years ago

I was able to fix the issue, it was matter of going through the logic used by Klarna, you have to use klarna.xml in youmodule/etc that should inject the custom total to payment and then place the model file of that injection at yourmodule/Model/.. The format of both files can be copied from core klarna files.

bzneil commented 4 years ago

I was able to fix the issue, it was matter of going through the logic used by Klarna, you have to use klarna.xml in youmodule/etc that should inject the custom total to payment and then place the model file of that injection at yourmodule/Model/.. The format of both files can be copied from core klarna files.

Hi - don't suppose you'd care to expand on this. I'm having the same issue but didn't quite follow your solution. Thanks

fme-tahir commented 4 years ago

Hi - don't suppose you'd care to expand on this. I'm having the same issue but didn't quite follow your solution. Thanks

create klarna.xml in etc folder, with code as below:

<klarna xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Klarna_Core:etc/klarna.xsd">
    <order_lines id="payments">
        <line id="custom_fee" class="YourVendor\CustomFee\Model\Klarna\Orderline\CustomFee"/>
    </order_lines>
</klarna>

Then create a Model Class mentioned above named CustomFee.php

namespace YourVendor\CustomFee\Model\Klarna\Orderline;

use Klarna\Core\Api\BuilderInterface;
use Klarna\Core\Helper\ConfigHelper;
use Klarna\Core\Helper\DataConverter;
use Klarna\Core\Helper\KlarnaConfig;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\DataObjectFactory;
use Magento\Tax\Model\Calculation;
use Klarna\Core\Model\Fpt\Rate;

/**
 * Class CustomFee
 *
 * @package Klarna\Core\Model\Checkout\Orderline
 */
class CustomFee extends \Klarna\Core\Model\Checkout\Orderline\AbstractLine
{

    const ITEM_TYPE_SURCHARGE = 'custom_fee';
    /** @var Rate $rate */
    private $rate;
    /** @var ConfigHelper $configHelper */
    private $configHelper;
    private $customHelper;

    /**
     * AbstractLine constructor.
     *
     * @param DataConverter        $helper
     * @param Calculation          $calculator
     * @param ScopeConfigInterface $config
     * @param DataObjectFactory    $dataObjectFactory
     * @param KlarnaConfig         $klarnaConfig
     * @param Rate                 $rate
     */
    public function __construct(
        DataConverter $helper,
        Calculation $calculator,
        ScopeConfigInterface $config,
        DataObjectFactory $dataObjectFactory,
        KlarnaConfig $klarnaConfig,
        ConfigHelper $configHelper,
        Rate $rate
    ) {
        parent::__construct(
            $helper,
            $calculator,
            $config,
            $dataObjectFactory,
            $klarnaConfig
        );
        $this->configHelper = $configHelper;
        $this->rate = $rate;
    }

    /**
     * Collect totals process.
     *
     * @param BuilderInterface $checkout
     *
     * @return $this
     * @throws \Klarna\Core\Exception
     */
    public function collect(BuilderInterface $checkout)
    {
        /** @var \Magento\Sales\Model\AbstractModel|\Magento\Quote\Model\Quote $object */
        $object = $checkout->getObject();

        $address = $this->getAddress($object);
        $store = $this->getStore($object, $address);
        $totals = $address->getTotals();

        if (is_array($totals) && isset($totals['custom_fee'])) {
            $CustomFee = $this->processCustomFeeFromTotals($checkout, $totals, $object, $store);
            $checkout->addData($CustomFee);
        } elseif ($object->getCustomFee() > 0) {
            $CustomFee = $this->processCustomFeeWithoutTotals($checkout, $object, $store);
            $checkout->addData($CustomFee);
        }

    }
    private function processCustomFeeWithoutTotals(BuilderInterface $checkout, $object, $store)
    {
        $CustomFeeLabel = "Custom Fee";

        $amount = $object->getCustomFee();

        /** @noinspection IsEmptyFunctionUsageInspection */
        if (empty($amount) && !empty($object->getCustomFee())) {
            $amount = $object->getBaseSubtotal() - $object->getCustomFee();
        }

        $taxRate = $this->getCustomFeeTaxRate($checkout, $object->getAllVisibleItems());

        if ($taxRate > 100) {
            $taxRate = $taxRate / 100;
        }

        if ($taxRate > 1) {
            $taxRate = $taxRate / 100;
        }

        $taxAmount = 0;

        $unitPrice = $amount;
        $totalAmount = $amount;
        if ($this->klarnaConfig->isSeparateTaxLine($store)) {
            $taxRate = 0;
            $taxAmount = 0;
        } else {
            if ($this->isPriceExcludesVat($store)) {
                $unitPrice += $taxAmount;
                $totalAmount += $taxAmount;
            }
        }
        return [
            'custom_fee_unit_price'   => -abs($this->helper->toApiFloat($unitPrice)),
            'custom_fee_tax_rate'     => $this->helper->toApiFloat($taxRate * 100),
            'custom_fee_total_amount' => -abs($this->helper->toApiFloat($totalAmount)),
            'custom_fee_tax_amount'   => $this->helper->toApiFloat($taxAmount),
            'custom_fee_title'        => $CustomFeeLabel,
            'custom_fee_reference'    => 'custom_fee'

        ];
    }
    private function processCustomFeeFromTotals(BuilderInterface $checkout, $totals, $object, $store)
    {
        $total = $totals['custom_fee'];

        $taxAmount = 0;

        $amount = $total->getValue();
        $taxRate = 0; 

        $unitPrice = $amount;
        $totalAmount = $amount;

        $unitPrice += $taxAmount;
        $totalAmount += $taxAmount;

        return [
            'custom_fee_unit_price'   => -$this->helper->toApiFloat($unitPrice),
            'custom_fee_tax_rate'     => $this->helper->toApiFloat($taxRate),
            'custom_fee_total_amount' => -$this->helper->toApiFloat($totalAmount),
            'custom_fee_tax_amount'   => -$this->helper->toApiFloat($taxAmount),
            'custom_fee_title'        => (string)$total->getTitle(),
            'custom_fee_reference'    => $total->getCode()

        ];
    }
    private function isPriceExcludesVat($store = null)
    {
        $scope = ($store === null ? ScopeConfigInterface::SCOPE_TYPE_DEFAULT : ScopeInterface::SCOPE_STORES);
        return !$this->config->isSetFlag('tax/calculation/price_includes_tax', $scope, $store);
    }
    /**
     * @param $object
     * @return mixed
     */
    private function getAddress($object)
    {
        $address = $object->getShippingAddress();
        if ($address) {
            return $address;
        }
        return $object->getBillingAddress();
    }

    /**
     * @param $object
     * @param $address
     * @return mixed
     */
    private function getStore($object, $address)
    {
        $store = $object->getStore();
        if (!$store && $address->getQuote()) {
            $store = $address->getQuote()->getStore();
        }
        return $store;
    }

    /**
     * Add order details to checkout request
     *
     * @param BuilderInterface $checkout
     *
     * @return $this
     */
    public function fetch(BuilderInterface $checkout)
    {

        if ($checkout->getCustomFeeUnitPrice() != 0) {
            $checkout->addOrderLine(
                [
                    'type'             => 'surchage',
                    'reference'        => $checkout->getCustomFeeReference(),
                    'name'             => $checkout->getCustomFeeTitle(),
                    'quantity'         => 1,
                    'unit_price'       => $checkout->getCustomFeeUnitPrice(),
                    'tax_rate'         => $checkout->getCustomFeeTaxRate(),
                    'total_amount'     => $checkout->getCustomFeeTotalAmount(),
                    'total_tax_amount' => $checkout->getCustomFeeTaxAmount(),
                ]
            );
        }

        return $this;
    }
}
like-vyk commented 4 years ago

I have the same issue, but while using a coupon code. The error appears when I go on checkout page and I get a blank.

We have the same issue. Did you find a fix for it?

opaque01 commented 2 years ago

I have the same issue with configurable products. Magento 2.4.3 p2 Any fixes?

bzneil commented 2 years ago

Not for me, sorry. I gave up and had to remove it as a payment option on our sites

On Fri, Nov 18, 2022 at 11:09 AM opaque01 @.***> wrote:

I have the same issue with configurable products. Magento 2.4.3 p2 Any fixes?

— Reply to this email directly, view it on GitHub https://github.com/mage2pro/klarna/issues/8#issuecomment-1319858539, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABIXUHU2Z4LTCZUFPC4CB3TWI5PVHANCNFSM4HFGWOFQ . You are receiving this because you commented.Message ID: @.***>

opaque01 commented 2 years ago

> I have the same issue with configurable products. Magento 2.4.3 p2 Any fixes?

It is not a problem with configurable products! I also use the extension "Add Free Product to Cart for Magento 2". If a free product is inside the cart the checkout page wont work.

fme-tahir commented 2 years ago

~> I have the same issue with configurable products. Magento 2.4.3 p2 Any fixes?~

It is not a problem with configurable products! I also use the extension "Add Free Product to Cart for Magento 2". If a free product is inside the cart the checkout page wont work.

For any custom fee, you have to follow the steps I mentioned above, that's how it worked for me.

hannes011 commented 1 year ago

Hi everyone,

I have the same error, even though I'm not using a custom total line.

My error occurs when \Klarna\Base\Helper\KlarnaConfig::isSeparateTaxLine is set to true only and if a discount (normal cart rule is sufficient) is applied and prices are configured to include tax! It is caused by the fact, that base_discount_amount contains tax while base_row_total does not - this leads to a partial mix of tax and non-tax prices.

I put quite some effort and time into debugging this issue. The following fix would solve that matter for me (and hopefully also for everyone).

Patch for klarna/module-orderlines in version 1.0.11 (klarna version 1.1.9 / base module version 9.1.10):

diff --git a/Model/Calculator/Item.php b/Model/Calculator/Item.php
index d3ccd66..3c7c567 100644
--- a/Model/Calculator/Item.php
+++ b/Model/Calculator/Item.php
@@ -74,6 +74,11 @@ class Item
         $itemResult['total_amount'] = $this->helper->toApiFloat(
             $item['base_row_total'] - $item['base_discount_amount']
         );
+        if ($this->taxConfig->priceIncludesTax($item['store'])) {  // needed since "base_discount_amount" contains tax if priceIncludesTax == true
+            $itemResult['total_amount'] = $this->helper->toApiFloat(
+                $item['base_row_total_incl_tax'] - $item['base_discount_amount'] - $item['base_tax_amount']
+            );
+        }
         $itemResult['total_discount_amount'] = $this->helper->toApiFloat($item['base_discount_amount']);

         if (!$this->klarnaConfig->isSeparateTaxLine($store)) {

For previous versions of Klarna (e.g. 8.3.6 with core 6.2.4) a similar fix would have to be made to Model/Checkout/Orderline/Items.php in klarna/module-core

I hope this fixes also the custom total line issue... but at least it solves a core bug in Klarna

itsbreadd commented 1 year ago

Hi everyone,

Also come across this issue. Might be worth searching isSeparateTaxLine in your entire code base. We had a free gift module setting this value to true unnecessarily, possibly similar to @opaque01

In our case, it only needs to be true when using the NA Klarna API Endpoint which the Klarna module handles anyway.

Also if you are using a free gift module and sending zero priced products to Klarna instead of products with a 100% discount, you will probably face issues with unit_price.

$_item['unit_price'] = $this->helper->toApiFloat($item->getBasePrice()) ?: $this->helper->toApiFloat($item->getBaseOriginalPrice());

If the getBasePrice is zero it resorts to getBaseOriginalPrice (which contains the full price) but the order line total in the request will still be zero, so Klevu rejects it and the payment method wont show on checkout.

Hope this helps someone.