Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
7e9e8871 | 4 | | CiviCRM version 4.7 | |
6a488035 | 5 | +--------------------------------------------------------------------+ |
0f03f337 | 6 | | Copyright CiviCRM LLC (c) 2004-2017 | |
6a488035 TO |
7 | +--------------------------------------------------------------------+ |
8 | | This file is a part of CiviCRM. | | |
9 | | | | |
10 | | CiviCRM is free software; you can copy, modify, and distribute it | | |
11 | | under the terms of the GNU Affero General Public License | | |
12 | | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | | |
13 | | | | |
14 | | CiviCRM is distributed in the hope that it will be useful, but | | |
15 | | WITHOUT ANY WARRANTY; without even the implied warranty of | | |
16 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | | |
17 | | See the GNU Affero General Public License for more details. | | |
18 | | | | |
19 | | You should have received a copy of the GNU Affero General Public | | |
20 | | License and the CiviCRM Licensing Exception along | | |
21 | | with this program; if not, contact CiviCRM LLC | | |
22 | | at info[AT]civicrm[DOT]org. If you have questions about the | | |
23 | | GNU Affero General Public License or the licensing of CiviCRM, | | |
24 | | see the CiviCRM license FAQ at http://civicrm.org/licensing | | |
25 | +--------------------------------------------------------------------+ | |
d25dd0ee | 26 | */ |
6a488035 | 27 | |
914a49bf | 28 | use Civi\Payment\System; |
7758bd2b | 29 | use Civi\Payment\Exception\PaymentProcessorException; |
353ffa53 | 30 | |
6a488035 | 31 | /** |
3782df3e | 32 | * Class CRM_Core_Payment. |
6a488035 | 33 | * |
3782df3e | 34 | * This class is the main class for the payment processor subsystem. |
6a488035 | 35 | * |
3782df3e EM |
36 | * It is the parent class for payment processors. It also holds some IPN related functions |
37 | * that need to be moved. In particular handlePaymentMethod should be moved to a factory class. | |
6a488035 | 38 | */ |
6a488035 TO |
39 | abstract class CRM_Core_Payment { |
40 | ||
41 | /** | |
05c302ec EM |
42 | * Component - ie. event or contribute. |
43 | * | |
44 | * This is used for setting return urls. | |
45 | * | |
46 | * @var string | |
47 | */ | |
fd359255 EM |
48 | protected $_component; |
49 | ||
05c302ec EM |
50 | /** |
51 | * How are we getting billing information. | |
52 | * | |
53 | * We are trying to completely deprecate these parameters. | |
6a488035 TO |
54 | * |
55 | * FORM - we collect it on the same page | |
56 | * BUTTON - the processor collects it and sends it back to us via some protocol | |
57 | */ | |
7da04cde | 58 | const |
6a488035 TO |
59 | BILLING_MODE_FORM = 1, |
60 | BILLING_MODE_BUTTON = 2, | |
61 | BILLING_MODE_NOTIFY = 4; | |
62 | ||
63 | /** | |
100fef9d | 64 | * Which payment type(s) are we using? |
6a488035 TO |
65 | * |
66 | * credit card | |
67 | * direct debit | |
68 | * or both | |
43e5f0f6 | 69 | * @todo create option group - nb omnipay uses a 3rd type - transparent redirect cc |
6a488035 | 70 | */ |
7da04cde | 71 | const |
6a488035 TO |
72 | PAYMENT_TYPE_CREDIT_CARD = 1, |
73 | PAYMENT_TYPE_DIRECT_DEBIT = 2; | |
74 | ||
75 | /** | |
76 | * Subscription / Recurring payment Status | |
77 | * START, END | |
6a488035 | 78 | */ |
7da04cde | 79 | const |
6a488035 TO |
80 | RECURRING_PAYMENT_START = 'START', |
81 | RECURRING_PAYMENT_END = 'END'; | |
82 | ||
353ffa53 | 83 | protected $_paymentProcessor; |
6a488035 | 84 | |
ec022878 | 85 | /** |
e4555ff1 | 86 | * Base url of the calling form (offsite processors). |
ec022878 | 87 | * |
88 | * @var string | |
89 | */ | |
90 | protected $baseReturnUrl; | |
91 | ||
e4555ff1 | 92 | /** |
93 | * Return url upon success (offsite processors). | |
94 | * | |
95 | * @var string | |
96 | */ | |
97 | protected $successUrl; | |
98 | ||
99 | /** | |
100 | * Return url upon failure (offsite processors). | |
101 | * | |
102 | * @var string | |
103 | */ | |
104 | protected $cancelUrl; | |
105 | ||
1d1fee72 | 106 | /** |
107 | * The profile configured to show on the billing form. | |
108 | * | |
109 | * Currently only the pseudo-profile 'billing' is supported but hopefully in time we will take an id and | |
110 | * load that from the DB and the processor will be able to return a set of fields that combines it's minimum | |
111 | * requirements with the configured requirements. | |
112 | * | |
113 | * Currently only the pseudo-processor 'manual' or 'pay-later' uses this setting to return a 'curated' set | |
114 | * of fields. | |
115 | * | |
116 | * Note this change would probably include converting 'billing' to a reserved profile. | |
117 | * | |
118 | * @var int|string | |
119 | */ | |
120 | protected $billingProfile; | |
121 | ||
18135422 | 122 | /** |
123 | * Payment instrument ID. | |
124 | * | |
125 | * This is normally retrieved from the payment_processor table. | |
126 | * | |
127 | * @var int | |
128 | */ | |
129 | protected $paymentInstrumentID; | |
130 | ||
131 | /** | |
132 | * Is this a back office transaction. | |
133 | * | |
134 | * @var bool | |
135 | */ | |
136 | protected $backOffice = FALSE; | |
137 | ||
138 | /** | |
139 | * @return bool | |
140 | */ | |
141 | public function isBackOffice() { | |
142 | return $this->backOffice; | |
143 | } | |
144 | ||
145 | /** | |
146 | * Set back office property. | |
147 | * | |
148 | * @param bool $isBackOffice | |
149 | */ | |
150 | public function setBackOffice($isBackOffice) { | |
151 | $this->backOffice = $isBackOffice; | |
152 | } | |
153 | ||
154 | /** | |
155 | * Get payment instrument id. | |
156 | * | |
157 | * @return int | |
158 | */ | |
159 | public function getPaymentInstrumentID() { | |
160 | return $this->paymentInstrumentID ? $this->paymentInstrumentID : $this->_paymentProcessor['payment_instrument_id']; | |
161 | } | |
162 | ||
163 | /** | |
164 | * Set payment Instrument id. | |
165 | * | |
166 | * By default we actually ignore the form value. The manual processor takes it more seriously. | |
167 | * | |
168 | * @param int $paymentInstrumentID | |
169 | */ | |
170 | public function setPaymentInstrumentID($paymentInstrumentID) { | |
171 | $this->paymentInstrumentID = $this->_paymentProcessor['payment_instrument_id']; | |
172 | } | |
173 | ||
ec022878 | 174 | /** |
e4555ff1 | 175 | * Set base return path (offsite processors). |
176 | * | |
177 | * This is only useful with an internal civicrm form. | |
ec022878 | 178 | * |
179 | * @param string $url | |
e4555ff1 | 180 | * Internal civicrm path. |
ec022878 | 181 | */ |
182 | public function setBaseReturnUrl($url) { | |
183 | $this->baseReturnUrl = $url; | |
184 | } | |
185 | ||
e4555ff1 | 186 | /** |
187 | * Set success return URL (offsite processors). | |
188 | * | |
189 | * This overrides $baseReturnUrl | |
190 | * | |
191 | * @param string $url | |
192 | * Full url of site to return browser to upon success. | |
193 | */ | |
194 | public function setSuccessUrl($url) { | |
195 | $this->successUrl = $url; | |
196 | } | |
197 | ||
198 | /** | |
199 | * Set cancel return URL (offsite processors). | |
200 | * | |
201 | * This overrides $baseReturnUrl | |
202 | * | |
203 | * @param string $url | |
204 | * Full url of site to return browser to upon failure. | |
205 | */ | |
206 | public function setCancelUrl($url) { | |
207 | $this->cancelUrl = $url; | |
208 | } | |
209 | ||
1d1fee72 | 210 | /** |
211 | * Set the configured payment profile. | |
212 | * | |
213 | * @param int|string $value | |
214 | */ | |
215 | public function setBillingProfile($value) { | |
216 | $this->billingProfile = $value; | |
217 | } | |
218 | ||
dbbd55dc EM |
219 | /** |
220 | * Opportunity for the payment processor to override the entire form build. | |
221 | * | |
222 | * @param CRM_Core_Form $form | |
223 | * | |
224 | * @return bool | |
225 | * Should form building stop at this point? | |
226 | */ | |
227 | public function buildForm(&$form) { | |
31d31a05 | 228 | return FALSE; |
dbbd55dc EM |
229 | } |
230 | ||
e2bef985 | 231 | /** |
3782df3e EM |
232 | * Log payment notification message to forensic system log. |
233 | * | |
43e5f0f6 | 234 | * @todo move to factory class \Civi\Payment\System (or similar) |
3782df3e EM |
235 | * |
236 | * @param array $params | |
237 | * | |
e2bef985 | 238 | * @return mixed |
239 | */ | |
240 | public static function logPaymentNotification($params) { | |
414e3596 | 241 | $message = 'payment_notification '; |
e2bef985 | 242 | if (!empty($params['processor_name'])) { |
414e3596 | 243 | $message .= 'processor_name=' . $params['processor_name']; |
e2bef985 | 244 | } |
245 | if (!empty($params['processor_id'])) { | |
246 | $message .= 'processor_id=' . $params['processor_id']; | |
247 | } | |
414e3596 | 248 | |
249 | $log = new CRM_Utils_SystemLogger(); | |
250 | $log->alert($message, $_REQUEST); | |
e2bef985 | 251 | } |
252 | ||
fbcb6fba | 253 | /** |
d09edf64 | 254 | * Check if capability is supported. |
3782df3e EM |
255 | * |
256 | * Capabilities have a one to one relationship with capability-related functions on this class. | |
257 | * | |
258 | * Payment processor classes should over-ride the capability-specific function rather than this one. | |
259 | * | |
6a0b768e TO |
260 | * @param string $capability |
261 | * E.g BackOffice, LiveMode, FutureRecurStartDate. | |
fbcb6fba EM |
262 | * |
263 | * @return bool | |
264 | */ | |
265 | public function supports($capability) { | |
266 | $function = 'supports' . ucfirst($capability); | |
267 | if (method_exists($this, $function)) { | |
268 | return $this->$function(); | |
269 | } | |
270 | return FALSE; | |
271 | } | |
272 | ||
273 | /** | |
3782df3e EM |
274 | * Are back office payments supported. |
275 | * | |
276 | * e.g paypal standard won't permit you to enter a credit card associated | |
277 | * with someone else's login. | |
278 | * The intention is to support off-site (other than paypal) & direct debit but that is not all working yet so to | |
279 | * reach a 'stable' point we disable. | |
280 | * | |
fbcb6fba EM |
281 | * @return bool |
282 | */ | |
d8ce0d68 | 283 | protected function supportsBackOffice() { |
9c39fb25 EM |
284 | if ($this->_paymentProcessor['billing_mode'] == 4 || $this->_paymentProcessor['payment_type'] != 1) { |
285 | return FALSE; | |
286 | } | |
287 | else { | |
288 | return TRUE; | |
289 | } | |
fbcb6fba EM |
290 | } |
291 | ||
75ead8de EM |
292 | /** |
293 | * Can more than one transaction be processed at once? | |
294 | * | |
295 | * In general processors that process payment by server to server communication support this while others do not. | |
296 | * | |
297 | * In future we are likely to hit an issue where this depends on whether a token already exists. | |
298 | * | |
299 | * @return bool | |
300 | */ | |
301 | protected function supportsMultipleConcurrentPayments() { | |
302 | if ($this->_paymentProcessor['billing_mode'] == 4 || $this->_paymentProcessor['payment_type'] != 1) { | |
303 | return FALSE; | |
304 | } | |
305 | else { | |
306 | return TRUE; | |
307 | } | |
308 | } | |
309 | ||
fbcb6fba | 310 | /** |
3782df3e EM |
311 | * Are live payments supported - e.g dummy doesn't support this. |
312 | * | |
fbcb6fba EM |
313 | * @return bool |
314 | */ | |
d8ce0d68 | 315 | protected function supportsLiveMode() { |
fbcb6fba EM |
316 | return TRUE; |
317 | } | |
318 | ||
52767de0 | 319 | /** |
d09edf64 | 320 | * Are test payments supported. |
3782df3e | 321 | * |
52767de0 EM |
322 | * @return bool |
323 | */ | |
324 | protected function supportsTestMode() { | |
325 | return TRUE; | |
326 | } | |
327 | ||
fbcb6fba | 328 | /** |
d09edf64 | 329 | * Should the first payment date be configurable when setting up back office recurring payments. |
3782df3e | 330 | * |
fbcb6fba | 331 | * We set this to false for historical consistency but in fact most new processors use tokens for recurring and can support this |
3782df3e | 332 | * |
fbcb6fba EM |
333 | * @return bool |
334 | */ | |
d8ce0d68 | 335 | protected function supportsFutureRecurStartDate() { |
fbcb6fba EM |
336 | return FALSE; |
337 | } | |
338 | ||
1e483eb5 EM |
339 | /** |
340 | * Does this processor support cancelling recurring contributions through code. | |
341 | * | |
3e473c0b | 342 | * If the processor returns true it must be possible to take action from within CiviCRM |
343 | * that will result in no further payments being processed. In the case of token processors (e.g | |
344 | * IATS, eWay) updating the contribution_recur table is probably sufficient. | |
345 | * | |
1e483eb5 EM |
346 | * @return bool |
347 | */ | |
348 | protected function supportsCancelRecurring() { | |
349 | return method_exists(CRM_Utils_System::getClassName($this), 'cancelSubscription'); | |
350 | } | |
351 | ||
3910048c EM |
352 | /** |
353 | * Does this processor support pre-approval. | |
354 | * | |
355 | * This would generally look like a redirect to enter credentials which can then be used in a later payment call. | |
356 | * | |
357 | * Currently Paypal express supports this, with a redirect to paypal after the 'Main' form is submitted in the | |
358 | * contribution page. This token can then be processed at the confirm phase. Although this flow 'looks' like the | |
359 | * 'notify' flow a key difference is that in the notify flow they don't have to return but in this flow they do. | |
360 | * | |
361 | * @return bool | |
362 | */ | |
363 | protected function supportsPreApproval() { | |
364 | return FALSE; | |
365 | } | |
366 | ||
677fe56c EM |
367 | /** |
368 | * Can recurring contributions be set against pledges. | |
369 | * | |
370 | * In practice all processors that use the baseIPN function to finish transactions or | |
371 | * call the completetransaction api support this by looking up previous contributions in the | |
372 | * series and, if there is a prior contribution against a pledge, and the pledge is not complete, | |
373 | * adding the new payment to the pledge. | |
374 | * | |
375 | * However, only enabling for processors it has been tested against. | |
376 | * | |
377 | * @return bool | |
378 | */ | |
379 | protected function supportsRecurContributionsForPledges() { | |
380 | return FALSE; | |
381 | } | |
382 | ||
3910048c EM |
383 | /** |
384 | * Function to action pre-approval if supported | |
385 | * | |
386 | * @param array $params | |
387 | * Parameters from the form | |
3910048c EM |
388 | * |
389 | * This function returns an array which should contain | |
390 | * - pre_approval_parameters (this will be stored on the calling form & available later) | |
391 | * - redirect_url (if set the browser will be redirected to this. | |
392 | */ | |
677730bd | 393 | public function doPreApproval(&$params) {} |
3910048c | 394 | |
3105efd2 EM |
395 | /** |
396 | * Get any details that may be available to the payment processor due to an approval process having happened. | |
397 | * | |
398 | * In some cases the browser is redirected to enter details on a processor site. Some details may be available as a | |
399 | * result. | |
400 | * | |
401 | * @param array $storedDetails | |
402 | * | |
403 | * @return array | |
404 | */ | |
405 | public function getPreApprovalDetails($storedDetails) { | |
406 | return array(); | |
407 | } | |
408 | ||
6a488035 | 409 | /** |
3782df3e EM |
410 | * Default payment instrument validation. |
411 | * | |
a479fe60 | 412 | * Implement the usual Luhn algorithm via a static function in the CRM_Core_Payment_Form if it's a credit card |
3782df3e EM |
413 | * Not a static function, because I need to check for payment_type. |
414 | * | |
415 | * @param array $values | |
416 | * @param array $errors | |
a479fe60 | 417 | */ |
418 | public function validatePaymentInstrument($values, &$errors) { | |
c319039f | 419 | CRM_Core_Form::validateMandatoryFields($this->getMandatoryFields(), $values, $errors); |
a479fe60 | 420 | if ($this->_paymentProcessor['payment_type'] == 1) { |
06051ca4 | 421 | CRM_Core_Payment_Form::validateCreditCard($values, $errors, $this->_paymentProcessor['id']); |
a479fe60 | 422 | } |
423 | } | |
424 | ||
80bcd255 EM |
425 | /** |
426 | * Getter for the payment processor. | |
427 | * | |
428 | * The payment processor array is based on the civicrm_payment_processor table entry. | |
429 | * | |
430 | * @return array | |
431 | * Payment processor array. | |
432 | */ | |
433 | public function getPaymentProcessor() { | |
434 | return $this->_paymentProcessor; | |
435 | } | |
436 | ||
437 | /** | |
438 | * Setter for the payment processor. | |
439 | * | |
440 | * @param array $processor | |
441 | */ | |
442 | public function setPaymentProcessor($processor) { | |
443 | $this->_paymentProcessor = $processor; | |
444 | } | |
445 | ||
6a488035 | 446 | /** |
3782df3e EM |
447 | * Setter for the payment form that wants to use the processor. |
448 | * | |
43e5f0f6 | 449 | * @deprecated |
3782df3e | 450 | * |
ac32ed13 | 451 | * @param CRM_Core_Form $paymentForm |
6a488035 | 452 | */ |
00be9182 | 453 | public function setForm(&$paymentForm) { |
6a488035 TO |
454 | $this->_paymentForm = $paymentForm; |
455 | } | |
456 | ||
457 | /** | |
d09edf64 | 458 | * Getter for payment form that is using the processor. |
43e5f0f6 | 459 | * @deprecated |
16b10e64 CW |
460 | * @return CRM_Core_Form |
461 | * A form object | |
6a488035 | 462 | */ |
00be9182 | 463 | public function getForm() { |
6a488035 TO |
464 | return $this->_paymentForm; |
465 | } | |
466 | ||
6841f76b | 467 | /** |
468 | * Get help text information (help, description, etc.) about this payment, | |
469 | * to display to the user. | |
470 | * | |
471 | * @param string $context | |
472 | * Context of the text. | |
473 | * Only explicitly supported contexts are handled without error. | |
474 | * Currently supported: | |
475 | * - contributionPageRecurringHelp (params: is_recur_installments, is_email_receipt) | |
476 | * | |
477 | * @param array $params | |
478 | * Parameters for the field, context specific. | |
479 | * | |
480 | * @return string | |
481 | */ | |
482 | public function getText($context, $params) { | |
483 | // I have deliberately added a noisy fail here. | |
484 | // The function is intended to be extendable, but not by changes | |
485 | // not documented clearly above. | |
486 | switch ($context) { | |
487 | case 'contributionPageRecurringHelp': | |
488 | // require exactly two parameters | |
489 | if (array_keys($params) == array('is_recur_installments', 'is_email_receipt')) { | |
490 | $gotText = ts('Your recurring contribution will be processed automatically.'); | |
491 | if ($params['is_recur_installments']) { | |
492 | $gotText .= ts(' You can specify the number of installments, or you can leave the number of installments blank if you want to make an open-ended commitment. In either case, you can choose to cancel at any time.'); | |
493 | } | |
494 | if ($params['is_email_receipt']) { | |
495 | $gotText .= ts(' You will receive an email receipt for each recurring contribution.'); | |
496 | } | |
497 | } | |
498 | break; | |
499 | } | |
500 | return $gotText; | |
501 | } | |
502 | ||
6a488035 | 503 | /** |
d09edf64 | 504 | * Getter for accessing member vars. |
6c99ada1 | 505 | * |
43e5f0f6 | 506 | * @todo believe this is unused |
6c99ada1 | 507 | * |
100fef9d | 508 | * @param string $name |
dc913073 EM |
509 | * |
510 | * @return null | |
6a488035 | 511 | */ |
00be9182 | 512 | public function getVar($name) { |
6a488035 TO |
513 | return isset($this->$name) ? $this->$name : NULL; |
514 | } | |
515 | ||
dc913073 | 516 | /** |
d09edf64 | 517 | * Get name for the payment information type. |
43e5f0f6 | 518 | * @todo - use option group + name field (like Omnipay does) |
dc913073 EM |
519 | * @return string |
520 | */ | |
521 | public function getPaymentTypeName() { | |
459091e1 | 522 | return $this->_paymentProcessor['payment_type'] == 1 ? 'credit_card' : 'direct_debit'; |
dc913073 EM |
523 | } |
524 | ||
525 | /** | |
d09edf64 | 526 | * Get label for the payment information type. |
43e5f0f6 | 527 | * @todo - use option group + labels (like Omnipay does) |
dc913073 EM |
528 | * @return string |
529 | */ | |
530 | public function getPaymentTypeLabel() { | |
459091e1 | 531 | return $this->_paymentProcessor['payment_type'] == 1 ? 'Credit Card' : 'Direct Debit'; |
dc913073 EM |
532 | } |
533 | ||
44b6505d | 534 | /** |
d09edf64 | 535 | * Get array of fields that should be displayed on the payment form. |
44b6505d EM |
536 | * @todo make payment type an option value & use it in the function name - currently on debit & credit card work |
537 | * @return array | |
538 | * @throws CiviCRM_API3_Exception | |
539 | */ | |
540 | public function getPaymentFormFields() { | |
dc913073 | 541 | if ($this->_paymentProcessor['billing_mode'] == 4) { |
44b6505d EM |
542 | return array(); |
543 | } | |
544 | return $this->_paymentProcessor['payment_type'] == 1 ? $this->getCreditCardFormFields() : $this->getDirectDebitFormFields(); | |
545 | } | |
546 | ||
0c6c47a5 | 547 | /** |
548 | * Get an array of the fields that can be edited on the recurring contribution. | |
549 | * | |
550 | * Some payment processors support editing the amount and other scheduling details of recurring payments, especially | |
551 | * those which use tokens. Others are fixed. This function allows the processor to return an array of the fields that | |
552 | * can be updated from the contribution recur edit screen. | |
553 | * | |
554 | * The fields are likely to be a subset of these | |
555 | * - 'amount', | |
556 | * - 'installments', | |
557 | * - 'frequency_interval', | |
558 | * - 'frequency_unit', | |
559 | * - 'cycle_day', | |
560 | * - 'next_sched_contribution_date', | |
561 | * - 'end_date', | |
562 | * - 'failure_retry_day', | |
563 | * | |
564 | * The form does not restrict which fields from the contribution_recur table can be added (although if the html_type | |
565 | * metadata is not defined in the xml for the field it will cause an error. | |
566 | * | |
567 | * Open question - would it make sense to return membership_id in this - which is sometimes editable and is on that | |
568 | * form (UpdateSubscription). | |
569 | * | |
570 | * @return array | |
571 | */ | |
572 | public function getEditableRecurringScheduleFields() { | |
573 | if (method_exists($this, 'changeSubscriptionAmount')) { | |
574 | return array('amount'); | |
575 | } | |
576 | } | |
577 | ||
578 | /** | |
579 | * Get the help text to present on the recurring update page. | |
580 | * | |
581 | * This should reflect what can or cannot be edited. | |
582 | * | |
583 | * @return string | |
584 | */ | |
585 | public function getRecurringScheduleUpdateHelpText() { | |
586 | if (!in_array('amount', $this->getEditableRecurringScheduleFields())) { | |
587 | return ts('Updates made using this form will change the recurring contribution information stored in your CiviCRM database, but will NOT be sent to the payment processor. You must enter the same changes using the payment processor web site.'); | |
588 | } | |
589 | return ts('Use this form to change the amount or number of installments for this recurring contribution. Changes will be automatically sent to the payment processor. You can not change the contribution frequency.'); | |
590 | } | |
591 | ||
c319039f | 592 | /** |
593 | * Get the metadata for all required fields. | |
594 | * | |
595 | * @return array; | |
596 | */ | |
597 | protected function getMandatoryFields() { | |
598 | $mandatoryFields = array(); | |
599 | foreach ($this->getAllFields() as $field_name => $field_spec) { | |
600 | if (!empty($field_spec['is_required'])) { | |
601 | $mandatoryFields[$field_name] = $field_spec; | |
602 | } | |
603 | } | |
604 | return $mandatoryFields; | |
605 | } | |
606 | ||
607 | /** | |
608 | * Get the metadata of all the fields configured for this processor. | |
609 | * | |
610 | * @return array | |
611 | */ | |
612 | protected function getAllFields() { | |
613 | $paymentFields = array_intersect_key($this->getPaymentFormFieldsMetadata(), array_flip($this->getPaymentFormFields())); | |
614 | $billingFields = array_intersect_key($this->getBillingAddressFieldsMetadata(), array_flip($this->getBillingAddressFields())); | |
615 | return array_merge($paymentFields, $billingFields); | |
616 | } | |
44b6505d | 617 | /** |
d09edf64 | 618 | * Get array of fields that should be displayed on the payment form for credit cards. |
dc913073 | 619 | * |
44b6505d EM |
620 | * @return array |
621 | */ | |
622 | protected function getCreditCardFormFields() { | |
623 | return array( | |
624 | 'credit_card_type', | |
625 | 'credit_card_number', | |
626 | 'cvv2', | |
627 | 'credit_card_exp_date', | |
628 | ); | |
629 | } | |
630 | ||
631 | /** | |
d09edf64 | 632 | * Get array of fields that should be displayed on the payment form for direct debits. |
dc913073 | 633 | * |
44b6505d EM |
634 | * @return array |
635 | */ | |
636 | protected function getDirectDebitFormFields() { | |
637 | return array( | |
638 | 'account_holder', | |
639 | 'bank_account_number', | |
640 | 'bank_identification_number', | |
641 | 'bank_name', | |
642 | ); | |
643 | } | |
644 | ||
dc913073 | 645 | /** |
d09edf64 | 646 | * Return an array of all the details about the fields potentially required for payment fields. |
3782df3e | 647 | * |
dc913073 EM |
648 | * Only those determined by getPaymentFormFields will actually be assigned to the form |
649 | * | |
a6c01b45 CW |
650 | * @return array |
651 | * field metadata | |
dc913073 EM |
652 | */ |
653 | public function getPaymentFormFieldsMetadata() { | |
654 | //@todo convert credit card type into an option value | |
655 | $creditCardType = array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::creditCard(); | |
656 | return array( | |
657 | 'credit_card_number' => array( | |
658 | 'htmlType' => 'text', | |
659 | 'name' => 'credit_card_number', | |
660 | 'title' => ts('Card Number'), | |
661 | 'cc_field' => TRUE, | |
662 | 'attributes' => array( | |
663 | 'size' => 20, | |
664 | 'maxlength' => 20, | |
21dfd5f5 | 665 | 'autocomplete' => 'off', |
f803aacb | 666 | 'class' => 'creditcard', |
dc913073 EM |
667 | ), |
668 | 'is_required' => TRUE, | |
669 | ), | |
670 | 'cvv2' => array( | |
671 | 'htmlType' => 'text', | |
672 | 'name' => 'cvv2', | |
673 | 'title' => ts('Security Code'), | |
674 | 'cc_field' => TRUE, | |
675 | 'attributes' => array( | |
676 | 'size' => 5, | |
677 | 'maxlength' => 10, | |
21dfd5f5 | 678 | 'autocomplete' => 'off', |
dc913073 | 679 | ), |
d356cdeb | 680 | 'is_required' => Civi::settings()->get('cvv_backoffice_required'), |
dc913073 EM |
681 | 'rules' => array( |
682 | array( | |
683 | 'rule_message' => ts('Please enter a valid value for your card security code. This is usually the last 3-4 digits on the card\'s signature panel.'), | |
684 | 'rule_name' => 'integer', | |
685 | 'rule_parameters' => NULL, | |
7c550ca0 | 686 | ), |
353ffa53 | 687 | ), |
dc913073 EM |
688 | ), |
689 | 'credit_card_exp_date' => array( | |
690 | 'htmlType' => 'date', | |
691 | 'name' => 'credit_card_exp_date', | |
692 | 'title' => ts('Expiration Date'), | |
693 | 'cc_field' => TRUE, | |
694 | 'attributes' => CRM_Core_SelectValues::date('creditCard'), | |
695 | 'is_required' => TRUE, | |
696 | 'rules' => array( | |
697 | array( | |
698 | 'rule_message' => ts('Card expiration date cannot be a past date.'), | |
699 | 'rule_name' => 'currentDate', | |
700 | 'rule_parameters' => TRUE, | |
7c550ca0 | 701 | ), |
353ffa53 | 702 | ), |
dc913073 EM |
703 | ), |
704 | 'credit_card_type' => array( | |
705 | 'htmlType' => 'select', | |
706 | 'name' => 'credit_card_type', | |
707 | 'title' => ts('Card Type'), | |
708 | 'cc_field' => TRUE, | |
709 | 'attributes' => $creditCardType, | |
710 | 'is_required' => FALSE, | |
711 | ), | |
712 | 'account_holder' => array( | |
713 | 'htmlType' => 'text', | |
714 | 'name' => 'account_holder', | |
715 | 'title' => ts('Account Holder'), | |
716 | 'cc_field' => TRUE, | |
717 | 'attributes' => array( | |
718 | 'size' => 20, | |
719 | 'maxlength' => 34, | |
21dfd5f5 | 720 | 'autocomplete' => 'on', |
dc913073 EM |
721 | ), |
722 | 'is_required' => TRUE, | |
723 | ), | |
724 | //e.g. IBAN can have maxlength of 34 digits | |
725 | 'bank_account_number' => array( | |
726 | 'htmlType' => 'text', | |
727 | 'name' => 'bank_account_number', | |
728 | 'title' => ts('Bank Account Number'), | |
729 | 'cc_field' => TRUE, | |
730 | 'attributes' => array( | |
731 | 'size' => 20, | |
732 | 'maxlength' => 34, | |
21dfd5f5 | 733 | 'autocomplete' => 'off', |
dc913073 EM |
734 | ), |
735 | 'rules' => array( | |
736 | array( | |
737 | 'rule_message' => ts('Please enter a valid Bank Identification Number (value must not contain punctuation characters).'), | |
738 | 'rule_name' => 'nopunctuation', | |
739 | 'rule_parameters' => NULL, | |
7c550ca0 | 740 | ), |
353ffa53 | 741 | ), |
dc913073 EM |
742 | 'is_required' => TRUE, |
743 | ), | |
744 | //e.g. SWIFT-BIC can have maxlength of 11 digits | |
745 | 'bank_identification_number' => array( | |
746 | 'htmlType' => 'text', | |
747 | 'name' => 'bank_identification_number', | |
748 | 'title' => ts('Bank Identification Number'), | |
749 | 'cc_field' => TRUE, | |
750 | 'attributes' => array( | |
751 | 'size' => 20, | |
752 | 'maxlength' => 11, | |
21dfd5f5 | 753 | 'autocomplete' => 'off', |
dc913073 EM |
754 | ), |
755 | 'is_required' => TRUE, | |
756 | 'rules' => array( | |
757 | array( | |
758 | 'rule_message' => ts('Please enter a valid Bank Identification Number (value must not contain punctuation characters).'), | |
759 | 'rule_name' => 'nopunctuation', | |
760 | 'rule_parameters' => NULL, | |
7c550ca0 | 761 | ), |
353ffa53 | 762 | ), |
dc913073 EM |
763 | ), |
764 | 'bank_name' => array( | |
765 | 'htmlType' => 'text', | |
766 | 'name' => 'bank_name', | |
767 | 'title' => ts('Bank Name'), | |
768 | 'cc_field' => TRUE, | |
769 | 'attributes' => array( | |
770 | 'size' => 20, | |
771 | 'maxlength' => 64, | |
21dfd5f5 | 772 | 'autocomplete' => 'off', |
dc913073 EM |
773 | ), |
774 | 'is_required' => TRUE, | |
775 | ||
21dfd5f5 | 776 | ), |
dc913073 EM |
777 | ); |
778 | } | |
44b6505d | 779 | |
3310ab71 | 780 | /** |
781 | * Get billing fields required for this processor. | |
782 | * | |
783 | * We apply the existing default of returning fields only for payment processor type 1. Processors can override to | |
784 | * alter. | |
785 | * | |
786 | * @param int $billingLocationID | |
787 | * | |
788 | * @return array | |
789 | */ | |
b576d770 | 790 | public function getBillingAddressFields($billingLocationID = NULL) { |
791 | if (!$billingLocationID) { | |
792 | // Note that although the billing id is passed around the forms the idea that it would be anything other than | |
793 | // the result of the function below doesn't seem to have eventuated. | |
794 | // So taking this as a param is possibly something to be removed in favour of the standard default. | |
795 | $billingLocationID = CRM_Core_BAO_LocationType::getBilling(); | |
796 | } | |
3310ab71 | 797 | if ($this->_paymentProcessor['billing_mode'] != 1 && $this->_paymentProcessor['billing_mode'] != 3) { |
798 | return array(); | |
799 | } | |
800 | return array( | |
801 | 'first_name' => 'billing_first_name', | |
802 | 'middle_name' => 'billing_middle_name', | |
803 | 'last_name' => 'billing_last_name', | |
804 | 'street_address' => "billing_street_address-{$billingLocationID}", | |
805 | 'city' => "billing_city-{$billingLocationID}", | |
806 | 'country' => "billing_country_id-{$billingLocationID}", | |
807 | 'state_province' => "billing_state_province_id-{$billingLocationID}", | |
808 | 'postal_code' => "billing_postal_code-{$billingLocationID}", | |
809 | ); | |
810 | } | |
811 | ||
812 | /** | |
813 | * Get form metadata for billing address fields. | |
814 | * | |
815 | * @param int $billingLocationID | |
816 | * | |
817 | * @return array | |
818 | * Array of metadata for address fields. | |
819 | */ | |
b576d770 | 820 | public function getBillingAddressFieldsMetadata($billingLocationID = NULL) { |
821 | if (!$billingLocationID) { | |
822 | // Note that although the billing id is passed around the forms the idea that it would be anything other than | |
823 | // the result of the function below doesn't seem to have eventuated. | |
824 | // So taking this as a param is possibly something to be removed in favour of the standard default. | |
825 | $billingLocationID = CRM_Core_BAO_LocationType::getBilling(); | |
826 | } | |
3310ab71 | 827 | $metadata = array(); |
828 | $metadata['billing_first_name'] = array( | |
829 | 'htmlType' => 'text', | |
830 | 'name' => 'billing_first_name', | |
831 | 'title' => ts('Billing First Name'), | |
832 | 'cc_field' => TRUE, | |
833 | 'attributes' => array( | |
834 | 'size' => 30, | |
835 | 'maxlength' => 60, | |
836 | 'autocomplete' => 'off', | |
837 | ), | |
838 | 'is_required' => TRUE, | |
839 | ); | |
840 | ||
841 | $metadata['billing_middle_name'] = array( | |
842 | 'htmlType' => 'text', | |
843 | 'name' => 'billing_middle_name', | |
844 | 'title' => ts('Billing Middle Name'), | |
845 | 'cc_field' => TRUE, | |
846 | 'attributes' => array( | |
847 | 'size' => 30, | |
848 | 'maxlength' => 60, | |
849 | 'autocomplete' => 'off', | |
850 | ), | |
851 | 'is_required' => FALSE, | |
852 | ); | |
853 | ||
854 | $metadata['billing_last_name'] = array( | |
855 | 'htmlType' => 'text', | |
856 | 'name' => 'billing_last_name', | |
857 | 'title' => ts('Billing Last Name'), | |
858 | 'cc_field' => TRUE, | |
859 | 'attributes' => array( | |
860 | 'size' => 30, | |
861 | 'maxlength' => 60, | |
862 | 'autocomplete' => 'off', | |
863 | ), | |
864 | 'is_required' => TRUE, | |
865 | ); | |
866 | ||
867 | $metadata["billing_street_address-{$billingLocationID}"] = array( | |
868 | 'htmlType' => 'text', | |
869 | 'name' => "billing_street_address-{$billingLocationID}", | |
870 | 'title' => ts('Street Address'), | |
871 | 'cc_field' => TRUE, | |
872 | 'attributes' => array( | |
873 | 'size' => 30, | |
874 | 'maxlength' => 60, | |
875 | 'autocomplete' => 'off', | |
876 | ), | |
877 | 'is_required' => TRUE, | |
878 | ); | |
879 | ||
880 | $metadata["billing_city-{$billingLocationID}"] = array( | |
881 | 'htmlType' => 'text', | |
882 | 'name' => "billing_city-{$billingLocationID}", | |
883 | 'title' => ts('City'), | |
884 | 'cc_field' => TRUE, | |
885 | 'attributes' => array( | |
886 | 'size' => 30, | |
887 | 'maxlength' => 60, | |
888 | 'autocomplete' => 'off', | |
889 | ), | |
890 | 'is_required' => TRUE, | |
891 | ); | |
892 | ||
893 | $metadata["billing_state_province_id-{$billingLocationID}"] = array( | |
894 | 'htmlType' => 'chainSelect', | |
895 | 'title' => ts('State/Province'), | |
896 | 'name' => "billing_state_province_id-{$billingLocationID}", | |
897 | 'cc_field' => TRUE, | |
898 | 'is_required' => TRUE, | |
899 | ); | |
900 | ||
901 | $metadata["billing_postal_code-{$billingLocationID}"] = array( | |
902 | 'htmlType' => 'text', | |
903 | 'name' => "billing_postal_code-{$billingLocationID}", | |
904 | 'title' => ts('Postal Code'), | |
905 | 'cc_field' => TRUE, | |
906 | 'attributes' => array( | |
907 | 'size' => 30, | |
908 | 'maxlength' => 60, | |
909 | 'autocomplete' => 'off', | |
910 | ), | |
911 | 'is_required' => TRUE, | |
912 | ); | |
913 | ||
914 | $metadata["billing_country_id-{$billingLocationID}"] = array( | |
915 | 'htmlType' => 'select', | |
916 | 'name' => "billing_country_id-{$billingLocationID}", | |
917 | 'title' => ts('Country'), | |
918 | 'cc_field' => TRUE, | |
919 | 'attributes' => array( | |
920 | '' => ts('- select -'), | |
921 | ) + CRM_Core_PseudoConstant::country(), | |
922 | 'is_required' => TRUE, | |
923 | ); | |
924 | return $metadata; | |
925 | } | |
926 | ||
aefd7f6b EM |
927 | /** |
928 | * Get base url dependent on component. | |
929 | * | |
ec022878 | 930 | * (or preferably set it using the setter function). |
931 | * | |
932 | * @return string | |
aefd7f6b EM |
933 | */ |
934 | protected function getBaseReturnUrl() { | |
ec022878 | 935 | if ($this->baseReturnUrl) { |
936 | return $this->baseReturnUrl; | |
937 | } | |
aefd7f6b EM |
938 | if ($this->_component == 'event') { |
939 | $baseURL = 'civicrm/event/register'; | |
940 | } | |
941 | else { | |
942 | $baseURL = 'civicrm/contribute/transact'; | |
943 | } | |
944 | return $baseURL; | |
945 | } | |
946 | ||
947 | /** | |
ddc4b5af | 948 | * Get url to return to after cancelled or failed transaction. |
aefd7f6b | 949 | * |
ddc4b5af | 950 | * @param string $qfKey |
951 | * @param int $participantID | |
aefd7f6b EM |
952 | * |
953 | * @return string cancel url | |
954 | */ | |
abfb35ee | 955 | public function getCancelUrl($qfKey, $participantID) { |
e4555ff1 | 956 | if (isset($this->cancelUrl)) { |
957 | return $this->cancelUrl; | |
958 | } | |
959 | ||
aefd7f6b EM |
960 | if ($this->_component == 'event') { |
961 | return CRM_Utils_System::url($this->getBaseReturnUrl(), array( | |
962 | 'reset' => 1, | |
963 | 'cc' => 'fail', | |
964 | 'participantId' => $participantID, | |
965 | ), | |
966 | TRUE, NULL, FALSE | |
967 | ); | |
968 | } | |
969 | ||
970 | return CRM_Utils_System::url($this->getBaseReturnUrl(), array( | |
971 | '_qf_Main_display' => 1, | |
972 | 'qfKey' => $qfKey, | |
973 | 'cancel' => 1, | |
974 | ), | |
975 | TRUE, NULL, FALSE | |
976 | ); | |
977 | } | |
978 | ||
979 | /** | |
49c882a3 | 980 | * Get URL to return the browser to on success. |
aefd7f6b EM |
981 | * |
982 | * @param $qfKey | |
983 | * | |
984 | * @return string | |
985 | */ | |
986 | protected function getReturnSuccessUrl($qfKey) { | |
e4555ff1 | 987 | if (isset($this->successUrl)) { |
988 | return $this->successUrl; | |
989 | } | |
990 | ||
aefd7f6b EM |
991 | return CRM_Utils_System::url($this->getBaseReturnUrl(), array( |
992 | '_qf_ThankYou_display' => 1, | |
05237b76 | 993 | 'qfKey' => $qfKey, |
aefd7f6b EM |
994 | ), |
995 | TRUE, NULL, FALSE | |
996 | ); | |
997 | } | |
998 | ||
49c882a3 EM |
999 | /** |
1000 | * Get URL to return the browser to on failure. | |
1001 | * | |
1002 | * @param string $key | |
1003 | * @param int $participantID | |
1004 | * @param int $eventID | |
1005 | * | |
1006 | * @return string | |
1007 | * URL for a failing transactor to be redirected to. | |
1008 | */ | |
1009 | protected function getReturnFailUrl($key, $participantID = NULL, $eventID = NULL) { | |
e4555ff1 | 1010 | if (isset($this->cancelUrl)) { |
1011 | return $this->cancelUrl; | |
1012 | } | |
1013 | ||
6109d8df | 1014 | $test = $this->_is_test ? '&action=preview' : ''; |
49c882a3 EM |
1015 | if ($this->_component == "event") { |
1016 | return CRM_Utils_System::url('civicrm/event/register', | |
1017 | "reset=1&cc=fail&participantId={$participantID}&id={$eventID}{$test}&qfKey={$key}", | |
1018 | FALSE, NULL, FALSE | |
1019 | ); | |
1020 | } | |
1021 | else { | |
1022 | return CRM_Utils_System::url('civicrm/contribute/transact', | |
1023 | "_qf_Main_display=1&cancel=1&qfKey={$key}{$test}", | |
1024 | FALSE, NULL, FALSE | |
1025 | ); | |
1026 | } | |
1027 | } | |
1028 | ||
1029 | /** | |
1030 | * Get URl for when the back button is pressed. | |
1031 | * | |
1032 | * @param $qfKey | |
1033 | * | |
1034 | * @return string url | |
1035 | */ | |
1036 | protected function getGoBackUrl($qfKey) { | |
1037 | return CRM_Utils_System::url($this->getBaseReturnUrl(), array( | |
1038 | '_qf_Confirm_display' => 'true', | |
6109d8df | 1039 | 'qfKey' => $qfKey, |
49c882a3 EM |
1040 | ), |
1041 | TRUE, NULL, FALSE | |
1042 | ); | |
1043 | } | |
1044 | ||
1045 | /** | |
1046 | * Get the notify (aka ipn, web hook or silent post) url. | |
1047 | * | |
1048 | * If there is no '.' in it we assume that we are dealing with localhost or | |
1049 | * similar and it is unreachable from the web & hence invalid. | |
1050 | * | |
1051 | * @return string | |
1052 | * URL to notify outcome of transaction. | |
1053 | */ | |
1054 | protected function getNotifyUrl() { | |
1055 | $url = CRM_Utils_System::url( | |
1056 | 'civicrm/payment/ipn/' . $this->_paymentProcessor['id'], | |
068fb0de | 1057 | array(), |
ddc4b5af | 1058 | TRUE, |
1059 | NULL, | |
1060 | FALSE | |
49c882a3 EM |
1061 | ); |
1062 | return (stristr($url, '.')) ? $url : ''; | |
1063 | } | |
1064 | ||
6a488035 | 1065 | /** |
8319cf11 EM |
1066 | * Calling this from outside the payment subsystem is deprecated - use doPayment. |
1067 | * | |
1068 | * Does a server to server payment transaction. | |
1069 | * | |
6a0b768e TO |
1070 | * @param array $params |
1071 | * Assoc array of input parameters for this transaction. | |
6a488035 | 1072 | * |
a6c01b45 | 1073 | * @return array |
863fadaa | 1074 | * the result in an nice formatted array (or an error object - but throwing exceptions is preferred) |
6a488035 | 1075 | */ |
863fadaa EM |
1076 | protected function doDirectPayment(&$params) { |
1077 | return $params; | |
1078 | } | |
6a488035 | 1079 | |
c1cc3e0c | 1080 | /** |
6c99ada1 EM |
1081 | * Process payment - this function wraps around both doTransferPayment and doDirectPayment. |
1082 | * | |
1083 | * The function ensures an exception is thrown & moves some of this logic out of the form layer and makes the forms | |
1084 | * more agnostic. | |
c1cc3e0c | 1085 | * |
3910048c | 1086 | * Payment processors should set payment_status_id. This function adds some historical defaults ie. the |
7758bd2b EM |
1087 | * assumption that if a 'doDirectPayment' processors comes back it completed the transaction & in fact |
1088 | * doTransferCheckout would not traditionally come back. | |
1089 | * | |
1090 | * doDirectPayment does not do an immediate payment for Authorize.net or Paypal so the default is assumed | |
1091 | * to be Pending. | |
1092 | * | |
3910048c EM |
1093 | * Once this function is fully rolled out then it will be preferred for processors to throw exceptions than to |
1094 | * return Error objects | |
1095 | * | |
c1cc3e0c EM |
1096 | * @param array $params |
1097 | * | |
7758bd2b | 1098 | * @param string $component |
c1cc3e0c | 1099 | * |
a6c01b45 | 1100 | * @return array |
7758bd2b EM |
1101 | * Result array |
1102 | * | |
1103 | * @throws \Civi\Payment\Exception\PaymentProcessorException | |
c1cc3e0c | 1104 | */ |
8319cf11 | 1105 | public function doPayment(&$params, $component = 'contribute') { |
05c302ec | 1106 | $this->_component = $component; |
7758bd2b | 1107 | $statuses = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id'); |
c51070b5 | 1108 | |
1109 | // If we have a $0 amount, skip call to processor and set payment_status to Completed. | |
1110 | // Conceivably a processor might override this - perhaps for setting up a token - but we don't | |
1111 | // have an example of that at the mome. | |
1112 | if ($params['amount'] == 0) { | |
1113 | $result['payment_status_id'] = array_search('Completed', $statuses); | |
1114 | return $result; | |
1115 | } | |
1116 | ||
c1cc3e0c EM |
1117 | if ($this->_paymentProcessor['billing_mode'] == 4) { |
1118 | $result = $this->doTransferCheckout($params, $component); | |
7c85dc65 EM |
1119 | if (is_array($result) && !isset($result['payment_status_id'])) { |
1120 | $result['payment_status_id'] = array_search('Pending', $statuses); | |
7758bd2b | 1121 | } |
c1cc3e0c EM |
1122 | } |
1123 | else { | |
f8453bef | 1124 | $result = $this->doDirectPayment($params, $component); |
7c85dc65 | 1125 | if (is_array($result) && !isset($result['payment_status_id'])) { |
9dd58272 | 1126 | if (!empty($params['is_recur'])) { |
7758bd2b | 1127 | // See comment block. |
0816949d | 1128 | $result['payment_status_id'] = array_search('Pending', $statuses); |
7758bd2b EM |
1129 | } |
1130 | else { | |
7c85dc65 | 1131 | $result['payment_status_id'] = array_search('Completed', $statuses); |
7758bd2b EM |
1132 | } |
1133 | } | |
c1cc3e0c EM |
1134 | } |
1135 | if (is_a($result, 'CRM_Core_Error')) { | |
7758bd2b | 1136 | throw new PaymentProcessorException(CRM_Core_Error::getMessages($result)); |
c1cc3e0c | 1137 | } |
a9cf9972 | 1138 | return $result; |
c1cc3e0c EM |
1139 | } |
1140 | ||
9d2f24ee | 1141 | /** |
1142 | * Query payment processor for details about a transaction. | |
1143 | * | |
1144 | * @param array $params | |
1145 | * Array of parameters containing one of: | |
1146 | * - trxn_id Id of an individual transaction. | |
1147 | * - processor_id Id of a recurring contribution series as stored in the civicrm_contribution_recur table. | |
1148 | * | |
1149 | * @return array | |
1150 | * Extra parameters retrieved. | |
1151 | * Any parameters retrievable through this should be documented in the function comments at | |
1152 | * CRM_Core_Payment::doQuery. Currently: | |
1153 | * - fee_amount Amount of fee paid | |
1154 | */ | |
1155 | public function doQuery($params) { | |
1156 | return array(); | |
1157 | } | |
1158 | ||
6a488035 | 1159 | /** |
d09edf64 | 1160 | * This function checks to see if we have the right config values. |
6a488035 | 1161 | * |
a6c01b45 CW |
1162 | * @return string |
1163 | * the error message if any | |
6a488035 | 1164 | */ |
7c550ca0 | 1165 | abstract protected function checkConfig(); |
6a488035 | 1166 | |
a0ee3941 | 1167 | /** |
6c99ada1 EM |
1168 | * Redirect for paypal. |
1169 | * | |
1170 | * @todo move to paypal class or remove | |
1171 | * | |
a0ee3941 | 1172 | * @param $paymentProcessor |
6c99ada1 | 1173 | * |
a0ee3941 EM |
1174 | * @return bool |
1175 | */ | |
00be9182 | 1176 | public static function paypalRedirect(&$paymentProcessor) { |
6a488035 TO |
1177 | if (!$paymentProcessor) { |
1178 | return FALSE; | |
1179 | } | |
1180 | ||
1181 | if (isset($_GET['payment_date']) && | |
1182 | isset($_GET['merchant_return_link']) && | |
1183 | CRM_Utils_Array::value('payment_status', $_GET) == 'Completed' && | |
1184 | $paymentProcessor['payment_processor_type'] == "PayPal_Standard" | |
1185 | ) { | |
1186 | return TRUE; | |
1187 | } | |
1188 | ||
1189 | return FALSE; | |
1190 | } | |
1191 | ||
1192 | /** | |
6c99ada1 EM |
1193 | * Handle incoming payment notification. |
1194 | * | |
1195 | * IPNs, also called silent posts are notifications of payment outcomes or activity on an external site. | |
1196 | * | |
43e5f0f6 | 1197 | * @todo move to0 \Civi\Payment\System factory method |
6a488035 | 1198 | * Page callback for civicrm/payment/ipn |
6a488035 | 1199 | */ |
00be9182 | 1200 | public static function handleIPN() { |
6a488035 TO |
1201 | self::handlePaymentMethod( |
1202 | 'PaymentNotification', | |
1203 | array( | |
1204 | 'processor_name' => @$_GET['processor_name'], | |
42b90e8f | 1205 | 'processor_id' => @$_GET['processor_id'], |
6a488035 TO |
1206 | 'mode' => @$_GET['mode'], |
1207 | ) | |
1208 | ); | |
160c9df2 | 1209 | CRM_Utils_System::civiExit(); |
6a488035 TO |
1210 | } |
1211 | ||
1212 | /** | |
3782df3e EM |
1213 | * Payment callback handler. |
1214 | * | |
1215 | * The processor_name or processor_id is passed in. | |
43d1ae00 EM |
1216 | * Note that processor_id is more reliable as one site may have more than one instance of a |
1217 | * processor & ideally the processor will be validating the results | |
6a488035 TO |
1218 | * Load requested payment processor and call that processor's handle<$method> method |
1219 | * | |
3782df3e EM |
1220 | * @todo move to \Civi\Payment\System factory method |
1221 | * | |
1222 | * @param string $method | |
1223 | * 'PaymentNotification' or 'PaymentCron' | |
4691b077 | 1224 | * @param array $params |
23de1ac0 EM |
1225 | * |
1226 | * @throws \CRM_Core_Exception | |
1227 | * @throws \Exception | |
6a488035 | 1228 | */ |
00be9182 | 1229 | public static function handlePaymentMethod($method, $params = array()) { |
42b90e8f | 1230 | if (!isset($params['processor_id']) && !isset($params['processor_name'])) { |
020c48ef | 1231 | $q = explode('/', CRM_Utils_Array::value(CRM_Core_Config::singleton()->userFrameworkURLVar, $_GET, '')); |
4164f4e1 EM |
1232 | $lastParam = array_pop($q); |
1233 | if (is_numeric($lastParam)) { | |
1234 | $params['processor_id'] = $_GET['processor_id'] = $lastParam; | |
1235 | } | |
1236 | else { | |
068fb0de | 1237 | self::logPaymentNotification($params); |
0ac9fd52 | 1238 | throw new CRM_Core_Exception("Either 'processor_id' (recommended) or 'processor_name' (deprecated) is required for payment callback."); |
4164f4e1 | 1239 | } |
6a488035 | 1240 | } |
4164f4e1 | 1241 | |
e2bef985 | 1242 | self::logPaymentNotification($params); |
6a488035 | 1243 | |
42b90e8f CB |
1244 | $sql = "SELECT ppt.class_name, ppt.name as processor_name, pp.id AS processor_id |
1245 | FROM civicrm_payment_processor_type ppt | |
1246 | INNER JOIN civicrm_payment_processor pp | |
1247 | ON pp.payment_processor_type_id = ppt.id | |
9ff0c7a1 | 1248 | AND pp.is_active"; |
42b90e8f CB |
1249 | |
1250 | if (isset($params['processor_id'])) { | |
1251 | $sql .= " WHERE pp.id = %2"; | |
1252 | $args[2] = array($params['processor_id'], 'Integer'); | |
0ac9fd52 | 1253 | $notFound = ts("No active instances of payment processor %1 were found.", array(1 => $params['processor_id'])); |
42b90e8f CB |
1254 | } |
1255 | else { | |
9ff0c7a1 EM |
1256 | // This is called when processor_name is passed - passing processor_id instead is recommended. |
1257 | $sql .= " WHERE ppt.name = %2 AND pp.is_test = %1"; | |
6c99ada1 EM |
1258 | $args[1] = array( |
1259 | (CRM_Utils_Array::value('mode', $params) == 'test') ? 1 : 0, | |
1260 | 'Integer', | |
1261 | ); | |
42b90e8f | 1262 | $args[2] = array($params['processor_name'], 'String'); |
0ac9fd52 | 1263 | $notFound = ts("No active instances of payment processor '%1' were found.", array(1 => $params['processor_name'])); |
42b90e8f CB |
1264 | } |
1265 | ||
1266 | $dao = CRM_Core_DAO::executeQuery($sql, $args); | |
6a488035 | 1267 | |
3782df3e | 1268 | // Check whether we found anything at all. |
6a488035 | 1269 | if (!$dao->N) { |
3782df3e | 1270 | CRM_Core_Error::fatal($notFound); |
6a488035 TO |
1271 | } |
1272 | ||
1273 | $method = 'handle' . $method; | |
1274 | $extension_instance_found = FALSE; | |
1275 | ||
1276 | // In all likelihood, we'll just end up with the one instance returned here. But it's | |
1277 | // possible we may get more. Hence, iterate through all instances .. | |
1278 | ||
1279 | while ($dao->fetch()) { | |
5495e78a | 1280 | // Check pp is extension - is this still required - surely the singleton below handles it. |
6a488035 TO |
1281 | $ext = CRM_Extension_System::singleton()->getMapper(); |
1282 | if ($ext->isExtensionKey($dao->class_name)) { | |
6a488035 TO |
1283 | $paymentClass = $ext->keyToClass($dao->class_name, 'payment'); |
1284 | require_once $ext->classToPath($paymentClass); | |
1285 | } | |
6a488035 | 1286 | |
7a3b0ca3 | 1287 | $processorInstance = System::singleton()->getById($dao->processor_id); |
6a488035 TO |
1288 | |
1289 | // Should never be empty - we already established this processor_id exists and is active. | |
81ebda7b | 1290 | if (empty($processorInstance)) { |
6a488035 TO |
1291 | continue; |
1292 | } | |
1293 | ||
6a488035 TO |
1294 | // Does PP implement this method, and can we call it? |
1295 | if (!method_exists($processorInstance, $method) || | |
1296 | !is_callable(array($processorInstance, $method)) | |
1297 | ) { | |
43d1ae00 EM |
1298 | // on the off chance there is a double implementation of this processor we should keep looking for another |
1299 | // note that passing processor_id is more reliable & we should work to deprecate processor_name | |
1300 | continue; | |
6a488035 TO |
1301 | } |
1302 | ||
1303 | // Everything, it seems, is ok - execute pp callback handler | |
1304 | $processorInstance->$method(); | |
a5ef96f6 | 1305 | $extension_instance_found = TRUE; |
6a488035 TO |
1306 | } |
1307 | ||
4f99ca55 | 1308 | if (!$extension_instance_found) { |
0ac9fd52 CB |
1309 | $message = "No extension instances of the '%1' payment processor were found.<br />" . |
1310 | "%2 method is unsupported in legacy payment processors."; | |
1311 | CRM_Core_Error::fatal(ts($message, array(1 => $params['processor_name'], 2 => $method))); | |
2aa397bc | 1312 | } |
6a488035 TO |
1313 | } |
1314 | ||
1315 | /** | |
100fef9d | 1316 | * Check whether a method is present ( & supported ) by the payment processor object. |
6a488035 | 1317 | * |
1e483eb5 EM |
1318 | * @deprecated - use $paymentProcessor->supports(array('cancelRecurring'); |
1319 | * | |
6a0b768e TO |
1320 | * @param string $method |
1321 | * Method to check for. | |
6a488035 | 1322 | * |
7c550ca0 | 1323 | * @return bool |
6a488035 | 1324 | */ |
1524a007 | 1325 | public function isSupported($method) { |
6a488035 TO |
1326 | return method_exists(CRM_Utils_System::getClassName($this), $method); |
1327 | } | |
1328 | ||
1ba4a3aa EM |
1329 | /** |
1330 | * Some processors replace the form submit button with their own. | |
1331 | * | |
1332 | * Returning false here will leave the button off front end forms. | |
1333 | * | |
1334 | * At this stage there is zero cross-over between back-office processors and processors that suppress the submit. | |
1335 | */ | |
1336 | public function isSuppressSubmitButtons() { | |
1337 | return FALSE; | |
1338 | } | |
1339 | ||
d253aeb8 EM |
1340 | /** |
1341 | * Checks to see if invoice_id already exists in db. | |
1342 | * | |
1343 | * It's arguable if this belongs in the payment subsystem at all but since several processors implement it | |
1344 | * it is better to standardise to being here. | |
1345 | * | |
1346 | * @param int $invoiceId The ID to check. | |
1347 | * | |
1348 | * @param null $contributionID | |
1349 | * If a contribution exists pass in the contribution ID. | |
1350 | * | |
1351 | * @return bool | |
1352 | * True if invoice ID otherwise exists, else false | |
1353 | */ | |
1354 | protected function checkDupe($invoiceId, $contributionID = NULL) { | |
1355 | $contribution = new CRM_Contribute_DAO_Contribution(); | |
1356 | $contribution->invoice_id = $invoiceId; | |
1357 | if ($contributionID) { | |
1358 | $contribution->whereAdd("id <> $contributionID"); | |
1359 | } | |
1360 | return $contribution->find(); | |
1361 | } | |
1362 | ||
a0ee3941 | 1363 | /** |
3782df3e EM |
1364 | * Get url for users to manage this recurring contribution for this processor. |
1365 | * | |
100fef9d | 1366 | * @param int $entityID |
a0ee3941 EM |
1367 | * @param null $entity |
1368 | * @param string $action | |
1369 | * | |
1370 | * @return string | |
1371 | */ | |
00be9182 | 1372 | public function subscriptionURL($entityID = NULL, $entity = NULL, $action = 'cancel') { |
03cfff4c KW |
1373 | // Set URL |
1374 | switch ($action) { | |
2aa397bc | 1375 | case 'cancel': |
03cfff4c KW |
1376 | $url = 'civicrm/contribute/unsubscribe'; |
1377 | break; | |
2aa397bc TO |
1378 | |
1379 | case 'billing': | |
03cfff4c | 1380 | //in notify mode don't return the update billing url |
68acd6ae | 1381 | if (!$this->isSupported('updateSubscriptionBillingInfo')) { |
03cfff4c KW |
1382 | return NULL; |
1383 | } | |
68acd6ae | 1384 | $url = 'civicrm/contribute/updatebilling'; |
03cfff4c | 1385 | break; |
2aa397bc TO |
1386 | |
1387 | case 'update': | |
03cfff4c KW |
1388 | $url = 'civicrm/contribute/updaterecur'; |
1389 | break; | |
6a488035 TO |
1390 | } |
1391 | ||
b7e7f943 | 1392 | $userId = CRM_Core_Session::singleton()->get('userID'); |
353ffa53 | 1393 | $contactID = 0; |
03cfff4c | 1394 | $checksumValue = ''; |
353ffa53 | 1395 | $entityArg = ''; |
03cfff4c KW |
1396 | |
1397 | // Find related Contact | |
1398 | if ($entityID) { | |
1399 | switch ($entity) { | |
2aa397bc | 1400 | case 'membership': |
03cfff4c KW |
1401 | $contactID = CRM_Core_DAO::getFieldValue("CRM_Member_DAO_Membership", $entityID, "contact_id"); |
1402 | $entityArg = 'mid'; | |
1403 | break; | |
1404 | ||
2aa397bc | 1405 | case 'contribution': |
03cfff4c KW |
1406 | $contactID = CRM_Core_DAO::getFieldValue("CRM_Contribute_DAO_Contribution", $entityID, "contact_id"); |
1407 | $entityArg = 'coid'; | |
1408 | break; | |
1409 | ||
2aa397bc | 1410 | case 'recur': |
03cfff4c | 1411 | $sql = " |
6a488035 TO |
1412 | SELECT con.contact_id |
1413 | FROM civicrm_contribution_recur rec | |
1414 | INNER JOIN civicrm_contribution con ON ( con.contribution_recur_id = rec.id ) | |
1415 | WHERE rec.id = %1 | |
1416 | GROUP BY rec.id"; | |
03cfff4c KW |
1417 | $contactID = CRM_Core_DAO::singleValueQuery($sql, array(1 => array($entityID, 'Integer'))); |
1418 | $entityArg = 'crid'; | |
1419 | break; | |
6a488035 | 1420 | } |
6a488035 TO |
1421 | } |
1422 | ||
03cfff4c KW |
1423 | // Add entity arguments |
1424 | if ($entityArg != '') { | |
1425 | // Add checksum argument | |
1426 | if ($contactID != 0 && $userId != $contactID) { | |
1427 | $checksumValue = '&cs=' . CRM_Contact_BAO_Contact_Utils::generateChecksum($contactID, NULL, 'inf'); | |
1428 | } | |
1429 | return CRM_Utils_System::url($url, "reset=1&{$entityArg}={$entityID}{$checksumValue}", TRUE, NULL, FALSE, TRUE); | |
1430 | } | |
1431 | ||
1432 | // Else login URL | |
6a488035 TO |
1433 | if ($this->isSupported('accountLoginURL')) { |
1434 | return $this->accountLoginURL(); | |
1435 | } | |
03cfff4c KW |
1436 | |
1437 | // Else default | |
735d988f | 1438 | return isset($this->_paymentProcessor['url_recur']) ? $this->_paymentProcessor['url_recur'] : ''; |
6a488035 | 1439 | } |
96025800 | 1440 | |
efa36d6e | 1441 | /** |
1442 | * Get description of payment to pass to processor. | |
1443 | * | |
1444 | * This is often what people see in the interface so we want to get | |
1445 | * as much unique information in as possible within the field length (& presumably the early part of the field) | |
1446 | * | |
1447 | * People seeing these can be assumed to be advanced users so quantity of information probably trumps | |
1448 | * having field names to clarify | |
1449 | * | |
1450 | * @param array $params | |
1451 | * @param int $length | |
1452 | * | |
1453 | * @return string | |
1454 | */ | |
1455 | protected function getPaymentDescription($params, $length = 24) { | |
1456 | $parts = array('contactID', 'contributionID', 'description', 'billing_first_name', 'billing_last_name'); | |
1457 | $validParts = array(); | |
1458 | if (isset($params['description'])) { | |
1459 | $uninformativeStrings = array(ts('Online Event Registration: '), ts('Online Contribution: ')); | |
1460 | $params['description'] = str_replace($uninformativeStrings, '', $params['description']); | |
1461 | } | |
1462 | foreach ($parts as $part) { | |
1463 | if ((!empty($params[$part]))) { | |
1464 | $validParts[] = $params[$part]; | |
1465 | } | |
1466 | } | |
1467 | return substr(implode('-', $validParts), 0, $length); | |
1468 | } | |
1469 | ||
95974e8e DG |
1470 | /** |
1471 | * Checks if backoffice recurring edit is allowed | |
1472 | * | |
1473 | * @return bool | |
1474 | */ | |
1475 | public function supportsEditRecurringContribution() { | |
1476 | return FALSE; | |
1477 | } | |
1478 | ||
cd3bc162 | 1479 | /** |
1480 | * Should a receipt be sent out for a pending payment. | |
1481 | * | |
1482 | * e.g for traditional pay later & ones with a delayed settlement a pending receipt makes sense. | |
1483 | */ | |
1484 | public function isSendReceiptForPending() { | |
1485 | return FALSE; | |
1486 | } | |
1487 | ||
6a488035 | 1488 | } |