Gate payment gateway behind Pro and Business plans.
Deploy Ladill Events / deploy (push) Successful in 46s
Deploy Ladill Events / deploy (push) Successful in 46s
Free accounts can no longer connect or use Paystack/Flutterwave/Hubtel; settings show an upgrade path and checkout requires a paid subscription.
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\PaymentGatewaySetting;
|
||||
use App\Models\QrSetting;
|
||||
use App\Services\Billing\BillingClient;
|
||||
use App\Services\Events\SubscriptionService;
|
||||
use App\Services\Payments\MerchantGatewayService;
|
||||
use App\Support\AccountBranding;
|
||||
use App\Support\Qr\QrCornerStyleCatalog;
|
||||
@@ -21,6 +22,7 @@ class AccountController extends Controller
|
||||
public function __construct(
|
||||
private BillingClient $billing,
|
||||
private MerchantGatewayService $gateway,
|
||||
private SubscriptionService $subscriptions,
|
||||
) {}
|
||||
|
||||
private function topupUrl(): string
|
||||
@@ -83,6 +85,7 @@ class AccountController extends Controller
|
||||
'cornerInnerStyles' => QrCornerStyleCatalog::innerStyles(),
|
||||
'frameStyles' => QrFrameStyleCatalog::visible(),
|
||||
'gateway' => $this->gateway->settingFor($account),
|
||||
'canUsePaymentGateway' => $this->subscriptions->canUsePaymentGateway($account),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -164,7 +167,7 @@ class AccountController extends Controller
|
||||
);
|
||||
|
||||
$provider = (string) ($data['gateway_provider'] ?? '');
|
||||
if ($provider !== '') {
|
||||
if ($provider !== '' && $this->subscriptions->canUsePaymentGateway($account)) {
|
||||
$existing = PaymentGatewaySetting::query()->firstOrNew([
|
||||
'owner_ref' => $account->public_id,
|
||||
]);
|
||||
|
||||
@@ -74,6 +74,12 @@ class SubscriptionService
|
||||
return $this->hasPaidPlan($user);
|
||||
}
|
||||
|
||||
/** Own payment gateway (Paystack / Flutterwave / Hubtel) requires Pro or Business. */
|
||||
public function canUsePaymentGateway(User $user): bool
|
||||
{
|
||||
return $this->hasPaidPlan($user);
|
||||
}
|
||||
|
||||
public function liveEventCount(User $user): int
|
||||
{
|
||||
return QrCode::query()
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Services\Payments;
|
||||
|
||||
use App\Models\PaymentGatewaySetting;
|
||||
use App\Models\User;
|
||||
use App\Services\Events\SubscriptionService;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use RuntimeException;
|
||||
|
||||
@@ -18,6 +19,10 @@ class MerchantGatewayService
|
||||
|
||||
public function isConfigured(User|string $owner): bool
|
||||
{
|
||||
if (! $this->ownerMayUseGateway($owner)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->settingFor($owner)?->isConfigured();
|
||||
}
|
||||
|
||||
@@ -62,6 +67,10 @@ class MerchantGatewayService
|
||||
|
||||
protected function requireConfigured(User|string $owner): PaymentGatewaySetting
|
||||
{
|
||||
if (! $this->ownerMayUseGateway($owner)) {
|
||||
throw new RuntimeException('Connecting your own payment gateway requires a paid plan. Upgrade to Pro or Business.');
|
||||
}
|
||||
|
||||
$setting = $this->settingFor($owner);
|
||||
if (! $setting?->isConfigured()) {
|
||||
throw new RuntimeException('Connect Paystack, Flutterwave, or Hubtel in Settings before accepting online payments.');
|
||||
@@ -70,6 +79,19 @@ class MerchantGatewayService
|
||||
return $setting;
|
||||
}
|
||||
|
||||
protected function ownerMayUseGateway(User|string $owner): bool
|
||||
{
|
||||
$user = $owner instanceof User
|
||||
? $owner
|
||||
: User::query()->where('public_id', (string) $owner)->first();
|
||||
|
||||
if (! $user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return app(SubscriptionService::class)->canUsePaymentGateway($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $metadata
|
||||
* @return array{checkout_url: string, reference: string, provider: string}
|
||||
|
||||
@@ -272,8 +272,9 @@
|
||||
<div class="rounded-2xl border border-slate-200 bg-white p-5 space-y-4">
|
||||
<div>
|
||||
<h2 class="text-base font-semibold text-slate-900">Payment gateway</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Connect Paystack, Flutterwave, or Hubtel. Ticket and contribution payments go 100% to you — 0% Ladill platform fee.</p>
|
||||
<p class="mt-1 text-sm text-slate-500">Connect Paystack, Flutterwave, or Hubtel. Ticket and contribution payments go 100% to you — 0% Ladill platform fee. Pro & Business only.</p>
|
||||
</div>
|
||||
@if ($canUsePaymentGateway ?? false)
|
||||
<div>
|
||||
<label class="text-sm font-medium text-slate-700">Provider</label>
|
||||
<select name="gateway_provider" class="mt-1 w-full rounded-lg border-slate-200">
|
||||
@@ -307,6 +308,13 @@
|
||||
@else
|
||||
<p class="rounded-lg bg-amber-50 px-3 py-2 text-sm text-amber-800">Paid checkouts stay disabled until a gateway is connected.</p>
|
||||
@endif
|
||||
@else
|
||||
<div class="rounded-lg border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm text-indigo-900">
|
||||
<p class="font-medium">Your own payment gateway is a Pro feature</p>
|
||||
<p class="mt-1 text-indigo-800/80">Upgrade to Pro or Business to connect Paystack, Flutterwave, or Hubtel for paid tickets and contributions with 0% Ladill fee.</p>
|
||||
<a href="{{ route('events.pro.index') }}" class="mt-3 inline-flex text-sm font-semibold text-indigo-700 hover:text-indigo-900">View plans →</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary">
|
||||
|
||||
@@ -75,4 +75,32 @@ class EventsProTest extends TestCase
|
||||
$this->assertTrue($svc->canCreateEvent($user));
|
||||
$this->assertSame(0, $svc->liveEventCount($user));
|
||||
}
|
||||
|
||||
public function test_free_user_cannot_use_payment_gateway(): void
|
||||
{
|
||||
$user = $this->user();
|
||||
$svc = app(SubscriptionService::class);
|
||||
|
||||
$this->assertFalse($svc->canUsePaymentGateway($user));
|
||||
$this->assertFalse(app(\App\Services\Payments\MerchantGatewayService::class)->isConfigured($user));
|
||||
}
|
||||
|
||||
public function test_pro_user_can_use_payment_gateway_when_configured(): void
|
||||
{
|
||||
Http::fake(['*/debit' => Http::response(['id' => 1], 201)]);
|
||||
$user = $this->user();
|
||||
$svc = app(SubscriptionService::class);
|
||||
$svc->subscribe($user);
|
||||
|
||||
\App\Models\PaymentGatewaySetting::create([
|
||||
'owner_ref' => $user->public_id,
|
||||
'provider' => \App\Models\PaymentGatewaySetting::PROVIDER_PAYSTACK,
|
||||
'public_key' => 'pk_test',
|
||||
'secret_key' => 'sk_test',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$this->assertTrue($svc->canUsePaymentGateway($user));
|
||||
$this->assertTrue(app(\App\Services\Payments\MerchantGatewayService::class)->isConfigured($user));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user