File "virtual_terminal.php"
Full Path: /home/romayxjt/public_html/wp-content/plugins/vikbooking/admin/helpers/widgets/virtual_terminal.php
File size: 40.78 KB
MIME-type: text/x-php
Charset: utf-8
<?php
/**
* @package VikBooking
* @subpackage com_vikbooking
* @author Alessio Gaggii - E4J srl
* @copyright Copyright (C) 2023 E4J srl. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
* @link https://vikwp.com
*/
defined('ABSPATH') or die('No script kiddies please!');
/**
* Class handler for admin widget "virtual terminal".
*
* @since 1.16.4 (J) - 1.6.4 (WP)
*/
class VikBookingAdminWidgetVirtualTerminal extends VikBookingAdminWidget
{
/**
* The instance counter of this widget. Since we do not load individual parameters
* for each widget's instance, we use a static counter to determine its settings.
*
* @var int
*/
protected static $instance_counter = -1;
/**
* Tells whether VCM is installed and updated.
*
* @var bool
*/
protected $vcm_exists = false;
/**
* The payment processor record loaded.
*
* @var array
*/
protected $payment_method = [];
/**
* Class constructor will define the widget name and identifier.
*/
public function __construct()
{
// call parent constructor
parent::__construct();
$this->widgetName = JText::translate('VBO_W_VIRTUALTERMINAL_TITLE');
$this->widgetDescr = JText::translate('VBO_W_VIRTUALTERMINAL_DESCR');
$this->widgetId = basename(__FILE__, '.php');
// define widget and icon and style name
$this->widgetIcon = '<i class="' . VikBookingIcons::i('credit-card') . '"></i>';
$this->widgetStyleName = 'red';
// whether VCM is available
if (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'lib.vikchannelmanager.php')) {
$this->vcm_exists = true;
}
}
/**
* Custom method for this widget only to load the virtual terminal CC form.
* The method is called by the admin controller through an AJAX request.
* The visibility should be public, it should not exit the process, and
* any content sent to output will be returned to the AJAX response.
* In this case we return an array because this method requires "return":1.
*/
public function loadTerminalForm()
{
$dbo = JFactory::getDbo();
$wrapper = VikRequest::getString('wrapper', '', 'request');
$bid = VikRequest::getString('bid', '', 'request');
if (!$bid) {
// booking ID is mandatory
VBOHttpDocument::getInstance()->close(500, JText::translate('VBPEDITBUSYONE'));
}
// get booking details
$booking_info = VikBooking::getBookingInfoFromID($bid);
if (!$booking_info) {
// booking record not found, try to see if an OTA reservation ID was given
$dbo->setQuery(
$dbo->getQuery(true)
->select($dbo->qn('id'))
->from($dbo->qn('#__vikbooking_orders'))
->where($dbo->qn('idorderota') . ' = ' . $dbo->q($bid))
->where($dbo->qn('channel') . ' IS NOT NULL'),
0, 1
);
$ota_id = $dbo->loadResult();
if ($ota_id) {
// overwrite booking ID value
$bid = $ota_id;
// get booking details
$booking_info = VikBooking::getBookingInfoFromID($bid);
}
if (!$booking_info) {
// booking record could not be found
VBOHttpDocument::getInstance()->close(404, JText::translate('VBPEDITBUSYONE'));
}
}
// ensure a valid payment processor is assigned to the reservation
if (!($processor = $this->getPaymentProcessor($booking_info))) {
// unable to proceed due to unsupported transaction
return [
'html' => '<p class="err">' . JText::translate('VBO_PAY_PROCESS_NO_DIRECT_CHARGE') . '</p>',
];
}
// tell whether off-session capturing is supported
$off_session_capturing = method_exists($processor, 'isOffSessionCaptureSupported') && $processor->isOffSessionCaptureSupported();
$off_session_tn_data = $off_session_capturing ? VBOModelReservation::getInstance($booking_info, true)->getOffSessionTransactionData() : [];
// make sure the permissions are met
if ($this->vcm_exists && !JFactory::getUser()->authorise('core.admin', 'com_vikchannelmanager') && !$off_session_tn_data) {
// insufficient permissions to handle CC details
return [
'html' => '<p class="err">' . JText::translate('JERROR_ALERTNOAUTHOR') . '</p>',
];
}
// get the reservation credit card value pairs, if any
$cc_value_pairs = VBOModelReservation::getInstance($booking_info, true)->getCardValuePairs();
// currency code
$currency_code = !empty($booking_info['chcurrency']) ? $booking_info['chcurrency'] : VikBooking::getCurrencyName();
if (empty($currency_code) || strlen((string)$currency_code) != 3) {
// fallback to currency transaction code
$currency_code = VikBooking::getCurrencyCodePp();
}
// check known CC values to build the hidden transaction values
$known_tn_vals = [];
$hidden_tn_vals = [];
if (isset($cc_value_pairs['name'])) {
$known_tn_vals['name'] = $cc_value_pairs['name'];
}
if (isset($cc_value_pairs['card_number'])) {
$known_tn_vals['card_number'] = $cc_value_pairs['card_number'];
}
if (isset($cc_value_pairs['expiration_date'])) {
$known_tn_vals['expiration_date'] = $cc_value_pairs['expiration_date'];
}
if (isset($cc_value_pairs['cvv'])) {
$known_tn_vals['cvv'] = $cc_value_pairs['cvv'];
}
foreach ($cc_value_pairs as $key => $value) {
if (!isset($known_tn_vals[$key])) {
$hidden_tn_vals[$key] = $value;
}
}
/**
* Consider calculating an outstanding balance in case of previous payments.
*
* @since 1.16.7 (J) - 1.6.7 (WP)
* @since 1.18.0 (J) - 1.8.0 (WP) added support to amount raw for off-session capture.
*/
$default_total = $booking_info['total'];
if ($booking_info['totpaid'] > 0 && $booking_info['totpaid'] < $booking_info['total']) {
// default to the outstanding balance
$default_total = $booking_info['total'] - $booking_info['totpaid'];
}
if (!$booking_info['totpaid'] && $off_session_tn_data && ($off_session_tn_data[0]->amount_raw ?? null)) {
// default to the payable amount at the time of payment, unless empty
$default_total = ((float) $off_session_tn_data[0]->amount_raw) ?: $booking_info['total'];
}
// tell whether we only have an off-session transaction to capture
$only_off_session = $off_session_tn_data && !$cc_value_pairs;
// start output buffering
ob_start();
?>
<div class="vbo-vterminal-cc-container">
<div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-amount">
<div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-currency">
<div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBOCPARAMCURRENCY'); ?></div>
<div class="vbo-vterminal-cc-val">
<input type="text" autocomplete="off" value="<?php echo JHtml::fetch('esc_attr', $currency_code); ?>" data-vt-cc-field="currency" />
</div>
</div>
<div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-amount">
<div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CC_AMOUNT'); ?></div>
<div class="vbo-vterminal-cc-val">
<input type="number" value="<?php echo $default_total; ?>" min="0" step="any" data-vt-cc-field="amount" />
</div>
</div>
</div>
<?php
if ($off_session_tn_data) {
// when an off-session transaction is available, display the capture button
?>
<div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-submit" data-tn-type="off-session">
<div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-submit" data-tn-type="off-session">
<div class="vbo-vterminal-cc-val">
<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>
</div>
</div>
</div>
<?php
}
?>
<div class="vbo-vterminal-cc-group-cardwrap"<?php echo $off_session_tn_data ? ' data-has-offsession="1"' : ''; ?>>
<?php
if ($off_session_tn_data) {
// add a label for using the card
?>
<div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-usecard">
<div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-usecard">
<div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CREDIT_CARD') . ($only_off_session ? ' - ' . JText::translate('VBMAINPAYMENTSNEW') : ''); ?></div>
</div>
</div>
<?php
}
?>
<div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-cardholder">
<div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-cardholder">
<div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBISNOMINATIVE'); ?></div>
<div class="vbo-vterminal-cc-val">
<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" />
</div>
</div>
</div>
<div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-ccpan">
<div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-ccpan">
<div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CC_NUMBER'); ?></div>
<div class="vbo-vterminal-cc-val vbo-vterminal-cc-val-withlogo">
<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" />
<span class="vbo-vterminal-cc-type-logo"></span>
</div>
</div>
</div>
<div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-ccextra">
<div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-ccexpiry">
<div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CC_EXPIRY_DT'); ?></div>
<div class="vbo-vterminal-cc-val">
<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" />
</div>
</div>
<div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-cvc">
<div class="vbo-vterminal-cc-lbl"><?php echo JText::translate('VBO_CC_CVV'); ?></div>
<div class="vbo-vterminal-cc-val">
<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" />
</div>
</div>
</div>
<div class="vbo-vterminal-cc-row-group vbo-vterminal-cc-row-group-submit" data-tn-type="direct-charge">
<div class="vbo-vterminal-cc-row vbo-vterminal-cc-row-submit" data-tn-type="direct-charge">
<div class="vbo-vterminal-cc-val">
<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>
</div>
</div>
</div>
</div>
<?php
// print hidden transaction values, if any
foreach ($hidden_tn_vals as $key => $value) {
?>
<input type="hidden" value="<?php echo JHtml::fetch('esc_attr', $value); ?>" data-vt-cc-field="<?php echo JHtml::fetch('esc_attr', $key); ?>" />
<?php
}
?>
</div>
<script type="text/javascript">
// store the last card type detected
var vbo_vt_last_cc_type = '';
// subscribe to the keyup event for the card number field
var vbo_vt_cc_num_f = document.querySelector('#<?php echo $wrapper; ?>').querySelector('[data-vt-cc-field="card_number"]');
vbo_vt_cc_num_f.addEventListener('keyup', (e) => {
if (!e || !e.target) {
return;
}
// the current CC number value
var cc_value = e.target.value;
// invoke the helper class to handle the card number
const card = new VBOCreditCard(cc_value);
// detect card type and format card number
var card_type = card.getCardType();
var cc_text = card.formatCreditCard(card_type);
// set formatted CC number value
e.target.value = cc_text;
// handle CC logo
if (vbo_vt_last_cc_type != card_type) {
var card_logo_uri = card.getCardLogoURI(card_type);
var card_logo_pnode = e.target.parentNode;
var card_logo_wrap = card_logo_pnode.querySelector('.vbo-vterminal-cc-type-logo');
if (!card_type) {
// hide CC logo
card_logo_wrap.innerHTML = '';
card_logo_pnode.classList.remove('vbo-vterminal-cc-type-logo-detected');
card_logo_pnode.classList.add('vbo-vterminal-cc-type-logo-unknown');
} else if (card_logo_uri) {
// set CC logo
card_logo_wrap.innerHTML = '<img src="' + card_logo_uri + '" />';
card_logo_pnode.classList.remove('vbo-vterminal-cc-type-logo-unknown');
card_logo_pnode.classList.add('vbo-vterminal-cc-type-logo-detected');
}
// overwrite value
vbo_vt_last_cc_type = card_type;
}
});
// subscribe to the keydown and keyup events for the card expiration date field
var vbo_vt_cc_exp_f = document.querySelector('#<?php echo $wrapper; ?>').querySelector('[data-vt-cc-field="expiry"]');
vbo_vt_cc_exp_f.addEventListener('keydown', (e) => {
if (!e || !e.key) {
return;
}
if (!isNaN(e.key) || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
return true;
}
switch (e.key) {
case "ArrowLeft":
case "ArrowRight":
case "Enter":
case "Escape":
case "Delete":
case "Backspace":
case "Tab":
case "/":
return true;
default:
event.preventDefault();
return false;
}
});
vbo_vt_cc_exp_f.addEventListener('keyup', (e) => {
if (!e || !e.target) {
return;
}
var date = e.target.value;
if (!date) {
return;
}
if (date.length === 2 && date.indexOf('/') < 0) {
e.target.value += '/';
return;
}
if (date.length > 7) {
e.target.value = e.target.value.substr(0, 7);
return;
}
});
// subscribe to the blur event to make sure the year is full
vbo_vt_cc_exp_f.addEventListener('blur', (e) => {
if (!e || !e.target) {
return;
}
var date = e.target.value;
if (!date || date.length >= 7 || date.indexOf('/') < 0) {
return;
}
var parts = date.split('/');
if (parts[1].length === 2) {
var today = new Date;
var year = (today.getFullYear() + '').substr(0, 2);
e.target.value = parts[0] + '/' + year + parts[1];
}
return;
});
// default state for CC number and expiry input fields
setTimeout(() => {
if (vbo_vt_cc_num_f.value) {
// trigger keyup event to format the current CC and display the logo
vbo_vt_cc_num_f.dispatchEvent(new Event('keyup'));
}
if (vbo_vt_cc_exp_f.value) {
// trigger blur event to format the current CC expiration date
vbo_vt_cc_exp_f.dispatchEvent(new Event('blur'));
}
}, 128);
</script>
<?php
// get the HTML buffer
$html_content = ob_get_contents();
ob_end_clean();
// return an associative array of values
return array(
'html' => $html_content,
);
}
/**
* Custom method for this widget only to debit a credit card.
*
* @return array|void
*/
public function doDirectCharge()
{
$wrapper = VikRequest::getString('wrapper', '', 'request');
$bid = VikRequest::getInt('bid', 0, 'request');
$card = VikRequest::getVar('card', [], 'request');
if (!$bid || !$card) {
// booking ID and CC details are mandatory
VBOHttpDocument::getInstance()->close(500, JText::translate('VBPEDITBUSYONE'));
}
// get booking details
$booking_info = VikBooking::getBookingInfoFromID($bid);
if (!$booking_info) {
// booking record not found
VBOHttpDocument::getInstance()->close(404, JText::translate('VBPEDITBUSYONE'));
}
// get the eligible payment processor by passing the card details
$processor = $this->getPaymentProcessor($booking_info, $card);
if (!$processor) {
VBOHttpDocument::getInstance()->close(500, JText::translate('VBO_PAY_PROCESS_NO_DIRECT_CHARGE'));
}
// default transaction response
$array_result = [
'verified' => 0,
];
try {
// perform the transaction
$array_result = $processor->directCharge();
} catch (Exception $e) {
// set error message
$array_result['log'] = sprintf(JText::translate('VBO_CC_TN_ERROR') . " \n%s", $e->getMessage());
}
if ($array_result['verified'] != 1) {
// erroneous response
if (!empty($array_result['log']) && is_string($array_result['log'])) {
VBOHttpDocument::getInstance()->close(500, $array_result['log']);
} else {
VBOHttpDocument::getInstance()->close(500, 'Operation failed');
}
}
// valid transaction response!
// update booking details
$dbo = JFactory::getDbo();
// get the amount paid
$tn_amount = isset($array_result['tot_paid']) ? (float) $array_result['tot_paid'] : null;
// get the log string, if any
$tn_log = !empty($array_result['log']) ? $array_result['log'] : '';
// update record
$upd_record = new stdClass;
$upd_record->id = $booking_info['id'];
if ($tn_amount) {
// update amount paid
$upd_record->totpaid = $booking_info['totpaid'] + $tn_amount;
// update payable amount (if needed)
$new_payable = $booking_info['payable'] - $tn_amount;
$new_payable = $new_payable < 0 ? 0 : $new_payable;
$upd_record->payable = $new_payable;
}
if ($tn_log) {
$upd_record->paymentlog = $booking_info['paymentlog'] . "\n\n" . date('c') . "\n" . $tn_log;
}
$upd_record->paymcount = ((int)$booking_info['paymcount'] + 1);
// update reservation record
$dbo->updateObject('#__vikbooking_orders', $upd_record, 'id');
// payment processor name
$pay_process_name = $this->payment_method ? $this->payment_method['name'] : 'CC Direct Charge';
// handle transaction data to eventually support a later transaction of type refund
$tn_data = isset($array_result['transaction']) ? $array_result['transaction'] : null;
if ($tn_amount) {
// check event data payload to store
if (is_array($tn_data)) {
// set key
$tn_data['amount_paid'] = $tn_amount;
} elseif (is_object($tn_data)) {
// set property
$tn_data->amount_paid = $tn_amount;
} elseif (!$tn_data) {
// build an array (we add the payment name because we know there is no other transaction data)
$tn_data = [
'amount_paid' => $tn_amount,
'payment_method' => $pay_process_name,
];
}
}
/**
* Check if the payment processor returned the information about the amount of processing fees.
*
* @since 1.16.9 (J) - 1.6.9 (WP)
*/
if ($tn_data && isset($array_result['tot_fees']) && $array_result['tot_fees']) {
// check event data payload to store
if (is_array($tn_data)) {
// set key
$tn_data['processing_fees'] = (float) $array_result['tot_fees'];
} elseif (is_object($tn_data)) {
// set property
$tn_data->processing_fees = (float) $array_result['tot_fees'];
}
}
// current admin-user
$now_user = JFactory::getUser();
// Booking History
$ev_descr = JText::translate('VBO_W_VIRTUALTERMINAL_TITLE') . " - {$pay_process_name} ({$now_user->name})";
VikBooking::getBookingHistoryInstance()->setBid($booking_info['id'])->setExtraData($tn_data)->store('P' . ($booking_info['paymcount'] > 0 ? 'N' : '0'), $ev_descr);
return [
'success' => 1,
'log' => $tn_log,
];
}
/**
* Custom method for this widget only to debit a credit card off-session.
*
* @return ?array
*
* @since 1.18.0 (J) - 1.8.0 (WP)
*/
public function doOffSessionCapture()
{
$app = JFactory::getApplication();
$wrapper = $app->input->getString('wrapper', '');
$bid = $app->input->getUInt('bid', 0);
$amount = $app->input->getFloat('amount', 0);
$currency = $app->input->getString('currency', '');
if (!$bid || !$amount) {
// booking ID and amount are mandatory
VBOHttpDocument::getInstance()->close(500, JText::translate('VBPEDITBUSYONE'));
}
// get booking details
$booking_info = VikBooking::getBookingInfoFromID($bid);
if (!$booking_info) {
// booking record not found
VBOHttpDocument::getInstance()->close(404, JText::translate('VBPEDITBUSYONE'));
}
// collect the previous transaction data list
$tn_data = VBOModelReservation::getInstance($booking_info, true)->getOffSessionTransactionData();
if (!$tn_data) {
// previous transaction data not found
VBOHttpDocument::getInstance()->close(400, 'Missing previous transaction data');
}
// set the amount to capture and transaction data before accessing the payment processor
$booking_info['total_to_pay'] = $amount;
$booking_info['tn_currency'] = $currency;
$booking_info['transaction'] = $tn_data[0];
// get the eligible payment processor
$processor = $this->getPaymentProcessor($booking_info);
if (!$processor) {
VBOHttpDocument::getInstance()->close(500, JText::translate('VBO_PAY_PROCESS_NO_DIRECT_CHARGE'));
}
// default transaction response
$array_result = [
'verified' => 0,
];
try {
// perform the transaction
$array_result = $processor->offSessionCapture();
} catch (Exception $e) {
// set error message
$array_result['log'] = sprintf(JText::translate('VBO_CC_TN_ERROR') . " \n%s", $e->getMessage());
}
if ($array_result['verified'] != 1) {
// erroneous response
if (!empty($array_result['log']) && is_string($array_result['log'])) {
VBOHttpDocument::getInstance()->close(500, $array_result['log']);
} else {
VBOHttpDocument::getInstance()->close(500, 'Operation failed');
}
}
// valid transaction response!
// update booking details
$dbo = JFactory::getDbo();
// get the amount paid
$tn_amount = isset($array_result['tot_paid']) ? (float) $array_result['tot_paid'] : null;
// get the log string, if any
$tn_log = !empty($array_result['log']) ? $array_result['log'] : '';
// update record
$upd_record = new stdClass;
$upd_record->id = $booking_info['id'];
if ($tn_amount) {
// update amount paid
$upd_record->totpaid = $booking_info['totpaid'] + $tn_amount;
// update payable amount (if needed)
$new_payable = $booking_info['payable'] - $tn_amount;
$new_payable = $new_payable < 0 ? 0 : $new_payable;
$upd_record->payable = $new_payable;
}
if ($tn_log) {
$upd_record->paymentlog = $booking_info['paymentlog'] . "\n\n" . date('c') . "\n" . $tn_log;
}
$upd_record->paymcount = ((int) $booking_info['paymcount'] + 1);
// update reservation record
$dbo->updateObject('#__vikbooking_orders', $upd_record, 'id');
// payment processor name
$pay_process_name = $this->payment_method ? $this->payment_method['name'] : 'CC Manual Charge (Off-Session)';
// handle transaction data to eventually support a later transaction of type refund
$tn_data = isset($array_result['transaction']) ? $array_result['transaction'] : null;
if ($tn_amount) {
// check event data payload to store
if (is_array($tn_data)) {
// set key
$tn_data['amount_paid'] = $tn_amount;
} elseif (is_object($tn_data)) {
// set property
$tn_data->amount_paid = $tn_amount;
} elseif (!$tn_data) {
// build an array (we add the payment name because we know there is no other transaction data)
$tn_data = [
'amount_paid' => $tn_amount,
'payment_method' => $pay_process_name,
];
}
}
/**
* Check if the payment processor returned the information about the amount of processing fees.
*/
if ($tn_data && isset($array_result['tot_fees']) && $array_result['tot_fees']) {
// check event data payload to store
if (is_array($tn_data)) {
// set key
$tn_data['processing_fees'] = (float) $array_result['tot_fees'];
} elseif (is_object($tn_data)) {
// set property
$tn_data->processing_fees = (float) $array_result['tot_fees'];
}
}
// current admin-user
$now_user = JFactory::getUser();
// Booking History
$ev_descr = JText::translate('VBO_W_VIRTUALTERMINAL_TITLE') . " - {$pay_process_name} ({$now_user->name})";
VikBooking::getBookingHistoryInstance()->setBid($booking_info['id'])->setExtraData($tn_data)->store('P' . ($booking_info['paymcount'] > 0 ? 'N' : '0'), $ev_descr);
return [
'success' => 1,
'log' => $tn_log,
];
}
/**
* Preload the necessary assets.
*
* @return void
*/
public function preload()
{
// JS lang def
JText::script('VBOUPLOADFILEDONE');
}
/**
* Main method to invoke the widget. Contents will be loaded
* through AJAX requests, not via PHP when the page loads.
*
* @param ?VBOMultitaskData $data
*
* @return void
*/
public function render(?VBOMultitaskData $data = null)
{
// increase widget's instance counter
static::$instance_counter++;
// check whether the widget is being rendered via AJAX when adding it through the customizer
$is_ajax = $this->isAjaxRendering();
// generate a unique ID for the sticky notes wrapper instance
$wrapper_instance = !$is_ajax ? static::$instance_counter : rand();
$wrapper_id = 'vbo-widget-vterminal-' . $wrapper_instance;
// get permissions
$vbo_auth_bookings = JFactory::getUser()->authorise('core.vbo.bookings', 'com_vikbooking');
if (!$vbo_auth_bookings) {
// display nothing
return;
}
// check multitask data
$page_bid = 0;
$js_modal_id = '';
if ($data) {
$is_modal_rendering = $data->isModalRendering();
$page_bid = $data->getBookingID() ?: $this->options()->fetchBookingId();
if ($is_modal_rendering) {
// get modal JS identifier
$js_modal_id = $data->getModalJsIdentifier();
}
}
if (!$page_bid) {
// unable to continue from a page outside the booking details or booking ID set
?>
<p class="warn"><?php echo JText::translate('VBPEDITBUSYONE'); ?></p>
<?php
return;
}
?>
<div id="<?php echo $wrapper_id; ?>" class="vbo-admin-widget-wrapper" data-instance="<?php echo $wrapper_instance; ?>" data-pagebid="<?php echo $page_bid; ?>" data-modalid="<?php echo $js_modal_id; ?>">
<div class="vbo-admin-widget-head">
<div class="vbo-admin-widget-head-inline">
<h4><?php echo $this->widgetIcon; ?> <span><?php echo $this->widgetName; ?></span></h4>
</div>
</div>
<div class="vbo-widget-vterminal-wrap">
<div class="vbo-widget-vterminal-inner">
<div class="vbo-widget-vterminal-form"></div>
</div>
</div>
</div>
<?php
if (static::$instance_counter === 0 || $is_ajax) {
/**
* Print the JS code only once for all instances of this widget.
* The real rendering is made through AJAX, not when the page loads.
*/
?>
<script type="text/javascript">
/**
* Default icons for status.
*/
var vbo_vt_icon_error = '<?php VikBookingIcons::e('exclamation-circle'); ?>';
var vbo_vt_icon_success = '<?php VikBookingIcons::e('check-circle'); ?>';
/**
* Perform the request to load the virtual terminal form.
*/
function vboWidgetVTerminalFormLoad(wrapper, page_bid) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
// check if multitask data passed a booking ID for the current page
var force_bid = 0;
if (typeof page_bid !== 'undefined' && page_bid) {
force_bid = page_bid;
}
// the widget method to call
var call_method = 'loadTerminalForm';
// make a request to load the bookings calendar
VBOCore.doAjax(
"<?php echo $this->getExecWidgetAjaxUri(); ?>",
{
widget_id: "<?php echo $this->getIdentifier(); ?>",
call: call_method,
return: 1,
bid: force_bid,
wrapper: wrapper,
tmpl: "component"
},
(response) => {
try {
var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
if (!obj_res.hasOwnProperty(call_method)) {
console.error('Unexpected JSON response', obj_res);
return false;
}
// replace HTML content
widget_instance.find('.vbo-widget-vterminal-form').html(obj_res[call_method]['html']);
} catch(err) {
console.error('could not parse JSON response', err, response);
}
},
(error) => {
// remove the skeleton loading
widget_instance.find('.vbo-widget-vterminal-form').find('.vbo-skeleton-loading').remove();
// display error message
alert(error.responseText);
}
);
}
/**
* Performs the request to charge the given card details.
*/
function vboWidgetVTerminalChargeCard(wrapper) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
// the trigger button
var charge_cmd = widget_instance.find('.vbo-vterminal-cc-row-submit[data-tn-type="direct-charge"]').find('button');
// disable button
charge_cmd.prop('disabled', true);
if (VBOCore.options.default_loading_body) {
// show loading spinner
charge_cmd.find('i').replaceWith(VBOCore.options.default_loading_body);
}
// gather all CC fields
var cc_fields = {};
widget_instance.find('.vbo-widget-vterminal-form').find('[data-vt-cc-field]').each(function() {
var cc_f_key = jQuery(this).attr('data-vt-cc-field');
cc_fields[cc_f_key] = jQuery(this).val();
});
// get the booking ID for the transaction
var force_bid = widget_instance.attr('data-pagebid');
// the widget method to call
var call_method = 'doDirectCharge';
// make a request to load the bookings calendar
VBOCore.doAjax(
"<?php echo $this->getExecWidgetAjaxUri(); ?>",
{
widget_id: "<?php echo $this->getIdentifier(); ?>",
call: call_method,
return: 1,
bid: force_bid,
card: cc_fields,
wrapper: wrapper,
tmpl: "component"
},
(response) => {
try {
var obj_res = typeof response === 'string' ? JSON.parse(response) : response;
if (!obj_res.hasOwnProperty(call_method)) {
console.error('Unexpected JSON response', obj_res);
return false;
}
// turn flag on
vbo_widget_vt_last_tn = 1;
// update button status
charge_cmd.removeClass('vbo-config-btn').addClass('btn-success').html(vbo_vt_icon_success + ' ' + Joomla.JText._('VBOUPLOADFILEDONE'));
// check if we need to dismiss the modal widget
var js_modal_id = widget_instance.attr('data-modalid');
if (js_modal_id) {
setTimeout(() => {
// dismiss modal widget
VBOCore.emitEvent('vbo-dismiss-widget-modal' + js_modal_id);
}, 1500);
}
} catch(err) {
console.error('could not parse JSON response', err, response);
}
},
(error) => {
// display error message
alert(error.responseText);
// restore button
charge_cmd.prop('disabled', false);
charge_cmd.find('i').replaceWith(vbo_vt_icon_error);
}
);
}
/**
* Performs the request to charge a pre-authorized card (off-session).
*/
function vboWidgetVTerminalOffSessionCharge(wrapper) {
let widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
// the trigger button
let charge_cmd = widget_instance.find('.vbo-vterminal-cc-row-submit[data-tn-type="off-session"]').find('button');
// disable button
charge_cmd.prop('disabled', true);
if (VBOCore.options.default_loading_body) {
// show loading spinner
charge_cmd.find('i').replaceWith(VBOCore.options.default_loading_body);
}
// get the booking ID for the transaction
let force_bid = widget_instance.attr('data-pagebid');
// the widget method to call
let call_method = 'doOffSessionCapture';
// make a request to load the bookings calendar
VBOCore.doAjax(
"<?php echo $this->getExecWidgetAjaxUri(); ?>",
{
widget_id: "<?php echo $this->getIdentifier(); ?>",
call: call_method,
return: 1,
bid: force_bid,
currency: widget_instance.find('input[data-vt-cc-field="currency"]').val(),
amount: widget_instance.find('input[data-vt-cc-field="amount"]').val(),
wrapper: wrapper,
tmpl: "component"
},
(response) => {
try {
let obj_res = typeof response === 'string' ? JSON.parse(response) : response;
if (!obj_res.hasOwnProperty(call_method)) {
console.error('Unexpected JSON response', obj_res);
return false;
}
// turn flag on
vbo_widget_vt_last_tn = 1;
// update button status
charge_cmd.removeClass('vbo-config-btn').addClass('btn-success').html(vbo_vt_icon_success + ' ' + Joomla.JText._('VBOUPLOADFILEDONE'));
// check if we need to dismiss the modal widget
let js_modal_id = widget_instance.attr('data-modalid');
if (js_modal_id) {
setTimeout(() => {
// dismiss modal widget
VBOCore.emitEvent('vbo-dismiss-widget-modal' + js_modal_id);
}, 1500);
}
} catch(err) {
console.error('could not parse JSON response', err, response);
}
},
(error) => {
// display error message
alert(error.responseText);
// restore button
charge_cmd.prop('disabled', false);
charge_cmd.find('i').replaceWith(vbo_vt_icon_error);
}
);
}
/**
* Generate the HTML skeleton loading string to build the form.
*/
function vboWidgetVTerminalFormSkeleton(wrapper) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
var def_loading = '';
def_loading += '<div class="vbo-dashboard-guest-activity vbo-dashboard-guest-activity-skeleton">';
def_loading += ' <div class="vbo-dashboard-guest-activity-avatar">';
def_loading += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-avatar"></div>';
def_loading += ' </div>';
def_loading += ' <div class="vbo-dashboard-guest-activity-content">';
def_loading += ' <div class="vbo-dashboard-guest-activity-content-head">';
def_loading += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-title"></div>';
def_loading += ' </div>';
def_loading += ' <div class="vbo-dashboard-guest-activity-content-subhead">';
def_loading += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-subtitle"></div>';
def_loading += ' </div>';
def_loading += ' <div class="vbo-dashboard-guest-activity-content-info-msg">';
def_loading += ' <div class="vbo-skeleton-loading vbo-skeleton-loading-content"></div>';
def_loading += ' </div>';
def_loading += ' </div>';
def_loading += '</div>';
widget_instance.find('.vbo-widget-vterminal-form').html(def_loading);
}
/**
* Triggers when the multitask panel opens.
*/
function vboWidgetVTerminalMultitaskOpen(wrapper) {
var widget_instance = jQuery('#' + wrapper);
if (!widget_instance.length) {
return false;
}
// check if a booking ID was set for this page
var page_bid = widget_instance.attr('data-pagebid');
if (!page_bid || page_bid < 1) {
return false;
}
// show loading skeletons
vboWidgetVTerminalFormSkeleton(wrapper);
// load data by injecting the current booking ID
vboWidgetVTerminalFormLoad(wrapper, page_bid);
}
/**
* Credit Card Detector Class.
*/
function VBOCreditCard(card) {
this.set(card);
}
VBOCreditCard.prototype.set = function(card) {
this.cards_logo_uri_base = '<?php echo VBO_ADMIN_URI . 'resources/js_upload/images/'; ?>';
this.card = [];
for (var i = 0; i < card.length; i++) {
var ch = card.charCodeAt(i);
if (ch >= 48 && ch <= 57) {
this.card.push(ch-48);
}
}
}
VBOCreditCard.prototype.get = function() {
return this.card;
}
VBOCreditCard.prototype.isEnoughSpace = function(len) {
return ( this.card.length >= len );
}
VBOCreditCard.prototype.isEmpty = function() {
return !this.isEnoughSpace(1);
}
VBOCreditCard.prototype.isValid = function() {
const type = this.getCardType();
if (type.length && this.card.length == VBOCreditCard.properties[type].size) {
return true;
}
return false;
}
VBOCreditCard.prototype.getNumberToIndex = function(i) {
var n = 0;
var factor = 1;
for (i = i-1 ; i >= 0; i--) {
n += factor * this.card[i];
factor *= 10;
}
return n;
}
VBOCreditCard.prototype.isVisa = function() {
return this.matchBrandRanges([
[4]
]);
}
VBOCreditCard.prototype.isMasterCard = function() {
return this.matchBrandRanges([
[51, 55],
[2221, 2720]
]);
}
VBOCreditCard.prototype.isAmericanExpress = function() {
return this.matchBrandRanges([
[34],
[37]
]);
}
VBOCreditCard.prototype.isDiners = function() {
return this.matchBrandRanges([
[300, 305],
[36],
[38, 39]
]);
}
VBOCreditCard.prototype.isDiscover = function() {
return this.matchBrandRanges([
[6011],
[65],
[622126, 622925],
[644, 649]
]);
}
VBOCreditCard.prototype.isJCB = function() {
return this.matchBrandRanges([
[3528, 3589]
]);
}
VBOCreditCard.prototype.getCardType = function() {
if (this.isVisa()) {
return VBOCreditCard.VISA;
} else if (this.isMasterCard()) {
return VBOCreditCard.MASTERCARD;
} else if (this.isAmericanExpress()) {
return VBOCreditCard.AMERICAN_EXPRESS;
} else if (this.isDiners()) {
return VBOCreditCard.DINERS;
} else if (this.isDiscover()) {
return VBOCreditCard.DISCOVER;
} else if (this.isJCB()) {
return VBOCreditCard.JCB;
}
return '';
}
VBOCreditCard.prototype.getCardLogoURI = function(type) {
if (!type) {
return '';
}
return this.cards_logo_uri_base + type + '.png';
}
VBOCreditCard.prototype.matchBrandRanges = function(ranges) {
for (var i = 0; i < ranges.length; i++) {
var r = ranges[i];
if (r.length == 1) {
if (this.isEnoughSpace((''+r[0]).length) && this.getNumberToIndex((''+r[0]).length) == r[0]) {
return true;
}
} else if (r.length == 2) {
var len = Math.max( (''+r[0]).length, (''+r[1]).length );
if (this.isEnoughSpace(len)) {
var val = this.getNumberToIndex(len);
if (r[0] <= val && val <= r[1]) {
return true;
}
}
}
}
return false;
}
VBOCreditCard.prototype.formatCreditCard = function(card_type) {
if (card_type === undefined) {
card_type = this.getCardType();
}
var blank_spaces = [];
if (card_type.length > 0) {
blank_spaces = VBOCreditCard.properties[card_type]['blank'];
}
var cc_str = '';
for (var i = 0; i < this.card.length; i++) {
cc_str += this.card[i];
if (blank_spaces.indexOf(i+1) != -1) {
cc_str += ' ';
}
}
return cc_str;
}
VBOCreditCard.properties = {
'visa': {
'size': 16,
'blank': [4, 8, 12]
},
'mastercard': {
'size': 16,
'blank': [4, 8, 12]
},
'amex': {
'size': 15,
'blank': [4, 10]
},
'discover': {
'size': 16,
'blank': [4, 8, 12]
},
'diners': {
'size': 14,
'blank': [4, 8, 12]
},
'jcb': {
'size': 16,
'blank': [4, 8, 12]
},
};
VBOCreditCard.VISA = 'visa';
VBOCreditCard.MASTERCARD = 'mastercard';
VBOCreditCard.AMERICAN_EXPRESS = 'amex';
VBOCreditCard.DINERS = 'diners';
VBOCreditCard.DISCOVER = 'discover';
VBOCreditCard.JCB = 'jcb';
</script>
<?php
}
?>
<script type="text/javascript">
// store the last processed transaction
var vbo_widget_vt_last_tn = null;
jQuery(function() {
// show loading skeletons
vboWidgetVTerminalFormSkeleton('<?php echo $wrapper_id; ?>');
// when document is ready, load terminal form for this widget's instance
vboWidgetVTerminalFormLoad('<?php echo $wrapper_id; ?>', '<?php echo $page_bid; ?>');
// subscribe to the multitask-panel-open event
document.addEventListener(VBOCore.multitask_open_event, function() {
vboWidgetVTerminalMultitaskOpen('<?php echo $wrapper_id; ?>');
});
// subscribe to the multitask-panel-close event to propagate the event for new transaction
document.addEventListener(VBOCore.multitask_close_event, function() {
if (vbo_widget_vt_last_tn) {
// emit the event with data for anyone who is listening to it
VBOCore.emitEvent('vbo_new_payment_transaction', {
tn: vbo_widget_vt_last_tn
});
}
});
<?php
if ($js_modal_id) {
// widget can be dismissed through the modal
?>
// subscribe to the modal-dismissed event to emit the event for the lastly created booking ID
document.addEventListener(VBOCore.widget_modal_dismissed + '<?php echo $js_modal_id; ?>', function() {
if (vbo_widget_vt_last_tn) {
// emit the event with data for anyone who is listening to it
VBOCore.emitEvent('vbo_new_payment_transaction', {
tn: vbo_widget_vt_last_tn
});
}
});
<?php
}
?>
});
</script>
<?php
}
/**
* Attempts to invoke the eligible payment processor assigned to the given reservation.
*
* @param array $booking the reservation record as an associative array.
* @param array $card the card details collected through the Virtual Terminal.
*
* @return ?object the payment processor dispatcher instance or null.
*/
protected function getPaymentProcessor(array $booking, array $card = [])
{
try {
$processor = VBOModelReservation::getInstance($booking, true)->getPaymentProcessor($card);
} catch (Exception $e) {
$processor = null;
}
return $processor;
}
}