Add WooCommerce integration for payments, invoices, and order management (v0.11.0)
All checks were successful
Create Release Package / build-release (push) Successful in 1m11s

- Product sync: Virtual WC products for rooms with bidirectional linking
- Cart/Checkout: Booking data in cart items, availability validation, dynamic pricing
- Orders: Automatic booking creation on payment, status mapping, guest record creation
- Invoices: PDF generation via mPDF, auto-attach to emails, configurable numbering
- Refunds: Full refund cancels booking, partial refund records amount only
- Admin: Cross-linked columns and row actions between bookings and orders
- Settings: WooCommerce tab with subtabs (General, Products, Orders, Invoices)
- HPOS compatibility declared for High-Performance Order Storage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 22:40:36 +01:00
parent 965060cc03
commit 2865956c56
15 changed files with 5036 additions and 9 deletions

View File

@@ -21,6 +21,7 @@ use Magdev\WpBnb\Frontend\Shortcodes;
use Magdev\WpBnb\Api\RestApi;
use Magdev\WpBnb\Integration\CF7;
use Magdev\WpBnb\Integration\Prometheus;
use Magdev\WpBnb\Integration\WooCommerce\Manager as WooCommerceManager;
use Magdev\WpBnb\Frontend\Widgets\AvailabilityCalendar;
use Magdev\WpBnb\Frontend\Widgets\BuildingRooms;
use Magdev\WpBnb\Frontend\Widgets\SimilarRooms;
@@ -147,6 +148,11 @@ final class Plugin {
// Initialize Prometheus metrics integration.
Prometheus::init();
// Initialize WooCommerce integration if WooCommerce is active.
if ( class_exists( 'WooCommerce' ) ) {
WooCommerceManager::init();
}
// Initialize REST API.
$this->init_rest_api();
@@ -636,6 +642,10 @@ final class Plugin {
class="nav-tab <?php echo 'api' === $active_tab ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'API', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wp-bnb-settings&tab=woocommerce' ) ); ?>"
class="nav-tab <?php echo 'woocommerce' === $active_tab ? 'nav-tab-active' : ''; ?>">
<?php esc_html_e( 'WooCommerce', 'wp-bnb' ); ?>
</a>
</nav>
<div class="tab-content">
@@ -656,6 +666,9 @@ final class Plugin {
case 'api':
$this->render_api_settings();
break;
case 'woocommerce':
$this->render_woocommerce_settings();
break;
default:
$this->render_general_settings();
break;
@@ -1854,6 +1867,9 @@ final class Plugin {
case 'api':
$this->save_api_settings();
break;
case 'woocommerce':
$this->save_woocommerce_settings();
break;
default:
$this->save_general_settings();
break;
@@ -2037,6 +2053,379 @@ final class Plugin {
settings_errors( 'wp_bnb_settings' );
}
/**
* Render WooCommerce settings tab.
*
* @return void
*/
private function render_woocommerce_settings(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Subtab switching only.
$active_subtab = isset( $_GET['subtab'] ) ? sanitize_key( $_GET['subtab'] ) : 'general';
$base_url = admin_url( 'admin.php?page=wp-bnb-settings&tab=woocommerce' );
$wc_active = class_exists( 'WooCommerce' );
?>
<div class="wp-bnb-subtabs">
<a href="<?php echo esc_url( $base_url . '&subtab=general' ); ?>"
class="wp-bnb-subtab <?php echo 'general' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-admin-generic"></span>
<?php esc_html_e( 'General', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( $base_url . '&subtab=products' ); ?>"
class="wp-bnb-subtab <?php echo 'products' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-products"></span>
<?php esc_html_e( 'Products', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( $base_url . '&subtab=orders' ); ?>"
class="wp-bnb-subtab <?php echo 'orders' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-cart"></span>
<?php esc_html_e( 'Orders', 'wp-bnb' ); ?>
</a>
<a href="<?php echo esc_url( $base_url . '&subtab=invoices' ); ?>"
class="wp-bnb-subtab <?php echo 'invoices' === $active_subtab ? 'active' : ''; ?>">
<span class="dashicons dashicons-media-document"></span>
<?php esc_html_e( 'Invoices', 'wp-bnb' ); ?>
</a>
</div>
<?php if ( ! $wc_active ) : ?>
<div class="notice notice-warning inline">
<p>
<strong><?php esc_html_e( 'WooCommerce is not active.', 'wp-bnb' ); ?></strong>
<?php esc_html_e( 'Please install and activate WooCommerce to use these features.', 'wp-bnb' ); ?>
</p>
</div>
<?php endif; ?>
<?php
switch ( $active_subtab ) {
case 'products':
$this->render_wc_products_subtab( $wc_active );
break;
case 'orders':
$this->render_wc_orders_subtab( $wc_active );
break;
case 'invoices':
$this->render_wc_invoices_subtab( $wc_active );
break;
default:
$this->render_wc_general_subtab( $wc_active );
break;
}
}
/**
* Render WooCommerce General subtab.
*
* @param bool $wc_active Whether WooCommerce is active.
* @return void
*/
private function render_wc_general_subtab( bool $wc_active ): void {
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<input type="hidden" name="wc_subtab" value="general">
<h2><?php esc_html_e( 'WooCommerce Integration', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Enable Integration', 'wp-bnb' ); ?></th>
<td>
<label for="wp_bnb_wc_enabled">
<input type="checkbox" name="wp_bnb_wc_enabled" id="wp_bnb_wc_enabled" value="yes"
<?php checked( 'yes', get_option( WooCommerceManager::OPTION_ENABLED, 'no' ) ); ?>
<?php disabled( ! $wc_active ); ?>>
<?php esc_html_e( 'Enable WooCommerce integration for bookings', 'wp-bnb' ); ?>
</label>
<p class="description">
<?php esc_html_e( 'Allow guests to book and pay through WooCommerce checkout.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Auto-Confirm Bookings', 'wp-bnb' ); ?></th>
<td>
<label for="wp_bnb_wc_auto_confirm_booking">
<input type="checkbox" name="wp_bnb_wc_auto_confirm_booking" id="wp_bnb_wc_auto_confirm_booking" value="yes"
<?php checked( 'yes', get_option( WooCommerceManager::OPTION_AUTO_CONFIRM, 'yes' ) ); ?>
<?php disabled( ! $wc_active ); ?>>
<?php esc_html_e( 'Automatically confirm booking when payment is completed', 'wp-bnb' ); ?>
</label>
<p class="description">
<?php esc_html_e( 'If disabled, bookings will remain pending until manually confirmed.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
</table>
<?php if ( $wc_active ) : ?>
<p class="submit">
<input type="submit" name="submit" class="button button-primary"
value="<?php esc_attr_e( 'Save Settings', 'wp-bnb' ); ?>">
</p>
<?php endif; ?>
</form>
<?php
}
/**
* Render WooCommerce Products subtab.
*
* @param bool $wc_active Whether WooCommerce is active.
* @return void
*/
private function render_wc_products_subtab( bool $wc_active ): void {
$categories = array();
if ( $wc_active ) {
$categories = get_terms(
array(
'taxonomy' => 'product_cat',
'hide_empty' => false,
)
);
}
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<input type="hidden" name="wc_subtab" value="products">
<h2><?php esc_html_e( 'Product Synchronization', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Auto-Sync Products', 'wp-bnb' ); ?></th>
<td>
<label for="wp_bnb_wc_auto_sync_products">
<input type="checkbox" name="wp_bnb_wc_auto_sync_products" id="wp_bnb_wc_auto_sync_products" value="yes"
<?php checked( 'yes', get_option( WooCommerceManager::OPTION_AUTO_SYNC, 'yes' ) ); ?>
<?php disabled( ! $wc_active ); ?>>
<?php esc_html_e( 'Automatically create/update WooCommerce products when rooms are saved', 'wp-bnb' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_wc_product_category"><?php esc_html_e( 'Product Category', 'wp-bnb' ); ?></label>
</th>
<td>
<select name="wp_bnb_wc_product_category" id="wp_bnb_wc_product_category" <?php disabled( ! $wc_active ); ?>>
<option value=""><?php esc_html_e( '— No category —', 'wp-bnb' ); ?></option>
<?php foreach ( $categories as $cat ) : ?>
<option value="<?php echo esc_attr( $cat->term_id ); ?>"
<?php selected( get_option( WooCommerceManager::OPTION_PRODUCT_CATEGORY, 0 ), $cat->term_id ); ?>>
<?php echo esc_html( $cat->name ); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description">
<?php esc_html_e( 'Assign synced room products to this WooCommerce category.', 'wp-bnb' ); ?>
</p>
</td>
</tr>
</table>
<?php if ( $wc_active ) : ?>
<h3><?php esc_html_e( 'Manual Sync', 'wp-bnb' ); ?></h3>
<p>
<button type="button" class="button bnb-sync-rooms-btn">
<span class="dashicons dashicons-update"></span>
<?php esc_html_e( 'Sync All Rooms Now', 'wp-bnb' ); ?>
</button>
<span class="sync-status"></span>
</p>
<p class="description">
<?php esc_html_e( 'Creates or updates WooCommerce products for all published rooms.', 'wp-bnb' ); ?>
</p>
<p class="submit">
<input type="submit" name="submit" class="button button-primary"
value="<?php esc_attr_e( 'Save Settings', 'wp-bnb' ); ?>">
</p>
<?php endif; ?>
</form>
<?php
}
/**
* Render WooCommerce Orders subtab.
*
* @param bool $wc_active Whether WooCommerce is active.
* @return void
*/
private function render_wc_orders_subtab( bool $wc_active ): void {
?>
<h2><?php esc_html_e( 'Order-Booking Status Mapping', 'wp-bnb' ); ?></h2>
<p class="description">
<?php esc_html_e( 'The following table shows how WooCommerce order statuses map to booking statuses.', 'wp-bnb' ); ?>
</p>
<table class="widefat fixed striped" style="max-width: 600px; margin-top: 15px;">
<thead>
<tr>
<th><?php esc_html_e( 'WooCommerce Status', 'wp-bnb' ); ?></th>
<th><?php esc_html_e( 'Booking Status', 'wp-bnb' ); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><?php esc_html_e( 'Pending Payment', 'wp-bnb' ); ?></td>
<td><span class="bnb-status-badge bnb-status-pending"><?php esc_html_e( 'Pending', 'wp-bnb' ); ?></span></td>
</tr>
<tr>
<td><?php esc_html_e( 'On Hold', 'wp-bnb' ); ?></td>
<td><span class="bnb-status-badge bnb-status-pending"><?php esc_html_e( 'Pending', 'wp-bnb' ); ?></span></td>
</tr>
<tr>
<td><?php esc_html_e( 'Processing', 'wp-bnb' ); ?></td>
<td>
<?php if ( get_option( WooCommerceManager::OPTION_AUTO_CONFIRM, 'yes' ) === 'yes' ) : ?>
<span class="bnb-status-badge bnb-status-confirmed"><?php esc_html_e( 'Confirmed', 'wp-bnb' ); ?></span>
<?php else : ?>
<span class="bnb-status-badge bnb-status-pending"><?php esc_html_e( 'Pending', 'wp-bnb' ); ?></span>
<?php endif; ?>
</td>
</tr>
<tr>
<td><?php esc_html_e( 'Completed', 'wp-bnb' ); ?></td>
<td><span class="bnb-status-badge bnb-status-confirmed"><?php esc_html_e( 'Confirmed', 'wp-bnb' ); ?></span></td>
</tr>
<tr>
<td><?php esc_html_e( 'Cancelled / Refunded / Failed', 'wp-bnb' ); ?></td>
<td><span class="bnb-status-badge bnb-status-cancelled"><?php esc_html_e( 'Cancelled', 'wp-bnb' ); ?></span></td>
</tr>
</tbody>
</table>
<h3><?php esc_html_e( 'Refund Handling', 'wp-bnb' ); ?></h3>
<ul style="list-style: disc; margin-left: 20px;">
<li><?php esc_html_e( 'Full refunds automatically cancel the linked booking.', 'wp-bnb' ); ?></li>
<li><?php esc_html_e( 'Partial refunds are recorded but do not cancel the booking.', 'wp-bnb' ); ?></li>
<li><?php esc_html_e( 'Cancellation emails are sent when a booking is cancelled due to refund.', 'wp-bnb' ); ?></li>
</ul>
<?php
}
/**
* Render WooCommerce Invoices subtab.
*
* @param bool $wc_active Whether WooCommerce is active.
* @return void
*/
private function render_wc_invoices_subtab( bool $wc_active ): void {
?>
<form method="post" action="">
<?php wp_nonce_field( 'wp_bnb_save_settings', 'wp_bnb_settings_nonce' ); ?>
<input type="hidden" name="wc_subtab" value="invoices">
<h2><?php esc_html_e( 'Invoice Settings', 'wp-bnb' ); ?></h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><?php esc_html_e( 'Attach to Emails', 'wp-bnb' ); ?></th>
<td>
<label for="wp_bnb_wc_invoice_attach">
<input type="checkbox" name="wp_bnb_wc_invoice_attach" id="wp_bnb_wc_invoice_attach" value="yes"
<?php checked( 'yes', get_option( WooCommerceManager::OPTION_INVOICE_ATTACH, 'yes' ) ); ?>
<?php disabled( ! $wc_active ); ?>>
<?php esc_html_e( 'Attach PDF invoice to order confirmation emails', 'wp-bnb' ); ?>
</label>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_invoice_prefix"><?php esc_html_e( 'Invoice Number Prefix', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="text" name="wp_bnb_invoice_prefix" id="wp_bnb_invoice_prefix"
value="<?php echo esc_attr( get_option( WooCommerceManager::OPTION_INVOICE_PREFIX, 'INV-' ) ); ?>"
class="regular-text" <?php disabled( ! $wc_active ); ?>>
<p class="description">
<?php esc_html_e( 'Prefix for invoice numbers (e.g., INV- for INV-00001).', 'wp-bnb' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_invoice_start_number"><?php esc_html_e( 'Starting Number', 'wp-bnb' ); ?></label>
</th>
<td>
<input type="number" name="wp_bnb_invoice_start_number" id="wp_bnb_invoice_start_number"
value="<?php echo esc_attr( get_option( WooCommerceManager::OPTION_INVOICE_START_NUMBER, 1000 ) ); ?>"
class="small-text" min="1" <?php disabled( ! $wc_active ); ?>>
<p class="description">
<?php esc_html_e( 'Starting number for new invoices (only applies if no invoices generated yet).', 'wp-bnb' ); ?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="wp_bnb_invoice_footer"><?php esc_html_e( 'Footer Text', 'wp-bnb' ); ?></label>
</th>
<td>
<textarea name="wp_bnb_invoice_footer" id="wp_bnb_invoice_footer" rows="3" class="large-text"
<?php disabled( ! $wc_active ); ?>><?php echo esc_textarea( get_option( WooCommerceManager::OPTION_INVOICE_FOOTER, '' ) ); ?></textarea>
<p class="description">
<?php esc_html_e( 'Custom footer text for invoices (e.g., terms and conditions).', 'wp-bnb' ); ?>
</p>
</td>
</tr>
</table>
<?php if ( $wc_active ) : ?>
<p class="submit">
<input type="submit" name="submit" class="button button-primary"
value="<?php esc_attr_e( 'Save Settings', 'wp-bnb' ); ?>">
</p>
<?php endif; ?>
</form>
<?php
}
/**
* Save WooCommerce settings.
*
* @return void
*/
private function save_woocommerce_settings(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in render_settings_page.
$subtab = isset( $_POST['wc_subtab'] ) ? sanitize_key( $_POST['wc_subtab'] ) : 'general';
switch ( $subtab ) {
case 'products':
$auto_sync = isset( $_POST['wp_bnb_wc_auto_sync_products'] ) ? 'yes' : 'no';
$category = isset( $_POST['wp_bnb_wc_product_category'] ) ? absint( $_POST['wp_bnb_wc_product_category'] ) : 0;
update_option( WooCommerceManager::OPTION_AUTO_SYNC, $auto_sync );
update_option( WooCommerceManager::OPTION_PRODUCT_CATEGORY, $category );
break;
case 'invoices':
$attach = isset( $_POST['wp_bnb_wc_invoice_attach'] ) ? 'yes' : 'no';
$prefix = isset( $_POST['wp_bnb_invoice_prefix'] ) ? sanitize_text_field( wp_unslash( $_POST['wp_bnb_invoice_prefix'] ) ) : 'INV-';
$start_number = isset( $_POST['wp_bnb_invoice_start_number'] ) ? absint( $_POST['wp_bnb_invoice_start_number'] ) : 1000;
$footer = isset( $_POST['wp_bnb_invoice_footer'] ) ? sanitize_textarea_field( wp_unslash( $_POST['wp_bnb_invoice_footer'] ) ) : '';
update_option( WooCommerceManager::OPTION_INVOICE_ATTACH, $attach );
update_option( WooCommerceManager::OPTION_INVOICE_PREFIX, $prefix );
update_option( WooCommerceManager::OPTION_INVOICE_START_NUMBER, $start_number );
update_option( WooCommerceManager::OPTION_INVOICE_FOOTER, $footer );
break;
default: // general
$enabled = isset( $_POST['wp_bnb_wc_enabled'] ) ? 'yes' : 'no';
$auto_confirm = isset( $_POST['wp_bnb_wc_auto_confirm_booking'] ) ? 'yes' : 'no';
update_option( WooCommerceManager::OPTION_ENABLED, $enabled );
update_option( WooCommerceManager::OPTION_AUTO_CONFIRM, $auto_confirm );
break;
}
add_settings_error( 'wp_bnb_settings', 'settings_saved', __( 'WooCommerce settings saved.', 'wp-bnb' ), 'success' );
settings_errors( 'wp_bnb_settings' );
}
/**
* AJAX handler for checking room availability.
*