Files
kaunter/app/Services/ShorakaFinancingService.php
T

243 lines
10 KiB
PHP

<?php
namespace App\Services;
use App\Http\Controllers\GadaianController;
use App\Http\Controllers\AliranTunaiController;
use App\Events\Dashboard\GadaiNotify;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Throwable;
use Illuminate\Support\Facades\Cache;
class ShorakaFinancingService
{
public function ensureReady(): void
{
$response = $this->api()->get(rtrim(config('api.url'), '/') . '/api/shoraka/readiness')->throw()->json();
abort_unless(!empty($response['ready']), 503, 'Shoraka belum dikonfigurasi. Pembiayaan baharu ditahan.');
}
public function api()
{
return Http::withOptions(['verify' => config('app.api_verify')])
->acceptJson()->timeout(75);
}
public function url(string $reference, string $suffix = ''): string
{
return rtrim(config('api.url'), '/') . '/api/shoraka/gadai/'
. rawurlencode(Auth::user()->cawangan) . '/' . rawurlencode($reference) . $suffix;
}
public function financingUrl(string $reference): string
{
return rtrim(config('api.url'), '/') . '/api/shoraka/financings/'
. rawurlencode(Auth::user()->cawangan) . '/' . rawurlencode($reference);
}
public function local(string $reference): ?array
{
$response = $this->api()->get($this->financingUrl($reference));
if ($response->status() === 404) {
return null;
}
if ($response->serverError()) {
abort(503, 'Shoraka belum dikonfigurasi. Pembiayaan baharu ditahan.');
}
$response->throw();
return $this->record($response->json()['data'] ?? []);
}
public function listForBranch(string $branch)
{
$response = $this->api()->get(rtrim(config('api.url'), '/') . '/api/shoraka/financings', [
'kodcaw' => $branch,
'per_page' => 100,
])->throw()->json();
return collect($response['data'] ?? [])->map(function ($row) {
return $this->record($row);
});
}
public function search(array $filters, ?string $onlyBranch): array
{
$query = [
'per_page' => 25,
'page' => max(1, (int) request()->input('page', 1)),
'with_branches' => 1,
];
if ($onlyBranch) {
$query['kodcaw'] = $onlyBranch;
$query['branch_scope'] = $onlyBranch;
} elseif (!empty($filters['kodcaw'])) {
$query['kodcaw'] = $filters['kodcaw'];
}
foreach (['norujukan', 'state', 'date_from', 'date_to'] as $field) {
if (!empty($filters[$field])) {
$query[$field] = $filters[$field];
}
}
$response = $this->api()->get(rtrim(config('api.url'), '/') . '/api/shoraka/financings', $query)->throw()->json();
$items = collect($response['data'] ?? [])->map(function ($row) {
return $this->record($row);
});
$records = new LengthAwarePaginator(
$items,
(int) ($response['total'] ?? 0),
(int) ($response['per_page'] ?? 25),
(int) ($response['current_page'] ?? 1),
['path' => request()->url(), 'query' => request()->query()]
);
return [$records, collect($response['branches'] ?? [])];
}
public function hold(Request $request, string $reference, string $action, array $extra = [])
{
$user = Auth::user();
$context = array_merge($request->except(['_token', 'password']), $extra);
if (!empty($extra['temporary_gadai'])) {
$key = 'shoraka:payout:' . hash('sha256', $user->cawangan . ':' . $reference);
$temporaryContext = $context;
$context = ['temporary_gadai' => true, 'pinjaman' => $extra['pinjaman']];
}
$response = $this->api()->post($this->financingUrl($reference), [
'teller' => $user->name,
'action' => $action,
'context' => $context,
])->throw()->json();
if (!empty($response['created'])) {
if (isset($temporaryContext) && !Cache::store('file')->put($key, $temporaryContext, 604800)) {
$this->api()->put($this->financingUrl($reference), ['state' => 'REVIEW'])->throw();
abort(503, 'Maklumat pembiayaan sementara tidak dapat disimpan.');
}
try {
$this->api()->post($this->url($reference, '/submit'));
} catch (Throwable $exception) {
// The persisted workflow remains available after timeouts.
}
}
return $this->pendingResponse($request, $reference);
}
public function pendingResponse(Request $request, string $reference)
{
$url = route('shoraka.show', ['norujukan' => $reference]);
if ($request->ajax() || $request->expectsJson()) {
return response()->json(['shoraka_pending' => true, 'redirect' => $url], 202);
}
return redirect($url);
}
public function finish(Request $request, string $reference)
{
$record = $this->local($reference);
abort_unless($record && ($record['teller'] ?? null) === Auth::user()->name, 403,
'Pembiayaan perlu diselesaikan oleh teller asal.');
if (($record['state'] ?? null) === 'DONE') {
return redirect()->route('shoraka.show', ['norujukan' => $reference]);
}
abort_unless(($record['state'] ?? null) === 'WAITING', 409,
'Penyelesaian telah diproses atau memerlukan semakan operasi. Jangan ulang bayaran.');
$this->api()->post($this->url($reference, '/refresh'))->throw();
$snapshot = $this->api()->get($this->url($reference))->throw()->json()['data'];
abort_unless(!empty($snapshot['can_complete']), 409,
'Bayaran ditahan sehingga Shoraka Completed dan amaun sepadan.');
abort_unless(
number_format((float) ($record['context']['pinjaman'] ?? 0), 2, '.', '')
=== number_format((float) ($snapshot['order_amount'] ?? 0), 2, '.', ''),
409, 'Amaun pembiayaan berubah. Semakan operasi diperlukan sebelum bayaran.'
);
$context = $record['context'] ?? [];
if (!empty($context['temporary_gadai'])) {
$context = Cache::store('file')->get('shoraka:payout:' . hash('sha256', $record['kodcaw'] . ':' . $reference));
abort_unless(is_array($context), 409, 'Maklumat sementara tamat. Semakan operasi diperlukan sebelum bayaran.');
}
$claim = $this->api()->put($this->financingUrl($reference), [
'state' => 'PROCESSING',
'expected_state' => 'WAITING',
])->throw()->json();
abort_unless(!empty($claim['claimed']), 409, 'Penyelesaian sedang diproses.');
$saved = new Request($context);
$saved->setLaravelSession($request->session());
try {
if (($record['action'] ?? null) === 'gadai') {
$result = app(GadaianController::class)->completeShorakaFinancing($saved);
} else {
if (!empty($record['context']['update_history'])) {
$this->api()->put(rtrim(config('api.url'), '/') . '/api/gadai/updatehistory/'
. rawurlencode($record['kodcaw']) . '/' . rawurlencode($reference), [
'historygadaian' => $saved->norujukan,
'jenistebus' => $saved->jenistebus,
])->throw();
}
$cash = app(AliranTunaiController::class);
if (($record['action'] ?? null) === 'autotawarruq') {
$cash->alirantunaiTebusAutoTawarruq($saved);
} else {
$cash->alirantunaiTebus($saved);
}
event(new \App\Events\Dashboard\TebusNotify('lulus', $record['kodcaw']));
event(new GadaiNotify('lulus', $record['kodcaw']));
$result = redirect()->route('shoraka.show', ['norujukan' => $reference]);
}
$this->api()->put($this->financingUrl($reference), [
'state' => 'DONE',
'context' => [],
])->throw();
if (!empty($context['temporary_gadai'])) {
Cache::store('file')->forget('shoraka:payout:' . hash('sha256', $record['kodcaw'] . ':' . $reference));
}
return $result;
} catch (Throwable $exception) {
try {
$this->api()->put($this->financingUrl($reference), ['state' => 'REVIEW'])->throw();
} catch (Throwable $updateError) {
}
throw $exception;
}
}
public function printData(string $reference): ?array
{
$response = $this->api()->get($this->url($reference))->throw()->json()['data'];
$order = $response['order'] ?? null;
if (!$order) {
abort_if($this->local($reference), 409, 'SAG ditahan: transaksi Shoraka belum tersedia.');
return null; // Historical financing keeps its historical print path.
}
abort_unless(!empty($response['can_complete']), 409, 'SAG ditahan: Shoraka belum Completed.');
$details = $response['details'] ?? [];
// Shoraka may return a list of pipe-delimited certificate details.
$certificateDetails = $details['certificateDetails1'] ?? '';
if (is_array($certificateDetails)) {
$certificateDetails = $certificateDetails[0] ?? '';
}
$certificate = is_string($certificateDetails) ? trim(explode('|', $certificateDetails)[0]) : '';
abort_unless($certificate !== '' && isset($details['unit'], $details['volume'], $details['commodityType']),
409, 'Maklumat sijil belum lengkap. Semak status Shoraka dahulu.');
return [
'komoditi' => ['no_sijil' => $certificate],
'product' => [
'kodproduk' => $details['commodityType'],
'description' => $details['commodityType'] === '000-COPPER' ? 'Copper' : 'Plastic resin',
'unit' => $details['unit'],
],
'kuantiti' => $details['volume'],
];
}
private function record(array $data): array
{
$data['context'] = $data['context'] ?? [];
return $data;
}
}