PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.8
VikBooking Hotel Booking Engine & PMS v1.8.8
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / admin / helpers / src / model / payschedules.php

payschedules.php in VikBooking Hotel Booking Engine & PMS 1.8.8, at admin/helpers/src/model/payschedules.php

400 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2024 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * VikBooking model Payment Schedules.
16 *
17 * @since 1.16.10 (J) - 1.6.10 (WP)
18 */
19 class VBOModelPayschedules
20 {
21 /** @var array */
22 protected $booking = [];
23
24 /**
25 * Proxy for immediately accessing the object.
26 *
27 * @return VBOModelPayschedules
28 */
29 public static function getInstance()
30 {
31 return new static;
32 }
33
34 /**
35 * Class constructor.
36 */
37 public function __construct()
38 {}
39
40 /**
41 * Sets the current booking record details.
42 *
43 * @param array $booking The booking record details.
44 *
45 * @return self
46 */
47 public function setBooking(array $booking)
48 {
49 $this->booking = $booking;
50
51 return $this;
52 }
53
54 /**
55 * Stores a new payment schedule record.
56 *
57 * @param array|object $record The record to store.
58 *
59 * @return int|null The new record ID or null.
60 */
61 public function save($record)
62 {
63 $dbo = JFactory::getDbo();
64
65 $record = (object) $record;
66
67 $dbo->insertObject('#__vikbooking_payschedules', $record, 'id');
68
69 return $record->id ?? null;
70 }
71
72 /**
73 * Updates an existing payment schedule record.
74 *
75 * @param array|object $record The record details to update.
76 *
77 * @return bool
78 */
79 public function update($record)
80 {
81 $dbo = JFactory::getDbo();
82
83 $record = (object) $record;
84
85 return (bool) $dbo->updateObject('#__vikbooking_payschedules', $record, 'id');
86 }
87
88 /**
89 * Item loading implementation.
90 *
91 * @param mixed $pk An optional primary key value to load the row by,
92 * or an associative array of fields to match.
93 *
94 * @return object|null The record object on success, null otherwise.
95 */
96 public function getItem($pk)
97 {
98 $dbo = JFactory::getDbo();
99
100 $q = $dbo->getQuery(true)
101 ->select('*')
102 ->from($dbo->qn('#__vikbooking_payschedules'));
103
104 if (is_array($pk)) {
105 foreach ($pk as $column => $value) {
106 $q->where($dbo->qn($column) . ' = ' . $dbo->q($value));
107 }
108 } else {
109 $q->where($dbo->qn('id') . ' = ' . (int) $pk);
110 }
111
112 $dbo->setQuery($q, 0, 1);
113
114 $record = $dbo->loadObject();
115
116 if ($record) {
117 $this->normalizeObject($record);
118 }
119
120 return $record;
121 }
122
123 /**
124 * Items loading implementation.
125 *
126 * @param array $clauses List of associative columns to fetch
127 * (column => [operator, value])
128 * @param int $start Query limit start.
129 * @param int $lim Query limit value.
130 *
131 * @return array List of record objects.
132 */
133 public function getItems(array $clauses = [], $start = 0, $lim = 0)
134 {
135 $dbo = JFactory::getDbo();
136
137 $q = $dbo->getQuery(true)
138 ->select('*')
139 ->from($dbo->qn('#__vikbooking_payschedules'));
140
141 foreach ($clauses as $column => $data) {
142 if (!is_array($data) || !isset($data['value'])) {
143 continue;
144 }
145 $q->where($dbo->qn($column) . ' ' . ($data['operator'] ?? '=') . ' ' . $dbo->q($data['value']));
146 }
147
148 $q->order($dbo->qn('status') . ' ASC');
149 $q->order($dbo->qn('fordt') . ' ASC');
150
151 $dbo->setQuery($q, $start, $lim);
152
153 $records = $dbo->loadObjectList();
154
155 return array_map([$this, 'normalizeObject'], $records);
156 }
157
158 /**
159 * Watches and eventually processes the automatic payment collections scheduled.
160 *
161 * @param int $lim the limit of payments to process, defaults to 5 per execution.
162 *
163 * @return int number of payments processed, where -1 indicates no running.
164 */
165 public function watch($lim = 5)
166 {
167 $dbo = JFactory::getDbo();
168
169 // number of schedules processed
170 $processed = 0;
171
172 // build date object intervals
173 $now_dt = JFactory::getDate('now', new DateTimeZone(date_default_timezone_get()));
174 $yesterday_dt = (clone $now_dt)->modify('-1 day');
175
176 // fetch unprocessed payments scheduled within the last 24 hours
177 $q = $dbo->getQuery(true)
178 ->select('*')
179 ->from($dbo->qn('#__vikbooking_payschedules'))
180 ->where($dbo->qn('fordt') . ' >= ' . $dbo->q($yesterday_dt->toSql(true)))
181 ->where($dbo->qn('fordt') . ' <= ' . $dbo->q($now_dt->toSql(true)))
182 ->where($dbo->qn('status') . ' = 0');
183
184 $dbo->setQuery($q, 0, $lim);
185 $payschedules = $dbo->loadObjectList();
186
187 foreach ($payschedules as $payschedule) {
188 // immediately update the record status to processed (1)
189 $dbo->setQuery(
190 $dbo->getQuery(true)
191 ->update($dbo->qn('#__vikbooking_payschedules'))
192 ->set($dbo->qn('status') . ' = 1')
193 ->where($dbo->qn('id') . ' = ' . (int) $payschedule->id)
194 );
195 $dbo->execute();
196
197 // process the automatic payment
198 try {
199 if ($this->processPaySchedule($payschedule)) {
200 // increase counter
201 $processed++;
202 }
203 } catch (Exception $e) {
204 // append failure execution log and update status (2 = error)
205 $dbo->setQuery(
206 $dbo->getQuery(true)
207 ->update($dbo->qn('#__vikbooking_payschedules'))
208 ->set($dbo->qn('logs') . ' = ' . $dbo->q(ltrim($payschedule->logs . "\n" . $e->getMessage(), "\n")))
209 ->set($dbo->qn('status') . ' = 2')
210 ->where($dbo->qn('id') . ' = ' . (int) $payschedule->id)
211 );
212 $dbo->execute();
213 }
214 }
215
216 return $processed;
217 }
218
219 /**
220 * Parses a payment schedule object to normalize some of its properties.
221 *
222 * @param object $payschedule The payment schedule record object.
223 *
224 * @return object
225 */
226 protected function normalizeObject($payschedule)
227 {
228 $now_dt = JFactory::getDate('now');
229 $target_dt = JFactory::getDate($payschedule->fordt);
230
231 $payschedule->time_hm = $target_dt->format('H:i');
232 $payschedule->dt_diff = VBORemindersHelper::getInstance()->relativeDatesDiff($target_dt, $now_dt);
233
234 return $payschedule;
235 }
236
237 /**
238 * Processes the scheduled automatic payment object by attempting
239 * to collect the money from the CC/VCC and by updating all data.
240 *
241 * @param object $payschedule The payment schedule record object.
242 *
243 * @return bool
244 *
245 * @throws Exception
246 */
247 protected function processPaySchedule($payschedule)
248 {
249 $dbo = JFactory::getDbo();
250
251 $booking_info = VikBooking::getBookingInfoFromID($payschedule->idorder);
252 if (!$booking_info) {
253 throw new Exception('Reservation not found.', 404);
254 }
255
256 // currency code
257 $currency_code = !empty($booking_info['chcurrency']) ? $booking_info['chcurrency'] : VikBooking::getCurrencyName();
258 if (empty($currency_code) || strlen((string) $currency_code) != 3) {
259 // fallback to currency transaction code
260 $currency_code = VikBooking::getCurrencyCodePp();
261 }
262
263 // access the reservation model
264 $model = VBOModelReservation::getInstance($booking_info, true);
265
266 try {
267 // get the card details associated with the booking
268 $card = $model->getCardValuePairs();
269
270 if (!$card) {
271 throw new Exception('No credit card details found for the reservation.', 500);
272 }
273
274 // set transaction values within the card array
275 $card['currency'] = $currency_code;
276 $card['amount'] = $payschedule->amount;
277 $card['cardholder'] = $card['cardholder'] ?? $card['name'] ?? null;
278 $card['expiry'] = $card['expiry'] ?? $card['expiration_date'] ?? null;
279
280 // get the payment processor with the card found
281 $processor = $model->getPaymentProcessor($card);
282 } catch (Exception $e) {
283 // propagate the error
284 throw $e;
285 }
286
287 if (!method_exists($processor, 'isDirectChargeSupported') || !$processor->isDirectChargeSupported()) {
288 throw new Exception('The payment method does not allow to directly charge credit cards.', 500);
289 }
290
291 // get the processor name
292 $payment_name = $model->getPaymentName();
293
294 // default transaction response
295 $array_result = [
296 'verified' => 0,
297 ];
298
299 try {
300 // perform the transaction
301 $array_result = $processor->directCharge();
302 } catch (Exception $e) {
303 // set error message
304 $array_result['log'] = sprintf(JText::translate('VBO_CC_TN_ERROR') . " \n%s", $e->getMessage());
305 }
306
307 if ($array_result['verified'] != 1) {
308 // erroneous response
309 if (!empty($array_result['log']) && is_string($array_result['log'])) {
310 throw new Exception($array_result['log'], 500);
311 } else {
312 throw new Exception('Operation failed.', 500);
313 }
314 }
315
316 // valid transaction response!
317 // update booking details
318
319 // get the amount paid
320 $tn_amount = isset($array_result['tot_paid']) ? (float) $array_result['tot_paid'] : null;
321
322 // get the log string, if any
323 $tn_log = !empty($array_result['log']) ? $array_result['log'] : '';
324
325 // update record
326 $upd_record = new stdClass;
327 $upd_record->id = $booking_info['id'];
328 if ($tn_amount) {
329 // update amount paid
330 $upd_record->totpaid = $booking_info['totpaid'] + $tn_amount;
331 // update payable amount (if needed)
332 $new_payable = $booking_info['payable'] - $tn_amount;
333 $new_payable = $new_payable < 0 ? 0 : $new_payable;
334 $upd_record->payable = $new_payable;
335 }
336 if ($tn_log) {
337 $upd_record->paymentlog = $booking_info['paymentlog'] . "\n\n" . date('c') . "\n" . $tn_log;
338 }
339 $upd_record->paymcount = ((int) $booking_info['paymcount'] + 1);
340
341 // update reservation record
342 $dbo->updateObject('#__vikbooking_orders', $upd_record, 'id');
343
344 // payment processor name
345 $pay_process_name = $payment_name ?: 'CC Scheduled Charge';
346
347 // handle transaction data to eventually support a later transaction of type refund
348 $tn_data = isset($array_result['transaction']) ? $array_result['transaction'] : null;
349 if ($tn_amount) {
350 // check event data payload to store
351 if (is_array($tn_data)) {
352 // set key
353 $tn_data['amount_paid'] = $tn_amount;
354 } elseif (is_object($tn_data)) {
355 // set property
356 $tn_data->amount_paid = $tn_amount;
357 } elseif (!$tn_data) {
358 // build an array (we add the payment name because we know there is no other transaction data)
359 $tn_data = [
360 'amount_paid' => $tn_amount,
361 'payment_method' => $pay_process_name,
362 ];
363 }
364 }
365
366 /**
367 * Check if the payment processor returned the information about the amount of processing fees.
368 */
369 if ($tn_data && isset($array_result['tot_fees']) && $array_result['tot_fees']) {
370 // check event data payload to store
371 if (is_array($tn_data)) {
372 // set key
373 $tn_data['processing_fees'] = (float)$array_result['tot_fees'];
374 } elseif (is_object($tn_data)) {
375 // set property
376 $tn_data->processing_fees = (float)$array_result['tot_fees'];
377 }
378 }
379
380 // add an extra data to identify the transaction as automatic, from a schedule
381 if ($tn_data) {
382 if (is_array($tn_data)) {
383 // set key
384 $tn_data['pay_schedule'] = $payschedule->id ?? 1;
385 } elseif (is_object($tn_data)) {
386 // set property
387 $tn_data->pay_schedule = $payschedule->id ?? 1;
388 }
389 }
390
391 // Booking History
392 $main_descr = JText::translate('VBO_AUTOPAY_SCHEDULED');
393 $main_descr = $main_descr != 'VBO_AUTOPAY_SCHEDULED' ? $main_descr : 'Automatic payment collection';
394 $ev_descr = "{$main_descr} - {$pay_process_name}";
395 VikBooking::getBookingHistoryInstance()->setBid($booking_info['id'])->setExtraData($tn_data)->store('P' . ($booking_info['paymcount'] > 0 ? 'N' : '0'), $ev_descr);
396
397 return true;
398 }
399 }
400