henryejemuta /
php-peyflex-vtu
| 1 | <?php |
||||
| 2 | |||||
| 3 | namespace HenryEjemuta\Peyflex; |
||||
| 4 | |||||
| 5 | use GuzzleHttp\Client as GuzzleClient; |
||||
| 6 | use GuzzleHttp\Exception\GuzzleException; |
||||
| 7 | |||||
| 8 | class Client |
||||
| 9 | { |
||||
| 10 | /** |
||||
| 11 | * The base URL for the Peyflex API. |
||||
| 12 | */ |
||||
| 13 | private const BASE_URL = 'https://client.peyflex.com.ng/api/'; |
||||
| 14 | |||||
| 15 | /** |
||||
| 16 | * The API Token. |
||||
| 17 | * |
||||
| 18 | * @var string |
||||
| 19 | */ |
||||
| 20 | private $token; |
||||
| 21 | |||||
| 22 | /** |
||||
| 23 | * The Guzzle HTTP Client instance. |
||||
| 24 | * |
||||
| 25 | * @var GuzzleClient |
||||
| 26 | */ |
||||
| 27 | private $httpClient; |
||||
| 28 | |||||
| 29 | /** |
||||
| 30 | * Client constructor. |
||||
| 31 | * |
||||
| 32 | * @param string $token The API Token. |
||||
| 33 | * @param array $config Configuration options (base_url, timeout, etc.). |
||||
| 34 | */ |
||||
| 35 | public function __construct(string $token, array $config = []) |
||||
| 36 | { |
||||
| 37 | $this->token = $token; |
||||
| 38 | |||||
| 39 | $baseUrl = $config['base_url'] ?? self::BASE_URL; |
||||
| 40 | // Ensure base URL ends with a slash |
||||
| 41 | if (substr($baseUrl, -1) !== '/') { |
||||
| 42 | $baseUrl .= '/'; |
||||
| 43 | } |
||||
| 44 | |||||
| 45 | $timeout = $config['timeout'] ?? 30; |
||||
| 46 | $retries = $config['retries'] ?? 3; |
||||
| 47 | |||||
| 48 | $handlerStack = $config['handler_stack'] ?? \GuzzleHttp\HandlerStack::create(); |
||||
| 49 | |||||
| 50 | if (! isset($config['handler_stack'])) { |
||||
| 51 | // Only add retry middleware if we are using the default stack |
||||
| 52 | // OR we can add it regardless, but we need to be careful about duplication if the passed stack has it. |
||||
| 53 | // Let's append it regardless, assuming the test usage knows what it's doing. |
||||
| 54 | } |
||||
| 55 | |||||
| 56 | $handlerStack->push(\GuzzleHttp\Middleware::retry( |
||||
| 57 | function ($retriesCount, $request, $response = null, $exception = null) use ($retries) { |
||||
| 58 | // Retry on connection exceptions |
||||
| 59 | if ($exception instanceof \GuzzleHttp\Exception\ConnectException) { |
||||
| 60 | return true; |
||||
| 61 | } |
||||
| 62 | |||||
| 63 | if ($exception instanceof \GuzzleHttp\Exception\RequestException && $exception->hasResponse()) { |
||||
| 64 | $response = $exception->getResponse(); |
||||
| 65 | } |
||||
| 66 | |||||
| 67 | // Retry on server errors (5xx) |
||||
| 68 | if ($response && $response->getStatusCode() >= 500) { |
||||
| 69 | // Check retries count before deciding to retry |
||||
| 70 | if ($retriesCount >= $retries) { |
||||
| 71 | return false; |
||||
| 72 | } |
||||
| 73 | |||||
| 74 | return true; |
||||
| 75 | } |
||||
| 76 | |||||
| 77 | return false; |
||||
| 78 | }, |
||||
| 79 | function ($retriesCount) { |
||||
| 80 | // Exponential backoff |
||||
| 81 | return pow(2, $retriesCount - 1) * 1000; |
||||
| 82 | } |
||||
| 83 | )); |
||||
| 84 | |||||
| 85 | $guzzleConfig = [ |
||||
| 86 | 'base_uri' => $baseUrl, |
||||
| 87 | 'timeout' => $timeout, |
||||
| 88 | 'handler' => $handlerStack, |
||||
| 89 | 'headers' => [ |
||||
| 90 | 'Accept' => 'application/json', |
||||
| 91 | 'Authorization' => 'Bearer '.$this->token, |
||||
| 92 | 'Content-Type' => 'application/json', |
||||
| 93 | ], |
||||
| 94 | ]; |
||||
| 95 | |||||
| 96 | $this->httpClient = new GuzzleClient($guzzleConfig); |
||||
| 97 | } |
||||
| 98 | |||||
| 99 | /** |
||||
| 100 | * Make a request to the API. |
||||
| 101 | * |
||||
| 102 | * @param string $method |
||||
| 103 | * @param string $uri |
||||
| 104 | * @param array $options |
||||
| 105 | * @return array |
||||
| 106 | * @throws PeyflexException |
||||
| 107 | */ |
||||
| 108 | private function request(string $method, string $uri, array $options = []): array |
||||
| 109 | { |
||||
| 110 | try { |
||||
| 111 | $response = $this->httpClient->request($method, $uri, $options); |
||||
| 112 | $content = $response->getBody()->getContents(); |
||||
| 113 | $data = json_decode($content, true); |
||||
| 114 | |||||
| 115 | if (json_last_error() !== JSON_ERROR_NONE) { |
||||
| 116 | throw new PeyflexException('Failed to decode JSON response: '.json_last_error_msg()); |
||||
| 117 | } |
||||
| 118 | |||||
| 119 | return $data; |
||||
| 120 | } catch (GuzzleException $e) { |
||||
| 121 | // Attempt to get error message from response body if available |
||||
| 122 | $message = $e->getMessage(); |
||||
| 123 | if ($e->hasResponse()) { |
||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 124 | $responseBody = $e->getResponse()->getBody()->getContents(); |
||||
|
0 ignored issues
–
show
The method
getResponse() does not exist on GuzzleHttp\Exception\GuzzleException. It seems like you code against a sub-type of GuzzleHttp\Exception\GuzzleException such as GuzzleHttp\Exception\RequestException.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 125 | $errorData = json_decode($responseBody, true); |
||||
| 126 | if (isset($errorData['message'])) { |
||||
| 127 | $message = $errorData['message']; |
||||
| 128 | } elseif (isset($errorData['error'])) { |
||||
| 129 | $message = $errorData['error']; |
||||
| 130 | } |
||||
| 131 | } |
||||
| 132 | throw new PeyflexException('API Request Failed: '.$message, $e->getCode(), $e); |
||||
| 133 | } |
||||
| 134 | } |
||||
| 135 | |||||
| 136 | /** |
||||
| 137 | * Get authenticated user profile. |
||||
| 138 | * |
||||
| 139 | * @return array |
||||
| 140 | * @throws PeyflexException |
||||
| 141 | */ |
||||
| 142 | public function getProfile(): array |
||||
| 143 | { |
||||
| 144 | return $this->request('GET', 'user/profile'); |
||||
| 145 | } |
||||
| 146 | |||||
| 147 | /** |
||||
| 148 | * Get wallet balance. |
||||
| 149 | * |
||||
| 150 | * @return array |
||||
| 151 | * @throws PeyflexException |
||||
| 152 | */ |
||||
| 153 | public function getBalance(): array |
||||
| 154 | { |
||||
| 155 | return $this->request('GET', 'user/balance'); |
||||
| 156 | } |
||||
| 157 | |||||
| 158 | /** |
||||
| 159 | * Get airtime networks. |
||||
| 160 | * |
||||
| 161 | * @return array |
||||
| 162 | * @throws PeyflexException |
||||
| 163 | */ |
||||
| 164 | public function getAirtimeNetworks(): array |
||||
| 165 | { |
||||
| 166 | return $this->request('GET', 'airtime/networks'); |
||||
| 167 | } |
||||
| 168 | |||||
| 169 | /** |
||||
| 170 | * Purchase airtime. |
||||
| 171 | * |
||||
| 172 | * @param string $network The network ID (e.g., 'mtn', 'glo'). |
||||
| 173 | * @param string $phone The phone number. |
||||
| 174 | * @param float $amount The amount to top up. |
||||
| 175 | * @return array |
||||
| 176 | * @throws PeyflexException |
||||
| 177 | */ |
||||
| 178 | public function purchaseAirtime(string $network, string $phone, float $amount): array |
||||
| 179 | { |
||||
| 180 | return $this->request('POST', 'airtime/purchase', [ |
||||
| 181 | 'json' => [ |
||||
| 182 | 'network' => $network, |
||||
| 183 | 'phone' => $phone, |
||||
| 184 | 'amount' => $amount, |
||||
| 185 | ], |
||||
| 186 | ]); |
||||
| 187 | } |
||||
| 188 | |||||
| 189 | /** |
||||
| 190 | * Get data networks. |
||||
| 191 | * |
||||
| 192 | * @return array |
||||
| 193 | * @throws PeyflexException |
||||
| 194 | */ |
||||
| 195 | public function getDataNetworks(): array |
||||
| 196 | { |
||||
| 197 | return $this->request('GET', 'data/networks'); |
||||
| 198 | } |
||||
| 199 | |||||
| 200 | /** |
||||
| 201 | * Get data plans for a network. |
||||
| 202 | * |
||||
| 203 | * @param string $networkId The network identifier (e.g., 'mtn_sme_data'). |
||||
| 204 | * @return array |
||||
| 205 | * @throws PeyflexException |
||||
| 206 | */ |
||||
| 207 | public function getDataPlans(string $networkId): array |
||||
| 208 | { |
||||
| 209 | return $this->request('GET', 'data/plans', [ |
||||
| 210 | 'query' => ['network' => $networkId], |
||||
| 211 | ]); |
||||
| 212 | } |
||||
| 213 | |||||
| 214 | /** |
||||
| 215 | * Purchase data. |
||||
| 216 | * |
||||
| 217 | * @param string $networkId The network identifier. |
||||
| 218 | * @param string $phone The phone number. |
||||
| 219 | * @param string $planId The plan identifier (from getDataPlans). |
||||
| 220 | * @return array |
||||
| 221 | * @throws PeyflexException |
||||
| 222 | */ |
||||
| 223 | public function purchaseData(string $networkId, string $phone, string $planId): array |
||||
| 224 | { |
||||
| 225 | return $this->request('POST', 'data/purchase', [ |
||||
| 226 | 'json' => [ |
||||
| 227 | 'network' => $networkId, |
||||
| 228 | 'phone' => $phone, |
||||
| 229 | 'plan' => $planId, |
||||
| 230 | ], |
||||
| 231 | ]); |
||||
| 232 | } |
||||
| 233 | |||||
| 234 | /** |
||||
| 235 | * Get cable providers. |
||||
| 236 | * |
||||
| 237 | * @return array |
||||
| 238 | * @throws PeyflexException |
||||
| 239 | */ |
||||
| 240 | public function getCableProviders(): array |
||||
| 241 | { |
||||
| 242 | return $this->request('GET', 'cable/providers'); |
||||
| 243 | } |
||||
| 244 | |||||
| 245 | /** |
||||
| 246 | * Verify cable IUC number. |
||||
| 247 | * |
||||
| 248 | * @param string $providerId The provider identifier (e.g., 'dstv'). |
||||
| 249 | * @param string $iucNumber The IUC/Smartcard number. |
||||
| 250 | * @return array |
||||
| 251 | * @throws PeyflexException |
||||
| 252 | */ |
||||
| 253 | public function verifyCable(string $providerId, string $iucNumber): array |
||||
| 254 | { |
||||
| 255 | return $this->request('POST', 'cable/verify', [ |
||||
| 256 | 'json' => [ |
||||
| 257 | 'provider' => $providerId, |
||||
| 258 | 'iuc_number' => $iucNumber, |
||||
| 259 | ], |
||||
| 260 | ]); |
||||
| 261 | } |
||||
| 262 | |||||
| 263 | /** |
||||
| 264 | * Purchase cable subscription. |
||||
| 265 | * |
||||
| 266 | * @param string $providerId The provider identifier. |
||||
| 267 | * @param string $iucNumber The IUC number. |
||||
| 268 | * @param string $planId The plan identifier. |
||||
| 269 | * @return array |
||||
| 270 | * @throws PeyflexException |
||||
| 271 | */ |
||||
| 272 | public function purchaseCable(string $providerId, string $iucNumber, string $planId): array |
||||
| 273 | { |
||||
| 274 | return $this->request('POST', 'cable/purchase', [ |
||||
| 275 | 'json' => [ |
||||
| 276 | 'provider' => $providerId, |
||||
| 277 | 'iuc_number' => $iucNumber, |
||||
| 278 | 'plan' => $planId, |
||||
| 279 | ], |
||||
| 280 | ]); |
||||
| 281 | } |
||||
| 282 | |||||
| 283 | /** |
||||
| 284 | * Get electricity plans/providers. |
||||
| 285 | * |
||||
| 286 | * @return array |
||||
| 287 | * @throws PeyflexException |
||||
| 288 | */ |
||||
| 289 | public function getElectricityPlans(): array |
||||
| 290 | { |
||||
| 291 | return $this->request('GET', 'electricity/plans', [ |
||||
| 292 | 'query' => ['identifier' => 'electricity'], |
||||
| 293 | ]); |
||||
| 294 | } |
||||
| 295 | |||||
| 296 | /** |
||||
| 297 | * Verify electricity meter. |
||||
| 298 | * |
||||
| 299 | * @param string $providerId The provider ID (e.g., 'ikeja_electric'). |
||||
| 300 | * @param string $meterNumber The meter number. |
||||
| 301 | * @param string $type The meter type ('prepaid' or 'postpaid'). |
||||
| 302 | * @return array |
||||
| 303 | * @throws PeyflexException |
||||
| 304 | */ |
||||
| 305 | public function verifyMeter(string $providerId, string $meterNumber, string $type = 'prepaid'): array |
||||
| 306 | { |
||||
| 307 | return $this->request('POST', 'electricity/verify', [ |
||||
| 308 | 'json' => [ |
||||
| 309 | 'identifier' => 'electricity', |
||||
| 310 | 'provider' => $providerId, |
||||
| 311 | 'meter_number' => $meterNumber, |
||||
| 312 | 'type' => $type, |
||||
| 313 | ], |
||||
| 314 | ]); |
||||
| 315 | } |
||||
| 316 | |||||
| 317 | /** |
||||
| 318 | * Purchase electricity token. |
||||
| 319 | * |
||||
| 320 | * @param string $providerId The provider ID. |
||||
| 321 | * @param string $meterNumber The meter number. |
||||
| 322 | * @param float $amount The amount to purchase. |
||||
| 323 | * @param string $type The meter type ('prepaid' or 'postpaid'). |
||||
| 324 | * @return array |
||||
| 325 | * @throws PeyflexException |
||||
| 326 | */ |
||||
| 327 | public function purchaseElectricity(string $providerId, string $meterNumber, float $amount, string $type = 'prepaid'): array |
||||
| 328 | { |
||||
| 329 | return $this->request('POST', 'electricity/purchase', [ |
||||
| 330 | 'json' => [ |
||||
| 331 | 'provider' => $providerId, |
||||
| 332 | 'meter_number' => $meterNumber, |
||||
| 333 | 'amount' => $amount, |
||||
| 334 | 'type' => $type, |
||||
| 335 | ], |
||||
| 336 | ]); |
||||
| 337 | } |
||||
| 338 | } |
||||
| 339 |