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