PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.16
VikBooking Hotel Booking Engine & PMS v1.8.16
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
← All changes | admin/helpers/widgets/virtual_terminal.php +317 -232 1.6.9 → 1.8.16 View file →
@@ -109,10 +109,10 @@
109 109 VBOHttpDocument::getInstance()->close(404, JText::translate('VBPEDITBUSYONE'));
110 110 }
111 111 }
112 112
113 - // check if direct charge is supported by the current payment processor
114 - if (!$this->getPaymentProcessor($booking_info)) {
113 + // ensure a valid payment processor is assigned to the reservation
114 + if (!($processor = $this->getPaymentProcessor($booking_info))) {
115 115 // unable to proceed due to unsupported transaction
116 116 return [
117 117 'html' => '<p class="err">' . JText::translate('VBO_PAY_PROCESS_NO_DIRECT_CHARGE') . '</p>',
118 118 ];
@@ -117,58 +117,22 @@
117 117 'html' => '<p class="err">' . JText::translate('VBO_PAY_PROCESS_NO_DIRECT_CHARGE') . '</p>',
118 118 ];
119 119 }
120 120
121 - // build complete credit card payload, if any
122 - $cc_payload_str = '';
123 - $booking_info['paymentlog'] = (string)$booking_info['paymentlog'];
124 - if (stripos($booking_info['paymentlog'], 'card number') !== false && strpos($booking_info['paymentlog'], '*') !== false) {
125 - // matched a log for an OTA CC
126 - $cc_payload_str = $booking_info['paymentlog'];
127 - } elseif (preg_match("/(([\d\*]{4,4}\s*){4,4})|(([\d\*]{4,6}\s*){3,3})/", $booking_info['paymentlog'])) {
128 - // matched a credit card
129 - $cc_payload_str = $booking_info['paymentlog'];
130 - }
121 + // tell whether off-session capturing is supported
122 + $off_session_capturing = method_exists($processor, 'isOffSessionCaptureSupported') && $processor->isOffSessionCaptureSupported();
123 + $off_session_tn_data = $off_session_capturing ? VBOModelReservation::getInstance($booking_info, true)->getOffSessionTransactionData() : [];
131 124
132 - // check if this is an OTA reservation with remotely decoded CC details
133 - $remote_cc_data = [];
134 - if ($this->vcm_exists && !empty($booking_info['idorderota']) && !empty($booking_info['channel'])) {
135 - // make sure the permissions are met
136 - if (!JFactory::getUser()->authorise('core.admin', 'com_vikchannelmanager')) {
137 - // insufficient permissions to handle CC details
138 - return [
139 - 'html' => '<p class="err">' . JText::translate('JERROR_ALERTNOAUTHOR') . '</p>',
140 - ];
141 - }
142 -
143 - // channel source
144 - $channel_source = (string)$booking_info['channel'];
145 - if (strpos($booking_info['channel'], '_') !== false) {
146 - $channelparts = explode('_', $booking_info['channel']);
147 - $channel_source = $channelparts[0];
148 - }
149 -
150 - // only updated versions of VCM will support remote CC decoding for OTA reservations
151 - if (class_exists('VCMOtaBooking')) {
152 - // invoke the OTA Booking helper class from VCM
153 - $cc_helper = VCMOtaBooking::getInstance([
154 - 'channel_source' => $channel_source,
155 - 'ota_id' => $booking_info['idorderota'],
156 - ], $anew = true);
157 -
158 - if (method_exists($cc_helper, 'decodeCreditCardDetails')) {
159 - $remote_cc_data = $cc_helper->decodeCreditCardDetails();
160 - // make sure the response was valid
161 - if (!$remote_cc_data || !empty($remote_cc_data['error'])) {
162 - // we ignore the error by simply resetting the array
163 - $remote_cc_data = [];
164 - }
165 - }
166 - }
125 + // make sure the permissions are met
126 + if ($this->vcm_exists && !JFactory::getUser()->authorise('core.admin', 'com_vikchannelmanager') && !$off_session_tn_data) {
127 + // insufficient permissions to handle CC details
128 + return [
129 + 'html' => '<p class="err">' . JText::translate('JERROR_ALERTNOAUTHOR') . '</p>',
130 + ];
167 131 }
168 132
169 - // merge remotely decoded CC details to parsed payment log (if any)
170 - $cc_value_pairs = array_merge($remote_cc_data, $this->parseCreditCardValuePairs($cc_payload_str, $remote_cc_data));
133 + // get the reservation credit card value pairs, if any
134 + $cc_value_pairs = VBOModelReservation::getInstance($booking_info, true)->getCardValuePairs();
171 135
172 136 // currency code
173 137 $currency_code = !empty($booking_info['chcurrency']) ? $booking_info['chcurrency'] : VikBooking::getCurrencyName();
174 138 if (empty($currency_code) || strlen((string)$currency_code) != 3) {
@@ -200,8 +164,9 @@
200 164 /**
201 165 * Consider calculating an outstanding balance in case of previous payments.
202 166 *
203 167 * @since 1.16.7 (J) - 1.6.7 (WP)
168 + * @since 1.18.0 (J) - 1.8.0 (WP) added support to amount raw for off-session capture.
204 169 */
205 170 $default_total = $booking_info['total'];
206 171 if ($booking_info['totpaid'] > 0 && $booking_info['totpaid'] < $booking_info['total']) {
207 172 // default to the outstanding balance
@@ -207,8 +172,16 @@
207 172 // default to the outstanding balance
208 173 $default_total = $booking_info['total'] - $booking_info['totpaid'];
209 174 }
210 175
176 + if (!$booking_info['totpaid'] && $off_session_tn_data && ($off_session_tn_data[0]->amount_raw ?? null)) {
177 + // default to the payable amount at the time of payment, unless empty
178 + $default_total = ((float) $off_session_tn_data[0]->amount_raw) ?: $booking_info['total'];
179 + }
180 +
181 + // tell whether we only have an off-session transaction to capture
182 + $only_off_session = $off_session_tn_data && !$cc_value_pairs;
183 +
211 184 // start output buffering
212 185 ob_start();
213 186
214 187 ?>
@@ -229,48 +202,80 @@
229 202 </div>
230 203 </div>
231 204 </div>
232 205
233 - <div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-cardholder">
234 - <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-cardholder">
235 - <div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBISNOMINATIVE'); ?></div>
206 + <?php
207 + if ($off_session_tn_data) {
208 + // when an off-session transaction is available, display the capture button
209 + ?>
210 + <div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-submit" data-tn-type="off-session">
211 + <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-submit" data-tn-type="off-session">
236 212 <div class="vbo-vterminal-cc-val">
237 - <input type="text" autocomplete="off" value="<?php echo isset($known_tn_vals['name']) ? JHtml::fetch('esc_attr', $cc_value_pairs['name']) : ''; ?>" data-vt-cc-field="cardholder" />
213 + <button type="button" class="btn vbo-config-btn" onclick="vboWidgetVTerminalOffSessionCharge('<?php echo $wrapper; ?>');"><?php VikBookingIcons::e('credit-card'); ?> <?php echo JText::translate('VBO_CHARGE_AUTHORIZED_CARD'); ?></button>
238 214 </div>
239 215 </div>
240 216 </div>
217 + <?php
218 + }
219 + ?>
241 220
242 - <div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-ccpan">
243 - <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-ccpan">
244 - <div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CC_NUMBER'); ?></div>
245 - <div class="vbo-vterminal-cc-val vbo-vterminal-cc-val-withlogo">
246 - <input type="text" autocomplete="off" value="<?php echo isset($known_tn_vals['card_number']) ? JHtml::fetch('esc_attr', $cc_value_pairs['card_number']) : ''; ?>" data-vt-cc-field="card_number" />
247 - <span class="vbo-vterminal-cc-type-logo"></span>
221 + <div class="vbo-vterminal-cc-group-cardwrap"<?php echo $off_session_tn_data ? ' data-has-offsession="1"' : ''; ?>>
222 +
223 + <?php
224 + if ($off_session_tn_data) {
225 + // add a label for using the card
226 + ?>
227 + <div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-usecard">
228 + <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-usecard">
229 + <div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CREDIT_CARD') . ($only_off_session ? ' - ' . JText::translate('VBMAINPAYMENTSNEW') : ''); ?></div>
248 230 </div>
249 231 </div>
250 - </div>
232 + <?php
233 + }
234 + ?>
251 235
252 - <div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-ccextra">
253 - <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-ccexpiry">
254 - <div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CC_EXPIRY_DT'); ?></div>
255 - <div class="vbo-vterminal-cc-val">
256 - <input type="text" autocomplete="off" placeholder="MM/YYYY" value="<?php echo isset($known_tn_vals['expiration_date']) ? JHtml::fetch('esc_attr', $cc_value_pairs['expiration_date']) : ''; ?>" data-vt-cc-field="expiry" />
236 + <div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-cardholder">
237 + <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-cardholder">
238 + <div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBISNOMINATIVE'); ?></div>
239 + <div class="vbo-vterminal-cc-val">
240 + <input type="text" autocomplete="off" value="<?php echo isset($known_tn_vals['name']) ? JHtml::fetch('esc_attr', $cc_value_pairs['name']) : ''; ?>" data-vt-cc-field="cardholder" />
241 + </div>
257 242 </div>
258 243 </div>
259 - <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-cvc">
260 - <div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CC_CVV'); ?></div>
261 - <div class="vbo-vterminal-cc-val">
262 - <input type="text" autocomplete="off" value="<?php echo isset($known_tn_vals['cvv']) ? JHtml::fetch('esc_attr', $cc_value_pairs['cvv']) : ''; ?>" data-vt-cc-field="cvv" />
244 +
245 + <div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-ccpan">
246 + <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-ccpan">
247 + <div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CC_NUMBER'); ?></div>
248 + <div class="vbo-vterminal-cc-val vbo-vterminal-cc-val-withlogo">
249 + <input type="text" autocomplete="off" value="<?php echo isset($known_tn_vals['card_number']) ? JHtml::fetch('esc_attr', $cc_value_pairs['card_number']) : ''; ?>" data-vt-cc-field="card_number" />
250 + <span class="vbo-vterminal-cc-type-logo"></span>
251 + </div>
263 252 </div>
264 253 </div>
265 - </div>
266 254
267 - <div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-submit">
268 - <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-submit">
269 - <div class="vbo-vterminal-cc-val">
270 - <button type="button" class="btn vbo-config-btn" onclick="vboWidgetVTerminalChargeCard('<?php echo $wrapper; ?>');"><?php VikBookingIcons::e('credit-card'); ?> <?php echo JText::translate('VBO_CC_DOCHARGE'); ?></button>
255 + <div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-ccextra">
256 + <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-ccexpiry">
257 + <div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CC_EXPIRY_DT'); ?></div>
258 + <div class="vbo-vterminal-cc-val">
259 + <input type="text" autocomplete="off" placeholder="MM/YYYY" value="<?php echo isset($known_tn_vals['expiration_date']) ? JHtml::fetch('esc_attr', $cc_value_pairs['expiration_date']) : ''; ?>" data-vt-cc-field="expiry" />
260 + </div>
271 261 </div>
262 + <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-cvc">
263 + <div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CC_CVV'); ?></div>
264 + <div class="vbo-vterminal-cc-val">
265 + <input type="text" autocomplete="off" value="<?php echo isset($known_tn_vals['cvv']) ? JHtml::fetch('esc_attr', $cc_value_pairs['cvv']) : ''; ?>" data-vt-cc-field="cvv" />
266 + </div>
267 + </div>
272 268 </div>
269 +
270 + <div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-submit" data-tn-type="direct-charge">
271 + <div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-submit" data-tn-type="direct-charge">
272 + <div class="vbo-vterminal-cc-val">
273 + <button type="button" class="btn vbo-config-btn" onclick="vboWidgetVTerminalChargeCard('<?php echo $wrapper; ?>');"><?php VikBookingIcons::e('credit-card'); ?> <?php echo JText::translate('VBO_CC_DOCHARGE'); ?></button>
274 + </div>
275 + </div>
276 + </div>
277 +
273 278 </div>
274 279
275 280 <?php
276 281 // print hidden transaction values, if any
@@ -487,9 +492,9 @@
487 492 // update booking details
488 493 $dbo = JFactory::getDbo();
489 494
490 495 // get the amount paid
491 - $tn_amount = isset($array_result['tot_paid']) ? (float)$array_result['tot_paid'] : null;
496 + $tn_amount = isset($array_result['tot_paid']) ? (float) $array_result['tot_paid'] : null;
492 497
493 498 // get the log string, if any
494 499 $tn_log = !empty($array_result['log']) ? $array_result['log'] : '';
495 500
@@ -542,12 +547,12 @@
542 547 if ($tn_data && isset($array_result['tot_fees']) && $array_result['tot_fees']) {
543 548 // check event data payload to store
544 549 if (is_array($tn_data)) {
545 550 // set key
546 - $tn_data['processing_fees'] = (float)$array_result['tot_fees'];
551 + $tn_data['processing_fees'] = (float) $array_result['tot_fees'];
547 552 } elseif (is_object($tn_data)) {
548 553 // set property
549 - $tn_data->processing_fees = (float)$array_result['tot_fees'];
554 + $tn_data->processing_fees = (float) $array_result['tot_fees'];
550 555 }
551 556 }
552 557
553 558 // current admin-user
@@ -563,8 +568,157 @@
563 568 ];
564 569 }
565 570
566 571 /**
572 + * Custom method for this widget only to debit a credit card off-session.
573 + *
574 + * @return ?array
575 + *
576 + * @since 1.18.0 (J) - 1.8.0 (WP)
577 + */
578 + public function doOffSessionCapture()
579 + {
580 + $app = JFactory::getApplication();
581 +
582 + $wrapper = $app->input->getString('wrapper', '');
583 + $bid = $app->input->getUInt('bid', 0);
584 + $amount = $app->input->getFloat('amount', 0);
585 + $currency = $app->input->getString('currency', '');
586 +
587 + if (!$bid || !$amount) {
588 + // booking ID and amount are mandatory
589 + VBOHttpDocument::getInstance()->close(500, JText::translate('VBPEDITBUSYONE'));
590 + }
591 +
592 + // get booking details
593 + $booking_info = VikBooking::getBookingInfoFromID($bid);
594 +
595 + if (!$booking_info) {
596 + // booking record not found
597 + VBOHttpDocument::getInstance()->close(404, JText::translate('VBPEDITBUSYONE'));
598 + }
599 +
600 + // collect the previous transaction data list
601 + $tn_data = VBOModelReservation::getInstance($booking_info, true)->getOffSessionTransactionData();
602 + if (!$tn_data) {
603 + // previous transaction data not found
604 + VBOHttpDocument::getInstance()->close(400, 'Missing previous transaction data');
605 + }
606 +
607 + // set the amount to capture and transaction data before accessing the payment processor
608 + $booking_info['total_to_pay'] = $amount;
609 + $booking_info['tn_currency'] = $currency;
610 + $booking_info['transaction'] = $tn_data[0];
611 +
612 + // get the eligible payment processor
613 + $processor = $this->getPaymentProcessor($booking_info);
614 +
615 + if (!$processor) {
616 + VBOHttpDocument::getInstance()->close(500, JText::translate('VBO_PAY_PROCESS_NO_DIRECT_CHARGE'));
617 + }
618 +
619 + // default transaction response
620 + $array_result = [
621 + 'verified' => 0,
622 + ];
623 +
624 + try {
625 + // perform the transaction
626 + $array_result = $processor->offSessionCapture();
627 + } catch (Exception $e) {
628 + // set error message
629 + $array_result['log'] = sprintf(JText::translate('VBO_CC_TN_ERROR') . " \n%s", $e->getMessage());
630 + }
631 +
632 + if ($array_result['verified'] != 1) {
633 + // erroneous response
634 + if (!empty($array_result['log']) && is_string($array_result['log'])) {
635 + VBOHttpDocument::getInstance()->close(500, $array_result['log']);
636 + } else {
637 + VBOHttpDocument::getInstance()->close(500, 'Operation failed');
638 + }
639 + }
640 +
641 + // valid transaction response!
642 +
643 + // update booking details
644 + $dbo = JFactory::getDbo();
645 +
646 + // get the amount paid
647 + $tn_amount = isset($array_result['tot_paid']) ? (float) $array_result['tot_paid'] : null;
648 +
649 + // get the log string, if any
650 + $tn_log = !empty($array_result['log']) ? $array_result['log'] : '';
651 +
652 + // update record
653 + $upd_record = new stdClass;
654 + $upd_record->id = $booking_info['id'];
655 + if ($tn_amount) {
656 + // update amount paid
657 + $upd_record->totpaid = $booking_info['totpaid'] + $tn_amount;
658 + // update payable amount (if needed)
659 + $new_payable = $booking_info['payable'] - $tn_amount;
660 + $new_payable = $new_payable < 0 ? 0 : $new_payable;
661 + $upd_record->payable = $new_payable;
662 + }
663 + if ($tn_log) {
664 + $upd_record->paymentlog = $booking_info['paymentlog'] . "\n\n" . date('c') . "\n" . $tn_log;
665 + }
666 + $upd_record->paymcount = ((int) $booking_info['paymcount'] + 1);
667 +
668 + // update reservation record
669 + $dbo->updateObject('#__vikbooking_orders', $upd_record, 'id');
670 +
671 + // payment processor name
672 + $pay_process_name = $this->payment_method ? $this->payment_method['name'] : 'CC Manual Charge (Off-Session)';
673 +
674 + // handle transaction data to eventually support a later transaction of type refund
675 + $tn_data = isset($array_result['transaction']) ? $array_result['transaction'] : null;
676 + if ($tn_amount) {
677 + // check event data payload to store
678 + if (is_array($tn_data)) {
679 + // set key
680 + $tn_data['amount_paid'] = $tn_amount;
681 + } elseif (is_object($tn_data)) {
682 + // set property
683 + $tn_data->amount_paid = $tn_amount;
684 + } elseif (!$tn_data) {
685 + // build an array (we add the payment name because we know there is no other transaction data)
686 + $tn_data = [
687 + 'amount_paid' => $tn_amount,
688 + 'payment_method' => $pay_process_name,
689 + ];
690 + }
691 + }
692 +
693 + /**
694 + * Check if the payment processor returned the information about the amount of processing fees.
695 + */
696 + if ($tn_data && isset($array_result['tot_fees']) && $array_result['tot_fees']) {
697 + // check event data payload to store
698 + if (is_array($tn_data)) {
699 + // set key
700 + $tn_data['processing_fees'] = (float) $array_result['tot_fees'];
701 + } elseif (is_object($tn_data)) {
702 + // set property
703 + $tn_data->processing_fees = (float) $array_result['tot_fees'];
704 + }
705 + }
706 +
707 + // current admin-user
708 + $now_user = JFactory::getUser();
709 +
710 + // Booking History
711 + $ev_descr = JText::translate('VBO_W_VIRTUALTERMINAL_TITLE') . " - {$pay_process_name} ({$now_user->name})";
712 + VikBooking::getBookingHistoryInstance()->setBid($booking_info['id'])->setExtraData($tn_data)->store('P' . ($booking_info['paymcount'] > 0 ? 'N' : '0'), $ev_descr);
713 +
714 + return [
715 + 'success' => 1,
716 + 'log' => $tn_log,
717 + ];
718 + }
719 +
720 + /**
567 721 * Preload the necessary assets.
568 722 *
569 723 * @return void
570 724 */
@@ -577,13 +731,13 @@
577 731 /**
578 732 * Main method to invoke the widget. Contents will be loaded
579 733 * through AJAX requests, not via PHP when the page loads.
580 734 *
581 - * @param VBOMultitaskData $data
735 + * @param ?VBOMultitaskData $data
582 736 *
583 737 * @return void
584 738 */
585 - public function render(VBOMultitaskData $data = null)
739 + public function render(?VBOMultitaskData $data = null)
586 740 {
587 741 // increase widget's instance counter
588 742 static::$instance_counter++;
589 743
@@ -714,9 +868,9 @@
714 868 return false;
715 869 }
716 870
717 871 // the trigger button
718 - var charge_cmd = widget_instance.find('.vbo-vterminal-cc-row-submit').find('button');
872 + var charge_cmd = widget_instance.find('.vbo-vterminal-cc-row-submit[data-tn-type="direct-charge"]').find('button');
719 873
720 874 // disable button
721 875 charge_cmd.prop('disabled', true);
722 876
@@ -786,8 +940,83 @@
786 940 );
787 941 }
788 942
789 943 /**
944 + * Performs the request to charge a pre-authorized card (off-session).
945 + */
946 + function vboWidgetVTerminalOffSessionCharge(wrapper) {
947 + let widget_instance = jQuery('#' + wrapper);
948 + if (!widget_instance.length) {
949 + return false;
950 + }
951 +
952 + // the trigger button
953 + let charge_cmd = widget_instance.find('.vbo-vterminal-cc-row-submit[data-tn-type="off-session"]').find('button');
954 +
955 + // disable button
956 + charge_cmd.prop('disabled', true);
957 +
958 + if (VBOCore.options.default_loading_body) {
959 + // show loading spinner
960 + charge_cmd.find('i').replaceWith(VBOCore.options.default_loading_body);
961 + }
962 +
963 + // get the booking ID for the transaction
964 + let force_bid = widget_instance.attr('data-pagebid');
965 +
966 + // the widget method to call
967 + let call_method = 'doOffSessionCapture';
968 +
969 + // make a request to load the bookings calendar
970 + VBOCore.doAjax(
971 + "<?php echo $this->getExecWidgetAjaxUri(); ?>",
972 + {
973 + widget_id: "<?php echo $this->getIdentifier(); ?>",
974 + call: call_method,
975 + return: 1,
976 + bid: force_bid,
977 + currency: widget_instance.find('input[data-vt-cc-field="currency"]').val(),
978 + amount: widget_instance.find('input[data-vt-cc-field="amount"]').val(),
979 + wrapper: wrapper,
980 + tmpl: "component"
981 + },
982 + (response) => {
983 + try {
984 + let obj_res = typeof response === 'string' ? JSON.parse(response) : response;
985 + if (!obj_res.hasOwnProperty(call_method)) {
986 + console.error('Unexpected JSON response', obj_res);
987 + return false;
988 + }
989 +
990 + // turn flag on
991 + vbo_widget_vt_last_tn = 1;
992 +
993 + // update button status
994 + charge_cmd.removeClass('vbo-config-btn').addClass('btn-success').html(vbo_vt_icon_success + ' ' + Joomla.JText._('VBOUPLOADFILEDONE'));
995 +
996 + // check if we need to dismiss the modal widget
997 + let js_modal_id = widget_instance.attr('data-modalid');
998 + if (js_modal_id) {
999 + setTimeout(() => {
1000 + // dismiss modal widget
1001 + VBOCore.emitEvent('vbo-dismiss-widget-modal' + js_modal_id);
1002 + }, 1500);
1003 + }
1004 + } catch(err) {
1005 + console.error('could not parse JSON response', err, response);
1006 + }
1007 + },
1008 + (error) => {
1009 + // display error message
1010 + alert(error.responseText);
1011 + // restore button
1012 + charge_cmd.prop('disabled', false);
1013 + charge_cmd.find('i').replaceWith(vbo_vt_icon_error);
1014 + }
1015 + );
1016 + }
1017 +
1018 + /**
790 1019 * Generate the HTML skeleton loading string to build the form.
791 1020 */
792 1021 function vboWidgetVTerminalFormSkeleton(wrapper) {
793 1022 var widget_instance = jQuery('#' + wrapper);
@@ -1058,9 +1287,9 @@
1058 1287
1059 1288 // show loading skeletons
1060 1289 vboWidgetVTerminalFormSkeleton('<?php echo $wrapper_id; ?>');
1061 1290
1062 - // when document is ready, load bookings calendar for this widget's instance
1291 + // when document is ready, load terminal form for this widget's instance
1063 1292 vboWidgetVTerminalFormLoad('<?php echo $wrapper_id; ?>', '<?php echo $page_bid; ?>');
1064 1293
1065 1294 // subscribe to the multitask-panel-open event
1066 1295 document.addEventListener(VBOCore.multitask_open_event, function() {
@@ -1066,9 +1295,9 @@
1066 1295 document.addEventListener(VBOCore.multitask_open_event, function() {
1067 1296 vboWidgetVTerminalMultitaskOpen('<?php echo $wrapper_id; ?>');
1068 1297 });
1069 1298
1070 - // subscribe to the multitask-panel-close event to emit the event for the lastly created booking ID
1299 + // subscribe to the multitask-panel-close event to propagate the event for new transaction
1071 1300 document.addEventListener(VBOCore.multitask_close_event, function() {
1072 1301 if (vbo_widget_vt_last_tn) {
1073 1302 // emit the event with data for anyone who is listening to it
1074 1303 VBOCore.emitEvent('vbo_new_payment_transaction', {
@@ -1106,161 +1335,17 @@
1106 1335 *
1107 1336 * @param array $booking the reservation record as an associative array.
1108 1337 * @param array $card the card details collected through the Virtual Terminal.
1109 1338 *
1110 - * @return object|null the payment processor dispatcher instance or null.
1339 + * @return ?object the payment processor dispatcher instance or null.
1111 1340 */
1112 1341 protected function getPaymentProcessor(array $booking, array $card = [])
1113 1342 {
1114 - $processor = null;
1115 - $payment = [];
1116 -
1117 - if (!empty($booking['idpayment'])) {
1118 - $payment = VikBooking::getPayment($booking['idpayment']);
1343 + try {
1344 + $processor = VBOModelReservation::getInstance($booking, true)->getPaymentProcessor($card);
1345 + } catch (Exception $e) {
1346 + $processor = null;
1119 1347 }
1120 1348
1121 - if ($card) {
1122 - // inject CC details for the payment processor
1123 - $booking['card'] = $card;
1124 - }
1125 -
1126 - if ($payment && VBOPlatformDetection::isWordPress()) {
1127 - /**
1128 - * @wponly The payment gateway is loaded
1129 - * through the apposite dispatcher.
1130 - */
1131 - JLoader::import('adapter.payment.dispatcher');
1132 - $processor = JPaymentDispatcher::getInstance('vikbooking', $payment['file'], $booking, $payment['params']);
1133 - } elseif ($payment && VBOPlatformDetection::isJoomla()) {
1134 - /**
1135 - * @joomlaonly The Payment Factory library will invoke the gateway.
1136 - */
1137 - require_once VBO_ADMIN_PATH . DIRECTORY_SEPARATOR . 'payments' . DIRECTORY_SEPARATOR . 'libraries' . DIRECTORY_SEPARATOR . 'factory.php';
1138 - $processor = VBOPaymentFactory::getPaymentInstance($payment['file'], $booking, $payment['params']);
1139 - }
1140 -
1141 - if ($processor && method_exists($processor, 'isDirectChargeSupported') && $processor->isDirectChargeSupported()) {
1142 - // set payment method flag loaded
1143 - $this->payment_method = $payment;
1144 -
1145 - // return the valid payment processor instance
1146 - return $processor;
1147 - }
1148 -
1149 - return null;
1150 - }
1151 -
1152 - /**
1153 - * Given a raw string of credit card key-value pairs from payments log,
1154 - * parse the corresponding keys and values into an associative array.
1155 - * In case of conflicting keys with the remotely decoded CC details,
1156 - * attempts to replace the masked numbers with asterisks.
1157 - *
1158 - * @param string $cc_payload the raw CC details from payment logs.
1159 - * @param array $remote_cc_data associative array of decoded CC data.
1160 - *
1161 - * @return array associative or empty array.
1162 - */
1163 - protected function parseCreditCardValuePairs($cc_payload, array $remote_cc_data = [])
1164 - {
1165 - $cc_value_pairs = [];
1166 -
1167 - if (empty($cc_payload)) {
1168 - return $cc_value_pairs;
1169 - }
1170 -
1171 - $cc_lines = preg_split("/(\r\n|\n|\r)/", $cc_payload);
1172 -
1173 - foreach ($cc_lines as $cc_line) {
1174 - if (strpos($cc_line, ':') === false) {
1175 - continue;
1176 - }
1177 -
1178 - $cc_line_parts = explode(':', $cc_line);
1179 -
1180 - if (empty($cc_line_parts[0]) || !strlen(trim($cc_line_parts[1]))) {
1181 - continue;
1182 - }
1183 -
1184 - $key = str_replace(' ', '_', strtolower($cc_line_parts[0]));
1185 - $value = trim($cc_line_parts[1]);
1186 -
1187 - if (isset($cc_value_pairs[$key])) {
1188 - /**
1189 - * Do not overwrite existing keys because this probably means that the
1190 - * credit card was updated by an OTA like Booking.com, hence the payment
1191 - * logs string in VBO may contain the information of two different cards.
1192 - * New credit card details are always pre-pended by VCM in the payment logs.
1193 - *
1194 - * @since 1.16.5 (J) - 1.6.5 (WP)
1195 - */
1196 - continue;
1197 - }
1198 -
1199 - if (!empty($remote_cc_data[$key]) && is_string($remote_cc_data[$key]) && strpos($value, '*') !== false) {
1200 - // replace masked numbers with remote content
1201 - $value = $this->replaceMaskedNumbers($value, $remote_cc_data[$key]);
1202 - }
1203 -
1204 - $cc_value_pairs[$key] = $value;
1205 - }
1206 -
1207 - return $cc_value_pairs;
1208 - }
1209 -
1210 - /**
1211 - * Given a local and a remote credit card number string with
1212 - * masked symbols, replaces the values in the corresponding
1213 - * positions with the unmasked numbers.
1214 - *
1215 - * @param string $local current string with masked values.
1216 - * @param string $remote remote string with unmasked values.
1217 - *
1218 - * @return string the local string with unmasked values.
1219 - */
1220 - protected function replaceMaskedNumbers($local, $remote)
1221 - {
1222 - // split anything but numbers
1223 - $numbers = preg_split("/([^0-9]+)/", trim($remote));
1224 -
1225 - if ($numbers) {
1226 - // filter empty values
1227 - $numbers = array_filter($numbers);
1228 - }
1229 -
1230 - if (!$numbers) {
1231 - // unable to proceed
1232 - return $local;
1233 - }
1234 -
1235 - // split anything but stars (asterisks)
1236 - $stars = preg_split("/([^\*]+)/", trim($local));
1237 -
1238 - if ($stars) {
1239 - // filter empty values
1240 - $stars = array_filter($stars);
1241 - }
1242 -
1243 - if (!$stars) {
1244 - // unable to proceed
1245 - return $local;
1246 - }
1247 -
1248 - // replace masked symbols with numbers at their first occurrence
1249 - foreach ($numbers as $k => $unmasked) {
1250 - if (!isset($stars[$k])) {
1251 - continue;
1252 - }
1253 -
1254 - $masked_pos = strpos($local, $stars[$k]);
1255 -
1256 - if ($masked_pos === false) {
1257 - continue;
1258 - }
1259 -
1260 - $local = substr_replace($local, $unmasked, $masked_pos, strlen($stars[$k]));
1261 - }
1262 -
1263 - // return the string with possibly unmasked values
1264 - return $local;
1349 + return $processor;
1265 1350 }
1266 1351 }