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 = '';
$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' => '
' . JText::translate('VBO_PAY_PROCESS_NO_DIRECT_CHARGE') . '
',
];
}
// 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' => '' . JText::translate('JERROR_ALERTNOAUTHOR') . '
',
];
}
// 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();
?>
$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
?>
getPaymentProcessor($card);
} catch (Exception $e) {
$processor = null;
}
return $processor;
}
}