settingValue('paystack_public_key', config('services.paystack.public_key', '')); } public function initializeTransaction(array $payload): array { return $this->initializeTransactionWithCredentials($payload); } public function initializeTransactionWithCredentials(array $payload, ?string $secretKey = null, ?string $baseUrl = null): array { $response = $this->request(secretKey: $secretKey, baseUrl: $baseUrl) ->post('/transaction/initialize', $payload); return $this->extractData($response, 'Failed to initialize Paystack transaction.'); } public function verifyTransaction(string $reference): array { return $this->verifyTransactionWithCredentials($reference); } public function verifyTransactionWithCredentials(string $reference, ?string $secretKey = null, ?string $baseUrl = null): array { $response = $this->request(secretKey: $secretKey, baseUrl: $baseUrl) ->get('/transaction/verify/' . urlencode($reference)); return $this->extractData($response, 'Failed to verify Paystack transaction.'); } public function verifyWebhookSignature(string $rawBody, ?string $signature): bool { $secret = (string) $this->settingValue('paystack_webhook_secret', config('services.paystack.webhook_secret')); if ($secret === '') { $secret = (string) $this->settingValue('paystack_secret_key', config('services.paystack.secret_key')); } if ($secret === '' || !$signature) { return false; } $computed = hash_hmac('sha512', $rawBody, $secret); return hash_equals($computed, $signature); } protected function request(?string $secretKey = null, ?string $baseUrl = null) { $resolvedBaseUrl = rtrim((string) ($baseUrl ?: $this->settingValue('paystack_base_url', config('services.paystack.base_url', 'https://api.paystack.co'))), '/'); $secret = $this->normalizeSecretKey( (string) ($secretKey ?: $this->settingValue('paystack_secret_key', config('services.paystack.secret_key'))) ); return Http::baseUrl($resolvedBaseUrl) ->withToken($secret) ->acceptJson() ->asJson(); } protected function settingsConnection(): string { return (string) config('billing.platform_settings_connection', 'platform'); } protected function settingValue(string $key, mixed $fallback = null): mixed { try { $connection = $this->settingsConnection(); if (! Schema::connection($connection)->hasTable('platform_settings')) { return $fallback; } $setting = PlatformSetting::query() ->where('key', $key) ->where('is_active', true) ->first(); return $setting ? ($setting->value['value'] ?? $fallback) : $fallback; } catch (\Throwable) { return $fallback; } } protected function extractData(Response $response, string $message): array { if ($response->failed()) { throw new RuntimeException($message); } $json = $response->json(); if (!is_array($json) || !($json['status'] ?? false)) { throw new RuntimeException((string) ($json['message'] ?? $message)); } $data = $json['data'] ?? null; return is_array($data) ? $data : []; } /** * List supported banks / mobile money providers. * @param string $country e.g. 'ghana', 'nigeria' * @param string $type 'nuban', 'mobile_money', or '' for all */ public function listBanks(string $country = 'ghana', string $type = '', ?string $currency = null): array { $query = ['perPage' => 200]; if ($currency !== null && $currency !== '') { $query['currency'] = strtoupper($currency); } else { $query['country'] = $country; } if ($type !== '') { $query['type'] = $type; } $response = $this->request()->get('/bank', $query); $json = $response->json(); return is_array($json['data'] ?? null) ? $json['data'] : []; } /** * Create a transfer recipient (bank account or mobile money). * @param array{type: string, name: string, account_number: string, bank_code: string, currency?: string} $payload */ public function createTransferRecipient(array $payload): array { $response = $this->request()->post('/transferrecipient', $payload); return $this->extractData($response, 'Failed to create transfer recipient.'); } /** * Initiate a transfer to a recipient. * @param array{source: string, amount: int, recipient: string, reason?: string, reference?: string} $payload */ public function initiateTransfer(array $payload): array { $response = $this->request()->post('/transfer', $payload); return $this->extractData($response, 'Failed to initiate transfer.'); } /** * Verify a transfer by reference. */ public function verifyTransfer(string $reference): array { $response = $this->request()->get('/transfer/verify/' . urlencode($reference)); return $this->extractData($response, 'Failed to verify transfer.'); } protected function normalizeSecretKey(string $secret): string { $normalized = trim($secret); if (str_starts_with(strtolower($normalized), 'bearer ')) { $normalized = trim(substr($normalized, 7)); } return $normalized; } }