Feature/bsas integration #24

Merged
ismailmasseran merged 3 commits from feature/bsas-integration into main 2026-10-26 15:35:48 +08:00
5 changed files with 95 additions and 48 deletions
Showing only changes of commit 4767316de7 - Show all commits
+6 -2
View File
@@ -22,7 +22,9 @@ class BsasOrder extends Model
'status_code',
'status_message',
'e_cert_no',
'ecert_response',
'bid_ecert_response',
'otc_ecert_response',
'stb_ecert_response',
'request_payload',
'order_response',
'result_response',
@@ -35,6 +37,8 @@ class BsasOrder extends Model
'request_payload' => 'array',
'order_response' => 'array',
'result_response' => 'array',
'ecert_response' => 'array',
'bid_ecert_response' => 'array',
'otc_ecert_response' => 'array',
'stb_ecert_response' => 'array',
];
}
+15 -9
View File
@@ -361,7 +361,9 @@ class KomuditiController extends Controller
'status_code',
'status_message',
'e_cert_no',
'ecert_response',
'bid_ecert_response',
'otc_ecert_response',
'stb_ecert_response',
'created_by',
'created_at',
'updated_at',
@@ -379,8 +381,10 @@ class KomuditiController extends Controller
$rows = $query->get()->map(function (BsasOrder $order) {
$payload = $order->toArray();
$payload['has_ecert'] = !empty($order->ecert_response);
unset($payload['ecert_response']);
$payload['has_bid_ecert'] = !empty($order->bid_ecert_response);
$payload['has_otc_ecert'] = !empty($order->otc_ecert_response);
$payload['has_stb_ecert'] = !empty($order->stb_ecert_response);
unset($payload['bid_ecert_response'], $payload['otc_ecert_response'], $payload['stb_ecert_response']);
return $payload;
});
@@ -404,16 +408,16 @@ class KomuditiController extends Controller
return response()->json(['message' => 'BSAS order tidak dijumpai untuk norujukan ini.'], 404);
}
$ecert = is_array($order->ecert_response) ? $order->ecert_response : [];
$otc = is_array($order->otc_ecert_response) ? $order->otc_ecert_response : [];
$productCode = (string) ($order->product_code ?: config('bsas.product_code'));
$description = trim((string) ($ecert['PNAME'] ?? ''));
$description = trim((string) ($otc['PNAME'] ?? ''));
if ($description === '') {
$produk = KomuditiProduk::on('mysql7')->where('kodproduk', $productCode)->first();
$description = $produk ? (string) $produk->description : $productCode;
}
$price = (float) ($ecert['PRICE_MYR_EQUIVALENT'] ?? $ecert['PRICE'] ?? 0);
$volume = (float) ($ecert['PVOLUME'] ?? 0);
$price = (float) ($otc['PRICE_MYR_EQUIVALENT'] ?? $otc['PRICE'] ?? 0);
$volume = (float) ($otc['PVOLUME'] ?? 0);
$pinjaman = (float) $order->bid_value;
if ($volume <= 0 && $price > 0 && $pinjaman > 0) {
$volume = round($pinjaman / $price, 4);
@@ -448,7 +452,7 @@ class KomuditiController extends Controller
return response()->json(['message' => 'BSAS order tidak dijumpai.'], 404);
}
if (empty($order->ecert_response)) {
if (empty($order->bid_ecert_response) && empty($order->otc_ecert_response) && empty($order->stb_ecert_response)) {
return response()->json(['message' => 'eCert belum tersedia untuk order ini.'], 404);
}
@@ -461,7 +465,9 @@ class KomuditiController extends Controller
'bid_value' => $order->bid_value,
'otc_murabaha_value' => $order->otc_murabaha_value,
'otc_counter_party' => $order->otc_counter_party,
'ecert' => $order->ecert_response,
'bid_ecert' => $order->bid_ecert_response,
'otc_ecert' => $order->otc_ecert_response,
'stb_ecert' => $order->stb_ecert_response,
]);
}
+17 -12
View File
@@ -100,7 +100,22 @@ class BsasClient
return $this->requestJson('get', '/process/svc/bsas/orderResult', $payload, $timeout);
}
public function getEcert(string $ecertNo): array
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' => [
@@ -109,7 +124,7 @@ class BsasClient
],
];
return $this->requestJson('get', '/process/svc/bsas/otcXML', $payload);
return $this->requestJson('get', $path, $payload);
}
protected function requestJson(string $method, string $path, array $payload, ?int $timeout = null): array
@@ -123,9 +138,6 @@ class BsasClient
->acceptJson();
if (strtolower($method) === 'get') {
// BSAS orderResult/otcXML use GET with a JSON body.
// - Do NOT use asJson(): it triggers Cloudflare 403 on this endpoint.
// - Do NOT use get()+withBody(): Laravel get() drops the body; uuid never arrives.
$response = $pending
->withHeaders(['Content-Type' => 'application/json'])
->send('GET', $url, [
@@ -139,13 +151,6 @@ class BsasClient
if (!is_array($json)) {
$body = $response->body();
$cloudflare = stripos($body, 'cloudflare') !== false || stripos($body, 'Attention Required') !== false;
Log::error('BSAS non-JSON response', [
'method' => $method,
'url' => $url,
'status' => $response->status(),
'cloudflare_block' => $cloudflare,
'body' => $body,
]);
if ($cloudflare) {
throw new RuntimeException(
+54 -24
View File
@@ -43,7 +43,7 @@ class BsasOrderService
->first();
if ($existing && $existing->status === 'confirmed') {
if (empty($existing->e_cert_no)) {
if ($this->needsEcertFetch($existing)) {
$this->fetchAndStoreEcert($existing);
}
$this->ensureKomuditiTransaksi($existing, $createdBy);
@@ -120,7 +120,7 @@ class BsasOrderService
}
if ($order->status === 'confirmed') {
if (empty($order->e_cert_no)) {
if ($this->needsEcertFetch($order)) {
$this->fetchAndStoreEcert($order);
}
return $order->fresh();
@@ -144,8 +144,10 @@ class BsasOrderService
$statusMessage = (string) data_get($result, 'body.0.statusMessage', '');
$bidErrNo = trim((string) data_get($result, 'body.0.bidErrNo', ''));
$otcErrNo = trim((string) data_get($result, 'body.0.otcErrNo', ''));
$stbErrNo = trim((string) data_get($result, 'body.0.stbErrNo', ''));
$bidMsg = trim((string) data_get($result, 'body.0.bidMsg', ''));
$otcMsg = trim((string) data_get($result, 'body.0.otcMsg', ''));
$stbMsg = trim((string) data_get($result, 'body.0.stbMsg', ''));
$ecertNo = $this->extractEcertNo($result, $order->order_response);
if ($headerError !== '') {
@@ -156,10 +158,11 @@ class BsasOrderService
throw new RuntimeException($order->status_message);
}
// Live BSAS result often has no statusCode; success is ecertNo and/or bidErrNo/otcErrNo=999 (OK).
// Live BSAS result often has no statusCode; success is ecertNo and/or bid/otc/stb ErrNo=999 (OK).
$successByStatusCode = $statusCode !== null && (int) $statusCode === 0;
$successByErrNo = ($bidErrNo === '999' || strtoupper($bidMsg) === 'OK')
&& ($otcErrNo === '999' || strtoupper($otcMsg) === 'OK');
&& ($otcErrNo === '999' || strtoupper($otcMsg) === 'OK')
&& ($stbErrNo === '999' || strtoupper($stbMsg) === 'OK');
$successByEcert = $ecertNo !== null && $ecertNo !== '';
if (!$successByStatusCode && !$successByErrNo && !$successByEcert) {
@@ -185,9 +188,11 @@ class BsasOrderService
}
$order->save();
if (!empty($order->e_cert_no) && empty($order->ecert_response)) {
if ($this->needsEcertFetch($order)) {
$this->fetchAndStoreEcert($order);
} elseif (empty($order->e_cert_no)) {
}
if (empty($order->e_cert_no)) {
Log::warning('BSAS order confirmed but ecertno missing', [
'norujukan' => $order->norujukan,
'uuid' => $order->uuid,
@@ -212,12 +217,17 @@ class BsasOrderService
'waitAllDoneYN' => 'Y',
]);
// Done when we have statusCode, ecert, or bid/otc result codes.
// Done when statusCode is set, or bid, OTC and STB have all reported.
$bidDone = trim((string) data_get($last, 'body.0.bidErrNo', '')) !== '';
$otcDone = trim((string) data_get($last, 'body.0.otcErrNo', '')) !== '';
$stbDone = trim((string) data_get($last, 'body.0.stbErrNo', '')) !== '';
$hasEcert = trim((string) data_get($last, 'body.0.ecertNo', '')) !== ''
|| trim((string) data_get($last, 'body.0.eCertNo', '')) !== '';
if (
data_get($last, 'body.0.statusCode') !== null
|| trim((string) data_get($last, 'body.0.ecertNo', '')) !== ''
|| trim((string) data_get($last, 'body.0.eCertNo', '')) !== ''
|| trim((string) data_get($last, 'body.0.bidErrNo', '')) !== ''
|| ($bidDone && $otcDone && $stbDone)
|| ($hasEcert && $stbDone)
) {
return $last;
}
@@ -226,6 +236,13 @@ class BsasOrderService
return $last;
}
protected function needsEcertFetch(BsasOrder $order): bool
{
return empty($order->bid_ecert_response)
|| empty($order->otc_ecert_response)
|| empty($order->stb_ecert_response);
}
protected function fetchAndStoreEcert(BsasOrder $order): void
{
$ecertNo = $order->e_cert_no;
@@ -241,20 +258,33 @@ class BsasOrderService
return;
}
try {
$ecert = $this->client->getEcert($ecertNo);
$order->ecert_response = $ecert;
if (empty($order->e_cert_no) && !empty($ecert['ECERTNO'])) {
$order->e_cert_no = $ecert['ECERTNO'];
$calls = [
'bid_ecert_response' => 'getBidEcert',
'otc_ecert_response' => 'getOtcEcert',
'stb_ecert_response' => 'getStbEcert',
];
foreach ($calls as $column => $method) {
if (!empty($order->{$column})) {
continue;
}
try {
$ecert = $this->client->{$method}($ecertNo);
$order->{$column} = $ecert;
if ($column === 'otc_ecert_response' && empty($order->e_cert_no) && !empty($ecert['ECERTNO'])) {
$order->e_cert_no = $ecert['ECERTNO'];
}
$order->save();
} catch (\Throwable $e) {
Log::error('BSAS eCert fetch failed after confirmed order', [
'norujukan' => $order->norujukan,
'uuid' => $order->uuid,
'e_cert_no' => $ecertNo,
'certificate' => $column,
'error' => $e->getMessage(),
]);
}
$order->save();
} catch (\Throwable $e) {
Log::error('BSAS eCert fetch failed after confirmed order', [
'norujukan' => $order->norujukan,
'uuid' => $order->uuid,
'e_cert_no' => $ecertNo,
'error' => $e->getMessage(),
]);
}
}
@@ -291,7 +321,7 @@ class BsasOrderService
'serialNumber' => $serial,
'bidOption' => 'Y',
'otcOption' => 'Y',
'stbOption' => 'N',
'stbOption' => 'Y',
'productCode' => config('bsas.product_code'),
'purchaseType' => 'P',
'clientName' => '',
@@ -29,7 +29,9 @@ class CreateBsasOrdersTable extends Migration
$table->integer('status_code')->nullable();
$table->string('status_message', 255)->nullable();
$table->string('e_cert_no', 100)->nullable()->index();
$table->json('ecert_response')->nullable();
$table->json('bid_ecert_response')->nullable();
$table->json('otc_ecert_response')->nullable();
$table->json('stb_ecert_response')->nullable();
$table->json('request_payload')->nullable();
$table->json('order_response')->nullable();
$table->json('result_response')->nullable();