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 07:43:22 +02:00
commit 5b1e2453c7
9816 changed files with 2784509 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
<?php
/**
* Class for the Stripe API.
*
* @link https://stripe.com/docs/api
*/
class WPCF7_Stripe_API {
const api_version = '2022-11-15';
const partner_id = 'pp_partner_HHbvqLh1AaO7Am';
const app_name = 'WordPress Contact Form 7';
const app_url = 'https://contactform7.com/stripe-integration/';
private $secret;
/**
* Constructor.
*
* @param string $secret Secret key.
*/
public function __construct( $secret ) {
$this->secret = $secret;
}
/**
* Sends a debug information for a remote request to the PHP error log.
*
* @param string $url URL to retrieve.
* @param array $request Request arguments.
* @param array|WP_Error $response The response or WP_Error on failure.
*/
private function log( $url, $request, $response ) {
wpcf7_log_remote_request( $url, $request, $response );
}
/**
* Returns default set of HTTP request headers used for Stripe API.
*
* @link https://stripe.com/docs/building-plugins#setappinfo
*
* @return array An associative array of headers.
*/
private function default_headers() {
$app_info = array(
'name' => self::app_name,
'partner_id' => self::partner_id,
'url' => self::app_url,
'version' => WPCF7_VERSION,
);
$ua = array(
'lang' => 'php',
'lang_version' => PHP_VERSION,
'application' => $app_info,
);
$headers = array(
'Authorization' => sprintf( 'Bearer %s', $this->secret ),
'Stripe-Version' => self::api_version,
'X-Stripe-Client-User-Agent' => wp_json_encode( $ua ),
'User-Agent' => sprintf(
'%1$s/%2$s (%3$s)',
self::app_name,
WPCF7_VERSION,
self::app_url
),
);
return $headers;
}
/**
* Creates a Payment Intent.
*
* @link https://stripe.com/docs/api/payment_intents/create
*
* @param string|array $args Optional. Arguments to control behavior.
* @return array|bool An associative array if 200 OK, false otherwise.
*/
public function create_payment_intent( $args = '' ) {
$args = wp_parse_args( $args, array(
'amount' => 0,
'currency' => '',
'receipt_email' => '',
) );
if ( ! is_email( $args['receipt_email'] ) ) {
unset( $args['receipt_email'] );
}
$endpoint = 'https://api.stripe.com/v1/payment_intents';
$request = array(
'headers' => $this->default_headers(),
'body' => $args,
);
$response = wp_remote_post( sanitize_url( $endpoint ), $request );
if ( 200 != wp_remote_retrieve_response_code( $response ) ) {
if ( WP_DEBUG ) {
$this->log( $endpoint, $request, $response );
}
return false;
}
$response_body = wp_remote_retrieve_body( $response );
$response_body = json_decode( $response_body, true );
return $response_body;
}
/**
* Retrieve a Payment Intent.
*
* @link https://stripe.com/docs/api/payment_intents/retrieve
*
* @param string $id Payment Intent identifier.
* @return array|bool An associative array if 200 OK, false otherwise.
*/
public function retrieve_payment_intent( $id ) {
$endpoint = sprintf(
'https://api.stripe.com/v1/payment_intents/%s',
urlencode( $id )
);
$request = array(
'headers' => $this->default_headers(),
);
$response = wp_remote_get( sanitize_url( $endpoint ), $request );
if ( 200 != wp_remote_retrieve_response_code( $response ) ) {
if ( WP_DEBUG ) {
$this->log( $endpoint, $request, $response );
}
return false;
}
$response_body = wp_remote_retrieve_body( $response );
$response_body = json_decode( $response_body, true );
return $response_body;
}
}

View File

@@ -0,0 +1,6 @@
<?php
return array(
'dependencies' => array(),
'version' => WPCF7_VERSION,
);

View File

@@ -0,0 +1 @@
document.addEventListener("DOMContentLoaded",(e=>{const t=()=>{const e=document.querySelectorAll("form.wpcf7-form .wpcf7-stripe");for(let t=0;t<e.length;t++){let r=e[t];r.querySelector("button").disabled=!0;let i=document.createElement("span");i.setAttribute("class","wpcf7-not-valid-tip"),i.insertAdjacentText("beforeend","This form includes a payment widget that requires a modern browser to work."),r.appendChild(i)}};if(void 0===window.wpcf7_stripe)return console.error("window.wpcf7_stripe is not defined."),void t();if("function"!=typeof window.Stripe)return console.error("window.Stripe is not defined."),void t();if("function"!=typeof wpcf7.submit)return console.error("wpcf7.submit is not defined."),void t();const r=Stripe(wpcf7_stripe.publishable_key),i=r.elements();document.addEventListener("wpcf7submit",(e=>{const t=e.detail.unitTag,s=`${t}-ve-stripe-card-element`,n=document.querySelector(`#${t} form`),o=n.closest(".wpcf7").querySelector(".screen-reader-response"),d=n.querySelector(".wpcf7-stripe .wpcf7-form-control-wrap"),c=n.querySelector(".wpcf7-stripe button.first"),a=n.querySelector(".wpcf7-stripe button.second"),l=n.querySelector('[name="_wpcf7_stripe_payment_intent"]');if(!l)return;l.setAttribute("value","");const u=e=>{const t=o.querySelector("ul"),r=t.querySelector(`li#${s}`);r&&r.remove();const i=document.createElement("li");i.setAttribute("id",s),i.insertAdjacentText("beforeend",e.message),t.appendChild(i)},p=e=>{const t=d.querySelector(".wpcf7-form-control");t.classList.add("wpcf7-not-valid"),t.setAttribute("aria-describedby",s);const r=document.createElement("span");r.setAttribute("class","wpcf7-not-valid-tip"),r.setAttribute("aria-hidden","true"),r.insertAdjacentText("beforeend",e.message),d.appendChild(r),d.querySelectorAll("[aria-invalid]").forEach((e=>{e.setAttribute("aria-invalid","true")})),t.closest(".use-floating-validation-tip")&&(t.addEventListener("focus",(e=>{r.setAttribute("style","display: none")})),r.addEventListener("mouseover",(e=>{r.setAttribute("style","display: none")})))},f=()=>{o.querySelectorAll(`ul li#${s}`).forEach((e=>{e.remove()})),d.querySelectorAll(".wpcf7-not-valid-tip").forEach((e=>{e.remove()})),d.querySelectorAll("[aria-invalid]").forEach((e=>{e.setAttribute("aria-invalid","false")})),d.querySelectorAll(".wpcf7-form-control").forEach((e=>{e.removeAttribute("aria-describedby"),e.classList.remove("wpcf7-not-valid")}))};if("payment_required"===e.detail.status){const s=e.detail.apiResponse.stripe.payment_intent;s.id&&l.setAttribute("value",s.id);const o=i.getElement("card")||i.create("card");o.mount(`#${t} .wpcf7-stripe .card-element`),o.clear(),d.classList.remove("hidden"),c.classList.add("hidden"),a.classList.remove("hidden"),a.disabled=!0,o.addEventListener("change",(e=>{if(f(),e.error){const t={message:e.error.message};u(t),p(t),a.disabled=!0}else a.disabled=!1})),a.addEventListener("click",(e=>{f(),a.disabled=!0,n.classList.add("submitting"),wpcf7.blocked||r.confirmCardPayment(s.client_secret,{payment_method:{card:o}}).then((e=>{if(e.error){e.error.decline_code&&["fraudulent","lost_card","merchant_blacklist","pickup_card","restricted_card","security_violation","service_not_allowed","stolen_card","transaction_not_allowed"].includes(e.error.decline_code)&&(wpcf7.blocked=!0),n.classList.remove("submitting");const t={message:e.error.message};u(t),p(t)}else"succeeded"===e.paymentIntent.status&&wpcf7.submit(n)}))}))}else d.classList.add("hidden"),c.classList.remove("hidden"),a.classList.add("hidden"),["mail_sent","mail_failed"].includes(e.detail.status)&&(c.disabled=!0)}))}));

View File

@@ -0,0 +1,262 @@
<?php
if ( ! class_exists( 'WPCF7_Service' ) ) {
return;
}
class WPCF7_Stripe extends WPCF7_Service {
private static $instance;
private $api_keys;
public static function get_instance() {
if ( empty( self::$instance ) ) {
self::$instance = new self;
}
return self::$instance;
}
private function __construct() {
$option = WPCF7::get_option( 'stripe' );
if ( isset( $option['api_keys']['publishable'] )
and isset( $option['api_keys']['secret'] ) ) {
$this->api_keys = array(
'publishable' => $option['api_keys']['publishable'],
'secret' => $option['api_keys']['secret'],
);
}
}
public function get_title() {
return __( 'Stripe', 'contact-form-7' );
}
public function is_active() {
return (bool) $this->get_api_keys();
}
public function api() {
if ( $this->is_active() ) {
$api = new WPCF7_Stripe_API( $this->api_keys['secret'] );
return $api;
}
}
public function get_api_keys() {
return $this->api_keys;
}
public function get_categories() {
return array( 'payments' );
}
public function icon() {
}
public function link() {
echo wpcf7_link(
'https://stripe.com/',
'stripe.com'
);
}
protected function menu_page_url( $args = '' ) {
$args = wp_parse_args( $args, array() );
$url = menu_page_url( 'wpcf7-integration', false );
$url = add_query_arg( array( 'service' => 'stripe' ), $url );
if ( ! empty( $args ) ) {
$url = add_query_arg( $args, $url );
}
return $url;
}
protected function save_data() {
WPCF7::update_option( 'stripe', array(
'api_keys' => $this->api_keys,
) );
}
protected function reset_data() {
$this->api_keys = null;
$this->save_data();
}
public function load( $action = '' ) {
if ( 'setup' == $action and 'POST' == $_SERVER['REQUEST_METHOD'] ) {
check_admin_referer( 'wpcf7-stripe-setup' );
if ( ! empty( $_POST['reset'] ) ) {
$this->reset_data();
$redirect_to = $this->menu_page_url( 'action=setup' );
} else {
$publishable = trim( $_POST['publishable'] ?? '' );
$secret = trim( $_POST['secret'] ?? '' );
if ( $publishable and $secret ) {
$this->api_keys = array(
'publishable' => $publishable,
'secret' => $secret,
);
$this->save_data();
$redirect_to = $this->menu_page_url( array(
'message' => 'success',
) );
} else {
$redirect_to = $this->menu_page_url( array(
'action' => 'setup',
'message' => 'invalid',
) );
}
}
wp_safe_redirect( $redirect_to );
exit();
}
}
public function admin_notice( $message = '' ) {
if ( 'invalid' === $message ) {
wp_admin_notice(
sprintf(
'<strong>%1$s</strong>: %2$s',
esc_html( __( "Error", 'contact-form-7' ) ),
esc_html( __( "Invalid key values.", 'contact-form-7' ) )
),
'type=error'
);
}
if ( 'success' === $message ) {
wp_admin_notice(
esc_html( __( "Settings saved.", 'contact-form-7' ) ),
'type=success'
);
}
}
public function display( $action = '' ) {
echo sprintf(
'<p>%s</p>',
// https://stripe.com/docs/partners/support#intro
esc_html( __( "Stripe is a simple and powerful way to accept payments online. Stripe has no setup fees, no monthly fees, and no hidden costs. Millions of businesses rely on Stripes software tools to accept payments securely and expand globally.", 'contact-form-7' ) )
);
echo sprintf(
'<p><strong>%s</strong></p>',
wpcf7_link(
__( 'https://contactform7.com/stripe-integration/', 'contact-form-7' ),
__( 'Stripe integration', 'contact-form-7' )
)
);
if ( $this->is_active() ) {
echo sprintf(
'<p class="dashicons-before dashicons-yes">%s</p>',
esc_html( __( "Stripe is active on this site.", 'contact-form-7' ) )
);
}
if ( 'setup' == $action ) {
$this->display_setup();
} elseif ( is_ssl() or WP_DEBUG ) {
echo sprintf(
'<p><a href="%1$s" class="button">%2$s</a></p>',
esc_url( $this->menu_page_url( 'action=setup' ) ),
esc_html( __( 'Setup Integration', 'contact-form-7' ) )
);
} else {
echo sprintf(
'<p class="dashicons-before dashicons-warning">%s</p>',
esc_html( __( "Stripe is not available on this site. It requires an HTTPS-enabled site.", 'contact-form-7' ) )
);
}
}
private function display_setup() {
$api_keys = $this->get_api_keys();
if ( $api_keys ) {
$publishable = $api_keys['publishable'];
$secret = $api_keys['secret'];
} else {
$publishable = '';
$secret = '';
}
?>
<form method="post" action="<?php echo esc_url( $this->menu_page_url( 'action=setup' ) ); ?>">
<?php wp_nonce_field( 'wpcf7-stripe-setup' ); ?>
<table class="form-table">
<tbody>
<tr>
<th scope="row"><label for="publishable"><?php echo esc_html( __( 'Publishable Key', 'contact-form-7' ) ); ?></label></th>
<td><?php
if ( $this->is_active() ) {
echo esc_html( $publishable );
echo sprintf(
'<input type="hidden" value="%s" id="publishable" name="publishable" />',
esc_attr( $publishable )
);
} else {
echo sprintf(
'<input type="text" aria-required="true" value="%s" id="publishable" name="publishable" class="regular-text code" />',
esc_attr( $publishable )
);
}
?></td>
</tr>
<tr>
<th scope="row"><label for="secret"><?php echo esc_html( __( 'Secret Key', 'contact-form-7' ) ); ?></label></th>
<td><?php
if ( $this->is_active() ) {
echo esc_html( wpcf7_mask_password( $secret ) );
echo sprintf(
'<input type="hidden" value="%s" id="secret" name="secret" />',
esc_attr( $secret )
);
} else {
echo sprintf(
'<input type="text" aria-required="true" value="%s" id="secret" name="secret" class="regular-text code" />',
esc_attr( $secret )
);
}
?></td>
</tr>
</tbody>
</table>
<?php
if ( $this->is_active() ) {
submit_button(
_x( 'Remove Keys', 'API keys', 'contact-form-7' ),
'small', 'reset'
);
} else {
submit_button( __( 'Save Changes', 'contact-form-7' ) );
}
?>
</form>
<?php
}
}

View File

@@ -0,0 +1,348 @@
<?php
/**
* Stripe module main file
*
* @link https://contactform7.com/stripe-integration/
*/
wpcf7_include_module_file( 'stripe/service.php' );
wpcf7_include_module_file( 'stripe/api.php' );
add_action(
'wpcf7_init',
'wpcf7_stripe_register_service',
50, 0
);
/**
* Registers the Stripe service.
*/
function wpcf7_stripe_register_service() {
$integration = WPCF7_Integration::get_instance();
$integration->add_service( 'stripe',
WPCF7_Stripe::get_instance()
);
}
add_action(
'wpcf7_enqueue_scripts',
'wpcf7_stripe_enqueue_scripts',
10, 0
);
/**
* Enqueues scripts and styles for the Stripe module.
*/
function wpcf7_stripe_enqueue_scripts() {
$service = WPCF7_Stripe::get_instance();
if ( ! $service->is_active() ) {
return;
}
wp_enqueue_style( 'wpcf7-stripe',
wpcf7_plugin_url( 'modules/stripe/style.css' ),
array(), WPCF7_VERSION, 'all'
);
wp_register_script(
'stripe',
'https://js.stripe.com/v3/',
array(),
null
);
$assets = include(
wpcf7_plugin_path( 'modules/stripe/index.asset.php' )
);
$assets = wp_parse_args( $assets, array(
'dependencies' => array(),
'version' => WPCF7_VERSION,
) );
wp_enqueue_script(
'wpcf7-stripe',
wpcf7_plugin_url( 'modules/stripe/index.js' ),
array_merge(
$assets['dependencies'],
array(
'wp-polyfill',
'contact-form-7',
'stripe',
)
),
$assets['version'],
array( 'in_footer' => true )
);
$api_keys = $service->get_api_keys();
if ( $api_keys['publishable'] ) {
wp_add_inline_script( 'wpcf7-stripe',
sprintf(
'var wpcf7_stripe = %s;',
wp_json_encode( array(
'publishable_key' => $api_keys['publishable'],
), JSON_PRETTY_PRINT )
),
'before'
);
}
}
add_filter(
'wpcf7_skip_spam_check',
'wpcf7_stripe_skip_spam_check',
10, 2
);
/**
* Skips the spam check if it is not necessary.
*
* @return bool True if the spam check is not necessary.
*/
function wpcf7_stripe_skip_spam_check( $skip_spam_check, $submission ) {
$service = WPCF7_Stripe::get_instance();
if ( ! $service->is_active() ) {
return $skip_spam_check;
}
if ( ! empty( $_POST['_wpcf7_stripe_payment_intent'] ) ) {
$pi_id = trim( $_POST['_wpcf7_stripe_payment_intent'] );
$payment_intent = $service->api()->retrieve_payment_intent( $pi_id );
if ( isset( $payment_intent['status'] )
and ( 'succeeded' === $payment_intent['status'] ) ) {
$submission->push( 'payment_intent', $pi_id );
}
}
if ( ! empty( $submission->pull( 'payment_intent' ) )
and $submission->verify_posted_data_hash() ) {
$skip_spam_check = true;
}
return $skip_spam_check;
}
add_action(
'wpcf7_before_send_mail',
'wpcf7_stripe_before_send_mail',
10, 3
);
/**
* Creates Stripe's Payment Intent.
*/
function wpcf7_stripe_before_send_mail( $contact_form, &$abort, $submission ) {
$service = WPCF7_Stripe::get_instance();
if ( ! $service->is_active() ) {
return;
}
$tags = $contact_form->scan_form_tags( array( 'type' => 'stripe' ) );
if ( ! $tags ) {
return;
}
if ( ! empty( $submission->pull( 'payment_intent' ) ) ) {
return;
}
$tag = $tags[0];
$amount = $tag->get_option( 'amount', 'int', true );
$currency = $tag->get_option( 'currency', '[a-zA-Z]{3}', true );
$payment_intent_params = apply_filters(
'wpcf7_stripe_payment_intent_parameters',
array(
'amount' => $amount ? absint( $amount ) : null,
'currency' => $currency ? strtolower( $currency ) : null,
'receipt_email' => $submission->get_posted_data( 'your-email' ),
)
);
$payment_intent = $service->api()->create_payment_intent(
$payment_intent_params
);
if ( $payment_intent ) {
$submission->add_result_props( array(
'stripe' => array(
'payment_intent' => array(
'id' => $payment_intent['id'],
'client_secret' => $payment_intent['client_secret'],
),
),
) );
$submission->set_status( 'payment_required' );
$submission->set_response(
__( "Payment is required. Please pay by credit card.", 'contact-form-7' )
);
}
$abort = true;
}
/**
* Returns payment link URL.
*
* @param string $pi_id Payment Intent ID.
* @return string The URL.
*/
function wpcf7_stripe_get_payment_link( $pi_id ) {
return sprintf(
'https://dashboard.stripe.com/payments/%s',
urlencode( $pi_id )
);
}
add_filter(
'wpcf7_special_mail_tags',
'wpcf7_stripe_smt',
10, 4
);
/**
* Registers the [_stripe_payment_link] special mail-tag.
*/
function wpcf7_stripe_smt( $output, $tag_name, $html, $mail_tag = null ) {
if ( '_stripe_payment_link' === $tag_name ) {
$submission = WPCF7_Submission::get_instance();
$pi_id = $submission->pull( 'payment_intent' );
if ( ! empty( $pi_id ) ) {
$output = wpcf7_stripe_get_payment_link( $pi_id );
}
}
return $output;
}
add_filter(
'wpcf7_flamingo_inbound_message_parameters',
'wpcf7_stripe_add_flamingo_inbound_message_params',
10, 1
);
/**
* Adds Stripe-related meta data to Flamingo Inbound Message parameters.
*/
function wpcf7_stripe_add_flamingo_inbound_message_params( $args ) {
$submission = WPCF7_Submission::get_instance();
$pi_id = $submission->pull( 'payment_intent' );
if ( empty( $pi_id ) ) {
return $args;
}
$pi_link = wpcf7_stripe_get_payment_link( $pi_id );
$meta = (array) $args['meta'];
$meta['stripe_payment_link'] = $pi_link;
$args['meta'] = $meta;
return $args;
}
add_action(
'wpcf7_init',
'wpcf7_add_form_tag_stripe',
10, 0
);
/**
* Registers the stripe form-tag handler.
*/
function wpcf7_add_form_tag_stripe() {
wpcf7_add_form_tag(
'stripe',
'wpcf7_stripe_form_tag_handler',
array(
'display-block' => true,
'singular' => true,
)
);
}
/**
* Defines the stripe form-tag handler.
*
* @return string HTML content that replaces a stripe form-tag.
*/
function wpcf7_stripe_form_tag_handler( $tag ) {
$card_element = sprintf(
'<div %s></div>',
wpcf7_format_atts( array(
'class' => 'card-element wpcf7-form-control',
'aria-invalid' => 'false',
) )
);
$card_element = sprintf(
'<div class="wpcf7-form-control-wrap hidden">%s</div>',
$card_element
);
$button_1_label = __( 'Proceed to checkout', 'contact-form-7' );
if ( isset( $tag->values[0] ) ) {
$button_1_label = trim( $tag->values[0] );
}
$button_1 = sprintf(
'<button %1$s>%2$s</button>',
wpcf7_format_atts( array(
'type' => 'submit',
'class' => 'first',
) ),
esc_html( $button_1_label )
);
$button_2_label = __( 'Complete payment', 'contact-form-7' );
if ( isset( $tag->values[1] ) ) {
$button_2_label = trim( $tag->values[1] );
}
$button_2 = sprintf(
'<button %1$s>%2$s</button>',
wpcf7_format_atts( array(
'type' => 'button',
'class' => 'second hidden',
) ),
esc_html( $button_2_label )
);
$buttons = sprintf(
'<span class="buttons has-spinner">%1$s %2$s</span>',
$button_1, $button_2
);
return sprintf(
'<div class="wpcf7-stripe">%1$s %2$s %3$s</div>',
$card_element,
$buttons,
'<input type="hidden" name="_wpcf7_stripe_payment_intent" value="" />'
);
}

View File

@@ -0,0 +1,18 @@
.wpcf7 .wpcf7-stripe .wpcf7-form-control-wrap {
margin: .6em 0;
}
.wpcf7 .wpcf7-stripe .wpcf7-form-control {
display: block;
background: #f6f7f7;
padding: 12px 12px;
border: 1px solid #787c82;
}
.wpcf7 .wpcf7-stripe button:disabled {
cursor: not-allowed;
}
.wpcf7 .wpcf7-stripe .hidden {
display: none;
}