Commit | Line | Data |
---|---|---|
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 | ||
39 | define("MAX_FAILURES", 4) | |
40 | ||
33422de3 | 41 | class 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 | } |