4 * Licensed to CiviCRM under the Academic Free License version 3.0.
6 * Written and contributed by Ideal Solution, LLC (http://www.idealso.com)
13 * @author Marshal Newrock <marshal@idealso.com>
16 use Civi\Payment\Exception\PaymentProcessorException
;
17 use Civi\Payment\PropertyBag
;
20 * Dummy payment processor
22 class CRM_Core_Payment_Dummy
extends CRM_Core_Payment
{
25 protected $_doDirectPaymentResult = [];
28 * Set result from do Direct Payment for test purposes.
30 * @param array $doDirectPaymentResult
31 * Result to be returned from test.
33 public function setDoDirectPaymentResult($doDirectPaymentResult) {
34 $this->_doDirectPaymentResult
= $doDirectPaymentResult;
35 if (empty($this->_doDirectPaymentResult
['trxn_id'])) {
36 $this->_doDirectPaymentResult
['trxn_id'] = [];
39 $this->_doDirectPaymentResult
['trxn_id'] = (array) $doDirectPaymentResult['trxn_id'];
47 * The mode of operation: live or test.
49 * @param array $paymentProcessor
51 public function __construct($mode, &$paymentProcessor) {
53 $this->_paymentProcessor
= $paymentProcessor;
57 * @param array|PropertyBag $params
59 * @param string $component
62 * Result array (containing at least the key payment_status_id)
64 * @throws \Civi\Payment\Exception\PaymentProcessorException
66 public function doPayment(&$params, $component = 'contribute') {
67 $this->_component
= $component;
68 $statuses = CRM_Contribute_BAO_Contribution
::buildOptions('contribution_status_id', 'validate');
70 $propertyBag = PropertyBag
::cast($params);
72 // If we have a $0 amount, skip call to processor and set payment_status to Completed.
73 // Conceivably a processor might override this - perhaps for setting up a token - but we don't
74 // have an example of that at the mome.
75 if ($propertyBag->getAmount() == 0) {
76 $result['payment_status_id'] = array_search('Completed', $statuses);
77 $result['payment_status'] = 'Completed';
81 // Invoke hook_civicrm_paymentProcessor
82 // In Dummy's case, there is no translation of parameters into
83 // the back-end's canonical set of parameters. But if a processor
84 // does this, it needs to invoke this hook after it has done translation,
85 // but before it actually starts talking to its proprietary back-end.
86 if ($propertyBag->getIsRecur()) {
87 $throwAnENoticeIfNotSetAsTheseAreRequired = $propertyBag->getRecurFrequencyInterval() . $propertyBag->getRecurFrequencyUnit();
89 // no translation in Dummy processor
90 CRM_Utils_Hook
::alterPaymentProcessorParams($this, $params, $propertyBag);
91 // This means we can test failing transactions by setting a past year in expiry. A full expiry check would
93 if (!empty($params['credit_card_exp_date']['Y']) && CRM_Utils_Time
::date('Y') >
94 CRM_Core_Payment_Form
::getCreditCardExpirationYear($params)) {
95 throw new PaymentProcessorException(ts('Invalid expiry date'));
98 if (!empty($this->_doDirectPaymentResult
)) {
99 $result = $this->_doDirectPaymentResult
;
100 if (empty($result['payment_status_id'])) {
101 $result['payment_status_id'] = array_search('Pending', $statuses);
102 $result['payment_status'] = 'Pending';
104 if ($result['payment_status_id'] === 'failed') {
105 throw new PaymentProcessorException($result['message'] ??
'failed');
107 $result['trxn_id'] = array_shift($this->_doDirectPaymentResult
['trxn_id']);
111 $result['trxn_id'] = $this->getTrxnID();
113 // Add a fee_amount so we can make sure fees are handled properly in underlying classes.
114 $result['fee_amount'] = 1.50;
115 $result['description'] = $this->getPaymentDescription($params);
117 if (!isset($result['payment_status_id'])) {
118 if (!empty($propertyBag->getIsRecur())) {
119 // See comment block.
120 $result['payment_status_id'] = array_search('Pending', $statuses);
121 $result['payment_status'] = 'Pending';
124 $result['payment_status_id'] = array_search('Completed', $statuses);
125 $result['payment_status'] = 'Completed';
129 // We shouldn't do this but it saves us changing the `testPayNowPayment` test to actually test the contribution
131 $result = array_merge($params, $result);
137 * Does this payment processor support refund?
141 public function supportsRefund() {
146 * Supports altering future start dates.
150 public function supportsFutureRecurStartDate() {
155 * Submit a refund payment
157 * @throws \Civi\Payment\Exception\PaymentProcessorException
159 * @param array $params
160 * Assoc array of input parameters for this transaction.
162 public function doRefund(&$params) {}
165 * This function checks to see if we have the right config values.
168 * the error message if any
170 public function checkConfig() {
175 * Get an array of the fields that can be edited on the recurring contribution.
177 * Some payment processors support editing the amount and other scheduling details of recurring payments, especially
178 * those which use tokens. Others are fixed. This function allows the processor to return an array of the fields that
179 * can be updated from the contribution recur edit screen.
181 * The fields are likely to be a subset of these
184 * - 'frequency_interval',
185 * - 'frequency_unit',
187 * - 'next_sched_contribution_date',
189 * - 'failure_retry_day',
191 * The form does not restrict which fields from the contribution_recur table can be added (although if the html_type
192 * metadata is not defined in the xml for the field it will cause an error.
194 * Open question - would it make sense to return membership_id in this - which is sometimes editable and is on that
195 * form (UpdateSubscription).
199 public function getEditableRecurringScheduleFields() {
200 return ['amount', 'next_sched_contribution_date'];
204 * Does this processor support cancelling recurring contributions through code.
206 * If the processor returns true it must be possible to take action from within CiviCRM
207 * that will result in no further payments being processed. In the case of token processors (e.g
208 * IATS, eWay) updating the contribution_recur table is probably sufficient.
212 protected function supportsCancelRecurring() {
217 * Cancel a recurring subscription.
219 * Payment processor classes should override this rather than implementing cancelSubscription.
221 * A PaymentProcessorException should be thrown if the update of the contribution_recur
222 * record should not proceed (in many cases this function does nothing
223 * as the payment processor does not need to take any action & this should silently
224 * proceed. Note the form layer will only call this after calling
225 * $processor->supports('cancelRecurring');
227 * @param \Civi\Payment\PropertyBag $propertyBag
231 * @throws \Civi\Payment\Exception\PaymentProcessorException
233 public function doCancelRecurring(PropertyBag
$propertyBag) {
234 return ['message' => ts('Recurring contribution cancelled')];
238 * Get a value for the transaction ID.
240 * Value is made up of the max existing value + a random string.
242 * Note the random string is likely a historical workaround.
246 protected function getTrxnID() {
247 $string = $this->_mode
;
248 $trxn_id = CRM_Core_DAO
::singleValueQuery("SELECT MAX(trxn_id) FROM civicrm_contribution WHERE trxn_id LIKE '{$string}_%'");
249 $trxn_id = str_replace($string, '', $trxn_id);
250 $trxn_id = (int) $trxn_id +
1;
251 return $string . '_' . $trxn_id . '_' . uniqid();