Skip to content

Commit 491fd56

Browse files
authored
fix: floating point error (#1211)
2 parents d843f1a + a1dac04 commit 491fd56

File tree

65 files changed

+971
-309
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+971
-309
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace App\Casts\Settings;
4+
5+
use App\Helpers\CurrencyHelper;
6+
use Spatie\LaravelSettings\SettingsCasts\SettingsCast;
7+
8+
class CurrencyCast implements SettingsCast
9+
{
10+
protected CurrencyHelper $currencyHelper;
11+
12+
public function __construct()
13+
{
14+
$this->currencyHelper = new CurrencyHelper();
15+
}
16+
17+
public function get($payload): mixed
18+
{
19+
// Conversion will only take place when the value is displayed.
20+
return $payload;
21+
}
22+
23+
public function set($payload): int
24+
{
25+
if ($payload === null) {
26+
return 0;
27+
}
28+
29+
return $this->currencyHelper->prepareForDatabase($payload);
30+
}
31+
}

app/Classes/PaymentExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ abstract class PaymentExtension extends AbstractExtension
1010
/**
1111
* Returns the redirect url of the payment gateway to redirect the user to
1212
*/
13-
abstract public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string;
13+
abstract public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, int $totalPrice): string;
1414
}

app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

33
namespace App\Extensions\PaymentGateways\MercadoPago;
44

5-
use App\Classes\AbstractExtension;
5+
use App\Classes\PaymentExtension;
66
use App\Enums\PaymentStatus;
77
use App\Events\PaymentEvent;
88
use App\Events\UserUpdateCreditsEvent;
99
use App\Models\Payment;
1010
use App\Models\ShopProduct;
1111
use App\Models\User;
12-
use App\Traits\Coupon as CouponTrait;
1312
use Exception;
1413
use Illuminate\Http\JsonResponse;
1514
use Illuminate\Http\Request;
@@ -24,10 +23,8 @@
2423
/**
2524
* Summary of MercadoPagoExtension
2625
*/
27-
class MercadoPagoExtension extends AbstractExtension
26+
class MercadoPagoExtension extends PaymentExtension
2827
{
29-
use CouponTrait;
30-
3128
public static function getConfig(): array
3229
{
3330
return [
@@ -38,7 +35,7 @@ public static function getConfig(): array
3835
];
3936
}
4037

41-
public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string
38+
public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, int $totalPrice): string
4239
{
4340
/**
4441
* For Mercado Pago to work correctly,
@@ -49,6 +46,9 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct
4946
throw new Exception(__('It is not possible to purchase via MercadoPago: APP_URL does not have HTTPS, required by Mercado Pago.'));
5047
}
5148

49+
// Converts from cents to decimal places.
50+
$totalPrice = $totalPrice / 1000;
51+
5252
$user = Auth::user();
5353
$user = User::findOrFail($user->id);
5454
$url = 'https://api.mercadopago.com/checkout/preferences';
@@ -72,7 +72,7 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct
7272
[
7373
'title' => "Order #{$payment->id} - " . $shopProduct->name,
7474
'quantity' => 1,
75-
'unit_price' => floatval($totalPriceString),
75+
'unit_price' => $totalPrice,
7676
'currency_id' => $shopProduct->currency_code,
7777
],
7878
],

app/Extensions/PaymentGateways/Mollie/MollieExtension.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22

33
namespace App\Extensions\PaymentGateways\Mollie;
44

5-
use App\Classes\AbstractExtension;
5+
use App\Classes\PaymentExtension;
66
use App\Enums\PaymentStatus;
77
use App\Events\PaymentEvent;
88
use App\Events\UserUpdateCreditsEvent;
99
use App\Models\Payment;
1010
use App\Models\ShopProduct;
1111
use App\Models\User;
1212
use App\Notifications\ConfirmPaymentNotification;
13-
use App\Traits\Coupon as CouponTrait;
1413
use Exception;
1514
use Illuminate\Http\JsonResponse;
1615
use Illuminate\Http\RedirectResponse;
@@ -22,10 +21,8 @@
2221
/**
2322
* Summary of PayPalExtension
2423
*/
25-
class MollieExtension extends AbstractExtension
24+
class MollieExtension extends PaymentExtension
2625
{
27-
use CouponTrait;
28-
2926
public static function getConfig(): array
3027
{
3128
return [
@@ -36,8 +33,16 @@ public static function getConfig(): array
3633
];
3734
}
3835

39-
public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string
36+
public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, int $totalPrice): string
4037
{
38+
/**
39+
* Mollie requires the price to be a string with two decimal places.
40+
* The price is in cents, so we need to divide by 10 to get the value in 100 factors.
41+
* The price is also in the format of 0.00, so we need to format it to two decimal places.
42+
*/
43+
$priceCents = $totalPrice / 10;
44+
$totalPrice = number_format($priceCents / 100, 2, '.', '');
45+
4146
$url = 'https://api.mollie.com/v2/payments';
4247
$settings = new MollieSettings();
4348
try {
@@ -47,7 +52,7 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct
4752
])->post($url, [
4853
'amount' => [
4954
'currency' => $shopProduct->currency_code,
50-
'value' => $totalPriceString,
55+
'value' => $totalPrice,
5156
],
5257
'description' => "Order #{$payment->id} - " . $shopProduct->name,
5358
'redirectUrl' => route('payment.MollieSuccess', ['payment_id' => $payment->id]),

app/Extensions/PaymentGateways/PayPal/PayPalExtension.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use App\Models\Payment;
1010
use App\Models\ShopProduct;
1111
use App\Models\User;
12-
use App\Traits\Coupon as CouponTrait;
1312
use Illuminate\Http\Request;
1413
use Illuminate\Support\Facades\Auth;
1514
use Illuminate\Support\Facades\Redirect;
@@ -27,8 +26,6 @@
2726
*/
2827
class PayPalExtension extends PaymentExtension
2928
{
30-
use CouponTrait;
31-
3229
public static function getConfig(): array
3330
{
3431
return [
@@ -37,8 +34,11 @@ public static function getConfig(): array
3734
];
3835
}
3936

40-
public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string
37+
public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, int $totalPrice): string
4138
{
39+
// Converts from cents to decimal places.
40+
$totalPrice = $totalPrice / 1000;
41+
4242
$request = new OrdersCreateRequest();
4343
$request->prefer('return=representation');
4444
$request->body = [
@@ -48,12 +48,12 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct
4848
"reference_id" => uniqid(),
4949
"description" => $shopProduct->display,
5050
"amount" => [
51-
"value" => $totalPriceString,
51+
"value" => $totalPrice,
5252
'currency_code' => strtoupper($shopProduct->currency_code),
5353
'breakdown' => [
5454
'item_total' => [
5555
'currency_code' => strtoupper($shopProduct->currency_code),
56-
'value' => $totalPriceString,
56+
'value' => $totalPrice,
5757
],
5858

5959
/* Removed due to errors in the coupon discount calculation. Its not used in other paymentgateways aswell and basically nice to have but unnessecary

app/Extensions/PaymentGateways/Stripe/StripeExtension.php

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace App\Extensions\PaymentGateways\Stripe;
44

5-
use App\Classes\AbstractExtension;
5+
use App\Classes\PaymentExtension;
66
use App\Enums\PaymentStatus;
77
use App\Events\PaymentEvent;
88
use App\Events\UserUpdateCreditsEvent;
@@ -19,7 +19,7 @@
1919
use Stripe\Stripe;
2020
use Stripe\StripeClient;
2121

22-
class StripeExtension extends AbstractExtension
22+
class StripeExtension extends PaymentExtension
2323
{
2424
use CouponTrait;
2525

@@ -43,11 +43,10 @@ public static function getConfig(): array
4343
];
4444
}
4545

46-
public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string
46+
public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, int $totalPrice): string
4747
{
4848
// check if the total price is valid for stripe
49-
$totalPriceNumber = floatval($totalPriceString);
50-
if (!self::checkPriceAmount($totalPriceNumber, strtoupper($shopProduct->currency_code), 'stripe')) {
49+
if (!self::checkPriceAmount(floatval($totalPrice), strtoupper($shopProduct->currency_code), 'stripe')) {
5150
throw new Exception('Invalid price amount');
5251
}
5352

@@ -61,7 +60,7 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct
6160
'name' => $shopProduct->display,
6261
'description' => $shopProduct->description,
6362
],
64-
'unit_amount_decimal' => self::convertAmount($totalPriceString, $shopProduct->currency_code),
63+
'unit_amount_decimal' => self::convertAmount($totalPrice, $shopProduct->currency_code),
6564
],
6665
'quantity' => 1,
6766
],
@@ -377,13 +376,13 @@ public static function checkPriceAmount(float $amount, string $currencyCode, st
377376
protected static function convertAmount(float $amount, string $currency): int
378377
{
379378
if (in_array($currency, self::ZERO_DECIMAL_CURRENCIES, true)) {
380-
return $amount;
379+
return $amount / 1000;
381380
}
382381

383382
if (in_array($currency, self::THREE_DECIMAL_CURRENCIES, true)) {
384-
return $amount * 1000;
383+
return $amount;
385384
}
386385

387-
return $amount * 100;
386+
return $amount / 10;
388387
}
389388
}

app/Facades/Currency.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace App\Facades;
4+
5+
use Illuminate\Support\Facades\Facade;
6+
7+
class Currency extends Facade
8+
{
9+
protected static function getFacadeAccessor()
10+
{
11+
return 'currency';
12+
}
13+
}

app/Helpers/CurrencyHelper.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace App\Helpers;
4+
5+
use NumberFormatter;
6+
7+
class CurrencyHelper
8+
{
9+
private function convertForDisplay($amount)
10+
{
11+
return $amount / 1000;
12+
}
13+
14+
public function formatForDisplay($amount, $decimals = 2)
15+
{
16+
return number_format($this->convertForDisplay($amount), $decimals, ',', '.');
17+
}
18+
19+
public function formatForForm($amount, $decimals = 2)
20+
{
21+
return number_format($this->convertForDisplay($amount), $decimals, '.', '');
22+
}
23+
24+
public function prepareForDatabase($amount)
25+
{
26+
return (int)($amount * 1000);
27+
}
28+
29+
public function formatToCurrency(int $amount, $currency_code, $locale = null,)
30+
{
31+
$locale = $locale ?: str_replace('_', '-', app()->getLocale());
32+
33+
$formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
34+
35+
return $formatter->formatCurrency($this->convertForDisplay($amount), $currency_code);
36+
}
37+
}

app/Http/Controllers/Admin/CouponController.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Http\Controllers\Admin;
44

5+
use App\Helpers\CurrencyHelper;
56
use App\Http\Controllers\Controller;
67
use App\Models\Coupon;
78
use App\Settings\LocaleSettings;
@@ -215,12 +216,12 @@ public function dataTable()
215216
->editColumn('uses', function (Coupon $coupon) {
216217
return "{$coupon->uses} / {$coupon->max_uses}";
217218
})
218-
->editColumn('value', function (Coupon $coupon) {
219+
->editColumn('value', function (Coupon $coupon, CurrencyHelper $currencyHelper) {
219220
if ($coupon->type === 'percentage') {
220221
return $coupon->value . "%";
221222
}
222223

223-
return number_format($coupon->value, 2, '.', '');
224+
return $currencyHelper->formatForDisplay($coupon->value);
224225
})
225226
->editColumn('expires_at', function (Coupon $coupon) {
226227
if (!$coupon->expires_at) {

app/Http/Controllers/Admin/OverViewController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace App\Http\Controllers\Admin;
44

55
use App\Classes\PterodactylClient;
6+
use App\Helpers\CurrencyHelper;
67
use App\Settings\PterodactylSettings;
78
use App\Settings\GeneralSettings;
89
use App\Http\Controllers\Controller;
@@ -30,7 +31,7 @@ public function __construct(PterodactylSettings $ptero_settings)
3031
$this->pterodactyl = new PterodactylClient($ptero_settings);
3132
}
3233

33-
public function index(GeneralSettings $general_settings)
34+
public function index(GeneralSettings $general_settings, CurrencyHelper $currencyHelper)
3435
{
3536
$this->checkAnyPermission([self::READ_PERMISSION,self::SYNC_PERMISSION]);
3637

0 commit comments

Comments
 (0)