Hotel Raxa - Advanced Booking System Implementation
🏨 Hotel Booking Enhancements: - Implemented Eagle Booking Advanced Pricing add-on - Added Booking.com-style rate management system - Created professional calendar interface for pricing - Integrated deals and discounts functionality 💰 Advanced Pricing Features: - Dynamic pricing models (per room, per person, per adult) - Base rates, adult rates, and child rates management - Length of stay discounts and early bird deals - Mobile rates and secret deals implementation - Seasonal promotions and flash sales 📅 Availability Management: - Real-time availability tracking - Stop sell and restriction controls - Closed to arrival/departure functionality - Minimum/maximum stay requirements - Automatic sold-out management 💳 Payment Integration: - Maintained Redsys payment gateway integration - Seamless integration with existing Eagle Booking - No modifications to core Eagle Booking plugin 🛠️ Technical Implementation: - Custom database tables for advanced pricing - WordPress hooks and filters integration - AJAX-powered admin interface - Data migration from existing Eagle Booking - Professional calendar view for revenue management 📊 Admin Interface: - Booking.com-style management dashboard - Visual rate and availability calendar - Bulk operations for date ranges - Statistics and analytics dashboard - Modal dialogs for quick editing 🔧 Code Quality: - WordPress coding standards compliance - Secure database operations with prepared statements - Proper input validation and sanitization - Error handling and logging - Responsive admin interface 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class EB_Redsys_Payment_Gateway {
|
||||
|
||||
private $api;
|
||||
private $test_mode;
|
||||
private $merchant_code;
|
||||
private $terminal;
|
||||
private $encryption_key;
|
||||
|
||||
public function __construct() {
|
||||
$this->id = 'redsys';
|
||||
$this->title = __('Credit Card (Redsys)', 'eb-redsys-gateway');
|
||||
$this->description = __('Pay securely via credit card using Redsys.', 'eb-redsys-gateway');
|
||||
|
||||
// Load settings
|
||||
$this->test_mode = get_option('eb_redsys_test_mode', 'yes');
|
||||
$this->merchant_code = get_option('eb_redsys_merchant_code');
|
||||
$this->terminal = get_option('eb_redsys_terminal');
|
||||
$this->encryption_key = get_option('eb_redsys_encryption_key');
|
||||
|
||||
// Initialize API
|
||||
$this->api = new Redsys_API();
|
||||
|
||||
// Hooks
|
||||
add_action('eb_payment_gateway_redsys', array($this, 'process_payment'));
|
||||
add_action('eb_payment_gateway_redsys_callback', array($this, 'process_callback'));
|
||||
}
|
||||
|
||||
public function get_endpoint() {
|
||||
return ($this->test_mode === 'yes')
|
||||
? 'https://sis-t.redsys.es:25443/sis/realizarPago'
|
||||
: 'https://sis.redsys.es/sis/realizarPago';
|
||||
}
|
||||
|
||||
public function process_payment($booking_data) {
|
||||
$order_id = $booking_data['booking_id'];
|
||||
$amount = $booking_data['amount'];
|
||||
|
||||
// Prepare payment data
|
||||
$payment_data = array(
|
||||
'DS_MERCHANT_AMOUNT' => $amount * 100, // Amount in cents
|
||||
'DS_MERCHANT_ORDER' => $order_id,
|
||||
'DS_MERCHANT_MERCHANTCODE' => $this->merchant_code,
|
||||
'DS_MERCHANT_CURRENCY' => '978', // EUR
|
||||
'DS_MERCHANT_TRANSACTIONTYPE' => '0', // Authorization
|
||||
'DS_MERCHANT_TERMINAL' => $this->terminal,
|
||||
'DS_MERCHANT_MERCHANTURL' => add_query_arg('gateway', 'redsys', eb_get_callback_url()),
|
||||
'DS_MERCHANT_URLOK' => add_query_arg('result', 'success', eb_get_return_url($order_id)),
|
||||
'DS_MERCHANT_URLKO' => add_query_arg('result', 'failure', eb_get_return_url($order_id))
|
||||
);
|
||||
|
||||
// For future dated payments
|
||||
if (!empty($booking_data['check_in_date'])) {
|
||||
$payment_data['DS_MERCHANT_DATEFRECUENCY'] = '1'; // Daily frequency
|
||||
$payment_data['DS_MERCHANT_CHARGEEXPIRYDATE'] = date('d/m/Y', strtotime($booking_data['check_in_date']));
|
||||
$payment_data['DS_MERCHANT_TRANSACTIONTYPE'] = 'L'; // Deferred payment
|
||||
}
|
||||
|
||||
// Generate signature
|
||||
$signature = $this->api->createMerchantSignature($this->encryption_key, $payment_data);
|
||||
|
||||
// Build form
|
||||
$form = '<form method="post" id="redsys-payment-form" action="' . esc_url($this->get_endpoint()) . '">';
|
||||
$form .= '<input type="hidden" name="Ds_SignatureVersion" value="HMAC_SHA256_V1">';
|
||||
$form .= '<input type="hidden" name="Ds_MerchantParameters" value="' . $this->api->createMerchantParameters($payment_data) . '">';
|
||||
$form .= '<input type="hidden" name="Ds_Signature" value="' . $signature . '">';
|
||||
$form .= '</form>';
|
||||
$form .= '<script>document.getElementById("redsys-payment-form").submit();</script>';
|
||||
|
||||
echo $form;
|
||||
exit;
|
||||
}
|
||||
|
||||
public function process_callback() {
|
||||
$params = $_POST['Ds_MerchantParameters'];
|
||||
$signature = $_POST['Ds_Signature'];
|
||||
|
||||
// Verify signature
|
||||
if (!$this->api->check_signature($params, $signature, $this->encryption_key)) {
|
||||
wp_die('Invalid signature', 'Redsys Error', array('response' => 403));
|
||||
}
|
||||
|
||||
$response = $this->api->decode_parameters($params);
|
||||
|
||||
// Check response code
|
||||
if ($response['Ds_Response'] < 100) {
|
||||
// Payment successful
|
||||
$order_id = $response['Ds_Order'];
|
||||
$transaction_id = $response['Ds_AuthorisationCode'];
|
||||
|
||||
// Update booking status
|
||||
eb_update_booking_status($order_id, 'completed', $transaction_id);
|
||||
|
||||
echo "OK"; // Response to Redsys
|
||||
exit;
|
||||
}
|
||||
|
||||
// Payment failed
|
||||
$order_id = $response['Ds_Order'];
|
||||
eb_update_booking_status($order_id, 'failed');
|
||||
|
||||
echo "KO";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Core payment processing functions for Redsys gateway
|
||||
*/
|
||||
class EB_Redsys_Payment_Functions {
|
||||
|
||||
private static $instance = null;
|
||||
private $api;
|
||||
|
||||
public static function get_instance() {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function __construct() {
|
||||
// Initialize Redsys API
|
||||
require_once EB_REDSYS_PATH . 'includes/api/apiRedsysFinal.php';
|
||||
$this->api = new RedsysAPI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process standard payment
|
||||
*/
|
||||
public function process_standard_payment($booking_data) {
|
||||
try {
|
||||
$params = $this->prepare_payment_params($booking_data);
|
||||
return $this->send_payment_request($params);
|
||||
} catch (Exception $e) {
|
||||
$this->log_error($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process future dated payment
|
||||
*/
|
||||
public function process_future_payment($booking_data) {
|
||||
try {
|
||||
$params = $this->prepare_payment_params($booking_data);
|
||||
|
||||
// Add future payment parameters
|
||||
$params['DS_MERCHANT_TRANSACTIONTYPE'] = 'L'; // Deferred payment
|
||||
$params['DS_MERCHANT_DATEFRECUENCY'] = '1'; // Daily
|
||||
$params['DS_MERCHANT_CHARGEEXPIRYDATE'] = date('d/m/Y', strtotime($booking_data['check_in_date']));
|
||||
|
||||
return $this->send_payment_request($params);
|
||||
} catch (Exception $e) {
|
||||
$this->log_error($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process subscription payment
|
||||
*/
|
||||
public function process_subscription_payment($booking_data) {
|
||||
try {
|
||||
$params = $this->prepare_payment_params($booking_data);
|
||||
|
||||
// Add subscription parameters
|
||||
$params['DS_MERCHANT_TRANSACTIONTYPE'] = 'L';
|
||||
$params['DS_MERCHANT_COF_INI'] = 'S'; // Initial recurring payment
|
||||
$params['DS_MERCHANT_COF_TYPE'] = 'R'; // Recurring payment type
|
||||
|
||||
return $this->send_payment_request($params);
|
||||
} catch (Exception $e) {
|
||||
$this->log_error($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process subsequent subscription payment
|
||||
*/
|
||||
public function process_subscription_renewal($booking_data, $reference_id) {
|
||||
try {
|
||||
$params = $this->prepare_payment_params($booking_data);
|
||||
|
||||
// Add subsequent payment parameters
|
||||
$params['DS_MERCHANT_TRANSACTIONTYPE'] = '0';
|
||||
$params['DS_MERCHANT_IDENTIFIER'] = $reference_id;
|
||||
$params['DS_MERCHANT_DIRECTPAYMENT'] = 'true';
|
||||
|
||||
return $this->send_payment_request($params);
|
||||
} catch (Exception $e) {
|
||||
$this->log_error($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare common payment parameters
|
||||
*/
|
||||
private function prepare_payment_params($booking_data) {
|
||||
return array(
|
||||
'DS_MERCHANT_AMOUNT' => $this->format_amount($booking_data['amount']),
|
||||
'DS_MERCHANT_ORDER' => $this->generate_order_number($booking_data['booking_id']),
|
||||
'DS_MERCHANT_MERCHANTCODE' => get_option('eb_redsys_merchant_code'),
|
||||
'DS_MERCHANT_CURRENCY' => '978', // EUR
|
||||
'DS_MERCHANT_TERMINAL' => get_option('eb_redsys_terminal'),
|
||||
'DS_MERCHANT_MERCHANTURL' => $this->get_notification_url($booking_data['booking_id']),
|
||||
'DS_MERCHANT_URLOK' => $this->get_return_url($booking_data['booking_id'], 'success'),
|
||||
'DS_MERCHANT_URLKO' => $this->get_return_url($booking_data['booking_id'], 'failure')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send payment request to Redsys
|
||||
*/
|
||||
private function send_payment_request($params) {
|
||||
$version = "HMAC_SHA256_V1";
|
||||
$key = get_option('eb_redsys_encryption_key');
|
||||
|
||||
// Create merchant parameters
|
||||
$merchant_params = $this->api->createMerchantParameters($params);
|
||||
|
||||
// Create merchant signature
|
||||
$signature = $this->api->createMerchantSignature($key, $merchant_params);
|
||||
|
||||
return array(
|
||||
'endpoint' => $this->get_endpoint(),
|
||||
'params' => array(
|
||||
'Ds_SignatureVersion' => $version,
|
||||
'Ds_MerchantParameters' => $merchant_params,
|
||||
'Ds_Signature' => $signature
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Redsys notification
|
||||
*/
|
||||
public function process_notification($data) {
|
||||
try {
|
||||
$key = get_option('eb_redsys_encryption_key');
|
||||
$merchant_params = $data['Ds_MerchantParameters'];
|
||||
$signature = $data['Ds_Signature'];
|
||||
|
||||
// Validate signature
|
||||
if (!$this->api->check_signature($merchant_params, $signature, $key)) {
|
||||
throw new Exception('Invalid signature');
|
||||
}
|
||||
|
||||
// Decode parameters
|
||||
$decoded_params = $this->api->decodeMerchantParameters($merchant_params);
|
||||
|
||||
// Check response code
|
||||
$response_code = $decoded_params['Ds_Response'];
|
||||
if ($response_code < 100) {
|
||||
// Payment successful
|
||||
$order_id = $this->extract_booking_id($decoded_params['Ds_Order']);
|
||||
$transaction_id = $decoded_params['Ds_AuthorisationCode'];
|
||||
|
||||
// Store card reference for future payments if available
|
||||
if (isset($decoded_params['Ds_Merchant_Identifier'])) {
|
||||
$this->store_card_reference(
|
||||
$order_id,
|
||||
$decoded_params['Ds_Merchant_Identifier']
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'booking_id' => $order_id,
|
||||
'transaction_id' => $transaction_id
|
||||
);
|
||||
}
|
||||
|
||||
throw new Exception("Payment failed with code: $response_code");
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->log_error($e->getMessage());
|
||||
return array('success' => false, 'error' => $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store card reference for future payments
|
||||
*/
|
||||
private function store_card_reference($booking_id, $reference) {
|
||||
update_post_meta($booking_id, '_redsys_card_reference', $reference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stored card reference
|
||||
*/
|
||||
public function get_card_reference($booking_id) {
|
||||
return get_post_meta($booking_id, '_redsys_card_reference', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Redsys endpoint based on mode
|
||||
*/
|
||||
private function get_endpoint() {
|
||||
return (get_option('eb_redsys_test_mode') === 'yes')
|
||||
? 'https://sis-t.redsys.es:25443/sis/realizarPago'
|
||||
: 'https://sis.redsys.es/sis/realizarPago';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format amount for Redsys (in cents)
|
||||
*/
|
||||
private function format_amount($amount) {
|
||||
return number_format($amount * 100, 0, '', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique order number
|
||||
*/
|
||||
private function generate_order_number($booking_id) {
|
||||
return sprintf('%010d', $booking_id) . date('is');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notification URL
|
||||
*/
|
||||
private function get_notification_url($booking_id) {
|
||||
return add_query_arg(array(
|
||||
'eb-api' => 'redsys',
|
||||
'booking_id' => $booking_id
|
||||
), home_url('/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get return URL
|
||||
*/
|
||||
private function get_return_url($booking_id, $result) {
|
||||
return add_query_arg(array(
|
||||
'booking_id' => $booking_id,
|
||||
'result' => $result
|
||||
), eb_get_checkout_url());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract booking ID from order number
|
||||
*/
|
||||
private function extract_booking_id($order_number) {
|
||||
return intval(substr($order_number, 0, 10));
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error message
|
||||
*/
|
||||
private function log_error($message) {
|
||||
if (function_exists('eb_log')) {
|
||||
eb_log('Redsys Error: ' . $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize payment functions
|
||||
function eb_redsys_payment() {
|
||||
return EB_Redsys_Payment_Functions::get_instance();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
?>
|
||||
<div id="eb-tab-redsys" class="payment-tab">
|
||||
<div class="payment-tab-content">
|
||||
<h3><?php esc_html_e('Pay with Credit Card', 'eb-redsys-gateway') ?></h3>
|
||||
<p><?php esc_html_e('You will be redirected to Redsys secure payment page to complete your purchase.', 'eb-redsys-gateway') ?></p>
|
||||
|
||||
<?php if (get_option('eb_redsys_test_mode') === 'yes'): ?>
|
||||
<div class="eb-alert eb-alert-warning">
|
||||
<?php esc_html_e('Test mode is enabled. Use test card details for payments.', 'eb-redsys-gateway') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form id="eb-redsys-payment-form" method="post" action="<?php echo esc_url(eb_checkout_url()) ?>">
|
||||
<input type="hidden" name="eb_arrive" value="1">
|
||||
<input type="hidden" name="eagle_booking_payment_method" value="redsys">
|
||||
<?php wp_nonce_field('eb_payment_nonce', 'eb_payment_nonce'); ?>
|
||||
<button type="submit" class="btn"><?php esc_html_e('Proceed to Payment', 'eb-redsys-gateway') ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
?>
|
||||
<li>
|
||||
<a href="#eb-tab-redsys" class="payment-method" data-gateway="redsys">
|
||||
<?php esc_html_e('Credit Card (Redsys)', 'eb-redsys-gateway') ?>
|
||||
<img src="<?php echo EB_REDSYS_URL; ?>assets/images/cards.png" alt="Credit Cards">
|
||||
</a>
|
||||
</li>
|
||||
Reference in New Issue
Block a user