Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions Civi/Paymentprocessingcore/DTO/RecurringContributionDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ class RecurringContributionDTO {
*/
private int $frequencyInterval;

/**
* @var int|null
*/
private ?int $paymentInstrumentId;

/**
* Private constructor — use fromApiResult() factory method.
*
Expand All @@ -76,6 +81,8 @@ class RecurringContributionDTO {
* Frequency unit (day, week, month, year).
* @param int $frequencyInterval
* Frequency interval.
* @param int|null $paymentInstrumentId
* Payment instrument ID or NULL.
*/
private function __construct(
int $id,
Expand All @@ -86,7 +93,8 @@ private function __construct(
?int $campaignId,
string $nextSchedContributionDate,
string $frequencyUnit,
int $frequencyInterval
int $frequencyInterval,
?int $paymentInstrumentId
) {
$this->id = $id;
$this->contactId = $contactId;
Expand All @@ -97,6 +105,7 @@ private function __construct(
$this->nextSchedContributionDate = $nextSchedContributionDate;
$this->frequencyUnit = $frequencyUnit;
$this->frequencyInterval = $frequencyInterval;
$this->paymentInstrumentId = $paymentInstrumentId;
}

/**
Expand Down Expand Up @@ -148,6 +157,10 @@ public static function fromApiResult(array $record): self {
? (int) $record['campaign_id']
: NULL;

$paymentInstrumentId = isset($record['payment_instrument_id']) && is_numeric($record['payment_instrument_id'])
? (int) $record['payment_instrument_id']
: NULL;
Comment on lines +160 to +162

Choose a reason for hiding this comment

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

medium

The use of isset() here could allow a payment_instrument_id of 0 to be considered valid. In CiviCRM, entity IDs are typically positive integers, so 0 is not a valid ID. This could lead to incorrect data being propagated.

To ensure only valid, positive IDs are processed, it would be safer to use !empty() which correctly handles 0, null, and empty strings as invalid values. This aligns with the principle of failing fast if critical data is missing, rather than masking potential issues with invalid default values.

    $paymentInstrumentId = !empty($record['payment_instrument_id']) && is_numeric($record['payment_instrument_id'])
      ? (int) $record['payment_instrument_id']
      : NULL;
References
  1. Avoid overly defensive variable extraction that can mask underlying data issues. It's safer to fail fast if critical data is missing, especially when default values can lead to unexpected behavior (e.g., strtotime('') evaluating to 1970-01-01).


return new self(
(int) $id,
(int) $contactId,
Expand All @@ -157,7 +170,8 @@ public static function fromApiResult(array $record): self {
$campaignId,
(string) $nextSchedDate,
(string) $frequencyUnit,
(int) $frequencyInterval
(int) $frequencyInterval,
$paymentInstrumentId
);
}

Expand Down Expand Up @@ -224,4 +238,11 @@ public function getFrequencyInterval(): int {
return $this->frequencyInterval;
}

/**
* Get the payment instrument ID, or NULL if not set.
*/
public function getPaymentInstrumentId(): ?int {
return $this->paymentInstrumentId;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ public function getDueRecurringContributions(string $processorType, int $batchSi
'amount',
'currency',
'financial_type_id',
'campaign_id'
'campaign_id',
'payment_instrument_id'

Choose a reason for hiding this comment

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

high

While adding payment_instrument_id to the query is correct, this new functionality is not covered by unit tests. The repository style guide requires new features to have corresponding tests.

Please add tests to InstalmentGenerationServiceTest.php to verify that when a recurring contribution has a payment_instrument_id, the generated instalment also has it. Additionally, tests in RecurringContributionDTOTest.php should be added to ensure payment_instrument_id is correctly handled by the DTO.

References
  1. New features and bug fixes include unit tests. (link)

)
->addJoin(
'PaymentProcessor AS pp',
Expand Down Expand Up @@ -258,6 +259,10 @@ public function createInstalment(RecurringContributionDTO $recur, string $receiv
$createAction->addValue('campaign_id', $recur->getCampaignId());
}

if ($recur->getPaymentInstrumentId() !== NULL) {
$createAction->addValue('payment_instrument_id', $recur->getPaymentInstrumentId());
}

$result = $createAction->execute()->first();

if (!is_array($result)) {
Expand Down