PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.11
VikBooking Hotel Booking Engine & PMS v1.8.11
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.11, at admin/helpers/src/model/payschedules.php

448 lines 14.2 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 * Method to delete one or more records.
160 *
161 * @param mixed $pks An array of record primary keys, or a single one.
162 *
163 * @return bool True if successful, false if an error occurs.
164 *
165 * @since 1.8.19 (J) - 1.8.9 (WP)
166 */
167 public function delete($pks)
168 {
169 $dbo = JFactory::getDbo();
170
171 if (!$pks) {
172 // nothing to delete
173 return false;
174 }
175
176 if (!is_array($pks)) {
177 // wrap into an array
178 $pks = [$pks];
179 }
180
181 $deleted = 0;
182
183 foreach ($pks as $pk) {
184 // ensure the item exists
185 $item = $this->getItem($pk);
186
187 if (!$item) {
188 continue;
189 }
190
191 $dbo->setQuery(
192 $dbo->getQuery(true)
193 ->delete($dbo->qn('#__vikbooking_payschedules'))
194 ->where($dbo->qn('id') . ' = ' . (int) $item->id)
195 );
196 $dbo->execute();
197
198 if ((bool) $dbo->getAffectedRows()) {
199 $deleted++;
200 }
201 }
202
203 return (bool) $deleted;
204 }
205
206 /**
207 * Watches and eventually processes the automatic payment collections scheduled.
208 *
209 * @param int $lim the limit of payments to process, defaults to 5 per execution.
210 *
211 * @return int number of payments processed, where -1 indicates no running.
212 */
213 public function watch($lim = 5)
214 {
215 $dbo = JFactory::getDbo();
216
217 // number of schedules processed
218 $processed = 0;
219
220 // build date object intervals
221 $now_dt = JFactory::getDate('now', new DateTimeZone(date_default_timezone_get()));
222 $yesterday_dt = (clone $now_dt)->modify('-1 day');
223
224 // fetch unprocessed payments scheduled within the last 24 hours
225 $q = $dbo->getQuery(true)
226 ->select('*')
227 ->from($dbo->qn('#__vikbooking_payschedules'))
228 ->where($dbo->qn('fordt') . ' >= ' . $dbo->q($yesterday_dt->toSql(true)))
229 ->where($dbo->qn('fordt') . ' <= ' . $dbo->q($now_dt->toSql(true)))
230 ->where($dbo->qn('status') . ' = 0');
231
232 $dbo->setQuery($q, 0, $lim);
233 $payschedules = $dbo->loadObjectList();
234
235 foreach ($payschedules as $payschedule) {
236 // immediately update the record status to processed (1)
237 $dbo->setQuery(
238 $dbo->getQuery(true)
239 ->update($dbo->qn('#__vikbooking_payschedules'))
240 ->set($dbo->qn('status') . ' = 1')
241 ->where($dbo->qn('id') . ' = ' . (int) $payschedule->id)
242 );
243 $dbo->execute();
244
245 // process the automatic payment
246 try {
247 if ($this->processPaySchedule($payschedule)) {
248 // increase counter
249 $processed++;
250 }
251 } catch (Exception $e) {
252 // append failure execution log and update status (2 = error)
253 $dbo->setQuery(
254 $dbo->getQuery(true)
255 ->update($dbo->qn('#__vikbooking_payschedules'))
256 ->set($dbo->qn('logs') . ' = ' . $dbo->q(ltrim($payschedule->logs . "\n" . $e->getMessage(), "\n")))
257 ->set($dbo->qn('status') . ' = 2')
258 ->where($dbo->qn('id') . ' = ' . (int) $payschedule->id)
259 );
260 $dbo->execute();
261 }
262 }
263
264 return $processed;
265 }
266
267 /**
268 * Parses a payment schedule object to normalize some of its properties.
269 *
270 * @param object $payschedule The payment schedule record object.
271 *
272 * @return object
273 */
274 protected function normalizeObject($payschedule)
275 {
276 $now_dt = JFactory::getDate('now');
277 $target_dt = JFactory::getDate($payschedule->fordt);
278
279 $payschedule->time_hm = $target_dt->format('H:i');
280 $payschedule->dt_diff = VBORemindersHelper::getInstance()->relativeDatesDiff($target_dt, $now_dt);
281
282 return $payschedule;
283 }
284
285 /**
286 * Processes the scheduled automatic payment object by attempting
287 * to collect the money from the CC/VCC and by updating all data.
288 *
289 * @param object $payschedule The payment schedule record object.
290 *
291 * @return bool
292 *
293 * @throws Exception
294 */
295 protected function processPaySchedule($payschedule)
296 {
297 $dbo = JFactory::getDbo();
298
299 $booking_info = VikBooking::getBookingInfoFromID($payschedule->idorder);
300 if (!$booking_info) {
301 throw new Exception('Reservation not found.', 404);
302 }
303
304 // currency code
305 $currency_code = !empty($booking_info['chcurrency']) ? $booking_info['chcurrency'] : VikBooking::getCurrencyName();
306 if (empty($currency_code) || strlen((string) $currency_code) != 3) {
307 // fallback to currency transaction code
308 $currency_code = VikBooking::getCurrencyCodePp();
309 }
310
311 // access the reservation model
312 $model = VBOModelReservation::getInstance($booking_info, true);
313
314 try {
315 // get the card details associated with the booking
316 $card = $model->getCardValuePairs();
317
318 if (!$card) {
319 throw new Exception('No credit card details found for the reservation.', 500);
320 }
321
322 // set transaction values within the card array
323 $card['currency'] = $currency_code;
324 $card['amount'] = $payschedule->amount;
325 $card['cardholder'] = $card['cardholder'] ?? $card['name'] ?? null;
326 $card['expiry'] = $card['expiry'] ?? $card['expiration_date'] ?? null;
327
328 // get the payment processor with the card found
329 $processor = $model->getPaymentProcessor($card);
330 } catch (Exception $e) {
331 // propagate the error
332 throw $e;
333 }
334
335 if (!method_exists($processor, 'isDirectChargeSupported') || !$processor->isDirectChargeSupported()) {
336 throw new Exception('The payment method does not allow to directly charge credit cards.', 500);
337 }
338
339 // get the processor name
340 $payment_name = $model->getPaymentName();
341
342 // default transaction response
343 $array_result = [
344 'verified' => 0,
345 ];
346
347 try {
348 // perform the transaction
349 $array_result = $processor->directCharge();
350 } catch (Exception $e) {
351 // set error message
352 $array_result['log'] = sprintf(JText::translate('VBO_CC_TN_ERROR') . " \n%s", $e->getMessage());
353 }
354
355 if ($array_result['verified'] != 1) {
356 // erroneous response
357 if (!empty($array_result['log']) && is_string($array_result['log'])) {
358 throw new Exception($array_result['log'], 500);
359 } else {
360 throw new Exception('Operation failed.', 500);
361 }
362 }
363
364 // valid transaction response!
365 // update booking details
366
367 // get the amount paid
368 $tn_amount = isset($array_result['tot_paid']) ? (float) $array_result['tot_paid'] : null;
369
370 // get the log string, if any
371 $tn_log = !empty($array_result['log']) ? $array_result['log'] : '';
372
373 // update record
374 $upd_record = new stdClass;
375 $upd_record->id = $booking_info['id'];
376 if ($tn_amount) {
377 // update amount paid
378 $upd_record->totpaid = $booking_info['totpaid'] + $tn_amount;
379 // update payable amount (if needed)
380 $new_payable = $booking_info['payable'] - $tn_amount;
381 $new_payable = $new_payable < 0 ? 0 : $new_payable;
382 $upd_record->payable = $new_payable;
383 }
384 if ($tn_log) {
385 $upd_record->paymentlog = $booking_info['paymentlog'] . "\n\n" . date('c') . "\n" . $tn_log;
386 }
387 $upd_record->paymcount = ((int) $booking_info['paymcount'] + 1);
388
389 // update reservation record
390 $dbo->updateObject('#__vikbooking_orders', $upd_record, 'id');
391
392 // payment processor name
393 $pay_process_name = $payment_name ?: 'CC Scheduled Charge';
394
395 // handle transaction data to eventually support a later transaction of type refund
396 $tn_data = isset($array_result['transaction']) ? $array_result['transaction'] : null;
397 if ($tn_amount) {
398 // check event data payload to store
399 if (is_array($tn_data)) {
400 // set key
401 $tn_data['amount_paid'] = $tn_amount;
402 } elseif (is_object($tn_data)) {
403 // set property
404 $tn_data->amount_paid = $tn_amount;
405 } elseif (!$tn_data) {
406 // build an array (we add the payment name because we know there is no other transaction data)
407 $tn_data = [
408 'amount_paid' => $tn_amount,
409 'payment_method' => $pay_process_name,
410 ];
411 }
412 }
413
414 /**
415 * Check if the payment processor returned the information about the amount of processing fees.
416 */
417 if ($tn_data && isset($array_result['tot_fees']) && $array_result['tot_fees']) {
418 // check event data payload to store
419 if (is_array($tn_data)) {
420 // set key
421 $tn_data['processing_fees'] = (float)$array_result['tot_fees'];
422 } elseif (is_object($tn_data)) {
423 // set property
424 $tn_data->processing_fees = (float)$array_result['tot_fees'];
425 }
426 }
427
428 // add an extra data to identify the transaction as automatic, from a schedule
429 if ($tn_data) {
430 if (is_array($tn_data)) {
431 // set key
432 $tn_data['pay_schedule'] = $payschedule->id ?? 1;
433 } elseif (is_object($tn_data)) {
434 // set property
435 $tn_data->pay_schedule = $payschedule->id ?? 1;
436 }
437 }
438
439 // Booking History
440 $main_descr = JText::translate('VBO_AUTOPAY_SCHEDULED');
441 $main_descr = $main_descr != 'VBO_AUTOPAY_SCHEDULED' ? $main_descr : 'Automatic payment collection';
442 $ev_descr = "{$main_descr} - {$pay_process_name}";
443 VikBooking::getBookingHistoryInstance()->setBid($booking_info['id'])->setExtraData($tn_data)->store('P' . ($booking_info['paymcount'] > 0 ? 'N' : '0'), $ev_descr);
444
445 return true;
446 }
447 }
448