diff --git a/app/Http/Requests/BalanceSummaryRequest.php b/app/Http/Requests/BalanceSummaryRequest.php index 6835df2..0e7a9f2 100644 --- a/app/Http/Requests/BalanceSummaryRequest.php +++ b/app/Http/Requests/BalanceSummaryRequest.php @@ -102,8 +102,10 @@ class BalanceSummaryRequest extends FormRequest 'account_number' => [ 'required', 'string', - 'max:50', - 'regex:/^[A-Z0-9-]+$/i' // Hanya alphanumeric dan dash + 'max:10', + 'min:10', + 'exists:account_balances,account_number', + 'regex:/^[0-9]+$/' // Numeric only ], 'start_date' => [ 'required', @@ -130,16 +132,19 @@ class BalanceSummaryRequest extends FormRequest public function messages(): array { return [ + 'account_number.exists' => 'Nomor rekening tidak ditemukan.', 'account_number.required' => 'Nomor rekening wajib diisi.', 'account_number.string' => 'Nomor rekening harus berupa teks.', 'account_number.max' => 'Nomor rekening maksimal :max karakter.', - 'account_number.regex' => 'Nomor rekening hanya boleh mengandung huruf, angka, dan strip.', + 'account_number.min' => 'Nomor rekening minimal :min karakter.', + 'account_number.regex' => 'Nomor rekening hanya boleh mengandung angka.', 'start_date.required' => 'Tanggal awal wajib diisi.', 'start_date.date_format' => 'Format tanggal awal harus YYYY-MM-DD.', 'start_date.before_or_equal' => 'Tanggal awal harus sebelum atau sama dengan tanggal akhir.', 'end_date.required' => 'Tanggal akhir wajib diisi.', 'end_date.date_format' => 'Format tanggal akhir harus YYYY-MM-DD.', 'end_date.after_or_equal' => 'Tanggal akhir harus sesudah atau sama dengan tanggal awal.', + 'end_date.before_or_equal' => 'Tanggal akhir harus sebelum atau sama dengan hari ini.', ]; } @@ -155,6 +160,18 @@ class BalanceSummaryRequest extends FormRequest { $errors = $validator->errors(); + if($errors->has('account_number') && $errors->first('account_number') === 'Nomor rekening tidak ditemukan.') { + throw new HttpResponseException( + response()->json( + ResponseCode::ACCOUNT_NOT_FOUND->toResponse( + null, + 'Nomor rekening tidak ditemukan' + ), + ResponseCode::ACCOUNT_NOT_FOUND->getHttpStatus() + ) + ); + } + throw new HttpResponseException( response()->json( ResponseCode::INVALID_FIELD->toResponse( @@ -176,8 +193,89 @@ class BalanceSummaryRequest extends FormRequest protected function failedAuthorization() { $xApiKey = $this->header('X-Api-Key'); + $xSignature = $this->header('X-Signature'); + $xTimestamp = $this->header('X-Timestamp'); + $expectedApiKey = config('webstatement.api_key'); + if(!$xApiKey){ + throw new HttpResponseException( + response()->json( + ResponseCode::INVALID_FIELD->toResponse( + ['errors' => ['X-Api-Key' => 'API Key wajib diisi']], + 'Validasi gagal' + ), + ResponseCode::INVALID_FIELD->getHttpStatus() + ) + ); + } + + if(!$xSignature){ + throw new HttpResponseException( + response()->json( + ResponseCode::INVALID_FIELD->toResponse( + ['errors' => ['X-Signature' => 'Signature wajib diisi']], + 'Validasi gagal' + ), + ResponseCode::INVALID_FIELD->getHttpStatus() + ) + ); + } + + if(!$xTimestamp){ + throw new HttpResponseException( + response()->json( + ResponseCode::INVALID_FIELD->toResponse( + ['errors' => ['X-Timestamp' => 'Timestamp wajib diisi']], + 'Validasi gagal' + ), + ResponseCode::INVALID_FIELD->getHttpStatus() + ) + ); + } + + // Validasi format timestamp ISO 8601 + if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/', $xTimestamp)) { + throw new HttpResponseException( + response()->json( + ResponseCode::INVALID_FIELD->toResponse( + ['errors' => ['X-Timestamp' => 'Format timestamp tidak valid. Gunakan format ISO 8601 (YYYY-MM-DDTHH:MM:SS.sssZ)']], + 'Validasi gagal' + ), + ResponseCode::INVALID_FIELD->getHttpStatus() + ) + ); + } + + // Validasi timestamp tidak lebih dari 5 menit dari waktu sekarang + try { + $timestamp = new \DateTime($xTimestamp); + $now = new \DateTime(); + $diff = $now->getTimestamp() - $timestamp->getTimestamp(); + + if (abs($diff) > 300) { // 5 menit = 300 detik + throw new HttpResponseException( + response()->json( + ResponseCode::INVALID_FIELD->toResponse( + ['errors' => ['X-Timestamp' => 'Timestamp expired. Maksimal selisih 5 menit dari waktu sekarang']], + 'Validasi gagal' + ), + ResponseCode::INVALID_FIELD->getHttpStatus() + ) + ); + } + } catch (\Exception $e) { + throw new HttpResponseException( + response()->json( + ResponseCode::INVALID_FIELD->toResponse( + ['errors' => ['X-Timestamp' => 'Timestamp tidak dapat diproses']], + 'Validasi gagal' + ), + ResponseCode::INVALID_FIELD->getHttpStatus() + ) + ); + } + // Cek apakah ini karena invalid API key if ($xApiKey !== $expectedApiKey) { throw new HttpResponseException( @@ -215,19 +313,5 @@ class BalanceSummaryRequest extends FormRequest 'ip' => $this->ip(), 'user_agent' => $this->userAgent() ]); - - $acount = AccountBalance::where('account_number', $this->account_number)->first(); - - if (!$acount) { - throw new HttpResponseException( - response()->json( - ResponseCode::ACCOUNT_NOT_FOUND->toResponse( - null, - 'Nomor rekening tidak ditemukan' - ), - ResponseCode::ACCOUNT_NOT_FOUND->getHttpStatus() - ) - ); - } } } diff --git a/config/config.php b/config/config.php index 40f5a20..d99ae5e 100644 --- a/config/config.php +++ b/config/config.php @@ -5,4 +5,17 @@ return [ // ZIP file password configuration 'zip_password' => env('WEBSTATEMENT_ZIP_PASSWORD', 'statement123'), + /* + |-------------------------------------------------------------------------- + | API Configuration + |-------------------------------------------------------------------------- + | + | These configuration values are used for API authentication using HMAC + | signature validation. These keys are used to validate incoming API + | requests and ensure secure communication. + | + */ + + 'api_key' => env('API_KEY'), + 'secret_key' => env('SECRET_KEY'), ];