246 lines
9.1 KiB
PHP
246 lines
9.1 KiB
PHP
<?php
|
|
/*------------------------------------------------------------+
|
|
| SYSTOPIA Twingle Integration |
|
|
| Copyright (C) 2019 SYSTOPIA |
|
|
| Author: B. Endres (endres@systopia.de) |
|
|
+-------------------------------------------------------------+
|
|
| This program is released as free software under the |
|
|
| Affero GPL license. You can redistribute it and/or |
|
|
| modify it under the terms of this license which you |
|
|
| can read by viewing the included agpl.txt or online |
|
|
| at www.gnu.org/licenses/agpl.html. Removal of this |
|
|
| copyright header is strictly prohibited without |
|
|
| written permission from the original author(s). |
|
|
+-------------------------------------------------------------*/
|
|
|
|
declare(strict_types = 1);
|
|
|
|
use CRM_Twingle_ExtensionUtil as E;
|
|
|
|
class CRM_Twingle_Tools {
|
|
|
|
/**
|
|
* This flag can be used to temporarily suspend twingle protection
|
|
* @var bool
|
|
*/
|
|
public static $protection_suspended = FALSE;
|
|
|
|
/**
|
|
* Check if the attempted modification of the recurring contribution is allowed.
|
|
* If not, an exception will be raised
|
|
*
|
|
* @param $recurring_contribution_id int
|
|
* @param $change array
|
|
* @throws Exception if the change is not allowed
|
|
*/
|
|
public static function checkRecurringContributionChange($recurring_contribution_id, $change) {
|
|
// check if a change to the status is planned
|
|
if (empty($change['contribution_status_id'])) {
|
|
return;
|
|
}
|
|
|
|
// check if the target status is not closed
|
|
if (in_array($change['contribution_status_id'], [2, 5])) {
|
|
return;
|
|
}
|
|
|
|
// check if we're suspended
|
|
if (self::$protection_suspended) {
|
|
return;
|
|
}
|
|
|
|
// check if protection is turned on
|
|
$protection_on = Civi::settings()->get('twingle_protect_recurring');
|
|
if (empty($protection_on)) {
|
|
return;
|
|
}
|
|
|
|
// load the recurring contribution
|
|
$recurring_contribution = civicrm_api3('ContributionRecur', 'getsingle', [
|
|
'return' => 'trxn_id,contribution_status_id,payment_instrument_id,contact_id',
|
|
'id' => $recurring_contribution_id,
|
|
]);
|
|
|
|
// check if this is a SEPA transaction (doesn't concern us)
|
|
if (self::isSDD($recurring_contribution['payment_instrument_id'])) {
|
|
return;
|
|
}
|
|
|
|
// see if this recurring contribution is from Twingle
|
|
if (!self::isTwingleRecurringContribution($recurring_contribution_id, $recurring_contribution)) {
|
|
return;
|
|
}
|
|
|
|
// check if it's really a termination (i.e. current status is 2 or 5)
|
|
if (!in_array($recurring_contribution['contribution_status_id'], [2, 5])) {
|
|
return;
|
|
}
|
|
|
|
// this _IS_ on of the cases where we should step in:
|
|
CRM_Twingle_Tools::processRecurringContributionTermination($recurring_contribution_id, $recurring_contribution);
|
|
}
|
|
|
|
/**
|
|
* @param $recurring_contribution_id int recurring contribution ID to check
|
|
* @param $recurring_contribution array recurring contribution data, optional
|
|
* @return bool|null true, false or null if can't be determined
|
|
* @throws CiviCRM_API3_Exception
|
|
*/
|
|
public static function isTwingleRecurringContribution($recurring_contribution_id, $recurring_contribution = NULL) {
|
|
// this currently only works with prefixes
|
|
$prefix = Civi::settings()->get('twingle_prefix');
|
|
if (empty($prefix)) {
|
|
return NULL;
|
|
}
|
|
|
|
// load recurring contribution if necessary
|
|
if (empty($recurring_contribution['trxn_id'])) {
|
|
$recurring_contribution = civicrm_api3('ContributionRecur', 'getsingle', ['id' => $recurring_contribution_id]);
|
|
}
|
|
|
|
// check if it's a Twingle contribution by checking the prefix
|
|
// fixme: better ways (e.g. tags) should be used to mark twingle contributions
|
|
return (substr($recurring_contribution['trxn_id'], 0, strlen($prefix)) == $prefix);
|
|
}
|
|
|
|
/**
|
|
* Execute the recurring contribution protection
|
|
*
|
|
* @param $recurring_contribution_id int recurring contribution ID
|
|
* @param $recurring_contribution array recurring contribution fields
|
|
* @throws Exception could be one of the measures
|
|
*/
|
|
public static function processRecurringContributionTermination($recurring_contribution_id, $recurring_contribution) {
|
|
// check if we're suspended
|
|
if (self::$protection_suspended) {
|
|
return;
|
|
}
|
|
|
|
$protection_mode = Civi::settings()->get('twingle_protect_recurring');
|
|
switch ($protection_mode) {
|
|
case CRM_Twingle_Config::RCUR_PROTECTION_OFF:
|
|
// do nothing
|
|
break;
|
|
|
|
case CRM_Twingle_Config::RCUR_PROTECTION_EXCEPTION:
|
|
// phpcs:disable Generic.Files.LineLength.TooLong
|
|
throw new Exception(E::ts(
|
|
'This is a Twingle recurring contribution. It should be terminated through the Twingle interface, otherwise it will still be collected.'
|
|
));
|
|
|
|
// phpcs:enable
|
|
|
|
case CRM_Twingle_Config::RCUR_PROTECTION_ACTIVITY:
|
|
// create contact source activity
|
|
// first: get the contact ID
|
|
if (!empty($recurring_contribution['contact_id'])) {
|
|
$target_id = (int) $recurring_contribution['contact_id'];
|
|
}
|
|
else {
|
|
$target_id = (int) civicrm_api3('ContributionRecur', 'getvalue', [
|
|
'id' => $recurring_contribution_id,
|
|
'return' => 'contact_id',
|
|
]);
|
|
}
|
|
if (!empty($recurring_contribution['trxn_id'])) {
|
|
$trxn_id = $recurring_contribution['trxn_id'];
|
|
}
|
|
else {
|
|
$trxn_id = civicrm_api3('ContributionRecur', 'getvalue', [
|
|
'id' => $recurring_contribution_id,
|
|
'return' => 'trxn_id',
|
|
]);
|
|
}
|
|
|
|
try {
|
|
civicrm_api3('Activity', 'create', [
|
|
'activity_type_id' => Civi::settings()->get('twingle_protect_recurring_activity_type'),
|
|
'subject' => Civi::settings()->get('twingle_protect_recurring_activity_subject'),
|
|
'activity_date_time' => date('YmdHis'),
|
|
'target_id' => $target_id,
|
|
'assignee_id' => Civi::settings()->get('twingle_protect_recurring_activity_assignee'),
|
|
'status_id' => Civi::settings()->get('twingle_protect_recurring_activity_status'),
|
|
// phpcs:disable Generic.Files.LineLength.TooLong
|
|
'details' => E::ts(
|
|
"Recurring contribution [%1] (Transaction ID '%2') was terminated by a user. You need to end the corresponding record in Twingle as well, or it will still be collected.",
|
|
[1 => $recurring_contribution_id, 2 => $trxn_id]
|
|
),
|
|
// phpcs:enable
|
|
'source_contact_id' => CRM_Core_Session::getLoggedInContactID(),
|
|
]);
|
|
}
|
|
catch (Exception $ex) {
|
|
Civi::log()->warning("TwingleAPI: Couldn't create recurring protection activity: " . $ex->getMessage());
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Civi::log()->warning("TwingleAPI: Unknown recurring contribution protection mode: '{$protection_mode}'");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the given payment instrument is SEPA
|
|
*
|
|
* @param $payment_instrument_id string payment instrument
|
|
* @return boolean
|
|
*/
|
|
public static function isSDD($payment_instrument_id) {
|
|
static $sepa_payment_instruments = NULL;
|
|
if ($sepa_payment_instruments === NULL) {
|
|
// init with instrument names
|
|
$sepa_payment_instruments = ['FRST', 'RCUR', 'OOFF'];
|
|
|
|
// lookup and add instrument IDs
|
|
$lookup = civicrm_api3('OptionValue', 'get', [
|
|
'option_group_id' => 'payment_instrument',
|
|
'name' => ['IN' => $sepa_payment_instruments],
|
|
'return' => 'value',
|
|
]);
|
|
foreach ($lookup['values'] as $payment_instrument) {
|
|
$sepa_payment_instruments[] = $payment_instrument['value'];
|
|
}
|
|
}
|
|
return in_array($payment_instrument_id, $sepa_payment_instruments);
|
|
}
|
|
|
|
/**
|
|
* Get a CiviSEPA mandate for the given contribution ID
|
|
*
|
|
* @param $contribution_id integer contribution ID *or* recurring contribution ID
|
|
* @return integer mandate ID or null
|
|
*/
|
|
public static function getMandateFor($contribution_id) {
|
|
$contribution_id = (int) $contribution_id;
|
|
if ($contribution_id) {
|
|
try {
|
|
// try recurring mandate
|
|
$rcur_mandate = civicrm_api3('SepaMandate', 'get', [
|
|
'entity_id' => $contribution_id,
|
|
'entity_table' => 'civicrm_contribution_recur',
|
|
'type' => 'RCUR',
|
|
]);
|
|
if ($rcur_mandate['count'] == 1) {
|
|
return reset($rcur_mandate['values']);
|
|
}
|
|
|
|
// try OOFF mandate
|
|
// try recurring mandate
|
|
$ooff_mandate = civicrm_api3('SepaMandate', 'get', [
|
|
'entity_id' => $contribution_id,
|
|
'entity_table' => 'civicrm_contribution',
|
|
'type' => 'OOFF',
|
|
]);
|
|
if ($ooff_mandate['count'] == 1) {
|
|
return reset($ooff_mandate['values']);
|
|
}
|
|
}
|
|
catch (Exception $ex) {
|
|
Civi::log()->warning("CRM_Twingle_Tools::getMandate failed for [{$contribution_id}]: " . $ex->getMessage());
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
}
|