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:
Hotel Raxa Dev
2025-07-11 14:44:06 +02:00
parent 5b1e2453c7
commit 8bd12173b5
14 changed files with 4653 additions and 1 deletions

View File

@@ -0,0 +1,850 @@
<?php
/**
* Redsys Payment Requests Extension
*
* Adds payment request functionality to Redsys gateway
* Allows sending secure payment links to customers
*
* 🤖 Generated with Claude Code (https://claude.ai/code)
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
class EB_Redsys_Payment_Requests {
/**
* Constructor
*/
public function __construct() {
add_action('init', array($this, 'init'));
}
/**
* Initialize the class
*/
public function init() {
// Add admin menu
add_action('admin_menu', array($this, 'add_admin_menu'));
// AJAX handlers
add_action('wp_ajax_send_payment_request', array($this, 'send_payment_request'));
add_action('wp_ajax_create_payment_link', array($this, 'create_payment_link'));
// Handle payment link processing
add_action('init', array($this, 'handle_payment_link'));
// Add payment request metabox to bookings
add_action('add_meta_boxes', array($this, 'add_booking_metabox'));
// Enqueue scripts
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
// Create database table
register_activation_hook(EB_REDSYS_PLUGIN_FILE, array($this, 'create_database_table'));
// Schedule automatic payment requests
add_action('eb_payment_request_cron', array($this, 'process_scheduled_payment_requests'));
// Add cron schedule
if (!wp_next_scheduled('eb_payment_request_cron')) {
wp_schedule_event(time(), 'daily', 'eb_payment_request_cron');
}
}
/**
* Add admin menu
*/
public function add_admin_menu() {
add_submenu_page(
'eb_bookings',
__('Payment Requests', 'informatiq-eb-redsys'),
__('Payment Requests', 'informatiq-eb-redsys'),
'manage_options',
'eb-payment-requests',
array($this, 'admin_page')
);
}
/**
* Render admin page
*/
public function admin_page() {
?>
<div class="wrap">
<h1><?php _e('Payment Requests', 'informatiq-eb-redsys'); ?></h1>
<div class="eb-payment-requests-admin">
<!-- Send New Payment Request -->
<div class="postbox">
<h2 class="hndle"><?php _e('Send New Payment Request', 'informatiq-eb-redsys'); ?></h2>
<div class="inside">
<form id="payment-request-form">
<table class="form-table">
<tr>
<th scope="row">
<label for="booking_id"><?php _e('Booking ID', 'informatiq-eb-redsys'); ?></label>
</th>
<td>
<input type="number" id="booking_id" name="booking_id" class="regular-text" required>
<button type="button" id="load-booking" class="button"><?php _e('Load Booking', 'informatiq-eb-redsys'); ?></button>
</td>
</tr>
<tr>
<th scope="row">
<label for="customer_email"><?php _e('Customer Email', 'informatiq-eb-redsys'); ?></label>
</th>
<td>
<input type="email" id="customer_email" name="customer_email" class="regular-text" required>
</td>
</tr>
<tr>
<th scope="row">
<label for="amount"><?php _e('Amount (€)', 'informatiq-eb-redsys'); ?></label>
</th>
<td>
<input type="number" id="amount" name="amount" step="0.01" class="regular-text" required>
<p class="description">
<?php _e('Amount to request from customer', 'informatiq-eb-redsys'); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="description"><?php _e('Description', 'informatiq-eb-redsys'); ?></label>
</th>
<td>
<textarea id="description" name="description" rows="3" class="large-text"></textarea>
<p class="description">
<?php _e('Optional description for the payment request', 'informatiq-eb-redsys'); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="expires_in"><?php _e('Expires In', 'informatiq-eb-redsys'); ?></label>
</th>
<td>
<select id="expires_in" name="expires_in">
<option value="24"><?php _e('24 hours', 'informatiq-eb-redsys'); ?></option>
<option value="72" selected><?php _e('3 days', 'informatiq-eb-redsys'); ?></option>
<option value="168"><?php _e('1 week', 'informatiq-eb-redsys'); ?></option>
<option value="336"><?php _e('2 weeks', 'informatiq-eb-redsys'); ?></option>
</select>
</td>
</tr>
</table>
<p class="submit">
<button type="submit" class="button-primary">
<?php _e('Send Payment Request', 'informatiq-eb-redsys'); ?>
</button>
</p>
</form>
</div>
</div>
<!-- Recent Payment Requests -->
<div class="postbox">
<h2 class="hndle"><?php _e('Recent Payment Requests', 'informatiq-eb-redsys'); ?></h2>
<div class="inside">
<?php $this->display_payment_requests_table(); ?>
</div>
</div>
<!-- Automatic Payment Requests -->
<div class="postbox">
<h2 class="hndle"><?php _e('Automatic Payment Requests', 'informatiq-eb-redsys'); ?></h2>
<div class="inside">
<form id="auto-payment-settings">
<table class="form-table">
<tr>
<th scope="row">
<?php _e('Enable Automatic Requests', 'informatiq-eb-redsys'); ?>
</th>
<td>
<label>
<input type="checkbox" id="auto_requests_enabled" name="auto_requests_enabled"
value="1" <?php checked(get_option('eb_redsys_auto_requests_enabled'), '1'); ?>>
<?php _e('Send payment requests automatically 14 days before check-in', 'informatiq-eb-redsys'); ?>
</label>
</td>
</tr>
<tr>
<th scope="row">
<label for="auto_percentage"><?php _e('Payment Percentage', 'informatiq-eb-redsys'); ?></label>
</th>
<td>
<input type="number" id="auto_percentage" name="auto_percentage"
value="<?php echo esc_attr(get_option('eb_redsys_auto_percentage', '50')); ?>"
min="1" max="100" class="small-text">
<span>%</span>
<p class="description">
<?php _e('Percentage of total booking amount to request', 'informatiq-eb-redsys'); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="days_before"><?php _e('Days Before Check-in', 'informatiq-eb-redsys'); ?></label>
</th>
<td>
<input type="number" id="days_before" name="days_before"
value="<?php echo esc_attr(get_option('eb_redsys_days_before', '14')); ?>"
min="1" max="365" class="small-text">
<p class="description">
<?php _e('Number of days before check-in to send the payment request', 'informatiq-eb-redsys'); ?>
</p>
</td>
</tr>
</table>
<p class="submit">
<button type="submit" class="button-primary">
<?php _e('Save Settings', 'informatiq-eb-redsys'); ?>
</button>
</p>
</form>
</div>
</div>
</div>
</div>
<style>
.eb-payment-requests-admin .postbox {
margin-bottom: 20px;
}
.payment-request-status {
padding: 3px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
}
.status-pending {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.status-sent {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-paid {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.status-expired {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.payment-link {
font-family: monospace;
background: #f8f9fa;
padding: 5px;
border-radius: 3px;
word-break: break-all;
}
</style>
<?php
}
/**
* Display payment requests table
*/
private function display_payment_requests_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_requests';
$requests = $wpdb->get_results(
"SELECT * FROM {$table_name} ORDER BY created_at DESC LIMIT 20"
);
if (empty($requests)) {
echo '<p>' . __('No payment requests found.', 'informatiq-eb-redsys') . '</p>';
return;
}
?>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th><?php _e('ID', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Booking', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Customer', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Amount', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Status', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Created', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Expires', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Actions', 'informatiq-eb-redsys'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($requests as $request): ?>
<tr>
<td><?php echo $request->id; ?></td>
<td>
<a href="<?php echo admin_url('post.php?post=' . $request->booking_id . '&action=edit'); ?>">
#<?php echo $request->booking_id; ?>
</a>
</td>
<td><?php echo esc_html($request->customer_email); ?></td>
<td>€<?php echo number_format($request->amount, 2); ?></td>
<td>
<span class="payment-request-status status-<?php echo esc_attr($request->status); ?>">
<?php echo ucfirst($request->status); ?>
</span>
</td>
<td><?php echo date_i18n(get_option('date_format'), strtotime($request->created_at)); ?></td>
<td><?php echo date_i18n(get_option('date_format'), strtotime($request->expires_at)); ?></td>
<td>
<?php if ($request->status === 'pending'): ?>
<button class="button button-small resend-request" data-request-id="<?php echo $request->id; ?>">
<?php _e('Resend', 'informatiq-eb-redsys'); ?>
</button>
<?php endif; ?>
<?php if ($request->status !== 'paid'): ?>
<button class="button button-small copy-link" data-link="<?php echo esc_attr($request->payment_link); ?>">
<?php _e('Copy Link', 'informatiq-eb-redsys'); ?>
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php
}
/**
* Send payment request (AJAX handler)
*/
public function send_payment_request() {
check_ajax_referer('payment_request_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die(__('Insufficient permissions', 'informatiq-eb-redsys'));
}
$booking_id = intval($_POST['booking_id']);
$customer_email = sanitize_email($_POST['customer_email']);
$amount = floatval($_POST['amount']);
$description = sanitize_textarea_field($_POST['description']);
$expires_in = intval($_POST['expires_in']);
// Validate inputs
if (!$booking_id || !$customer_email || !$amount) {
wp_send_json_error(__('Missing required fields', 'informatiq-eb-redsys'));
}
// Create payment request
$request_id = $this->create_payment_request(
$booking_id,
$customer_email,
$amount,
$description,
$expires_in
);
if ($request_id) {
// Send email
$sent = $this->send_payment_request_email($request_id);
if ($sent) {
wp_send_json_success(__('Payment request sent successfully', 'informatiq-eb-redsys'));
} else {
wp_send_json_error(__('Payment request created but email failed to send', 'informatiq-eb-redsys'));
}
} else {
wp_send_json_error(__('Failed to create payment request', 'informatiq-eb-redsys'));
}
}
/**
* Create payment request in database
*/
private function create_payment_request($booking_id, $customer_email, $amount, $description, $expires_in) {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_requests';
// Generate unique token
$token = wp_generate_password(32, false);
// Create payment link
$payment_link = home_url('/?eb_payment_request=' . $token);
// Calculate expiry date
$expires_at = date('Y-m-d H:i:s', strtotime("+{$expires_in} hours"));
$result = $wpdb->insert(
$table_name,
array(
'booking_id' => $booking_id,
'customer_email' => $customer_email,
'amount' => $amount,
'description' => $description,
'token' => $token,
'payment_link' => $payment_link,
'status' => 'pending',
'created_at' => current_time('mysql'),
'expires_at' => $expires_at
),
array('%d', '%s', '%f', '%s', '%s', '%s', '%s', '%s', '%s')
);
return $result ? $wpdb->insert_id : false;
}
/**
* Send payment request email
*/
private function send_payment_request_email($request_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_requests';
$request = $wpdb->get_row(
$wpdb->prepare("SELECT * FROM {$table_name} WHERE id = %d", $request_id)
);
if (!$request) {
return false;
}
// Get booking details
$booking = get_post($request->booking_id);
$subject = sprintf(
__('Payment Request for Booking #%d - Hotel Raxa', 'informatiq-eb-redsys'),
$request->booking_id
);
$message = $this->get_payment_request_email_template($request, $booking);
$headers = array(
'Content-Type: text/html; charset=UTF-8',
'From: Hotel Raxa <noreply@hotelraxa.com>'
);
$sent = wp_mail($request->customer_email, $subject, $message, $headers);
if ($sent) {
// Update status
$wpdb->update(
$table_name,
array('status' => 'sent', 'sent_at' => current_time('mysql')),
array('id' => $request_id),
array('%s', '%s'),
array('%d')
);
}
return $sent;
}
/**
* Get payment request email template
*/
private function get_payment_request_email_template($request, $booking) {
$template = '
<html>
<head>
<meta charset="UTF-8">
<title>Payment Request - Hotel Raxa</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: #2c5aa0; color: white; padding: 20px; text-align: center;">
<h1 style="margin: 0;">Hotel Raxa</h1>
<p style="margin: 10px 0 0;">Payment Request</p>
</div>
<div style="padding: 30px; border: 1px solid #ddd;">
<h2>Dear Guest,</h2>
<p>We hope you are looking forward to your stay at Hotel Raxa. This is a payment request for your upcoming booking.</p>
<div style="background: #f8f9fa; padding: 20px; margin: 20px 0; border-radius: 5px;">
<h3 style="margin-top: 0;">Booking Details</h3>
<p><strong>Booking ID:</strong> #' . $request->booking_id . '</p>
<p><strong>Amount Requested:</strong> €' . number_format($request->amount, 2) . '</p>
' . ($request->description ? '<p><strong>Description:</strong> ' . esc_html($request->description) . '</p>' : '') . '
<p><strong>Payment Due:</strong> ' . date_i18n(get_option('date_format'), strtotime($request->expires_at)) . '</p>
</div>
<div style="text-align: center; margin: 30px 0;">
<a href="' . $request->payment_link . '"
style="background: #007cba; color: white; padding: 15px 30px; text-decoration: none; border-radius: 5px; display: inline-block; font-weight: bold;">
Pay Now - €' . number_format($request->amount, 2) . '
</a>
</div>
<div style="background: #fff3cd; padding: 15px; margin: 20px 0; border-radius: 5px; border-left: 4px solid #ffc107;">
<p style="margin: 0;"><strong>Important:</strong> This payment link will expire on ' . date_i18n(get_option('datetime_format'), strtotime($request->expires_at)) . '. Please complete your payment before this date to secure your reservation.</p>
</div>
<p>If you have any questions about this payment request or your booking, please contact us immediately.</p>
<p>Thank you for choosing Hotel Raxa!</p>
</div>
<div style="background: #f8f9fa; padding: 15px; text-align: center; font-size: 12px; color: #666;">
<p>Hotel Raxa | For questions, please contact: info@hotelraxa.com</p>
<p>This email was sent automatically. Please do not reply to this email.</p>
</div>
</div>
</body>
</html>';
return $template;
}
/**
* Handle payment link processing
*/
public function handle_payment_link() {
if (!isset($_GET['eb_payment_request'])) {
return;
}
$token = sanitize_text_field($_GET['eb_payment_request']);
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_requests';
$request = $wpdb->get_row(
$wpdb->prepare("SELECT * FROM {$table_name} WHERE token = %s", $token)
);
if (!$request) {
wp_die(__('Invalid payment request', 'informatiq-eb-redsys'));
}
// Check if expired
if (strtotime($request->expires_at) < time()) {
wp_die(__('This payment request has expired', 'informatiq-eb-redsys'));
}
// Check if already paid
if ($request->status === 'paid') {
wp_die(__('This payment request has already been completed', 'informatiq-eb-redsys'));
}
// Process payment with Redsys
$this->process_payment_request($request);
}
/**
* Process payment request
*/
private function process_payment_request($request) {
// Get Redsys settings
$merchant_code = get_option('eb_redsys_merchant_code');
$terminal = get_option('eb_redsys_terminal');
$encryption_key = get_option('eb_redsys_encryption_key');
$test_mode = get_option('eb_redsys_test_mode') === 'yes';
// Include Redsys API
require_once dirname(__FILE__) . '/../api/class-redsys-api.php';
$redsys = new Redsys_API();
// Generate order number
$order_number = 'PR' . str_pad($request->id, 8, '0', STR_PAD_LEFT);
// Set payment parameters
$redsys->setParameter('DS_MERCHANT_AMOUNT', $request->amount * 100); // Amount in cents
$redsys->setParameter('DS_MERCHANT_ORDER', $order_number);
$redsys->setParameter('DS_MERCHANT_MERCHANTCODE', $merchant_code);
$redsys->setParameter('DS_MERCHANT_CURRENCY', '978'); // EUR
$redsys->setParameter('DS_MERCHANT_TRANSACTIONTYPE', '0'); // Authorization
$redsys->setParameter('DS_MERCHANT_TERMINAL', $terminal);
$redsys->setParameter('DS_MERCHANT_MERCHANTURL', home_url('/?eb-redsys-callback=payment-request'));
$redsys->setParameter('DS_MERCHANT_URLOK', home_url('/?eb_payment_success=' . $request->token));
$redsys->setParameter('DS_MERCHANT_URLKO', home_url('/?eb_payment_error=' . $request->token));
// Generate signature
$signature = $redsys->createMerchantSignature($encryption_key);
// Get Redsys URL
$redsys_url = $test_mode ?
'https://sis-t.redsys.es:25443/sis/realizarPago' :
'https://sis.redsys.es/sis/realizarPago';
// Display payment form
$this->display_payment_form($redsys_url, $redsys, $signature, $request);
}
/**
* Display payment form
*/
private function display_payment_form($redsys_url, $redsys, $signature, $request) {
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><?php _e('Payment Request - Hotel Raxa', 'informatiq-eb-redsys'); ?></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.header { text-align: center; background: #2c5aa0; color: white; padding: 20px; margin: -30px -30px 30px; border-radius: 10px 10px 0 0; }
.amount { font-size: 2em; color: #2c5aa0; text-align: center; margin: 20px 0; }
.details { background: #f8f9fa; padding: 20px; margin: 20px 0; border-radius: 5px; }
.pay-button { background: #007cba; color: white; padding: 15px 30px; border: none; border-radius: 5px; font-size: 16px; cursor: pointer; width: 100%; }
.pay-button:hover { background: #005a87; }
.security-info { font-size: 12px; color: #666; text-align: center; margin-top: 20px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Hotel Raxa</h1>
<p>Secure Payment Request</p>
</div>
<div class="details">
<h3><?php _e('Payment Details', 'informatiq-eb-redsys'); ?></h3>
<p><strong><?php _e('Booking ID:', 'informatiq-eb-redsys'); ?></strong> #<?php echo $request->booking_id; ?></p>
<p><strong><?php _e('Description:', 'informatiq-eb-redsys'); ?></strong> <?php echo esc_html($request->description ?: __('Hotel booking payment', 'informatiq-eb-redsys')); ?></p>
<p><strong><?php _e('Payment expires:', 'informatiq-eb-redsys'); ?></strong> <?php echo date_i18n(get_option('datetime_format'), strtotime($request->expires_at)); ?></p>
</div>
<div class="amount">
€<?php echo number_format($request->amount, 2); ?>
</div>
<form action="<?php echo $redsys_url; ?>" method="post" id="payment-form">
<input type="hidden" name="Ds_SignatureVersion" value="HMAC_SHA256_V1">
<input type="hidden" name="Ds_MerchantParameters" value="<?php echo $redsys->createMerchantParameters(); ?>">
<input type="hidden" name="Ds_Signature" value="<?php echo $signature; ?>">
<button type="submit" class="pay-button">
<?php printf(__('Pay €%.2f Securely', 'informatiq-eb-redsys'), $request->amount); ?>
</button>
</form>
<div class="security-info">
<p><?php _e('This payment is processed securely through Redsys payment gateway.', 'informatiq-eb-redsys'); ?></p>
<p><?php _e('Your payment information is encrypted and secure.', 'informatiq-eb-redsys'); ?></p>
</div>
</div>
<script>
// Auto-submit form after 2 seconds to improve UX
setTimeout(function() {
if (confirm('<?php _e("Redirect to secure payment page?", "informatiq-eb-redsys"); ?>')) {
document.getElementById('payment-form').submit();
}
}, 2000);
</script>
</body>
</html>
<?php
exit;
}
/**
* Create database table for payment requests
*/
public function create_database_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_requests';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$table_name} (
id int(11) NOT NULL AUTO_INCREMENT,
booking_id int(11) NOT NULL,
customer_email varchar(255) NOT NULL,
amount decimal(10,2) NOT NULL,
description text,
token varchar(32) NOT NULL,
payment_link text NOT NULL,
status varchar(20) DEFAULT 'pending',
created_at datetime NOT NULL,
sent_at datetime,
expires_at datetime NOT NULL,
paid_at datetime,
redsys_order varchar(20),
redsys_auth_code varchar(20),
PRIMARY KEY (id),
UNIQUE KEY token (token),
KEY booking_id (booking_id),
KEY status (status),
KEY expires_at (expires_at)
) {$charset_collate};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
}
/**
* Process scheduled payment requests
*/
public function process_scheduled_payment_requests() {
if (get_option('eb_redsys_auto_requests_enabled') !== '1') {
return;
}
$days_before = intval(get_option('eb_redsys_days_before', 14));
$percentage = intval(get_option('eb_redsys_auto_percentage', 50));
// Get bookings that need payment requests
global $wpdb;
$target_date = date('Y-m-d', strtotime("+{$days_before} days"));
$bookings = $wpdb->get_results($wpdb->prepare("
SELECT p.ID, p.post_title,
pm1.meta_value as checkin_date,
pm2.meta_value as customer_email,
pm3.meta_value as total_price,
pm4.meta_value as payment_status
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm1 ON p.ID = pm1.post_id AND pm1.meta_key = '_eb_checkin_date'
LEFT JOIN {$wpdb->postmeta} pm2 ON p.ID = pm2.post_id AND pm2.meta_key = '_eb_customer_email'
LEFT JOIN {$wpdb->postmeta} pm3 ON p.ID = pm3.post_id AND pm3.meta_key = '_eb_total_price'
LEFT JOIN {$wpdb->postmeta} pm4 ON p.ID = pm4.post_id AND pm4.meta_key = '_eb_payment_status'
WHERE p.post_type = 'eagle_booking'
AND p.post_status = 'publish'
AND pm1.meta_value = %s
AND pm4.meta_value = 'pending'
", $target_date));
foreach ($bookings as $booking) {
// Check if payment request already sent
$existing_request = $wpdb->get_var($wpdb->prepare("
SELECT id FROM {$wpdb->prefix}eb_payment_requests
WHERE booking_id = %d AND status != 'failed'
", $booking->ID));
if ($existing_request) {
continue; // Skip if already sent
}
// Calculate amount
$amount = ($booking->total_price * $percentage) / 100;
// Create payment request
$description = sprintf(
__('Payment for your stay at Hotel Raxa (Booking #%d) - %d%% of total amount', 'informatiq-eb-redsys'),
$booking->ID,
$percentage
);
$request_id = $this->create_payment_request(
$booking->ID,
$booking->customer_email,
$amount,
$description,
72 // 3 days expiry
);
if ($request_id) {
$this->send_payment_request_email($request_id);
}
}
}
/**
* Add payment request metabox to booking edit page
*/
public function add_booking_metabox() {
add_meta_box(
'eb-payment-requests',
__('Payment Requests', 'informatiq-eb-redsys'),
array($this, 'booking_metabox_callback'),
'eagle_booking',
'side',
'default'
);
}
/**
* Booking metabox callback
*/
public function booking_metabox_callback($post) {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_requests';
$requests = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$table_name} WHERE booking_id = %d ORDER BY created_at DESC",
$post->ID
));
?>
<div class="eb-payment-requests-metabox">
<?php if (empty($requests)): ?>
<p><?php _e('No payment requests sent for this booking.', 'informatiq-eb-redsys'); ?></p>
<?php else: ?>
<?php foreach ($requests as $request): ?>
<div class="payment-request-item" style="border: 1px solid #ddd; padding: 10px; margin: 10px 0; border-radius: 4px;">
<p><strong><?php _e('Amount:', 'informatiq-eb-redsys'); ?></strong> €<?php echo number_format($request->amount, 2); ?></p>
<p><strong><?php _e('Status:', 'informatiq-eb-redsys'); ?></strong>
<span class="payment-request-status status-<?php echo esc_attr($request->status); ?>">
<?php echo ucfirst($request->status); ?>
</span>
</p>
<p><strong><?php _e('Created:', 'informatiq-eb-redsys'); ?></strong> <?php echo date_i18n(get_option('datetime_format'), strtotime($request->created_at)); ?></p>
<p><strong><?php _e('Expires:', 'informatiq-eb-redsys'); ?></strong> <?php echo date_i18n(get_option('datetime_format'), strtotime($request->expires_at)); ?></p>
<?php if ($request->status !== 'paid'): ?>
<button class="button button-small copy-payment-link" data-link="<?php echo esc_attr($request->payment_link); ?>">
<?php _e('Copy Payment Link', 'informatiq-eb-redsys'); ?>
</button>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
<hr>
<button type="button" class="button" id="send-new-payment-request" data-booking-id="<?php echo $post->ID; ?>">
<?php _e('Send New Payment Request', 'informatiq-eb-redsys'); ?>
</button>
</div>
<?php
}
/**
* Enqueue admin scripts
*/
public function enqueue_admin_scripts($hook) {
if (strpos($hook, 'eb-payment-requests') !== false || $hook === 'post.php') {
wp_enqueue_script(
'eb-payment-requests-admin',
plugin_dir_url(__FILE__) . '../assets/js/payment-requests-admin.js',
array('jquery'),
'1.0.0',
true
);
wp_localize_script('eb-payment-requests-admin', 'ebPaymentRequests', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('payment_request_nonce'),
'strings' => array(
'confirmSend' => __('Are you sure you want to send this payment request?', 'informatiq-eb-redsys'),
'linkCopied' => __('Payment link copied to clipboard!', 'informatiq-eb-redsys'),
'copyFailed' => __('Failed to copy link. Please copy manually.', 'informatiq-eb-redsys')
)
));
}
}
}
// Initialize the class
new EB_Redsys_Payment_Requests();

View File

@@ -0,0 +1,820 @@
<?php
/**
* Redsys Preauthorization Extension
*
* Adds preauthorization functionality to Redsys gateway
* Allows holding funds without immediate capture
*
* 🤖 Generated with Claude Code (https://claude.ai/code)
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
class EB_Redsys_Preauthorization {
/**
* Constructor
*/
public function __construct() {
add_action('init', array($this, 'init'));
}
/**
* Initialize the class
*/
public function init() {
// Add preauthorization option to payment settings
add_filter('eb_redsys_payment_settings', array($this, 'add_preauth_settings'));
// Modify payment processing for preauthorization
add_filter('eb_redsys_payment_parameters', array($this, 'modify_payment_parameters'), 10, 2);
// Add preauthorization management to admin
add_action('admin_menu', array($this, 'add_admin_menu'));
// Add preauthorization metabox to bookings
add_action('add_meta_boxes', array($this, 'add_preauth_metabox'));
// AJAX handlers for preauthorization actions
add_action('wp_ajax_capture_preauth', array($this, 'capture_preauthorization'));
add_action('wp_ajax_cancel_preauth', array($this, 'cancel_preauthorization'));
// Handle preauthorization notifications
add_action('eb_redsys_payment_notification', array($this, 'handle_preauth_notification'), 10, 2);
// Create database table for preauthorizations
register_activation_hook(EB_REDSYS_PLUGIN_FILE, array($this, 'create_preauth_table'));
// Enqueue admin scripts
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
}
/**
* Add preauthorization settings to payment gateway
*/
public function add_preauth_settings($settings) {
$preauth_settings = array(
'preauth_enabled' => array(
'title' => __('Enable Preauthorization', 'informatiq-eb-redsys'),
'type' => 'checkbox',
'description' => __('Enable preauthorization mode for reservations (hold funds without immediate capture)', 'informatiq-eb-redsys'),
'default' => 'no'
),
'preauth_amount_type' => array(
'title' => __('Preauthorization Amount', 'informatiq-eb-redsys'),
'type' => 'select',
'description' => __('Choose how much to preauthorize', 'informatiq-eb-redsys'),
'default' => 'full',
'options' => array(
'full' => __('Full booking amount', 'informatiq-eb-redsys'),
'deposit' => __('Deposit amount only', 'informatiq-eb-redsys'),
'fixed' => __('Fixed amount', 'informatiq-eb-redsys')
)
),
'preauth_fixed_amount' => array(
'title' => __('Fixed Preauthorization Amount (€)', 'informatiq-eb-redsys'),
'type' => 'number',
'description' => __('Fixed amount to preauthorize (only used if "Fixed amount" is selected above)', 'informatiq-eb-redsys'),
'default' => '50',
'custom_attributes' => array(
'step' => '0.01',
'min' => '0.01'
)
),
'preauth_auto_capture' => array(
'title' => __('Auto-capture Preauthorizations', 'informatiq-eb-redsys'),
'type' => 'checkbox',
'description' => __('Automatically capture preauthorizations 24 hours before check-in', 'informatiq-eb-redsys'),
'default' => 'yes'
),
'preauth_expiry_days' => array(
'title' => __('Preauthorization Expiry (Days)', 'informatiq-eb-redsys'),
'type' => 'number',
'description' => __('Number of days before preauthorization expires (max 7 days for most cards)', 'informatiq-eb-redsys'),
'default' => '7',
'custom_attributes' => array(
'min' => '1',
'max' => '7'
)
)
);
return array_merge($settings, $preauth_settings);
}
/**
* Modify payment parameters for preauthorization
*/
public function modify_payment_parameters($parameters, $booking_data) {
if (get_option('eb_redsys_preauth_enabled') !== 'yes') {
return $parameters;
}
// Change transaction type to preauthorization
$parameters['DS_MERCHANT_TRANSACTIONTYPE'] = '1'; // Preauthorization
// Calculate preauthorization amount
$preauth_amount = $this->calculate_preauth_amount($booking_data);
$parameters['DS_MERCHANT_AMOUNT'] = $preauth_amount * 100; // Convert to cents
// Add preauthorization identifier
$parameters['DS_MERCHANT_PRODUCTDESCRIPTION'] = 'Preauth: ' . $parameters['DS_MERCHANT_PRODUCTDESCRIPTION'];
return $parameters;
}
/**
* Calculate preauthorization amount
*/
private function calculate_preauth_amount($booking_data) {
$amount_type = get_option('eb_redsys_preauth_amount_type', 'full');
$total_amount = $booking_data['total_price'];
switch ($amount_type) {
case 'full':
return $total_amount;
case 'deposit':
$deposit_percentage = get_option('eb_deposit_percentage', 30);
return ($total_amount * $deposit_percentage) / 100;
case 'fixed':
return floatval(get_option('eb_redsys_preauth_fixed_amount', 50));
default:
return $total_amount;
}
}
/**
* Add admin menu
*/
public function add_admin_menu() {
add_submenu_page(
'eb_bookings',
__('Preauthorizations', 'informatiq-eb-redsys'),
__('Preauthorizations', 'informatiq-eb-redsys'),
'manage_options',
'eb-preauthorizations',
array($this, 'admin_page')
);
}
/**
* Render admin page
*/
public function admin_page() {
?>
<div class="wrap">
<h1><?php _e('Preauthorizations Management', 'informatiq-eb-redsys'); ?></h1>
<div class="eb-preauth-admin">
<!-- Preauthorization Statistics -->
<div class="postbox">
<h2 class="hndle"><?php _e('Preauthorization Statistics', 'informatiq-eb-redsys'); ?></h2>
<div class="inside">
<?php $this->display_preauth_statistics(); ?>
</div>
</div>
<!-- Active Preauthorizations -->
<div class="postbox">
<h2 class="hndle"><?php _e('Active Preauthorizations', 'informatiq-eb-redsys'); ?></h2>
<div class="inside">
<?php $this->display_active_preauthorizations(); ?>
</div>
</div>
<!-- Preauthorization History -->
<div class="postbox">
<h2 class="hndle"><?php _e('Preauthorization History', 'informatiq-eb-redsys'); ?></h2>
<div class="inside">
<?php $this->display_preauth_history(); ?>
</div>
</div>
</div>
</div>
<style>
.eb-preauth-admin .postbox {
margin-bottom: 20px;
}
.preauth-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin: 20px 0;
}
.preauth-stat-box {
background: #f8f9fa;
padding: 20px;
text-align: center;
border-radius: 5px;
border: 1px solid #e1e5e9;
}
.preauth-stat-box h3 {
margin: 0 0 10px;
font-size: 2em;
color: #2271b1;
}
.preauth-status {
padding: 3px 8px;
border-radius: 3px;
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
}
.status-active {
background: #d4edda;
color: #155724;
}
.status-captured {
background: #d1ecf1;
color: #0c5460;
}
.status-cancelled {
background: #f8d7da;
color: #721c24;
}
.status-expired {
background: #e2e3e5;
color: #383d41;
}
.preauth-actions {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.preauth-actions .button {
font-size: 11px;
padding: 2px 8px;
height: auto;
}
</style>
<?php
}
/**
* Display preauthorization statistics
*/
private function display_preauth_statistics() {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_preauthorizations';
$stats = $wpdb->get_row("
SELECT
COUNT(*) as total_preauths,
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active_preauths,
SUM(CASE WHEN status = 'captured' THEN 1 ELSE 0 END) as captured_preauths,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_preauths,
SUM(CASE WHEN status = 'active' THEN amount ELSE 0 END) as active_amount,
SUM(CASE WHEN status = 'captured' THEN amount ELSE 0 END) as captured_amount
FROM {$table_name}
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
");
?>
<div class="preauth-stats">
<div class="preauth-stat-box">
<h3><?php echo $stats->total_preauths ?: 0; ?></h3>
<p><?php _e('Total Preauthorizations (30 days)', 'informatiq-eb-redsys'); ?></p>
</div>
<div class="preauth-stat-box">
<h3><?php echo $stats->active_preauths ?: 0; ?></h3>
<p><?php _e('Active Preauthorizations', 'informatiq-eb-redsys'); ?></p>
</div>
<div class="preauth-stat-box">
<h3>€<?php echo number_format($stats->active_amount ?: 0, 2); ?></h3>
<p><?php _e('Active Amount', 'informatiq-eb-redsys'); ?></p>
</div>
<div class="preauth-stat-box">
<h3>€<?php echo number_format($stats->captured_amount ?: 0, 2); ?></h3>
<p><?php _e('Captured Amount (30 days)', 'informatiq-eb-redsys'); ?></p>
</div>
</div>
<?php
}
/**
* Display active preauthorizations
*/
private function display_active_preauthorizations() {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_preauthorizations';
$preauths = $wpdb->get_results("
SELECT * FROM {$table_name}
WHERE status = 'active'
ORDER BY created_at DESC
");
if (empty($preauths)) {
echo '<p>' . __('No active preauthorizations found.', 'informatiq-eb-redsys') . '</p>';
return;
}
?>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th><?php _e('Booking ID', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Customer', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Amount', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Auth Code', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Expires', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Actions', 'informatiq-eb-redsys'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($preauths as $preauth): ?>
<tr>
<td>
<a href="<?php echo admin_url('post.php?post=' . $preauth->booking_id . '&action=edit'); ?>">
#<?php echo $preauth->booking_id; ?>
</a>
</td>
<td><?php echo esc_html($preauth->customer_email); ?></td>
<td>€<?php echo number_format($preauth->amount, 2); ?></td>
<td><code><?php echo esc_html($preauth->auth_code); ?></code></td>
<td>
<?php
$expires = strtotime($preauth->expires_at);
$now = time();
$days_left = ceil(($expires - $now) / (24 * 60 * 60));
if ($days_left <= 0) {
echo '<span style="color: #d63638;">' . __('Expired', 'informatiq-eb-redsys') . '</span>';
} else {
echo date_i18n(get_option('date_format'), $expires);
echo '<br><small>' . sprintf(_n('%d day left', '%d days left', $days_left, 'informatiq-eb-redsys'), $days_left) . '</small>';
}
?>
</td>
<td>
<div class="preauth-actions">
<button class="button button-primary button-small capture-preauth"
data-preauth-id="<?php echo $preauth->id; ?>"
data-booking-id="<?php echo $preauth->booking_id; ?>"
data-amount="<?php echo $preauth->amount; ?>">
<?php _e('Capture', 'informatiq-eb-redsys'); ?>
</button>
<button class="button button-secondary button-small cancel-preauth"
data-preauth-id="<?php echo $preauth->id; ?>"
data-booking-id="<?php echo $preauth->booking_id; ?>">
<?php _e('Cancel', 'informatiq-eb-redsys'); ?>
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php
}
/**
* Display preauthorization history
*/
private function display_preauth_history() {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_preauthorizations';
$preauths = $wpdb->get_results("
SELECT * FROM {$table_name}
WHERE status != 'active'
ORDER BY created_at DESC
LIMIT 20
");
if (empty($preauths)) {
echo '<p>' . __('No preauthorization history found.', 'informatiq-eb-redsys') . '</p>';
return;
}
?>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th><?php _e('Booking ID', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Customer', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Amount', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Status', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Created', 'informatiq-eb-redsys'); ?></th>
<th><?php _e('Updated', 'informatiq-eb-redsys'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($preauths as $preauth): ?>
<tr>
<td>
<a href="<?php echo admin_url('post.php?post=' . $preauth->booking_id . '&action=edit'); ?>">
#<?php echo $preauth->booking_id; ?>
</a>
</td>
<td><?php echo esc_html($preauth->customer_email); ?></td>
<td>€<?php echo number_format($preauth->amount, 2); ?></td>
<td>
<span class="preauth-status status-<?php echo esc_attr($preauth->status); ?>">
<?php echo ucfirst($preauth->status); ?>
</span>
</td>
<td><?php echo date_i18n(get_option('datetime_format'), strtotime($preauth->created_at)); ?></td>
<td><?php echo $preauth->updated_at ? date_i18n(get_option('datetime_format'), strtotime($preauth->updated_at)) : '-'; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php
}
/**
* Capture preauthorization (AJAX handler)
*/
public function capture_preauthorization() {
check_ajax_referer('preauth_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die(__('Insufficient permissions', 'informatiq-eb-redsys'));
}
$preauth_id = intval($_POST['preauth_id']);
$booking_id = intval($_POST['booking_id']);
global $wpdb;
$table_name = $wpdb->prefix . 'eb_preauthorizations';
$preauth = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$table_name} WHERE id = %d AND status = 'active'",
$preauth_id
));
if (!$preauth) {
wp_send_json_error(__('Preauthorization not found or not active', 'informatiq-eb-redsys'));
}
// Process capture with Redsys
$result = $this->process_capture($preauth);
if ($result['success']) {
wp_send_json_success(__('Preauthorization captured successfully', 'informatiq-eb-redsys'));
} else {
wp_send_json_error($result['error']);
}
}
/**
* Cancel preauthorization (AJAX handler)
*/
public function cancel_preauthorization() {
check_ajax_referer('preauth_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die(__('Insufficient permissions', 'informatiq-eb-redsys'));
}
$preauth_id = intval($_POST['preauth_id']);
$booking_id = intval($_POST['booking_id']);
global $wpdb;
$table_name = $wpdb->prefix . 'eb_preauthorizations';
$preauth = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$table_name} WHERE id = %d AND status = 'active'",
$preauth_id
));
if (!$preauth) {
wp_send_json_error(__('Preauthorization not found or not active', 'informatiq-eb-redsys'));
}
// Process cancellation with Redsys
$result = $this->process_cancellation($preauth);
if ($result['success']) {
wp_send_json_success(__('Preauthorization cancelled successfully', 'informatiq-eb-redsys'));
} else {
wp_send_json_error($result['error']);
}
}
/**
* Process capture with Redsys
*/
private function process_capture($preauth) {
// Include Redsys API
require_once dirname(__FILE__) . '/../api/class-redsys-api.php';
$redsys = new Redsys_API();
// Set capture parameters
$redsys->setParameter('DS_MERCHANT_AMOUNT', $preauth->amount * 100);
$redsys->setParameter('DS_MERCHANT_ORDER', $preauth->order_number);
$redsys->setParameter('DS_MERCHANT_MERCHANTCODE', get_option('eb_redsys_merchant_code'));
$redsys->setParameter('DS_MERCHANT_CURRENCY', '978');
$redsys->setParameter('DS_MERCHANT_TRANSACTIONTYPE', '2'); // Capture
$redsys->setParameter('DS_MERCHANT_TERMINAL', get_option('eb_redsys_terminal'));
$redsys->setParameter('DS_MERCHANT_AUTHCODE', $preauth->auth_code);
// Generate signature
$signature = $redsys->createMerchantSignature(get_option('eb_redsys_encryption_key'));
// Send request to Redsys
$result = $this->send_redsys_request($redsys, $signature);
if ($result['success']) {
// Update preauthorization status
global $wpdb;
$wpdb->update(
$wpdb->prefix . 'eb_preauthorizations',
array(
'status' => 'captured',
'captured_at' => current_time('mysql'),
'updated_at' => current_time('mysql')
),
array('id' => $preauth->id),
array('%s', '%s', '%s'),
array('%d')
);
// Update booking payment status
update_post_meta($preauth->booking_id, '_eb_payment_status', 'completed');
return array('success' => true);
} else {
return array('success' => false, 'error' => $result['error']);
}
}
/**
* Process cancellation with Redsys
*/
private function process_cancellation($preauth) {
// Include Redsys API
require_once dirname(__FILE__) . '/../api/class-redsys-api.php';
$redsys = new Redsys_API();
// Set cancellation parameters
$redsys->setParameter('DS_MERCHANT_AMOUNT', $preauth->amount * 100);
$redsys->setParameter('DS_MERCHANT_ORDER', $preauth->order_number);
$redsys->setParameter('DS_MERCHANT_MERCHANTCODE', get_option('eb_redsys_merchant_code'));
$redsys->setParameter('DS_MERCHANT_CURRENCY', '978');
$redsys->setParameter('DS_MERCHANT_TRANSACTIONTYPE', '9'); // Cancellation
$redsys->setParameter('DS_MERCHANT_TERMINAL', get_option('eb_redsys_terminal'));
$redsys->setParameter('DS_MERCHANT_AUTHCODE', $preauth->auth_code);
// Generate signature
$signature = $redsys->createMerchantSignature(get_option('eb_redsys_encryption_key'));
// Send request to Redsys
$result = $this->send_redsys_request($redsys, $signature);
if ($result['success']) {
// Update preauthorization status
global $wpdb;
$wpdb->update(
$wpdb->prefix . 'eb_preauthorizations',
array(
'status' => 'cancelled',
'cancelled_at' => current_time('mysql'),
'updated_at' => current_time('mysql')
),
array('id' => $preauth->id),
array('%s', '%s', '%s'),
array('%d')
);
// Update booking payment status
update_post_meta($preauth->booking_id, '_eb_payment_status', 'cancelled');
return array('success' => true);
} else {
return array('success' => false, 'error' => $result['error']);
}
}
/**
* Send request to Redsys
*/
private function send_redsys_request($redsys, $signature) {
$test_mode = get_option('eb_redsys_test_mode') === 'yes';
$url = $test_mode ?
'https://sis-t.redsys.es:25443/sis/operaciones' :
'https://sis.redsys.es/sis/operaciones';
$post_data = array(
'Ds_SignatureVersion' => 'HMAC_SHA256_V1',
'Ds_MerchantParameters' => $redsys->createMerchantParameters(),
'Ds_Signature' => $signature
);
$response = wp_remote_post($url, array(
'timeout' => 45,
'body' => $post_data
));
if (is_wp_error($response)) {
return array('success' => false, 'error' => $response->get_error_message());
}
$body = wp_remote_retrieve_body($response);
$xml = simplexml_load_string($body);
if ($xml && isset($xml->OPERACION->Ds_Response)) {
$response_code = (string) $xml->OPERACION->Ds_Response;
if ($response_code >= 0 && $response_code <= 99) {
return array('success' => true, 'response_code' => $response_code);
} else {
return array('success' => false, 'error' => 'Response code: ' . $response_code);
}
}
return array('success' => false, 'error' => 'Invalid response from Redsys');
}
/**
* Handle preauthorization notification
*/
public function handle_preauth_notification($notification_data, $booking_id) {
if ($notification_data['Ds_TransactionType'] !== '1') {
return; // Not a preauthorization
}
$response_code = intval($notification_data['Ds_Response']);
if ($response_code >= 0 && $response_code <= 99) {
// Successful preauthorization
$this->save_preauthorization_data($booking_id, $notification_data);
} else {
// Failed preauthorization
update_post_meta($booking_id, '_eb_payment_status', 'failed');
}
}
/**
* Save preauthorization data
*/
private function save_preauthorization_data($booking_id, $notification_data) {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_preauthorizations';
$expiry_days = intval(get_option('eb_redsys_preauth_expiry_days', 7));
$expires_at = date('Y-m-d H:i:s', strtotime("+{$expiry_days} days"));
$wpdb->insert(
$table_name,
array(
'booking_id' => $booking_id,
'customer_email' => get_post_meta($booking_id, '_eb_customer_email', true),
'amount' => $notification_data['Ds_Amount'] / 100,
'order_number' => $notification_data['Ds_Order'],
'auth_code' => $notification_data['Ds_AuthorisationCode'],
'status' => 'active',
'created_at' => current_time('mysql'),
'expires_at' => $expires_at
),
array('%d', '%s', '%f', '%s', '%s', '%s', '%s', '%s')
);
// Update booking payment status
update_post_meta($booking_id, '_eb_payment_status', 'preauthorized');
}
/**
* Add preauthorization metabox to booking
*/
public function add_preauth_metabox() {
add_meta_box(
'eb-preauthorization',
__('Preauthorization', 'informatiq-eb-redsys'),
array($this, 'preauth_metabox_callback'),
'eagle_booking',
'side',
'default'
);
}
/**
* Preauthorization metabox callback
*/
public function preauth_metabox_callback($post) {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_preauthorizations';
$preauth = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$table_name} WHERE booking_id = %d ORDER BY created_at DESC LIMIT 1",
$post->ID
));
if (!$preauth) {
echo '<p>' . __('No preauthorization found for this booking.', 'informatiq-eb-redsys') . '</p>';
return;
}
?>
<div class="eb-preauth-metabox">
<p><strong><?php _e('Amount:', 'informatiq-eb-redsys'); ?></strong> €<?php echo number_format($preauth->amount, 2); ?></p>
<p><strong><?php _e('Status:', 'informatiq-eb-redsys'); ?></strong>
<span class="preauth-status status-<?php echo esc_attr($preauth->status); ?>">
<?php echo ucfirst($preauth->status); ?>
</span>
</p>
<p><strong><?php _e('Auth Code:', 'informatiq-eb-redsys'); ?></strong> <code><?php echo esc_html($preauth->auth_code); ?></code></p>
<p><strong><?php _e('Created:', 'informatiq-eb-redsys'); ?></strong> <?php echo date_i18n(get_option('datetime_format'), strtotime($preauth->created_at)); ?></p>
<?php if ($preauth->status === 'active'): ?>
<p><strong><?php _e('Expires:', 'informatiq-eb-redsys'); ?></strong> <?php echo date_i18n(get_option('datetime_format'), strtotime($preauth->expires_at)); ?></p>
<div class="preauth-actions">
<button class="button button-primary capture-preauth"
data-preauth-id="<?php echo $preauth->id; ?>"
data-booking-id="<?php echo $post->ID; ?>">
<?php _e('Capture Payment', 'informatiq-eb-redsys'); ?>
</button>
<button class="button button-secondary cancel-preauth"
data-preauth-id="<?php echo $preauth->id; ?>"
data-booking-id="<?php echo $post->ID; ?>">
<?php _e('Cancel Preauth', 'informatiq-eb-redsys'); ?>
</button>
</div>
<?php endif; ?>
</div>
<?php
}
/**
* Create preauthorization database table
*/
public function create_preauth_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_preauthorizations';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$table_name} (
id int(11) NOT NULL AUTO_INCREMENT,
booking_id int(11) NOT NULL,
customer_email varchar(255) NOT NULL,
amount decimal(10,2) NOT NULL,
order_number varchar(20) NOT NULL,
auth_code varchar(20) NOT NULL,
status varchar(20) DEFAULT 'active',
created_at datetime NOT NULL,
expires_at datetime NOT NULL,
captured_at datetime,
cancelled_at datetime,
updated_at datetime,
PRIMARY KEY (id),
KEY booking_id (booking_id),
KEY status (status),
KEY expires_at (expires_at)
) {$charset_collate};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
}
/**
* Enqueue admin scripts
*/
public function enqueue_admin_scripts($hook) {
if (strpos($hook, 'eb-preauthorizations') !== false || $hook === 'post.php') {
wp_enqueue_script(
'eb-preauth-admin',
plugin_dir_url(__FILE__) . '../assets/js/preauth-admin.js',
array('jquery'),
'1.0.0',
true
);
wp_localize_script('eb-preauth-admin', 'ebPreauth', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('preauth_nonce'),
'strings' => array(
'confirmCapture' => __('Are you sure you want to capture this preauthorization?', 'informatiq-eb-redsys'),
'confirmCancel' => __('Are you sure you want to cancel this preauthorization?', 'informatiq-eb-redsys')
)
));
}
}
}
// Initialize the class
new EB_Redsys_Preauthorization();

View File

@@ -0,0 +1,829 @@
<?php
/**
* Automated Payment Request Scheduler
*
* Handles automated sending of payment requests 14 days before check-in
* Manages the 50% payment workflow for Hotel Raxa
*
* 🤖 Generated with Claude Code (https://claude.ai/code)
*/
// Exit if accessed directly
if (!defined('ABSPATH')) {
exit;
}
class EB_Payment_Scheduler {
/**
* Constructor
*/
public function __construct() {
add_action('init', array($this, 'init'));
}
/**
* Initialize the scheduler
*/
public function init() {
// Schedule the daily cron job
add_action('wp', array($this, 'schedule_payment_requests'));
// Hook the cron job
add_action('eb_daily_payment_check', array($this, 'process_daily_payment_requests'));
// Add admin interface
add_action('admin_menu', array($this, 'add_admin_menu'));
// AJAX handlers
add_action('wp_ajax_save_scheduler_settings', array($this, 'save_scheduler_settings'));
add_action('wp_ajax_manual_payment_check', array($this, 'manual_payment_check'));
// Enqueue admin scripts
add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
}
/**
* Schedule the payment requests cron job
*/
public function schedule_payment_requests() {
if (!wp_next_scheduled('eb_daily_payment_check')) {
wp_schedule_event(time(), 'daily', 'eb_daily_payment_check');
}
}
/**
* Process daily payment requests
*/
public function process_daily_payment_requests() {
// Check if automated requests are enabled
if (get_option('eb_redsys_auto_requests_enabled') !== '1') {
return;
}
$settings = $this->get_scheduler_settings();
$results = array(
'processed' => 0,
'sent' => 0,
'skipped' => 0,
'errors' => 0
);
// Get bookings that need payment requests
$bookings = $this->get_bookings_for_payment_requests($settings['days_before']);
foreach ($bookings as $booking) {
$results['processed']++;
try {
// Check if payment request already sent
if ($this->payment_request_already_sent($booking->ID)) {
$results['skipped']++;
continue;
}
// Calculate payment amount
$amount = $this->calculate_payment_amount($booking, $settings['percentage']);
// Create description
$description = sprintf(
__('Pre-arrival payment for your stay at Hotel Raxa (Booking #%d)', 'informatiq-eb-redsys'),
$booking->ID
);
// Create payment request
$request_id = $this->create_automated_payment_request(
$booking->ID,
$booking->customer_email,
$amount,
$description,
$settings['expiry_hours']
);
if ($request_id) {
// Send email
$sent = $this->send_automated_payment_email($request_id, $booking);
if ($sent) {
$results['sent']++;
// Log the activity
$this->log_payment_request_activity($booking->ID, 'sent', $amount);
} else {
$results['errors']++;
$this->log_payment_request_activity($booking->ID, 'email_failed', $amount);
}
} else {
$results['errors']++;
$this->log_payment_request_activity($booking->ID, 'creation_failed', $amount);
}
} catch (Exception $e) {
$results['errors']++;
$this->log_payment_request_activity($booking->ID, 'error', 0, $e->getMessage());
}
}
// Update last run statistics
update_option('eb_scheduler_last_run', current_time('mysql'));
update_option('eb_scheduler_last_results', $results);
// Send admin notification if there were errors
if ($results['errors'] > 0) {
$this->send_admin_error_notification($results);
}
return $results;
}
/**
* Get scheduler settings
*/
private function get_scheduler_settings() {
return array(
'days_before' => intval(get_option('eb_scheduler_days_before', 14)),
'percentage' => intval(get_option('eb_scheduler_percentage', 50)),
'expiry_hours' => intval(get_option('eb_scheduler_expiry_hours', 72)),
'email_template' => get_option('eb_scheduler_email_template', 'default'),
'admin_notifications' => get_option('eb_scheduler_admin_notifications', 'yes')
);
}
/**
* Get bookings that need payment requests
*/
private function get_bookings_for_payment_requests($days_before) {
global $wpdb;
$target_date = date('Y-m-d', strtotime("+{$days_before} days"));
$bookings = $wpdb->get_results($wpdb->prepare("
SELECT p.ID, p.post_title,
pm1.meta_value as checkin_date,
pm2.meta_value as customer_email,
pm3.meta_value as total_price,
pm4.meta_value as payment_status,
pm5.meta_value as customer_name
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm1 ON p.ID = pm1.post_id AND pm1.meta_key = '_eb_checkin_date'
LEFT JOIN {$wpdb->postmeta} pm2 ON p.ID = pm2.post_id AND pm2.meta_key = '_eb_customer_email'
LEFT JOIN {$wpdb->postmeta} pm3 ON p.ID = pm3.post_id AND pm3.meta_key = '_eb_total_price'
LEFT JOIN {$wpdb->postmeta} pm4 ON p.ID = pm4.post_id AND pm4.meta_key = '_eb_payment_status'
LEFT JOIN {$wpdb->postmeta} pm5 ON p.ID = pm5.post_id AND pm5.meta_key = '_eb_customer_name'
WHERE p.post_type = 'eagle_booking'
AND p.post_status = 'publish'
AND pm1.meta_value = %s
AND (pm4.meta_value = 'pending' OR pm4.meta_value = 'preauthorized')
AND pm2.meta_value != ''
AND pm3.meta_value > 0
", $target_date));
return $bookings;
}
/**
* Check if payment request already sent for booking
*/
private function payment_request_already_sent($booking_id) {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_requests';
$existing = $wpdb->get_var($wpdb->prepare("
SELECT id FROM {$table_name}
WHERE booking_id = %d
AND status IN ('pending', 'sent', 'paid')
AND created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
", $booking_id));
return (bool) $existing;
}
/**
* Calculate payment amount
*/
private function calculate_payment_amount($booking, $percentage) {
$total_price = floatval($booking->total_price);
return round(($total_price * $percentage) / 100, 2);
}
/**
* Create automated payment request
*/
private function create_automated_payment_request($booking_id, $customer_email, $amount, $description, $expiry_hours) {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_requests';
// Generate unique token
$token = wp_generate_password(32, false);
// Create payment link
$payment_link = home_url('/?eb_payment_request=' . $token);
// Calculate expiry date
$expires_at = date('Y-m-d H:i:s', strtotime("+{$expiry_hours} hours"));
$result = $wpdb->insert(
$table_name,
array(
'booking_id' => $booking_id,
'customer_email' => $customer_email,
'amount' => $amount,
'description' => $description,
'token' => $token,
'payment_link' => $payment_link,
'status' => 'pending',
'created_at' => current_time('mysql'),
'expires_at' => $expires_at,
'automated' => 1
),
array('%d', '%s', '%f', '%s', '%s', '%s', '%s', '%s', '%s', '%d')
);
return $result ? $wpdb->insert_id : false;
}
/**
* Send automated payment email
*/
private function send_automated_payment_email($request_id, $booking) {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_requests';
$request = $wpdb->get_row(
$wpdb->prepare("SELECT * FROM {$table_name} WHERE id = %d", $request_id)
);
if (!$request) {
return false;
}
$subject = sprintf(
__('Pre-arrival Payment Required - Hotel Raxa Booking #%d', 'informatiq-eb-redsys'),
$request->booking_id
);
$message = $this->get_automated_payment_email_template($request, $booking);
$headers = array(
'Content-Type: text/html; charset=UTF-8',
'From: Hotel Raxa <reservas@hotelraxa.com>',
'Reply-To: Hotel Raxa <info@hotelraxa.com>'
);
$sent = wp_mail($request->customer_email, $subject, $message, $headers);
if ($sent) {
// Update status
$wpdb->update(
$table_name,
array('status' => 'sent', 'sent_at' => current_time('mysql')),
array('id' => $request_id),
array('%s', '%s'),
array('%d')
);
}
return $sent;
}
/**
* Get automated payment email template
*/
private function get_automated_payment_email_template($request, $booking) {
$checkin_date = get_post_meta($booking->ID, '_eb_checkin_date', true);
$checkout_date = get_post_meta($booking->ID, '_eb_checkout_date', true);
$customer_name = $booking->customer_name ?: 'Valued Guest';
$template = '
<html>
<head>
<meta charset="UTF-8">
<title>Pre-arrival Payment - Hotel Raxa</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; background: #f5f5f5; margin: 0; padding: 20px;">
<div style="max-width: 600px; margin: 0 auto; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
<!-- Header -->
<div style="background: linear-gradient(135deg, #2c5aa0 0%, #1e3d6f 100%); color: white; padding: 30px 20px; text-align: center;">
<h1 style="margin: 0; font-size: 28px;">Hotel Raxa</h1>
<p style="margin: 10px 0 0; font-size: 16px; opacity: 0.9;">Pre-arrival Payment Request</p>
</div>
<!-- Content -->
<div style="padding: 30px;">
<h2 style="color: #2c5aa0; margin-top: 0;">Dear ' . esc_html($customer_name) . ',</h2>
<p style="font-size: 16px; margin-bottom: 20px;">
Thank you for choosing Hotel Raxa for your upcoming stay! We are excited to welcome you and ensure you have an exceptional experience with us.
</p>
<p style="font-size: 16px; margin-bottom: 25px;">
To secure your reservation and streamline your check-in process, we kindly request a pre-arrival payment. This helps us guarantee your room and prepare for your arrival.
</p>
<!-- Booking Details Box -->
<div style="background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 25px; margin: 25px 0;">
<h3 style="margin-top: 0; color: #2c5aa0; border-bottom: 2px solid #2c5aa0; padding-bottom: 10px;">Booking Details</h3>
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td style="padding: 8px 0; font-weight: bold; width: 40%;">Booking Reference:</td>
<td style="padding: 8px 0;">#' . $request->booking_id . '</td>
</tr>
' . ($checkin_date ? '<tr><td style="padding: 8px 0; font-weight: bold;">Check-in Date:</td><td style="padding: 8px 0;">' . date_i18n(get_option('date_format'), strtotime($checkin_date)) . '</td></tr>' : '') . '
' . ($checkout_date ? '<tr><td style="padding: 8px 0; font-weight: bold;">Check-out Date:</td><td style="padding: 8px 0;">' . date_i18n(get_option('date_format'), strtotime($checkout_date)) . '</td></tr>' : '') . '
<tr>
<td style="padding: 8px 0; font-weight: bold;">Payment Amount:</td>
<td style="padding: 8px 0; font-size: 18px; color: #2c5aa0; font-weight: bold;">€' . number_format($request->amount, 2) . '</td>
</tr>
<tr>
<td style="padding: 8px 0; font-weight: bold;">Payment Due:</td>
<td style="padding: 8px 0; color: #dc3545;">' . date_i18n(get_option('datetime_format'), strtotime($request->expires_at)) . '</td>
</tr>
</table>
</div>
<!-- Payment Button -->
<div style="text-align: center; margin: 35px 0;">
<a href="' . $request->payment_link . '"
style="background: linear-gradient(135deg, #007cba 0%, #005a87 100%);
color: white;
padding: 18px 40px;
text-decoration: none;
border-radius: 8px;
display: inline-block;
font-weight: bold;
font-size: 18px;
box-shadow: 0 4px 10px rgba(0,124,186,0.3);
transition: all 0.3s ease;">
🔒 Pay Securely Now - €' . number_format($request->amount, 2) . '
</a>
</div>
<!-- Important Notice -->
<div style="background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 8px; padding: 20px; margin: 25px 0;">
<h4 style="margin-top: 0; color: #856404; display: flex; align-items: center;">
⚠️ Important Information
</h4>
<ul style="margin: 10px 0; color: #856404; padding-left: 20px;">
<li>This payment link will expire on <strong>' . date_i18n(get_option('datetime_format'), strtotime($request->expires_at)) . '</strong></li>
<li>Payment is processed securely through our trusted payment gateway</li>
<li>This represents 50% of your total booking amount</li>
<li>The remaining balance can be paid upon arrival</li>
</ul>
</div>
<!-- Why Pre-payment -->
<div style="background: #e3f2fd; border-left: 4px solid #2196f3; padding: 20px; margin: 25px 0;">
<h4 style="margin-top: 0; color: #1565c0;">Why do we request pre-payment?</h4>
<p style="margin-bottom: 0; color: #1565c0;">
Pre-payment helps us guarantee your reservation, prepare your room according to your preferences,
and ensures a smooth, fast check-in process when you arrive. It also helps us provide you with
the highest level of personalized service during your stay.
</p>
</div>
<p style="font-size: 16px;">
If you have any questions about this payment or your reservation, please do not hesitate to contact us.
Our team is here to assist you and ensure your stay exceeds your expectations.
</p>
<p style="font-size: 16px;">
We look forward to welcoming you to Hotel Raxa very soon!
</p>
<p style="font-size: 16px; margin-bottom: 0;">
Warm regards,<br>
<strong>The Hotel Raxa Team</strong>
</p>
</div>
<!-- Footer -->
<div style="background: #f8f9fa; padding: 25px; text-align: center; border-top: 1px solid #e9ecef;">
<p style="margin: 0 0 10px; font-weight: bold; color: #2c5aa0;">Hotel Raxa</p>
<p style="margin: 0 0 10px; font-size: 14px; color: #666;">
📧 Email: info@hotelraxa.com | 📞 Phone: +34 XXX XXX XXX
</p>
<p style="margin: 0; font-size: 12px; color: #999;">
This email was sent automatically. Please do not reply to this email.<br>
For support, contact us at info@hotelraxa.com
</p>
</div>
</div>
</body>
</html>';
return $template;
}
/**
* Log payment request activity
*/
private function log_payment_request_activity($booking_id, $action, $amount, $error_message = '') {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_scheduler_log';
$wpdb->insert(
$table_name,
array(
'booking_id' => $booking_id,
'action' => $action,
'amount' => $amount,
'error_message' => $error_message,
'created_at' => current_time('mysql')
),
array('%d', '%s', '%f', '%s', '%s')
);
}
/**
* Send admin error notification
*/
private function send_admin_error_notification($results) {
if (get_option('eb_scheduler_admin_notifications') !== 'yes') {
return;
}
$admin_email = get_option('admin_email');
$site_name = get_bloginfo('name');
$subject = sprintf(
__('Payment Scheduler Errors - %s', 'informatiq-eb-redsys'),
$site_name
);
$message = sprintf(
__('The automated payment request scheduler encountered errors:\n\nProcessed: %d bookings\nSent: %d requests\nSkipped: %d bookings\nErrors: %d\n\nPlease check the scheduler log for details.', 'informatiq-eb-redsys'),
$results['processed'],
$results['sent'],
$results['skipped'],
$results['errors']
);
wp_mail($admin_email, $subject, $message);
}
/**
* Add admin menu
*/
public function add_admin_menu() {
add_submenu_page(
'eb_bookings',
__('Payment Scheduler', 'informatiq-eb-redsys'),
__('Payment Scheduler', 'informatiq-eb-redsys'),
'manage_options',
'eb-payment-scheduler',
array($this, 'admin_page')
);
}
/**
* Render admin page
*/
public function admin_page() {
$settings = $this->get_scheduler_settings();
$last_run = get_option('eb_scheduler_last_run');
$last_results = get_option('eb_scheduler_last_results', array());
?>
<div class="wrap">
<h1><?php _e('Automated Payment Scheduler', 'informatiq-eb-redsys'); ?></h1>
<div class="eb-scheduler-admin">
<!-- Current Status -->
<div class="postbox">
<h2 class="hndle"><?php _e('Scheduler Status', 'informatiq-eb-redsys'); ?></h2>
<div class="inside">
<table class="form-table">
<tr>
<th scope="row"><?php _e('Status', 'informatiq-eb-redsys'); ?></th>
<td>
<?php if (wp_next_scheduled('eb_daily_payment_check')): ?>
<span style="color: #00a32a; font-weight: bold;">✓ Active</span>
<p class="description">
<?php _e('Next run:', 'informatiq-eb-redsys'); ?>
<?php echo date_i18n(get_option('datetime_format'), wp_next_scheduled('eb_daily_payment_check')); ?>
</p>
<?php else: ?>
<span style="color: #d63638; font-weight: bold;">✗ Inactive</span>
<?php endif; ?>
</td>
</tr>
<tr>
<th scope="row"><?php _e('Last Run', 'informatiq-eb-redsys'); ?></th>
<td>
<?php if ($last_run): ?>
<?php echo date_i18n(get_option('datetime_format'), strtotime($last_run)); ?>
<?php if (!empty($last_results)): ?>
<br>
<small>
<?php
printf(
__('Processed: %d | Sent: %d | Skipped: %d | Errors: %d', 'informatiq-eb-redsys'),
$last_results['processed'] ?? 0,
$last_results['sent'] ?? 0,
$last_results['skipped'] ?? 0,
$last_results['errors'] ?? 0
);
?>
</small>
<?php endif; ?>
<?php else: ?>
<?php _e('Never run', 'informatiq-eb-redsys'); ?>
<?php endif; ?>
</td>
</tr>
</table>
<p>
<button type="button" id="manual-payment-check" class="button button-primary">
<?php _e('Run Manual Check', 'informatiq-eb-redsys'); ?>
</button>
</p>
</div>
</div>
<!-- Settings -->
<div class="postbox">
<h2 class="hndle"><?php _e('Scheduler Settings', 'informatiq-eb-redsys'); ?></h2>
<div class="inside">
<form id="scheduler-settings-form">
<table class="form-table">
<tr>
<th scope="row">
<label for="days_before"><?php _e('Days Before Check-in', 'informatiq-eb-redsys'); ?></label>
</th>
<td>
<input type="number" id="days_before" name="days_before"
value="<?php echo esc_attr($settings['days_before']); ?>"
min="1" max="365" class="small-text">
<p class="description">
<?php _e('Number of days before check-in to send payment requests', 'informatiq-eb-redsys'); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="percentage"><?php _e('Payment Percentage', 'informatiq-eb-redsys'); ?></label>
</th>
<td>
<input type="number" id="percentage" name="percentage"
value="<?php echo esc_attr($settings['percentage']); ?>"
min="1" max="100" class="small-text">
<span>%</span>
<p class="description">
<?php _e('Percentage of total booking amount to request', 'informatiq-eb-redsys'); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="expiry_hours"><?php _e('Payment Link Expiry', 'informatiq-eb-redsys'); ?></label>
</th>
<td>
<select id="expiry_hours" name="expiry_hours">
<option value="24" <?php selected($settings['expiry_hours'], 24); ?>><?php _e('24 hours', 'informatiq-eb-redsys'); ?></option>
<option value="48" <?php selected($settings['expiry_hours'], 48); ?>><?php _e('48 hours', 'informatiq-eb-redsys'); ?></option>
<option value="72" <?php selected($settings['expiry_hours'], 72); ?>><?php _e('72 hours', 'informatiq-eb-redsys'); ?></option>
<option value="168" <?php selected($settings['expiry_hours'], 168); ?>><?php _e('1 week', 'informatiq-eb-redsys'); ?></option>
</select>
</td>
</tr>
<tr>
<th scope="row">
<?php _e('Admin Notifications', 'informatiq-eb-redsys'); ?>
</th>
<td>
<label>
<input type="checkbox" id="admin_notifications" name="admin_notifications"
value="yes" <?php checked($settings['admin_notifications'], 'yes'); ?>>
<?php _e('Send email notifications when errors occur', 'informatiq-eb-redsys'); ?>
</label>
</td>
</tr>
</table>
<p class="submit">
<button type="submit" class="button-primary">
<?php _e('Save Settings', 'informatiq-eb-redsys'); ?>
</button>
</p>
</form>
</div>
</div>
<!-- Recent Activity -->
<div class="postbox">
<h2 class="hndle"><?php _e('Recent Activity', 'informatiq-eb-redsys'); ?></h2>
<div class="inside">
<?php $this->display_recent_activity(); ?>
</div>
</div>
</div>
</div>
<style>
.eb-scheduler-admin .postbox {
margin-bottom: 20px;
}
.activity-log {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
background: #f9f9f9;
}
.activity-item {
padding: 10px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-success {
border-left: 4px solid #00a32a;
}
.activity-error {
border-left: 4px solid #d63638;
}
.activity-info {
border-left: 4px solid #2271b1;
}
</style>
<?php
}
/**
* Display recent activity
*/
private function display_recent_activity() {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_scheduler_log';
// Check if table exists
if ($wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") !== $table_name) {
echo '<p>' . __('No activity log available. The log table will be created automatically when the scheduler runs.', 'informatiq-eb-redsys') . '</p>';
return;
}
$activities = $wpdb->get_results("
SELECT * FROM {$table_name}
ORDER BY created_at DESC
LIMIT 20
");
if (empty($activities)) {
echo '<p>' . __('No recent activity found.', 'informatiq-eb-redsys') . '</p>';
return;
}
echo '<div class="activity-log">';
foreach ($activities as $activity) {
$class = '';
$icon = '';
switch ($activity->action) {
case 'sent':
$class = 'activity-success';
$icon = '✓';
$message = sprintf(__('Payment request sent for booking #%d (€%.2f)', 'informatiq-eb-redsys'), $activity->booking_id, $activity->amount);
break;
case 'email_failed':
$class = 'activity-error';
$icon = '✗';
$message = sprintf(__('Email failed for booking #%d', 'informatiq-eb-redsys'), $activity->booking_id);
break;
case 'creation_failed':
$class = 'activity-error';
$icon = '✗';
$message = sprintf(__('Payment request creation failed for booking #%d', 'informatiq-eb-redsys'), $activity->booking_id);
break;
case 'error':
$class = 'activity-error';
$icon = '✗';
$message = sprintf(__('Error processing booking #%d: %s', 'informatiq-eb-redsys'), $activity->booking_id, $activity->error_message);
break;
default:
$class = 'activity-info';
$icon = '';
$message = sprintf(__('Action "%s" for booking #%d', 'informatiq-eb-redsys'), $activity->action, $activity->booking_id);
}
echo '<div class="activity-item ' . $class . '">';
echo '<div><span style="margin-right: 10px;">' . $icon . '</span>' . esc_html($message) . '</div>';
echo '<div><small>' . date_i18n(get_option('datetime_format'), strtotime($activity->created_at)) . '</small></div>';
echo '</div>';
}
echo '</div>';
}
/**
* Save scheduler settings (AJAX handler)
*/
public function save_scheduler_settings() {
check_ajax_referer('scheduler_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die(__('Insufficient permissions', 'informatiq-eb-redsys'));
}
$settings = array(
'eb_scheduler_days_before' => intval($_POST['days_before']),
'eb_scheduler_percentage' => intval($_POST['percentage']),
'eb_scheduler_expiry_hours' => intval($_POST['expiry_hours']),
'eb_scheduler_admin_notifications' => isset($_POST['admin_notifications']) ? 'yes' : 'no'
);
foreach ($settings as $key => $value) {
update_option($key, $value);
}
wp_send_json_success(__('Settings saved successfully', 'informatiq-eb-redsys'));
}
/**
* Manual payment check (AJAX handler)
*/
public function manual_payment_check() {
check_ajax_referer('scheduler_nonce', 'nonce');
if (!current_user_can('manage_options')) {
wp_die(__('Insufficient permissions', 'informatiq-eb-redsys'));
}
$results = $this->process_daily_payment_requests();
wp_send_json_success($results);
}
/**
* Enqueue admin scripts
*/
public function enqueue_admin_scripts($hook) {
if (strpos($hook, 'eb-payment-scheduler') === false) {
return;
}
wp_enqueue_script(
'eb-scheduler-admin',
plugin_dir_url(__FILE__) . '../assets/js/scheduler-admin.js',
array('jquery'),
'1.0.0',
true
);
wp_localize_script('eb-scheduler-admin', 'ebScheduler', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('scheduler_nonce'),
'strings' => array(
'confirmManualRun' => __('Are you sure you want to run a manual payment check?', 'informatiq-eb-redsys'),
'processing' => __('Processing...', 'informatiq-eb-redsys'),
'completed' => __('Manual check completed!', 'informatiq-eb-redsys')
)
));
}
/**
* Create database table for scheduler log
*/
public static function create_scheduler_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'eb_payment_scheduler_log';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE {$table_name} (
id int(11) NOT NULL AUTO_INCREMENT,
booking_id int(11) NOT NULL,
action varchar(50) NOT NULL,
amount decimal(10,2) DEFAULT 0,
error_message text,
created_at datetime NOT NULL,
PRIMARY KEY (id),
KEY booking_id (booking_id),
KEY action (action),
KEY created_at (created_at)
) {$charset_collate};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
}
}
// Initialize the scheduler
new EB_Payment_Scheduler();
// Create table on activation
register_activation_hook(EB_REDSYS_FILE, array('EB_Payment_Scheduler', 'create_scheduler_table'));