thephpleague / omnipay-common

Core components for the Omnipay PHP payment processing library
MIT License
330 stars 244 forks source link

Exception: Amount precision is too high for currency. #143

Open lukeholder opened 7 years ago

lukeholder commented 7 years ago

Using the Đồng (VND) currency, which has zero decimal places (zero minor units), returns an exception: "Amount precision is too high for currency." inside the ->getAmount() method.

I believe this commit is causing the error:

https://github.com/thephpleague/omnipay-common/pull/70/commits/529b18fe6b3196b4606b28548cb97ee913eaa914

The relevant line of code changed in the above commit is:

$decimal_count = strlen(substr(strrchr(sprintf('%.8g', $amount), '.'), 1));

with $amount of VND 103500000, (an int of 103500000) returns 6 as the $decimal_count, which is obviously incorrect. This is due to the result of sprintf('%.8g', $amount) being 41035000001.035e+8 which has 6 chars to the right of the decimal.

After restoring the original line:

$decimal_count = strlen(substr(strrchr((string)$amount, '.'), 1));

with $amount of VND 103500000, (an int of 103500000) returns 0 as the $decimal_count, which is the correct number of decimal places, and the transaction succeeds.

I don't quite understand the rationale for the above commit, hoping that experienced omnipay minds can assist.

/cc @judgej

Example 1:

$amount = 100000.3333;
echo "original amount: ".$amount;
echo "<br>";
echo "sprintf formatted amount: ".sprintf('%.8g', $amount);
echo "<br>";
echo "new result: ".strlen(substr(strrchr(sprintf('%.8g', $amount), '.'), 1));
echo "<br>";
echo "original result: ".strlen(substr(strrchr((string)$amount, '.'), 1));

results in:

original amount: 100000.3333
sprintf formatted amount: 100000.33
new result: 2
original result: 4

Example 2:

$amount = 103500000;
echo "original amount: ".$amount;
echo "<br>";
echo "sprintf formatted amount: ".sprintf('%.8g', $amount);
echo "<br>";
echo "new result: ".strlen(substr(strrchr(sprintf('%.8g', $amount), '.'), 1));
echo "<br>";
echo "original result: ".strlen(substr(strrchr((string)$amount, '.'), 1));

results in:


original amount: 103500000
sprintf formatted amount: 1.035e+8
new result: 6
original result: 0
judgej commented 7 years ago

Sorry - only just spotted this.

The original commits came from issues with different decimal accuracies being set for different PHP installations, meaning a simple cast to (string) gave different results on different installations. For example, some sites may give 1.66 and others may give 1.659999999997 The sprintf() was meant to bypass that and make sure the accuracy (decimal digits to which a string representation of a float is rounded to) was the same for all installs.

Of course, nothing should ever go via a float in the first place, but we are stuck with it as legacy (for Omnipay 2.x at least).

barryvdh commented 7 years ago

What do you suggest for 3.x?

judgej commented 7 years ago

Not sure. I've always delegated money amounts to popular libraries with the assumption that they know how to handle this type of stuff better than me.

barryvdh commented 7 years ago

Well, in principle, we shouldn't touch the given value in most cases, unless we need to convert to integers at all.

In this case, we seem te be making a wrong decimal count, by casting it to max 8 decimals, see https://github.com/thephpleague/omnipay-common/pull/150

barryvdh commented 7 years ago

Would the fix using number_format and removing insignificant zeros work?

judgej commented 7 years ago

Yes, it should do. An rtrim($string, '0') should remove the trailing zeros, which surprisingly the PHP documentation does not explicitly state are added. It also doesn't mention the number is rounded.

barryvdh commented 7 years ago

Only thing I'm concerned about is the 'magic' 8 number. Is it good for everyone? Should it be number of decimals + 2? Should it depend on precision?

barryvdh commented 7 years ago

And should we throw an error at all? Downside of rounding is that it will depend on user input, so could fail depending on user input, right?

kurund commented 5 months ago

Hello,

Is there a work around for this issue? Thank you.