2 namespace Civi\Payment
;
4 use InvalidArgumentException
;
7 use CRM_Core_PseudoConstant
;
12 * This class provides getters and setters for arguments needed by CRM_Core_Payment methods.
14 * The setters know how to validate each setting that they are responsible for.
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.
21 * This class is also supposed to help with transition away from array key naming nightmares.
24 class PropertyBag
implements \ArrayAccess
{
26 protected $props = ['default' => []];
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,
38 'contact_id' => 'contactID',
39 'contributionID' => TRUE,
40 'contribution_id' => 'contributionID',
41 'contributionRecurID' => TRUE,
42 'contribution_recur_id' => 'contributionRecurID',
44 'currencyID' => 'currency',
45 'description' => TRUE,
48 'fee_amount' => 'feeAmount',
49 'first_name' => 'firstName',
52 'invoice_id' => 'invoiceID',
53 'isBackOffice' => TRUE,
54 'is_back_office' => 'isBackOffice',
56 'is_recur' => 'isRecur',
57 'last_name' => 'lastName',
59 'paymentToken' => TRUE,
60 'payment_token' => 'paymentToken',
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,
77 * Temporary, internal variable to help ease transition to PropertyBag.
78 * Used by cast() to suppress legacy warnings.
80 protected $suppressLegacyWarnings = FALSE;
83 * Get the property bag.
85 * This allows us to swap a 'might be an array might be a property bag'
86 * variable for a known PropertyBag.
88 * @param \Civi\Payment\PropertyBag|array $params
90 * @return \Civi\Payment\PropertyBag
92 public static function cast($params) {
93 if ($params instanceof self
) {
96 $propertyBag = new self();
97 $propertyBag->mergeLegacyInputParams($params);
102 * Just for unit testing.
109 * Implements ArrayAccess::offsetExists
111 * @param mixed $offset
112 * @return bool TRUE if we have that value (on our default store)
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']);
122 * Implements ArrayAccess::offsetGet
124 * @param mixed $offset
127 public function offsetGet($offset) {
129 $prop = $this->handleLegacyPropNames($offset);
131 catch (InvalidArgumentException
$e) {
133 CRM_Core_Error
::deprecatedFunctionWarning(
134 "proper getCustomProperty('$offset') for non-core properties. "
136 "PropertyBag array access to get '$offset'"
140 return $this->getCustomProperty($offset, 'default');
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."
146 "PropertyBag array access to get unset property '$offset'"
152 CRM_Core_Error
::deprecatedFunctionWarning(
153 "get" . ucfirst($offset) . "()",
154 "PropertyBag array access for core property '$offset'"
156 return $this->get($prop, 'default');
160 * Implements ArrayAccess::offsetSet
162 * @param mixed $offset
163 * @param mixed $value
165 public function offsetSet($offset, $value) {
167 $prop = $this->handleLegacyPropNames($offset);
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'.
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. "
181 "PropertyBag array access to set '$offset'"
184 $this->setCustomProperty($offset, $value, 'default');
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
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(
202 "PropertyBag array access to set core property '$offset'"
205 $this->$setter($value, 'default');
209 * Implements ArrayAccess::offsetUnset
211 * @param mixed $offset
213 public function offsetUnset ($offset) {
214 $prop = $this->handleLegacyPropNames($offset);
215 unset($this->props
['default'][$prop]);
219 * Save any legacy warnings to log.
221 * Called as a shutdown function.
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']);
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.
237 protected function handleLegacyPropNames($prop, $silent = FALSE) {
238 $newName = static::$propMap[$prop] ??
NULL;
239 if ($newName === TRUE) {
240 // Good, modern name.
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)])
247 $newName = substr($prop, 0, -2);
250 if ($newName === NULL) {
252 // Only for use by offsetExists
255 throw new \
InvalidArgumentException("Unknown property '$prop'.");
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'");
268 * @param mixed $prop Valid property name
269 * @param string $label e.g. 'default'
273 protected function get($prop, $label) {
274 if (array_key_exists($prop, $this->props
[$label] ??
[])) {
275 return $this->props
[$label][$prop];
277 throw new \
BadMethodCallException("Property '$prop' has not been set.");
283 * @param mixed $prop Valid property name
284 * @param string $label e.g. 'default'
285 * @param mixed $value
287 * @return PropertyBag $this object so you can chain set setters.
289 protected function set($prop, $label = 'default', $value) {
290 $this->props
[$label][$prop] = $value;
297 protected function coercePseudoConstantStringToInt(string $baoName, string $field, $input) {
298 if (is_numeric($input)) {
299 // We've been given a numeric ID.
302 elseif (is_string($input)) {
303 // We've been given a named instrument.
304 $_ = (int) CRM_Core_PseudoConstant
::getKey($baoName, $field, $input);
307 throw new InvalidArgumentException("Expected an integer ID or a String name for $field.");
310 throw new InvalidArgumentException("Expected an integer greater than 0 for $field.");
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]);
325 * This is used to merge values from an array.
326 * It's a transitional, internal function and should not be used!
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);
340 $this->suppressLegacyWarnings
= FALSE;
344 * Throw an exception if any of the props is unset.
346 * @param array $props Array of proper property names (no legacy aliases allowed).
348 * @return PropertyBag
350 public function require(array $props) {
352 foreach ($props as $prop) {
353 if (!isset($this->props
['default'][$prop])) {
358 throw new \
InvalidArgumentException("Required properties missing: " . implode(', ', $missing));
363 // Public getters, setters.
366 * Get a property by its name (but still using its getter).
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'
376 public function getter($prop, $allowUnset = FALSE, $default = NULL, $label = 'default') {
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)
386 // This is not a property name we know, but they could be requesting a
388 return (!$allowUnset ||
$this->has($prop, $label))
389 ?
$this->getCustomProperty($prop, $label)
394 * Set a property by its name (but still using its setter).
396 * @param string $prop valid property name, like contactID
397 * @param mixed $value
398 * @param string $label e.g. 'default' or 'old' or 'new'
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);
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.");
413 * Get the monetary amount.
415 public function getAmount($label = 'default') {
416 return $this->get('amount', $label);
420 * Get the monetary amount.
422 public function setAmount($value, $label = 'default') {
423 if (!is_numeric($value)) {
424 throw new \
InvalidArgumentException("setAmount requires a numeric amount value");
427 return $this->set('amount', $label, \CRM_Utils_Money
::format($value, NULL, NULL, TRUE));
431 * BillingStreetAddress getter.
435 public function getBillingStreetAddress($label = 'default') {
436 return $this->get('billingStreetAddress', $label);
440 * BillingStreetAddress setter.
442 * @param string $input
443 * @param string $label e.g. 'default'
445 public function setBillingStreetAddress($input, $label = 'default') {
446 return $this->set('billingStreetAddress', $label, (string) $input);
450 * BillingSupplementalAddress1 getter.
454 public function getBillingSupplementalAddress1($label = 'default') {
455 return $this->get('billingSupplementalAddress1', $label);
459 * BillingSupplementalAddress1 setter.
461 * @param string $input
462 * @param string $label e.g. 'default'
464 public function setBillingSupplementalAddress1($input, $label = 'default') {
465 return $this->set('billingSupplementalAddress1', $label, (string) $input);
469 * BillingSupplementalAddress2 getter.
473 public function getBillingSupplementalAddress2($label = 'default') {
474 return $this->get('billingSupplementalAddress2', $label);
478 * BillingSupplementalAddress2 setter.
480 * @param string $input
481 * @param string $label e.g. 'default'
483 public function setBillingSupplementalAddress2($input, $label = 'default') {
484 return $this->set('billingSupplementalAddress2', $label, (string) $input);
488 * BillingSupplementalAddress3 getter.
492 public function getBillingSupplementalAddress3($label = 'default') {
493 return $this->get('billingSupplementalAddress3', $label);
497 * BillingSupplementalAddress3 setter.
499 * @param string $input
500 * @param string $label e.g. 'default'
502 public function setBillingSupplementalAddress3($input, $label = 'default') {
503 return $this->set('billingSupplementalAddress3', $label, (string) $input);
507 * BillingCity getter.
511 public function getBillingCity($label = 'default') {
512 return $this->get('billingCity', $label);
516 * BillingCity setter.
518 * @param string $input
519 * @param string $label e.g. 'default'
521 * @return \Civi\Payment\PropertyBag
523 public function setBillingCity($input, $label = 'default') {
524 return $this->set('billingCity', $label, (string) $input);
528 * BillingPostalCode getter.
532 public function getBillingPostalCode($label = 'default') {
533 return $this->get('billingPostalCode', $label);
537 * BillingPostalCode setter.
539 * @param string $input
540 * @param string $label e.g. 'default'
542 public function setBillingPostalCode($input, $label = 'default') {
543 return $this->set('billingPostalCode', $label, (string) $input);
547 * BillingCounty getter.
551 public function getBillingCounty($label = 'default') {
552 return $this->get('billingCounty', $label);
556 * BillingCounty setter.
558 * Nb. we can't validate this unless we have the country ID too, so we don't.
560 * @param string $input
561 * @param string $label e.g. 'default'
563 public function setBillingCounty($input, $label = 'default') {
564 return $this->set('billingCounty', $label, (string) $input);
568 * BillingCountry getter.
572 public function getBillingCountry($label = 'default') {
573 return $this->get('billingCountry', $label);
577 * BillingCountry setter.
579 * Nb. We require and we store a 2 character country code.
581 * @param string $input
582 * @param string $label e.g. 'default'
584 public function setBillingCountry($input, $label = 'default') {
585 if (!is_string($input) ||
strlen($input) !== 2) {
586 throw new \
InvalidArgumentException("setBillingCountry expects ISO 3166-1 alpha-2 country code.");
588 if (!CRM_Core_PseudoConstant
::getKey('CRM_Core_BAO_Address', 'country_id', $input)) {
589 throw new \
InvalidArgumentException("setBillingCountry expects ISO 3166-1 alpha-2 country code.");
591 return $this->set('billingCountry', $label, (string) $input);
597 public function getContactID($label = 'default'): int {
598 return $this->get('contactID', $label);
602 * @param int $contactID
603 * @param string $label
605 public function setContactID($contactID, $label = 'default') {
606 // We don't use this because it counts zero as positive: CRM_Utils_Type::validate($contactID, 'Positive');
607 if (!($contactID > 0)) {
608 throw new InvalidArgumentException("ContactID must be a positive integer");
611 return $this->set('contactID', $label, (int) $contactID);
615 * Getter for contributionID.
618 * @param string $label
620 public function getContributionID($label = 'default') {
621 return $this->get('contributionID', $label);
625 * @param int $contributionID
626 * @param string $label e.g. 'default'
628 public function setContributionID($contributionID, $label = 'default') {
629 // We don't use this because it counts zero as positive: CRM_Utils_Type::validate($contactID, 'Positive');
630 if (!($contributionID > 0)) {
631 throw new InvalidArgumentException("ContributionID must be a positive integer");
634 return $this->set('contributionID', $label, (int) $contributionID);
638 * Getter for contributionRecurID.
641 * @param string $label
643 public function getContributionRecurID($label = 'default') {
644 return $this->get('contributionRecurID', $label);
648 * @param int $contributionRecurID
649 * @param string $label e.g. 'default'
651 * @return \Civi\Payment\PropertyBag
653 public function setContributionRecurID($contributionRecurID, $label = 'default') {
654 // We don't use this because it counts zero as positive: CRM_Utils_Type::validate($contactID, 'Positive');
655 if (!($contributionRecurID > 0)) {
656 throw new InvalidArgumentException('ContributionRecurID must be a positive integer');
659 return $this->set('contributionRecurID', $label, (int) $contributionRecurID);
663 * Three character currency code.
665 * https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3
667 * @param string $label e.g. 'default'
669 public function getCurrency($label = 'default') {
670 return $this->get('currency', $label);
674 * Three character currency code.
676 * @param string $value
677 * @param string $label e.g. 'default'
679 public function setCurrency($value, $label = 'default') {
680 if (!preg_match('/^[A-Z]{3}$/', $value)) {
681 throw new \
InvalidArgumentException("Attemted to setCurrency with a value that was not an ISO 3166-1 alpha 3 currency code");
683 return $this->set('currency', $label, $value);
688 * @param string $label
692 public function getDescription($label = 'default') {
693 return $this->get('description', $label);
697 * @param string $description
698 * @param string $label e.g. 'default'
700 public function setDescription($description, $label = 'default') {
701 // @todo this logic was copied from a commit that then got deleted. Is it good?
702 $uninformativeStrings = [
703 ts('Online Event Registration: '),
704 ts('Online Contribution: '),
706 $cleanedDescription = str_replace($uninformativeStrings, '', $description);
707 return $this->set('description', $label, $cleanedDescription);
715 public function getEmail($label = 'default') {
716 return $this->get('email', $label);
722 * @param string $email
723 * @param string $label e.g. 'default'
725 public function setEmail($email, $label = 'default') {
726 return $this->set('email', $label, (string) $email);
730 * Amount of money charged in fees by the payment processor.
732 * This is notified by (some) payment processers.
734 * @param string $label
738 public function getFeeAmount($label = 'default') {
739 return $this->get('feeAmount', $label);
743 * @param string $feeAmount
744 * @param string $label e.g. 'default'
746 public function setFeeAmount($feeAmount, $label = 'default') {
747 if (!is_numeric($feeAmount)) {
748 throw new \
InvalidArgumentException("feeAmount must be a number.");
750 return $this->set('feeAmount', $label, (float) $feeAmount);
758 public function getFirstName($label = 'default') {
759 return $this->get('firstName', $label);
765 * @param string $firstName
766 * @param string $label e.g. 'default'
768 public function setFirstName($firstName, $label = 'default') {
769 return $this->set('firstName', $label, (string) $firstName);
773 * Getter for invoiceID.
775 * @param string $label
777 * @return string|null
779 public function getInvoiceID($label = 'default') {
780 return $this->get('invoiceID', $label);
784 * @param string $invoiceID
785 * @param string $label e.g. 'default'
787 public function setInvoiceID($invoiceID, $label = 'default') {
788 return $this->set('invoiceID', $label, $invoiceID);
792 * Getter for isBackOffice.
794 * @param string $label
798 public function getIsBackOffice($label = 'default'):bool {
799 // @todo should this return FALSE instead of exception to keep current situation?
800 return $this->get('isBackOffice', $label);
804 * @param bool $isBackOffice
805 * @param string $label e.g. 'default'
807 public function setIsBackOffice($isBackOffice, $label = 'default') {
808 if (is_null($isBackOffice)) {
809 throw new \
InvalidArgumentException("isBackOffice must be a bool, received NULL.");
811 return $this->set('isBackOffice', $label, (bool) $isBackOffice);
815 * Getter for isRecur.
817 * @param string $label
821 public function getIsRecur($label = 'default'):bool {
822 if (!$this->has('isRecur')) {
825 return $this->get('isRecur', $label);
829 * @param bool $isRecur
830 * @param string $label e.g. 'default'
832 public function setIsRecur($isRecur, $label = 'default') {
833 if (is_null($isRecur)) {
834 throw new \
InvalidArgumentException("isRecur must be a bool, received NULL.");
836 return $this->set('isRecur', $label, (bool) $isRecur);
840 * Set whether the user has selected to notify the processor of a cancellation request.
842 * When cancelling the user may be presented with an option to notify the processor. The payment
843 * processor can take their response, if present, into account.
846 * @param string $label e.g. 'default'
848 * @return \Civi\Payment\PropertyBag
850 public function setIsNotifyProcessorOnCancelRecur($value, $label = 'default') {
851 return $this->set('isNotifyProcessorOnCancelRecur', $label, (bool) $value);
855 * Get whether the user has selected to notify the processor of a cancellation request.
857 * When cancelling the user may be presented with an option to notify the processor. The payment
858 * processor can take their response, if present, into account.
860 * @param string $label e.g. 'default'
862 * @return \Civi\Payment\PropertyBag
864 public function getIsNotifyProcessorOnCancelRecur($label = 'default') {
865 return $this->get('isNotifyProcessorOnCancelRecur', $label);
873 public function getLastName($label = 'default') {
874 return $this->get('lastName', $label);
880 * @param string $lastName
881 * @param string $label e.g. 'default'
883 public function setLastName($lastName, $label = 'default') {
884 return $this->set('lastName', $label, (string) $lastName);
888 * Getter for payment processor generated string for charging.
890 * A payment token could be a single use token (e.g generated by
891 * a client side script) or a token that permits recurring or on demand charging.
893 * The key thing is it is passed to the processor in lieu of card details to
894 * initiate a payment.
896 * Generally if a processor is going to pass in a payment token generated through
897 * javascript it would add 'payment_token' to the array it returns in it's
898 * implementation of getPaymentFormFields. This will add a hidden 'payment_token' field to
899 * the form. A good example is client side encryption where credit card details are replaced by
900 * an encrypted token using a gateway provided javascript script. In this case the javascript will
901 * remove the credit card details from the form before submitting and populate the payment_token field.
903 * A more complex example is used by paypal checkout where the payment token is generated
904 * via a pre-approval process. In that case the doPreApproval function is called on the processor
905 * class to get information to then interact with paypal via js, finally getting a payment token.
906 * (at this stage the pre-approve api is not in core but that is likely to change - we just need
907 * to think about the permissions since we don't want to expose to anonymous user without thinking
908 * through any risk of credit-card testing using it.
910 * If the token is not totally transient it would be saved to civicrm_payment_token.token.
912 * @param string $label
914 * @return string|null
916 public function getPaymentToken($label = 'default') {
917 return $this->get('paymentToken', $label);
921 * @param string $paymentToken
922 * @param string $label e.g. 'default'
924 public function setPaymentToken($paymentToken, $label = 'default') {
925 return $this->set('paymentToken', $label, $paymentToken);
933 public function getPhone($label = 'default') {
934 return $this->get('phone', $label);
940 * @param string $phone
941 * @param string $label e.g. 'default'
943 public function setPhone($phone, $label = 'default') {
944 return $this->set('phone', $label, (string) $phone);
948 * Combined with recurFrequencyUnit this gives how often billing will take place.
950 * e.g every if this is 1 and recurFrequencyUnit is 'month' then it is every 1 month.
953 public function getRecurFrequencyInterval($label = 'default') {
954 return $this->get('recurFrequencyInterval', $label);
958 * @param int $recurFrequencyInterval
959 * @param string $label e.g. 'default'
961 public function setRecurFrequencyInterval($recurFrequencyInterval, $label = 'default') {
962 if (!($recurFrequencyInterval > 0)) {
963 throw new InvalidArgumentException("recurFrequencyInterval must be a positive integer");
966 return $this->set('recurFrequencyInterval', $label, (int) $recurFrequencyInterval);
970 * Getter for recurFrequencyUnit.
971 * Combined with recurFrequencyInterval this gives how often billing will take place.
973 * e.g every if this is 'month' and recurFrequencyInterval is 1 then it is every 1 month.
976 * @param string $label
978 * @return string month|day|year
980 public function getRecurFrequencyUnit($label = 'default') {
981 return $this->get('recurFrequencyUnit', $label);
985 * @param string $recurFrequencyUnit month|day|week|year
986 * @param string $label e.g. 'default'
988 public function setRecurFrequencyUnit($recurFrequencyUnit, $label = 'default') {
989 if (!preg_match('/^day|week|month|year$/', $recurFrequencyUnit)) {
990 throw new \
InvalidArgumentException("recurFrequencyUnit must be day|week|month|year");
992 return $this->set('recurFrequencyUnit', $label, $recurFrequencyUnit);
996 * Set the unique payment processor service provided ID for a particular subscription.
998 * Nb. this is stored in civicrm_contribution_recur.processor_id and is NOT
999 * in any way related to the payment processor ID.
1001 * @param string $label
1003 * @return string|null
1005 public function getRecurProcessorID($label = 'default') {
1006 return $this->get('recurProcessorID', $label);
1010 * Set the unique payment processor service provided ID for a particular
1013 * See https://github.com/civicrm/civicrm-core/pull/17292 for discussion
1014 * of how this function accepting NULL fits with standard / planned behaviour.
1016 * @param string|null $input
1017 * @param string $label e.g. 'default'
1019 * @return \Civi\Payment\PropertyBag
1021 public function setRecurProcessorID($input, $label = 'default') {
1022 if ($input === '') {
1025 if (strlen($input) > 255 ||
in_array($input, [FALSE, 0], TRUE)) {
1026 throw new \
InvalidArgumentException('processorID field has max length of 255');
1028 return $this->set('recurProcessorID', $label, $input);
1032 * Getter for payment processor generated string for the transaction ID.
1034 * Note some gateways generate a reference for the order and one for the
1035 * payment. This is for the payment reference and is saved to
1036 * civicrm_financial_trxn.trxn_id.
1038 * @param string $label
1040 * @return string|null
1042 public function getTransactionID($label = 'default') {
1043 return $this->get('transactionID', $label);
1047 * @param string $transactionID
1048 * @param string $label e.g. 'default'
1050 public function setTransactionID($transactionID, $label = 'default') {
1051 return $this->set('transactionID', $label, $transactionID);
1055 * Getter for trxnResultCode.
1057 * Additional information returned by the payment processor regarding the
1060 * This would normally be saved in civicrm_financial_trxn.trxn_result_code.
1063 * @param string $label
1065 * @return string|null
1067 public function getTrxnResultCode($label = 'default') {
1068 return $this->get('trxnResultCode', $label);
1072 * @param string $trxnResultCode
1073 * @param string $label e.g. 'default'
1075 public function setTrxnResultCode($trxnResultCode, $label = 'default') {
1076 return $this->set('trxnResultCode', $label, $trxnResultCode);
1079 // Custom Properties.
1082 * Sometimes we may need to pass in things that are specific to the Payment
1085 * @param string $prop
1086 * @param string $label e.g. 'default' or 'old' or 'new'
1089 * @throws InvalidArgumentException if trying to use this against a non-custom property.
1091 public function getCustomProperty($prop, $label = 'default') {
1092 if (isset(static::$propMap[$prop])) {
1093 throw new \
InvalidArgumentException("Attempted to get '$prop' via getCustomProperty - must use using its getter.");
1096 if (!array_key_exists($prop, $this->props
[$label] ??
[])) {
1097 throw new \
BadMethodCallException("Property '$prop' has not been set.");
1099 return $this->props
[$label][$prop] ??
NULL;
1103 * We have to leave validation to the processor, but we can still give them a
1104 * way to store their data on this PropertyBag
1106 * @param string $prop
1107 * @param mixed $value
1108 * @param string $label e.g. 'default' or 'old' or 'new'
1110 * @throws InvalidArgumentException if trying to use this against a non-custom property.
1112 public function setCustomProperty($prop, $value, $label = 'default') {
1113 if (isset(static::$propMap[$prop])) {
1114 throw new \
InvalidArgumentException("Attempted to set '$prop' via setCustomProperty - must use using its setter.");
1116 $this->props
[$label][$prop] = $value;