Skip to content

Commit

Permalink
Support refund for iATS 1stPay CC and iATS EFT/ACH
Browse files Browse the repository at this point in the history
  • Loading branch information
monishdeb committed Aug 12, 2021
1 parent 4344a3a commit a6f6f4e
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 8 deletions.
40 changes: 40 additions & 0 deletions CRM/Core/Payment/Faps.php
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,46 @@ public function doPayment(&$params, $component = 'contribute') {
}
}

/**
* Does this payment processor support refund?
*
* @return bool
*/
public function supportsRefund() {
return TRUE;
}

// might become a supported core function but for now just create our own function name
public function doRefund($params = []) {
$request = [
'refNumber' => $params['trxn_id'],
'transactionAmount' => sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['total_amount'])),
];

$options = [
'action' => 'Credit',
'test' => $this->is_test,
];
$token_request = new CRM_Iats_FapsRequest($options);
// CRM_Core_Error::debug_var('token request', $request);
$credentials = [
'merchantKey' => $this->_paymentProcessor['signature'],
'processorId' => $this->_paymentProcessor['user_name']
];
$result = $token_request->request($credentials, $request);

$this->error($result);

if (!empty($result['authResponse'] == 'ACCEPTED')) {
$refundParams = [
'refund_trxn_id' => $result['referenceNumber'],
'refund_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
'refund_status_name' => 'Completed',
];
return $refundParams;
}
}

/**
* Support corresponding CiviCRM method
*/
Expand Down
41 changes: 41 additions & 0 deletions CRM/Core/Payment/iATSServiceACHEFT.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,47 @@ protected function buildForm_CAD(&$form) {
));
}

/**
* Does this payment processor support refund?
*
* @return bool
*/
public function supportsRefund() {
return TRUE;
}

// might become a supported core function but for now just create our own function name
public function doRefund($params = []) {
$iats = new CRM_Iats_iATSServiceRequest([
'type' => 'process',
'method' => 'acheft_refund',
'iats_domain' => $this->_profile['iats_domain'],
]);
$request = [
'transactionId' => $params['trxn_id'],
'total' => (-1 * sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['total_amount']))),
'customerIPAddress' => (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']),
];
$credentials = [
'agentCode' => $this->_paymentProcessor['user_name'],
'password' => $this->_paymentProcessor['password'],
];
// Make the soap request.
$response = $iats->request($credentials, $request);

$result = $iats->result($response);
if ($result['status']) {
$refundParams = [
'refund_trxn_id' => trim($result['remote_id']) . ':' . time(),
'refund_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'),
'refund_status_name' => 'Completed',
];
return $refundParams;
}
else {
return self::error($result['reasonMessage']);
}
}

/**
*
Expand Down
116 changes: 116 additions & 0 deletions CRM/Iats/Form/Refund.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

use CRM_Iats_ExtensionUtil as E;

/**
* Form controller class
*
* @see https://wiki.civicrm.org/confluence/display/CRMDOC/QuickForm+Reference
*/
class CRM_Iats_Form_Refund extends CRM_Core_Form {

/**
* contact ID
* @var object
*/
protected $_contactID;

/**
* Test or live mode
* @var object
*/
protected $_isTest;

protected $_paymentProcessorID;


/**
* Set variables up before form is built.
*/
public function preProcess() {
// Check permission for action.
if (!CRM_Core_Permission::checkActionPermission('CiviContribute', CRM_Core_Action::UPDATE)) {
// @todo replace with throw new CRM_Core_Exception().
CRM_Core_Error::fatal(ts('You do not have permission to access this page.'));
}

$this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this, TRUE);
$this->_contactID = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE);

$this->_paymentProcessorID = E::getPaymentProcessorByContributionID($this->_id);
if (!$this->_paymentProcessorID) {
CRM_Core_Error::statusBounce(ts('Payment processor not found'));
}
parent::preProcess();

$this->_isTest = 0;
if ($this->_action & CRM_Core_Action::PREVIEW) {
$this->_isTest = 1;
}
}

public function buildQuickForm() {
$this->addButtons(
array(
array(
'type' => 'next',
'name' => ts('Refund'),
'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
'isDefault' => TRUE,
),
array(
'type' => 'cancel',
'name' => ts('Cancel'),
),
)
);
}

public function postProcess() {
// find the token for this contribution
try {
$contribution = civicrm_api3('Contribution', 'getsingle', array('id' => $this->_id));
}
catch (CiviCRM_API3_Exception $e) {
// FIXME: display an error message or something ?
throw new \Civi\Payment\Exception\PaymentProcessorException($e->getMessage());
}

try {
$refundParams = [
'payment_processor_id' => $this->_paymentProcessorID,
'amount' => $contribution['total_amount'],
'currency' => $contribution['currency'],
'trxn_id' => $contribution['trxn_id'],
];
$refund = civicrm_api3('PaymentProcessor', 'Refund', $refundParams)['values'];
if ($refund['refund_status_name'] === 'Completed') {
$payments = civicrm_api3('Payment', 'get', ['entity_id' => $params['contribution_id']]);
if (!empty($payments['count']) && !empty($payments['values'])) {
foreach ($payments['values'] as $payment) {
civicrm_api3('Payment', 'cancel', [
'id' => $payment['id'],
'trxn_date' = date('Y-m-d H:i:s'),
]);
}
}
}
$refundPaymentParams = [
'contribution_id' => $this->_id,
'trxn_id' => $refund['refund_trxn_id'],
'total_amount' => (-1 * $contribution['total_amount']),
'payment_processor_id' => $this->_paymentProcessorID,
];
$trxn = CRM_Financial_BAO_Payment::create($refundPaymentParams);

CRM_Core_Session::setStatus(E::ts('Refund was processed successfully.'), 'Refund processed', 'success');

CRM_Core_Session::singleton()->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view',
"reset=1&cid={$this->_contactID}&selectedChild=contribute"
));
} catch (Exception $e) {
CRM_Core_Error::statusBounce($e->getMessage(), NULL, 'Refund failed');
}
}

}
7 changes: 7 additions & 0 deletions CRM/Iats/iATSServiceRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,13 @@ public function methodInfo($type = '', $method = '') {
'message' => 'ProcessACHEFTWithCustomerCode',
'response' => 'ProcessACHEFTWithCustomerCodeResult',
),
'acheft_refund' => array(
'title' => 'Refund a specific ACH / EFT transaction',
'description' => $desc . 'ProcessACHEFTRefundWithTransactionId',
'method' => 'ProcessACHEFTRefundWithTransactionId',
'message' => 'ProcessACHEFTRefundWithTransactionId',
'response' => 'ProcessACHEFTRefundWithTransactionIdResult',
),
);
break;
case 'report':
Expand Down
31 changes: 31 additions & 0 deletions iats.civix.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,37 @@ public static function findClass($suffix) {
return self::CLASS_PREFIX . '_' . str_replace('\\', '_', $suffix);
}

/**
* Get iATS payment processor ID
*
* @param int $contributionID
*
* @return int|void
*/
public static function getPaymentProcessorByContributionID($contributionID) {
$paymentProcessorID = civicrm_api3('EntityFinancialTrxn', 'get', [
'return' => ['financial_trxn_id.payment_processor_id'],
'entity_table' => 'civicrm_contribution',
'entity_id' => $contributionID,
'sequential' => 1,
'financial_trxn_id.is_payment' => TRUE,
'options' => ['sort' => 'financial_trxn_id DESC', 'limit' => 1],
])['values'][0]['financial_trxn_id.payment_processor_id'] ?? NULL;

if ($paymentProcessorId) {
$className = civicrm_api3('PaymentProcessor', 'getvalue', [
'sequential' => 1,
'id' => $paymentProcessorID,
'return' => 'class_name',
]);
if (!in_array($className, ['Payment_iATSServiceACHEFT', 'Payment_Faps'])) {
return NULL;
}
}

return $paymentProcessorID;
}

}

use CRM_Iats_ExtensionUtil as E;
Expand Down
29 changes: 21 additions & 8 deletions iats.php
Original file line number Diff line number Diff line change
Expand Up @@ -339,9 +339,9 @@ function iats_civicrm_buildForm($formName, &$form) {
* Modifications to a (public/frontend) contribution financial forms for iATS
* procesors.
* 1. enable public selection of future recurring contribution start date.
*
*
* We're only handling financial payment class forms here. Note that we can no
* longer test for whether the page has/is recurring or not.
* longer test for whether the page has/is recurring or not.
*/

function iats_civicrm_buildForm_CRM_Financial_Form_Payment(&$form) {
Expand All @@ -355,7 +355,7 @@ function iats_civicrm_buildForm_CRM_Financial_Form_Payment(&$form) {
return;
}

// If enabled provide a way to set future contribution dates.
// If enabled provide a way to set future contribution dates.
// Uses javascript to hide/reset unless they have recurring contributions checked.
$settings = Civi::settings()->get('iats_settings');
if (!empty($settings['enable_public_future_recurring_start'])
Expand Down Expand Up @@ -400,7 +400,7 @@ function iats_civicrm_pageRun(&$page) {
* link to iATS CustomerLink display and editing pages.
*/
function iats_civicrm_pageRun_CRM_Contribute_Page_ContributionRecur($page) {
// Get the corresponding (most recently created) iATS customer code record
// Get the corresponding (most recently created) iATS customer code record
// we'll also get the expiry date and last four digits (at least, our best information about that).
$extra = array();
$crid = CRM_Utils_Request::retrieve('id', 'Integer', $page, FALSE);
Expand Down Expand Up @@ -502,7 +502,7 @@ function iats_civicrm_pre($op, $objectName, $objectId, &$params) {

function iats_get_setting($key = NULL) {
static $settings;
if (empty($settings)) {
if (empty($settings)) {
$settings = Civi::settings()->get('iats_settings');
}
return empty($key) ? $settings : (isset($settings[$key]) ? $settings[$key] : '');
Expand Down Expand Up @@ -598,7 +598,7 @@ function _iats_get_form_payment_processors($form) {
$id = $form->_paymentProcessor['id'];
return array($id => $form->_paymentProcessor);
}
else {
else {
// Handle the legacy: event and contribution page forms
if (empty($form->_paymentProcessors)) {
if (empty($form->_paymentProcessorIDs)) {
Expand Down Expand Up @@ -717,7 +717,7 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_UpdateSubscription(&$form) {
}
$allow_days = empty($settings['days']) ? array('-1') : $settings['days'];
if (0 < max($allow_days)) {
$userAlert = ts('Your next scheduled contribution date will automatically be updated to the next allowable day of the month: %1',
$userAlert = ts('Your next scheduled contribution date will automatically be updated to the next allowable day of the month: %1',
array(1 => implode(', ', $allow_days)));
CRM_Core_Session::setStatus($userAlert, ts('Warning'), 'alert');
}
Expand Down Expand Up @@ -750,7 +750,7 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_UpdateSubscription(&$form) {
'is_email_receipt' => 'Email receipt for each Contribution in this Recurring Series',
);
$dupe_fields = array();
// To be a good citizen, I check if core or another extension hasn't already added these fields
// To be a good citizen, I check if core or another extension hasn't already added these fields
// and don't add them again if they have.
foreach (array_keys($edit_fields) as $fid) {
if ($form->elementExists($fid)) {
Expand Down Expand Up @@ -819,3 +819,16 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_UpdateBilling(&$form) {
$form->addElement('hidden', 'crid', $crid);
}
}

function iats_civicrm_links($op, $objectName, $objectId, &$links, &$mask, &$values) {
if ($objectName == 'Contribution' && $op == 'contribution.selector.row') {
$links[] = array(
'name' => 'refund',
'url' => 'civicrm/iats/refund',
'qs' => 'reset=1&id=%%contribId%%&cid=%%cid%%',
'title' => 'Refund Live Payment',
//'class' => 'no-popup',
);
$values['contribId'] = $objectId;
}
}
11 changes: 11 additions & 0 deletions templates/CRM/Iats/Refund.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

<div class="crm-block crm-form-block crm-contribution-form-block">
<div class="messages status no-popup">
<div class="icon inform-icon"></div>
{ts}You are about to refund the total amount of this contribution using iATS.{/ts} {ts}Do you want to continue?{/ts}
</div>
</div>

<div class="crm-submit-buttons">
{include file="CRM/common/formButtons.tpl"}
</div>
6 changes: 6 additions & 0 deletions xml/Menu/iats.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,10 @@
<page_callback>CRM_Iats_Form_IATSOneTimeCharge</page_callback>
<title>IATSOneTimeCharge</title>
</item>
<item>
<path>civicrm/iats/refund</path>
<page_callback>CRM_Iats_Form_Refund</page_callback>
<title>Refund</title>
<access_arguments>access CiviCRM</access_arguments>
</item>
</menu>

0 comments on commit a6f6f4e

Please sign in to comment.