Merge pull request #10187 from eileenmcnaughton/prad
[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
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
1414INNER 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}