feat(shoraka): defer gadai persistence until commodity confirmation and add simulation support

This commit is contained in:
2026-10-19 13:07:10 +08:00
parent 63d8c23785
commit 011fbfe8b2
18 changed files with 437 additions and 59 deletions
+23 -3
View File
@@ -10,12 +10,13 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Throwable;
use Illuminate\Support\Facades\Cache;
class ShorakaFinancingService
{
public function ensureReady(): void
{
// Check local migration before any registration side effect.
// Check the configured financing database before any registration side effect.
ShorakaFinancing::where('kodcaw', Auth::user()->cawangan)->exists();
$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.');
@@ -42,15 +43,25 @@ class ShorakaFinancingService
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']];
}
$record = ShorakaFinancing::firstOrCreate(
['kodcaw' => $user->cawangan, 'norujukan' => $reference],
[
'teller' => $user->name, 'action' => $action, 'state' => 'WAITING',
'context' => array_merge($request->except(['_token', 'password']), $extra),
'context' => $context,
]
);
// A duplicate request only opens the saved workflow; it never replays registration.
if ($record->wasRecentlyCreated) {
if (isset($temporaryContext) && !Cache::store('file')->put($key, $temporaryContext, 604800)) {
$record->update(['state' => 'REVIEW']);
abort(503, 'Maklumat pembiayaan sementara tidak dapat disimpan.');
}
try {
$this->api()->post($this->url($reference, '/submit'));
} catch (Throwable $exception) {
@@ -91,11 +102,17 @@ class ShorakaFinancingService
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.');
}
$claimed = ShorakaFinancing::where('id', $record->id)->where('state', 'WAITING')
->update(['state' => 'PROCESSING', 'updated_at' => now()]);
abort_unless($claimed === 1, 409, 'Penyelesaian sedang diproses.');
$saved = new Request($record->context);
$saved = new Request($context);
$saved->setLaravelSession($request->session());
try {
if ($record->action === 'gadai') {
@@ -119,6 +136,9 @@ class ShorakaFinancingService
$result = redirect()->route('shoraka.show', ['norujukan' => $reference]);
}
$record->update(['state' => 'DONE', 'context' => []]);
if (!empty($context['temporary_gadai'])) {
Cache::store('file')->forget('shoraka:payout:' . hash('sha256', $record->kodcaw . ':' . $reference));
}
return $result;
} catch (Throwable $exception) {
// A failed remote payment response can be ambiguous. Never retry automatically.