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