You've already forked Atomcms-edit
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:
@@ -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'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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')],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user