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