Skip to content

Currency API Layer Error Handling#3776

Open
P4NK4J wants to merge 6 commits intomasterfrom
fix/api-error-handling
Open

Currency API Layer Error Handling#3776
P4NK4J wants to merge 6 commits intomasterfrom
fix/api-error-handling

Conversation

@P4NK4J
Copy link
Member

@P4NK4J P4NK4J commented Oct 9, 2025

Due to the refactoring of currenlylayer API, the regular invoice workflows in portal are failing. As the part of a temporary fix, I have enabled sufficient try catch blogs, for better logging and handling of errors received in API. Even when the API fails, it won't crash the entire page

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability of currency conversion with robust error handling and safe fallbacks.
    • Prevents failures during network or API issues by returning a default INR rate when needed.
    • Handles missing API key gracefully with non-blocking behavior and clear logging.
  • Chores

    • Updated external currency API endpoint and client timeouts for more stable responses.
    • Standardized response validation and logging for clearer diagnostics.

@coderabbitai
Copy link

coderabbitai bot commented Oct 9, 2025

Walkthrough

Updates CurrencyService to call apilayer API with timeouts, add structured error handling and logging, validate responses, and provide fallback defaults (e.g., 83.00 for USDINR). Both single-rate and all-rates fetch methods were revised to parse quotes, handle missing API keys, and standardize fallback returns.

Changes

Cohort / File(s) Summary of Changes
Currency service API and error handling
Modules/Invoice/Services/CurrencyService.php
- Switched base URI to https://api.apilayer.com with timeouts
- Enhanced missing API key handling: log warning, return default rate(s)
- Added try/catch for ConnectException, RequestException, Throwable with targeted log messages
- fetchExchangeRateInINR: call with currencies=INR, source=USD; validate/parse response; fallback to 83.00
- fetchAllExchangeRateInINR: call with source=USD; validate/parse quotes; fallback to default mapping including USDINR
- Minor internal header setup simplification; adjusted return shape in missing-key branch to include USDINR

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Caller
  participant S as CurrencyService
  participant A as apilayer API
  participant L as Logger

  rect rgb(240,245,255)
  note right of S: fetchExchangeRateInINR (USD→INR)
  C->>S: fetchExchangeRateInINR()
  alt API key missing
    S->>L: warn("Missing API key")
    S-->>C: defaultRate(83.00)
  else API key present
    S->>A: GET /currency_data/live?source=USD&currencies=INR
    alt Network/API error
      S->>L: error(details)
      S-->>C: defaultRate(83.00)
    else Response OK
      S->>S: validate structure (quotes.USDINR)
      alt Valid
        S-->>C: quotes.USDINR
      else Invalid
        S->>L: error("Invalid response structure")
        S-->>C: defaultRate(83.00)
      end
    end
  end
  end

  rect rgb(240,255,240)
  note right of S: fetchAllExchangeRateInINR (USD source)
  C->>S: fetchAllExchangeRateInINR()
  alt API key missing
    S->>L: warn("Missing API key")
    S-->>C: defaultQuotes({USDINR: 83.00, ...})
  else API key present
    S->>A: GET /currency_data/live?source=USD
    alt Error
      S->>L: error(details)
      S-->>C: defaultQuotes({USDINR: 83.00, ...})
    else OK
      S->>S: validate quotes map
      alt Valid
        S-->>C: quotes
      else Invalid
        S->>L: error("Invalid response structure")
        S-->>C: defaultQuotes({USDINR: 83.00, ...})
      end
    end
  end
  end
Loading

Best Practices Flags

  • Magic numbers: Hardcoded default rate 83.00; extract to named constant/config and document source/freshness.
  • Inconsistent return shapes: getAllCurrentRatesInINR branch returning only USDINR vs full quotes may surprise callers; standardize schema across all paths.
  • Hidden failure modes: Defaulting on missing API key or errors can mask issues; consider surfacing status alongside value (Result type) or metrics/alerts.
  • Duplication: Similar request/parse/fallback logic in both fetch methods; refactor into shared private helpers (request(), parseQuotes(), fallback()).
  • Tight coupling: apilayer-specific query params scattered; encapsulate endpoint and parameter building to ease provider swaps.
  • Error handling policy: Mixed throwing vs fallback (throws on invalid structure, but often falls back). Define consistent contract—either never throw and return Result, or throw and let callers decide.
  • Validation depth: Only presence checks for quotes.USDINR; add type and range checks, and ensure numeric parsing.
  • Configuration: Timeouts hardcoded; move to config with sane defaults and environment overrides. Consider retries with backoff and circuit breaking.
  • Logging: Ensure logs don’t leak secrets; unify log levels/messages and include correlation/request IDs if available.
  • Naming clarity: Method names emphasize INR, but one fetches “all” with USD source; clarify naming or docblocks to state behavior and units.
  • Testing: Add unit/integration tests covering success, missing key, network failure, invalid payload, and fallback paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A rate sails in from distant seas,
When winds fail, we steady, please—
83 anchors through the squall,
Logs record the rise and fall.
With cleaner charts and safer foam,
Our currency ship finds its way home. ⛵💱

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately describes the primary change introduced by this pull request, which is enhancing error handling for the currency API layer by adding try/catch blocks and logging logic. It is concise, specific, and clearly conveys the main intent to reviewers.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/api-error-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@P4NK4J P4NK4J changed the title API Layer Error Handling Currency API Layer Error Handling Oct 9, 2025
@P4NK4J P4NK4J requested a review from pokhiii October 9, 2025 11:24
@P4NK4J P4NK4J self-assigned this Oct 9, 2025
@cypress
Copy link

cypress bot commented Oct 9, 2025

Employee portal    Run #9000

Run Properties:  status check passed Passed #9000  •  git commit a1085dbc62 ℹ️: Merge 06e6103f4e954018a3e119681aca54118ddd46d6 into 0fce9c19629d1bfb34131fcf427c...
Project Employee portal
Branch Review refs/pull/3776/merge
Run status status check passed Passed #9000
Run duration 00m 22s
Commit git commit a1085dbc62 ℹ️: Merge 06e6103f4e954018a3e119681aca54118ddd46d6 into 0fce9c19629d1bfb34131fcf427c...
Committer Pankaj Kandpal
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 4
View all changes introduced in this branch ↗︎

@P4NK4J P4NK4J added the status : ready for review Items that are ready for code review label Oct 9, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 9, 2025

Coverage report for commit: 06e6103
File: coverage.xml

Cover ┌─────────────────────────┐ Freq.
   0% │ ███████████████████████ │ 76.2%
  10% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.6%
  20% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.6%
  30% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.3%
  40% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.3%
  50% │ ██░░░░░░░░░░░░░░░░░░░░░ │  4.3%
  60% │ █░░░░░░░░░░░░░░░░░░░░░░ │  1.2%
  70% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.3%
  80% │ █░░░░░░░░░░░░░░░░░░░░░░ │  0.6%
  90% │ █░░░░░░░░░░░░░░░░░░░░░░ │  3.1%
 100% │ ████░░░░░░░░░░░░░░░░░░░ │ 12.3%
      └─────────────────────────┘
 *Legend:* █ = Current Distribution 
Summary - Lines: 7.17% | Methods: 8.98%
FilesLinesMethodsBranches
Modules/HR/Console
   JobExpiredEmailToHr.php--100.00%
   QuarterlyReviewSystemForEmployee.php--100.00%
Modules/HR/Console/Recruitment
   ApplicationNoShow.php--100.00%
   DailyMessage.php--100.00%
   MappingOfJobsAndHrRounds.php--100.00%
   MarkApplicationForFollowUp.php--100.00%
   MoveFilesToWordPress.php--100.00%
   ResetIsLatestApplicationRound.php45.45%66.67%100.00%
   SendFollowUpThresholdMail.php--100.00%
   SendInterviewReminders.php--100.00%
Modules/HR/Contracts
   ApplicationServiceContract.php100.00%100.00%100.00%
   EvaluationServiceContract.php100.00%100.00%100.00%
   UniversityServiceContract.php100.00%100.00%100.00%
Modules/HR/Database/Factories
   HrApplicantsFactory.php100.00%100.00%100.00%
   HrApplicationEvaluationFactory.php--100.00%
   HrApplicationMetaFactory.php--100.00%
   HrApplicationRejectionFactory.php--100.00%
   HrApplicationRoundReviewFactory.php--100.00%
   HrApplicationSegmentFactory.php--100.00%
   HrApplicationsFactory.php--100.00%
   HrEmployeeFactory.php--100.00%
   HrFollowUpFactory.php--100.00%
   HrJobsFactory.php--100.00%
   HrResourcesCategoriesFactory.php--100.00%
   HrResourcesFactory.php--100.00%
   HrUniversitiesContactsFactory.php100.00%100.00%100.00%
   HrUniversitiesFactory.php100.00%100.00%100.00%
   HrUniversityAliasesFactory.php100.00%100.00%100.00%
Modules/HR/Database/Migrations
   2020_07_15_110611_create_hr_application_segments_table.php91.67%50.00%100.00%
   2020_07_15_142215_rename_next_interview_comments_column.php50.00%50.00%100.00%
   2020_07_25_202622_update_segment_table_with_segment_parent.php61.54%50.00%100.00%
   2020_09_04_234639_update_application_round_with_calendar_meeting_id.php66.67%50.00%100.00%
   2020_09_06_234443_update_hr_application_round_table_with_is_latest.php50.00%50.00%100.00%
   2020_09_07_171154_create_hr_follow_ups_table.php92.31%50.00%100.00%
   2020_09_08_073438_create_hr_universities_table.php87.50%50.00%100.00%
   2020_09_08_073538_create_hr_universities_contacts_table.php90.00%50.00%100.00%
   2020_09_08_073651_add_hr_university_id_to_hr_applicants_table.php42.86%50.00%100.00%
   2020_11_24_141131_create_hr_university_aliases_table.php85.71%50.00%100.00%
   2020_12_31_112259_alter_posted_by_in_hr_jobs_table.php50.00%50.00%100.00%
   2021_01_01_113149_add_status_to_hr_jobs_table.php50.00%50.00%100.00%
   2021_01_01_123714_add_soft_deletes_to_hr_jobs_table.php50.00%50.00%100.00%
   2022_03_08_064931_update_hr_jobs_table.php50.00%50.00%100.00%
   2022_06_15_141424_add_parameter_slug_column_in_evaluation_parameter_table.php50.00%50.00%100.00%
   2022_07_04_165601_add_verification_status_to_application.php50.00%50.00%100.00%
   2022_07_07_075310_create_hr_job_domains_table.php85.71%50.00%100.00%
   2022_07_15_115016_create_hr_channels_table.php85.71%50.00%100.00%
   2022_07_15_115134_update_hr_applications_with_hr_channels_table.php50.00%50.00%100.00%
   2022_09_01_230847_create_job_requisition_table.php57.14%50.00%100.00%
   2022_09_06_232653_add_status_to_job_requisition.php50.00%50.00%100.00%
   2022_09_12_140344_create_hr_applicant_meta_table.php76.92%50.00%100.00%
   2022_09_19_160414_create_hr_job_designation_table.php85.71%50.00%100.00%
   2022_09_21_145858_create_batch_members_table.php57.14%50.00%100.00%
   2022_09_21_151304_create_batches_table.php83.33%50.00%100.00%
   2022_09_26_134702_alter_column_for_application_meta_value.php50.00%50.00%100.00%
   2024_03_20_161855_add_commencement_date_column_to_employee_salary_table.php50.00%50.00%100.00%
Modules/HR/Database/Seeders
   AddPreparatoryRoundsSeeder.php--100.00%
   ApplicationEvaluationTableSeeder.php--100.00%
   DomainTableSeeder.php--100.00%
   HRDatabaseSeeder.php--100.00%
   HRJobsSeederTableSeeder.php--100.00%
   HRPermissionsTableSeeder.php--100.00%
   HRRoundsTableSeeder.php--100.00%
   HrApplicantsTableSeeder.php--100.00%
   HrApplicationEvaluationSegmentTableSeeder.php--100.00%
   HrApplicationMetaTableSeeder.php--100.00%
   HrApplicationRejectionReasonTableSeeder.php--100.00%
   HrApplicationRoundReviewTableSeeder.php--100.00%
   HrApplicationRoundTableSeeder.php--100.00%
   HrApplicationSegmentTableSeeder.php--100.00%
   HrApplicationsTableSeeder.php--100.00%
   HrChannelsTableSeeder.php--100.00%
   HrDesignationTableSeeder.php100.00%100.00%100.00%
   HrDomainTableSeeder.php100.00%100.00%100.00%
   HrFollowUpTableSeeder.php--100.00%
   HrResourcesCategoriesTableSeeder.php--100.00%
   HrResourcesTableSeeder.php--100.00%
   HrUniversitiesContactsTableSeeder.php--100.00%
   HrUniversitiesTableSeeder.php--100.00%
   HrUniversityAliasesTableSeeder.php--100.00%
   ResumeScreeningEvaluationSeeder.php--100.00%
   SettingsTableSeeder.php--100.00%
   TagTableSeeder.php--100.00%
Modules/HR/Emails
   AppointmentSlotSelectionMail.php--100.00%
   SendHiringMail.php--100.00%
   SendJobExpiredMail.php--100.00%
   SendPayrollListMail.php--100.00%
   SendThreshholdFollowUp.php--100.00%
Modules/HR/Emails/Recruitment/Applicant
   ApplicantCreateAutoResponder.php--100.00%
   NoShow.php--100.00%
   OnHold.php--100.00%
   RoundReviewed.php--100.00%
   ScheduledInterviewReminder.php--100.00%
Modules/HR/Emails/Recruitment/Application
   ApplicationHandover.php--100.00%
   CustomApplicationMail.php--100.00%
   JobChanged.php--100.00%
   RoundNotConducted.php--100.00%
Modules/HR/Emails/Recruitment
   InterviewerScheduledRoundsReminder.php--100.00%
   SendForApproval.php--100.00%
   SendOfferLetter.php--100.00%
Modules/HR/Entities
   Applicant.php1.69%10.00%100.00%
   ApplicantMeta.php--100.00%
   Application.php--100.00%
   ApplicationEvaluationSegment.php--100.00%
   ApplicationMeta.php--100.00%
   ApplicationRound.php--100.00%
   ApplicationRoundReview.php--100.00%
   Assessment.php--100.00%
   Employee.php1.39%4.35%100.00%
   FollowUp.php--100.00%
   HRJobsRounds.php100.00%100.00%100.00%
   HRRejectionReason.php--100.00%
   HRRequisitionHiredBatch.php--100.00%
   HRRequisitionHiredBatchMembers.php--100.00%
   HrChannel.php100.00%100.00%100.00%
   HrJobDesignation.php--100.00%
   HrJobDomain.php--100.00%
   IndividualAssessment.php--100.00%
   Job.php--100.00%
   JobRequisition.php--100.00%
   Round.php--100.00%
   University.php33.33%33.33%100.00%
   UniversityAlias.php100.00%100.00%100.00%
   UniversityContact.php100.00%100.00%100.00%
Modules/HR/Entities/Evaluation
   ApplicationEvaluation.php--100.00%
   Parameter.php--100.00%
   ParameterOption.php--100.00%
   Segment.php--100.00%
Modules/HR/Events
   ApplicationMovedToNewRound.php--100.00%
   CustomMailTriggeredForApplication.php--100.00%
   FollowUpEvent.php--100.00%
   InterviewCommunicationEmailSent.php--100.00%
Modules/HR/Events/Recruitment
   ApplicantEmailVerified.php--100.00%
   ApplicationCreated.php--100.00%
   JobUpdated.php--100.00%
Modules/HR/Exports
   ContractorFeeExport.php--100.00%
   EmployeePayrollExport.php--100.00%
Modules/HR/Helpers
   TemplateHelper.php--100.00%
Modules/HR/Http/Controllers
   EmployeeController.php--100.00%
   EvaluationController.php--100.00%
   HiringController.php--100.00%
   HrChannelController.php--100.00%
   HrJobDesignationController.php--100.00%
   RequisitionController.php--100.00%
   ResourcesController.php--100.00%
   TagsController.php--100.00%
Modules/HR/Http/Controllers/Recruitment
   ApplicantController.php--100.00%
   ApplicationController.php--100.00%
   ApplicationRoundController.php--100.00%
   CampaignsController.php--100.00%
   InternshipApplicationController.php--100.00%
   JobApplicationController.php--100.00%
   JobController.php--100.00%
Table truncated to fit comment

🤖 comment via lucassabreu/comment-coverage-clover

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Modules/Invoice/Services/CurrencyService.php (1)

1-123: Fix code formatting issues flagged by PHP CS Fixer.

The CI pipeline detected multiple formatting violations that need to be addressed. Run the following command to automatically fix them:

php-cs-fixer fix Modules/Invoice/Services/CurrencyService.php

Or review and apply fixes manually for:

  • ordered_class_elements
  • trailing_comma_in_multiline
  • not_operator_with_successor_space
  • blank_line_before_statement
  • ordered_imports
  • binary_operator_spaces

Based on pipeline failures.

🧹 Nitpick comments (1)
Modules/Invoice/Services/CurrencyService.php (1)

55-84: Extract duplicate default rate retrieval and response validation.

The error handling approach is solid, but there are opportunities to reduce duplication:

  1. The default rate retrieval round(config('services.currencylayer.default_rate', 83.00), 2) appears multiple times across both fetch methods.
  2. Response structure validation could be extracted into a helper method.
  3. Generic \Exception at Line 71 is immediately caught by the \Throwable handler—consider using a more specific exception type or removing the intermediate throw.

Consider extracting common logic:

private function getDefaultRate(): float
{
    return round(config('services.currencylayer.default_rate', 83.00), 2);
}

private function validateResponse(array $data, array $requiredKeys): void
{
    foreach ($requiredKeys as $key) {
        if (empty($data[$key])) {
            throw new \RuntimeException("Missing required key: {$key}");
        }
    }
}

Then simplify the fetch methods:

-if (!config('services.currencylayer.access_key')) {
-    Log::warning('CurrencyLayer API key missing. Using default rate.');
-    return round(config('services.currencylayer.default_rate', 83.00), 2);
+if (!config('services.currencylayer.access_key')) {
+    Log::warning('CurrencyLayer API key missing. Using default rate.');
+    return $this->getDefaultRate();
 }

 try {
     // ... API call ...
     $data = json_decode($response->getBody()->getContents(), true);
     
-    if (empty($data) || empty($data['quotes']['USDINR'])) {
-        throw new \Exception('Invalid API response structure.');
-    }
+    $this->validateResponse($data, ['quotes']);
+    if (empty($data['quotes']['USDINR'])) {
+        throw new \RuntimeException('Invalid API response: USDINR rate missing.');
+    }
     
     return round($data['quotes']['USDINR'], 2);
 } catch (ConnectException $e) {
     // ... logging ...
 } catch (RequestException $e) {
     // ... logging ...
 } catch (\Throwable $e) {
     Log::error('Unexpected error fetching exchange rate: ' . $e->getMessage());
 }
 
-return round(config('services.currencylayer.default_rate', 83.00), 2);
+return $this->getDefaultRate();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0fce9c1 and 9289c26.

📒 Files selected for processing (1)
  • Modules/Invoice/Services/CurrencyService.php (4 hunks)
🧰 Additional context used
🪛 GitHub Actions: Coding Standards
Modules/Invoice/Services/CurrencyService.php

[error] 1-1: PHP CS Fixer failed on dry-run. Detectable issues in CurrencyService.php: ordered_class_elements, trailing_comma_in_multiline, not_operator_with_successor_space, blank_line_before_statement, ordered_imports, binary_operator_spaces. Apply fixes (e.g., run 'php-cs-fixer fix' or follow the diff) to resolve.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: portal
  • GitHub Check: portal
🔇 Additional comments (3)
Modules/Invoice/Services/CurrencyService.php (3)

6-9: LGTM!

The new imports are necessary for the enhanced error handling and logging functionality.


23-32: Good addition of timeout configurations.

The connection and request timeouts (5s and 10s respectively) are sensible defaults that prevent indefinite blocking on external API calls.


21-21: Visibility change is non-breaking — no external calls to CurrencyService::setClient; CalendarMeetingService defines its own setClient.

Comment on lines 89 to 122
if (!config('services.currencylayer.access_key')) {
Log::warning('CurrencyLayer API key missing. Using default rate.');
return [
'USDINR' => round(config('services.currencylayer.default_rate', 83.00), 2)
];
}

$response = $this->client->get('currency_data/live', [
'query' => [
'access_key' => config('services.currencylayer.access_key'),
],
]);

$data = json_decode($response->getBody()->getContents(), true);
try {
$response = $this->client->get('currency_data/live', [
'query' => [
'source' => 'USD',
],
]);

$data = json_decode($response->getBody()->getContents(), true);

if (empty($data) || empty($data['quotes'])) {
throw new \Exception('Invalid API response structure.');
}

return $data['quotes'];
} catch (ConnectException $e) {
Log::error('Currency API connection failed: ' . $e->getMessage());
} catch (RequestException $e) {
Log::error('Currency API request error: ' . $e->getMessage());
} catch (\Throwable $e) {
Log::error('Unexpected error fetching all exchange rates: ' . $e->getMessage());
}

return $data['quotes'];
// fallback if API fails
return [
'USDINR' => round(config('services.currencylayer.default_rate', 83.00), 2)
];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Eliminate duplicate error handling across both fetch methods.

The fetchAllExchangeRateInINR method duplicates the entire error-handling pattern from fetchExchangeRateInINR. This violates the DRY principle and makes maintenance harder.

Extract the common API call logic into a reusable method:

private function makeApiCall(string $endpoint, array $queryParams, callable $processor)
{
    if (!config('services.currencylayer.access_key')) {
        Log::warning('CurrencyLayer API key missing. Using default rate.');
        return $this->getDefaultRate();
    }

    try {
        $response = $this->client->get($endpoint, ['query' => $queryParams]);
        $data = json_decode($response->getBody()->getContents(), true);
        
        if (empty($data)) {
            throw new \RuntimeException('Empty API response.');
        }
        
        return $processor($data);
    } catch (ConnectException $e) {
        Log::error('Currency API connection failed: ' . $e->getMessage());
    } catch (RequestException $e) {
        Log::error('Currency API request error: ' . $e->getMessage());
    } catch (\Throwable $e) {
        Log::error('Unexpected error in API call: ' . $e->getMessage());
    }

    return null; // or throw exception for caller to handle fallback
}

Then refactor both methods to use it:

private function fetchExchangeRateInINR()
{
    $result = $this->makeApiCall(
        'currency_data/live',
        ['currencies' => 'INR', 'source' => 'USD'],
        function ($data) {
            if (empty($data['quotes']['USDINR'])) {
                throw new \RuntimeException('USDINR rate missing from response.');
            }
            return round($data['quotes']['USDINR'], 2);
        }
    );
    
    return $result ?? $this->getDefaultRate();
}

private function fetchAllExchangeRateInINR()
{
    $result = $this->makeApiCall(
        'currency_data/live',
        ['source' => 'USD'],
        function ($data) {
            if (empty($data['quotes'])) {
                throw new \RuntimeException('Quotes missing from response.');
            }
            return $data['quotes'];
        }
    );
    
    return $result ?? ['USDINR' => $this->getDefaultRate()];
}
🤖 Prompt for AI Agents
In Modules/Invoice/Services/CurrencyService.php around lines 89 to 122, the
error-handling and API-call logic in fetchAllExchangeRateInINR duplicates
fetchExchangeRateInINR; extract common behavior into a private helper (e.g.,
makeApiCall) that handles api-key check, performs the Guzzle GET with given
endpoint and query, decodes payload, validates basic structure, logs
ConnectException/RequestException/Throwable uniformly, and returns decoded data
or null on failure; add a getDefaultRate helper that returns the rounded default
USDINR from config; then refactor both fetchExchangeRateInINR and
fetchAllExchangeRateInINR to call makeApiCall with a small processor callback
that extracts/validates the specific fields (USDINR or quotes) and return
processor result or fallback to getDefaultRate (or array with USDINR) when null.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
Modules/Invoice/Services/CurrencyService.php (2)

61-84: DRY violation: error handling duplicated across methods.

The error handling pattern in this method is nearly identical to fetchAllExchangeRateInINR (lines 97-121). This duplication was already flagged in a previous review, and the suggestion to extract common API call logic into a reusable helper method remains valid.

Please refer to the previous review comment suggesting extraction of common API call logic into a makeApiCall helper method. This would eliminate the duplication and make the code more maintainable.


89-95: Same issues as fetchExchangeRateInINR.

This segment has the same default rate duplication issue flagged in lines 55-59. After extracting getDefaultRate(), update this section to use it as well.

🧹 Nitpick comments (1)
Modules/Invoice/Services/CurrencyService.php (1)

72-72: Consider using a custom exception type.

Throwing generic \Exception makes it harder to distinguish between different failure scenarios. Consider creating a custom exception class for currency service errors.

Create a custom exception class:

namespace Modules\Invoice\Exceptions;

class CurrencyServiceException extends \Exception
{
}

Then use it instead of generic \Exception:

-                throw new \Exception('Invalid API response structure.');
+                throw new CurrencyServiceException('Invalid API response structure.');
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9289c26 and 06e6103.

📒 Files selected for processing (1)
  • Modules/Invoice/Services/CurrencyService.php (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: portal
  • GitHub Check: portal
🔇 Additional comments (3)
Modules/Invoice/Services/CurrencyService.php (3)

6-9: LGTM!

The imports are appropriate for the error handling implementation and are used correctly throughout the file.


23-32: Good addition of timeouts to prevent hanging requests.

The timeout configuration (10s request, 5s connection) prevents the application from blocking indefinitely when the API is slow or unresponsive. These values are reasonable for external API calls.


106-108: Better validation approach than fetchExchangeRateInINR.

This validation correctly checks $data['quotes'] without accessing nested keys directly, avoiding the PHP notice issue present on Line 71. However, consider using a custom exception type here as well (see comment on Line 72).

Comment on lines 55 to +59
if (! config('services.currencylayer.access_key')) {
return round(config('services.currencylayer.default_rate'), 2);
}
Log::warning('CurrencyLayer API key missing. Using default rate.');

$response = $this->client->get('currency_data/live', [
'query' => [
'access_key' => config('services.currencylayer.access_key'),
'currencies' => 'INR',
],
]);
return round(config('services.currencylayer.default_rate', 83.00), 2);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Extract default rate to a helper method.

The default rate calculation round(config('services.currencylayer.default_rate', 83.00), 2) is duplicated across lines 58, 84, 93, and 120. This violates the DRY principle.

Extract this to a private helper method:

+    private function getDefaultRate()
+    {
+        return round(config('services.currencylayer.default_rate', 83.00), 2);
+    }
+
     private function fetchExchangeRateInINR()
     {
         if (! config('services.currencylayer.access_key')) {
             Log::warning('CurrencyLayer API key missing. Using default rate.');
 
-            return round(config('services.currencylayer.default_rate', 83.00), 2);
+            return $this->getDefaultRate();
         }

Then replace all other occurrences (lines 84, 93, 120) with $this->getDefaultRate().

🤖 Prompt for AI Agents
In Modules/Invoice/Services/CurrencyService.php around lines 55-59 (and also
replace duplicates at lines 84, 93, and 120), extract the duplicated default
rate expression round(config('services.currencylayer.default_rate', 83.00), 2)
into a private helper method named getDefaultRate() that returns that rounded
value, then replace each occurrence with $this->getDefaultRate(); ensure the
helper is private, placed in the class, and all four sites use that method so
the logic is centralized.

Comment on lines +71 to +73
if (empty($data) || empty($data['quotes']['USDINR'])) {
throw new \Exception('Invalid API response structure.');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix potential PHP notice in validation logic.

The expression empty($data['quotes']['USDINR']) on Line 71 will trigger a PHP notice if $data['quotes'] doesn't exist. The validation should check the structure step-by-step.

Apply this diff to fix the validation:

-            if (empty($data) || empty($data['quotes']['USDINR'])) {
+            if (empty($data) || !isset($data['quotes']['USDINR']) || empty($data['quotes']['USDINR'])) {
                 throw new \Exception('Invalid API response structure.');
             }

Alternatively, validate in stages:

-            if (empty($data) || empty($data['quotes']['USDINR'])) {
+            if (empty($data)) {
+                throw new \Exception('Empty API response.');
+            }
+            
+            if (!isset($data['quotes']['USDINR']) || empty($data['quotes']['USDINR'])) {
                 throw new \Exception('Invalid API response structure.');
             }
🤖 Prompt for AI Agents
In Modules/Invoice/Services/CurrencyService.php around lines 71 to 73, the
current validation uses empty($data['quotes']['USDINR']) which can raise a PHP
notice if $data['quotes'] is undefined; update the check to validate the
structure in stages — first ensure $data is not empty and is an array, then
ensure $data['quotes'] exists and is an array (or use isset) before checking
$data['quotes']['USDINR'], and throw the same exception if any of those checks
fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status : ready for review Items that are ready for code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant