Merge pull request #16052 from eileenmcnaughton/desc
[civicrm-core.git] / CRM / Core / Payment.php
index 089dffc822152ac165381bb8f703411f07a2fbc0..1af72a2301f03e52b8dbd79952d03d886dea4fe9 100644 (file)
@@ -1,32 +1,17 @@
 <?php
 /*
  +--------------------------------------------------------------------+
- | CiviCRM version 5                                                  |
- +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2019                                |
- +--------------------------------------------------------------------+
- | This file is a part of CiviCRM.                                    |
- |                                                                    |
- | CiviCRM is free software; you can copy, modify, and distribute it  |
- | under the terms of the GNU Affero General Public License           |
- | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
- |                                                                    |
- | CiviCRM is distributed in the hope that it will be useful, but     |
- | WITHOUT ANY WARRANTY; without even the implied warranty of         |
- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
- | See the GNU Affero General Public License for more details.        |
+ | Copyright CiviCRM LLC. All rights reserved.                        |
  |                                                                    |
- | You should have received a copy of the GNU Affero General Public   |
- | License and the CiviCRM Licensing Exception along                  |
- | with this program; if not, contact CiviCRM LLC                     |
- | at info[AT]civicrm[DOT]org. If you have questions about the        |
- | GNU Affero General Public License or the licensing of CiviCRM,     |
- | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
  +--------------------------------------------------------------------+
  */
 
 use Civi\Payment\System;
 use Civi\Payment\Exception\PaymentProcessorException;
+use Civi\Payment\PropertyBag;
 
 /**
  * Class CRM_Core_Payment.
@@ -150,378 +135,60 @@ abstract class CRM_Core_Payment {
   protected $backOffice = FALSE;
 
   /**
-   * Contribution that is being paid.
-   *
-   * @var int
-   */
-  protected $contributionID;
-
-  /**
-   * Contact ID for payment.
-   *
-   * @var int
-   */
-  protected $contactID;
-
-  /**
-   * Recurring contribution that is being paid.
-   *
-   * @var int
-   */
-  protected $contributionRecurID;
-
-  /**
-   * Description of purchase.
-   *
-   * @var string
-   */
-  protected $description;
-
-  /**
-   * CiviCRM generated Invoice ID.
-   *
-   * @var string
-   */
-  protected $invoiceID;
-
-  /**
-   * Is this a recurring contribution.
-   *
-   * @var bool
-   */
-  protected $isRecur;
-
-  /**
-   * Payment processor generated string for charging.
-   *
-   * A payment token could be a single use token (e.g generated by
-   * a client side script) or a token that permits recurring or on demand charging.
-   *
-   * The key thing is it is passed to the processor in lieu of card details to
-   * initiate a payment.
-   *
-   * Generally if a processor is going to pass in a payment token generated through
-   * javascript it would add 'payment_token' to the array it returns in it's
-   * implementation of getPaymentFormFields. This will add a hidden 'payment_token' field to
-   * the form. A good example is client side encryption where credit card details are replaced by
-   * an encrypted token using a gateway provided javascript script. In this case the javascript will
-   * remove the credit card details from the form before submitting and populate the payment_token field.
-   *
-   * A more complex example is used by paypal checkout where the payment token is generated
-   * via a pre-approval process. In that case the doPreApproval function is called on the processor
-   * class to get information to then interact with paypal via js, finally getting a payment token.
-   * (at this stage the pre-approve api is not in core but that is likely to change - we just need
-   * to think about the permissions since we don't want to expose to anonymous user without thinking
-   * through any risk of credit-card testing using it.
-   *
-   * If the token is not totally transient it would be saved to civicrm_payment_token.token.
-   *
-   * @var string
-   */
-  protected $paymentToken;
-
-  /**
-   * Three digit currency code.
-   *
-   * @var string
-   */
-  protected $currency;
-
-  /**
-   * Payment processor generated string for the transaction ID.
-   *
-   * Note some gateways generate a reference for the order and one for the
-   * payment. This is for the payment reference and is saved to
-   * civicrm_financial_trxn.trxn_id.
-   *
-   * @var string
-   */
-  protected $transactionID;
-
-  /**
-   * Amount of money charged in fees by the payment processor.
-   *
-   * This is notified by (some) payment processers.
-   *
-   * @var float
-   */
-  protected $feeAmount;
-
-  /**
-   * Additional information returned by the payment processor regarding the payment outcome.
+   * This is only needed during the transitional phase. In future you should
+   * pass your own PropertyBag into the method you're calling.
    *
-   * This would normally be saved in civicrm_financial_trxn.trxn_result_code.
+   * New code should NOT use $this->propertyBag.
    *
-   * @var string
-   */
-  protected $trxnResultCode;
-
-  /**
-   * Combined with recurFrequencyUnit this gives how often billing will take place.
-   *
-   * e.g every if this is 1 and recurFrequencyUnit is 'month' then it is every 1 month.
-   *
-   * @var int
+   * @var Civi\Payment\PropertyBag
    */
-  protected $recurFrequencyInterval;
+  protected $propertyBag;
 
   /**
-   * Combined with recurFrequencyInterval this gives how often billing will take place.
-   *
-   * e.g every if this is 'month' and recurFrequencyInterval is 1 then it is every 1 month.
+   * Return the payment instrument ID to use.
    *
-   * @var string
-   *   month|day|year
-   */
-  protected $recurFrequencyUnit;
-
-  /**
-   * Passed in parameters.
-   *
-   * Using individual getters & setters is preferred but these will be used if
-   * they are not available.
-   *
-   * @var array
-   */
-  protected $inputParams = [];
-
-  /**
-   * @return int
-   */
-  public function getContactID(): int {
-    return $this->contactID ?? $this->inputParams['contactID'];
-  }
-
-  /**
-   * @param int $contactID
-   */
-  public function setContactID(int $contactID) {
-    $this->contactID = $contactID;
-  }
-
-  /**
-   * Getter for contributionRecurID.
+   * Note:
+   * We normally SHOULD be returning the payment instrument of the payment processor.
+   * However there is an outstanding case where this needs overriding, which is
+   * when using CRM_Core_Payment_Manual which uses the pseudoprocessor (id = 0).
    *
-   * Ideally this would always be set by the calling function using the setting
-   * but we need to fall back to the historical passing on inputParams as a transition.
+   * i.e. If you're writing a Payment Processor you should NOT be using
+   * setPaymentInstrumentID() at all.
    *
-   * In time we can add a notice if it's set in input params & not via a setter.
+   * @todo
+   * Ideally this exception-to-the-rule should be handled outside of this class
+   * i.e. this class's getPaymentInstrumentID method should return it from the
+   * payment processor and CRM_Core_Payment_Manual could override it to provide 0.
    *
    * @return int
    */
-  public function getContributionRecurID(): int {
-    return $this->contributionRecurID ?? $this->inputParams['contributionRecurID'];
-  }
-
-  /**
-   * @param int $contributionRecurID
-   */
-  public function setContributionRecurID(int $contributionRecurID) {
-    $this->contributionRecurID = $contributionRecurID;
-  }
-
-  /**
-   * Getter for Payment Token.
-   *
-   * @return string
-   */
-  public function getPaymentToken(): string {
-    return $this->paymentToken ?? $this->inputParams['token'];
-  }
-
-  /**
-   * Setter for Payment Token.
-   *
-   * @param string $paymentToken
-   */
-  public function setPaymentToken(string $paymentToken) {
-    $this->paymentToken = $paymentToken;
-  }
-
-  /**
-   * Get gateway generated transaction ID.
-   *
-   * @return string
-   */
-  public function getTransactionID(): string {
-    return $this->transactionID;
-  }
-
-  /**
-   * Set gateway generated transaction ID for the payment.
-   *
-   * Note some gateways generate a reference for the order and one for the
-   * payment. This is for the payment reference and is saved to
-   * civicrm_financial_trxn.trxn_id.
-   *
-   * @param string $transactionID
-   */
-  public function setTransactionID(string $transactionID) {
-    $this->transactionID = $transactionID;
-  }
-
-  /**
-   * Get additional result information received from gateway.
-   *
-   * This is saved to civicrm_financial_trxn.trxn_result_code.
-   *
-   * @return string
-   */
-  public function getTrxnResultCode(): string {
-    return $this->trxnResultCode;
-  }
-
-  /**
-   * Set additional result information from gateway.
-   *
-   * This is saved to civicrm_financial_trxn.trxn_result_code.
-   *
-   * @param string $resultCode
-   */
-  public function setTrxnResultCode(string $resultCode) {
-    $this->trxnResultCode = $resultCode;
+  public function getPaymentInstrumentID() {
+    return isset($this->paymentInstrumentID)
+      ? $this->paymentInstrumentID
+      : (int) ($this->_paymentProcessor['payment_instrument_id'] ?? 0);
   }
 
   /**
-   * Get recurring frequency interval.
+   * Getter for the id Payment Processor ID.
    *
    * @return int
    */
-  public function getRecurFrequencyInterval(): int {
-    return $this->recurFrequencyInterval ?? $this->inputParams['frequency_interval'];
-  }
-
-  /**
-   * Set recurring frequency interval.
-   *
-   * @param int $recurFrequencyInterval
-   */
-  public function setRecurFrequencyInterval(int $recurFrequencyInterval) {
-    $this->recurFrequencyInterval = $recurFrequencyInterval;
-  }
-
-  /**
-   * Get recurring frequency unit.
-   *
-   * @return string
-   */
-  public function getRecurFrequencyUnit(): string {
-    return $this->recurFrequencyUnit;
-  }
-
-  /**
-   * Set recurring frequency unit.
-   *
-   * @param string $recurFrequencyUnit
-   */
-  public function setRecurFrequencyUnit(string $recurFrequencyUnit) {
-    $this->recurFrequencyUnit = $recurFrequencyUnit ?? $this->inputParams['frequency_unit'];
-  }
-
-  /**
-   * Get invoice ID (CiviCRM generated invoice reference).
-   *
-   * @return string
-   */
-  public function getInvoiceID(): string {
-    return $this->invoiceID ?? $this->inputParams['invoiceID'];
-  }
-
-  /**
-   * Set invoice ID (CiviCRM generated invoice reference).
-   *
-   * @param string $invoiceID
-   */
-  public function setInvoiceID(string $invoiceID) {
-    $this->invoiceID = $invoiceID;
-  }
-
-  /**
-   * Get whether the payment is recurring.
-   *
-   * @return bool
-   */
-  public function isRecur(): bool {
-    return $this->isRecur;
-  }
-
-  /**
-   * Set whether the payment is recurring.
-   *
-   * @param bool $isRecur
-   */
-  public function setIsRecur(bool $isRecur) {
-    $this->isRecur = $isRecur ?? $this->inputParams['is_recur'];
-  }
-
-  /**
-   * Get the description.
-   *
-   * This generates a description string from params if one has been passed in but
-   * ideally calling functions would use the setDescription function.
-   *
-   * @return string
-   */
-  public function getDescription(): string {
-    if ($this->description) {
-      return $this->description;
-    }
-    if (isset($this->inputParams['description'])) {
-      $uninformativeStrings = [
-        ts('Online Event Registration: '),
-        ts('Online Contribution: '),
-      ];
-      $this->description = str_replace($uninformativeStrings, '', $this->inputParams['description']);
-    }
-    return $this->description;
-  }
-
-  /**
-   * Set description.
-   *
-   * Generally this should be called when instantiating the processor to override
-   * getPaymentDescription, if desired.
-   *
-   * @param string $description
-   */
-  public function setDescription(string $description) {
-    $this->description = $description;
-  }
-
-  /**
-   * Get fee amount returned by processor.
-   *
-   * @return float
-   */
-  public function getFeeAmount(): float {
-    return $this->feeAmount;
-  }
-
-  /**
-   * Set fee amount returned by processor.
-   *
-   * @param float $feeAmount
-   */
-  public function setFeeAmount(float $feeAmount) {
-    $this->feeAmount = $feeAmount;
+  public function getID() {
+    return (int) $this->_paymentProcessor['id'];
   }
 
   /**
-   * Get the contribution ID.
+   * @deprecated Set payment Instrument id - see note on getPaymentInstrumentID.
    *
-   * We prefer the one set by the setter but traditional forms just pass in 'contributionID'.
+   * By default we actually ignore the form value. The manual processor takes
+   * it more seriously.
    *
-   * @return int
-   */
-  public function getContributionID(): int {
-    return $this->contributionID ?? $this->inputParams['contributionID'];
-  }
-
-  /**
-   * @param int $contributionID
+   * @param int $paymentInstrumentID
    */
-  public function setContributionID(int $contributionID) {
-    $this->contributionID = $contributionID;
+  public function setPaymentInstrumentID($paymentInstrumentID) {
+    $this->paymentInstrumentID = (int) $paymentInstrumentID;
+    // See note on getPaymentInstrumentID().
+    return $this->getPaymentInstrumentID();
   }
 
   /**
@@ -540,35 +207,6 @@ abstract class CRM_Core_Payment {
     $this->backOffice = $isBackOffice;
   }
 
-  /**
-   * Get payment instrument id.
-   *
-   * @return int
-   */
-  public function getPaymentInstrumentID() {
-    return $this->paymentInstrumentID ? $this->paymentInstrumentID : $this->_paymentProcessor['payment_instrument_id'];
-  }
-
-  /**
-   * Getter for the id.
-   *
-   * @return int
-   */
-  public function getID() {
-    return (int) $this->_paymentProcessor['id'];
-  }
-
-  /**
-   * Set payment Instrument id.
-   *
-   * By default we actually ignore the form value. The manual processor takes it more seriously.
-   *
-   * @param int $paymentInstrumentID
-   */
-  public function setPaymentInstrumentID($paymentInstrumentID) {
-    $this->paymentInstrumentID = $this->_paymentProcessor['payment_instrument_id'];
-  }
-
   /**
    * Set base return path (offsite processors).
    *
@@ -1104,8 +742,6 @@ abstract class CRM_Core_Payment {
    *
    * @return array
    *   field metadata
-   *
-   * @throws \Exception
    */
   public function getPaymentFormFieldsMetadata() {
     //@todo convert credit card type into an option value
@@ -1437,23 +1073,25 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Get the currency for the transaction.
+   * Get the currency for the transaction from the params.
+   *
+   * Legacy wrapper. Better for a method to work on its own PropertyBag.
    *
-   * Handle any inconsistency about how it is passed in here.
+   * This code now uses PropertyBag to allow for old inputs like currencyID.
    *
    * @param $params
    *
    * @return string
    */
   protected function getCurrency($params = []) {
-    $params = array_merge($params, (array) $this->inputParams);
-    return $this->currency ?? CRM_Utils_Array::value('currencyID', $params, CRM_Utils_Array::value('currency', $params));
+    $localPropertyBag = new PropertyBag();
+    $localPropertyBag->mergeLegacyInputParams($params);
+    return $localPropertyBag->getCurrency();
   }
 
   /**
-   * Get the currency for the transaction.
-   *
-   * Handle any inconsistency about how it is passed in here.
+   * Legacy. Better for a method to work on its own PropertyBag,
+   * but also, this function does not do very much.
    *
    * @param array $params
    *
@@ -1461,8 +1099,7 @@ abstract class CRM_Core_Payment {
    * @throws \CRM_Core_Exception
    */
   protected function getAmount($params = []) {
-    $amount = $this->amount ?? $params['amount'];
-    return CRM_Utils_Money::format($amount, NULL, NULL, TRUE);
+    return CRM_Utils_Money::format($params['amount'], NULL, NULL, TRUE);
   }
 
   /**
@@ -1599,6 +1236,23 @@ abstract class CRM_Core_Payment {
     return $params;
   }
 
+  /**
+   * Processors may need to inspect, validate, cast and copy data that is
+   * specific to this Payment Processor from the input array to custom fields
+   * on the PropertyBag.
+   *
+   * @param Civi\Payment\PropertyBag $propertyBag
+   * @param array $params
+   * @param string $component
+   *
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public function extractCustomPropertiesForDoPayment(PropertyBag $propertyBag, array $params, $component = 'contribute') {
+    // example
+    // (validation and casting goes first)
+    // $propertyBag->setCustomProperty('myprocessor_customPropertyName', $value);
+  }
+
   /**
    * Process payment - this function wraps around both doTransferCheckout and doDirectPayment.
    * Any processor that still implements the deprecated doTransferCheckout() or doDirectPayment() should be updated to use doPayment().
@@ -1614,7 +1268,7 @@ abstract class CRM_Core_Payment {
    *  For the current status see: https://lab.civicrm.org/dev/financial/issues/53
    * If we DO have a contribution ID, then the payment processor can (and should) update parameters on the contribution record as necessary.
    *
-   * @param array $params
+   * @param array|PropertyBag $params
    *
    * @param string $component
    *
@@ -1624,7 +1278,6 @@ abstract class CRM_Core_Payment {
    * @throws \Civi\Payment\Exception\PaymentProcessorException
    */
   public function doPayment(&$params, $component = 'contribute') {
-    $this->inputParams = $params;
     $this->_component = $component;
     $statuses = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id', 'validate');
 
@@ -2011,25 +1664,19 @@ INNER JOIN civicrm_contribution con ON ( con.contribution_recur_id = rec.id )
    * @return string
    */
   protected function getPaymentDescription($params = [], $length = 24) {
+    $propertyBag = PropertyBag::cast($params);
     $parts = [
-      'contactID',
-      'contributionID',
-      'description',
-      'billing_first_name',
-      'billing_last_name',
+      $propertyBag->getter('contactID', TRUE),
+      $propertyBag->getter('contributionID', TRUE),
+      $propertyBag->getter('description', TRUE) ?: ($propertyBag->getter('isRecur', TRUE) ? ts('Recurring payment') : NULL),
+      $propertyBag->getter('billing_first_name', TRUE),
+      $propertyBag->getter('billing_last_name', TRUE),
     ];
-    $validParts = [];
-    $params['description'] = $this->getDescription();
-    foreach ($parts as $part) {
-      if ((!empty($params[$part]))) {
-        $validParts[] = $params[$part];
-      }
-    }
-    return substr(implode('-', $validParts), 0, $length);
+    return substr(implode('-', array_filter($parts)), 0, $length);
   }
 
   /**
-   * Checks if backoffice recurring edit is allowed
+   * Checks if back-office recurring edit is allowed
    *
    * @return bool
    */