Added a function to count successive failures
[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 *
ab8492a8 18 * Copyright 2016, Lisa Marie Maginnis <lisa@fsf.org> (http://www.fsf.org)
33422de3
LMM
19 *
20 */
21
ab8492a8
LMM
22/**
23 * CiviCRM (Instant Payment Notification) IPN processor module for
24 * TrustCommerece.
25 *
26 * For full documentation on the
27 * TrustCommerece API, please see the TCDevGuide for more information:
28 * https://vault.trustcommerce.com/downloads/TCDevGuide.htm
29 *
30 * This module supports the following features: Single credit/debit card
31 * transactions, AVS checking, recurring (create, update, and cancel
32 * subscription) optional blacklist with fail2ban,
33 *
34 * @copyright Lisa Marie Maginnis <lisa@fsf.org> (http://www.fsf.org)
35 * @version 1.0
36 * @package org.fsf.payment.trustcommerce.ipn
37 */
38
39define("MAX_FAILURES", 4)
40
33422de3 41class CRM_Core_Payment_trustcommerce_IPN extends CRM_Core_Payment_BaseIPN {
ab8492a8
LMM
42
43 /**
44 * Inheret
45 *
46 * @return void
47 */
33422de3
LMM
48 function __construct() {
49 parent::__construct();
50 }
51
ab8492a8
LMM
52 function getLastFailures($recur_id) {
53 $sql="SELECT contribution_recur_id, trxn_id, contribution_status_id,
54 SUM(id > COALESCE(
55 ( SELECT max(id) FROM civicrm_contribution AS c2
56 WHERE c2.id = c.id and c2.contribution_status_id = 1),
57 4 )) AS numfails
58 FROM civicrm_contribution AS c
59 WHERE c.contribution_recur_id = $recur_id GROUP BY c.contribution_recur_id;"
33422de3 60
ab8492a8
LMM
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 }
71
33422de3
LMM
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) {
de12f901
LMM
99 $msg = 'TrustCommerceIPN: Skipping duplicate contribution: '.$ids['contribution'].' for contact: '.$ids['contact'].' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id']."\n";
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
LMM
111
112 $paymentProcessorID = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessorType',
113 'TrustCommerce', 'id', 'name'
114 );
115
116 if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) {
117 return FALSE;
118 }
119 // var_dump($objects);
120
121 if ($component == 'contribute' && $ids['contributionRecur']) {
122 // check if first contribution is completed, else complete first contribution
123 $first = TRUE;
124 if ($objects['contribution']->contribution_status_id == 1) {
125 $first = FALSE;
126 }
127
128
129 return $this->processRecur($input, $ids, $objects, $first);
130
131 }
132
133 }
134}
135
136 protected function checkDuplicate($input, $ids) {
137// $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';
138 $sql="select id from civicrm_contribution where trxn_id = '".$ids['trxn_id']."'";
139
140
141 $result = CRM_Core_DAO::executeQuery($sql);
de12f901
LMM
142 if($result->fetch()) {
143 $id = $result->id;
144 } else {
145 $id = NULL;
146 }
147
33422de3
LMM
148 return $id;
149 }
150 protected function processRecur($input, $ids, $objects, $first) {
151 $recur = &$objects['contributionRecur'];
152 $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
153
154 $transaction = new CRM_Core_Transaction();
155
156 $now = date('YmdHis');
157
158 // fix dates that already exist
159 $dates = array('create_date', 'start_date', 'end_date', 'cancel_date', 'modified_date');
160 foreach ($dates as $name) {
161 if ($recur->$name) {
162 $recur->$name = CRM_Utils_Date::isoToMysql($recur->$name);
163 }
164 }
165
166 if (!$first) {
167 // create a contribution and then get it processed
168 $contribution = new CRM_Contribute_BAO_Contribution();
169 $contribution->contact_id = $ids['contact'];
170 $contribution->financial_type_id = $objects['contributionType']->id;
171 $contribution->contribution_page_id = $ids['contributionPage'];
172 $contribution->contribution_recur_id = $ids['contributionRecur'];
173 $contribution->receive_date = $input['date'];
174 $contribution->currency = $objects['contribution']->currency;
175 $contribution->payment_instrument_id = 1;
176 $contribution->amount_level = $objects['contribution']->amount_level;
177 $contribution->address_id = $objects['contribution']->address_id;
33422de3
LMM
178 $contribution->campaign_id = $objects['contribution']->campaign_id;
179 $contribution->total_amount = $input['amount'];
180
181 $objects['contribution'] = &$contribution;
182 }
de12f901 183
33422de3
LMM
184 $objects['contribution']->invoice_id = md5(uniqid(rand(), TRUE));
185 // $objects['contribution']->total_amount = $objects['contribution']->total_amount;
186 $objects['contribution']->trxn_id = $input['trxn_id'];
187
3e3d3df5
LMM
188 // check if contribution is already completed, if so we ignore this ipn
189 if ($objects['contribution']->contribution_status_id == 1) {
190 $transaction->commit();
191 CRM_Core_Error::debug_log_message("returning since contribution has already been handled");
192 echo 'Success: Contribution has already been handled<p>';
193 echo '';
194 return TRUE;
195 }
33422de3
LMM
196
197 $sendNotification = FALSE;
de12f901
LMM
198
199 $recur->trxn_id = $input['trxn_id'];
200 $recur->total_amount = $input['amount'];
201 $recur->payment_instrument_id = 1;
202 $recur->fee = 0;
203 $recur->net_amount = $input['amount'];
204
33422de3
LMM
205 if ($input['status'] == 1) {
206
207 // Approved
208 if ($first) {
209 $recur->start_date = $now;
210 $sendNotification = TRUE;
211 $subscriptionPaymentStatus = CRM_Core_Payment::RECURRING_PAYMENT_START;
212 }
213 $statusName = 'In Progress';
214 if (($recur->installments > 0) &&
215 ($input['subscription_paynum'] >= $recur->installments)
216 ) {
217 // this is the last payment
218 $statusName = 'Completed';
219 $recur->end_date = $now;
220
221 $sendNotification = TRUE;
222 $subscriptionPaymentStatus = CRM_Core_Payment::RECURRING_PAYMENT_END;
223 }
33422de3
LMM
224
225 $recur->modified_date = $now;
226 $recur->contribution_status_id = array_search($statusName, $contributionStatus);
227 $recur->save();
de12f901
LMM
228 $input['is_test'] = 0;
229 $msg = 'TrustCommerceIPN: Created contribution: '.$ids['contribution'].' for contact: '.$ids['contact'].' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id'].' status: Completed'."\n";
230 echo $msg;
231 CRM_Core_Error::debug_log_message($msg);
232
233 $this->completeTransaction($input, $ids, $objects, $transaction, $recur);
33422de3 234 }
de12f901 235 else if( $input['status'] == 4 ) {
33422de3
LMM
236 // Declined
237 // failed status
de12f901 238
33422de3
LMM
239 $recur->contribution_status_id = array_search('Failed', $contributionStatus);
240 $recur->cancel_date = $now;
241 $recur->save();
de12f901
LMM
242
243 $msg = 'TrustCommerceIPN: Created contribution: '.$ids['contribution'].' for contact: '.$ids['contact'].' amount: $'.$input['amount'].' trxn_id: '.$input['trxn_id'].' status: Failed'."\n";
244 echo $msg;
245 CRM_Core_Error::debug_log_message($msg);
246
247 return $this->failed($objects, $transaction);
33422de3
LMM
248 }
249
33422de3
LMM
250 if ($sendNotification) {
251 $autoRenewMembership = FALSE;
de12f901 252 if ($recur->id && isset($ids['membership']) && $ids['membership'] ) {
33422de3
LMM
253 $autoRenewMembership = TRUE;
254 }
255
33422de3
LMM
256 //send recurring Notification email for user
257 CRM_Contribute_BAO_ContributionPage::recurringNotify($subscriptionPaymentStatus,
258 $ids['contact'],
259 $ids['contributionPage'],
260 $recur,
261 $autoRenewMembership
262 );
33422de3 263 }
33422de3
LMM
264 }
265
266 protected function getIDs($billingid, $input, $module) {
267 $sql = "SELECT cr.id, cr.contact_id, co.id as coid
268 FROM civicrm_contribution_recur cr
269 INNER JOIN civicrm_contribution co ON co.contribution_recur_id = cr.id
270 WHERE cr.processor_id = '$billingid' LIMIT 1";
271
272
273 $result = CRM_Core_DAO::executeQuery($sql);
274 $result->fetch();
275 $ids['contribution'] = $result->coid;
276 $ids['contributionRecur'] = $result->id;
277 $ids['contact'] = $result->contact_id;
278
279 if (!$ids['contributionRecur']) {
280 CRM_Core_Error::debug_log_message("Could not find billingid: ".$billingid);
281 echo "Failure: Could not find contributionRecur<p>\n";
282 exit();
283 }
284
285 // get page id based on contribution id
286 $ids['contributionPage'] = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution',
287 $ids['contribution'],
288 'contribution_page_id'
289 );
290
291 if ($module == 'event') {
292 // FIXME: figure out fields for event
293 }
294 else {
295 // get the optional ids
296
297 // Get membershipId. Join with membership payment table for additional checks
298 $sql = "
299 SELECT m.id
300 FROM civicrm_membership as m
301 WHERE m.contribution_recur_id = '{$ids['contributionRecur']}'
302 LIMIT 1";
303 if ($membershipId = CRM_Core_DAO::singleValueQuery($sql)) {
304
305 $ids['membership'] = $membershipId;
306 }
307
308 }
309
310 return $ids;
311}
312
313
314
315}