From f6188b739dce0731c6234aa8f5ba83fa6cdd6dfe Mon Sep 17 00:00:00 2001 From: Alan Dixon Date: Mon, 9 Aug 2021 09:52:57 -0400 Subject: [PATCH 1/4] Update README.md In response to issue #372, documentation of recurring failure handling. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index db73818c..5fb5fc9b 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,8 @@ Some issues may be related to core CiviCRM issues, and may not have an immediate Below is a list of some of the most common issues: +Unexpected failures. If you get an unexpectedly large number of failures for your recurring contributions, please review this page to understand how the extension does it's best to handle them and what administrators can do: https://github.com/iATSPayments/com.iatspayments.civicrm/wiki/Recurring-Contribution-Failure-Handling + 9002 Error - if you get this when trying to make a contribution, then you're getting that error back from the iATS server due to an account misconfiguration. When this happens and your using a 'legacy' iATS account -> check if you have special characters in your password (and remove them). If you're using '1st Pay' contact iATS Customer Service to ensure your account is configured properly. CiviCRM core assigns Membership status (=new) and extends Membership End date as well as Event status (=registered) as soon as ACH/EFT is submitted (so while payment is still pending - this could be several days for ACH/EFT). If the contribution receives a Ok:BankAccept -> the extension will mark the contribution in CiviCRM as completed. If the contribution does NOT receive a Ok:BankAccept -> the extension will mark the contribution in CiviCRM as rejected - however - associated existing Membership and Event records may need to be updated manually. From 92cdbd0b4fba63f65fd93a564929177e4a87d7ba Mon Sep 17 00:00:00 2001 From: Monish Deb Date: Mon, 2 Aug 2021 20:20:09 +0530 Subject: [PATCH 2/4] Support 'Update billing details' for Faps CC donation --- CRM/Core/Payment/Faps.php | 159 +++++++++++++++++++++++++++++++------- CRM/Iats/FapsRequest.php | 10 +++ 2 files changed, 143 insertions(+), 26 deletions(-) diff --git a/CRM/Core/Payment/Faps.php b/CRM/Core/Payment/Faps.php index 5719b25b..c8ec7bd5 100644 --- a/CRM/Core/Payment/Faps.php +++ b/CRM/Core/Payment/Faps.php @@ -461,6 +461,66 @@ public function getEditableRecurringScheduleFields() { public function getRecurringScheduleUpdateHelpText() { return 'Use this form to change the amount or number of installments for this recurring contribution.'; } + + /* + * Implement the ability to update the billing info for recurring contributions, + * This functionality will apply to back-end and front-end, + * so it's only enabled when configured as on via the iATS admin settings. + * The default isSupported method is overridden above to achieve this. + * + * Return TRUE on success or an error. + */ + public function updateSubscriptionBillingInfo(&$message = '', $params = array()) { + // Fix billing form update bug https://github.com/iATSPayments/com.iatspayments.civicrm/issues/252 by getting crid from _POST + if (empty($params['crid'])) { + $params['crid'] = !empty($_POST['crid']) ? (int) $_POST['crid'] : (!empty($_GET['crid']) ? (int) $_GET['crid'] : 0); + if (empty($params['crid']) && !empty($params['entryURL'])) { + $components = parse_url($params['entryURL']); + parse_str(html_entity_decode($components['query']), $entryURLquery); + $params['crid'] = $entryURLquery['crid']; + } + } + // updatedBillingInfo array changed sometime after 4.7.27 + $crid = !empty($params['crid']) ? $params['crid'] : $params['recur_id']; + if (empty($crid)) { + $alert = ts('This system is unable to perform self-service updates to credit cards. Please contact the administrator of this site.'); + throw new Exception($alert); + } + $contribution_recur = civicrm_api3('ContributionRecur', 'getsingle', ['id' => $crid]); + $payment_token = $result = civicrm_api3('PaymentToken', 'getsingle', ['id' => $contribution_recur['payment_token_id']]); + $params['token'] = $payment_token['token']; + // construct the array of data that I'll submit to the iATS Payments server. + $options = [ + 'action' => 'VaultUpdateCCRecord', + ]; + $vault_request = new CRM_Iats_FapsRequest($options); + + $request = $this->convertParams($params, $options['action']); + $result = CRM_Iats_FapsRequest::credentials($contribution_recur['payment_processor_id']); + $credentials = [ + 'merchantKey' => $result['signature'], + 'processorId' => $result['user_name'], + ]; + $token_request->request($credentials, $request); + + // Make the soap request. + try { + $response = $vault_request->request($credentials, $submit_values); + // note: don't log this to the iats_response table. + $faps_result = $vault_request->result($response, TRUE); + // CRM_Core_Error::debug_var('iats result', $iats_result); + if (!empty($faps_result['recordsUpdated'])) { + return TRUE; + } + else { + return self::error($faps_result); + } + } + catch (Exception $error) { // what could go wrong? + $message = $error->getMessage(); + return $this->error('9002', $message); + } + } /** * Convert the values in the civicrm params to the request array with keys as expected by FAPS @@ -471,15 +531,50 @@ public function getRecurringScheduleUpdateHelpText() { * @return array */ protected function convertParams($params, $method) { + $convert = array( + 'ownerEmail' => 'email', + 'ownerStreet' => 'street_address', + 'ownerCity' => 'city', + 'ownerState' => 'state_province', + 'ownerZip' => 'postal_code', + 'ownerCountry' => 'country', + 'orderId' => 'invoiceID', + 'cardNumber' => 'credit_card_number', + 'cardExpYear' => 'year', + 'cardExpMonth' => 'month', + 'cVV' => 'cvv2', + 'ownerName' => [ + 'billing_first_name', + 'billing_last_name', + ], + ); + if (in_array($method, ['GenerateTokenFromCreditCard', 'VaultCreateCCRecord'])) { + $convert = array_merge($convert, [ + 'creditCardCryptogram' => 'cryptogram', + 'transactionAmount' => 'amount', + ]); + } + if ($method == 'VaultUpdateCCRecord') { + $convert = array_merge($convert, [ + 'cardtype' => 'credit_card_type', + 'ownerName' => [ + 'first_name', + 'middle_name', + 'last_name', + ], + 'vaultKey' => 'token', + ]); + } + if (empty($params['country']) && !empty($params['country_id'])) { try { $result = civicrm_api3('Country', 'get', [ 'sequential' => 1, 'return' => ['name'], - 'id' => $params['country_id'], + 'id' => $params['country_id'], 'options' => ['limit' => 1], ]); - $params['country'] = $result['values'][0]['name']; + $params['country'] = $result['values'][0]['name']; } catch (CiviCRM_API3_Exception $e) { Civi::log()->info('Unexpected error from api3 looking up countries/states/provinces'); @@ -490,32 +585,51 @@ protected function convertParams($params, $method) { $result = civicrm_api3('StateProvince', 'get', [ 'sequential' => 1, 'return' => ['name'], - 'id' => $params['state_province_id'], + 'id' => $params['state_province_id'], 'options' => ['limit' => 1], ]); - $params['state_province'] = $result['values'][0]['name']; + $params['state_province'] = $result['values'][0]['name']; } catch (CiviCRM_API3_Exception $e) { Civi::log()->info('Unexpected error from api3 looking up countries/states/provinces'); } } $request = array(); - $convert = array( - 'ownerEmail' => 'email', - 'ownerStreet' => 'street_address', - 'ownerCity' => 'city', - 'ownerState' => 'state_province', - 'ownerZip' => 'postal_code', - 'ownerCountry' => 'country', - 'orderId' => 'invoiceID', - 'cardNumber' => 'credit_card_number', -// 'cardtype' => 'credit_card_type', - 'cVV' => 'cvv2', - 'creditCardCryptogram' => 'cryptogram', - ); foreach ($convert as $r => $p) { + if ($r == 'ownerName') { + $request[$r] = ''; + foreach ($p as $namePart) { + $request[$r] .= !empty($params[$namePart]) ? $params[$namePart] . ' ' : ''; + } + continue; + } if (isset($params[$p])) { - $request[$r] = htmlspecialchars($params[$p]); + if ($r == 'transactionAmount') { + $request[$r] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params[$p])); + } + elseif ($r == 'cardExpYear') { + $request[$r] = sprintf('%02d', $params[$p] % 100); + } + elseif ($r == 'cardExpMonth') { + $request[$r] = sprintf('%02d', $params[$p]); + } + elseif ($r == 'cardtype') { + $mop = [ + 'Visa' => 'VISA', + 'MasterCard' => 'MC', + 'Amex' => 'AMX', + 'Discover' => 'DSC', + ]; + $request[$r] = $mop[$params[$p]]; + } + elseif ($r == 'vaultKey') { + $matches = explode(':', $params[$p]); + $request['id'] = $matches[1]; + $request[$r] = $matches[0]; + } + else { + $request[$r] = htmlspecialchars($params[$p]); + } } } if (empty($params['email'])) { @@ -526,14 +640,7 @@ protected function convertParams($params, $method) { $request['ownerEmail'] = $params['email-Primary']; } } - $request['ownerName'] = $params['billing_first_name'].' '.$params['billing_last_name']; - if (!empty($params['month'])) { - $request['cardExpMonth'] = sprintf('%02d', $params['month']); - } - if (!empty($params['year'])) { - $request['cardExpYear'] = sprintf('%02d', $params['year'] % 100); - } - $request['transactionAmount'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount'])); + // additional method-specific values (none!) //CRM_Core_Error::debug_var('params for conversion', $params); //CRM_Core_Error::debug_var('method', $method); diff --git a/CRM/Iats/FapsRequest.php b/CRM/Iats/FapsRequest.php index 2542fdd9..0b8a5a24 100644 --- a/CRM/Iats/FapsRequest.php +++ b/CRM/Iats/FapsRequest.php @@ -130,4 +130,14 @@ public function request($credentials, $request_params, $log_failure = TRUE) { return $e->getMessage(); } } + + public static function credentials($payment_processor_id) { + static $credentials = []; + if (empty($credentials[$payment_processor_id])) { + $credentials[$payment_processor_id] = civicrm_api3('PaymentProcessor', 'get', [ + 'id' => $payment_processor_id, + ])['values'][$payment_processor_id]; + } + return $credentials[$payment_processor_id]; + } } From 2cec12d0a0ad56a42982a3d8727e4cbc2dfbcbac Mon Sep 17 00:00:00 2001 From: Monish Deb Date: Mon, 9 Aug 2021 13:40:11 +0530 Subject: [PATCH 3/4] QA fixes --- CRM/Core/Payment/Faps.php | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/CRM/Core/Payment/Faps.php b/CRM/Core/Payment/Faps.php index c8ec7bd5..dd7158b8 100644 --- a/CRM/Core/Payment/Faps.php +++ b/CRM/Core/Payment/Faps.php @@ -191,7 +191,7 @@ public function buildForm(&$form) { $markup = ''; // '; CRM_Core_Region::instance('billing-block')->add(array( 'markup' => $markup, - )); + )); // the cryptojs above is the one on the 1pay server, now I load and invoke the extension's crypto.js $myCryptoJs = $resources->getUrl('com.iatspayments.civicrm', 'js/crypto.js'); // after manually doing what addVars('iats', $jsVariables) would normally do @@ -461,7 +461,7 @@ public function getEditableRecurringScheduleFields() { public function getRecurringScheduleUpdateHelpText() { return 'Use this form to change the amount or number of installments for this recurring contribution.
  • You can not change the contribution frequency.
  • You can also modify the next scheduled contribution date.
  • You can change whether the contributor is sent an email receipt for each contribution.
  • You have an option to notify the contributor of these changes.
'; } - + /* * Implement the ability to update the billing info for recurring contributions, * This functionality will apply to back-end and front-end, @@ -501,24 +501,22 @@ public function updateSubscriptionBillingInfo(&$message = '', $params = array()) 'merchantKey' => $result['signature'], 'processorId' => $result['user_name'], ]; - $token_request->request($credentials, $request); - + // Make the soap request. try { - $response = $vault_request->request($credentials, $submit_values); + $response = $vault_request->request($credentials, $request); // note: don't log this to the iats_response table. - $faps_result = $vault_request->result($response, TRUE); - // CRM_Core_Error::debug_var('iats result', $iats_result); - if (!empty($faps_result['recordsUpdated'])) { + // CRM_Core_Error::debug_var('faps result', $response); + if (!empty($response['recordsUpdated'])) { return TRUE; } else { - return self::error($faps_result); + return self::error($response); } } catch (Exception $error) { // what could go wrong? $message = $error->getMessage(); - return $this->error('9002', $message); + throw new PaymentProcessorException($message, '9002'); } } @@ -565,7 +563,7 @@ protected function convertParams($params, $method) { 'vaultKey' => 'token', ]); } - + if (empty($params['country']) && !empty($params['country_id'])) { try { $result = civicrm_api3('Country', 'get', [ @@ -599,7 +597,7 @@ protected function convertParams($params, $method) { if ($r == 'ownerName') { $request[$r] = ''; foreach ($p as $namePart) { - $request[$r] .= !empty($params[$namePart]) ? $params[$namePart] . ' ' : ''; + $request[$r] .= !empty($params[$namePart]) ? $params[$namePart] . ' ' : ''; } continue; } @@ -657,9 +655,6 @@ public function &error($error = NULL) { if (is_object($error)) { throw new PaymentProcessorException(ts('Error %1', [1 => $error->getMessage()]), $error_code); } - elseif ($error && is_numeric($error)) { - throw new PaymentProcessorException(ts('Error %1', [1 => $this->errorString($error)]), $error_code); - } elseif (is_array($error)) { $errors = array(); if ($error['isError']) { @@ -679,7 +674,7 @@ public function &error($error = NULL) { else { /* in the event I'm handling an unexpected argument */ throw new PaymentProcessorException(ts('Unknown System Error.'), 'process_1stpay_extension'); } - return $e; + return $error; } /* @@ -761,6 +756,3 @@ protected function updateContribution($params, $update = array()) { } - - - From a6f6f4e41314cdd2b9c69aaabde648bbdfffcf60 Mon Sep 17 00:00:00 2001 From: Monish Deb Date: Wed, 4 Aug 2021 15:13:06 +0530 Subject: [PATCH 4/4] Support refund for iATS 1stPay CC and iATS EFT/ACH --- CRM/Core/Payment/Faps.php | 40 +++++++++ CRM/Core/Payment/iATSServiceACHEFT.php | 41 +++++++++ CRM/Iats/Form/Refund.php | 116 +++++++++++++++++++++++++ CRM/Iats/iATSServiceRequest.php | 7 ++ iats.civix.php | 31 +++++++ iats.php | 29 +++++-- templates/CRM/Iats/Refund.tpl | 11 +++ xml/Menu/iats.xml | 6 ++ 8 files changed, 273 insertions(+), 8 deletions(-) create mode 100644 CRM/Iats/Form/Refund.php create mode 100644 templates/CRM/Iats/Refund.tpl diff --git a/CRM/Core/Payment/Faps.php b/CRM/Core/Payment/Faps.php index dd7158b8..2409d854 100644 --- a/CRM/Core/Payment/Faps.php +++ b/CRM/Core/Payment/Faps.php @@ -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 */ diff --git a/CRM/Core/Payment/iATSServiceACHEFT.php b/CRM/Core/Payment/iATSServiceACHEFT.php index 5332d44c..f297e90f 100644 --- a/CRM/Core/Payment/iATSServiceACHEFT.php +++ b/CRM/Core/Payment/iATSServiceACHEFT.php @@ -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']); + } + } /** * diff --git a/CRM/Iats/Form/Refund.php b/CRM/Iats/Form/Refund.php new file mode 100644 index 00000000..7fa88443 --- /dev/null +++ b/CRM/Iats/Form/Refund.php @@ -0,0 +1,116 @@ +_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' => '         ', + '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'); + } + } + +} diff --git a/CRM/Iats/iATSServiceRequest.php b/CRM/Iats/iATSServiceRequest.php index 13700124..c9263cee 100644 --- a/CRM/Iats/iATSServiceRequest.php +++ b/CRM/Iats/iATSServiceRequest.php @@ -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': diff --git a/iats.civix.php b/iats.civix.php index 575aa5a5..872183f0 100644 --- a/iats.civix.php +++ b/iats.civix.php @@ -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; diff --git a/iats.php b/iats.php index 8f253aa1..af536313 100644 --- a/iats.php +++ b/iats.php @@ -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) { @@ -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']) @@ -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); @@ -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] : ''); @@ -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)) { @@ -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'); } @@ -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)) { @@ -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; + } +} diff --git a/templates/CRM/Iats/Refund.tpl b/templates/CRM/Iats/Refund.tpl new file mode 100644 index 00000000..a462ed25 --- /dev/null +++ b/templates/CRM/Iats/Refund.tpl @@ -0,0 +1,11 @@ + +
+
+
+ {ts}You are about to refund the total amount of this contribution using iATS.{/ts} {ts}Do you want to continue?{/ts} +
+
+ +
+{include file="CRM/common/formButtons.tpl"} +
\ No newline at end of file diff --git a/xml/Menu/iats.xml b/xml/Menu/iats.xml index bf2c127d..fb4b7ba3 100644 --- a/xml/Menu/iats.xml +++ b/xml/Menu/iats.xml @@ -34,4 +34,10 @@ CRM_Iats_Form_IATSOneTimeCharge IATSOneTimeCharge + + civicrm/iats/refund + CRM_Iats_Form_Refund + Refund + access CiviCRM +