829 lines
35 KiB
PHP
829 lines
35 KiB
PHP
|
|
<?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'));
|