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