2 namespace Civi\Payment
;
4 use InvalidArgumentException
;
6 use CRM_Core_PseudoConstant
;
11 * This class provides getters and setters for arguments needed by CRM_Core_Payment methods.
13 * The setters know how to validate each setting that they are responsible for.
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.
20 * This class is also supposed to help with transition away from array key naming nightmares.
23 class PropertyBag
implements \ArrayAccess
{
25 protected $props = ['default' => []];
27 protected static $propMap = [
29 'billingStreetAddress' => TRUE,
30 'billing_street_address' => 'billingStreetAddress',
31 'street_address' => 'billingStreetAddress',
32 'billingSupplementalAddress1' => TRUE,
33 'billingSupplementalAddress2' => TRUE,
34 'billingSupplementalAddress3' => TRUE,
35 'billingCity' => TRUE,
36 'billing_city' => 'billingCity',
37 'city' => 'billingCity',
38 'billingPostalCode' => TRUE,
39 'billing_postal_code' => 'billingPostalCode',
40 'postal_code' => 'billingPostalCode',
41 'billingCounty' => TRUE,
42 'billingStateProvince' => TRUE,
43 'billing_state_province' => 'billingStateProvince',
44 'state_province' => 'billingStateProvince',
45 'billingCountry' => TRUE,
47 'contact_id' => 'contactID',
48 'contributionID' => TRUE,
49 'contribution_id' => 'contributionID',
50 'contributionRecurID' => TRUE,
51 'contribution_recur_id' => 'contributionRecurID',
53 'currencyID' => 'currency',
54 'description' => TRUE,
57 'fee_amount' => 'feeAmount',
58 'first_name' => 'firstName',
61 'invoice_id' => 'invoiceID',
62 'isBackOffice' => TRUE,
63 'is_back_office' => 'isBackOffice',
65 'is_recur' => 'isRecur',
66 'last_name' => 'lastName',
68 'paymentToken' => TRUE,
69 'payment_token' => 'paymentToken',
71 'recurFrequencyInterval' => TRUE,
72 'frequency_interval' => 'recurFrequencyInterval',
73 'recurFrequencyUnit' => TRUE,
74 'frequency_unit' => 'recurFrequencyUnit',
75 'recurInstallments' => TRUE,
76 'installments' => 'recurInstallments',
77 'subscriptionId' => 'recurProcessorID',
78 'recurProcessorID' => TRUE,
79 'transactionID' => TRUE,
80 'transaction_id' => 'transactionID',
81 'trxnResultCode' => TRUE,
82 'isNotifyProcessorOnCancelRecur' => TRUE,
88 * Temporary, internal variable to help ease transition to PropertyBag.
89 * Used by cast() to suppress legacy warnings.
90 * For paymentprocessors that have not converted to propertyBag we need to support "legacy" properties - eg. "is_recur"
91 * without warnings. Setting this allows us to pass a propertyBag into doPayment() and expect it to "work" with
92 * existing payment processors.
94 protected $suppressLegacyWarnings = TRUE;
97 * Get the value of the suppressLegacyWarnings parameter
100 public function getSuppressLegacyWarnings() {
101 return $this->suppressLegacyWarnings
;
105 * Set the suppressLegacyWarnings parameter - useful for unit tests.
106 * Eg. you could set to FALSE for unit tests on a paymentprocessor to capture use of legacy keys in that processor
108 * @param bool $suppressLegacyWarnings
110 public function setSuppressLegacyWarnings(bool $suppressLegacyWarnings) {
111 $this->suppressLegacyWarnings
= $suppressLegacyWarnings;
115 * Get the property bag.
117 * This allows us to swap a 'might be an array might be a property bag'
118 * variable for a known PropertyBag.
120 * @param \Civi\Payment\PropertyBag|array $params
122 * @return \Civi\Payment\PropertyBag
124 public static function cast($params) {
125 if ($params instanceof self
) {
128 $propertyBag = new self();
129 $propertyBag->mergeLegacyInputParams($params);
134 * Just for unit testing.
141 * Implements ArrayAccess::offsetExists
143 * @param mixed $offset
144 * @return bool TRUE if we have that value (on our default store)
146 public function offsetExists ($offset): bool {
147 $prop = $this->handleLegacyPropNames($offset, TRUE);
148 // If there's no prop, assume it's a custom property.
149 $prop = $prop ??
$offset;
150 return array_key_exists($prop, $this->props
['default']);
154 * Implements ArrayAccess::offsetGet
156 * @param mixed $offset
159 #[\ReturnTypeWillChange]
160 public function offsetGet($offset) {
162 $prop = $this->handleLegacyPropNames($offset);
164 catch (InvalidArgumentException
$e) {
166 if (!$this->getSuppressLegacyWarnings()) {
167 CRM_Core_Error
::deprecatedFunctionWarning(
168 "proper getCustomProperty('$offset') for non-core properties. "
170 "PropertyBag array access to get '$offset'"
175 return $this->getCustomProperty($offset, 'default');
177 catch (BadMethodCallException
$e) {
178 CRM_Core_Error
::deprecatedFunctionWarning(
179 "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."
181 "PropertyBag array access to get unset property '$offset'"
187 if (!$this->getSuppressLegacyWarnings()) {
188 CRM_Core_Error
::deprecatedFunctionWarning(
189 "get" . ucfirst($offset) . "()",
190 "PropertyBag array access for core property '$offset'"
193 return $this->get($prop, 'default');
197 * Implements ArrayAccess::offsetSet
199 * @param mixed $offset
200 * @param mixed $value
202 public function offsetSet($offset, $value): void
{
204 $prop = $this->handleLegacyPropNames($offset);
206 catch (InvalidArgumentException
$e) {
207 // We need to make a lot of noise here, we're being asked to merge in
208 // something we can't validate because we don't know what this property is.
209 // This is fine if it's something particular to a payment processor
210 // (which should be using setCustomProperty) however it could also lead to
211 // things like 'my_weirly_named_contact_id'.
213 // From 5.28 we suppress this when using PropertyBag::cast() to ease transition.
214 if (!$this->suppressLegacyWarnings
) {
215 CRM_Core_Error
::deprecatedFunctionWarning(
216 "proper setCustomProperty('$offset', \$value) for non-core properties. "
218 "PropertyBag array access to set '$offset'"
221 $this->setCustomProperty($offset, $value, 'default');
225 // Coerce legacy values that were in use but shouldn't be in our new way of doing things.
226 if ($prop === 'feeAmount' && $value === '') {
227 // At least the following classes pass in ZLS for feeAmount.
228 // CRM_Core_Payment_AuthorizeNetTest::testCreateSingleNowDated
229 // CRM_Core_Payment_AuthorizeNetTest::testCreateSinglePostDated
233 // These lines are here (and not in try block) because the catch must only
234 // catch the case when the prop is custom.
235 $setter = 'set' . ucfirst($prop);
236 if (!$this->suppressLegacyWarnings
) {
237 CRM_Core_Error
::deprecatedFunctionWarning(
239 "PropertyBag array access to set core property '$offset'"
242 $this->$setter($value, 'default');
246 * Implements ArrayAccess::offsetUnset
248 * @param mixed $offset
250 public function offsetUnset ($offset): void
{
251 $prop = $this->handleLegacyPropNames($offset);
252 unset($this->props
['default'][$prop]);
256 * @param string $prop
257 * @param bool $silent if TRUE return NULL instead of throwing an exception. This is because offsetExists should be safe and not throw exceptions.
258 * @return string canonical name.
259 * @throws \InvalidArgumentException if prop name not known.
261 protected function handleLegacyPropNames($prop, $silent = FALSE) {
262 $newName = static::$propMap[$prop] ??
NULL;
263 if ($newName === TRUE) {
264 // Good, modern name.
267 // Handling for legacy addition of billing details.
268 if ($newName === NULL && substr($prop, -2) === '-' . \CRM_Core_BAO_LocationType
::getBilling()
269 && isset(static::$propMap[substr($prop, 0, -2)])
271 $billingAddressProp = substr($prop, 0, -2);
272 $newName = static::$propMap[$billingAddressProp] ??
NULL;
273 if ($newName === TRUE) {
274 // Good, modern name.
275 return $billingAddressProp;
279 if ($newName === NULL) {
281 // Only for use by offsetExists
284 throw new \
InvalidArgumentException("Unknown property '$prop'.");
286 // Remaining case is legacy name that's been translated.
287 if (!$this->getSuppressLegacyWarnings()) {
288 CRM_Core_Error
::deprecatedFunctionWarning("Canonical property name '$newName'", "Legacy property name '$prop'");
297 * @param mixed $prop Valid property name
298 * @param string $label e.g. 'default'
302 protected function get($prop, $label) {
303 if (array_key_exists($prop, $this->props
[$label] ??
[])) {
304 return $this->props
[$label][$prop];
306 throw new \
BadMethodCallException("Property '$prop' has not been set.");
312 * @param mixed $prop Valid property name
313 * @param string $label e.g. 'default'
314 * @param mixed $value
316 * @return PropertyBag $this object so you can chain set setters.
318 protected function set($prop, $label, $value) {
319 $this->props
[$label][$prop] = $value;
326 protected function coercePseudoConstantStringToInt(string $baoName, string $field, $input) {
327 if (is_numeric($input)) {
328 // We've been given a numeric ID.
331 elseif (is_string($input)) {
332 // We've been given a named instrument.
333 $_ = (int) CRM_Core_PseudoConstant
::getKey($baoName, $field, $input);
336 throw new InvalidArgumentException("Expected an integer ID or a String name for $field.");
339 throw new InvalidArgumentException("Expected an integer greater than 0 for $field.");
346 public function has($prop, $label = 'default') {
347 // We do NOT translate legacy prop names since only new code should be
348 // using this method, and new code should be using canonical names.
349 // $prop = $this->handleLegacyPropNames($prop);
350 return isset($this->props
[$label][$prop]);
354 * This is used to merge values from an array.
355 * It's a transitional, internal function and should not be used!
359 public function mergeLegacyInputParams($data) {
360 // Suppress legacy warnings for merging an array of data as this
361 // suits our migration plan at this moment. Future behaviour may differ.
362 // @see https://github.com/civicrm/civicrm-core/pull/17643
363 $suppressLegacyWarnings = $this->getSuppressLegacyWarnings();
364 $this->setSuppressLegacyWarnings(TRUE);
365 foreach ($data as $key => $value) {
366 if ($value !== NULL && $value !== '') {
367 $this->offsetSet($key, $value);
370 $this->setSuppressLegacyWarnings($suppressLegacyWarnings);
374 * Throw an exception if any of the props is unset.
376 * @param array $props Array of proper property names (no legacy aliases allowed).
378 * @return PropertyBag
380 public function require(array $props) {
382 foreach ($props as $prop) {
383 if (!isset($this->props
['default'][$prop])) {
388 throw new \
InvalidArgumentException("Required properties missing: " . implode(', ', $missing));
393 // Public getters, setters.
396 * Get a property by its name (but still using its getter).
398 * @param string $prop valid property name, like contactID
399 * @param bool $allowUnset If TRUE, return the default value if the property is
400 * not set - normal behaviour would be to throw an exception.
401 * @param mixed $default
402 * @param string $label e.g. 'default' or 'old' or 'new'
406 public function getter($prop, $allowUnset = FALSE, $default = NULL, $label = 'default') {
408 if ((static::$propMap[$prop] ??
NULL) === TRUE) {
409 // This is a standard property that will have a getter method.
410 $getter = 'get' . ucfirst($prop);
411 return (!$allowUnset ||
$this->has($prop, $label))
412 ?
$this->$getter($label)
416 // This is not a property name we know, but they could be requesting a
418 return (!$allowUnset ||
$this->has($prop, $label))
419 ?
$this->getCustomProperty($prop, $label)
424 * Set a property by its name (but still using its setter).
426 * @param string $prop valid property name, like contactID
427 * @param mixed $value
428 * @param string $label e.g. 'default' or 'old' or 'new'
432 public function setter($prop, $value = NULL, $label = 'default') {
433 if ((static::$propMap[$prop] ??
NULL) === TRUE) {
434 // This is a standard property.
435 $setter = 'set' . ucfirst($prop);
436 return $this->$setter($value, $label);
438 // We don't allow using the setter for custom properties.
439 throw new \
BadMethodCallException("Cannot use generic setter with non-standard properties; you must use setCustomProperty for custom properties.");
443 * Get the monetary amount.
445 public function getAmount($label = 'default') {
446 return $this->get('amount', $label);
450 * Set the monetary amount.
452 * - We expect to be called with a string amount with optional decimals using
453 * a '.' as the decimal point (not a ',').
455 * - We're ok with floats/ints being passed in, too, but we'll cast them to a
458 * - Negatives are fine.
460 * @see https://github.com/civicrm/civicrm-core/pull/18219
462 * @param string|float|int $value
463 * @param string $label
465 public function setAmount($value, $label = 'default') {
466 if (!is_numeric($value)) {
467 throw new \
InvalidArgumentException("setAmount requires a numeric amount value");
469 return $this->set('amount', $label, filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT
, FILTER_FLAG_ALLOW_FRACTION
));
473 * BillingStreetAddress getter.
477 public function getBillingStreetAddress($label = 'default') {
478 return $this->get('billingStreetAddress', $label);
482 * BillingStreetAddress setter.
484 * @param string $input
485 * @param string $label e.g. 'default'
487 public function setBillingStreetAddress($input, $label = 'default') {
488 return $this->set('billingStreetAddress', $label, (string) $input);
492 * BillingSupplementalAddress1 getter.
496 public function getBillingSupplementalAddress1($label = 'default') {
497 return $this->get('billingSupplementalAddress1', $label);
501 * BillingSupplementalAddress1 setter.
503 * @param string $input
504 * @param string $label e.g. 'default'
506 public function setBillingSupplementalAddress1($input, $label = 'default') {
507 return $this->set('billingSupplementalAddress1', $label, (string) $input);
511 * BillingSupplementalAddress2 getter.
515 public function getBillingSupplementalAddress2($label = 'default') {
516 return $this->get('billingSupplementalAddress2', $label);
520 * BillingSupplementalAddress2 setter.
522 * @param string $input
523 * @param string $label e.g. 'default'
525 public function setBillingSupplementalAddress2($input, $label = 'default') {
526 return $this->set('billingSupplementalAddress2', $label, (string) $input);
530 * BillingSupplementalAddress3 getter.
534 public function getBillingSupplementalAddress3($label = 'default') {
535 return $this->get('billingSupplementalAddress3', $label);
539 * BillingSupplementalAddress3 setter.
541 * @param string $input
542 * @param string $label e.g. 'default'
544 public function setBillingSupplementalAddress3($input, $label = 'default') {
545 return $this->set('billingSupplementalAddress3', $label, (string) $input);
549 * BillingCity getter.
553 public function getBillingCity($label = 'default') {
554 return $this->get('billingCity', $label);
558 * BillingCity setter.
560 * @param string $input
561 * @param string $label e.g. 'default'
563 * @return \Civi\Payment\PropertyBag
565 public function setBillingCity($input, $label = 'default') {
566 return $this->set('billingCity', $label, (string) $input);
570 * BillingPostalCode getter.
574 public function getBillingPostalCode($label = 'default') {
575 return $this->get('billingPostalCode', $label);
579 * BillingPostalCode setter.
581 * @param string $input
582 * @param string $label e.g. 'default'
584 public function setBillingPostalCode($input, $label = 'default') {
585 return $this->set('billingPostalCode', $label, (string) $input);
589 * BillingCounty getter.
593 public function getBillingCounty($label = 'default') {
594 return $this->get('billingCounty', $label);
598 * BillingCounty setter.
600 * Nb. we can't validate this unless we have the country ID too, so we don't.
602 * @param string $input
603 * @param string $label e.g. 'default'
605 public function setBillingCounty($input, $label = 'default') {
606 return $this->set('billingCounty', $label, (string) $input);
610 * BillingStateProvince getter.
614 public function getBillingStateProvince($label = 'default') {
615 return $this->get('billingStateProvince', $label);
619 * BillingStateProvince setter.
621 * Nb. we can't validate this unless we have the country ID too, so we don't.
623 * @param string $input
624 * @param string $label e.g. 'default'
626 public function setBillingStateProvince($input, $label = 'default') {
627 return $this->set('billingStateProvince', $label, (string) $input);
631 * BillingCountry getter.
635 public function getBillingCountry($label = 'default') {
636 return $this->get('billingCountry', $label);
640 * BillingCountry setter.
642 * Nb. We require and we store a 2 character country code.
644 * @param string $input
645 * @param string $label e.g. 'default'
647 public function setBillingCountry($input, $label = 'default') {
648 if (!is_string($input) ||
strlen($input) !== 2) {
649 throw new \
InvalidArgumentException("setBillingCountry expects ISO 3166-1 alpha-2 country code.");
651 if (!CRM_Core_PseudoConstant
::getKey('CRM_Core_BAO_Address', 'country_id', $input)) {
652 throw new \
InvalidArgumentException("setBillingCountry expects ISO 3166-1 alpha-2 country code.");
654 return $this->set('billingCountry', $label, (string) $input);
660 public function getContactID($label = 'default'): int {
661 return $this->get('contactID', $label);
665 * @param int $contactID
666 * @param string $label
668 public function setContactID($contactID, $label = 'default') {
669 // We don't use this because it counts zero as positive: CRM_Utils_Type::validate($contactID, 'Positive');
670 if (!($contactID > 0)) {
671 throw new InvalidArgumentException("ContactID must be a positive integer");
674 return $this->set('contactID', $label, (int) $contactID);
678 * Getter for contributionID.
681 * @param string $label
683 public function getContributionID($label = 'default') {
684 return $this->get('contributionID', $label);
688 * @param int $contributionID
689 * @param string $label e.g. 'default'
691 public function setContributionID($contributionID, $label = 'default') {
692 // We don't use this because it counts zero as positive: CRM_Utils_Type::validate($contactID, 'Positive');
693 if (!($contributionID > 0)) {
694 throw new InvalidArgumentException("ContributionID must be a positive integer");
697 return $this->set('contributionID', $label, (int) $contributionID);
701 * Getter for contributionRecurID.
704 * @param string $label
706 public function getContributionRecurID($label = 'default') {
707 return $this->get('contributionRecurID', $label);
711 * @param int $contributionRecurID
712 * @param string $label e.g. 'default'
714 * @return \Civi\Payment\PropertyBag
716 public function setContributionRecurID($contributionRecurID, $label = 'default') {
717 // We don't use this because it counts zero as positive: CRM_Utils_Type::validate($contactID, 'Positive');
718 if (!($contributionRecurID > 0)) {
719 throw new InvalidArgumentException('ContributionRecurID must be a positive integer');
722 return $this->set('contributionRecurID', $label, (int) $contributionRecurID);
726 * Three character currency code.
728 * https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3
730 * @param string $label e.g. 'default'
732 public function getCurrency($label = 'default') {
733 return $this->get('currency', $label);
737 * Three character currency code.
739 * @param string $value
740 * @param string $label e.g. 'default'
742 public function setCurrency($value, $label = 'default') {
743 if (!preg_match('/^[A-Z]{3}$/', $value)) {
744 throw new \
InvalidArgumentException("Attemted to setCurrency with a value that was not an ISO 3166-1 alpha 3 currency code");
746 return $this->set('currency', $label, $value);
751 * @param string $label
755 public function getDescription($label = 'default') {
756 return $this->get('description', $label);
760 * @param string $description
761 * @param string $label e.g. 'default'
763 public function setDescription($description, $label = 'default') {
764 // @todo this logic was copied from a commit that then got deleted. Is it good?
765 $uninformativeStrings = [
766 ts('Online Event Registration: '),
767 ts('Online Contribution: '),
769 $cleanedDescription = str_replace($uninformativeStrings, '', $description);
770 return $this->set('description', $label, $cleanedDescription);
778 public function getEmail($label = 'default') {
779 return $this->get('email', $label);
785 * @param string $email
786 * @param string $label e.g. 'default'
788 public function setEmail($email, $label = 'default') {
789 return $this->set('email', $label, (string) $email);
793 * Amount of money charged in fees by the payment processor.
795 * This is notified by (some) payment processers.
797 * @param string $label
801 public function getFeeAmount($label = 'default') {
802 return $this->get('feeAmount', $label);
806 * @param string $feeAmount
807 * @param string $label e.g. 'default'
809 public function setFeeAmount($feeAmount, $label = 'default') {
810 if (!is_numeric($feeAmount)) {
811 throw new \
InvalidArgumentException("feeAmount must be a number.");
813 return $this->set('feeAmount', $label, (float) $feeAmount);
821 public function getFirstName($label = 'default') {
822 return $this->get('firstName', $label);
828 * @param string $firstName
829 * @param string $label e.g. 'default'
831 public function setFirstName($firstName, $label = 'default') {
832 return $this->set('firstName', $label, (string) $firstName);
836 * Getter for invoiceID.
838 * @param string $label
840 * @return string|null
842 public function getInvoiceID($label = 'default') {
843 return $this->get('invoiceID', $label);
847 * @param string $invoiceID
848 * @param string $label e.g. 'default'
850 public function setInvoiceID($invoiceID, $label = 'default') {
851 return $this->set('invoiceID', $label, $invoiceID);
855 * Getter for isBackOffice.
857 * @param string $label
861 public function getIsBackOffice($label = 'default'):bool {
862 // @todo should this return FALSE instead of exception to keep current situation?
863 return $this->get('isBackOffice', $label);
867 * @param bool $isBackOffice
868 * @param string $label e.g. 'default'
870 public function setIsBackOffice($isBackOffice, $label = 'default') {
871 if (is_null($isBackOffice)) {
872 throw new \
InvalidArgumentException("isBackOffice must be a bool, received NULL.");
874 return $this->set('isBackOffice', $label, (bool) $isBackOffice);
878 * Getter for isRecur.
880 * @param string $label
884 public function getIsRecur($label = 'default'):bool {
885 if (!$this->has('isRecur')) {
888 return $this->get('isRecur', $label);
892 * @param bool $isRecur
893 * @param string $label e.g. 'default'
895 public function setIsRecur($isRecur, $label = 'default') {
896 if (is_null($isRecur)) {
897 throw new \
InvalidArgumentException("isRecur must be a bool, received NULL.");
899 return $this->set('isRecur', $label, (bool) $isRecur);
903 * Set whether the user has selected to notify the processor of a cancellation request.
905 * When cancelling the user may be presented with an option to notify the processor. The payment
906 * processor can take their response, if present, into account.
909 * @param string $label e.g. 'default'
911 * @return \Civi\Payment\PropertyBag
913 public function setIsNotifyProcessorOnCancelRecur($value, $label = 'default') {
914 return $this->set('isNotifyProcessorOnCancelRecur', $label, (bool) $value);
918 * Get whether the user has selected to notify the processor of a cancellation request.
920 * When cancelling the user may be presented with an option to notify the processor. The payment
921 * processor can take their response, if present, into account.
923 * @param string $label e.g. 'default'
925 * @return \Civi\Payment\PropertyBag
927 public function getIsNotifyProcessorOnCancelRecur($label = 'default') {
928 return $this->get('isNotifyProcessorOnCancelRecur', $label);
936 public function getLastName($label = 'default') {
937 return $this->get('lastName', $label);
943 * @param string $lastName
944 * @param string $label e.g. 'default'
946 public function setLastName($lastName, $label = 'default') {
947 return $this->set('lastName', $label, (string) $lastName);
951 * Getter for payment processor generated string for charging.
953 * A payment token could be a single use token (e.g generated by
954 * a client side script) or a token that permits recurring or on demand charging.
956 * The key thing is it is passed to the processor in lieu of card details to
957 * initiate a payment.
959 * Generally if a processor is going to pass in a payment token generated through
960 * javascript it would add 'payment_token' to the array it returns in it's
961 * implementation of getPaymentFormFields. This will add a hidden 'payment_token' field to
962 * the form. A good example is client side encryption where credit card details are replaced by
963 * an encrypted token using a gateway provided javascript script. In this case the javascript will
964 * remove the credit card details from the form before submitting and populate the payment_token field.
966 * A more complex example is used by paypal checkout where the payment token is generated
967 * via a pre-approval process. In that case the doPreApproval function is called on the processor
968 * class to get information to then interact with paypal via js, finally getting a payment token.
969 * (at this stage the pre-approve api is not in core but that is likely to change - we just need
970 * to think about the permissions since we don't want to expose to anonymous user without thinking
971 * through any risk of credit-card testing using it.
973 * If the token is not totally transient it would be saved to civicrm_payment_token.token.
975 * @param string $label
977 * @return string|null
979 public function getPaymentToken($label = 'default') {
980 return $this->get('paymentToken', $label);
984 * @param string $paymentToken
985 * @param string $label e.g. 'default'
987 public function setPaymentToken($paymentToken, $label = 'default') {
988 return $this->set('paymentToken', $label, $paymentToken);
996 public function getPhone($label = 'default') {
997 return $this->get('phone', $label);
1003 * @param string $phone
1004 * @param string $label e.g. 'default'
1006 public function setPhone($phone, $label = 'default') {
1007 return $this->set('phone', $label, (string) $phone);
1011 * Combined with recurFrequencyUnit this gives how often billing will take place.
1013 * e.g every if this is 1 and recurFrequencyUnit is 'month' then it is every 1 month.
1016 public function getRecurFrequencyInterval($label = 'default') {
1017 return $this->get('recurFrequencyInterval', $label);
1021 * @param int $recurFrequencyInterval
1022 * @param string $label e.g. 'default'
1024 public function setRecurFrequencyInterval($recurFrequencyInterval, $label = 'default') {
1025 if (!($recurFrequencyInterval > 0)) {
1026 throw new InvalidArgumentException("recurFrequencyInterval must be a positive integer");
1029 return $this->set('recurFrequencyInterval', $label, (int) $recurFrequencyInterval);
1033 * Getter for recurFrequencyUnit.
1034 * Combined with recurFrequencyInterval this gives how often billing will take place.
1036 * e.g every if this is 'month' and recurFrequencyInterval is 1 then it is every 1 month.
1039 * @param string $label
1041 * @return string month|day|year
1043 public function getRecurFrequencyUnit($label = 'default') {
1044 return $this->get('recurFrequencyUnit', $label);
1048 * @param string $recurFrequencyUnit month|day|week|year
1049 * @param string $label e.g. 'default'
1051 public function setRecurFrequencyUnit($recurFrequencyUnit, $label = 'default') {
1052 if (!preg_match('/^day|week|month|year$/', ($recurFrequencyUnit ??
''))) {
1053 throw new \
InvalidArgumentException("recurFrequencyUnit must be day|week|month|year");
1055 return $this->set('recurFrequencyUnit', $label, $recurFrequencyUnit);
1059 * @param string $label
1063 public function getRecurInstallments($label = 'default') {
1064 return $this->get('recurInstallments', $label);
1068 * @param int $recurInstallments
1069 * @param string $label
1071 * @return \Civi\Payment\PropertyBag
1072 * @throws \CRM_Core_Exception
1074 public function setRecurInstallments($recurInstallments, $label = 'default') {
1075 // Counts zero as positive which is ok - means no installments
1077 \CRM_Utils_Type
::validate($recurInstallments, 'Positive');
1079 catch (\CRM_Core_Exception
$e) {
1080 throw new InvalidArgumentException('recurInstallments must be 0 or a positive integer');
1083 return $this->set('recurInstallments', $label, (int) $recurInstallments);
1087 * Set the unique payment processor service provided ID for a particular subscription.
1089 * Nb. this is stored in civicrm_contribution_recur.processor_id and is NOT
1090 * in any way related to the payment processor ID.
1092 * @param string $label
1094 * @return string|null
1096 public function getRecurProcessorID($label = 'default') {
1097 return $this->get('recurProcessorID', $label);
1101 * Set the unique payment processor service provided ID for a particular
1104 * See https://github.com/civicrm/civicrm-core/pull/17292 for discussion
1105 * of how this function accepting NULL fits with standard / planned behaviour.
1107 * @param string|null $input
1108 * @param string $label e.g. 'default'
1110 * @return \Civi\Payment\PropertyBag
1112 public function setRecurProcessorID($input, $label = 'default') {
1113 if ($input === '') {
1116 if (strlen($input ??
'') > 255 ||
in_array($input, [FALSE, 0], TRUE)) {
1117 throw new \
InvalidArgumentException('processorID field has max length of 255');
1119 return $this->set('recurProcessorID', $label, $input);
1123 * Getter for payment processor generated string for the transaction ID.
1125 * Note some gateways generate a reference for the order and one for the
1126 * payment. This is for the payment reference and is saved to
1127 * civicrm_financial_trxn.trxn_id.
1129 * @param string $label
1131 * @return string|null
1133 public function getTransactionID($label = 'default') {
1134 return $this->get('transactionID', $label);
1138 * @param string $transactionID
1139 * @param string $label e.g. 'default'
1141 public function setTransactionID($transactionID, $label = 'default') {
1142 return $this->set('transactionID', $label, $transactionID);
1146 * Getter for trxnResultCode.
1148 * Additional information returned by the payment processor regarding the
1151 * This would normally be saved in civicrm_financial_trxn.trxn_result_code.
1154 * @param string $label
1156 * @return string|null
1158 public function getTrxnResultCode($label = 'default') {
1159 return $this->get('trxnResultCode', $label);
1163 * @param string $trxnResultCode
1164 * @param string $label e.g. 'default'
1166 public function setTrxnResultCode($trxnResultCode, $label = 'default') {
1167 return $this->set('trxnResultCode', $label, $trxnResultCode);
1170 // Custom Properties.
1173 * Sometimes we may need to pass in things that are specific to the Payment
1176 * @param string $prop
1177 * @param string $label e.g. 'default' or 'old' or 'new'
1180 * @throws InvalidArgumentException if trying to use this against a non-custom property.
1182 public function getCustomProperty($prop, $label = 'default') {
1183 if (isset(static::$propMap[$prop])) {
1184 throw new \
InvalidArgumentException("Attempted to get '$prop' via getCustomProperty - must use using its getter.");
1187 if (!array_key_exists($prop, $this->props
[$label] ??
[])) {
1188 throw new \
BadMethodCallException("Property '$prop' has not been set.");
1190 return $this->props
[$label][$prop] ??
NULL;
1194 * We have to leave validation to the processor, but we can still give them a
1195 * way to store their data on this PropertyBag
1197 * @param string $prop
1198 * @param mixed $value
1199 * @param string $label e.g. 'default' or 'old' or 'new'
1201 * @throws InvalidArgumentException if trying to use this against a non-custom property.
1203 public function setCustomProperty($prop, $value, $label = 'default') {
1204 if (isset(static::$propMap[$prop])) {
1205 throw new \
InvalidArgumentException("Attempted to set '$prop' via setCustomProperty - must use using its setter.");
1207 $this->props
[$label][$prop] = $value;