185 lines
7.0 KiB
PHP
185 lines
7.0 KiB
PHP
<?php
|
|
/**
|
|
* qbirr — PHP client for the qbirr payment verification API.
|
|
*
|
|
* $qb = new Qbirr\Qbirr('your_api_key');
|
|
* $r = $qb->verify([
|
|
* 'provider' => 'cbe',
|
|
* 'ref' => 'FT23001234ABC',
|
|
* 'amount' => 500,
|
|
* 'receiver_name' => 'YOUR NAME',
|
|
* 'receiver_account' => '1000017692643',
|
|
* ]);
|
|
* if ($r['verified']) echo "Paid by " . $r['payer'];
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Qbirr;
|
|
|
|
class QbirrException extends \RuntimeException {
|
|
public ?int $status;
|
|
public $body;
|
|
public function __construct(string $msg, ?int $status = null, $body = null) {
|
|
parent::__construct($msg);
|
|
$this->status = $status;
|
|
$this->body = $body;
|
|
}
|
|
}
|
|
|
|
class Qbirr {
|
|
public const VERSION = '1.0.0';
|
|
public const DEFAULT_BASE_URL = 'https://verify.qbirr.com';
|
|
|
|
private string $apiKey;
|
|
private string $baseUrl;
|
|
private int $timeoutSec;
|
|
|
|
public function __construct(string $apiKey, string $baseUrl = self::DEFAULT_BASE_URL, int $timeoutSec = 30) {
|
|
if ($apiKey === '') throw new QbirrException('apiKey is required');
|
|
$this->apiKey = $apiKey;
|
|
$this->baseUrl = rtrim($baseUrl, '/');
|
|
$this->timeoutSec = $timeoutSec;
|
|
}
|
|
|
|
/**
|
|
* Verify a transaction.
|
|
*
|
|
* @param array $req {
|
|
* provider: cbe | telebirr | abyssinia | dashen | mpesa | awash | ebirr
|
|
* ref: Transaction ref. For awash, SMS URL or token. For ebirr, "tenant/token" or URL.
|
|
* amount: Expected payment in ETB.
|
|
* receiver_name: Your receiving account name.
|
|
* receiver_account: Required for cbe and abyssinia.
|
|
* }
|
|
* @return array { verified: bool, payer: string, amount: float, error: string }
|
|
*/
|
|
public function verify(array $req): array {
|
|
$body = [
|
|
'provider' => $req['provider'] ?? '',
|
|
'ref' => $req['ref'] ?? '',
|
|
'amount' => $req['amount'] ?? 0,
|
|
'receiver_name' => $req['receiver_name'] ?? '',
|
|
];
|
|
if (!empty($req['receiver_account'])) $body['receiver_account'] = $req['receiver_account'];
|
|
|
|
$data = $this->request('POST', '/api/v1/verify', $body);
|
|
return [
|
|
'verified' => (bool) ($data['verified'] ?? false),
|
|
'payer' => (string) ($data['payer'] ?? ''),
|
|
'amount' => (float) ($data['amount'] ?? 0),
|
|
'error' => (string) ($data['error'] ?? ''),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array { used, limit, remaining, plan, period_start, period_end }
|
|
*/
|
|
public function usage(): array {
|
|
return $this->request('GET', '/api/v1/usage');
|
|
}
|
|
|
|
/**
|
|
* Verify by uploading a receipt PDF or image.
|
|
*
|
|
* @param array $req { provider, amount, receiver_name, [receiver_account], filePath }
|
|
* OR { provider, ..., fileContent, fileName, mimeType }
|
|
*/
|
|
public function verifyUpload(array $req): array {
|
|
if (isset($req['filePath'])) {
|
|
$fileContent = file_get_contents($req['filePath']);
|
|
if ($fileContent === false) throw new QbirrException("Could not read file: " . $req['filePath']);
|
|
$fileName = basename($req['filePath']);
|
|
$mimeType = mime_content_type($req['filePath']) ?: 'application/octet-stream';
|
|
} else {
|
|
$fileContent = $req['fileContent'] ?? '';
|
|
$fileName = $req['fileName'] ?? 'upload.pdf';
|
|
$mimeType = $req['mimeType'] ?? 'application/octet-stream';
|
|
}
|
|
if ($fileContent === '') throw new QbirrException("Empty file content");
|
|
|
|
$boundary = '----qbirrphp' . bin2hex(random_bytes(8));
|
|
$body = '';
|
|
foreach (['provider', 'amount', 'receiver_name', 'receiver_account'] as $k) {
|
|
if (isset($req[$k]) && $req[$k] !== '') {
|
|
$body .= "--{$boundary}\r\n"
|
|
. "Content-Disposition: form-data; name=\"{$k}\"\r\n\r\n"
|
|
. $req[$k] . "\r\n";
|
|
}
|
|
}
|
|
$body .= "--{$boundary}\r\n"
|
|
. "Content-Disposition: form-data; name=\"file\"; filename=\"{$fileName}\"\r\n"
|
|
. "Content-Type: {$mimeType}\r\n\r\n"
|
|
. $fileContent . "\r\n"
|
|
. "--{$boundary}--\r\n";
|
|
|
|
$ch = curl_init();
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_URL => $this->baseUrl . '/api/v1/verify/upload',
|
|
CURLOPT_POST => true,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => $this->timeoutSec + 30,
|
|
CURLOPT_HTTPHEADER => [
|
|
'X-API-Key: ' . $this->apiKey,
|
|
'Content-Type: multipart/form-data; boundary=' . $boundary,
|
|
'Accept: application/json',
|
|
'User-Agent: qbirr-php/' . self::VERSION,
|
|
],
|
|
CURLOPT_POSTFIELDS => $body,
|
|
]);
|
|
$raw = curl_exec($ch);
|
|
$err = curl_error($ch);
|
|
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
if ($raw === false) throw new QbirrException("Network error: $err");
|
|
$data = json_decode((string) $raw, true);
|
|
if (!is_array($data)) throw new QbirrException("Non-JSON response (HTTP $code)", $code, $raw);
|
|
if ($code < 200 || $code >= 300) {
|
|
throw new QbirrException((string)($data['error'] ?? "HTTP $code"), $code, $data);
|
|
}
|
|
return [
|
|
'verified' => (bool) ($data['verified'] ?? false),
|
|
'payer' => (string) ($data['payer'] ?? ''),
|
|
'amount' => (float) ($data['amount'] ?? 0),
|
|
'error' => (string) ($data['error'] ?? ''),
|
|
];
|
|
}
|
|
|
|
private function request(string $method, string $path, ?array $body = null): array {
|
|
$ch = curl_init();
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_URL => $this->baseUrl . $path,
|
|
CURLOPT_CUSTOMREQUEST => $method,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => $this->timeoutSec,
|
|
CURLOPT_HTTPHEADER => [
|
|
'X-API-Key: ' . $this->apiKey,
|
|
'Content-Type: application/json',
|
|
'Accept: application/json',
|
|
'User-Agent: qbirr-php/' . self::VERSION,
|
|
],
|
|
]);
|
|
if ($body !== null) {
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
|
|
}
|
|
|
|
$raw = curl_exec($ch);
|
|
$err = curl_error($ch);
|
|
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
if ($raw === false) throw new QbirrException("Network error: $err");
|
|
|
|
$data = json_decode((string) $raw, true);
|
|
if (!is_array($data)) {
|
|
throw new QbirrException("Non-JSON response (HTTP $code)", $code, $raw);
|
|
}
|
|
if ($code < 200 || $code >= 300) {
|
|
$msg = $data['error'] ?? "HTTP $code";
|
|
throw new QbirrException((string) $msg, $code, $data);
|
|
}
|
|
return $data;
|
|
}
|
|
}
|