Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,15 +3,23 @@
|
|||||||
namespace App\Http\Controllers\Qr;
|
namespace App\Http\Controllers\Qr;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\PaymentGatewaySetting;
|
||||||
use App\Models\QrSetting;
|
use App\Models\QrSetting;
|
||||||
use App\Services\Billing\BillingClient;
|
use App\Services\Billing\BillingClient;
|
||||||
|
use App\Services\Billing\PlanEntitlementService;
|
||||||
|
use App\Services\Payments\MerchantGatewayService;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class AccountController extends Controller
|
class AccountController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(private BillingClient $billing) {}
|
public function __construct(
|
||||||
|
private BillingClient $billing,
|
||||||
|
private PlanEntitlementService $entitlements,
|
||||||
|
private MerchantGatewayService $gateway,
|
||||||
|
) {}
|
||||||
|
|
||||||
private function topupUrl(): string
|
private function topupUrl(): string
|
||||||
{
|
{
|
||||||
@@ -65,6 +73,8 @@ class AccountController extends Controller
|
|||||||
return view('give.settings', [
|
return view('give.settings', [
|
||||||
'account' => $account,
|
'account' => $account,
|
||||||
'settings' => $settings,
|
'settings' => $settings,
|
||||||
|
'gateway' => $this->gateway->settingFor($account),
|
||||||
|
'canUsePaymentGateway' => $this->entitlements->canUseCustomPaymentGateway($account),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +87,10 @@ class AccountController extends Controller
|
|||||||
'product_updates' => ['nullable', 'boolean'],
|
'product_updates' => ['nullable', 'boolean'],
|
||||||
'notify_registrations' => ['nullable', 'boolean'],
|
'notify_registrations' => ['nullable', 'boolean'],
|
||||||
'notify_payouts' => ['nullable', 'boolean'],
|
'notify_payouts' => ['nullable', 'boolean'],
|
||||||
|
'gateway_provider' => ['nullable', Rule::in(['', PaymentGatewaySetting::PROVIDER_PAYSTACK])],
|
||||||
|
'gateway_public_key' => ['nullable', 'string', 'max:2000'],
|
||||||
|
'gateway_secret_key' => ['nullable', 'string', 'max:2000'],
|
||||||
|
'gateway_is_active' => ['nullable', 'boolean'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
QrSetting::updateOrCreate(
|
QrSetting::updateOrCreate(
|
||||||
@@ -89,6 +103,25 @@ class AccountController extends Controller
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($this->entitlements->canUseCustomPaymentGateway($account)) {
|
||||||
|
$provider = trim((string) ($data['gateway_provider'] ?? ''));
|
||||||
|
$setting = PaymentGatewaySetting::query()->where('owner_ref', $account->public_id)->first();
|
||||||
|
if ($provider !== '' || $setting) {
|
||||||
|
$setting ??= new PaymentGatewaySetting(['owner_ref' => $account->public_id]);
|
||||||
|
if ($provider !== '') {
|
||||||
|
$setting->provider = $provider;
|
||||||
|
}
|
||||||
|
if (filled($data['gateway_public_key'] ?? null)) {
|
||||||
|
$setting->public_key = $data['gateway_public_key'];
|
||||||
|
}
|
||||||
|
if (filled($data['gateway_secret_key'] ?? null)) {
|
||||||
|
$setting->secret_key = $data['gateway_secret_key'];
|
||||||
|
}
|
||||||
|
$setting->is_active = $provider !== '' && $request->boolean('gateway_is_active');
|
||||||
|
$setting->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('account.settings')->with('success', 'Settings saved.');
|
return redirect()->route('account.settings')->with('success', 'Settings saved.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class PaymentGatewaySetting extends Model
|
||||||
|
{
|
||||||
|
public const PROVIDER_PAYSTACK = 'paystack';
|
||||||
|
|
||||||
|
protected $fillable = ['owner_ref', 'provider', 'public_key', 'secret_key', 'is_active', 'metadata'];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'public_key' => 'encrypted',
|
||||||
|
'secret_key' => 'encrypted',
|
||||||
|
'is_active' => 'boolean',
|
||||||
|
'metadata' => 'array',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isConfigured(): bool
|
||||||
|
{
|
||||||
|
return $this->is_active
|
||||||
|
&& $this->provider === self::PROVIDER_PAYSTACK
|
||||||
|
&& str_starts_with(trim((string) $this->public_key), 'pk_')
|
||||||
|
&& str_starts_with(trim((string) $this->secret_key), 'sk_');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,6 +45,15 @@ class BillingClient
|
|||||||
return $this->get('/service-ledger', ['user' => $publicId, 'service' => $service]);
|
return $this->get('/service-ledger', ['user' => $publicId, 'service' => $service]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return array<string,mixed> */
|
||||||
|
public function suiteEntitlement(string $publicId): array
|
||||||
|
{
|
||||||
|
return (array) ($this->get('/suite-entitlements', [
|
||||||
|
'user' => $publicId,
|
||||||
|
'app' => (string) config('billing.service', 'give'),
|
||||||
|
])['data'] ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debit the wallet. Returns true on success, false on insufficient balance
|
* Debit the wallet. Returns true on success, false on insufficient balance
|
||||||
* (HTTP 402). Idempotent by $reference.
|
* (HTTP 402). Idempotent by $reference.
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Billing;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class PlanEntitlementService
|
||||||
|
{
|
||||||
|
public const CUSTOM_PAYMENT_GATEWAY = 'custom_payment_gateway';
|
||||||
|
|
||||||
|
public function __construct(private readonly BillingClient $billing) {}
|
||||||
|
|
||||||
|
public function has(User $owner, string $feature): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return in_array($feature, (array) ($this->billing->suiteEntitlement((string) $owner->public_id)['features'] ?? []), true);
|
||||||
|
} catch (Throwable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canUseCustomPaymentGateway(User $owner): bool
|
||||||
|
{
|
||||||
|
return $this->has($owner, self::CUSTOM_PAYMENT_GATEWAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ use App\Services\Billing\BillingClient;
|
|||||||
use App\Services\Billing\PaystackService;
|
use App\Services\Billing\PaystackService;
|
||||||
use App\Services\Billing\SmsService;
|
use App\Services\Billing\SmsService;
|
||||||
use App\Services\Pay\PayClient;
|
use App\Services\Pay\PayClient;
|
||||||
|
use App\Services\Payments\MerchantGatewayService;
|
||||||
use App\Support\LadillLink;
|
use App\Support\LadillLink;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
@@ -19,6 +20,7 @@ class GiveDonationService
|
|||||||
private PaystackService $paystack,
|
private PaystackService $paystack,
|
||||||
private BillingClient $billing,
|
private BillingClient $billing,
|
||||||
private SmsService $sms,
|
private SmsService $sms,
|
||||||
|
private MerchantGatewayService $gateway,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,7 +65,22 @@ class GiveDonationService
|
|||||||
'payment_reference' => null,
|
'payment_reference' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$payOrder = $this->pay->createCheckout([
|
$usesOwnerGateway = $this->gateway->shouldUseOwnerGateway($qrCode->user);
|
||||||
|
$payOrder = null;
|
||||||
|
if ($usesOwnerGateway) {
|
||||||
|
try {
|
||||||
|
$payOrder = $this->gateway->initialize(
|
||||||
|
$qrCode->user, $amountMinor, (string) ($qrCode->content()['currency'] ?? 'GHS'),
|
||||||
|
(string) config('pay.mini_checkout_email', 'pay@ladill.com'),
|
||||||
|
$callbackUrl, 'GGP-'.strtoupper(Str::random(16)),
|
||||||
|
['give_donation_id' => $donation->id, 'give_reference' => $reference, 'qr_code_id' => $qrCode->id],
|
||||||
|
);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$usesOwnerGateway = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! $usesOwnerGateway) {
|
||||||
|
$payOrder = $this->pay->createCheckout([
|
||||||
'merchant' => $qrCode->user->public_id,
|
'merchant' => $qrCode->user->public_id,
|
||||||
'fee_tier' => 'donations',
|
'fee_tier' => 'donations',
|
||||||
'source_service' => 'give',
|
'source_service' => 'give',
|
||||||
@@ -85,10 +102,11 @@ class GiveDonationService
|
|||||||
'qr_code_id' => $qrCode->id,
|
'qr_code_id' => $qrCode->id,
|
||||||
'collection_type' => $collectionType,
|
'collection_type' => $collectionType,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$donation->update([
|
$donation->update([
|
||||||
'pay_order_id' => $payOrder['id'] ?? null,
|
'pay_order_id' => $usesOwnerGateway ? null : ($payOrder['id'] ?? null),
|
||||||
'payment_reference' => $payOrder['reference'],
|
'payment_reference' => $payOrder['reference'],
|
||||||
'platform_fee_minor' => $payOrder['platform_fee_minor'] ?? null,
|
'platform_fee_minor' => $payOrder['platform_fee_minor'] ?? null,
|
||||||
'merchant_amount_minor' => $payOrder['merchant_amount_minor'] ?? null,
|
'merchant_amount_minor' => $payOrder['merchant_amount_minor'] ?? null,
|
||||||
@@ -113,6 +131,9 @@ class GiveDonationService
|
|||||||
if (str_starts_with($paymentReference, 'LP-')) {
|
if (str_starts_with($paymentReference, 'LP-')) {
|
||||||
return $this->completeLadillPay($paymentReference);
|
return $this->completeLadillPay($paymentReference);
|
||||||
}
|
}
|
||||||
|
if (str_starts_with($paymentReference, 'GGP-')) {
|
||||||
|
return $this->completeOwnerGateway($paymentReference);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->completeLegacy($paymentReference);
|
return $this->completeLegacy($paymentReference);
|
||||||
}
|
}
|
||||||
@@ -182,6 +203,31 @@ class GiveDonationService
|
|||||||
return $donation;
|
return $donation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function completeOwnerGateway(string $reference): GiveDonation
|
||||||
|
{
|
||||||
|
$donation = GiveDonation::where('payment_reference', $reference)
|
||||||
|
->where('status', GiveDonation::STATUS_PENDING)
|
||||||
|
->with('merchant')
|
||||||
|
->firstOrFail();
|
||||||
|
$result = $this->gateway->verify($donation->merchant, $reference);
|
||||||
|
if (! $result['paid']) {
|
||||||
|
$donation->update(['status' => GiveDonation::STATUS_FAILED]);
|
||||||
|
throw new RuntimeException('Payment was not successful.');
|
||||||
|
}
|
||||||
|
$paidMinor = (int) ($result['amount_minor'] ?: $donation->amount_minor);
|
||||||
|
$donation->update([
|
||||||
|
'status' => GiveDonation::STATUS_PAID,
|
||||||
|
'amount_minor' => $paidMinor,
|
||||||
|
'platform_fee_minor' => 0,
|
||||||
|
'merchant_amount_minor' => $paidMinor,
|
||||||
|
'paid_at' => now(),
|
||||||
|
'metadata' => array_merge((array) $donation->metadata, ['merchant_gateway' => $result]),
|
||||||
|
]);
|
||||||
|
$this->notifyDonor($donation->fresh(['qrCode', 'merchant']));
|
||||||
|
|
||||||
|
return $donation;
|
||||||
|
}
|
||||||
|
|
||||||
private function notifyDonor(GiveDonation $donation): void
|
private function notifyDonor(GiveDonation $donation): void
|
||||||
{
|
{
|
||||||
if (! $donation->payer_phone) {
|
if (! $donation->payer_phone) {
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\Payments;
|
||||||
|
|
||||||
|
use App\Models\PaymentGatewaySetting;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\Billing\PlanEntitlementService;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class MerchantGatewayService
|
||||||
|
{
|
||||||
|
public function __construct(private readonly PlanEntitlementService $entitlements) {}
|
||||||
|
|
||||||
|
public function settingFor(User|string $owner): ?PaymentGatewaySetting
|
||||||
|
{
|
||||||
|
$ownerRef = $owner instanceof User ? (string) $owner->public_id : (string) $owner;
|
||||||
|
|
||||||
|
return PaymentGatewaySetting::query()->where('owner_ref', $ownerRef)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shouldUseOwnerGateway(User $owner): bool
|
||||||
|
{
|
||||||
|
return $this->entitlements->canUseCustomPaymentGateway($owner)
|
||||||
|
&& (bool) $this->settingFor($owner)?->isConfigured();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array{checkout_url:string,reference:string,provider:string} */
|
||||||
|
public function initialize(User $owner, int $amountMinor, string $currency, string $email, string $callbackUrl, string $reference, array $metadata = []): array
|
||||||
|
{
|
||||||
|
if (! $this->shouldUseOwnerGateway($owner)) {
|
||||||
|
throw new RuntimeException('Owner gateway is not available.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$setting = $this->settingFor($owner);
|
||||||
|
$response = Http::withToken((string) $setting->secret_key)->acceptJson()->timeout(20)
|
||||||
|
->post('https://api.paystack.co/transaction/initialize', [
|
||||||
|
'email' => $email !== '' ? $email : 'pay@ladill.com',
|
||||||
|
'amount' => $amountMinor,
|
||||||
|
'currency' => strtoupper($currency ?: 'GHS'),
|
||||||
|
'reference' => $reference,
|
||||||
|
'callback_url' => $callbackUrl,
|
||||||
|
'metadata' => $metadata,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! $response->successful() || ! ($response->json('status') ?? false) || ! $response->json('data.authorization_url')) {
|
||||||
|
throw new RuntimeException($response->json('message') ?: 'Paystack could not start checkout.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'checkout_url' => (string) $response->json('data.authorization_url'),
|
||||||
|
'reference' => (string) ($response->json('data.reference') ?: $reference),
|
||||||
|
'provider' => PaymentGatewaySetting::PROVIDER_PAYSTACK,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string,mixed> */
|
||||||
|
public function verify(User|string $owner, string $reference): array
|
||||||
|
{
|
||||||
|
$setting = $this->settingFor($owner);
|
||||||
|
if (! $setting?->isConfigured()) {
|
||||||
|
throw new RuntimeException('The owner gateway used for this payment is no longer configured.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = Http::withToken((string) $setting->secret_key)->acceptJson()->timeout(20)
|
||||||
|
->get('https://api.paystack.co/transaction/verify/'.rawurlencode($reference));
|
||||||
|
$data = (array) ($response->json('data') ?? []);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'paid' => ($response->json('status') ?? false) && strtolower((string) ($data['status'] ?? '')) === 'success',
|
||||||
|
'amount_minor' => (int) ($data['amount'] ?? 0),
|
||||||
|
'reference' => (string) ($data['reference'] ?? $reference),
|
||||||
|
'provider' => PaymentGatewaySetting::PROVIDER_PAYSTACK,
|
||||||
|
'raw' => $data,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('payment_gateway_settings', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('owner_ref', 64)->unique();
|
||||||
|
$table->string('provider', 32);
|
||||||
|
$table->text('public_key')->nullable();
|
||||||
|
$table->text('secret_key')->nullable();
|
||||||
|
$table->boolean('is_active')->default(false);
|
||||||
|
$table->json('metadata')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('payment_gateway_settings');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -56,6 +56,32 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-2xl border border-slate-200 bg-white p-5 space-y-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-sm font-semibold text-slate-900">Donor payment engine</h2>
|
||||||
|
<p class="mt-1 text-xs text-slate-500">Ladill Pay is recommended, zero-config, and selected by default.</p>
|
||||||
|
</div>
|
||||||
|
<p class="rounded-lg bg-emerald-50 px-3 py-2 text-sm text-emerald-800">Ladill Pay remains the safe fallback and requires no owner keys.</p>
|
||||||
|
@if ($canUsePaymentGateway ?? false)
|
||||||
|
<select name="gateway_provider" class="w-full rounded-lg border-slate-200 text-sm">
|
||||||
|
<option value="">Ladill Pay (recommended)</option>
|
||||||
|
<option value="paystack" @selected(old('gateway_provider', $gateway?->is_active ? $gateway?->provider : '') === 'paystack')>My Paystack account</option>
|
||||||
|
</select>
|
||||||
|
<div class="grid gap-3 sm:grid-cols-2">
|
||||||
|
<input name="gateway_public_key" placeholder="{{ $gateway?->public_key ? 'Public key saved — leave blank to keep' : 'pk_live_…' }}" class="rounded-lg border-slate-200 text-sm">
|
||||||
|
<input type="password" name="gateway_secret_key" autocomplete="new-password" placeholder="{{ $gateway?->secret_key ? 'Secret saved — leave blank to keep' : 'sk_live_…' }}" class="rounded-lg border-slate-200 text-sm">
|
||||||
|
</div>
|
||||||
|
<label class="flex items-center gap-2 text-sm text-slate-700">
|
||||||
|
<input type="hidden" name="gateway_is_active" value="0">
|
||||||
|
<input type="checkbox" name="gateway_is_active" value="1" @checked(old('gateway_is_active', $gateway?->is_active ?? false)) class="rounded border-slate-300 text-indigo-600">
|
||||||
|
Use my Paystack account
|
||||||
|
</label>
|
||||||
|
<p class="text-xs text-slate-500">Optional. Invalid or incomplete keys automatically fall back to Ladill Pay.</p>
|
||||||
|
@else
|
||||||
|
<p class="rounded-lg bg-indigo-50 px-3 py-2 text-sm text-indigo-800">Free uses Ladill Pay only. Pro and higher plans may optionally connect owner Paystack.</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn-primary">
|
<button type="submit" class="btn-primary">
|
||||||
Save settings
|
Save settings
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\PaymentGatewaySetting;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\Payments\MerchantGatewayService;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class PaymentGatewayPolicyTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_free_cannot_activate_saved_owner_gateway_but_pro_can(): void
|
||||||
|
{
|
||||||
|
config(['billing.api_url' => 'https://ladill.com/api/billing', 'billing.api_key' => 'give-test']);
|
||||||
|
$owner = User::factory()->create(['public_id' => 'usr_give_policy']);
|
||||||
|
$setting = PaymentGatewaySetting::create([
|
||||||
|
'owner_ref' => $owner->public_id, 'provider' => 'paystack',
|
||||||
|
'public_key' => 'pk_test_saved', 'secret_key' => 'sk_test_saved', 'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Http::fakeSequence()
|
||||||
|
->push(['data' => ['effective_plan' => 'free', 'features' => []]])
|
||||||
|
->push(['data' => ['effective_plan' => 'enterprise', 'features' => ['custom_payment_gateway']]])
|
||||||
|
->push(['data' => ['effective_plan' => 'enterprise', 'features' => ['custom_payment_gateway']]]);
|
||||||
|
$this->assertFalse(app(MerchantGatewayService::class)->shouldUseOwnerGateway($owner));
|
||||||
|
|
||||||
|
$setting->update(['is_active' => false]);
|
||||||
|
$this->assertFalse(app(MerchantGatewayService::class)->shouldUseOwnerGateway($owner));
|
||||||
|
$setting->update(['is_active' => true]);
|
||||||
|
$this->assertTrue(app(MerchantGatewayService::class)->shouldUseOwnerGateway($owner));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_invalid_pro_credentials_fall_back_to_ladill_pay(): void
|
||||||
|
{
|
||||||
|
config(['billing.api_url' => 'https://ladill.com/api/billing', 'billing.api_key' => 'give-test']);
|
||||||
|
Http::fake(['*/suite-entitlements*' => Http::response(['data' => [
|
||||||
|
'effective_plan' => 'pro', 'features' => ['custom_payment_gateway'],
|
||||||
|
]])]);
|
||||||
|
$owner = User::factory()->create(['public_id' => 'usr_give_invalid']);
|
||||||
|
PaymentGatewaySetting::create([
|
||||||
|
'owner_ref' => $owner->public_id, 'provider' => 'paystack',
|
||||||
|
'public_key' => 'bad', 'secret_key' => 'bad', 'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertFalse(app(MerchantGatewayService::class)->shouldUseOwnerGateway($owner));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user