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