Feature/bsas integration #24
+6
-2
@@ -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',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user