Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Support refund for iATS 1stPay CC and iATS EFT/ACH #5

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 173 additions & 34 deletions CRM/Core/Payment/Faps.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public function buildForm(&$form) {
$markup = '<link type="text/css" rel="stylesheet" href="'.$cryptoCss.'" media="all" />'; // <script type="text/javascript" src="'.$cryptojs.'"></script>';
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
Expand Down 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 Expand Up @@ -462,6 +502,64 @@ public function getRecurringScheduleUpdateHelpText() {
return 'Use this form to change the amount or number of installments for this recurring contribution.<ul><li>You can not change the contribution frequency.</li><li>You can also modify the next scheduled contribution date.</li><li>You can change whether the contributor is sent an email receipt for each contribution.<li>You have an option to notify the contributor of these changes.</li></ul>';
}

/*
* 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'],
];

// Make the soap request.
try {
$response = $vault_request->request($credentials, $request);
// note: don't log this to the iats_response table.
// CRM_Core_Error::debug_var('faps result', $response);
if (!empty($response['recordsUpdated'])) {
return TRUE;
}
else {
return self::error($response);
}
}
catch (Exception $error) { // what could go wrong?
$message = $error->getMessage();
throw new PaymentProcessorException($message, '9002');
}
}

/**
* Convert the values in the civicrm params to the request array with keys as expected by FAPS
*
Expand All @@ -471,15 +569,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');
Expand All @@ -490,32 +623,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'])) {
Expand All @@ -526,14 +678,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);
Expand All @@ -550,9 +695,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']) {
Expand All @@ -572,7 +714,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;
}

/*
Expand Down Expand Up @@ -654,6 +796,3 @@ protected function updateContribution($params, $update = array()) {


}



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
10 changes: 10 additions & 0 deletions CRM/Iats/FapsRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
}
Loading