diff --git a/app/Http/Controllers/Qr/AccountController.php b/app/Http/Controllers/Qr/AccountController.php index 65d949d..2cb8baf 100644 --- a/app/Http/Controllers/Qr/AccountController.php +++ b/app/Http/Controllers/Qr/AccountController.php @@ -174,12 +174,17 @@ class AccountController extends Controller ); $provider = (string) ($data['gateway_provider'] ?? ''); - if ($provider !== '' && $this->subscriptions->canUsePaymentGateway($account)) { + if ($this->subscriptions->canUsePaymentGateway($account)) { $existing = PaymentGatewaySetting::query()->firstOrNew([ 'owner_ref' => $account->public_id, ]); - $existing->provider = $provider; - $existing->is_active = $request->boolean('gateway_is_active', true); + if ($provider === '' && ! $existing->exists) { + return redirect()->route('account.settings')->with('success', 'Settings saved.'); + } + if ($provider !== '') { + $existing->provider = $provider; + } + $existing->is_active = $provider !== '' && $request->boolean('gateway_is_active'); if (filled($data['gateway_public_key'] ?? null)) { $existing->public_key = $data['gateway_public_key']; } diff --git a/app/Models/PaymentGatewaySetting.php b/app/Models/PaymentGatewaySetting.php index 7c34791..72e0d40 100644 --- a/app/Models/PaymentGatewaySetting.php +++ b/app/Models/PaymentGatewaySetting.php @@ -39,12 +39,13 @@ class PaymentGatewaySetting extends Model return false; } + $public = trim((string) $this->public_key); $secret = trim((string) $this->secret_key); - return $secret !== '' && in_array($this->provider, [ - self::PROVIDER_PAYSTACK, - self::PROVIDER_FLUTTERWAVE, - self::PROVIDER_HUBTEL, - ], true); + return match ($this->provider) { + self::PROVIDER_PAYSTACK => str_starts_with($public, 'pk_') && str_starts_with($secret, 'sk_'), + self::PROVIDER_FLUTTERWAVE, self::PROVIDER_HUBTEL => $public !== '' && $secret !== '', + default => false, + }; } } diff --git a/app/Services/Events/EventRegistrationService.php b/app/Services/Events/EventRegistrationService.php index 2e80d7c..b50216d 100644 --- a/app/Services/Events/EventRegistrationService.php +++ b/app/Services/Events/EventRegistrationService.php @@ -133,30 +133,54 @@ class EventRegistrationService $feeTier = $mode === 'contributions' ? 'donations' : 'sales'; $callbackUrl = LadillLink::path($qrCode->short_code, 'register/callback'); - $payOrder = $this->pay->createCheckout([ - 'merchant' => $qrCode->user->public_id, - 'fee_tier' => $feeTier, - 'source_service' => 'events', - 'source_ref' => (string) $qrCode->id, - 'callback_url' => $callbackUrl, - 'customer_name' => $name, - 'customer_email' => $email, - 'customer_phone' => $registration->attendee_phone, - 'line_items' => [ - [ + $usesOwnerGateway = $this->gateway->shouldUseOwnerGateway($qrCode->user); + $payOrder = null; + if ($usesOwnerGateway) { + try { + $payOrder = $this->gateway->initializeCheckout( + $qrCode->user, + $amountMinor, + (string) ($content['currency'] ?? 'GHS'), + (string) config('pay.mini_checkout_email', 'pay@ladill.com'), + $callbackUrl, + 'EVTP-'.strtoupper(Str::random(16)), + [ + 'title' => $lineName, + 'registration_id' => $registration->id, + 'registration_reference' => $registration->reference, + 'qr_code_id' => $qrCode->id, + 'mode' => $mode, + 'attendee_email' => $email, + ], + ); + } catch (\Throwable) { + $usesOwnerGateway = false; + } + } + if (! $usesOwnerGateway) { + $payOrder = $this->pay->createCheckout([ + 'merchant' => $qrCode->user->public_id, + 'fee_tier' => $feeTier, + 'source_service' => 'events', + 'source_ref' => (string) $qrCode->id, + 'callback_url' => $callbackUrl, + 'customer_name' => $name, + 'customer_email' => $email, + 'customer_phone' => $registration->attendee_phone, + 'line_items' => [[ 'name' => $lineName, 'unit_price_minor' => $amountMinor, 'quantity' => 1, + ]], + 'metadata' => [ + 'registration_id' => $registration->id, + 'registration_reference' => $registration->reference, + 'qr_code_id' => $qrCode->id, + 'mode' => $mode, + 'attendee_email' => $email, ], - ], - 'metadata' => [ - 'registration_id' => $registration->id, - 'registration_reference' => $registration->reference, - 'qr_code_id' => $qrCode->id, - 'mode' => $mode, - 'attendee_email' => $email, - ], - ]); + ]); + } $checkoutUrl = (string) ($payOrder['checkout_url'] ?? ''); if ($checkoutUrl === '') { @@ -164,10 +188,11 @@ class EventRegistrationService } $registration->update([ - 'pay_order_id' => $payOrder['id'] ?? null, + 'pay_order_id' => $usesOwnerGateway ? null : ($payOrder['id'] ?? null), 'payment_reference' => $payOrder['reference'], 'metadata' => array_merge((array) $registration->metadata, [ - 'ladill_pay_init' => [ + ($usesOwnerGateway ? 'merchant_gateway_init' : 'ladill_pay_init') => [ + 'provider' => $payOrder['provider'] ?? 'ladill_pay', 'platform_fee_minor' => $payOrder['platform_fee_minor'] ?? null, 'merchant_amount_minor' => $payOrder['merchant_amount_minor'] ?? null, ], diff --git a/app/Services/Payments/MerchantGatewayService.php b/app/Services/Payments/MerchantGatewayService.php index 14da68a..38633bc 100644 --- a/app/Services/Payments/MerchantGatewayService.php +++ b/app/Services/Payments/MerchantGatewayService.php @@ -19,11 +19,13 @@ class MerchantGatewayService public function isConfigured(User|string $owner): bool { - if (! $this->ownerMayUseGateway($owner)) { - return false; - } + return $this->shouldUseOwnerGateway($owner); + } - return (bool) $this->settingFor($owner)?->isConfigured(); + public function shouldUseOwnerGateway(User|string $owner): bool + { + return $this->ownerMayUseGateway($owner) + && (bool) $this->settingFor($owner)?->isConfigured(); } /** @@ -55,7 +57,12 @@ class MerchantGatewayService */ public function verify(User|string $owner, string $reference): array { - $setting = $this->requireConfigured($owner); + // Do not re-check the current plan here: an owner-gateway payment + // initiated before a downgrade must remain verifiable. + $setting = $this->settingFor($owner); + if (! $setting?->isConfigured()) { + throw new RuntimeException('The payment gateway used for this transaction is no longer configured.'); + } return match ($setting->provider) { PaymentGatewaySetting::PROVIDER_PAYSTACK => $this->paystackVerify($setting, $reference), diff --git a/resources/views/qr/account/settings.blade.php b/resources/views/qr/account/settings.blade.php index c4d89a0..ce327e0 100644 --- a/resources/views/qr/account/settings.blade.php +++ b/resources/views/qr/account/settings.blade.php @@ -272,9 +272,31 @@
Ticket and contribution payments are processed by Ladill Pay (Paystack). Funds settle to your Ladill wallet — no API keys required.
+Ladill Pay is the recommended default and needs no API keys. Pro and Business may optionally select an owner gateway.
Ladill Pay is active for paid tickets and contributions on all plans.
+Ladill Pay is active on all plans and remains the automatic fallback.
+ @if ($canUsePaymentGateway ?? false) + +Incomplete or invalid credentials automatically fall back to Ladill Pay.
+ @else +Free uses Ladill Pay only. Upgrade to Pro to optionally connect your own gateway.
+ @endif