validate($request, [ 'norujukan' => 'required|string', 'ownership' => 'required|string', 'order_amount' => 'required|numeric|min:0.01', 'murabaha_amount' => 'required|numeric|min:0.01', 'tenor_other' => 'required|integer|min:1', ]); /* * Jangan hubungi Shoraka selagi credentials * belum diterima atau belum dimasukkan. */ if ( empty(config('services.shoraka.api_id')) || empty(config('services.shoraka.secret_key')) ) { return response()->json([ 'success' => false, 'message' => 'API ID dan Secret Key Shoraka belum dikonfigurasi.', ], 503); } /* * Semak sama ada nombor rujukan yang sama * sudah pernah berjaya dihantar. */ $existing = ShorakaTransaction::where( 'norujukan', $validated['norujukan'] )->first(); if ( $existing && !empty($existing->order_id) ) { return response()->json([ 'success' => false, 'message' => 'Transaksi ini telah dihantar ke Shoraka sebelum ini.', 'data' => [ 'norujukan' => $existing->norujukan, 'order_id' => $existing->order_id, 'status' => $existing->status, ], ], 409); } /* * Sediakan data mengikut format Shoraka. */ $payload = [ 'product_type' => 'FINANCING', /* * Kod komoditi perlu disahkan * dengan pihak Shoraka. */ 'commodity_type' => '000-COPPER', /* * Nama pelanggan. */ 'ownership' => $validated['ownership'], /* * Nombor rujukan pembiayaan eRahn. */ 'ownership_doc_no' => $validated['norujukan'], /* * Format yang diminta Shoraka: * DD/MM/YYYY */ 'value_date' => Carbon::now()->format('d/m/Y'), 'order_currency' => 'MYR', 'order_amount' => $validated['order_amount'], 'murabaha_amount' => $validated['murabaha_amount'], /* * Sila sahkan dengan Shoraka sama ada * nilai sebenar ialah OTHER atau OTHERS. */ 'tenor' => 'OTHERS', 'tenor_other' => $validated['tenor_other'], 'tenor_other_unit' => 'MONTH', 'order_type' => 'Buy & Sell', ]; /* * Simpan rekod awal sebelum request dihantar. */ $transaction = ShorakaTransaction::updateOrCreate( [ 'norujukan' => $validated['norujukan'], ], [ 'status' => 'PENDING', 'product_type' => $payload['product_type'], 'commodity_type' => $payload['commodity_type'], 'ownership_name' => $payload['ownership'], 'ownership_doc_no' => $payload['ownership_doc_no'], 'order_amount' => $payload['order_amount'], 'murabaha_amount' => $payload['murabaha_amount'], 'request_payload' => $payload, 'error_message' => null, ] ); try { /* * Tambah bilangan percubaan request. */ $transaction->increment( 'attempt_count' ); /* * Hantar order kepada API Shoraka. */ $response = $shoraka->submitOrder( $payload ); /* * Shoraka mesti memberikan orderId. */ if (empty($response['orderId'])) { throw new RuntimeException( 'Shoraka tidak memberikan orderId.' ); } $status = isset($response['status']) ? $response['status'] : 'SUBMITTED'; /* * Simpan respons Shoraka. */ $transaction->update([ 'order_id' => $response['orderId'], 'status' => $status, 'certificate_url' => isset($response['certificateUrl']) ? $response['certificateUrl'] : null, 'response_payload' => $response, 'submitted_at' => Carbon::now(), 'completed_at' => $status === 'Completed' ? Carbon::now() : null, 'error_message' => null, ]); return response()->json([ 'success' => true, 'message' => 'Order berjaya dihantar ke Shoraka.', 'data' => [ 'norujukan' => $transaction->norujukan, 'order_id' => $transaction->order_id, 'status' => $transaction->status, 'certificate_url' => $transaction->certificate_url, ], ], 200); } catch (Throwable $exception) { /* * Simpan status gagal. */ $transaction->update([ 'status' => 'FAILED', 'error_message' => $exception->getMessage(), ]); /* * Rekodkan ralat tanpa API Secret Key. */ Log::error( 'Shoraka order submission failed', [ 'norujukan' => $validated['norujukan'], 'message' => $exception->getMessage(), ] ); return response()->json([ 'success' => false, 'message' => 'Order gagal dihantar ke Shoraka.', /* * Paparkan error untuk UAT sahaja. * Buang field ini semasa production. */ 'error' => $exception->getMessage(), ], 502); } } }