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