0451ad3dbe
Reviewed-on: #24 Co-authored-by: ISMAIL MASSERAN <ismail@koppkb.com> Co-committed-by: ISMAIL MASSERAN <ismail@koppkb.com>
194 lines
6.1 KiB
PHP
194 lines
6.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Bsas;
|
|
|
|
use Illuminate\Http\Client\PendingRequest;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
use RuntimeException;
|
|
|
|
class BsasClient
|
|
{
|
|
const TOKEN_CACHE_KEY = 'bsas.oauth.token';
|
|
const TOKEN_CACHE_SECONDS = 82800; // 23 hours
|
|
|
|
public function getAccessToken(): string
|
|
{
|
|
$cached = Cache::get(self::TOKEN_CACHE_KEY);
|
|
if (is_string($cached) && $cached !== '') {
|
|
return $cached;
|
|
}
|
|
|
|
// Drop bad cache entries (e.g. whole JSON array accidentally stored).
|
|
if ($cached !== null) {
|
|
Cache::forget(self::TOKEN_CACHE_KEY);
|
|
}
|
|
|
|
$tokenUrl = config('bsas.token_url');
|
|
$clientId = config('bsas.client_id');
|
|
$clientSecret = config('bsas.client_secret');
|
|
|
|
if (!$tokenUrl || !$clientId || !$clientSecret) {
|
|
throw new RuntimeException('BSAS OAuth is not configured (token_url / client_id / client_secret).');
|
|
}
|
|
|
|
$response = $this->http()
|
|
->asForm()
|
|
->post($tokenUrl, [
|
|
'grant_type' => 'client_credentials',
|
|
'client_id' => $clientId,
|
|
'client_secret' => $clientSecret,
|
|
]);
|
|
|
|
$payload = $response->json();
|
|
if (!is_array($payload)) {
|
|
$payload = [];
|
|
}
|
|
|
|
if (!$response->successful()) {
|
|
Log::error('BSAS OAuth token request failed', [
|
|
'status' => $response->status(),
|
|
'body' => $payload ?: $response->body(),
|
|
]);
|
|
throw new RuntimeException('Gagal mendapatkan BSAS access token.');
|
|
}
|
|
|
|
$token = $payload['access_token'] ?? null;
|
|
if (!is_string($token) || $token === '') {
|
|
Log::error('BSAS OAuth response missing string access_token', [
|
|
'body' => $payload,
|
|
]);
|
|
throw new RuntimeException('BSAS OAuth response missing access_token.');
|
|
}
|
|
|
|
$expiresIn = (int) ($payload['expires_in'] ?? self::TOKEN_CACHE_SECONDS);
|
|
$ttl = max(60, min(self::TOKEN_CACHE_SECONDS, $expiresIn - 300));
|
|
Cache::put(self::TOKEN_CACHE_KEY, $token, $ttl);
|
|
|
|
return $token;
|
|
}
|
|
|
|
public function placeOrder(array $payload): array
|
|
{
|
|
return $this->requestJson('post', '/process/svc/bsas/order', $payload);
|
|
}
|
|
|
|
public function orderResult(string $uuid, array $body = []): array
|
|
{
|
|
$maxWait = (string) ($body['maxWaitTime'] ?? config('bsas.max_wait_time', 10));
|
|
|
|
$payload = [
|
|
'header' => [
|
|
'memberShortName' => config('bsas.member_short_name'),
|
|
'uuid' => $uuid,
|
|
],
|
|
'body' => [
|
|
'serialNumbers' => (string) ($body['serialNumbers'] ?? '1'),
|
|
'forceYN' => (string) ($body['forceYN'] ?? 'Y'),
|
|
'maxWaitTime' => $maxWait,
|
|
'waitAllDoneYN' => (string) ($body['waitAllDoneYN'] ?? 'Y'),
|
|
],
|
|
];
|
|
|
|
// Long poll: client timeout must exceed BSAS maxWaitTime.
|
|
$timeout = max(
|
|
(int) config('bsas.http_timeout', 30),
|
|
(int) $maxWait + 20
|
|
);
|
|
|
|
return $this->requestJson('get', '/process/svc/bsas/orderResult', $payload, $timeout);
|
|
}
|
|
|
|
public function getBidEcert(string $ecertNo): array
|
|
{
|
|
return $this->requestEcert('/process/svc/bsas/bidXML', $ecertNo);
|
|
}
|
|
|
|
public function getOtcEcert(string $ecertNo): array
|
|
{
|
|
return $this->requestEcert('/process/svc/bsas/otcXML', $ecertNo);
|
|
}
|
|
|
|
public function getStbEcert(string $ecertNo): array
|
|
{
|
|
return $this->requestEcert('/process/svc/bsas/stbXML', $ecertNo);
|
|
}
|
|
|
|
protected function requestEcert(string $path, string $ecertNo): array
|
|
{
|
|
$payload = [
|
|
'input' => [
|
|
'ecertno' => $ecertNo,
|
|
'membershortname' => config('bsas.member_short_name'),
|
|
],
|
|
];
|
|
|
|
return $this->requestJson('get', $path, $payload);
|
|
}
|
|
|
|
protected function requestJson(string $method, string $path, array $payload, ?int $timeout = null): array
|
|
{
|
|
$url = rtrim(config('bsas.base_url'), '/') . '/' . ltrim($path, '/');
|
|
$timeout = $timeout ?? (int) config('bsas.http_timeout', 30);
|
|
$token = $this->getAccessToken();
|
|
|
|
$pending = $this->http($timeout)
|
|
->withToken($token)
|
|
->acceptJson();
|
|
|
|
if (strtolower($method) === 'get') {
|
|
$response = $pending
|
|
->withHeaders(['Content-Type' => 'application/json'])
|
|
->send('GET', $url, [
|
|
'body' => json_encode($payload),
|
|
]);
|
|
} else {
|
|
$response = $pending->asJson()->post($url, $payload);
|
|
}
|
|
|
|
$json = $response->json();
|
|
if (!is_array($json)) {
|
|
$body = $response->body();
|
|
$cloudflare = stripos($body, 'cloudflare') !== false || stripos($body, 'Attention Required') !== false;
|
|
|
|
if ($cloudflare) {
|
|
throw new RuntimeException(
|
|
'BSAS blocked by Cloudflare (check IP whitelist). HTTP ' . $response->status()
|
|
);
|
|
}
|
|
|
|
throw new RuntimeException('BSAS returned invalid JSON response.');
|
|
}
|
|
|
|
if (!$response->successful()) {
|
|
Log::error('BSAS HTTP error', [
|
|
'method' => $method,
|
|
'url' => $url,
|
|
'status' => $response->status(),
|
|
'body' => $json,
|
|
]);
|
|
throw new RuntimeException('BSAS request failed (HTTP ' . $response->status() . ').');
|
|
}
|
|
|
|
Log::info('BSAS API call', [
|
|
'method' => $method,
|
|
'path' => $path,
|
|
'status' => $response->status(),
|
|
'header' => $json['header'] ?? null,
|
|
]);
|
|
|
|
return $json;
|
|
}
|
|
|
|
protected function http(?int $timeout = null): PendingRequest
|
|
{
|
|
$timeout = $timeout ?? (int) config('bsas.http_timeout', 30);
|
|
|
|
return Http::withOptions([
|
|
'timeout' => $timeout,
|
|
'connect_timeout' => min(15, $timeout),
|
|
]);
|
|
}
|
|
}
|