if ('Cancelled' === CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $objectRef->contribution_status_id)) {
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.
*
*
* @group headless
*/
-class IPNCancelTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, HookInterface, TransactionalInterface {
+class CancelTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, HookInterface, TransactionalInterface {
use \Civi\Test\Api3TestTrait;
* @throws \Civi\API\Exception\UnauthorizedException
*/
public function testPaypalProCancel() {
- $this->ids['contact'][0] = Civi\Api4\Contact::create()->setValues(['first_name' => 'Brer', 'last_name' => 'Rabbit'])->execute()->first()['id'];
+ $this->createContact();
$this->createMembershipType();
Relationship::create()->setValues([
'contact_id_a' => $this->ids['contact'][0],
* @throws \CRM_Core_Exception
*/
public function testPaypalStandardCancel() {
- $this->ids['contact'][0] = Civi\Api4\Contact::create()->setValues(['first_name' => 'Brer', 'last_name' => 'Rabbit'])->execute()->first()['id'];
- $event = Event::create()->setValues(['title' => 'Event', 'start_date' => 'tomorrow', 'event_type_id:name' => 'Workshop'])->execute()->first();
+ $this->createContact();
+ $orderID = $this->createEventOrder();
+ $ipn = new CRM_Core_Payment_PayPalIPN([
+ 'mc_gross' => 200,
+ 'contactID' => $this->ids['contact'][0],
+ 'contributionID' => $orderID,
+ 'module' => 'event',
+ 'invoice' => 123,
+ 'eventID' => $this->ids['event'][0],
+ 'participantID' => Participant::get()->addWhere('event_id', '=', $this->ids['event'][0])->addSelect('id')->execute()->first()['id'],
+ 'payment_status' => 'Refunded',
+ 'processor_id' => $this->createPaymentProcessor(['payment_processor_type_id' => 'PayPal_Standard']),
+ ]);
+ $ipn->main();
+ $this->callAPISuccessGetSingle('Contribution', ['contribution_status_id' => 'Cancelled']);
+ $this->callAPISuccessGetCount('Participant', ['status_id' => 'Cancelled'], 1);
+ }
+
+ /**
+ * Test cancel order api
+ * @throws API_Exception
+ * @throws CRM_Core_Exception
+ */
+ public function testCancelOrderWithParticipant() {
+ $this->createContact();
+ $orderID = $this->createEventOrder();
+ $this->callAPISuccess('Order', 'cancel', ['contribution_id' => $orderID]);
+ $this->callAPISuccess('Order', 'get', ['contribution_id' => $orderID]);
+ $this->callAPISuccessGetSingle('Contribution', ['contribution_status_id' => 'Cancelled']);
+ $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() {
+ $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']);
+ }
+
+ /**
+ * Create an event and an order for a participant in that event.
+ *
+ * @return int
+ * @throws API_Exception
+ * @throws CRM_Core_Exception
+ */
+ protected function createEventOrder() {
+ $this->ids['event'][0] = (int) Event::create()->setValues(['title' => 'Event', 'start_date' => 'tomorrow', 'event_type_id:name' => 'Workshop'])->execute()->first()['id'];
$order = $this->callAPISuccess('Order', 'create', [
'contact_id' => $this->ids['contact'][0],
'financial_type_id' => 'Donation',
],
'params' => [
'contact_id' => $this->ids['contact'][0],
- 'event_id' => $event['id'],
+ 'event_id' => $this->ids['event'][0],
],
],
],
]);
- $ipn = new CRM_Core_Payment_PayPalIPN([
- 'mc_gross' => 200,
- 'contactID' => $this->ids['contact'][0],
- 'contributionID' => $order['id'],
- 'module' => 'event',
- 'invoice' => 123,
- 'eventID' => $event['id'],
- 'participantID' => Participant::get()->addWhere('event_id', '=', (int) $event['id'])->addSelect('id')->execute()->first()['id'],
- 'payment_status' => 'Refunded',
- 'processor_id' => $this->createPaymentProcessor(['payment_processor_type_id' => 'PayPal_Standard']),
- ]);
- $ipn->main();
- $this->callAPISuccessGetSingle('Contribution', ['contribution_status_id' => 'Cancelled']);
- $this->callAPISuccessGetCount('Participant', ['status_id' => 'Cancelled'], 1);
+ return (int) $order['id'];
+ }
+
+ /**
+ * Create a contact for use in the test.
+ *
+ * @throws API_Exception
+ */
+ protected function createContact(): void {
+ $this->ids['contact'][0] = Civi\Api4\Contact::create()->setValues(['first_name' => 'Brer', 'last_name' => 'Rabbit'])->execute()->first()['id'];
}
}
]);
}
- /**
- * Test cancel order api
- */
- public function testCancelWithParticipant() {
- $event = $this->eventCreate();
- $this->_eventId = $event['id'];
- $eventParams = [
- 'id' => $this->_eventId,
- 'financial_type_id' => 4,
- 'is_monetary' => 1,
- ];
- $this->callAPISuccess('event', 'create', $eventParams);
- $participantParams = [
- 'financial_type_id' => 4,
- 'event_id' => $this->_eventId,
- 'role_id' => 1,
- 'status_id' => 1,
- 'fee_currency' => 'USD',
- 'contact_id' => $this->_individualId,
- ];
- $participant = $this->callAPISuccess('Participant', 'create', $participantParams);
- $extraParams = [
- 'contribution_mode' => 'participant',
- 'participant_id' => $participant['id'],
- ];
- $contribution = $this->addOrder(TRUE, 100, $extraParams);
- $paymentParticipant = [
- 'participant_id' => $participant['id'],
- 'contribution_id' => $contribution['id'],
- ];
- $this->callAPISuccess('ParticipantPayment', 'create', $paymentParticipant);
- $params = [
- 'contribution_id' => $contribution['id'],
- ];
- $this->callAPISuccess('order', 'cancel', $params);
- $order = $this->callAPISuccess('Order', 'get', $params);
- $expectedResult = [
- $contribution['id'] => [
- 'total_amount' => 100,
- 'contribution_id' => $contribution['id'],
- 'contribution_status' => 'Cancelled',
- 'net_amount' => 100,
- ],
- ];
- $this->checkPaymentResult($order, $expectedResult);
- $participantPayment = $this->callAPISuccess('ParticipantPayment', 'getsingle', $params);
- $participant = $this->callAPISuccess('participant', 'get', ['id' => $participantPayment['participant_id']]);
- $this->assertEquals($participant['values'][$participant['id']]['participant_status'], 'Cancelled');
- $this->callAPISuccess('Contribution', 'Delete', [
- 'id' => $contribution['id'],
- ]);
- }
-
/**
* Test an exception is thrown if line items do not add up to total_amount, no tax.
*/