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,34 @@
|
||||
<x-filament-panels::page>
|
||||
<form wire:submit="save">
|
||||
{{ $this->form }}
|
||||
|
||||
<div style="margin-top: 16px; display: flex; gap: 8px; justify-content: flex-end;">
|
||||
<button type="submit"
|
||||
class="fi-btn fi-btn-color-primary"
|
||||
style="padding: 10px 24px; background: #6366f1; color: white; border-radius: 8px; border: none; cursor: pointer; font-weight: 500;"
|
||||
wire:loading.attr="disabled">
|
||||
<span wire:loading.remove>💾 Salvează setările</span>
|
||||
<span wire:loading>Se salvează...</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div style="margin-top:24px;background:#f9fafb;border:1px dashed #e5e7eb;border-radius:8px;padding:16px;font-size:13px;line-height:1.7;">
|
||||
<b style="color:#6366f1;">📚 Cum se face plata în sistem:</b>
|
||||
<ol style="margin:8px 0 0 18px;padding:0;">
|
||||
<li>Operatorul (tu) creezi factură: <code>/admin/companies/{id}</code> → „Generează factură"</li>
|
||||
<li>Tenantul vede factura în interiorul lui la <code>https://{slug}.service.mir.md/billing</code></li>
|
||||
<li>Click „Plătește" → opțiuni: card (Stripe), PayPal, sau transfer manual</li>
|
||||
<li>La succes: webhook → automat status=paid + extends company.active_until</li>
|
||||
<li>La transfer manual: tenant trimite confirmare; operator click „Marchează plătit" în /admin/subscriptions</li>
|
||||
</ol>
|
||||
<div style="margin-top:12px;padding-top:12px;border-top:1px solid #e5e7eb;">
|
||||
<b>⚠ Pentru a primi plăți LIVE:</b>
|
||||
<ul style="margin:4px 0 0 18px;padding:0;font-size:12px;">
|
||||
<li>Stripe: înregistrează cont la <a href="https://dashboard.stripe.com" target="_blank" style="color:#6366f1;">dashboard.stripe.com</a> → ia keys → setează webhook la URL-ul de mai sus</li>
|
||||
<li>PayPal: <a href="https://developer.paypal.com/dashboard/applications" target="_blank" style="color:#6366f1;">developer.paypal.com</a> → creează aplicație live → ia credentials</li>
|
||||
<li>Transfer bancar: pune doar IBAN-ul tău, fără cont gateway</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</x-filament-panels::page>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ro">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Instrucțiuni transfer bancar — {{ $sub->invoice_number }}</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; background: #f3f4f6; padding: 24px; color: #1f2937; }
|
||||
.box { max-width: 600px; margin: 0 auto; background:#fff; padding:24px; border-radius:12px; box-shadow:0 4px 12px rgba(0,0,0,.05); }
|
||||
h1 { font-size: 20px; margin-bottom: 12px; }
|
||||
.row { padding: 8px 0; border-bottom: 1px dashed #e5e7eb; display: flex; justify-content: space-between; gap: 12px; font-size: 14px; }
|
||||
.row b { color:#111827; }
|
||||
.row code { background: #f9fafb; padding: 4px 8px; border-radius: 4px; font-family: ui-monospace, monospace; cursor: pointer; }
|
||||
.ref-box { background: #fef3c7; border-left: 4px solid #f59e0b; padding: 14px 16px; border-radius: 6px; margin-top: 16px; font-size: 13px; line-height: 1.6; }
|
||||
.btn-back { display:inline-block; margin-top: 16px; padding: 10px 20px; background: {{ $themeColor }}; color: #fff; border-radius: 8px; text-decoration: none; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<h1>🏦 Instrucțiuni transfer bancar</h1>
|
||||
|
||||
<div class="row"><span>Beneficiar:</span> <b>{{ $bank['beneficiary'] ?? '—' }}</b></div>
|
||||
<div class="row"><span>IBAN:</span> <code onclick="navigator.clipboard.writeText(this.textContent)">{{ $bank['iban'] ?? '—' }}</code></div>
|
||||
<div class="row"><span>BIC / SWIFT:</span> <code>{{ $bank['bic'] ?? '—' }}</code></div>
|
||||
<div class="row"><span>Banca:</span> {{ $bank['bank_name'] ?? '—' }}</div>
|
||||
<div class="row"><span>Sumă:</span> <b>{{ number_format($sub->amount, 2) }} {{ $sub->currency }}</b></div>
|
||||
|
||||
<div class="ref-box">
|
||||
<b>⚠ Important — la „Detalii plată" / „Reference" scrie EXACT:</b><br>
|
||||
<code style="font-size:14px;font-weight:700;display:block;margin:8px 0;padding:8px;background:#fff;border-radius:4px;">{{ $sub->invoice_number ?? 'INV-' . $sub->id }}</code>
|
||||
Fără acest cod, plata va fi greu de identificat.
|
||||
</div>
|
||||
|
||||
@if (! empty($bank['instructions']))
|
||||
<p style="margin-top:16px;font-size:13px;line-height:1.6;color:#4b5563;">{{ $bank['instructions'] }}</p>
|
||||
@endif
|
||||
|
||||
<div style="margin-top:20px;padding-top:16px;border-top:1px solid #e5e7eb;font-size:12px;color:#6b7280;line-height:1.6;">
|
||||
După efectuarea transferului:
|
||||
<ul style="margin: 8px 0 0 20px;">
|
||||
<li>Plata apare în 1-3 zile lucrătoare</li>
|
||||
<li>Operatorul confirmă manual factura ca plătită</li>
|
||||
<li>Abonamentul se extinde automat</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<a href="/billing" class="btn-back">← Înapoi la facturi</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,179 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ro">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>Facturi & abonament — {{ $tenant->display_name ?? $tenant->name }}</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: system-ui, -apple-system, sans-serif; background: #f3f4f6; color: #1f2937; line-height: 1.5; }
|
||||
.wrap { max-width: 900px; margin: 0 auto; padding: 24px 16px; }
|
||||
.nav-back { display: inline-block; margin-bottom: 16px; color: {{ $themeColor }}; text-decoration: none; font-size: 14px; }
|
||||
h1 { font-size: 24px; margin-bottom: 4px; }
|
||||
.sub { color: #6b7280; font-size: 14px; margin-bottom: 20px; }
|
||||
.invoice {
|
||||
background: #fff; border: 1px solid #e5e7eb; border-radius: 12px;
|
||||
padding: 20px; margin-bottom: 12px; display: flex; gap: 16px; align-items: center;
|
||||
}
|
||||
.invoice .num { font-weight: 600; font-size: 14px; flex: 0 0 120px; }
|
||||
.invoice .info { flex: 1; }
|
||||
.invoice .info .row { font-size: 13px; color: #6b7280; }
|
||||
.invoice .info .row b { color: #1f2937; }
|
||||
.invoice .amount { font-weight: 700; font-size: 20px; min-width: 100px; text-align: right; }
|
||||
.invoice .actions { flex: 0 0 auto; }
|
||||
.badge { display:inline-block; padding: 3px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; }
|
||||
.badge-paid { background:#dcfce7; color:#166534; }
|
||||
.badge-pending { background:#fef3c7; color:#92400e; }
|
||||
.badge-overdue { background:#fee2e2; color:#991b1b; }
|
||||
.btn-pay {
|
||||
background: {{ $themeColor }}; color: #fff; padding: 10px 20px; border-radius: 8px;
|
||||
border: none; cursor: pointer; font-size: 14px; font-weight: 600; text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn-pay:hover { opacity: .9; }
|
||||
.empty { text-align: center; padding: 60px 20px; color: #6b7280; }
|
||||
|
||||
.modal { display:none; position:fixed; inset:0; background:rgba(0,0,0,.5); z-index: 100; align-items: center; justify-content: center; }
|
||||
.modal.open { display: flex; }
|
||||
.modal-box { background:#fff; border-radius:12px; padding:24px; max-width:480px; width: 90%; }
|
||||
.modal-box h3 { margin-bottom: 8px; font-size: 18px; }
|
||||
.modal-box p { color:#6b7280; font-size: 13px; margin-bottom: 16px; }
|
||||
.method {
|
||||
display: flex; gap: 12px; align-items: center; padding: 12px;
|
||||
border: 2px solid #e5e7eb; border-radius: 8px; cursor: pointer; margin-bottom: 8px;
|
||||
transition: border-color .15s;
|
||||
}
|
||||
.method:hover { border-color: {{ $themeColor }}; }
|
||||
.method.disabled { opacity: .4; cursor: not-allowed; }
|
||||
.method .icon { font-size: 24px; }
|
||||
.method .label { font-weight: 600; flex: 1; font-size: 14px; }
|
||||
.method .desc { font-size: 11px; color: #6b7280; }
|
||||
.method-form { display: inline; }
|
||||
.method-btn { all: unset; cursor: pointer; width: 100%; }
|
||||
.modal-close { float:right; cursor:pointer; color:#9ca3af; font-size: 22px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<a href="/app" class="nav-back">← Înapoi la AutoCRM</a>
|
||||
<h1>Facturi & abonament</h1>
|
||||
<p class="sub">{{ $tenant->display_name ?? $tenant->name }} · {{ $tenant->slug }}.service.mir.md</p>
|
||||
|
||||
@if ($invoices->isEmpty())
|
||||
<div class="empty">
|
||||
<div style="font-size:48px;margin-bottom:8px;">📄</div>
|
||||
<div style="font-size:16px;font-weight:600;margin-bottom:4px;">Nicio factură emisă încă</div>
|
||||
<div style="font-size:13px;">Operatorul îți va emite factură când e timpul abonamentului.</div>
|
||||
</div>
|
||||
@else
|
||||
@foreach ($invoices as $inv)
|
||||
<div class="invoice">
|
||||
<div class="num">
|
||||
{{ $inv->invoice_number ?? '#' . $inv->id }}
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="row"><b>{{ $inv->plan?->name ?? 'Abonament' }}</b> · {{ $inv->period === 'yearly' ? 'Anual' : 'Lunar' }}</div>
|
||||
<div class="row">{{ $inv->period_start?->format('d.m.Y') }} → {{ $inv->period_end?->format('d.m.Y') }}</div>
|
||||
<div class="row" style="margin-top:6px;">
|
||||
<span class="badge badge-{{ $inv->status === 'paid' ? 'paid' : ($inv->status === 'overdue' ? 'overdue' : 'pending') }}">
|
||||
@switch($inv->status)
|
||||
@case('paid') ✓ Plătit @if($inv->paid_at) la {{ $inv->paid_at->format('d.m.Y') }} @endif @break
|
||||
@case('overdue') ⚠ Întârziat @break
|
||||
@case('pending') ⏳ În așteptare @break
|
||||
@default {{ $inv->status }}
|
||||
@endswitch
|
||||
</span>
|
||||
@if ($inv->due_at)
|
||||
<span style="margin-left:8px;color:#6b7280;font-size:11px;">scadent {{ $inv->due_at->format('d.m.Y') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="amount">{{ number_format($inv->amount, 2) }} {{ $inv->currency }}</div>
|
||||
<div class="actions">
|
||||
@if ($inv->status === 'paid')
|
||||
<span style="color:#10b981;font-weight:600;">✓</span>
|
||||
@else
|
||||
<button class="btn-pay" onclick="document.getElementById('m-{{ $inv->id }}').classList.add('open')">
|
||||
💳 Plătește
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Modal de plată --}}
|
||||
<div id="m-{{ $inv->id }}" class="modal" onclick="if(event.target===this)this.classList.remove('open')">
|
||||
<div class="modal-box">
|
||||
<span class="modal-close" onclick="document.getElementById('m-{{ $inv->id }}').classList.remove('open')">×</span>
|
||||
<h3>Plătește {{ $inv->invoice_number ?? '#' . $inv->id }}</h3>
|
||||
<p>Sumă: <b>{{ number_format($inv->amount, 2) }} {{ $inv->currency }}</b></p>
|
||||
|
||||
@if ($methods['stripe'])
|
||||
<form method="POST" action="{{ route('pay.start', ['subscription' => $inv->id]) }}" class="method-form">
|
||||
@csrf
|
||||
<input type="hidden" name="method" value="stripe">
|
||||
<button class="method-btn">
|
||||
<div class="method">
|
||||
<div class="icon">💳</div>
|
||||
<div>
|
||||
<div class="label">Card bancar</div>
|
||||
<div class="desc">Visa, Mastercard prin Stripe</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if ($methods['paypal'])
|
||||
<form method="POST" action="{{ route('pay.start', ['subscription' => $inv->id]) }}" class="method-form">
|
||||
@csrf
|
||||
<input type="hidden" name="method" value="paypal">
|
||||
<button class="method-btn">
|
||||
<div class="method">
|
||||
<div class="icon">🅿️</div>
|
||||
<div>
|
||||
<div class="label">PayPal</div>
|
||||
<div class="desc">Cont PayPal sau card prin PayPal</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if ($methods['bank'])
|
||||
<form method="POST" action="{{ route('pay.start', ['subscription' => $inv->id]) }}" class="method-form">
|
||||
@csrf
|
||||
<input type="hidden" name="method" value="bank">
|
||||
<button class="method-btn">
|
||||
<div class="method">
|
||||
<div class="icon">🏦</div>
|
||||
<div>
|
||||
<div class="label">Transfer bancar</div>
|
||||
<div class="desc">Plătește direct din bancă (1-3 zile)</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if (! $methods['stripe'] && ! $methods['paypal'] && ! $methods['bank'])
|
||||
<div style="text-align:center;color:#9ca3af;padding:32px 16px;font-size:13px;">
|
||||
Nicio metodă de plată configurată. Contactează operatorul.
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
@if (! empty($legal['name']))
|
||||
<div style="margin-top:32px;padding:16px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;font-size:12px;color:#6b7280;">
|
||||
<b>Operator platformă:</b> {{ $legal['name'] }}
|
||||
@if (! empty($legal['idno'])) · IDNO {{ $legal['idno'] }} @endif
|
||||
@if (! empty($legal['address'])) · {{ $legal['address'] }} @endif
|
||||
@if (! empty($legal['email'])) · {{ $legal['email'] }} @endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ro">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{{ $gateway }} Checkout</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; background: #f3f4f6; padding: 60px 20px; color: #1f2937; text-align: center; }
|
||||
.box { max-width: 540px; margin: 0 auto; background:#fff; padding:40px 24px; border-radius:12px; box-shadow:0 4px 12px rgba(0,0,0,.05); }
|
||||
h1 { font-size: 22px; margin-bottom: 12px; }
|
||||
.note { background: #fef3c7; border-left: 4px solid #f59e0b; padding: 12px 16px; border-radius: 6px; margin: 16px 0; text-align: left; font-size: 13px; line-height: 1.6; }
|
||||
.btn { display:inline-block; padding: 10px 20px; background: #6366f1; color: #fff; border-radius: 8px; text-decoration: none; margin-top: 12px; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<div style="font-size:48px;margin-bottom:12px;">🚧</div>
|
||||
<h1>{{ $gateway }} Checkout</h1>
|
||||
<p style="color:#6b7280;font-size:14px;">Factură: <b>{{ $sub->invoice_number ?? '#' . $sub->id }}</b> · {{ number_format($sub->amount, 2) }} {{ $sub->currency }}</p>
|
||||
|
||||
<div class="note">
|
||||
<b>Mod stub:</b><br>
|
||||
{{ $note }}<br><br>
|
||||
În prod, aici redirectează la pagina {{ $gateway }} Checkout cu sumele și metadatele facturii. La success, webhook-ul marchează automat factura ca plătită.
|
||||
</div>
|
||||
|
||||
<a href="/billing" class="btn">← Înapoi</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ro">
|
||||
<head><meta charset="UTF-8"><title>Plată anulată</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; background: #fef2f2; padding: 60px 20px; text-align: center; color: #1f2937; }
|
||||
.box { max-width: 480px; margin: 0 auto; background:#fff; padding:40px 24px; border-radius:12px; }
|
||||
.btn { display:inline-block; margin-top: 16px; padding: 10px 24px; background: #6b7280; color: #fff; border-radius: 8px; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<div style="font-size:48px;">↩</div>
|
||||
<h1 style="color:#ef4444;">Plată anulată</h1>
|
||||
<p style="color:#6b7280;">Nu a fost prelevată nicio sumă. Poți încerca din nou oricând.</p>
|
||||
<a href="/billing" class="btn">← Înapoi la facturi</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ro">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Plată reușită</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; background: #f0fdf4; padding: 60px 20px; text-align: center; color: #1f2937; }
|
||||
.box { max-width: 480px; margin: 0 auto; background:#fff; padding:40px 24px; border-radius:12px; box-shadow:0 4px 12px rgba(0,0,0,.05); }
|
||||
h1 { color: #10b981; font-size: 24px; margin: 12px 0; }
|
||||
.btn { display:inline-block; margin-top: 16px; padding: 10px 24px; background: #10b981; color: #fff; border-radius: 8px; text-decoration: none; font-weight: 500; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<div style="font-size:64px;">✓</div>
|
||||
<h1>Plată reușită!</h1>
|
||||
<p style="color:#6b7280;">Factura {{ $sub->invoice_number ?? '#' . $sub->id }} a fost achitată.</p>
|
||||
<p style="color:#6b7280;font-size:13px;margin-top:8px;">Abonamentul tău a fost extins.</p>
|
||||
<a href="/app" class="btn">→ Mergi la AutoCRM</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user