Skip to content

Commit

Permalink
dev/core#2206 Move cancel pledge into Contribution.create and fix
Browse files Browse the repository at this point in the history
Per https://lab.civicrm.org/dev/core/-/issues/2206 I think updating pledge payments is a data issue
rather than a business process issue. Notably you can't record a new contribution against the pledge
unless the existing one is removed

This is the last thing that needs to be done before we can unhide contributioncancelactions
  • Loading branch information
eileenmcnaughton committed Dec 30, 2020
1 parent 43fd2d5 commit 178c5df
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 49 deletions.
43 changes: 43 additions & 0 deletions CRM/Contribute/BAO/Contribution.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Civi\Api4\ContributionPage;
use Civi\Api4\ContributionRecur;
use Civi\Api4\PaymentProcessor;
use Civi\Api4\PledgePayment;

/**
*
Expand Down Expand Up @@ -501,6 +502,10 @@ public static function create(&$params) {

CRM_Contribute_BAO_ContributionSoft::processSoftContribution($params, $contribution);

if (!empty($params['id']) && !empty($params['contribution_status_id'])
) {
self::disconnectPledgePaymentsIfCancelled((int) $params['id'], CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $params['contribution_status_id']));
}
$transaction->commit();

if (empty($contribution->contact_id)) {
Expand Down Expand Up @@ -1281,6 +1286,44 @@ protected static function isEmailReceipt(array $input, $contributionPageID, $rec
return TRUE;
}

/**
* Disconnect pledge payments from cancelled or failed contributions.
*
* If the contribution has been cancelled or has failed check to
* see if it is linked to a pledge and unlink it.
*
* @param int $pledgePaymentID
* @param string $contributionStatus
*
* @throws \API_Exception
* @throws \Civi\API\Exception\UnauthorizedException
*/
protected static function disconnectPledgePaymentsIfCancelled(int $pledgePaymentID, $contributionStatus): void {
if (!in_array($contributionStatus, ['Failed', 'Cancelled'], TRUE)) {
return;
}
// Check first since just doing an update could be locking under load.
$pledgePayment = PledgePayment::get(FALSE)
->addWhere('contribution_id', '=', $pledgePaymentID)
->setSelect(['id', 'pledge_id', 'scheduled_date', 'scheduled_amount'])
->execute()
->first();
if (!empty($pledgePayment)) {
PledgePayment::update(FALSE)->setValues([
'contribution_id' => NULL,
'actual_amount' => NULL,
'status_id:name' => 'Pending',
// We need to set these fields for now because the PledgePayment::create
// function doesn't handled updates well at the moment. Test cover
// in testCancelOrderWithPledge.
'scheduled_date' => $pledgePayment['scheduled_date'],
'installment_amount' => $pledgePayment['scheduled_amount'],
'installments' => 1,
'pledge_id' => $pledgePayment['pledge_id'],
])->addWhere('id', '=', $pledgePayment['id'])->execute();
}
}

/**
* @inheritDoc
*/
Expand Down
2 changes: 1 addition & 1 deletion CRM/Pledge/BAO/PledgePayment.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public static function create($params) {

for ($i = 1; $i <= $params['installments']; $i++) {
// calculate the scheduled amount for every installment.
if ($i == $params['installments']) {
if ($i == $params['installments'] && isset($params['amount'])) {
$params['scheduled_amount'] = $params['amount'] - ($i - 1) * $params['scheduled_amount'];
}
if (!isset($params['contribution_id']) && $params['installments'] > 1) {
Expand Down
29 changes: 29 additions & 0 deletions Civi/Api4/Pledge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/

/**
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
*/


namespace Civi\Api4;

/**
* Pledge entity.
*
* @package Civi\Api4
*/
class Pledge extends Generic\DAOEntity {

}
29 changes: 29 additions & 0 deletions Civi/Api4/PledgePayment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/

/**
*
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
*/


namespace Civi\Api4;

/**
* PledgePayment entity.
*
* @package Civi\Api4
*/
class PledgePayment extends Generic\DAOEntity {

}
24 changes: 0 additions & 24 deletions ext/contributioncancelactions/contributioncancelactions.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,10 @@ function contributioncancelactions_civicrm_post($op, $objectName, $objectId, $ob
)) {
contributioncancelactions_cancel_related_pending_memberships((int) $objectId);
contributioncancelactions_cancel_related_pending_participant_records((int) $objectId);
contributioncancelactions_update_related_pledge((int) $objectId, (int) $objectRef->contribution_status_id);
}
}
}

/**
* Update any related pledge when a contribution is cancelled.
*
* This updates the status of the pledge and amount paid.
*
* The functionality should probably be give more thought in that it currently
* does not un-assign the contribution id from the pledge payment. However,
* at time of writing the goal is to move rather than fix functionality.
*
* @param int $contributionID
* @param int $contributionStatusID
*
* @throws CiviCRM_API3_Exception
*/
function contributioncancelactions_update_related_pledge(int $contributionID, int $contributionStatusID) {
$pledgePayments = civicrm_api3('PledgePayment', 'get', ['contribution_id' => $contributionID])['values'];
if (!empty($pledgePayments)) {
$pledgePaymentIDS = array_keys($pledgePayments);
$pledgePayment = reset($pledgePayments);
CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgePayment['pledge_id'], $pledgePaymentIDS, $contributionStatusID);
}
}

/**
* Find and cancel any pending participant records.
*
Expand Down
24 changes: 0 additions & 24 deletions ext/contributioncancelactions/tests/phpunit/CancelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,30 +256,6 @@ public function testCancelOrderWithParticipant(): void {
$this->callAPISuccessGetCount('Participant', ['status_id' => 'Cancelled'], 1);
}

/**
* Test cancel order api when a pledge is linked.
*
* The pledge status should be updated. I believe the contribution should also be unlinked but
* the goal at this point is no change.
*
* @throws CRM_Core_Exception
* @throws API_Exception
*/
public function testCancelOrderWithPledge(): void {
$this->createContact();
$pledgeID = (int) $this->callAPISuccess('Pledge', 'create', ['contact_id' => $this->ids['contact'][0], 'amount' => 4, 'installments' => 2, 'frequency_unit' => 'month', 'original_installment_amount' => 2, 'create_date' => 'now', 'financial_type_id' => 'Donation', 'start_date' => '+5 days'])['id'];
$orderID = (int) $this->callAPISuccess('Order', 'create', ['contact_id' => $this->ids['contact'][0], 'total_amount' => 2, 'financial_type_id' => 'Donation', 'api.Payment.create' => ['total_amount' => 2]])['id'];
$pledgePayments = $this->callAPISuccess('PledgePayment', 'get')['values'];
$this->callAPISuccess('PledgePayment', 'create', ['id' => key($pledgePayments), 'pledge_id' => $pledgeID, 'contribution_id' => $orderID, 'status_id' => 'Completed', 'actual_amount' => 2]);
$beforePledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]);
$this->assertEquals(2, $beforePledge['pledge_total_paid']);
$this->callAPISuccess('Order', 'cancel', ['contribution_id' => $orderID]);

$this->callAPISuccessGetSingle('Contribution', ['contribution_status_id' => 'Cancelled']);
$afterPledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]);
$this->assertEquals('', $afterPledge['pledge_total_paid']);
}

/**
* Test cancelling a contribution with a membership on the contribution edit
* form.
Expand Down
28 changes: 28 additions & 0 deletions tests/phpunit/CRM/Contribute/BAO/ContributionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
+--------------------------------------------------------------------+
*/
use Civi\Api4\Activity;
use Civi\Api4\PledgePayment;

/**
* Class CRM_Contribute_BAO_ContributionTest
Expand Down Expand Up @@ -1660,4 +1661,31 @@ public function testSendMailUpdateReceiptDate() {
$this->assertDBNotNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After sendMail with the permission to allow update receipt date must be set');
}

/**
* Test cancel order api when a pledge is linked.
*
* The pledge status should be updated. I believe the contribution should
* also be unlinked but the goal at this point is no change.
*
* @throws CRM_Core_Exception
* @throws \CiviCRM_API3_Exception
* @throws \API_Exception
*/
public function testCancelOrderWithPledge(): void {
$this->ids['contact'][0] = $this->individualCreate();
$pledgeID = (int) $this->callAPISuccess('Pledge', 'create', ['contact_id' => $this->ids['contact'][0], 'amount' => 4, 'installments' => 2, 'frequency_unit' => 'month', 'original_installment_amount' => 2, 'create_date' => 'now', 'financial_type_id' => 'Donation', 'start_date' => '+5 days'])['id'];
$orderID = (int) $this->callAPISuccess('Order', 'create', ['contact_id' => $this->ids['contact'][0], 'total_amount' => 2, 'financial_type_id' => 'Donation', 'api.Payment.create' => ['total_amount' => 2]])['id'];
$pledgePayments = $this->callAPISuccess('PledgePayment', 'get')['values'];
$this->callAPISuccess('PledgePayment', 'create', ['id' => key($pledgePayments), 'pledge_id' => $pledgeID, 'contribution_id' => $orderID, 'status_id' => 'Completed', 'actual_amount' => 2]);
$beforePledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]);
$this->assertEquals(2, $beforePledge['pledge_total_paid']);
$this->callAPISuccess('Order', 'cancel', ['contribution_id' => $orderID]);

$this->callAPISuccessGetSingle('Contribution', ['contribution_status_id' => 'Cancelled']);
$afterPledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]);
$this->assertEquals('', $afterPledge['pledge_total_paid']);
$payments = PledgePayment::get(FALSE)->addWhere('contribution_id', 'IS NOT NULL')->execute();
$this->assertCount(0, $payments);
}

}

0 comments on commit 178c5df

Please sign in to comment.