Merge pull request #23258 from civicrm/5.49
[civicrm-core.git] / CRM / Core / Payment / Dummy.php
CommitLineData
6a488035
TO
1<?php
2/*
3 * Copyright (C) 2007
4 * Licensed to CiviCRM under the Academic Free License version 3.0.
5 *
6 * Written and contributed by Ideal Solution, LLC (http://www.idealso.com)
7 *
8 */
9
10/**
11 *
12 * @package CRM
13 * @author Marshal Newrock <marshal@idealso.com>
6a488035
TO
14 */
15
65f1a9a1 16use Civi\Payment\Exception\PaymentProcessorException;
3bfefe56 17use Civi\Payment\PropertyBag;
65f1a9a1 18
c866eb5f
TO
19/**
20 * Dummy payment processor
6a488035
TO
21 */
22class CRM_Core_Payment_Dummy extends CRM_Core_Payment {
6a488035 23
651bff36 24 protected $_mode;
be2fb01f 25 protected $_doDirectPaymentResult = [];
f8fe0df6 26
7403f6a2
KK
27 /**
28 * This support variable is used to allow the capabilities supported by the Dummy processor to be set from unit tests
29 * so that we don't need to create a lot of new processors to test combinations of features.
30 * Initially these capabilities are set to TRUE, however they can be altered by calling the setSupports function directly from outside the class.
31 * @var bool[]
32 */
33 protected $supports = [
34 'MultipleConcurrentPayments' => TRUE,
35 'EditRecurringContribution' => TRUE,
36 'CancelRecurringNotifyOptional' => TRUE,
37 'BackOffice' => TRUE,
38 'NoEmailProvided' => TRUE,
39 'CancelRecurring' => TRUE,
40 'FutureRecurStartDate' => TRUE,
41 'Refund' => TRUE,
42 ];
43
f8fe0df6 44 /**
eca28728
EM
45 * Set result from do Direct Payment for test purposes.
46 *
f8fe0df6 47 * @param array $doDirectPaymentResult
eca28728 48 * Result to be returned from test.
f8fe0df6 49 */
50 public function setDoDirectPaymentResult($doDirectPaymentResult) {
51 $this->_doDirectPaymentResult = $doDirectPaymentResult;
9d0f10b7 52 if (empty($this->_doDirectPaymentResult['trxn_id'])) {
be2fb01f 53 $this->_doDirectPaymentResult['trxn_id'] = [];
9d0f10b7
EM
54 }
55 else {
56 $this->_doDirectPaymentResult['trxn_id'] = (array) $doDirectPaymentResult['trxn_id'];
57 }
f8fe0df6 58 }
6a488035 59
6a488035 60 /**
fe482240 61 * Constructor.
6a488035 62 *
6a0b768e
TO
63 * @param string $mode
64 * The mode of operation: live or test.
6a488035 65 *
3bfefe56 66 * @param array $paymentProcessor
6a488035 67 */
00be9182 68 public function __construct($mode, &$paymentProcessor) {
6a488035
TO
69 $this->_mode = $mode;
70 $this->_paymentProcessor = $paymentProcessor;
6a488035
TO
71 }
72
7403f6a2
KK
73 /**
74 * Does this payment processor support refund?
75 *
76 * @return bool
77 */
78 public function supportsRefund() {
79 return $this->supports['Refund'];
80 }
81
82 /**
83 * Should the first payment date be configurable when setting up back office recurring payments.
84 *
85 * We set this to false for historical consistency but in fact most new processors use tokens for recurring and can support this
86 *
87 * @return bool
88 */
89 public function supportsFutureRecurStartDate() {
90 return $this->supports['FutureRecurStartDate'];
91 }
92
93 /**
94 * Can more than one transaction be processed at once?
95 *
96 * In general processors that process payment by server to server communication support this while others do not.
97 *
98 * In future we are likely to hit an issue where this depends on whether a token already exists.
99 *
100 * @return bool
101 */
102 protected function supportsMultipleConcurrentPayments() {
103 return $this->supports['MultipleConcurrentPayments'];
104 }
105
106 /**
107 * Checks if back-office recurring edit is allowed
108 *
109 * @return bool
110 */
111 public function supportsEditRecurringContribution() {
112 return $this->supports['EditRecurringContribution'];
113 }
114
115 /**
116 * Are back office payments supported.
117 *
118 * e.g paypal standard won't permit you to enter a credit card associated
119 * with someone else's login.
120 * The intention is to support off-site (other than paypal) & direct debit but that is not all working yet so to
121 * reach a 'stable' point we disable.
122 *
123 * @return bool
124 */
125 protected function supportsBackOffice() {
126 return $this->supports['BackOffice'];
127 }
128
129 /**
130 * Does the processor work without an email address?
131 *
132 * The historic assumption is that all processors require an email address. This capability
133 * allows a processor to state it does not need to be provided with an email address.
134 * NB: when this was added (Feb 2020), the Manual processor class overrides this but
135 * the only use of the capability is in the webform_civicrm module. It is not currently
136 * used in core but may be in future.
137 *
138 * @return bool
139 */
140 protected function supportsNoEmailProvided() {
141 return $this->supports['NoEmailProvided'];
142 }
143
144 /**
145 * Does this processor support cancelling recurring contributions through code.
146 *
147 * If the processor returns true it must be possible to take action from within CiviCRM
148 * that will result in no further payments being processed. In the case of token processors (e.g
149 * IATS, eWay) updating the contribution_recur table is probably sufficient.
150 *
151 * @return bool
152 */
153 protected function supportsCancelRecurring() {
154 return $this->supports['CancelRecurring'];
155 }
156
157 /**
158 * Does the processor support the user having a choice as to whether to cancel the recurring with the processor?
159 *
160 * If this returns TRUE then there will be an option to send a cancellation request in the cancellation form.
161 *
162 * This would normally be false for processors where CiviCRM maintains the schedule.
163 *
164 * @return bool
165 */
166 protected function supportsCancelRecurringNotifyOptional() {
167 return $this->supports['CancelRecurringNotifyOptional'];
168 }
169
170 /**
171 * Set the return value of support functions. By default it is TRUE
172 *
173 */
174 public function setSupports(array $support) {
175 $this->supports = array_merge($this->supports, $support);
176 }
177
6a488035 178 /**
e4fcfa1d 179 * @param array|PropertyBag $params
6a488035 180 *
e4fcfa1d 181 * @param string $component
6a488035 182 *
a6c01b45 183 * @return array
e4fcfa1d
MW
184 * Result array (containing at least the key payment_status_id)
185 *
65f1a9a1 186 * @throws \Civi\Payment\Exception\PaymentProcessorException
6a488035 187 */
e4fcfa1d
MW
188 public function doPayment(&$params, $component = 'contribute') {
189 $this->_component = $component;
190 $statuses = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id', 'validate');
191
3bfefe56 192 $propertyBag = PropertyBag::cast($params);
e4fcfa1d
MW
193
194 // If we have a $0 amount, skip call to processor and set payment_status to Completed.
195 // Conceivably a processor might override this - perhaps for setting up a token - but we don't
196 // have an example of that at the mome.
197 if ($propertyBag->getAmount() == 0) {
198 $result['payment_status_id'] = array_search('Completed', $statuses);
199 $result['payment_status'] = 'Completed';
200 return $result;
201 }
202
6a488035
TO
203 // Invoke hook_civicrm_paymentProcessor
204 // In Dummy's case, there is no translation of parameters into
205 // the back-end's canonical set of parameters. But if a processor
206 // does this, it needs to invoke this hook after it has done translation,
207 // but before it actually starts talking to its proprietary back-end.
3bfefe56 208 if ($propertyBag->getIsRecur()) {
209 $throwAnENoticeIfNotSetAsTheseAreRequired = $propertyBag->getRecurFrequencyInterval() . $propertyBag->getRecurFrequencyUnit();
3a1f9fd3 210 }
6a488035 211 // no translation in Dummy processor
e4fcfa1d 212 CRM_Utils_Hook::alterPaymentProcessorParams($this, $params, $propertyBag);
7758bd2b
EM
213 // This means we can test failing transactions by setting a past year in expiry. A full expiry check would
214 // be more complete.
118a7b84 215 if (!empty($params['credit_card_exp_date']['Y']) && CRM_Utils_Time::date('Y') >
7758bd2b 216 CRM_Core_Payment_Form::getCreditCardExpirationYear($params)) {
90530b87 217 throw new PaymentProcessorException(ts('Invalid expiry date'));
7758bd2b 218 }
e4fcfa1d 219
f8fe0df6 220 if (!empty($this->_doDirectPaymentResult)) {
9d0f10b7 221 $result = $this->_doDirectPaymentResult;
78bdb5a6
MW
222 if (empty($result['payment_status_id'])) {
223 $result['payment_status_id'] = array_search('Pending', $statuses);
224 $result['payment_status'] = 'Pending';
225 }
226 if ($result['payment_status_id'] === 'failed') {
65f1a9a1 227 throw new PaymentProcessorException($result['message'] ?? 'failed');
228 }
9d0f10b7
EM
229 $result['trxn_id'] = array_shift($this->_doDirectPaymentResult['trxn_id']);
230 return $result;
f8fe0df6 231 }
5562e2e3 232
e4fcfa1d 233 $result['trxn_id'] = $this->getTrxnID();
5562e2e3 234
3a2251cf 235 // Add a fee_amount so we can make sure fees are handled properly in underlying classes.
e4fcfa1d
MW
236 $result['fee_amount'] = 1.50;
237 $result['description'] = $this->getPaymentDescription($params);
238
239 if (!isset($result['payment_status_id'])) {
240 if (!empty($propertyBag->getIsRecur())) {
241 // See comment block.
242 $result['payment_status_id'] = array_search('Pending', $statuses);
243 $result['payment_status'] = 'Pending';
244 }
245 else {
246 $result['payment_status_id'] = array_search('Completed', $statuses);
247 $result['payment_status'] = 'Completed';
248 }
249 }
250
e4fcfa1d 251 return $result;
6a488035
TO
252 }
253
6bc775cf
MD
254 /**
255 * Submit a refund payment
256 *
257 * @throws \Civi\Payment\Exception\PaymentProcessorException
258 *
259 * @param array $params
260 * Assoc array of input parameters for this transaction.
261 */
262 public function doRefund(&$params) {}
263
6a488035 264 /**
fe482240 265 * This function checks to see if we have the right config values.
6a488035 266 *
a6c01b45
CW
267 * @return string
268 * the error message if any
6a488035 269 */
00be9182 270 public function checkConfig() {
6a488035
TO
271 return NULL;
272 }
96025800 273
10df056e 274 /**
275 * Get an array of the fields that can be edited on the recurring contribution.
276 *
277 * Some payment processors support editing the amount and other scheduling details of recurring payments, especially
278 * those which use tokens. Others are fixed. This function allows the processor to return an array of the fields that
279 * can be updated from the contribution recur edit screen.
280 *
281 * The fields are likely to be a subset of these
282 * - 'amount',
283 * - 'installments',
284 * - 'frequency_interval',
285 * - 'frequency_unit',
286 * - 'cycle_day',
287 * - 'next_sched_contribution_date',
288 * - 'end_date',
289 * - 'failure_retry_day',
290 *
291 * The form does not restrict which fields from the contribution_recur table can be added (although if the html_type
292 * metadata is not defined in the xml for the field it will cause an error.
293 *
294 * Open question - would it make sense to return membership_id in this - which is sometimes editable and is on that
295 * form (UpdateSubscription).
296 *
297 * @return array
298 */
299 public function getEditableRecurringScheduleFields() {
be2fb01f 300 return ['amount', 'next_sched_contribution_date'];
10df056e 301 }
302
3bfefe56 303 /**
304 * Cancel a recurring subscription.
305 *
306 * Payment processor classes should override this rather than implementing cancelSubscription.
307 *
308 * A PaymentProcessorException should be thrown if the update of the contribution_recur
309 * record should not proceed (in many cases this function does nothing
310 * as the payment processor does not need to take any action & this should silently
311 * proceed. Note the form layer will only call this after calling
312 * $processor->supports('cancelRecurring');
313 *
314 * @param \Civi\Payment\PropertyBag $propertyBag
315 *
316 * @return array
317 *
318 * @throws \Civi\Payment\Exception\PaymentProcessorException
319 */
320 public function doCancelRecurring(PropertyBag $propertyBag) {
321 return ['message' => ts('Recurring contribution cancelled')];
322 }
323
5562e2e3 324 /**
325 * Get a value for the transaction ID.
326 *
327 * Value is made up of the max existing value + a random string.
328 *
329 * Note the random string is likely a historical workaround.
330 *
331 * @return string
332 */
333 protected function getTrxnID() {
334 $string = $this->_mode;
335 $trxn_id = CRM_Core_DAO::singleValueQuery("SELECT MAX(trxn_id) FROM civicrm_contribution WHERE trxn_id LIKE '{$string}_%'");
336 $trxn_id = str_replace($string, '', $trxn_id);
337 $trxn_id = (int) $trxn_id + 1;
338 return $string . '_' . $trxn_id . '_' . uniqid();
339 }
340
6a488035 341}