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