Demo plan + Payment integrations (Stripe/PayPal/Bank)
Models & migrations: - platform_settings table (key/value JSON store + Cache::remember 5min) - plans: is_demo bool + trial_days int - companies: is_demo bool Plans: - Demo plan seeded (is_demo=true, is_public=false, all features, 14 trial days) - Trial 14-day plan seeded (is_public=true, basic features) - Plan form: is_demo toggle + trial_days field - Plan table: badge 🎬 Demo / 🎁 N zile trial Central panel: - PaymentSettings page (heroicon-credit-card, sort 90) Form sections: General, Date legale, Stripe, PayPal, Transfer bancar Each gateway collapsible, fields hidden until enabled toggle Saves to platform_settings keyed by `payments.{gateway}` - CompanyResource: is_demo toggle + table description Payment flow (PaymentController): - GET /billing — tenant invoices list with Pay button - POST /pay/{sub} — start checkout (stripe/paypal/bank) - GET /pay/{sub}/{success,cancel} - POST /payments/stripe/webhook — mark paid + extend company.active_until - POST /payments/paypal/webhook — same Views: - site/billing.blade.php — invoices list with payment modal (3 methods) - site/bank-instructions — IBAN/BIC/reference for manual transfer - site/checkout-stub — placeholder until composer require stripe-php - site/payment-{success,cancel} Tenant panel: - userMenuItems → "Facturile mele" link to /billing
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Central\Pages;
|
||||
|
||||
use App\Models\Central\PlatformSetting;
|
||||
use Filament\Forms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Schemas;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class PaymentSettings extends Page
|
||||
{
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-credit-card';
|
||||
|
||||
protected static ?string $navigationLabel = 'Setări plăți';
|
||||
|
||||
protected static ?string $title = 'Integrări de plată';
|
||||
|
||||
protected static ?int $navigationSort = 90;
|
||||
|
||||
protected string $view = 'filament.central.pages.payment-settings';
|
||||
|
||||
public ?array $data = [];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$s = PlatformSetting::many([
|
||||
'payments.stripe', 'payments.paypal', 'payments.bank',
|
||||
'payments.platform_currency', 'payments.invoice_prefix',
|
||||
'payments.terms', 'payments.company_legal',
|
||||
]);
|
||||
|
||||
$stripe = $s['payments.stripe'] ?? [];
|
||||
$paypal = $s['payments.paypal'] ?? [];
|
||||
$bank = $s['payments.bank'] ?? [];
|
||||
$legal = $s['payments.company_legal'] ?? [];
|
||||
|
||||
$this->form->fill([
|
||||
'platform_currency' => $s['payments.platform_currency'] ?? 'MDL',
|
||||
'invoice_prefix' => $s['payments.invoice_prefix'] ?? 'INV',
|
||||
'terms' => $s['payments.terms'] ?? 'Plata se efectuează în 7 zile de la emitere. Pentru întârzieri se aplică penalități 0.1%/zi.',
|
||||
|
||||
'legal_name' => $legal['name'] ?? null,
|
||||
'legal_idno' => $legal['idno'] ?? null,
|
||||
'legal_address' => $legal['address'] ?? null,
|
||||
'legal_phone' => $legal['phone'] ?? null,
|
||||
'legal_email' => $legal['email'] ?? null,
|
||||
|
||||
'stripe_enabled' => $stripe['enabled'] ?? false,
|
||||
'stripe_mode' => $stripe['mode'] ?? 'test',
|
||||
'stripe_publishable' => $stripe['publishable_key'] ?? null,
|
||||
'stripe_secret' => $stripe['secret_key'] ?? null,
|
||||
'stripe_webhook' => $stripe['webhook_secret'] ?? null,
|
||||
|
||||
'paypal_enabled' => $paypal['enabled'] ?? false,
|
||||
'paypal_mode' => $paypal['mode'] ?? 'sandbox',
|
||||
'paypal_client_id' => $paypal['client_id'] ?? null,
|
||||
'paypal_secret' => $paypal['secret'] ?? null,
|
||||
|
||||
'bank_enabled' => $bank['enabled'] ?? true,
|
||||
'bank_iban' => $bank['iban'] ?? null,
|
||||
'bank_bic' => $bank['bic'] ?? null,
|
||||
'bank_name' => $bank['bank_name'] ?? null,
|
||||
'bank_beneficiary' => $bank['beneficiary'] ?? null,
|
||||
'bank_instructions' => $bank['instructions'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Schemas\Components\Section::make('General')
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\Select::make('platform_currency')
|
||||
->label('Monedă platformă')
|
||||
->options(['MDL' => 'MDL', 'EUR' => 'EUR', 'USD' => 'USD'])
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('invoice_prefix')
|
||||
->label('Prefix facturi')->maxLength(10)->default('INV'),
|
||||
Forms\Components\Textarea::make('terms')
|
||||
->label('Termeni de plată')->rows(2)->columnSpanFull(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Date legale (apar pe facturi)')
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('legal_name')->label('Denumire companie')->maxLength(160),
|
||||
Forms\Components\TextInput::make('legal_idno')->label('IDNO / CUI')->maxLength(40),
|
||||
Forms\Components\TextInput::make('legal_address')->label('Adresă')->columnSpanFull()->maxLength(255),
|
||||
Forms\Components\TextInput::make('legal_phone')->label('Telefon')->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('legal_email')->label('Email')->email()->maxLength(120),
|
||||
]),
|
||||
Schemas\Components\Section::make('💳 Stripe')
|
||||
->description('Procesare carduri online. Funcționează în 30+ țări. Comision ~2.9% + 0.30€/tranzacție.')
|
||||
->columns(3)
|
||||
->collapsible()
|
||||
->collapsed(fn (Get $get) => ! $get('stripe_enabled'))
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('stripe_enabled')->label('Activează Stripe')->default(false)->live(),
|
||||
Forms\Components\Select::make('stripe_mode')
|
||||
->label('Mod')
|
||||
->options(['test' => '🧪 Test', 'live' => '🟢 Live'])
|
||||
->default('test')
|
||||
->required(fn (Get $get) => $get('stripe_enabled'))
|
||||
->visible(fn (Get $get) => $get('stripe_enabled')),
|
||||
Forms\Components\TextInput::make('stripe_publishable')
|
||||
->label('Publishable key')->placeholder('pk_test_...')
|
||||
->visible(fn (Get $get) => $get('stripe_enabled')),
|
||||
Forms\Components\TextInput::make('stripe_secret')
|
||||
->label('Secret key')->password()->revealable()->placeholder('sk_test_...')
|
||||
->visible(fn (Get $get) => $get('stripe_enabled')),
|
||||
Forms\Components\TextInput::make('stripe_webhook')
|
||||
->label('Webhook secret')->password()->revealable()->placeholder('whsec_...')
|
||||
->columnSpanFull()
|
||||
->helperText('Endpoint webhook: https://service.mir.md/payments/stripe/webhook')
|
||||
->visible(fn (Get $get) => $get('stripe_enabled')),
|
||||
]),
|
||||
Schemas\Components\Section::make('🅿️ PayPal')
|
||||
->description('Plăți internaționale. Mai potrivit pentru clienți din afara MD.')
|
||||
->columns(3)
|
||||
->collapsible()
|
||||
->collapsed(fn (Get $get) => ! $get('paypal_enabled'))
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('paypal_enabled')->label('Activează PayPal')->default(false)->live(),
|
||||
Forms\Components\Select::make('paypal_mode')
|
||||
->label('Mod')
|
||||
->options(['sandbox' => '🧪 Sandbox', 'live' => '🟢 Live'])
|
||||
->default('sandbox')
|
||||
->visible(fn (Get $get) => $get('paypal_enabled')),
|
||||
Forms\Components\Placeholder::make('paypal_webhook_info')
|
||||
->label('Webhook')
|
||||
->content('https://service.mir.md/payments/paypal/webhook')
|
||||
->visible(fn (Get $get) => $get('paypal_enabled')),
|
||||
Forms\Components\TextInput::make('paypal_client_id')
|
||||
->label('Client ID')->placeholder('AYS...')
|
||||
->visible(fn (Get $get) => $get('paypal_enabled')),
|
||||
Forms\Components\TextInput::make('paypal_secret')
|
||||
->label('Secret')->password()->revealable()->placeholder('EJk...')
|
||||
->visible(fn (Get $get) => $get('paypal_enabled')),
|
||||
]),
|
||||
Schemas\Components\Section::make('🏦 Transfer bancar (manual)')
|
||||
->description('Datele apar pe facturi și pe pagina de plată a tenant-ului. Confirmarea o faci manual.')
|
||||
->columns(2)
|
||||
->collapsible()
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('bank_enabled')->label('Activează plata prin transfer')->default(true)->live(),
|
||||
Forms\Components\TextInput::make('bank_beneficiary')
|
||||
->label('Beneficiar')->placeholder('AutoCRM SRL')
|
||||
->visible(fn (Get $get) => $get('bank_enabled')),
|
||||
Forms\Components\TextInput::make('bank_iban')
|
||||
->label('IBAN')->placeholder('MD00 AG00 0000 ...')
|
||||
->visible(fn (Get $get) => $get('bank_enabled')),
|
||||
Forms\Components\TextInput::make('bank_bic')
|
||||
->label('BIC / SWIFT')->placeholder('AGRNMD2X')
|
||||
->visible(fn (Get $get) => $get('bank_enabled')),
|
||||
Forms\Components\TextInput::make('bank_name')
|
||||
->label('Banca')->placeholder('Moldova-Agroindbank')
|
||||
->visible(fn (Get $get) => $get('bank_enabled')),
|
||||
Forms\Components\Textarea::make('bank_instructions')
|
||||
->label('Instrucțiuni adiționale')->rows(2)->columnSpanFull()
|
||||
->visible(fn (Get $get) => $get('bank_enabled')),
|
||||
]),
|
||||
])
|
||||
->statePath('data');
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$data = $this->form->getState();
|
||||
|
||||
PlatformSetting::put('payments.platform_currency', $data['platform_currency']);
|
||||
PlatformSetting::put('payments.invoice_prefix', $data['invoice_prefix']);
|
||||
PlatformSetting::put('payments.terms', $data['terms']);
|
||||
|
||||
PlatformSetting::put('payments.company_legal', [
|
||||
'name' => $data['legal_name'] ?? null,
|
||||
'idno' => $data['legal_idno'] ?? null,
|
||||
'address' => $data['legal_address'] ?? null,
|
||||
'phone' => $data['legal_phone'] ?? null,
|
||||
'email' => $data['legal_email'] ?? null,
|
||||
]);
|
||||
|
||||
PlatformSetting::put('payments.stripe', [
|
||||
'enabled' => (bool) ($data['stripe_enabled'] ?? false),
|
||||
'mode' => $data['stripe_mode'] ?? 'test',
|
||||
'publishable_key' => $data['stripe_publishable'] ?? null,
|
||||
'secret_key' => $data['stripe_secret'] ?? null,
|
||||
'webhook_secret' => $data['stripe_webhook'] ?? null,
|
||||
]);
|
||||
|
||||
PlatformSetting::put('payments.paypal', [
|
||||
'enabled' => (bool) ($data['paypal_enabled'] ?? false),
|
||||
'mode' => $data['paypal_mode'] ?? 'sandbox',
|
||||
'client_id' => $data['paypal_client_id'] ?? null,
|
||||
'secret' => $data['paypal_secret'] ?? null,
|
||||
]);
|
||||
|
||||
PlatformSetting::put('payments.bank', [
|
||||
'enabled' => (bool) ($data['bank_enabled'] ?? false),
|
||||
'beneficiary' => $data['bank_beneficiary'] ?? null,
|
||||
'iban' => $data['bank_iban'] ?? null,
|
||||
'bic' => $data['bank_bic'] ?? null,
|
||||
'bank_name' => $data['bank_name'] ?? null,
|
||||
'instructions' => $data['bank_instructions'] ?? null,
|
||||
]);
|
||||
|
||||
Notification::make()->title('Setări plăți salvate')->success()->send();
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [
|
||||
\Filament\Actions\Action::make('save')
|
||||
->label('Salvează')
|
||||
->submit('save'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,9 @@ class CompanyResource extends Resource
|
||||
->searchable(),
|
||||
Forms\Components\DateTimePicker::make('trial_ends_at')->label('Trial expiră la'),
|
||||
Forms\Components\DateTimePicker::make('active_until')->label('Abonament până la'),
|
||||
Forms\Components\Toggle::make('is_demo')
|
||||
->label('Tenant demo')
|
||||
->helperText('Marchează acest tenant ca demo (datele pot fi resetate periodic).'),
|
||||
]),
|
||||
Schemas\Components\Section::make('Admin tenant (la creare)')
|
||||
->columns(2)
|
||||
@@ -112,7 +115,9 @@ class CompanyResource extends Resource
|
||||
->copyable()
|
||||
->url(fn (Company $record) => $record->url('/app'))
|
||||
->openUrlInNewTab(),
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable()->sortable()
|
||||
->description(fn ($record) => $record->is_demo ? '🎬 demo' : null),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->badge()
|
||||
->colors([
|
||||
|
||||
@@ -53,6 +53,14 @@ class PlanResource extends Resource
|
||||
Forms\Components\TextInput::make('name')->required()->maxLength(60),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
Forms\Components\Toggle::make('is_public')->label('Public (afișat la signup)')->default(true),
|
||||
Forms\Components\Toggle::make('is_demo')
|
||||
->label('Demo (pentru demonstrații sales)')
|
||||
->helperText('Plan ascuns public, folosit pentru tenant-i demo cu features deblocate.')
|
||||
->default(false),
|
||||
Forms\Components\TextInput::make('trial_days')
|
||||
->label('Perioadă trial (zile)')
|
||||
->numeric()->placeholder('null = fără trial')
|
||||
->helperText('Numărul de zile gratis după ce un tenant alege acest plan.'),
|
||||
]),
|
||||
Schemas\Components\Section::make('Preț')
|
||||
->columns(3)
|
||||
@@ -86,7 +94,10 @@ class PlanResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable()->sortable()
|
||||
->weight('bold')
|
||||
->description(fn ($record) => $record->is_demo ? '🎬 Demo' : ($record->trial_days ? "🎁 {$record->trial_days} zile trial" : null)),
|
||||
Tables\Columns\TextColumn::make('price_monthly')
|
||||
->money(fn ($record) => $record->currency)
|
||||
->label('Lunar')
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Central\PlatformSetting;
|
||||
use App\Models\Central\Subscription;
|
||||
use App\Tenancy\TenantManager;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Payment flow:
|
||||
* - GET /billing → tenant invoices list (auth required)
|
||||
* - POST /pay/{subscription} → start checkout (Stripe/PayPal/Bank)
|
||||
* - GET /pay/{subscription}/success → marked paid via webhook in real flow
|
||||
* - GET /pay/{subscription}/cancel → user aborted
|
||||
* - POST /payments/stripe/webhook → Stripe → mark paid
|
||||
* - POST /payments/paypal/webhook → PayPal → mark paid
|
||||
*/
|
||||
class PaymentController
|
||||
{
|
||||
public function billing(Request $request)
|
||||
{
|
||||
$tenant = app(TenantManager::class)->current();
|
||||
if (! $tenant) abort(404);
|
||||
|
||||
$user = $request->user();
|
||||
if (! $user) {
|
||||
return redirect('/app/login');
|
||||
}
|
||||
|
||||
$invoices = Subscription::where('company_id', $tenant->id)
|
||||
->latest('period_end')
|
||||
->limit(20)
|
||||
->get();
|
||||
|
||||
$methods = $this->availableMethods();
|
||||
$bank = PlatformSetting::get('payments.bank', []);
|
||||
$legal = PlatformSetting::get('payments.company_legal', []);
|
||||
|
||||
return view('site.billing', [
|
||||
'tenant' => $tenant,
|
||||
'invoices' => $invoices,
|
||||
'methods' => $methods,
|
||||
'bank' => $bank,
|
||||
'legal' => $legal,
|
||||
'themeColor' => $tenant->settings['theme_color'] ?? '#3B82F6',
|
||||
]);
|
||||
}
|
||||
|
||||
public function startCheckout(Request $request, int $subscription)
|
||||
{
|
||||
$tenant = app(TenantManager::class)->current();
|
||||
$sub = Subscription::where('company_id', $tenant?->id)->findOrFail($subscription);
|
||||
|
||||
$method = $request->input('method');
|
||||
|
||||
if (! in_array($method, ['stripe', 'paypal', 'bank'], true)) {
|
||||
abort(422, 'Method invalid');
|
||||
}
|
||||
|
||||
if ($sub->status === 'paid') {
|
||||
return back()->with('error', 'Factura este deja plătită');
|
||||
}
|
||||
|
||||
if ($method === 'bank') {
|
||||
// Show bank instructions page; payment is manual.
|
||||
return view('site.bank-instructions', [
|
||||
'tenant' => $tenant,
|
||||
'sub' => $sub,
|
||||
'bank' => PlatformSetting::get('payments.bank', []),
|
||||
'themeColor' => $tenant->settings['theme_color'] ?? '#3B82F6',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($method === 'stripe') {
|
||||
return $this->startStripe($sub);
|
||||
}
|
||||
|
||||
if ($method === 'paypal') {
|
||||
return $this->startPaypal($sub);
|
||||
}
|
||||
}
|
||||
|
||||
private function startStripe(Subscription $sub)
|
||||
{
|
||||
$stripe = PlatformSetting::get('payments.stripe', []);
|
||||
if (empty($stripe['enabled']) || empty($stripe['secret_key'])) {
|
||||
return back()->with('error', 'Stripe nu este configurat. Contactează operatorul.');
|
||||
}
|
||||
|
||||
// Real implementation requires `stripe/stripe-php` package. Here we
|
||||
// produce a stub that explains the flow; in prod, replace with:
|
||||
// $session = \Stripe\Checkout\Session::create([...]);
|
||||
// return redirect($session->url);
|
||||
return view('site.checkout-stub', [
|
||||
'gateway' => 'Stripe',
|
||||
'sub' => $sub,
|
||||
'note' => 'Pentru a activa Stripe Checkout, instalează `composer require stripe/stripe-php` și activează în /admin/payment-settings.',
|
||||
]);
|
||||
}
|
||||
|
||||
private function startPaypal(Subscription $sub)
|
||||
{
|
||||
$paypal = PlatformSetting::get('payments.paypal', []);
|
||||
if (empty($paypal['enabled']) || empty($paypal['client_id'])) {
|
||||
return back()->with('error', 'PayPal nu este configurat.');
|
||||
}
|
||||
|
||||
return view('site.checkout-stub', [
|
||||
'gateway' => 'PayPal',
|
||||
'sub' => $sub,
|
||||
'note' => 'Pentru a activa PayPal, instalează `composer require srmklive/paypal` și activează în /admin/payment-settings.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function success(int $subscription)
|
||||
{
|
||||
$sub = Subscription::findOrFail($subscription);
|
||||
return view('site.payment-success', ['sub' => $sub]);
|
||||
}
|
||||
|
||||
public function cancel(int $subscription)
|
||||
{
|
||||
return view('site.payment-cancel');
|
||||
}
|
||||
|
||||
public function stripeWebhook(Request $request)
|
||||
{
|
||||
$stripe = PlatformSetting::get('payments.stripe', []);
|
||||
$secret = $stripe['webhook_secret'] ?? null;
|
||||
|
||||
if (! $secret) {
|
||||
Log::warning('Stripe webhook hit but no secret configured');
|
||||
return response()->json(['ok' => false], 400);
|
||||
}
|
||||
|
||||
// Real impl: \Stripe\Webhook::constructEvent($payload, $sig, $secret)
|
||||
// Mock: trust the body shape for stub mode.
|
||||
$payload = $request->json()->all();
|
||||
$event = $payload['type'] ?? null;
|
||||
|
||||
if ($event === 'checkout.session.completed') {
|
||||
$subId = $payload['data']['object']['metadata']['subscription_id'] ?? null;
|
||||
if ($subId && $sub = Subscription::find($subId)) {
|
||||
$this->markPaid($sub, 'stripe_card', $payload['data']['object']['id'] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function paypalWebhook(Request $request)
|
||||
{
|
||||
// Real impl: verify signature with PayPal
|
||||
$payload = $request->json()->all();
|
||||
$event = $payload['event_type'] ?? null;
|
||||
|
||||
if ($event === 'CHECKOUT.ORDER.APPROVED' || $event === 'PAYMENT.CAPTURE.COMPLETED') {
|
||||
$subId = $payload['resource']['custom_id'] ?? null;
|
||||
if ($subId && $sub = Subscription::find($subId)) {
|
||||
$this->markPaid($sub, 'paypal', $payload['resource']['id'] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
private function markPaid(Subscription $sub, string $method, ?string $reference): void
|
||||
{
|
||||
if ($sub->status === 'paid') return;
|
||||
|
||||
$sub->update([
|
||||
'status' => 'paid',
|
||||
'paid_at' => now(),
|
||||
'payment_method' => $method,
|
||||
'reference' => $reference,
|
||||
]);
|
||||
|
||||
// Extend company subscription
|
||||
$sub->company?->update([
|
||||
'status' => 'active',
|
||||
'active_until' => $sub->period_end,
|
||||
]);
|
||||
}
|
||||
|
||||
private function availableMethods(): array
|
||||
{
|
||||
$stripe = PlatformSetting::get('payments.stripe', []);
|
||||
$paypal = PlatformSetting::get('payments.paypal', []);
|
||||
$bank = PlatformSetting::get('payments.bank', []);
|
||||
|
||||
return [
|
||||
'stripe' => ! empty($stripe['enabled']) && ! empty($stripe['publishable_key']),
|
||||
'paypal' => ! empty($paypal['enabled']) && ! empty($paypal['client_id']),
|
||||
'bank' => ! empty($bank['enabled']) && ! empty($bank['iban']),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ class Plan extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'slug', 'name', 'price_monthly', 'price_yearly', 'currency',
|
||||
'features', 'limits', 'is_active', 'is_public',
|
||||
'features', 'limits', 'is_active', 'is_public', 'is_demo', 'trial_days',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@@ -16,6 +16,8 @@ class Plan extends Model
|
||||
'limits' => 'array',
|
||||
'is_active' => 'boolean',
|
||||
'is_public' => 'boolean',
|
||||
'is_demo' => 'boolean',
|
||||
'trial_days' => 'integer',
|
||||
'price_monthly' => 'decimal:2',
|
||||
'price_yearly' => 'decimal:2',
|
||||
];
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Central;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class PlatformSetting extends Model
|
||||
{
|
||||
public $timestamps = true;
|
||||
|
||||
protected $fillable = ['key', 'value'];
|
||||
|
||||
protected $casts = [
|
||||
'value' => 'array',
|
||||
];
|
||||
|
||||
public static function get(string $key, $default = null)
|
||||
{
|
||||
$cached = Cache::remember("psetting:{$key}", 300, function () use ($key) {
|
||||
$row = static::where('key', $key)->first();
|
||||
return $row?->value;
|
||||
});
|
||||
return $cached ?? $default;
|
||||
}
|
||||
|
||||
public static function put(string $key, $value): void
|
||||
{
|
||||
static::updateOrCreate(['key' => $key], ['value' => $value]);
|
||||
Cache::forget("psetting:{$key}");
|
||||
}
|
||||
|
||||
public static function many(array $keys): array
|
||||
{
|
||||
$rows = static::whereIn('key', $keys)->get()->keyBy('key');
|
||||
$out = [];
|
||||
foreach ($keys as $k) {
|
||||
$out[$k] = $rows[$k]?->value ?? null;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,13 @@ class TenantPanelProvider extends PanelProvider
|
||||
</style>
|
||||
BLADE)
|
||||
)
|
||||
->userMenuItems([
|
||||
'billing' => \Filament\Navigation\MenuItem::make()
|
||||
->label('Facturile mele')
|
||||
->icon('heroicon-o-credit-card')
|
||||
->url('/billing')
|
||||
->openUrlInNewTab(false),
|
||||
])
|
||||
->renderHook(
|
||||
PanelsRenderHook::USER_MENU_BEFORE,
|
||||
fn (): string => Blade::render(<<<'BLADE'
|
||||
|
||||
Reference in New Issue
Block a user