Modernize dependencies: replace abandoned packages, update versions

- Replace flowframe/laravel-trend with direct Eloquent DB queries
- Replace srmklive/paypal (abandoned) with new PayPalService using PayPal REST API v2 via Guzzle
- Remove old config/paypal.php, migrate to config('habbo.paypal.*')
- Update blade templates to use habbo.paypal config
- Bump npm packages to latest: @inertiajs/react, axios, esbuild, eslint, sass, tailwindcss, etc.
- Run composer update and yarn upgrade
This commit is contained in:
root
2026-06-20 15:01:48 +02:00
parent 7c72ed82b6
commit 53f88b840a
12 changed files with 1171 additions and 1206 deletions
@@ -4,9 +4,8 @@ namespace App\Filament\Resources\DashboardResource\Widgets;
use App\Models\User\UserOrder;
use Filament\Widgets\ChartWidget;
use Flowframe\Trend\Trend;
use Flowframe\Trend\TrendValue;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Facades\DB;
class OrdersAggregateChart extends ChartWidget
{
@@ -28,47 +27,45 @@ class OrdersAggregateChart extends ChartWidget
return __('filament::resources.stats.orders_chart.description');
}
private function getTrendData($query): array
{
return $query->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])
->selectRaw('DATE(created_at) as date, COUNT(*) as aggregate')
->groupBy(DB::raw('DATE(created_at)'))
->orderBy('date')
->pluck('aggregate', 'date')
->toArray();
}
#[\Override]
protected function getData(): array
{
$pendingOrder = Trend::query(UserOrder::pending())
->between(start: now()->startOfMonth(), end: now()->endOfMonth())
->perDay()
->count();
$pendingData = $this->getTrendData(UserOrder::pending());
$cancelledData = $this->getTrendData(UserOrder::cancelled());
$completedData = $this->getTrendData(UserOrder::completed());
$cancelledOrder = Trend::query(UserOrder::cancelled())
->between(start: now()->startOfMonth(), end: now()->endOfMonth())
->perDay()
->count();
$completedOrder = Trend::query(UserOrder::completed())
->between(start: now()->startOfMonth(), end: now()->endOfMonth())
->perDay()
->count();
$allDates = collect(array_keys(array_merge($pendingData, $cancelledData, $completedData)))
->unique()
->sort()
->values();
$datasets = [
$this->getDataset($pendingOrder, __('filament::resources.stats.orders_chart.pending'), '#fbbf24', '#f59e0b'),
$this->getDataset($cancelledOrder, __('filament::resources.stats.orders_chart.cancelled'), '#dc2626', '#b91c1c'),
$this->getDataset($completedOrder, __('filament::resources.stats.orders_chart.completed'), '#10b981', '#059669'),
$this->getDataset($pendingData, $allDates, __('filament::resources.stats.orders_chart.pending'), '#fbbf24', '#f59e0b'),
$this->getDataset($cancelledData, $allDates, __('filament::resources.stats.orders_chart.cancelled'), '#dc2626', '#b91c1c'),
$this->getDataset($completedData, $allDates, __('filament::resources.stats.orders_chart.completed'), '#10b981', '#059669'),
];
$data = $pendingOrder->map(fn (TrendValue $value) => $value->date)->merge(
$cancelledOrder->map(fn (TrendValue $value) => $value->date),
)->merge(
$completedOrder->map(fn (TrendValue $value) => $value->date),
)->unique()->sort()->flatten();
return [
'datasets' => $datasets,
'labels' => $data,
'labels' => $allDates,
];
}
protected function getDataset($data, $label, string $backgroundColor, string $borderColor): array
private function getDataset(array $data, $allDates, string $label, string $backgroundColor, string $borderColor): array
{
return [
'label' => $label,
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
'data' => $allDates->map(fn ($date) => $data[$date] ?? 0),
'backgroundColor' => $backgroundColor,
'borderColor' => $borderColor,
];
@@ -4,9 +4,8 @@ namespace App\Filament\Widgets;
use App\Models\Articles\WebsiteArticle;
use Filament\Widgets\ChartWidget;
use Flowframe\Trend\Trend;
use Flowframe\Trend\TrendValue;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Facades\DB;
class ArticlesAggregateChart extends ChartWidget
{
@@ -34,13 +33,12 @@ class ArticlesAggregateChart extends ChartWidget
#[\Override]
protected function getData(): array
{
$data = Trend::model(WebsiteArticle::class)
->between(
start: now()->startOfMonth(),
end: now()->endOfMonth(),
)
->perDay()
->count();
$data = WebsiteArticle::query()
->whereBetween('created_at', [now()->startOfMonth(), now()->endOfMonth()])
->selectRaw('DATE(created_at) as date, COUNT(*) as aggregate')
->groupBy(DB::raw('DATE(created_at)'))
->orderBy('date')
->get();
$label = __('filament::resources.stats.articles_chart.label');
@@ -48,10 +46,10 @@ class ArticlesAggregateChart extends ChartWidget
'datasets' => [
[
'label' => $label,
'data' => $data->map(fn (TrendValue $value) => $value->aggregate),
'data' => $data->pluck('aggregate'),
],
],
'labels' => $data->map(fn (TrendValue $value) => $value->date),
'labels' => $data->pluck('date'),
];
}
+29 -33
View File
@@ -4,11 +4,10 @@ namespace App\Http\Controllers\Shop;
use App\Http\Controllers\Controller;
use App\Http\Requests\AccountTopupFormRequest;
use App\Services\PayPalService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Srmklive\PayPal\Services\PayPal;
use Srmklive\PayPal\Services\PayPal as PayPalClient;
use Symfony\Component\HttpFoundation\Response;
class PayPalController extends Controller
@@ -17,17 +16,11 @@ class PayPalController extends Controller
private const string STATUS_COMPLETED = 'COMPLETED';
private ?PayPalClient $provider = null;
private PayPalService $paypal;
private function getProvider(): PayPalClient
public function __construct(PayPalService $paypal)
{
if (! $this->provider instanceof PayPal) {
$this->provider = new PayPalClient;
$this->provider->setApiCredentials(config('habbo.paypal'));
$this->provider->getAccessToken();
}
return $this->provider;
$this->paypal = $paypal;
}
public function process(AccountTopupFormRequest $request): Response|RedirectResponse
@@ -44,7 +37,7 @@ class PayPalController extends Controller
'user_action' => 'CONTINUE',
],
'purchase_units' => [
0 => [
[
'amount' => [
'currency_code' => config('habbo.paypal.currency'),
'value' => (string) $amount,
@@ -53,14 +46,13 @@ class PayPalController extends Controller
],
];
$response = $this->getProvider()->createOrder($orderData);
$response = $this->paypal->createOrder($orderData);
if (isset($response['id']) === false) {
if ($response === null || ! isset($response['id'])) {
$message = $response['message'] ?? __('Something went wrong');
Log::error('Error creating order', ['response' => $response]);
return to_route('shop.index')->withErrors(
['message' => $response['message'] ?? __('Something went wrong')],
);
return to_route('shop.index')->withErrors(['message' => $message]);
}
foreach ($response['links'] as $links) {
@@ -76,7 +68,7 @@ class PayPalController extends Controller
}
return to_route('shop.index')->withErrors(
['message' => $response['message'] ?? __('Something went wrong')],
['message' => __('Something went wrong')],
);
}
@@ -97,26 +89,30 @@ class PayPalController extends Controller
return to_route('shop.index')->withErrors(['message' => __('Transaction already processed')]);
}
$response = $this->getProvider()->capturePaymentOrder($request['token']);
$paymentDetails = $response['purchase_units'][0]['payments']['captures'][0] ?? null;
$response = $this->paypal->capturePaymentOrder($request['token']);
if ($response === null) {
Log::error('PayPal capture returned null');
return to_route('shop.index')->withErrors(['message' => __('Something went wrong, please try again later')]);
}
if (isset($response['status'], $response['purchase_units'][0]['payments']['captures'][0]) === false) {
if (isset($response['error']['details'][0])) {
$details = $response['error']['details'][0];
$transaction->update([
'status' => $response['name'] ?? 'ERROR',
'description' => sprintf('%s - %s', $details['issue'] ?? '', $details['description'] ?? ''),
'amount' => 0,
]);
}
if (! isset($response['status'], $paymentDetails)) {
Log::error('Invalid response from PayPal', ['response' => $response]);
return to_route('shop.index')->withErrors(['message' => __('Something went wrong, please try again later')]);
}
if (($response['status'] ?? null) === null) {
$details = $response['error']['details'][0];
$transaction->update([
'status' => $response['name'],
'description' => sprintf('%s - %s', $details['issue'], $details['description']),
'amount' => 0,
]);
return to_route('shop.index')->withErrors(['message' => __('Something went wrong, please check your paypal account to make sure nothing was deducted and try again')]);
}
$paymentDetails = $response['purchase_units'][0]['payments']['captures'][0];
$capturedAmount = (float) $paymentDetails['amount']['value'];
$expectedAmount = (float) ($transaction->expected_amount ?? 0);
@@ -144,7 +140,7 @@ class PayPalController extends Controller
if ($response['status'] !== self::STATUS_COMPLETED) {
return to_route('shop.index')->withErrors(
['message' => $response['message'] ?? __('Something went wrong')],
['message' => __('Something went wrong')],
);
}
+152
View File
@@ -0,0 +1,152 @@
<?php
namespace App\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class PayPalService
{
private Client $client;
private string $mode;
private string $clientId;
private string $clientSecret;
public function __construct()
{
$this->mode = config('habbo.paypal.mode', 'sandbox');
$this->clientId = config("habbo.paypal.{$this->mode}.client_id", '');
$this->clientSecret = config("habbo.paypal.{$this->mode}.client_secret", '');
$baseUrl = $this->mode === 'live'
? 'https://api-m.paypal.com'
: 'https://api-m.sandbox.paypal.com';
$this->client = new Client([
'base_uri' => $baseUrl,
'timeout' => 30,
'http_errors' => false,
]);
}
public function getAccessToken(): ?string
{
$cacheKey = 'paypal_access_token_' . $this->mode;
return Cache::remember($cacheKey, 32400, function () {
try {
$response = $this->client->post('/v1/oauth2/token', [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/x-www-form-urlencoded',
],
'auth' => [$this->clientId, $this->clientSecret],
'form_params' => [
'grant_type' => 'client_credentials',
],
]);
$body = json_decode($response->getBody(), true);
if ($response->getStatusCode() !== 200 || ! isset($body['access_token'])) {
Log::error('PayPal access token request failed', [
'status' => $response->getStatusCode(),
'response' => $body,
]);
return;
}
return $body['access_token'];
} catch (GuzzleException $e) {
Log::error('PayPal access token exception', [
'message' => $e->getMessage(),
]);
return;
}
});
}
public function createOrder(array $orderData): ?array
{
$accessToken = $this->getAccessToken();
if ($accessToken === null) {
return null;
}
try {
$response = $this->client->post('/v2/checkout/orders', [
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => "Bearer {$accessToken}",
'Accept' => 'application/json',
],
'json' => $orderData,
]);
$body = json_decode($response->getBody(), true);
if ($response->getStatusCode() >= 400) {
Log::error('PayPal create order failed', [
'status' => $response->getStatusCode(),
'response' => $body,
]);
return $body;
}
return $body;
} catch (GuzzleException $e) {
Log::error('PayPal create order exception', [
'message' => $e->getMessage(),
]);
return null;
}
}
public function capturePaymentOrder(string $orderId): ?array
{
$accessToken = $this->getAccessToken();
if ($accessToken === null) {
return null;
}
try {
$response = $this->client->post("/v2/checkout/orders/{$orderId}/capture", [
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => "Bearer {$accessToken}",
'Accept' => 'application/json',
],
]);
$body = json_decode($response->getBody(), true);
if ($response->getStatusCode() >= 400) {
Log::error('PayPal capture order failed', [
'status' => $response->getStatusCode(),
'response' => $body,
]);
return $body;
}
return $body;
} catch (GuzzleException $e) {
Log::error('PayPal capture order exception', [
'message' => $e->getMessage(),
]);
return null;
}
}
}