Merge in 5.26
[civicrm-core.git] / Civi / Payment / PropertyBag.php
1 <?php
2 namespace Civi\Payment;
3
4 use InvalidArgumentException;
5 use Civi;
6 use CRM_Core_PseudoConstant;
7
8 /**
9 * @class
10 *
11 * This class provides getters and setters for arguments needed by CRM_Core_Payment methods.
12 *
13 * The setters know how to validate each setting that they are responsible for.
14 *
15 * Different methods need different settings and the concept is that by passing
16 * in a property bag we can encapsulate the params needed for a particular
17 * method call, rather than setting arguments for different methods on the main
18 * CRM_Core_Payment object.
19 *
20 * This class is also supposed to help with transition away from array key naming nightmares.
21 *
22 */
23 class PropertyBag implements \ArrayAccess {
24 /**
25 * @var array
26 * - see legacyWarning
27 */
28 public static $legacyWarnings = [];
29
30 protected $props = ['default' => []];
31
32 protected static $propMap = [
33 'billingStreetAddress' => TRUE,
34 'billingSupplementalAddress1' => TRUE,
35 'billingSupplementalAddress2' => TRUE,
36 'billingSupplementalAddress3' => TRUE,
37 'billingCity' => TRUE,
38 'billingPostalCode' => TRUE,
39 'billingCounty' => TRUE,
40 'billingCountry' => TRUE,
41 'contactID' => TRUE,
42 'contact_id' => 'contactID',
43 'contributionID' => TRUE,
44 'contribution_id' => 'contributionID',
45 'contributionRecurID' => TRUE,
46 'contribution_recur_id' => 'contributionRecurID',
47 'currency' => TRUE,
48 'currencyID' => 'currency',
49 'description' => TRUE,
50 'email' => TRUE,
51 'feeAmount' => TRUE,
52 'fee_amount' => 'feeAmount',
53 'first_name' => 'firstName',
54 'firstName' => TRUE,
55 'invoiceID' => TRUE,
56 'invoice_id' => 'invoiceID',
57 'isBackOffice' => TRUE,
58 'is_back_office' => 'isBackOffice',
59 'isRecur' => TRUE,
60 'is_recur' => 'isRecur',
61 'last_name' => 'lastName',
62 'lastName' => TRUE,
63 'paymentToken' => TRUE,
64 'payment_token' => 'paymentToken',
65 'phone' => TRUE,
66 'recurFrequencyInterval' => TRUE,
67 'frequency_interval' => 'recurFrequencyInterval',
68 'recurFrequencyUnit' => TRUE,
69 'frequency_unit' => 'recurFrequencyUnit',
70 'subscriptionId' => 'recurProcessorID',
71 'recurProcessorID' => TRUE,
72 'transactionID' => TRUE,
73 'transaction_id' => 'transactionID',
74 'trxnResultCode' => TRUE,
75 'isNotifyProcessorOnCancelRecur' => TRUE,
76 ];
77
78 /**
79 * Get the property bag.
80 *
81 * This allows us to swap a 'might be an array might be a property bag'
82 * variable for a known PropertyBag.
83 *
84 * @param \Civi\Payment\PropertyBag|array $params
85 *
86 * @return \Civi\Payment\PropertyBag
87 */
88 public static function cast($params) {
89 if ($params instanceof self) {
90 return $params;
91 }
92 $propertyBag = new self();
93 $propertyBag->mergeLegacyInputParams($params);
94 return $propertyBag;
95 }
96
97 /**
98 * Just for unit testing.
99 *
100 * @var string
101 */
102 public $lastWarning;
103
104 /**
105 * Implements ArrayAccess::offsetExists
106 *
107 * @param mixed $offset
108 * @return bool TRUE if we have that value (on our default store)
109 */
110 public function offsetExists ($offset): bool {
111 $prop = $this->handleLegacyPropNames($offset, TRUE);
112 return $prop && isset($this->props['default'][$prop]);
113 }
114
115 /**
116 * Implements ArrayAccess::offsetGet
117 *
118 * @param mixed $offset
119 * @return mixed
120 */
121 public function offsetGet ($offset) {
122 $prop = $this->handleLegacyPropNames($offset);
123 return $this->get($prop, 'default');
124 }
125
126 /**
127 * Implements ArrayAccess::offsetSet
128 *
129 * @param mixed $offset
130 * @param mixed $value
131 */
132 public function offsetSet($offset, $value) {
133 try {
134 $prop = $this->handleLegacyPropNames($offset);
135 }
136 catch (InvalidArgumentException $e) {
137 // We need to make a lot of noise here, we're being asked to merge in
138 // something we can't validate because we don't know what this property is.
139 // This is fine if it's something particular to a payment processor
140 // (which should be using setCustomProperty) however it could also lead to
141 // things like 'my_weirly_named_contact_id'.
142 $this->legacyWarning($e->getMessage() . " We have merged this in for now as a custom property. Please rewrite your code to use PropertyBag->setCustomProperty if it is a genuinely custom property, or a standardised setter like PropertyBag->setContactID for standard properties");
143 $this->setCustomProperty($offset, $value, 'default');
144 return;
145 }
146
147 // Coerce legacy values that were in use but shouldn't be in our new way of doing things.
148 if ($prop === 'feeAmount' && $value === '') {
149 // At least the following classes pass in ZLS for feeAmount.
150 // CRM_Core_Payment_AuthorizeNetTest::testCreateSingleNowDated
151 // CRM_Core_Payment_AuthorizeNetTest::testCreateSinglePostDated
152 $value = 0;
153 }
154
155 // These lines are here (and not in try block) because the catch must only
156 // catch the case when the prop is custom.
157 $setter = 'set' . ucfirst($prop);
158 $this->$setter($value, 'default');
159 }
160
161 /**
162 * Implements ArrayAccess::offsetUnset
163 *
164 * @param mixed $offset
165 */
166 public function offsetUnset ($offset) {
167 $prop = $this->handleLegacyPropNames($offset);
168 unset($this->props['default'][$prop]);
169 }
170
171 /**
172 * Log legacy warnings info.
173 *
174 * @param string $message
175 */
176 protected function legacyWarning($message) {
177 if (empty(static::$legacyWarnings)) {
178 // First time we have been called.
179 register_shutdown_function([PropertyBag::class, 'writeLegacyWarnings']);
180 }
181 // Store warnings instead of logging immediately, as calls to Civi::log()
182 // can take over half a second to work in some hosting environments.
183 static::$legacyWarnings[$message] = TRUE;
184
185 // For unit tests:
186 $this->lastWarning = $message;
187 }
188
189 /**
190 * Save any legacy warnings to log.
191 *
192 * Called as a shutdown function.
193 */
194 public static function writeLegacyWarnings() {
195 if (!empty(static::$legacyWarnings)) {
196 $message = "Civi\\Payment\\PropertyBag related deprecation warnings:\n"
197 . implode("\n", array_keys(static::$legacyWarnings));
198 Civi::log()->warning($message, ['civi.tag' => 'deprecated']);
199 }
200 }
201
202 /**
203 * @param string $prop
204 * @param bool $silent if TRUE return NULL instead of throwing an exception. This is because offsetExists should be safe and not throw exceptions.
205 * @return string canonical name.
206 * @throws \InvalidArgumentException if prop name not known.
207 */
208 protected function handleLegacyPropNames($prop, $silent = FALSE) {
209 $newName = static::$propMap[$prop] ?? NULL;
210 if ($newName === TRUE) {
211 // Good, modern name.
212 return $prop;
213 }
214 // Handling for legacy addition of billing details.
215 if ($newName === NULL && substr($prop, -2) === '-' . \CRM_Core_BAO_LocationType::getBilling()
216 && isset(static::$propMap[substr($prop, 0, -2)])
217 ) {
218 $newName = substr($prop, 0, -2);
219 }
220
221 if ($newName === NULL) {
222 if ($silent) {
223 // Only for use by offsetExists
224 return;
225 }
226 throw new \InvalidArgumentException("Unknown property '$prop'.");
227 }
228 // Remaining case is legacy name that's been translated.
229 $this->legacyWarning("We have translated '$prop' to '$newName' for you, but please update your code to use the propper setters and getters.");
230 return $newName;
231 }
232
233 /**
234 * Internal getter.
235 *
236 * @param mixed $prop Valid property name
237 * @param string $label e.g. 'default'
238 *
239 * @return mixed
240 */
241 protected function get($prop, $label) {
242 if (array_key_exists($prop, $this->props['default'])) {
243 return $this->props[$label][$prop];
244 }
245 throw new \BadMethodCallException("Property '$prop' has not been set.");
246 }
247
248 /**
249 * Internal setter.
250 *
251 * @param mixed $prop Valid property name
252 * @param string $label e.g. 'default'
253 * @param mixed $value
254 *
255 * @return PropertyBag $this object so you can chain set setters.
256 */
257 protected function set($prop, $label = 'default', $value) {
258 $this->props[$label][$prop] = $value;
259 return $this;
260 }
261
262 /**
263 * DRY code.
264 */
265 protected function coercePseudoConstantStringToInt(string $baoName, string $field, $input) {
266 if (is_numeric($input)) {
267 // We've been given a numeric ID.
268 $_ = (int) $input;
269 }
270 elseif (is_string($input)) {
271 // We've been given a named instrument.
272 $_ = (int) CRM_Core_PseudoConstant::getKey($baoName, $field, $input);
273 }
274 else {
275 throw new InvalidArgumentException("Expected an integer ID or a String name for $field.");
276 }
277 if (!($_ > 0)) {
278 throw new InvalidArgumentException("Expected an integer greater than 0 for $field.");
279 }
280 return $_;
281 }
282
283 /**
284 */
285 public function has($prop, $label = 'default') {
286 // We do NOT translate legacy prop names since only new code should be
287 // using this method, and new code should be using canonical names.
288 // $prop = $this->handleLegacyPropNames($prop);
289 return isset($this->props[$label][$prop]);
290 }
291
292 /**
293 * This is used to merge values from an array.
294 * It's a transitional function and should not be used!
295 *
296 * @param array $data
297 */
298 public function mergeLegacyInputParams($data) {
299 $this->legacyWarning('We have merged input params into the property bag for now but please rewrite code to not use this.');
300 foreach ($data as $key => $value) {
301 if ($value !== NULL && $value !== '') {
302 $this->offsetSet($key, $value);
303 }
304 }
305 }
306
307 /**
308 * Throw an exception if any of the props is unset.
309 *
310 * @param array $props Array of proper property names (no legacy aliases allowed).
311 *
312 * @return PropertyBag
313 */
314 public function require(array $props) {
315 $missing = [];
316 foreach ($props as $prop) {
317 if (!isset($this->props['default'][$prop])) {
318 $missing[] = $prop;
319 }
320 }
321 if ($missing) {
322 throw new \InvalidArgumentException("Required properties missing: " . implode(', ', $missing));
323 }
324 return $this;
325 }
326
327 // Public getters, setters.
328
329 /**
330 * Get a property by its name (but still using its getter).
331 *
332 * @param string $prop valid property name, like contactID
333 * @param bool $allowUnset If TRUE, return the default value if the property is
334 * not set - normal behaviour would be to throw an exception.
335 * @param mixed $default
336 * @param string $label e.g. 'default' or 'old' or 'new'
337 *
338 * @return mixed
339 */
340 public function getter($prop, $allowUnset = FALSE, $default = NULL, $label = 'default') {
341
342 if ((static::$propMap[$prop] ?? NULL) === TRUE) {
343 // This is a standard property that will have a getter method.
344 $getter = 'get' . ucfirst($prop);
345 return (!$allowUnset || $this->has($prop, $label))
346 ? $this->$getter($label)
347 : $default;
348 }
349
350 // This is not a property name we know, but they could be requesting a
351 // custom property.
352 return (!$allowUnset || $this->has($prop, $label))
353 ? $this->getCustomProperty($prop, $label)
354 : $default;
355 }
356
357 /**
358 * Set a property by its name (but still using its setter).
359 *
360 * @param string $prop valid property name, like contactID
361 * @param mixed $value
362 * @param string $label e.g. 'default' or 'old' or 'new'
363 *
364 * @return mixed
365 */
366 public function setter($prop, $value = NULL, $label = 'default') {
367 if ((static::$propMap[$prop] ?? NULL) === TRUE) {
368 // This is a standard property.
369 $setter = 'set' . ucfirst($prop);
370 return $this->$setter($value, $label);
371 }
372 // We don't allow using the setter for custom properties.
373 throw new \BadMethodCallException("Cannot use generic setter with non-standard properties; you must use setCustomProperty for custom properties.");
374 }
375
376 /**
377 * Get the monetary amount.
378 */
379 public function getAmount($label = 'default') {
380 return $this->get('amount', $label);
381 }
382
383 /**
384 * Get the monetary amount.
385 */
386 public function setAmount($value, $label = 'default') {
387 if (!is_numeric($value)) {
388 throw new \InvalidArgumentException("setAmount requires a numeric amount value");
389 }
390
391 return $this->set('amount', CRM_Utils_Money::format($value, NULL, NULL, TRUE), $label);
392 }
393
394 /**
395 * BillingStreetAddress getter.
396 *
397 * @return string
398 */
399 public function getBillingStreetAddress($label = 'default') {
400 return $this->get('billingStreetAddress', $label);
401 }
402
403 /**
404 * BillingStreetAddress setter.
405 *
406 * @param string $input
407 * @param string $label e.g. 'default'
408 */
409 public function setBillingStreetAddress($input, $label = 'default') {
410 return $this->set('billingStreetAddress', $label, (string) $input);
411 }
412
413 /**
414 * BillingSupplementalAddress1 getter.
415 *
416 * @return string
417 */
418 public function getBillingSupplementalAddress1($label = 'default') {
419 return $this->get('billingSupplementalAddress1', $label);
420 }
421
422 /**
423 * BillingSupplementalAddress1 setter.
424 *
425 * @param string $input
426 * @param string $label e.g. 'default'
427 */
428 public function setBillingSupplementalAddress1($input, $label = 'default') {
429 return $this->set('billingSupplementalAddress1', $label, (string) $input);
430 }
431
432 /**
433 * BillingSupplementalAddress2 getter.
434 *
435 * @return string
436 */
437 public function getBillingSupplementalAddress2($label = 'default') {
438 return $this->get('billingSupplementalAddress2', $label);
439 }
440
441 /**
442 * BillingSupplementalAddress2 setter.
443 *
444 * @param string $input
445 * @param string $label e.g. 'default'
446 */
447 public function setBillingSupplementalAddress2($input, $label = 'default') {
448 return $this->set('billingSupplementalAddress2', $label, (string) $input);
449 }
450
451 /**
452 * BillingSupplementalAddress3 getter.
453 *
454 * @return string
455 */
456 public function getBillingSupplementalAddress3($label = 'default') {
457 return $this->get('billingSupplementalAddress3', $label);
458 }
459
460 /**
461 * BillingSupplementalAddress3 setter.
462 *
463 * @param string $input
464 * @param string $label e.g. 'default'
465 */
466 public function setBillingSupplementalAddress3($input, $label = 'default') {
467 return $this->set('billingSupplementalAddress3', $label, (string) $input);
468 }
469
470 /**
471 * BillingCity getter.
472 *
473 * @return string
474 */
475 public function getBillingCity($label = 'default') {
476 return $this->get('billingCity', $label);
477 }
478
479 /**
480 * BillingCity setter.
481 *
482 * @param string $input
483 * @param string $label e.g. 'default'
484 *
485 * @return \Civi\Payment\PropertyBag
486 */
487 public function setBillingCity($input, $label = 'default') {
488 return $this->set('billingCity', $label, (string) $input);
489 }
490
491 /**
492 * BillingPostalCode getter.
493 *
494 * @return string
495 */
496 public function getBillingPostalCode($label = 'default') {
497 return $this->get('billingPostalCode', $label);
498 }
499
500 /**
501 * BillingPostalCode setter.
502 *
503 * @param string $input
504 * @param string $label e.g. 'default'
505 */
506 public function setBillingPostalCode($input, $label = 'default') {
507 return $this->set('billingPostalCode', $label, (string) $input);
508 }
509
510 /**
511 * BillingCounty getter.
512 *
513 * @return string
514 */
515 public function getBillingCounty($label = 'default') {
516 return $this->get('billingCounty', $label);
517 }
518
519 /**
520 * BillingCounty setter.
521 *
522 * Nb. we can't validate this unless we have the country ID too, so we don't.
523 *
524 * @param string $input
525 * @param string $label e.g. 'default'
526 */
527 public function setBillingCounty($input, $label = 'default') {
528 return $this->set('billingCounty', $label, (string) $input);
529 }
530
531 /**
532 * BillingCountry getter.
533 *
534 * @return string
535 */
536 public function getBillingCountry($label = 'default') {
537 return $this->get('billingCountry', $label);
538 }
539
540 /**
541 * BillingCountry setter.
542 *
543 * Nb. We require and we store a 2 character country code.
544 *
545 * @param string $input
546 * @param string $label e.g. 'default'
547 */
548 public function setBillingCountry($input, $label = 'default') {
549 if (!is_string($input) || strlen($input) !== 2) {
550 throw new \InvalidArgumentException("setBillingCountry expects ISO 3166-1 alpha-2 country code.");
551 }
552 if (!CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Address', 'country_id', $input)) {
553 throw new \InvalidArgumentException("setBillingCountry expects ISO 3166-1 alpha-2 country code.");
554 }
555 return $this->set('billingCountry', $label, (string) $input);
556 }
557
558 /**
559 * @return int
560 */
561 public function getContactID($label = 'default'): int {
562 return $this->get('contactID', $label);
563 }
564
565 /**
566 * @param int $contactID
567 * @param string $label
568 */
569 public function setContactID($contactID, $label = 'default') {
570 // We don't use this because it counts zero as positive: CRM_Utils_Type::validate($contactID, 'Positive');
571 if (!($contactID > 0)) {
572 throw new InvalidArgumentException("ContactID must be a positive integer");
573 }
574
575 return $this->set('contactID', $label, (int) $contactID);
576 }
577
578 /**
579 * Getter for contributionID.
580 *
581 * @return int|null
582 * @param string $label
583 */
584 public function getContributionID($label = 'default') {
585 return $this->get('contributionID', $label);
586 }
587
588 /**
589 * @param int $contributionID
590 * @param string $label e.g. 'default'
591 */
592 public function setContributionID($contributionID, $label = 'default') {
593 // We don't use this because it counts zero as positive: CRM_Utils_Type::validate($contactID, 'Positive');
594 if (!($contributionID > 0)) {
595 throw new InvalidArgumentException("ContributionID must be a positive integer");
596 }
597
598 return $this->set('contributionID', $label, (int) $contributionID);
599 }
600
601 /**
602 * Getter for contributionRecurID.
603 *
604 * @return int|null
605 * @param string $label
606 */
607 public function getContributionRecurID($label = 'default') {
608 return $this->get('contributionRecurID', $label);
609 }
610
611 /**
612 * @param int $contributionRecurID
613 * @param string $label e.g. 'default'
614 *
615 * @return \Civi\Payment\PropertyBag
616 */
617 public function setContributionRecurID($contributionRecurID, $label = 'default') {
618 // We don't use this because it counts zero as positive: CRM_Utils_Type::validate($contactID, 'Positive');
619 if (!($contributionRecurID > 0)) {
620 throw new InvalidArgumentException('ContributionRecurID must be a positive integer');
621 }
622
623 return $this->set('contributionRecurID', $label, (int) $contributionRecurID);
624 }
625
626 /**
627 * Three character currency code.
628 *
629 * https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3
630 *
631 * @param string $label e.g. 'default'
632 */
633 public function getCurrency($label = 'default') {
634 return $this->get('currency', $label);
635 }
636
637 /**
638 * Three character currency code.
639 *
640 * @param string $value
641 * @param string $label e.g. 'default'
642 */
643 public function setCurrency($value, $label = 'default') {
644 if (!preg_match('/^[A-Z]{3}$/', $value)) {
645 throw new \InvalidArgumentException("Attemted to setCurrency with a value that was not an ISO 3166-1 alpha 3 currency code");
646 }
647 return $this->set('currency', $label, $value);
648 }
649
650 /**
651 *
652 * @param string $label
653 *
654 * @return string
655 */
656 public function getDescription($label = 'default') {
657 return $this->get('description', $label);
658 }
659
660 /**
661 * @param string $description
662 * @param string $label e.g. 'default'
663 */
664 public function setDescription($description, $label = 'default') {
665 // @todo this logic was copied from a commit that then got deleted. Is it good?
666 $uninformativeStrings = [
667 ts('Online Event Registration: '),
668 ts('Online Contribution: '),
669 ];
670 $cleanedDescription = str_replace($uninformativeStrings, '', $description);
671 return $this->set('description', $label, $cleanedDescription);
672 }
673
674 /**
675 * Email getter.
676 *
677 * @return string
678 */
679 public function getEmail($label = 'default') {
680 return $this->get('email', $label);
681 }
682
683 /**
684 * Email setter.
685 *
686 * @param string $email
687 * @param string $label e.g. 'default'
688 */
689 public function setEmail($email, $label = 'default') {
690 return $this->set('email', $label, (string) $email);
691 }
692
693 /**
694 * Amount of money charged in fees by the payment processor.
695 *
696 * This is notified by (some) payment processers.
697 *
698 * @param string $label
699 *
700 * @return float
701 */
702 public function getFeeAmount($label = 'default') {
703 return $this->get('feeAmount', $label);
704 }
705
706 /**
707 * @param string $feeAmount
708 * @param string $label e.g. 'default'
709 */
710 public function setFeeAmount($feeAmount, $label = 'default') {
711 if (!is_numeric($feeAmount)) {
712 throw new \InvalidArgumentException("feeAmount must be a number.");
713 }
714 return $this->set('feeAmount', $label, (float) $feeAmount);
715 }
716
717 /**
718 * First name
719 *
720 * @return string
721 */
722 public function getFirstName($label = 'default') {
723 return $this->get('firstName', $label);
724 }
725
726 /**
727 * First name setter.
728 *
729 * @param string $firstName
730 * @param string $label e.g. 'default'
731 */
732 public function setFirstName($firstName, $label = 'default') {
733 return $this->set('firstName', $label, (string) $firstName);
734 }
735
736 /**
737 * Getter for invoiceID.
738 *
739 * @param string $label
740 *
741 * @return string|null
742 */
743 public function getInvoiceID($label = 'default') {
744 return $this->get('invoiceID', $label);
745 }
746
747 /**
748 * @param string $invoiceID
749 * @param string $label e.g. 'default'
750 */
751 public function setInvoiceID($invoiceID, $label = 'default') {
752 return $this->set('invoiceID', $label, $invoiceID);
753 }
754
755 /**
756 * Getter for isBackOffice.
757 *
758 * @param string $label
759 *
760 * @return bool|null
761 */
762 public function getIsBackOffice($label = 'default'):bool {
763 // @todo should this return FALSE instead of exception to keep current situation?
764 return $this->get('isBackOffice', $label);
765 }
766
767 /**
768 * @param bool $isBackOffice
769 * @param string $label e.g. 'default'
770 */
771 public function setIsBackOffice($isBackOffice, $label = 'default') {
772 if (is_null($isBackOffice)) {
773 throw new \InvalidArgumentException("isBackOffice must be a bool, received NULL.");
774 }
775 return $this->set('isBackOffice', $label, (bool) $isBackOffice);
776 }
777
778 /**
779 * Getter for isRecur.
780 *
781 * @param string $label
782 *
783 * @return bool|null
784 */
785 public function getIsRecur($label = 'default'):bool {
786 return $this->get('isRecur', $label);
787 }
788
789 /**
790 * @param bool $isRecur
791 * @param string $label e.g. 'default'
792 */
793 public function setIsRecur($isRecur, $label = 'default') {
794 if (is_null($isRecur)) {
795 throw new \InvalidArgumentException("isRecur must be a bool, received NULL.");
796 }
797 return $this->set('isRecur', $label, (bool) $isRecur);
798 }
799
800 /**
801 * Set whether the user has selected to notify the processor of a cancellation request.
802 *
803 * When cancelling the user may be presented with an option to notify the processor. The payment
804 * processor can take their response, if present, into account.
805 *
806 * @param bool $value
807 * @param string $label e.g. 'default'
808 *
809 * @return \Civi\Payment\PropertyBag
810 */
811 public function setIsNotifyProcessorOnCancelRecur($value, $label = 'default') {
812 return $this->set('isNotifyProcessorOnCancelRecur', $label, (bool) $value);
813 }
814
815 /**
816 * Get whether the user has selected to notify the processor of a cancellation request.
817 *
818 * When cancelling the user may be presented with an option to notify the processor. The payment
819 * processor can take their response, if present, into account.
820 *
821 * @param string $label e.g. 'default'
822 *
823 * @return \Civi\Payment\PropertyBag
824 */
825 public function getIsNotifyProcessorOnCancelRecur($label = 'default') {
826 return $this->get('isNotifyProcessorOnCancelRecur', $label);
827 }
828
829 /**
830 * Last name
831 *
832 * @return string
833 */
834 public function getLastName($label = 'default') {
835 return $this->get('lastName', $label);
836 }
837
838 /**
839 * Last name setter.
840 *
841 * @param string $lastName
842 * @param string $label e.g. 'default'
843 */
844 public function setLastName($lastName, $label = 'default') {
845 return $this->set('lastName', $label, (string) $lastName);
846 }
847
848 /**
849 * Getter for payment processor generated string for charging.
850 *
851 * A payment token could be a single use token (e.g generated by
852 * a client side script) or a token that permits recurring or on demand charging.
853 *
854 * The key thing is it is passed to the processor in lieu of card details to
855 * initiate a payment.
856 *
857 * Generally if a processor is going to pass in a payment token generated through
858 * javascript it would add 'payment_token' to the array it returns in it's
859 * implementation of getPaymentFormFields. This will add a hidden 'payment_token' field to
860 * the form. A good example is client side encryption where credit card details are replaced by
861 * an encrypted token using a gateway provided javascript script. In this case the javascript will
862 * remove the credit card details from the form before submitting and populate the payment_token field.
863 *
864 * A more complex example is used by paypal checkout where the payment token is generated
865 * via a pre-approval process. In that case the doPreApproval function is called on the processor
866 * class to get information to then interact with paypal via js, finally getting a payment token.
867 * (at this stage the pre-approve api is not in core but that is likely to change - we just need
868 * to think about the permissions since we don't want to expose to anonymous user without thinking
869 * through any risk of credit-card testing using it.
870 *
871 * If the token is not totally transient it would be saved to civicrm_payment_token.token.
872 *
873 * @param string $label
874 *
875 * @return string|null
876 */
877 public function getPaymentToken($label = 'default') {
878 return $this->get('paymentToken', $label);
879 }
880
881 /**
882 * @param string $paymentToken
883 * @param string $label e.g. 'default'
884 */
885 public function setPaymentToken($paymentToken, $label = 'default') {
886 return $this->set('paymentToken', $label, $paymentToken);
887 }
888
889 /**
890 * Phone getter.
891 *
892 * @return string
893 */
894 public function getPhone($label = 'default') {
895 return $this->get('phone', $label);
896 }
897
898 /**
899 * Phone setter.
900 *
901 * @param string $phone
902 * @param string $label e.g. 'default'
903 */
904 public function setPhone($phone, $label = 'default') {
905 return $this->set('phone', $label, (string) $phone);
906 }
907
908 /**
909 * Combined with recurFrequencyUnit this gives how often billing will take place.
910 *
911 * e.g every if this is 1 and recurFrequencyUnit is 'month' then it is every 1 month.
912 * @return int|null
913 */
914 public function getRecurFrequencyInterval($label = 'default') {
915 return $this->get('recurFrequencyInterval', $label);
916 }
917
918 /**
919 * @param int $recurFrequencyInterval
920 * @param string $label e.g. 'default'
921 */
922 public function setRecurFrequencyInterval($recurFrequencyInterval, $label = 'default') {
923 if (!($recurFrequencyInterval > 0)) {
924 throw new InvalidArgumentException("recurFrequencyInterval must be a positive integer");
925 }
926
927 return $this->set('recurFrequencyInterval', $label, (int) $recurFrequencyInterval);
928 }
929
930 /**
931 * Getter for recurFrequencyUnit.
932 * Combined with recurFrequencyInterval this gives how often billing will take place.
933 *
934 * e.g every if this is 'month' and recurFrequencyInterval is 1 then it is every 1 month.
935 *
936 *
937 * @param string $label
938 *
939 * @return string month|day|year
940 */
941 public function getRecurFrequencyUnit($label = 'default') {
942 return $this->get('recurFrequencyUnit', $label);
943 }
944
945 /**
946 * @param string $recurFrequencyUnit month|day|week|year
947 * @param string $label e.g. 'default'
948 */
949 public function setRecurFrequencyUnit($recurFrequencyUnit, $label = 'default') {
950 if (!preg_match('/^day|week|month|year$/', $recurFrequencyUnit)) {
951 throw new \InvalidArgumentException("recurFrequencyUnit must be day|week|month|year");
952 }
953 return $this->set('recurFrequencyUnit', $label, $recurFrequencyUnit);
954 }
955
956 /**
957 * Set the unique payment processor service provided ID for a particular subscription.
958 *
959 * Nb. this is stored in civicrm_contribution_recur.processor_id and is NOT
960 * in any way related to the payment processor ID.
961 *
962 * @param string $label
963 *
964 * @return string|null
965 */
966 public function getRecurProcessorID($label = 'default') {
967 return $this->get('recurProcessorID', $label);
968 }
969
970 /**
971 * Set the unique payment processor service provided ID for a particular
972 * subscription.
973 *
974 * See https://github.com/civicrm/civicrm-core/pull/17292 for discussion
975 * of how this function accepting NULL fits with standard / planned behaviour.
976 *
977 * @param string|null $input
978 * @param string $label e.g. 'default'
979 *
980 * @return \Civi\Payment\PropertyBag
981 */
982 public function setRecurProcessorID($input, $label = 'default') {
983 if ($input === '') {
984 $input = NULL;
985 }
986 if (strlen($input) > 255 || in_array($input, [FALSE, 0], TRUE)) {
987 throw new \InvalidArgumentException('processorID field has max length of 255');
988 }
989 return $this->set('recurProcessorID', $label, $input);
990 }
991
992 /**
993 * Getter for payment processor generated string for the transaction ID.
994 *
995 * Note some gateways generate a reference for the order and one for the
996 * payment. This is for the payment reference and is saved to
997 * civicrm_financial_trxn.trxn_id.
998 *
999 * @param string $label
1000 *
1001 * @return string|null
1002 */
1003 public function getTransactionID($label = 'default') {
1004 return $this->get('transactionID', $label);
1005 }
1006
1007 /**
1008 * @param string $transactionID
1009 * @param string $label e.g. 'default'
1010 */
1011 public function setTransactionID($transactionID, $label = 'default') {
1012 return $this->set('transactionID', $label, $transactionID);
1013 }
1014
1015 /**
1016 * Getter for trxnResultCode.
1017 *
1018 * Additional information returned by the payment processor regarding the
1019 * payment outcome.
1020 *
1021 * This would normally be saved in civicrm_financial_trxn.trxn_result_code.
1022 *
1023 *
1024 * @param string $label
1025 *
1026 * @return string|null
1027 */
1028 public function getTrxnResultCode($label = 'default') {
1029 return $this->get('trxnResultCode', $label);
1030 }
1031
1032 /**
1033 * @param string $trxnResultCode
1034 * @param string $label e.g. 'default'
1035 */
1036 public function setTrxnResultCode($trxnResultCode, $label = 'default') {
1037 return $this->set('trxnResultCode', $label, $trxnResultCode);
1038 }
1039
1040 // Custom Properties.
1041
1042 /**
1043 * Sometimes we may need to pass in things that are specific to the Payment
1044 * Processor.
1045 *
1046 * @param string $prop
1047 * @param string $label e.g. 'default' or 'old' or 'new'
1048 * @return mixed
1049 *
1050 * @throws InvalidArgumentException if trying to use this against a non-custom property.
1051 */
1052 public function getCustomProperty($prop, $label = 'default') {
1053 if (isset(static::$propMap[$prop])) {
1054 throw new \InvalidArgumentException("Attempted to get '$prop' via getCustomProperty - must use using its getter.");
1055 }
1056 return $this->props[$label][$prop] ?? NULL;
1057 }
1058
1059 /**
1060 * We have to leave validation to the processor, but we can still give them a
1061 * way to store their data on this PropertyBag
1062 *
1063 * @param string $prop
1064 * @param mixed $value
1065 * @param string $label e.g. 'default' or 'old' or 'new'
1066 *
1067 * @throws InvalidArgumentException if trying to use this against a non-custom property.
1068 */
1069 public function setCustomProperty($prop, $value, $label = 'default') {
1070 if (isset(static::$propMap[$prop])) {
1071 throw new \InvalidArgumentException("Attempted to set '$prop' via setCustomProperty - must use using its setter.");
1072 }
1073 $this->props[$label][$prop] = $value;
1074 }
1075
1076 }