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