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