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