Merge pull request #10634 from JMAConsulting/CRM-20673
[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
a55e39e9 63 /**
64 * Which payment type(s) are we using?
65 *
66 * credit card
67 * direct debit
68 * or both
69 * @todo create option group - nb omnipay uses a 3rd type - transparent redirect cc
70 */
71 const
72 PAYMENT_TYPE_CREDIT_CARD = 1,
73 PAYMENT_TYPE_DIRECT_DEBIT = 2;
74
6a488035
TO
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 ),
794d4fc0 777 'check_number' => array(
778 'htmlType' => 'text',
779 'name' => 'check_number',
780 'title' => ts('Check Number'),
781 'is_required' => FALSE,
782 'cc_field' => TRUE,
783 'attributes' => NULL,
784 ),
785 'pan_truncation' => array(
786 'htmlType' => 'text',
787 'name' => 'pan_truncation',
788 'title' => ts('Last 4 digits of the card'),
789 'is_required' => FALSE,
790 'cc_field' => TRUE,
791 'attributes' => array(
792 'size' => 4,
793 'maxlength' => 4,
a7f2d5fd 794 'minlength' => 4,
794d4fc0 795 'autocomplete' => 'off',
796 ),
a55e39e9 797 'rules' => array(
798 array(
799 'rule_message' => ts('Please enter valid last 4 digit card number.'),
800 'rule_name' => 'numeric',
801 'rule_parameters' => NULL,
802 ),
803 ),
794d4fc0 804 ),
dc913073
EM
805 );
806 }
44b6505d 807
3310ab71 808 /**
809 * Get billing fields required for this processor.
810 *
811 * We apply the existing default of returning fields only for payment processor type 1. Processors can override to
812 * alter.
813 *
814 * @param int $billingLocationID
815 *
816 * @return array
817 */
b576d770 818 public function getBillingAddressFields($billingLocationID = NULL) {
819 if (!$billingLocationID) {
820 // Note that although the billing id is passed around the forms the idea that it would be anything other than
821 // the result of the function below doesn't seem to have eventuated.
822 // So taking this as a param is possibly something to be removed in favour of the standard default.
823 $billingLocationID = CRM_Core_BAO_LocationType::getBilling();
824 }
3310ab71 825 if ($this->_paymentProcessor['billing_mode'] != 1 && $this->_paymentProcessor['billing_mode'] != 3) {
826 return array();
827 }
828 return array(
829 'first_name' => 'billing_first_name',
830 'middle_name' => 'billing_middle_name',
831 'last_name' => 'billing_last_name',
832 'street_address' => "billing_street_address-{$billingLocationID}",
833 'city' => "billing_city-{$billingLocationID}",
834 'country' => "billing_country_id-{$billingLocationID}",
835 'state_province' => "billing_state_province_id-{$billingLocationID}",
836 'postal_code' => "billing_postal_code-{$billingLocationID}",
837 );
838 }
839
840 /**
841 * Get form metadata for billing address fields.
842 *
843 * @param int $billingLocationID
844 *
845 * @return array
846 * Array of metadata for address fields.
847 */
b576d770 848 public function getBillingAddressFieldsMetadata($billingLocationID = NULL) {
849 if (!$billingLocationID) {
850 // Note that although the billing id is passed around the forms the idea that it would be anything other than
851 // the result of the function below doesn't seem to have eventuated.
852 // So taking this as a param is possibly something to be removed in favour of the standard default.
853 $billingLocationID = CRM_Core_BAO_LocationType::getBilling();
854 }
3310ab71 855 $metadata = array();
856 $metadata['billing_first_name'] = array(
857 'htmlType' => 'text',
858 'name' => 'billing_first_name',
859 'title' => ts('Billing First Name'),
860 'cc_field' => TRUE,
861 'attributes' => array(
862 'size' => 30,
863 'maxlength' => 60,
864 'autocomplete' => 'off',
865 ),
866 'is_required' => TRUE,
867 );
868
869 $metadata['billing_middle_name'] = array(
870 'htmlType' => 'text',
871 'name' => 'billing_middle_name',
872 'title' => ts('Billing Middle Name'),
873 'cc_field' => TRUE,
874 'attributes' => array(
875 'size' => 30,
876 'maxlength' => 60,
877 'autocomplete' => 'off',
878 ),
879 'is_required' => FALSE,
880 );
881
882 $metadata['billing_last_name'] = array(
883 'htmlType' => 'text',
884 'name' => 'billing_last_name',
885 'title' => ts('Billing Last Name'),
886 'cc_field' => TRUE,
887 'attributes' => array(
888 'size' => 30,
889 'maxlength' => 60,
890 'autocomplete' => 'off',
891 ),
892 'is_required' => TRUE,
893 );
894
895 $metadata["billing_street_address-{$billingLocationID}"] = array(
896 'htmlType' => 'text',
897 'name' => "billing_street_address-{$billingLocationID}",
898 'title' => ts('Street Address'),
899 'cc_field' => TRUE,
900 'attributes' => array(
901 'size' => 30,
902 'maxlength' => 60,
903 'autocomplete' => 'off',
904 ),
905 'is_required' => TRUE,
906 );
907
908 $metadata["billing_city-{$billingLocationID}"] = array(
909 'htmlType' => 'text',
910 'name' => "billing_city-{$billingLocationID}",
911 'title' => ts('City'),
912 'cc_field' => TRUE,
913 'attributes' => array(
914 'size' => 30,
915 'maxlength' => 60,
916 'autocomplete' => 'off',
917 ),
918 'is_required' => TRUE,
919 );
920
921 $metadata["billing_state_province_id-{$billingLocationID}"] = array(
922 'htmlType' => 'chainSelect',
923 'title' => ts('State/Province'),
924 'name' => "billing_state_province_id-{$billingLocationID}",
925 'cc_field' => TRUE,
926 'is_required' => TRUE,
927 );
928
929 $metadata["billing_postal_code-{$billingLocationID}"] = array(
930 'htmlType' => 'text',
931 'name' => "billing_postal_code-{$billingLocationID}",
932 'title' => ts('Postal Code'),
933 'cc_field' => TRUE,
934 'attributes' => array(
935 'size' => 30,
936 'maxlength' => 60,
937 'autocomplete' => 'off',
938 ),
939 'is_required' => TRUE,
940 );
941
942 $metadata["billing_country_id-{$billingLocationID}"] = array(
943 'htmlType' => 'select',
944 'name' => "billing_country_id-{$billingLocationID}",
945 'title' => ts('Country'),
946 'cc_field' => TRUE,
947 'attributes' => array(
948 '' => ts('- select -'),
949 ) + CRM_Core_PseudoConstant::country(),
950 'is_required' => TRUE,
951 );
952 return $metadata;
953 }
954
aefd7f6b
EM
955 /**
956 * Get base url dependent on component.
957 *
ec022878 958 * (or preferably set it using the setter function).
959 *
960 * @return string
aefd7f6b
EM
961 */
962 protected function getBaseReturnUrl() {
ec022878 963 if ($this->baseReturnUrl) {
964 return $this->baseReturnUrl;
965 }
aefd7f6b
EM
966 if ($this->_component == 'event') {
967 $baseURL = 'civicrm/event/register';
968 }
969 else {
970 $baseURL = 'civicrm/contribute/transact';
971 }
972 return $baseURL;
973 }
974
975 /**
ddc4b5af 976 * Get url to return to after cancelled or failed transaction.
aefd7f6b 977 *
ddc4b5af 978 * @param string $qfKey
979 * @param int $participantID
aefd7f6b
EM
980 *
981 * @return string cancel url
982 */
abfb35ee 983 public function getCancelUrl($qfKey, $participantID) {
e4555ff1 984 if (isset($this->cancelUrl)) {
985 return $this->cancelUrl;
986 }
987
aefd7f6b
EM
988 if ($this->_component == 'event') {
989 return CRM_Utils_System::url($this->getBaseReturnUrl(), array(
990 'reset' => 1,
991 'cc' => 'fail',
992 'participantId' => $participantID,
993 ),
994 TRUE, NULL, FALSE
995 );
996 }
997
998 return CRM_Utils_System::url($this->getBaseReturnUrl(), array(
999 '_qf_Main_display' => 1,
1000 'qfKey' => $qfKey,
1001 'cancel' => 1,
1002 ),
1003 TRUE, NULL, FALSE
1004 );
1005 }
1006
1007 /**
49c882a3 1008 * Get URL to return the browser to on success.
aefd7f6b
EM
1009 *
1010 * @param $qfKey
1011 *
1012 * @return string
1013 */
1014 protected function getReturnSuccessUrl($qfKey) {
e4555ff1 1015 if (isset($this->successUrl)) {
1016 return $this->successUrl;
1017 }
1018
aefd7f6b
EM
1019 return CRM_Utils_System::url($this->getBaseReturnUrl(), array(
1020 '_qf_ThankYou_display' => 1,
05237b76 1021 'qfKey' => $qfKey,
aefd7f6b
EM
1022 ),
1023 TRUE, NULL, FALSE
1024 );
1025 }
1026
49c882a3
EM
1027 /**
1028 * Get URL to return the browser to on failure.
1029 *
1030 * @param string $key
1031 * @param int $participantID
1032 * @param int $eventID
1033 *
1034 * @return string
1035 * URL for a failing transactor to be redirected to.
1036 */
1037 protected function getReturnFailUrl($key, $participantID = NULL, $eventID = NULL) {
e4555ff1 1038 if (isset($this->cancelUrl)) {
1039 return $this->cancelUrl;
1040 }
1041
6109d8df 1042 $test = $this->_is_test ? '&action=preview' : '';
49c882a3
EM
1043 if ($this->_component == "event") {
1044 return CRM_Utils_System::url('civicrm/event/register',
1045 "reset=1&cc=fail&participantId={$participantID}&id={$eventID}{$test}&qfKey={$key}",
1046 FALSE, NULL, FALSE
1047 );
1048 }
1049 else {
1050 return CRM_Utils_System::url('civicrm/contribute/transact',
1051 "_qf_Main_display=1&cancel=1&qfKey={$key}{$test}",
1052 FALSE, NULL, FALSE
1053 );
1054 }
1055 }
1056
1057 /**
1058 * Get URl for when the back button is pressed.
1059 *
1060 * @param $qfKey
1061 *
1062 * @return string url
1063 */
1064 protected function getGoBackUrl($qfKey) {
1065 return CRM_Utils_System::url($this->getBaseReturnUrl(), array(
1066 '_qf_Confirm_display' => 'true',
6109d8df 1067 'qfKey' => $qfKey,
49c882a3
EM
1068 ),
1069 TRUE, NULL, FALSE
1070 );
1071 }
1072
1073 /**
1074 * Get the notify (aka ipn, web hook or silent post) url.
1075 *
1076 * If there is no '.' in it we assume that we are dealing with localhost or
1077 * similar and it is unreachable from the web & hence invalid.
1078 *
1079 * @return string
1080 * URL to notify outcome of transaction.
1081 */
1082 protected function getNotifyUrl() {
1083 $url = CRM_Utils_System::url(
1084 'civicrm/payment/ipn/' . $this->_paymentProcessor['id'],
068fb0de 1085 array(),
ddc4b5af 1086 TRUE,
1087 NULL,
1088 FALSE
49c882a3
EM
1089 );
1090 return (stristr($url, '.')) ? $url : '';
1091 }
1092
6a488035 1093 /**
8319cf11
EM
1094 * Calling this from outside the payment subsystem is deprecated - use doPayment.
1095 *
1096 * Does a server to server payment transaction.
1097 *
6a0b768e
TO
1098 * @param array $params
1099 * Assoc array of input parameters for this transaction.
6a488035 1100 *
a6c01b45 1101 * @return array
863fadaa 1102 * the result in an nice formatted array (or an error object - but throwing exceptions is preferred)
6a488035 1103 */
863fadaa
EM
1104 protected function doDirectPayment(&$params) {
1105 return $params;
1106 }
6a488035 1107
c1cc3e0c 1108 /**
6c99ada1
EM
1109 * Process payment - this function wraps around both doTransferPayment and doDirectPayment.
1110 *
1111 * The function ensures an exception is thrown & moves some of this logic out of the form layer and makes the forms
1112 * more agnostic.
c1cc3e0c 1113 *
3910048c 1114 * Payment processors should set payment_status_id. This function adds some historical defaults ie. the
7758bd2b
EM
1115 * assumption that if a 'doDirectPayment' processors comes back it completed the transaction & in fact
1116 * doTransferCheckout would not traditionally come back.
1117 *
1118 * doDirectPayment does not do an immediate payment for Authorize.net or Paypal so the default is assumed
1119 * to be Pending.
1120 *
3910048c
EM
1121 * Once this function is fully rolled out then it will be preferred for processors to throw exceptions than to
1122 * return Error objects
1123 *
c1cc3e0c
EM
1124 * @param array $params
1125 *
7758bd2b 1126 * @param string $component
c1cc3e0c 1127 *
a6c01b45 1128 * @return array
7758bd2b
EM
1129 * Result array
1130 *
1131 * @throws \Civi\Payment\Exception\PaymentProcessorException
c1cc3e0c 1132 */
8319cf11 1133 public function doPayment(&$params, $component = 'contribute') {
05c302ec 1134 $this->_component = $component;
7758bd2b 1135 $statuses = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id');
c51070b5 1136
1137 // If we have a $0 amount, skip call to processor and set payment_status to Completed.
1138 // Conceivably a processor might override this - perhaps for setting up a token - but we don't
1139 // have an example of that at the mome.
1140 if ($params['amount'] == 0) {
1141 $result['payment_status_id'] = array_search('Completed', $statuses);
1142 return $result;
1143 }
1144
c1cc3e0c
EM
1145 if ($this->_paymentProcessor['billing_mode'] == 4) {
1146 $result = $this->doTransferCheckout($params, $component);
7c85dc65
EM
1147 if (is_array($result) && !isset($result['payment_status_id'])) {
1148 $result['payment_status_id'] = array_search('Pending', $statuses);
7758bd2b 1149 }
c1cc3e0c
EM
1150 }
1151 else {
f8453bef 1152 $result = $this->doDirectPayment($params, $component);
7c85dc65 1153 if (is_array($result) && !isset($result['payment_status_id'])) {
9dd58272 1154 if (!empty($params['is_recur'])) {
7758bd2b 1155 // See comment block.
0816949d 1156 $result['payment_status_id'] = array_search('Pending', $statuses);
7758bd2b
EM
1157 }
1158 else {
7c85dc65 1159 $result['payment_status_id'] = array_search('Completed', $statuses);
7758bd2b
EM
1160 }
1161 }
c1cc3e0c
EM
1162 }
1163 if (is_a($result, 'CRM_Core_Error')) {
7758bd2b 1164 throw new PaymentProcessorException(CRM_Core_Error::getMessages($result));
c1cc3e0c 1165 }
a9cf9972 1166 return $result;
c1cc3e0c
EM
1167 }
1168
9d2f24ee 1169 /**
1170 * Query payment processor for details about a transaction.
1171 *
1172 * @param array $params
1173 * Array of parameters containing one of:
1174 * - trxn_id Id of an individual transaction.
1175 * - processor_id Id of a recurring contribution series as stored in the civicrm_contribution_recur table.
1176 *
1177 * @return array
1178 * Extra parameters retrieved.
1179 * Any parameters retrievable through this should be documented in the function comments at
1180 * CRM_Core_Payment::doQuery. Currently:
1181 * - fee_amount Amount of fee paid
1182 */
1183 public function doQuery($params) {
1184 return array();
1185 }
1186
6a488035 1187 /**
d09edf64 1188 * This function checks to see if we have the right config values.
6a488035 1189 *
a6c01b45
CW
1190 * @return string
1191 * the error message if any
6a488035 1192 */
7c550ca0 1193 abstract protected function checkConfig();
6a488035 1194
a0ee3941 1195 /**
6c99ada1
EM
1196 * Redirect for paypal.
1197 *
1198 * @todo move to paypal class or remove
1199 *
a0ee3941 1200 * @param $paymentProcessor
6c99ada1 1201 *
a0ee3941
EM
1202 * @return bool
1203 */
00be9182 1204 public static function paypalRedirect(&$paymentProcessor) {
6a488035
TO
1205 if (!$paymentProcessor) {
1206 return FALSE;
1207 }
1208
1209 if (isset($_GET['payment_date']) &&
1210 isset($_GET['merchant_return_link']) &&
1211 CRM_Utils_Array::value('payment_status', $_GET) == 'Completed' &&
1212 $paymentProcessor['payment_processor_type'] == "PayPal_Standard"
1213 ) {
1214 return TRUE;
1215 }
1216
1217 return FALSE;
1218 }
1219
1220 /**
6c99ada1
EM
1221 * Handle incoming payment notification.
1222 *
1223 * IPNs, also called silent posts are notifications of payment outcomes or activity on an external site.
1224 *
43e5f0f6 1225 * @todo move to0 \Civi\Payment\System factory method
6a488035 1226 * Page callback for civicrm/payment/ipn
6a488035 1227 */
00be9182 1228 public static function handleIPN() {
6a488035
TO
1229 self::handlePaymentMethod(
1230 'PaymentNotification',
1231 array(
1232 'processor_name' => @$_GET['processor_name'],
42b90e8f 1233 'processor_id' => @$_GET['processor_id'],
6a488035
TO
1234 'mode' => @$_GET['mode'],
1235 )
1236 );
160c9df2 1237 CRM_Utils_System::civiExit();
6a488035
TO
1238 }
1239
1240 /**
3782df3e
EM
1241 * Payment callback handler.
1242 *
1243 * The processor_name or processor_id is passed in.
43d1ae00
EM
1244 * Note that processor_id is more reliable as one site may have more than one instance of a
1245 * processor & ideally the processor will be validating the results
6a488035
TO
1246 * Load requested payment processor and call that processor's handle<$method> method
1247 *
3782df3e
EM
1248 * @todo move to \Civi\Payment\System factory method
1249 *
1250 * @param string $method
1251 * 'PaymentNotification' or 'PaymentCron'
4691b077 1252 * @param array $params
23de1ac0
EM
1253 *
1254 * @throws \CRM_Core_Exception
1255 * @throws \Exception
6a488035 1256 */
00be9182 1257 public static function handlePaymentMethod($method, $params = array()) {
42b90e8f 1258 if (!isset($params['processor_id']) && !isset($params['processor_name'])) {
020c48ef 1259 $q = explode('/', CRM_Utils_Array::value(CRM_Core_Config::singleton()->userFrameworkURLVar, $_GET, ''));
4164f4e1
EM
1260 $lastParam = array_pop($q);
1261 if (is_numeric($lastParam)) {
1262 $params['processor_id'] = $_GET['processor_id'] = $lastParam;
1263 }
1264 else {
068fb0de 1265 self::logPaymentNotification($params);
0ac9fd52 1266 throw new CRM_Core_Exception("Either 'processor_id' (recommended) or 'processor_name' (deprecated) is required for payment callback.");
4164f4e1 1267 }
6a488035 1268 }
4164f4e1 1269
e2bef985 1270 self::logPaymentNotification($params);
6a488035 1271
42b90e8f
CB
1272 $sql = "SELECT ppt.class_name, ppt.name as processor_name, pp.id AS processor_id
1273 FROM civicrm_payment_processor_type ppt
1274 INNER JOIN civicrm_payment_processor pp
1275 ON pp.payment_processor_type_id = ppt.id
9ff0c7a1 1276 AND pp.is_active";
42b90e8f
CB
1277
1278 if (isset($params['processor_id'])) {
1279 $sql .= " WHERE pp.id = %2";
1280 $args[2] = array($params['processor_id'], 'Integer');
0ac9fd52 1281 $notFound = ts("No active instances of payment processor %1 were found.", array(1 => $params['processor_id']));
42b90e8f
CB
1282 }
1283 else {
9ff0c7a1
EM
1284 // This is called when processor_name is passed - passing processor_id instead is recommended.
1285 $sql .= " WHERE ppt.name = %2 AND pp.is_test = %1";
6c99ada1
EM
1286 $args[1] = array(
1287 (CRM_Utils_Array::value('mode', $params) == 'test') ? 1 : 0,
1288 'Integer',
1289 );
42b90e8f 1290 $args[2] = array($params['processor_name'], 'String');
0ac9fd52 1291 $notFound = ts("No active instances of payment processor '%1' were found.", array(1 => $params['processor_name']));
42b90e8f
CB
1292 }
1293
1294 $dao = CRM_Core_DAO::executeQuery($sql, $args);
6a488035 1295
3782df3e 1296 // Check whether we found anything at all.
6a488035 1297 if (!$dao->N) {
3782df3e 1298 CRM_Core_Error::fatal($notFound);
6a488035
TO
1299 }
1300
1301 $method = 'handle' . $method;
1302 $extension_instance_found = FALSE;
1303
1304 // In all likelihood, we'll just end up with the one instance returned here. But it's
1305 // possible we may get more. Hence, iterate through all instances ..
1306
1307 while ($dao->fetch()) {
5495e78a 1308 // Check pp is extension - is this still required - surely the singleton below handles it.
6a488035
TO
1309 $ext = CRM_Extension_System::singleton()->getMapper();
1310 if ($ext->isExtensionKey($dao->class_name)) {
6a488035
TO
1311 $paymentClass = $ext->keyToClass($dao->class_name, 'payment');
1312 require_once $ext->classToPath($paymentClass);
1313 }
6a488035 1314
7a3b0ca3 1315 $processorInstance = System::singleton()->getById($dao->processor_id);
6a488035
TO
1316
1317 // Should never be empty - we already established this processor_id exists and is active.
81ebda7b 1318 if (empty($processorInstance)) {
6a488035
TO
1319 continue;
1320 }
1321
6a488035
TO
1322 // Does PP implement this method, and can we call it?
1323 if (!method_exists($processorInstance, $method) ||
1324 !is_callable(array($processorInstance, $method))
1325 ) {
43d1ae00
EM
1326 // on the off chance there is a double implementation of this processor we should keep looking for another
1327 // note that passing processor_id is more reliable & we should work to deprecate processor_name
1328 continue;
6a488035
TO
1329 }
1330
1331 // Everything, it seems, is ok - execute pp callback handler
1332 $processorInstance->$method();
a5ef96f6 1333 $extension_instance_found = TRUE;
6a488035
TO
1334 }
1335
4f99ca55 1336 if (!$extension_instance_found) {
0ac9fd52
CB
1337 $message = "No extension instances of the '%1' payment processor were found.<br />" .
1338 "%2 method is unsupported in legacy payment processors.";
1339 CRM_Core_Error::fatal(ts($message, array(1 => $params['processor_name'], 2 => $method)));
2aa397bc 1340 }
6a488035
TO
1341 }
1342
1343 /**
100fef9d 1344 * Check whether a method is present ( & supported ) by the payment processor object.
6a488035 1345 *
1e483eb5
EM
1346 * @deprecated - use $paymentProcessor->supports(array('cancelRecurring');
1347 *
6a0b768e
TO
1348 * @param string $method
1349 * Method to check for.
6a488035 1350 *
7c550ca0 1351 * @return bool
6a488035 1352 */
1524a007 1353 public function isSupported($method) {
6a488035
TO
1354 return method_exists(CRM_Utils_System::getClassName($this), $method);
1355 }
1356
1ba4a3aa
EM
1357 /**
1358 * Some processors replace the form submit button with their own.
1359 *
1360 * Returning false here will leave the button off front end forms.
1361 *
1362 * At this stage there is zero cross-over between back-office processors and processors that suppress the submit.
1363 */
1364 public function isSuppressSubmitButtons() {
1365 return FALSE;
1366 }
1367
d253aeb8
EM
1368 /**
1369 * Checks to see if invoice_id already exists in db.
1370 *
1371 * It's arguable if this belongs in the payment subsystem at all but since several processors implement it
1372 * it is better to standardise to being here.
1373 *
1374 * @param int $invoiceId The ID to check.
1375 *
1376 * @param null $contributionID
1377 * If a contribution exists pass in the contribution ID.
1378 *
1379 * @return bool
1380 * True if invoice ID otherwise exists, else false
1381 */
1382 protected function checkDupe($invoiceId, $contributionID = NULL) {
1383 $contribution = new CRM_Contribute_DAO_Contribution();
1384 $contribution->invoice_id = $invoiceId;
1385 if ($contributionID) {
1386 $contribution->whereAdd("id <> $contributionID");
1387 }
1388 return $contribution->find();
1389 }
1390
a0ee3941 1391 /**
3782df3e
EM
1392 * Get url for users to manage this recurring contribution for this processor.
1393 *
100fef9d 1394 * @param int $entityID
a0ee3941
EM
1395 * @param null $entity
1396 * @param string $action
1397 *
1398 * @return string
1399 */
00be9182 1400 public function subscriptionURL($entityID = NULL, $entity = NULL, $action = 'cancel') {
03cfff4c
KW
1401 // Set URL
1402 switch ($action) {
2aa397bc 1403 case 'cancel':
03cfff4c
KW
1404 $url = 'civicrm/contribute/unsubscribe';
1405 break;
2aa397bc
TO
1406
1407 case 'billing':
03cfff4c 1408 //in notify mode don't return the update billing url
68acd6ae 1409 if (!$this->isSupported('updateSubscriptionBillingInfo')) {
03cfff4c
KW
1410 return NULL;
1411 }
68acd6ae 1412 $url = 'civicrm/contribute/updatebilling';
03cfff4c 1413 break;
2aa397bc
TO
1414
1415 case 'update':
03cfff4c
KW
1416 $url = 'civicrm/contribute/updaterecur';
1417 break;
6a488035
TO
1418 }
1419
b7e7f943 1420 $userId = CRM_Core_Session::singleton()->get('userID');
353ffa53 1421 $contactID = 0;
03cfff4c 1422 $checksumValue = '';
353ffa53 1423 $entityArg = '';
03cfff4c
KW
1424
1425 // Find related Contact
1426 if ($entityID) {
1427 switch ($entity) {
2aa397bc 1428 case 'membership':
03cfff4c
KW
1429 $contactID = CRM_Core_DAO::getFieldValue("CRM_Member_DAO_Membership", $entityID, "contact_id");
1430 $entityArg = 'mid';
1431 break;
1432
2aa397bc 1433 case 'contribution':
03cfff4c
KW
1434 $contactID = CRM_Core_DAO::getFieldValue("CRM_Contribute_DAO_Contribution", $entityID, "contact_id");
1435 $entityArg = 'coid';
1436 break;
1437
2aa397bc 1438 case 'recur':
03cfff4c 1439 $sql = "
6a488035
TO
1440 SELECT con.contact_id
1441 FROM civicrm_contribution_recur rec
1442INNER JOIN civicrm_contribution con ON ( con.contribution_recur_id = rec.id )
1443 WHERE rec.id = %1
1444 GROUP BY rec.id";
03cfff4c
KW
1445 $contactID = CRM_Core_DAO::singleValueQuery($sql, array(1 => array($entityID, 'Integer')));
1446 $entityArg = 'crid';
1447 break;
6a488035 1448 }
6a488035
TO
1449 }
1450
03cfff4c
KW
1451 // Add entity arguments
1452 if ($entityArg != '') {
1453 // Add checksum argument
1454 if ($contactID != 0 && $userId != $contactID) {
1455 $checksumValue = '&cs=' . CRM_Contact_BAO_Contact_Utils::generateChecksum($contactID, NULL, 'inf');
1456 }
1457 return CRM_Utils_System::url($url, "reset=1&{$entityArg}={$entityID}{$checksumValue}", TRUE, NULL, FALSE, TRUE);
1458 }
1459
1460 // Else login URL
6a488035
TO
1461 if ($this->isSupported('accountLoginURL')) {
1462 return $this->accountLoginURL();
1463 }
03cfff4c
KW
1464
1465 // Else default
735d988f 1466 return isset($this->_paymentProcessor['url_recur']) ? $this->_paymentProcessor['url_recur'] : '';
6a488035 1467 }
96025800 1468
efa36d6e 1469 /**
1470 * Get description of payment to pass to processor.
1471 *
1472 * This is often what people see in the interface so we want to get
1473 * as much unique information in as possible within the field length (& presumably the early part of the field)
1474 *
1475 * People seeing these can be assumed to be advanced users so quantity of information probably trumps
1476 * having field names to clarify
1477 *
1478 * @param array $params
1479 * @param int $length
1480 *
1481 * @return string
1482 */
1483 protected function getPaymentDescription($params, $length = 24) {
1484 $parts = array('contactID', 'contributionID', 'description', 'billing_first_name', 'billing_last_name');
1485 $validParts = array();
1486 if (isset($params['description'])) {
1487 $uninformativeStrings = array(ts('Online Event Registration: '), ts('Online Contribution: '));
1488 $params['description'] = str_replace($uninformativeStrings, '', $params['description']);
1489 }
1490 foreach ($parts as $part) {
1491 if ((!empty($params[$part]))) {
1492 $validParts[] = $params[$part];
1493 }
1494 }
1495 return substr(implode('-', $validParts), 0, $length);
1496 }
1497
95974e8e
DG
1498 /**
1499 * Checks if backoffice recurring edit is allowed
1500 *
1501 * @return bool
1502 */
1503 public function supportsEditRecurringContribution() {
1504 return FALSE;
1505 }
1506
cd3bc162 1507 /**
1508 * Should a receipt be sent out for a pending payment.
1509 *
1510 * e.g for traditional pay later & ones with a delayed settlement a pending receipt makes sense.
1511 */
1512 public function isSendReceiptForPending() {
1513 return FALSE;
1514 }
1515
6a488035 1516}