Skip to content

CIVIPLMMSR-655: Include custom-query handler counts in reconcile summary#28

Open
erawat wants to merge 1 commit into
masterfrom
CIVIPLMMSR-655-reconcile-summary-reporting
Open

CIVIPLMMSR-655: Include custom-query handler counts in reconcile summary#28
erawat wants to merge 1 commit into
masterfrom
CIVIPLMMSR-655-reconcile-summary-reporting

Conversation

@erawat

@erawat erawat commented Jul 2, 2026

Copy link
Copy Markdown
Member

Overview

The "Reconcile stuck payment attempts" scheduled job reconciles GoCardless payments correctly, but the count shown on the Scheduled Jobs screen always reads "0 reconciled", making it look as though the job is doing nothing. This makes the real number appear in the summary.

Before

The reconcile run summary (reconciled/unchanged/errored/unhandled, returned by PaymentAttemptReconcile.Run and shown on the Scheduled Jobs screen) is built only from PaymentAttempt records reported via setAttemptResult(). GoCardless reconciles its own data and creates no PaymentAttempt records, so it had no way to report totals — the summary always showed reconciled=0 for it.

Observed on LCC (CIVIPLMMSR-655): all 30 recorded runs report reconciled=0, while the CiviCRM log for the same days shows ~99/day genuinely reconciled.

After

Custom-query handlers report their totals through a new channel on the event, and the service folds them into the run summary, so the screen shows the real number.

N/A for screenshots — backend/telemetry only.

Technical Details

  • ReconcilePaymentAttemptBatchEvent: add reportCounts() / getReportedCounts() — a processor-agnostic channel for handlers that don't use PaymentAttempt records.
  • PaymentAttemptReconcileService::reconcileByProcessor(): fold the reported totals into the per-processor summary before returning.

No regression for Stripe (or any setAttemptResult() handler). The two paths are disjoint and additive:

  • Stripe uses setAttemptResult() and never calls reportCounts()getReportedCounts() returns all-zeros → the fold adds nothing → the Stripe summary is unchanged.
  • GoCardless has zero PaymentAttempt records → the existing foreach ($attempts …) loop contributes nothing → its counts come only from the new channel.

Tests:

  • testSetAttemptResultPathUnaffectedWhenNoCountsReported — Stripe-style run produces the identical summary and leaves the reported channel empty.
  • testReportedCountsFromCustomQueryHandlerAppearInSummary — custom-query run (0 attempts) surfaces its reported counts.
  • testSetAttemptResultAndReportedCountsCombineWithoutDoubleCounting — mixed run sums correctly, no double-count.
  • Event-level unit tests for the new methods (defaults, accumulation, optional buckets).

Core overrides

None — no CiviCRM core files are overridden or patched.

Comments

Requires the companion GoCardless PR (compucorp/io.compuco.gocardless, branch CIVIPLMMSR-655-reconcile-summary-reporting) for the handler to call reportCounts(). Safe to merge in either order — the handler guards the call with method_exists(), so neither PR breaks without the other.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request improves the accuracy of the reconciliation summary on the Scheduled Jobs screen. Previously, processors that perform internal reconciliation without creating PaymentAttempt records (such as GoCardless) could not report their results, leading to misleading '0 reconciled' counts. This change introduces a new event-based reporting channel that allows these handlers to contribute their totals to the final run summary, ensuring transparency in telemetry and reporting.

Highlights

  • New Reporting Channel: Added reportCounts() and getReportedCounts() to ReconcilePaymentAttemptBatchEvent to allow custom-query handlers to report reconciliation totals directly.
  • Summary Integration: Updated PaymentAttemptReconcileService to fold reported counts into the final reconciliation summary, ensuring accurate reporting for processors like GoCardless.
  • Regression Testing: Added comprehensive unit tests to verify that existing Stripe-style reconciliation remains unaffected and that mixed-processor runs calculate totals correctly without double-counting.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for custom-query payment processors (such as GoCardless) that reconcile their own data and report totals directly instead of using individual PaymentAttempt records. It adds methods to ReconcilePaymentAttemptBatchEvent to accumulate and retrieve these counts, integrates them into the run summary within PaymentAttemptReconcileService, and provides thorough test coverage. The review feedback recommends adding validation to prevent negative counts and to disallow mixing the PaymentAttempt-based and custom-query patterns on a single event instance, as well as adding corresponding unit tests for these validation checks.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@erawat erawat force-pushed the CIVIPLMMSR-655-reconcile-summary-reporting branch 2 times, most recently from 478d302 to 9ca8236 Compare July 2, 2026 17:43
The reconcile run summary (and the API result shown on the Scheduled Jobs
screen) was built solely from PaymentAttempt records reported via
setAttemptResult(). Processors that reconcile their own data without
PaymentAttempt records — GoCardless — had no way to report their totals, so
the summary always showed 0 for them even when work was done.

Add reportCounts()/getReportedCounts() to ReconcilePaymentAttemptBatchEvent
and fold the reported totals into the per-processor summary. The setAttemptResult()
path (Stripe) never calls reportCounts(), so its summary is unchanged.
@erawat

erawat commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

Thanks — two suggestions raised: negative-count validation and a guard to disallow mixing the PaymentAttempt (setAttemptResult()) and custom-query (reportCounts()) paths on a single event instance.

We've taken the negative-count validationreportCounts() now throws InvalidArgumentException on any negative bucket, with a test.

We've deliberately not added the mixing guard. A hard throw would forbid a legitimate future case: a processor that reconciles most items through PaymentAttempt records and reports a few extras it discovered by its own query. The two paths are additive by design, and the service folds them together without double-counting (see testSetAttemptResultAndReportedCountsCombineWithoutDoubleCounting). The docblock on reportCounts() already warns that a handler should pick one path per processor; that's the right level of protection here — a runtime exception would be over-constraining for a purely internal, single-consumer API.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants