Merge branch 'master' into live
[tc-ipn-receiver.git] / trustcommerceIPN.php
CommitLineData
33422de3
LMM
1<?php
2/*
3 * This file is part of CiviCRM.
4 *
5 * CiviCRM is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * CiviCRM is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with CiviCRM. If not, see <http://www.gnu.org/licenses/>.
17 *
c7a8fffd
RR
18 * Copyright 2014-2017, Free Software Foundation
19 * Copyright 2014-2017, Lisa Marie Maginnis <lisa@fsf.org>
33422de3
LMM
20 *
21 */
22
c7a8fffd
RR
23/**
24 * CiviCRM (Instant Payment Notification) IPN processor module for
25 * TrustCommerece.
26 *
27 * For full documentation on the
28 * TrustCommerece API, please see the TCDevGuide for more information:
29 * https://vault.trustcommerce.com/downloads/TCDevGuide.htm
30 *
31 * This module supports the following features: Single credit/debit card
32 * transactions, AVS checking, recurring (create, update, and cancel
33 * subscription) optional blacklist with fail2ban,
34 *
35 * @copyright Free Software Foundation 2014-2017
36 * @version 1.0
37 * @package org.fsf.payment.trustcommerce.ipn
38 */
39
40define("MAX_FAILURES", 4);
41
33422de3 42class CRM_Core_Payment_trustcommerce_IPN extends CRM_Core_Payment_BaseIPN {
c7a8fffd
RR
43
44 /**
45 * Inherit
46 *
47 * @return void
48 */
33422de3
LMM
49 function __construct() {
50 parent::__construct();
51 }
52
c7a8fffd
RR
53 function getLastFailures($recur_id) {
54 $sql="SELECT count(*) as numfails
55 FROM civicrm_contribution
56 WHERE contribution_recur_id = $recur_id
57 AND
58 id > (SELECT MAX(id) FROM civicrm_contribution WHERE contribution_recur_id = $recur_id
59 AND contribution_status_id = 1);";
60
61 $result = CRM_Core_DAO::executeQuery($sql);
62 if($result->fetch()) {
63 $failures = $result->numfails;
64 } else {
65 $failures = NULL;
66 }
67
68 return $failures;
69
70 }
33422de3
LMM
71
72 function main($component = 'contribute') {
73 static $no = NULL;
74 $billingid = CRM_Utils_Request::retrieve('billingid', 'String', $no, FALSE, 'GET');
75 $input['status'] = CRM_Utils_Request::retrieve('status', 'String', $no, FALSE, 'GET');
76 $input['amount'] = CRM_Utils_Request::retrieve('amount', 'String', $no, FALSE, 'GET');
77 $input['date'] = CRM_Utils_Request::retrieve('date', 'String', $no, FALSE, 'GET');
78 $input['trxn_id'] = CRM_Utils_Request::retrieve('trxn_id', 'String', $no, FALSE, 'GET');
79 $checksum = CRM_Utils_Request::retrieve('checksum', 'String', $no, FALSE, 'GET');
80
81 if ($billingid) {
82 if( $input['status'] == '' || $input['amount'] == '' || $input['date'] == '' || $input['trxn_id'] == '' || md5($billingid.$input['trxn_id'].$input['amount'].$input['date']) != $checksum) {
83 CRM_Core_Error::debug_log_message("Error: IPN called with out proper fields");
84 echo "Error: invalid paramaters<p>\n";
85 exit;
86 }
87
88
89 $ids = $objects = array();
90 $input['component'] = $component;
91
92 // load post ids in $ids
93 $ids = NULL;
94 $ids = $this->getIDs($billingid, $input, $input['component']);
95
96 $ids['trxn_id'] = $input['trxn_id'];
97
98 if($this->checkDuplicate($input, $ids) != NULL) {
38aae692 99 $msg = 'TrustCommerceIPN: Skipping duplicate contribution: '.$ids['contribution'].' for contact: '.$ids['contact'].' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id']."\n";
de12f901
LMM
100 echo $msg;
101 CRM_Core_Error::debug_log_message($msg);
33422de3
LMM
102 exit;
103 }
33422de3
LMM
104
105 if(array_key_exists('membership', $ids)) {
106 $membership = array();
107 $params = array('id' => $ids['membership']);
108 $obj = CRM_Member_BAO_Membership::retrieve($params, $membership);
109 $objects['membership'] = array(&$obj);
110 }
33422de3 111
3c6bd815 112 $paymentProcessorTypeID = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessorType',
33422de3
LMM
113 'TrustCommerce', 'id', 'name'
114 );
3c6bd815
RR
115 $paymentProcessorID = civicrm_api3('PaymentProcessor', 'getvalue', array(
116 'is_test' => 0,
117 'options' => array('limit' => 1),
118 'payment_processor_type_id' => $paymentProcessorTypeID,
119 'return' => 'id',
120 ));
33422de3
LMM
121
122 if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) {
123 return FALSE;
124 }
125 // var_dump($objects);
126
127 if ($component == 'contribute' && $ids['contributionRecur']) {
128 // check if first contribution is completed, else complete first contribution
129 $first = TRUE;
130 if ($objects['contribution']->contribution_status_id == 1) {
131 $first = FALSE;
132 }
133
134
135 return $this->processRecur($input, $ids, $objects, $first);
136
c7a8fffd
RR
137 }
138
139 }
140 }
141
142 protected function disableAutorenew($recur_id) {
143 /* Load payment processor object */
144 // HARD CODED
145 $msg = 'TrustCommerceIPN: MAX_FAILURES hit, unstoring billing ID: '.$recur_id."\n";
146
147 CRM_Core_Error::debug_log_message($msg);
148 echo $msg;
149
150 $sql = "SELECT user_name, password, url_site FROM civicrm_payment_processor WHERE id = 8 LIMIT 1";
151
152 $result = CRM_Core_DAO::executeQuery($sql);
153 if($result->fetch()) {
154 $request = array(
155 'custid' => $result->user_name,
156 'password' => $result->password,
157 'action' => 'unstore',
158 'billingid' => $recur_id
159 );
160
161 $update = 'UPDATE civicrm_contribution_recur SET contribution_status_id = 3 WHERE processor_id = "'.$recur_id.'";';
162 $result1 = CRM_Core_DAO::executeQuery($update);
163
164 $tc = tclink_send($request);
165 if(!$tc) {
166 return -1;
167 }
168
169 return TRUE;
170
171 } else {
172 echo 'CRITICAL ERROR: Could not load payment processor object';
173 return;
33422de3
LMM
174 }
175
176 }
33422de3
LMM
177
178 protected function checkDuplicate($input, $ids) {
c7a8fffd 179 // $sql='select id from civicrm_contribution where receive_date like \''.$input['date'].'%\' and total_amount='.$input['amount'].' and contact_id='.$ids['contact'].' and contribution_status_id = 1 limit 1';
1c234935 180 $sql="select id from civicrm_contribution where trxn_id = '".$ids['trxn_id']."' and contribution_status_id != 2";
33422de3 181
33422de3 182 $result = CRM_Core_DAO::executeQuery($sql);
de12f901
LMM
183 if($result->fetch()) {
184 $id = $result->id;
185 } else {
186 $id = NULL;
187 }
188
33422de3
LMM
189 return $id;
190 }
191 protected function processRecur($input, $ids, $objects, $first) {
dcb7146e 192 $lastfailures = $this->getLastFailures($ids['contributionRecur']);
33422de3
LMM
193 $recur = &$objects['contributionRecur'];
194 $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
195
196 $transaction = new CRM_Core_Transaction();
197
198 $now = date('YmdHis');
199
200 // fix dates that already exist
201 $dates = array('create_date', 'start_date', 'end_date', 'cancel_date', 'modified_date');
202 foreach ($dates as $name) {
203 if ($recur->$name) {
204 $recur->$name = CRM_Utils_Date::isoToMysql($recur->$name);
205 }
206 }
207
208 if (!$first) {
209 // create a contribution and then get it processed
210 $contribution = new CRM_Contribute_BAO_Contribution();
211 $contribution->contact_id = $ids['contact'];
212 $contribution->financial_type_id = $objects['contributionType']->id;
213 $contribution->contribution_page_id = $ids['contributionPage'];
214 $contribution->contribution_recur_id = $ids['contributionRecur'];
215 $contribution->receive_date = $input['date'];
216 $contribution->currency = $objects['contribution']->currency;
217 $contribution->payment_instrument_id = 1;
218 $contribution->amount_level = $objects['contribution']->amount_level;
219 $contribution->address_id = $objects['contribution']->address_id;
33422de3
LMM
220 $contribution->campaign_id = $objects['contribution']->campaign_id;
221 $contribution->total_amount = $input['amount'];
222
223 $objects['contribution'] = &$contribution;
224 }
de12f901 225
33422de3
LMM
226 $objects['contribution']->invoice_id = md5(uniqid(rand(), TRUE));
227 // $objects['contribution']->total_amount = $objects['contribution']->total_amount;
228 $objects['contribution']->trxn_id = $input['trxn_id'];
229
3e3d3df5
LMM
230 // check if contribution is already completed, if so we ignore this ipn
231 if ($objects['contribution']->contribution_status_id == 1) {
232 $transaction->commit();
233 CRM_Core_Error::debug_log_message("returning since contribution has already been handled");
234 echo 'Success: Contribution has already been handled<p>';
235 echo '';
236 return TRUE;
237 }
33422de3
LMM
238
239 $sendNotification = FALSE;
de12f901
LMM
240
241 $recur->trxn_id = $input['trxn_id'];
242 $recur->total_amount = $input['amount'];
243 $recur->payment_instrument_id = 1;
244 $recur->fee = 0;
245 $recur->net_amount = $input['amount'];
246
7bd867b8 247 $objects['contribution']->save();
802589c9 248
33422de3
LMM
249 if ($input['status'] == 1) {
250
251 // Approved
252 if ($first) {
253 $recur->start_date = $now;
254 $sendNotification = TRUE;
255 $subscriptionPaymentStatus = CRM_Core_Payment::RECURRING_PAYMENT_START;
256 }
257 $statusName = 'In Progress';
258 if (($recur->installments > 0) &&
259 ($input['subscription_paynum'] >= $recur->installments)
260 ) {
261 // this is the last payment
262 $statusName = 'Completed';
263 $recur->end_date = $now;
264
265 $sendNotification = TRUE;
266 $subscriptionPaymentStatus = CRM_Core_Payment::RECURRING_PAYMENT_END;
267 }
33422de3
LMM
268
269 $recur->modified_date = $now;
270 $recur->contribution_status_id = array_search($statusName, $contributionStatus);
271 $recur->save();
de12f901 272 $input['is_test'] = 0;
38aae692 273 $msg = 'TrustCommerceIPN: Created contribution: '.$ids['contribution'].' for contact: '.$ids['contact'].' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id'].' status: Completed'."\n";
de12f901
LMM
274 echo $msg;
275 CRM_Core_Error::debug_log_message($msg);
276
277 $this->completeTransaction($input, $ids, $objects, $transaction, $recur);
33422de3 278 }
de12f901 279 else if( $input['status'] == 4 ) {
33422de3
LMM
280 // Declined
281 // failed status
de12f901 282
33422de3
LMM
283 $recur->contribution_status_id = array_search('Failed', $contributionStatus);
284 $recur->cancel_date = $now;
285 $recur->save();
de12f901 286
38aae692 287 $msg = 'TrustCommerceIPN: Created contribution: '.$ids['contribution'].' for contact: '.$ids['contact'].' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id'].' status: Failed'."\n";
de12f901
LMM
288 echo $msg;
289 CRM_Core_Error::debug_log_message($msg);
571c1830
LMM
290
291 /* Disable cancelling transactions */
5d395c94 292 $input['IAmAHorribleNastyBeyondExcusableHackInTheCRMEventFORMTaskClassThatNeedsToBERemoved'] = 1;
c7a8fffd
RR
293
294 /* Action for repeated failures */
dcb7146e 295 if(MAX_FAILURES <= $lastfailures) {
c7a8fffd
RR
296 //$this->disableAutoRenew(($ids['contributionRecur']));
297 $this->disableAutorenew($ids['processor_id']);
298 }
299
38aae692 300 return $this->failed($objects, $transaction, $input);
33422de3
LMM
301 }
302
33422de3
LMM
303 if ($sendNotification) {
304 $autoRenewMembership = FALSE;
de12f901 305 if ($recur->id && isset($ids['membership']) && $ids['membership'] ) {
33422de3
LMM
306 $autoRenewMembership = TRUE;
307 }
308
33422de3
LMM
309 //send recurring Notification email for user
310 CRM_Contribute_BAO_ContributionPage::recurringNotify($subscriptionPaymentStatus,
311 $ids['contact'],
312 $ids['contributionPage'],
313 $recur,
314 $autoRenewMembership
315 );
33422de3 316 }
33422de3
LMM
317 }
318
c7a8fffd 319 protected function getIDs($billingid, $input, $module) {
33422de3 320 $sql = "SELECT cr.id, cr.contact_id, co.id as coid
c7a8fffd
RR
321 FROM civicrm_contribution_recur cr
322 INNER JOIN civicrm_contribution co ON co.contribution_recur_id = cr.id
323 WHERE cr.processor_id = '$billingid' LIMIT 1";
33422de3
LMM
324
325 $result = CRM_Core_DAO::executeQuery($sql);
326 $result->fetch();
327 $ids['contribution'] = $result->coid;
328 $ids['contributionRecur'] = $result->id;
329 $ids['contact'] = $result->contact_id;
c7a8fffd 330 $ids['processor_id'] = $billingid;
33422de3
LMM
331
332 if (!$ids['contributionRecur']) {
333 CRM_Core_Error::debug_log_message("Could not find billingid: ".$billingid);
1c234935 334 echo "Failure: Could not find contributionRecur: $billingid <p>\n";
33422de3
LMM
335 exit();
336 }
337
338 // get page id based on contribution id
339 $ids['contributionPage'] = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution',
340 $ids['contribution'],
341 'contribution_page_id'
342 );
343
344 if ($module == 'event') {
345 // FIXME: figure out fields for event
346 }
347 else {
348 // get the optional ids
349
350 // Get membershipId. Join with membership payment table for additional checks
c7a8fffd
RR
351 $sql = "SELECT m.id
352 FROM civicrm_membership as m
353 WHERE m.contribution_recur_id = '{$ids['contributionRecur']}'
354 LIMIT 1";
33422de3
LMM
355 if ($membershipId = CRM_Core_DAO::singleValueQuery($sql)) {
356
357 $ids['membership'] = $membershipId;
358 }
359
360 }
361
c7a8fffd
RR
362 return $ids;
363 }
33422de3
LMM
364
365}