diff --git a/app/Filament/Central/Pages/PaymentSettings.php b/app/Filament/Central/Pages/PaymentSettings.php index ea967ec..b3b0ba3 100644 --- a/app/Filament/Central/Pages/PaymentSettings.php +++ b/app/Filament/Central/Pages/PaymentSettings.php @@ -27,13 +27,14 @@ class PaymentSettings extends Page public function mount(): void { $s = PlatformSetting::many([ - 'payments.stripe', 'payments.paypal', 'payments.bank', + 'payments.stripe', 'payments.paypal', 'payments.paynet', 'payments.bank', 'payments.platform_currency', 'payments.invoice_prefix', 'payments.terms', 'payments.company_legal', ]); $stripe = $s['payments.stripe'] ?? []; $paypal = $s['payments.paypal'] ?? []; + $paynet = $s['payments.paynet'] ?? []; $bank = $s['payments.bank'] ?? []; $legal = $s['payments.company_legal'] ?? []; @@ -59,6 +60,14 @@ class PaymentSettings extends Page 'paypal_client_id' => $paypal['client_id'] ?? null, 'paypal_secret' => $paypal['secret'] ?? null, + 'paynet_enabled' => $paynet['enabled'] ?? false, + 'paynet_mode' => $paynet['mode'] ?? 'test', + 'paynet_merchant_code' => $paynet['merchant_code'] ?? null, + 'paynet_service_id' => $paynet['service_id'] ?? null, + 'paynet_user' => $paynet['user'] ?? null, + 'paynet_password' => $paynet['password'] ?? null, + 'paynet_secret' => $paynet['secret'] ?? null, + 'bank_enabled' => $bank['enabled'] ?? true, 'bank_iban' => $bank['iban'] ?? null, 'bank_bic' => $bank['bic'] ?? null, @@ -141,6 +150,43 @@ class PaymentSettings extends Page ->label('Secret')->password()->revealable()->placeholder('EJk...') ->visible(fn (Get $get) => $get('paypal_enabled')), ]), + Schemas\Components\Section::make('🇲🇩 Paynet (Moldova)') + ->description('Procesator de plăți Moldova. Acceptă carduri MAIB, MICB, Victoriabank, OTP, MD Cash, Bitcoin, e-money. Comision negociabil ~1.8-2.5%.') + ->columns(3) + ->collapsible() + ->collapsed(fn (Get $get) => ! $get('paynet_enabled')) + ->schema([ + Forms\Components\Toggle::make('paynet_enabled')->label('Activează Paynet')->default(false)->live(), + Forms\Components\Select::make('paynet_mode') + ->label('Mod') + ->options(['test' => '🧪 Test', 'live' => '🟢 Live']) + ->default('test') + ->visible(fn (Get $get) => $get('paynet_enabled')), + Forms\Components\Placeholder::make('paynet_webhook_info') + ->label('Webhook (Notify URL)') + ->content('https://service.mir.md/payments/paynet/webhook') + ->visible(fn (Get $get) => $get('paynet_enabled')), + Forms\Components\TextInput::make('paynet_merchant_code') + ->label('Merchant Code') + ->placeholder('AUTOCRM_001') + ->visible(fn (Get $get) => $get('paynet_enabled')), + Forms\Components\TextInput::make('paynet_service_id') + ->label('Service ID') + ->numeric() + ->placeholder('12345') + ->visible(fn (Get $get) => $get('paynet_enabled')), + Forms\Components\TextInput::make('paynet_user') + ->label('User API') + ->visible(fn (Get $get) => $get('paynet_enabled')), + Forms\Components\TextInput::make('paynet_password') + ->label('Parolă API')->password()->revealable() + ->visible(fn (Get $get) => $get('paynet_enabled')), + Forms\Components\TextInput::make('paynet_secret') + ->label('Secret semnătură')->password()->revealable() + ->columnSpanFull() + ->helperText('Cheia HMAC pentru semnarea cererilor + verificarea webhook-urilor de la Paynet.') + ->visible(fn (Get $get) => $get('paynet_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) @@ -198,6 +244,16 @@ class PaymentSettings extends Page 'secret' => $data['paypal_secret'] ?? null, ]); + PlatformSetting::put('payments.paynet', [ + 'enabled' => (bool) ($data['paynet_enabled'] ?? false), + 'mode' => $data['paynet_mode'] ?? 'test', + 'merchant_code' => $data['paynet_merchant_code'] ?? null, + 'service_id' => $data['paynet_service_id'] ?? null, + 'user' => $data['paynet_user'] ?? null, + 'password' => $data['paynet_password'] ?? null, + 'secret' => $data['paynet_secret'] ?? null, + ]); + PlatformSetting::put('payments.bank', [ 'enabled' => (bool) ($data['bank_enabled'] ?? false), 'beneficiary' => $data['bank_beneficiary'] ?? null, diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 8251a3d..2a2eb85 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -55,7 +55,7 @@ class PaymentController $method = $request->input('method'); - if (! in_array($method, ['stripe', 'paypal', 'bank'], true)) { + if (! in_array($method, ['stripe', 'paypal', 'paynet', 'bank'], true)) { abort(422, 'Method invalid'); } @@ -80,6 +80,10 @@ class PaymentController if ($method === 'paypal') { return $this->startPaypal($sub); } + + if ($method === 'paynet') { + return $this->startPaynet($sub); + } } private function startStripe(Subscription $sub) @@ -114,6 +118,26 @@ class PaymentController ]); } + private function startPaynet(Subscription $sub) + { + $paynet = PlatformSetting::get('payments.paynet', []); + if (empty($paynet['enabled']) || empty($paynet['merchant_code']) || empty($paynet['secret'])) { + return back()->with('error', 'Paynet nu este configurat. Contactează operatorul.'); + } + + $base = $paynet['mode'] === 'live' + ? 'https://paynet.md/payment' + : 'https://test.paynet.md/payment'; + + // Real flow: build POST form to Paynet with HMAC-SHA256 signature. + // Stubbed for now until merchant credentials are set + tested. + return view('site.checkout-stub', [ + 'gateway' => 'Paynet (' . strtoupper($paynet['mode']) . ')', + 'sub' => $sub, + 'note' => "În prod: redirect către {$base} cu form ascuns (Merchant_Code, Service_ID, Order_ID={$sub->invoice_number}, Amount, Currency, Notify_URL, Return_URL, semnătură HMAC-SHA256). La success, webhook semnat verifică plata și marchează factura.", + ]); + } + public function success(int $subscription) { $sub = Subscription::findOrFail($subscription); @@ -166,6 +190,44 @@ class PaymentController return response()->json(['ok' => true]); } + public function paynetWebhook(Request $request) + { + $paynet = PlatformSetting::get('payments.paynet', []); + $secret = $paynet['secret'] ?? null; + + if (! $secret) { + Log::warning('Paynet webhook hit but no secret configured'); + return response('error', 400); + } + + // Paynet sends form-encoded notification. Verify HMAC-SHA256 signature + // computed over the canonical concatenated payload. + $orderId = $request->input('Order_ID'); // matches our invoice_number + $status = $request->input('Status'); // OK / FAIL + $amount = $request->input('Amount'); + $signature = $request->input('Signature'); + + // Canonical signature payload (Merchant_Code|Order_ID|Amount|Status). + $canonical = ($paynet['merchant_code'] ?? '') . '|' . $orderId . '|' . $amount . '|' . $status; + $expected = hash_hmac('sha256', $canonical, $secret); + + if (! hash_equals($expected, (string) $signature)) { + Log::warning('Paynet webhook signature mismatch', ['order' => $orderId]); + return response('invalid signature', 400); + } + + if ($status !== 'OK') { + return response('ok', 200); + } + + $sub = Subscription::where('invoice_number', $orderId)->first(); + if ($sub) { + $this->markPaid($sub, 'paynet', $request->input('Reference_ID')); + } + + return response('ok', 200); + } + private function markPaid(Subscription $sub, string $method, ?string $reference): void { if ($sub->status === 'paid') return; @@ -188,11 +250,13 @@ class PaymentController { $stripe = PlatformSetting::get('payments.stripe', []); $paypal = PlatformSetting::get('payments.paypal', []); + $paynet = PlatformSetting::get('payments.paynet', []); $bank = PlatformSetting::get('payments.bank', []); return [ 'stripe' => ! empty($stripe['enabled']) && ! empty($stripe['publishable_key']), 'paypal' => ! empty($paypal['enabled']) && ! empty($paypal['client_id']), + 'paynet' => ! empty($paynet['enabled']) && ! empty($paynet['merchant_code']), 'bank' => ! empty($bank['enabled']) && ! empty($bank['iban']), ]; } diff --git a/resources/views/site/billing.blade.php b/resources/views/site/billing.blade.php index 93282e0..8eb369b 100644 --- a/resources/views/site/billing.blade.php +++ b/resources/views/site/billing.blade.php @@ -140,6 +140,22 @@ @endif + @if ($methods['paynet']) +
+ @endif + @if ($methods['bank']) @endif - @if (! $methods['stripe'] && ! $methods['paypal'] && ! $methods['bank']) + @if (! $methods['stripe'] && ! $methods['paypal'] && ! $methods['paynet'] && ! $methods['bank'])