Deploy Ladill Merchant / deploy (push) Successful in 46s
Public order/pay POSTs hit ladl.link first; satellite CSRF tokens cannot bind to Link (or platform/satellite) sessions, which returned 419 and showed "Could not start checkout." Except public checkout paths and return access_code from owner-gateway Paystack init. Sync contained Paystack sheet.
89 lines
3.6 KiB
PHP
89 lines
3.6 KiB
PHP
<?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.');
|
|
}
|
|
|
|
$accessCode = (string) ($response->json('data.access_code') ?? '');
|
|
$checkoutUrl = (string) $response->json('data.authorization_url');
|
|
if ($accessCode === '' && $checkoutUrl !== '') {
|
|
$path = ltrim((string) (parse_url($checkoutUrl, PHP_URL_PATH) ?: ''), '/');
|
|
$maybe = explode('/', $path)[0] ?? '';
|
|
if (preg_match('/^[A-Za-z0-9_-]{6,}$/', $maybe) === 1) {
|
|
$accessCode = $maybe;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'checkout_url' => $checkoutUrl,
|
|
'access_code' => $accessCode !== '' ? $accessCode : null,
|
|
'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,
|
|
];
|
|
}
|
|
}
|