PageRenderTime 45ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/sites/all/modules/contrib/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceUKDD.php

https://gitlab.com/virtualrealms/d7civicrm
PHP | 341 lines | 221 code | 20 blank | 100 comment | 32 complexity | a7fe84d5cba4db99ecfaaadac06ccfd3 MD5 | raw file
  1. <?php
  2. /**
  3. * @file Copyright iATS Payments (c) 2014.
  4. * @author Alan Dixon
  5. *
  6. * This file is a part of CiviCRM published extension.
  7. *
  8. * This extension is free software; you can copy, modify, and distribute it
  9. * under the terms of the GNU Affero General Public License
  10. * Version 3, 19 November 2007.
  11. *
  12. * It is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  15. * See the GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public
  18. * License with this program; if not, see http://www.gnu.org/licenses/
  19. *
  20. * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the iATS_Service_Request object
  21. * for UK Direct Debit Recurring contributions ONLY
  22. */
  23. /**
  24. *
  25. */
  26. class CRM_Core_Payment_iATSServiceUKDD extends CRM_Core_Payment {
  27. /**
  28. * We only need one instance of this object. So we use the singleton
  29. * pattern and cache the instance in this variable.
  30. *
  31. * @var object
  32. * @static
  33. */
  34. static private $_singleton = NULL;
  35. /**
  36. * Constructor.
  37. *
  38. * @param string $mode
  39. * the mode of operation: live or test.
  40. *
  41. * @return void
  42. */
  43. public function __construct($mode, &$paymentProcessor) {
  44. $this->_paymentProcessor = $paymentProcessor;
  45. $this->_processorName = ts('iATS Payments UK Direct Debit');
  46. // Get merchant data from config.
  47. $config = CRM_Core_Config::singleton();
  48. // Live or test.
  49. $this->_profile['mode'] = $mode;
  50. // We only use the domain of the configured url, which is different for NA vs. UK.
  51. $this->_profile['iats_domain'] = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
  52. }
  53. /**
  54. *
  55. */
  56. static public function &singleton($mode, &$paymentProcessor, &$paymentForm = NULL, $force = FALSE) {
  57. $processorName = $paymentProcessor['name'];
  58. if (self::$_singleton[$processorName] === NULL) {
  59. self::$_singleton[$processorName] = new CRM_Core_Payment_iATSServiceUKDD($mode, $paymentProcessor);
  60. }
  61. return self::$_singleton[$processorName];
  62. }
  63. /**
  64. * Function checkParams.
  65. */
  66. public function checkParams($params) {
  67. if (!$this->_profile) {
  68. return self::error('Unexpected error, missing profile');
  69. }
  70. $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID'];
  71. if (!$isRecur) {
  72. return self::error('Not a recurring contribution: you can only use UK Direct Debit with a recurring contribution.');
  73. }
  74. if ('GBP' != $params['currencyID']) {
  75. return self::error(ts('Invalid currency %1, must by GBP', array(1 => $params['currencyID'])));
  76. }
  77. if (empty($params['installments'])) {
  78. return self::error(ts('You must specify the number of installments, open-ended contributions are not allowed.'));
  79. }
  80. elseif (1 >= $params['installments']) {
  81. return self::error(ts('You must specify a number of installments greater than 1.'));
  82. }
  83. }
  84. /**
  85. *
  86. */
  87. public function getSchedule($params) {
  88. // Convert params recurring information into iATS equivalents.
  89. $scheduleType = NULL;
  90. $paymentsRecur = $params['installments'] - 1;
  91. // IATS requires begin and end date, calculated here
  92. // to be converted to date format later
  93. // begin date has to be more than 12 days from now, not checked here.
  94. $beginTime = strtotime($beginDate = $params['payer_validate_start_date']);
  95. $date = getdate($beginTime);
  96. $interval = $params['frequency_interval'] ? $params['frequency_interval'] : 1;
  97. switch ($params['frequency_unit']) {
  98. case 'week':
  99. if (1 != $interval) {
  100. return self::error(ts('You can only choose each week on a weekly schedule.'));
  101. }
  102. $scheduleType = 'Weekly';
  103. $scheduleDate = $date['wday'] + 1;
  104. $endTime = $beginTime + ($paymentsRecur * 7 * 24 * 60 * 60);
  105. break;
  106. case 'month':
  107. $scheduleType = 'Monthly';
  108. $scheduleDate = $date['mday'];
  109. if (3 == $interval) {
  110. $scheduleType = 'Quarterly';
  111. $scheduleDate = '';
  112. }
  113. elseif (1 != $interval) {
  114. return self::error(ts('You can only choose monthly or every three months (quarterly) for a monthly schedule.'));
  115. }
  116. $date['mon'] += ($interval * $paymentsRecur);
  117. while ($date['mon'] > 12) {
  118. $date['mon'] -= 12;
  119. $date['year'] += 1;
  120. }
  121. $endTime = mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']);
  122. break;
  123. case 'year':
  124. if (1 != $interval) {
  125. return self::error(ts('You can only choose each year for a yearly schedule.'));
  126. }
  127. $scheduleType = 'Yearly';
  128. $scheduleDate = '';
  129. $date['year'] += $paymentsRecur;
  130. $endTime = mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']);
  131. break;
  132. default:
  133. return self::error(ts('Invalid frequency unit: %1', array(1 => $params['frequency_unit'])));
  134. break;
  135. }
  136. $endDate = date('c', $endTime);
  137. $beginDate = date('c', $beginTime);
  138. return array('scheduleType' => $scheduleType, 'scheduleDate' => $scheduleDate, 'endDate' => $endDate, 'beginDate' => $beginDate);
  139. }
  140. /**
  141. *
  142. */
  143. public function doDirectPayment(&$params) {
  144. $error = $this->checkParams($params);
  145. if (!empty($error)) {
  146. return $error;
  147. }
  148. // $params['start_date'] = $params['receive_date'];
  149. // use the iATSService object for interacting with iATS.
  150. require_once "CRM/iATS/iATSService.php";
  151. $iats = new iATS_Service_Request(array('type' => 'customer', 'method' => 'direct_debit_create_acheft_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
  152. $schedule = $this->getSchedule($params);
  153. // Assume an error object to return.
  154. if (!is_array($schedule)) {
  155. return $schedule;
  156. }
  157. $request = array_merge($this->convertParamsCreateCustomerCode($params), $schedule);
  158. $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
  159. $request['customerCode'] = '';
  160. $request['accountType'] = 'CHECKING';
  161. $credentials = array(
  162. 'agentCode' => $this->_paymentProcessor['user_name'],
  163. 'password' => $this->_paymentProcessor['password'],
  164. );
  165. // Get the API endpoint URL for the method's transaction mode.
  166. // TODO: enable override of the default url in the request object
  167. // $url = $this->_paymentProcessor['url_site'];.
  168. // Make the soap request.
  169. $response = $iats->request($credentials, $request);
  170. // Process the soap response into a readable result.
  171. $result = $iats->result($response);
  172. // drupal_set_message('<pre>'.print_r($result,TRUE).'</pre>');.
  173. if ($result['status']) {
  174. // Always pending.
  175. $params['contribution_status_id'] = 2;
  176. // For future versions, the proper key.
  177. $params['payment_status_id'] = 2;
  178. $params['trxn_id'] = trim($result['remote_id']) . ':' . time();
  179. $params['gross_amount'] = $params['amount'];
  180. // Save the client info in my custom table
  181. // Allow further manipulation of the arguments via custom hooks,.
  182. $customer_code = $result['CUSTOMERCODE'];
  183. if (isset($params['email'])) {
  184. $email = $params['email'];
  185. }
  186. elseif (isset($params['email-5'])) {
  187. $email = $params['email-5'];
  188. }
  189. elseif (isset($params['email-Primary'])) {
  190. $email = $params['email-Primary'];
  191. }
  192. $query_params = array(
  193. 1 => array($customer_code, 'String'),
  194. 2 => array($request['customerIPAddress'], 'String'),
  195. 3 => array('', 'String'),
  196. 4 => array($params['contactID'], 'Integer'),
  197. 5 => array($email, 'String'),
  198. 6 => array($params['contributionRecurID'], 'Integer'),
  199. );
  200. // drupal_set_message('<pre>'.print_r($query_params,TRUE).'</pre>');.
  201. CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_customer_codes
  202. (customer_code, ip, expiry, cid, email, recur_id) VALUES (%1, %2, %3, %4, %5, %6)", $query_params);
  203. // Save their payer validation data in civicrm_iats_ukdd_validate.
  204. $query_params = array(
  205. 1 => array($customer_code, 'String'),
  206. 2 => array($params['payer_validate_reference'], 'String'),
  207. 3 => array($params['contactID'], 'Integer'),
  208. 4 => array($params['contributionRecurID'], 'Integer'),
  209. 5 => array($params['payer_validate_declaration'], 'Integer'),
  210. 6 => array(date('c'), 'String'),
  211. );
  212. // drupal_set_message('<pre>'.print_r($query_params,TRUE).'</pre>');.
  213. CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_ukdd_validate
  214. (customer_code, acheft_reference_num, cid, recur_id, validated, validated_datetime) VALUES (%1, %2, %3, %4, %5, %6)", $query_params);
  215. // Set the status of the initial contribution to pending (currently is redundant), and the date to what I'm asking iATS for.
  216. $params['contribution_status_id'] = 2;
  217. $params['start_date'] = $params['payer_validate_start_date'];
  218. // Optimistically set this date, even though CiviCRM will likely not do anything with it yet - I'll change it with my pre hook in the meanwhile
  219. // $params['receive_date'] = strtotime($params['payer_validate_start_date']);
  220. // also set next_sched_contribution, though it won't be used.
  221. $params['next_sched_contribution'] = strtotime($params['payer_validate_start_date'] . ' + ' . $params['frequency_interval'] . ' ' . $params['frequency_unit']);
  222. return $params;
  223. }
  224. else {
  225. return self::error($result['reasonMessage']);
  226. }
  227. }
  228. /**
  229. * TODO: requires custom link
  230. * function changeSubscriptionAmount(&$message = '', $params = array()) {
  231. * $userAlert = ts('You have updated the amount of this recurring contribution.');
  232. * CRM_Core_Session::setStatus($userAlert, ts('Warning'), 'alert');
  233. * return TRUE;
  234. * } .
  235. */
  236. public function &error($error = NULL) {
  237. $e = CRM_Core_Error::singleton();
  238. if (is_object($error)) {
  239. $e->push($error->getResponseCode(),
  240. 0, NULL,
  241. $error->getMessage()
  242. );
  243. }
  244. elseif ($error && is_numeric($error)) {
  245. $e->push($error,
  246. 0, NULL,
  247. $this->errorString($error)
  248. );
  249. }
  250. elseif (is_string($error)) {
  251. $e->push(9002,
  252. 0, NULL,
  253. $error
  254. );
  255. }
  256. else {
  257. $e->push(9001, 0, NULL, "Unknown System Error.");
  258. }
  259. return $e;
  260. }
  261. /**
  262. * This function checks to see if we have the right config values.
  263. *
  264. * @param string $mode
  265. * the mode we are operating in (live or test)
  266. *
  267. * @return string the error message if any
  268. *
  269. * @public
  270. */
  271. public function checkConfig() {
  272. $error = array();
  273. if (empty($this->_paymentProcessor['user_name'])) {
  274. $error[] = ts('Agent Code is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
  275. }
  276. if (empty($this->_paymentProcessor['password'])) {
  277. $error[] = ts('Password is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
  278. }
  279. if (empty($this->_paymentProcessor['signature'])) {
  280. $error[] = ts('Service User Number (SUN) is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
  281. }
  282. $iats_domain = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
  283. if ('www.uk.iatspayments.com' != $iats_domain) {
  284. $error[] = ts('You can only use this payment processor with a UK iATS account');
  285. }
  286. if (!empty($error)) {
  287. return implode('<p>', $error);
  288. }
  289. else {
  290. return NULL;
  291. }
  292. }
  293. /**
  294. * Convert the values in the civicrm params to the request array with keys as expected by iATS.
  295. */
  296. public function convertParamsCreateCustomerCode($params) {
  297. $request = array();
  298. $convert = array(
  299. 'firstName' => 'billing_first_name',
  300. 'lastName' => 'billing_last_name',
  301. 'address' => 'street_address',
  302. 'city' => 'city',
  303. 'state' => 'state_province',
  304. 'zipCode' => 'postal_code',
  305. 'country' => 'country',
  306. 'ACHEFTReferenceNum' => 'payer_validate_reference',
  307. 'accountCustomerName' => 'account_holder',
  308. 'email' => 'email',
  309. 'recurring' => 'is_recur',
  310. 'amount' => 'amount',
  311. );
  312. foreach ($convert as $r => $p) {
  313. if (isset($params[$p])) {
  314. $request[$r] = $params[$p];
  315. }
  316. }
  317. // Account custom name is first name + last name, truncated to a maximum of 30 chars.
  318. $request['accountNum'] = trim($params['bank_identification_number']) . trim($params['bank_account_number']);
  319. return $request;
  320. }
  321. }