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