From 4767316de711ac8178e0a69595858ff843a39e60 Mon Sep 17 00:00:00 2001 From: ISMAIL MASSERAN Date: Thu, 22 Oct 2026 10:10:38 +0800 Subject: [PATCH] DONE: add certs 1 and certs 3 endpoint ~ bid,otc,stb --- app/BsasOrder.php | 8 +- app/Http/Controllers/KomuditiController.php | 24 +++--- app/Services/Bsas/BsasClient.php | 29 ++++--- app/Services/Bsas/BsasOrderService.php | 78 +++++++++++++------ ..._10_13_160000_create_bsas_orders_table.php | 4 +- 5 files changed, 95 insertions(+), 48 deletions(-) diff --git a/app/BsasOrder.php b/app/BsasOrder.php index bfd1c21..9dd03ed 100644 --- a/app/BsasOrder.php +++ b/app/BsasOrder.php @@ -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', ]; } diff --git a/app/Http/Controllers/KomuditiController.php b/app/Http/Controllers/KomuditiController.php index d4f90e0..1c65b12 100644 --- a/app/Http/Controllers/KomuditiController.php +++ b/app/Http/Controllers/KomuditiController.php @@ -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, ]); } diff --git a/app/Services/Bsas/BsasClient.php b/app/Services/Bsas/BsasClient.php index 5be1515..9f64b5e 100644 --- a/app/Services/Bsas/BsasClient.php +++ b/app/Services/Bsas/BsasClient.php @@ -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( diff --git a/app/Services/Bsas/BsasOrderService.php b/app/Services/Bsas/BsasOrderService.php index 7234e23..94fa585 100644 --- a/app/Services/Bsas/BsasOrderService.php +++ b/app/Services/Bsas/BsasOrderService.php @@ -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' => '', diff --git a/database/migrations/2026_10_13_160000_create_bsas_orders_table.php b/database/migrations/2026_10_13_160000_create_bsas_orders_table.php index 34461f8..35ff155 100644 --- a/database/migrations/2026_10_13_160000_create_bsas_orders_table.php +++ b/database/migrations/2026_10_13_160000_create_bsas_orders_table.php @@ -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();