commit 8fd8d9708c4200b894356877df605b6db1f1663b Author: Ruben Ramirez Date: Mon Jun 30 21:56:49 2025 -0400 Initial commit: Complete Event Tickets Mail-In Payment Gateway - Complete mail-in payment gateway for Event Tickets Commerce - Support for check payments with collapsible checkout interface - Admin interface for managing check payments and order status - Professional asset organization with proper WordPress enqueuing - Order management with status tracking (pending, received, bounced) - Template system with responsive design and accessibility features - Integration with Event Tickets Commerce order system - Settings page for configuring payment instructions and addresses diff --git a/README.md b/README.md new file mode 100644 index 0000000..11c410c --- /dev/null +++ b/README.md @@ -0,0 +1,221 @@ +# Event Tickets - Mail-In Payment Gateway + +A complementary WordPress plugin that adds a mail-in payment gateway for Event Tickets Commerce, allowing customers to pay by mailing checks. + +## Description + +This plugin extends the Event Tickets plugin by adding a "Mail-In Payment (Check)" gateway that allows customers to reserve tickets and pay by mailing a check. It provides comprehensive order management features for administrators to track and process check payments. + +## Features + +- **Check Payment Gateway**: Seamlessly integrates with Event Tickets Commerce +- **Order Management**: Admin interface to track pending check payments +- **Email Notifications**: Automated emails for payment instructions, confirmations, and cancellations +- **Customizable Settings**: Configure mailing address, payment instructions, and reservation periods +- **Order Tracking**: Track check received status, bounce handling, and order notes +- **Auto-Cancellation**: Optional automatic cancellation of expired orders +- **Template System**: Customizable checkout and email templates + +## Requirements + +- WordPress 6.6 or higher +- PHP 7.4 or higher +- Event Tickets plugin (with Commerce module) +- The Events Calendar plugin (recommended) + +## Installation + +1. Download the plugin files +2. Upload the `event-tickets-mail-in-payment` folder to `/wp-content/plugins/` +3. Activate the plugin through the 'Plugins' menu in WordPress +4. Configure the gateway settings under **Tickets > Settings > Payments** + +## Configuration + +### Basic Setup + +1. Navigate to **Tickets > Settings > Payments** +2. Find the "Mail-In Payment (Check) Settings" section +3. Configure the required settings: + - **Enable Mail-In Payments**: Check to activate the gateway + - **Make Check Payable To**: Organization or person name for checks + - **Mailing Address**: Complete address where checks should be sent + - **Payment Instructions**: Custom instructions for customers + +### Advanced Settings + +- **Payment Method Title**: Customize the gateway name shown to customers +- **Payment Method Description**: Brief description shown during checkout +- **Reservation Period**: Number of days to hold tickets while awaiting payment +- **Auto-Cancel Expired Orders**: Automatically cancel orders after reservation period + +## Order Management + +### Admin Interface + +Access mail-in payment management through: +- **Tickets > Orders > Mail-In Payments** - Dedicated management page +- Individual order edit screens include mail-in payment details + +### Order Processing + +1. **Pending Orders**: Orders awaiting check payment appear in the management interface +2. **Mark as Received**: When a check arrives, mark it as received with optional notes +3. **Mark as Bounced**: Handle returned/bounced checks with reason tracking +4. **Automatic Emails**: System sends confirmation emails when status changes + +### Order Statuses + +- **Pending/Approved**: Order created, awaiting check payment +- **Completed**: Check received and processed +- **Denied**: Check bounced or order manually cancelled + +## Templates + +### Customization + +Templates can be overridden by copying them to your theme: + +``` +wp-content/themes/[your-theme]/event-tickets-mail-in/ +├── checkout/ +│ └── mail-in-form.php +├── emails/ +│ ├── payment-instructions.php +│ ├── payment-confirmation.php +│ └── order-cancelled.php +└── admin/ + ├── mail-in-orders.php + └── order-meta-box.php +``` + +### Available Templates + +- **Checkout Form**: `checkout/mail-in-form.php` - Payment method display during checkout +- **Email Templates**: Complete HTML email templates for all notifications +- **Admin Templates**: Management interface templates + +## Hooks and Filters + +### Actions + +```php +// Triggered when a check is marked as received +do_action( 'et_mail_in_check_received', $order_id, $notes ); + +// Triggered when a check is marked as bounced +do_action( 'et_mail_in_check_bounced', $order_id, $reason ); + +// Daily cleanup of expired orders +do_action( 'et_mail_in_cleanup_expired_orders' ); +``` + +### Filters + +```php +// Customize payment instructions +add_filter( 'et_mail_in_payment_instructions', function( $instructions, $order ) { + return $instructions; +}, 10, 2 ); + +// Modify email content +add_filter( 'et_mail_in_email_content', function( $content, $template, $order ) { + return $content; +}, 10, 3 ); + +// Customize reservation period per order +add_filter( 'et_mail_in_reservation_days', function( $days, $order ) { + return $days; +}, 10, 2 ); +``` + +## Email System + +### Automated Emails + +1. **Payment Instructions**: Sent when order is created +2. **Payment Confirmation**: Sent when check is marked as received +3. **Order Cancellation**: Sent when order is cancelled or expires + +### Email Customization + +- HTML templates with responsive design +- Includes order details, payment instructions, and mailing address +- Uses WordPress mail system with customizable headers + +## Security Features + +- **Nonce Verification**: All admin actions are protected with WordPress nonces +- **Capability Checks**: Admin functions require appropriate user permissions +- **Data Sanitization**: All user inputs are properly sanitized +- **Order Validation**: Prevents unauthorized access to order data + +## Development + +### File Structure + +``` +event-tickets-mail-in-payment/ +├── event-tickets-mail-in-payment.php # Main plugin file +├── src/ # Core classes +│ ├── Gateway.php # Payment gateway implementation +│ ├── Order.php # Order management +│ ├── Settings.php # Configuration handling +│ ├── Provider.php # Service provider +│ └── Hooks.php # WordPress hooks +├── templates/ # Template files +│ ├── checkout/ # Frontend templates +│ ├── emails/ # Email templates +│ └── admin/ # Admin interface templates +└── README.md # Documentation +``` + +### Extending the Plugin + +The plugin is built with extensibility in mind: + +- PSR-4 autoloading with namespace `ET_Mail_In_Payment` +- Service provider pattern for modular functionality +- Hook system for customization +- Template override system + +## Troubleshooting + +### Common Issues + +1. **Gateway Not Appearing**: Ensure Event Tickets plugin is active and Commerce module is enabled +2. **Settings Not Saving**: Check file permissions and WordPress error logs +3. **Emails Not Sending**: Verify WordPress mail configuration +4. **Template Issues**: Clear any caching plugins after template changes + +### Debug Mode + +Enable WordPress debug mode to see detailed error messages: + +```php +define( 'WP_DEBUG', true ); +define( 'WP_DEBUG_LOG', true ); +``` + +## Support + +For support and questions: + +1. Check the plugin settings and this documentation +2. Review WordPress error logs for any issues +3. Ensure all requirements are met +4. Contact the plugin developer for technical support + +## License + +This plugin is licensed under the GPL v2 or later. + +## Changelog + +### 1.0.0 +- Initial release +- Mail-in payment gateway implementation +- Order management interface +- Email notification system +- Template system +- Settings configuration \ No newline at end of file diff --git a/assets/css/admin.css b/assets/css/admin.css new file mode 100644 index 0000000..c406717 --- /dev/null +++ b/assets/css/admin.css @@ -0,0 +1,122 @@ +/** + * Mail-In Payment Gateway Admin Styles + * @since 1.0.0 + */ + +.et-mail-in-admin-page { + max-width: 1200px; + margin: 20px 0; +} + +.et-mail-in-orders-table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} + +.et-mail-in-orders-table th, +.et-mail-in-orders-table td { + padding: 12px; + text-align: left; + border-bottom: 1px solid #ddd; +} + +.et-mail-in-orders-table th { + background-color: #f1f1f1; + font-weight: bold; +} + +.et-mail-in-status { + padding: 4px 8px; + border-radius: 3px; + font-size: 12px; + font-weight: bold; + text-transform: uppercase; +} + +.et-mail-in-status--pending { + background-color: #fff3cd; + color: #856404; + border: 1px solid #ffeaa7; +} + +.et-mail-in-status--received { + background-color: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; +} + +.et-mail-in-status--bounced { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; +} + +.et-mail-in-action-buttons { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.et-mail-in-action-button { + padding: 6px 12px; + border: none; + border-radius: 3px; + cursor: pointer; + font-size: 12px; + text-decoration: none; + display: inline-block; + transition: background-color 0.2s; +} + +.et-mail-in-action-button--primary { + background-color: #0073aa; + color: white; +} + +.et-mail-in-action-button--primary:hover { + background-color: #005a87; +} + +.et-mail-in-action-button--danger { + background-color: #dc3545; + color: white; +} + +.et-mail-in-action-button--danger:hover { + background-color: #c82333; +} + +.et-mail-in-meta-box { + background: #fff; + border: 1px solid #ccd0d4; + border-radius: 4px; + padding: 20px; + margin-bottom: 20px; +} + +.et-mail-in-meta-row { + display: flex; + margin-bottom: 12px; + align-items: center; +} + +.et-mail-in-meta-label { + font-weight: bold; + min-width: 150px; + color: #23282d; +} + +.et-mail-in-meta-value { + flex: 1; + color: #32373c; +} + +.et-mail-in-address-block { + background: #f9f9f9; + padding: 10px; + border-left: 3px solid #0073aa; + font-family: monospace; + white-space: pre-line; + border-radius: 0 3px 3px 0; +} \ No newline at end of file diff --git a/assets/css/checkout.css b/assets/css/checkout.css new file mode 100644 index 0000000..af0ea91 --- /dev/null +++ b/assets/css/checkout.css @@ -0,0 +1,192 @@ +/** + * Mail-In Payment Gateway Checkout Styles + * @since 1.0.0 + */ + +.tribe-tickets-commerce-gateway-mail-in { + margin: 30px 0 15px 0 !important; /* More space above */ + border-radius: 4px; + border: 1px solid #ddd !important; + overflow: hidden; +} + +/* Toggle Button Styling */ +.tribe-tickets-commerce-gateway-mail-in__toggle { + background: #f8f9fa; + border-bottom: 1px solid #dee2e6; +} + +.tribe-tickets-commerce-gateway-mail-in__toggle-button { + width: 100%; + padding: 18px 20px; /* Slightly more padding for larger appearance */ + background: none; + border: none; + text-align: center; /* Center the text */ + cursor: pointer; + font-size: 18px; /* Larger font size */ + font-weight: 500; /* Slightly bolder */ + color: #0073aa; + display: flex; + align-items: center; + justify-content: center; /* Center the content */ + gap: 12px; /* Slightly more gap */ + transition: background-color 0.2s; +} + +.tribe-tickets-commerce-gateway-mail-in__toggle-button:hover { + background: #e9ecef; +} + +.tribe-tickets-commerce-gateway-mail-in__toggle-button:focus { + outline: 2px solid #0073aa; + outline-offset: -2px; +} + +.toggle-icon { + font-size: 12px; + transition: transform 0.2s; + width: 12px; + display: inline-block; +} + +/* Content Styling */ +.tribe-tickets-commerce-gateway-mail-in__content { + padding: 20px; + background: #fff; +} + +.tribe-tickets-commerce-gateway-mail-in__header h4 { + margin: 0 0 10px 0; + color: #333; +} + +.tribe-tickets-commerce-gateway-mail-in__description { + margin: 0 0 20px 0; + font-style: italic; + color: #666; +} + +.tribe-tickets-commerce-gateway-mail-in__instructions { + background: #fff; + padding: 15px; + border-radius: 4px; + border: 1px solid #e5e5e5; +} + +.tribe-tickets-commerce-gateway-mail-in__instructions strong { + color: #333; + font-size: 14px; +} + +.tribe-tickets-commerce-gateway-mail-in__details { + margin: 15px 0; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; +} + +@media (max-width: 768px) { + .tribe-tickets-commerce-gateway-mail-in__details { + grid-template-columns: 1fr; + } +} + +.tribe-tickets-commerce-gateway-mail-in__payable-to-name { + font-size: 16px; + font-weight: bold; + margin: 5px 0; + color: #0073aa; +} + +.tribe-tickets-commerce-gateway-mail-in__address-block { + background: #f5f5f5; + padding: 10px; + border-radius: 4px; + border-left: 3px solid #0073aa; + line-height: 1.6; + margin-top: 5px; +} + +.tribe-tickets-commerce-gateway-mail-in__important-note { + background: #fff3cd; + border: 1px solid #ffeaa7; + padding: 15px; + border-radius: 4px; + margin-top: 15px; +} + +.tribe-tickets-commerce-gateway-mail-in__important-note strong { + color: #856404; +} + +.tribe-tickets-commerce-gateway-mail-in__important-note ul { + margin: 10px 0 0 0; + padding-left: 20px; +} + +.tribe-tickets-commerce-gateway-mail-in__important-note li { + margin-bottom: 5px; + color: #856404; +} + +/* Form styling */ +.tribe-tickets__commerce-checkout-purchaser-info { + background: #f8f9fa; + padding: 20px; + border-radius: 4px; + border: 1px solid #dee2e6; + margin: 20px 0; +} + +.tribe-tickets__commerce-checkout-purchaser-info h4 { + margin: 0 0 15px 0; + color: #333; +} + +.tribe-tickets__commerce-checkout-form-field { + margin-bottom: 15px; +} + +.tribe-tickets__commerce-checkout-form-field label { + display: block; + margin-bottom: 5px; + font-weight: bold; + color: #333; +} + +.tribe-tickets__commerce-checkout-form-field .required { + color: #dc3545; +} + +.tribe-tickets__commerce-checkout-form-field input { + width: 100%; + padding: 8px 12px; + border: 1px solid #ced4da; + border-radius: 4px; + font-size: 14px; +} + +.tribe-tickets__commerce-checkout-form-field input:focus { + outline: none; + border-color: #0073aa; + box-shadow: 0 0 0 2px rgba(0, 115, 170, 0.2); +} + +.tribe-tickets__commerce-checkout-form-submit { + margin-top: 20px; +} + +.tribe-tickets__commerce-checkout-form-submit-button { + background: #0073aa; + color: white; + border: none; + padding: 12px 24px; + border-radius: 4px; + font-size: 16px; + cursor: pointer; + transition: background-color 0.2s; +} + +.tribe-tickets__commerce-checkout-form-submit-button:hover { + background: #005a87; +} \ No newline at end of file diff --git a/assets/js/admin.js b/assets/js/admin.js new file mode 100644 index 0000000..2ce6d04 --- /dev/null +++ b/assets/js/admin.js @@ -0,0 +1,63 @@ +/** + * Mail-In Payment Gateway Admin JavaScript + * @since 1.0.0 + */ + +(function($) { + 'use strict'; + + $(document).ready(function() { + // Confirmation dialogs for admin actions + $('.et-mail-in-mark-received').on('click', function(e) { + const confirmMessage = etMailInAdmin.strings.confirm_received || 'Are you sure you want to mark this check as received?'; + if (!confirm(confirmMessage)) { + e.preventDefault(); + return false; + } + }); + + $('.et-mail-in-mark-bounced').on('click', function(e) { + const confirmMessage = etMailInAdmin.strings.confirm_bounced || 'Are you sure you want to mark this check as bounced?'; + if (!confirm(confirmMessage)) { + e.preventDefault(); + return false; + } + }); + + // Auto-hide admin notices after 5 seconds + setTimeout(function() { + $('.notice.is-dismissible').fadeOut(); + }, 5000); + + // Enhanced form validation + $('form[data-et-mail-in-form]').on('submit', function(e) { + const form = $(this); + const requiredFields = form.find('[required]'); + let hasErrors = false; + + requiredFields.each(function() { + const field = $(this); + const value = field.val().trim(); + + if (!value) { + field.addClass('error'); + hasErrors = true; + } else { + field.removeClass('error'); + } + }); + + if (hasErrors) { + e.preventDefault(); + alert('Please fill in all required fields.'); + return false; + } + }); + + // Remove error styling on input + $('input, textarea, select').on('input change', function() { + $(this).removeClass('error'); + }); + }); + +})(jQuery); \ No newline at end of file diff --git a/assets/js/checkout.js b/assets/js/checkout.js new file mode 100644 index 0000000..17f8983 --- /dev/null +++ b/assets/js/checkout.js @@ -0,0 +1,46 @@ +/** + * Mail-In Payment Gateway Checkout JavaScript + * @since 1.0.0 + */ + +document.addEventListener('DOMContentLoaded', function() { + const toggleButton = document.getElementById('mail-in-toggle-button'); + const content = document.getElementById('mail-in-content'); + const toggleIcon = toggleButton ? toggleButton.querySelector('.toggle-icon') : null; + + // Only initialize if elements exist + if (!toggleButton || !content || !toggleIcon) { + return; + } + + // Initialize accessibility attributes + toggleButton.setAttribute('aria-expanded', 'false'); + toggleButton.setAttribute('aria-controls', 'mail-in-content'); + content.setAttribute('aria-hidden', 'true'); + + toggleButton.addEventListener('click', function() { + const isExpanded = content.style.display !== 'none'; + + if (isExpanded) { + // Hide content + content.style.display = 'none'; + toggleIcon.textContent = '▶'; + toggleButton.setAttribute('aria-expanded', 'false'); + content.setAttribute('aria-hidden', 'true'); + } else { + // Show content + content.style.display = 'block'; + toggleIcon.textContent = '▼'; + toggleButton.setAttribute('aria-expanded', 'true'); + content.setAttribute('aria-hidden', 'false'); + } + }); + + // Handle keyboard navigation + toggleButton.addEventListener('keydown', function(e) { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggleButton.click(); + } + }); +}); \ No newline at end of file diff --git a/event-tickets-mail-in-payment.php b/event-tickets-mail-in-payment.php new file mode 100644 index 0000000..27500bd --- /dev/null +++ b/event-tickets-mail-in-payment.php @@ -0,0 +1,185 @@ +is_event_tickets_active() ) { + add_action( 'admin_notices', [ $this, 'event_tickets_missing_notice' ] ); + return; + } + + // Load text domain + load_plugin_textdomain( 'event-tickets-mail-in', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' ); + + // Include required files + $this->includes(); + + // Initialize components + $this->init_components(); + } + + /** + * Check if Event Tickets is active + * + * @return bool + */ + private function is_event_tickets_active() { + return class_exists( 'Tribe__Tickets__Main' ) && + class_exists( 'TEC\Tickets\Commerce\Module' ); + } + + /** + * Include required files + */ + private function includes() { + require_once ET_MAIL_IN_PATH . 'src/Gateway.php'; + require_once ET_MAIL_IN_PATH . 'src/Order.php'; + require_once ET_MAIL_IN_PATH . 'src/Settings.php'; + require_once ET_MAIL_IN_PATH . 'src/Provider.php'; + require_once ET_MAIL_IN_PATH . 'src/Hooks.php'; + } + + /** + * Initialize components + */ + private function init_components() { + // Register our provider with dependency injection container + tribe_register_provider( ET_Mail_In_Payment\Provider::class ); + } + + /** + * Plugin activation + */ + public function activate() { + // Check dependencies + if ( ! $this->is_event_tickets_active() ) { + deactivate_plugins( plugin_basename( __FILE__ ) ); + wp_die( + __( 'Event Tickets - Mail-In Payment Gateway requires Event Tickets to be installed and activated.', 'event-tickets-mail-in' ), + __( 'Plugin Activation Error', 'event-tickets-mail-in' ), + [ 'back_link' => true ] + ); + } + + // Set default options if they don't exist + $this->set_default_options(); + } + + /** + * Plugin deactivation + */ + public function deactivate() { + // Clean up if needed + } + + /** + * Get default payment instructions + * + * @return string + */ + private function get_default_instructions() { + return __( 'Please mail your check to the address provided below. Your tickets will be reserved for 7 days while we wait for your payment to arrive. Include your order number on the check memo line.', 'event-tickets-mail-in' ); + } + + /** + * Set default options on activation + */ + private function set_default_options() { + // Set default options if they don't exist + if ( false === tribe_get_option( 'tickets-commerce-mail-in-enabled' ) ) { + tribe_update_option( 'tickets-commerce-mail-in-enabled', true ); + } + + if ( empty( tribe_get_option( 'tickets-commerce-mail-in-title' ) ) ) { + tribe_update_option( 'tickets-commerce-mail-in-title', __( 'Mail-In Payment (Check)', 'event-tickets-mail-in' ) ); + } + + if ( empty( tribe_get_option( 'tickets-commerce-mail-in-description' ) ) ) { + tribe_update_option( 'tickets-commerce-mail-in-description', __( 'Pay by mailing a check to our address.', 'event-tickets-mail-in' ) ); + } + + if ( empty( tribe_get_option( 'tickets-commerce-mail-in-instructions' ) ) ) { + tribe_update_option( 'tickets-commerce-mail-in-instructions', $this->get_default_instructions() ); + } + + // For testing purposes, set some default values + if ( empty( tribe_get_option( 'tickets-commerce-mail-in-address' ) ) ) { + tribe_update_option( 'tickets-commerce-mail-in-address', "123 Main Street\nAnytown, ST 12345\nUSA" ); + } + + if ( empty( tribe_get_option( 'tickets-commerce-mail-in-payable-to' ) ) ) { + tribe_update_option( 'tickets-commerce-mail-in-payable-to', "Your Organization Name" ); + } + } + + /** + * Display admin notice when Event Tickets is missing + */ + public function event_tickets_missing_notice() { + echo '

'; + echo esc_html__( 'Event Tickets - Mail-In Payment Gateway requires Event Tickets to be installed and activated.', 'event-tickets-mail-in' ); + echo '

'; + } +} + +// Initialize the plugin +ET_Mail_In_Payment::instance(); \ No newline at end of file diff --git a/src/Gateway.php b/src/Gateway.php new file mode 100644 index 0000000..63544ea --- /dev/null +++ b/src/Gateway.php @@ -0,0 +1,546 @@ +'; + echo '

🔵 MAIL-IN CHECKOUT FORM 🔵

'; + echo '

Label: ' . esc_html( static::get_label() ) . '

'; + echo '

Description: ' . esc_html( static::get_description() ) . '

'; + echo '

Payable To: ' . esc_html( $payable_to ) . '

'; + echo '

Address: ' . esc_html( $mailing_address ) . '

'; + echo ''; + } + + /** + * Process the payment + * + * @since 1.0.0 + * @param array $order_data Order data + * @return array Processing result + */ + public function process_payment( $order_data ) { + // For mail-in payments, we create the order in "pending" status + // and provide payment instructions + try { + // Get cart instance + $cart = tribe( \TEC\Tickets\Commerce\Cart::class ); + + if ( ! $cart->has_items() ) { + return [ + 'success' => false, + 'message' => __( 'Your cart is empty.', 'event-tickets-mail-in' ), + ]; + } + + // Extract purchaser data from order_data + $purchaser = null; + if ( isset( $order_data['purchaser_name'] ) || isset( $order_data['purchaser_email'] ) ) { + $purchaser = [ + 'purchaser_full_name' => $order_data['purchaser_name'] ?? '', + 'purchaser_first_name' => $order_data['purchaser_first_name'] ?? '', + 'purchaser_last_name' => $order_data['purchaser_last_name'] ?? '', + 'purchaser_email' => $order_data['purchaser_email'] ?? '', + 'purchaser_user_id' => $order_data['purchaser_user_id'] ?? null, + ]; + } + + // Create order from current cart using the main Order class (like other gateways) + $order = tribe( \TEC\Tickets\Commerce\Order::class )->create_from_cart( $this, $purchaser ); + + if ( ! $order ) { + return [ + 'success' => false, + 'message' => __( 'Failed to create order. Please try again.', 'event-tickets-mail-in' ), + ]; + } + + // Set gateway order ID so success page can find the order + update_post_meta( $order->ID, '_tec_tc_order_gateway_order_id', $order->ID ); + + // Add mail-in specific metadata + $this->add_mail_in_meta( $order ); + + // Set order to pending status (waiting for check to arrive) + tribe( \TEC\Tickets\Commerce\Order::class )->modify_status( $order->ID, Status\Pending::SLUG ); + + // Clear the cart + $cart->clear_cart(); + + return [ + 'success' => true, + 'order_id' => $order->ID, + 'redirect_url' => $this->get_success_url( $order ), + 'message' => __( 'Order created successfully. Please mail your check as instructed.', 'event-tickets-mail-in' ), + ]; + + } catch ( \Exception $e ) { + return [ + 'success' => false, + 'message' => sprintf( + __( 'Payment processing failed: %s', 'event-tickets-mail-in' ), + $e->getMessage() + ), + ]; + } + } + + /** + * Create order for mail-in payment + * + * @since 1.0.0 + * @param array $order_data Order data (contains purchaser info) + * @return \WP_Post|false + */ + protected function create_order( $order_data ) { + // Extract purchaser data from order_data + $purchaser = null; + if ( isset( $order_data['purchaser_name'] ) || isset( $order_data['purchaser_email'] ) ) { + $purchaser = [ + 'purchaser_full_name' => $order_data['purchaser_name'] ?? '', + 'purchaser_first_name' => $order_data['purchaser_first_name'] ?? '', + 'purchaser_last_name' => $order_data['purchaser_last_name'] ?? '', + 'purchaser_email' => $order_data['purchaser_email'] ?? '', + 'purchaser_user_id' => $order_data['purchaser_user_id'] ?? null, + ]; + } + + // Use the main Order class like other gateways + $order = tribe( \TEC\Tickets\Commerce\Order::class )->create_from_cart( $this, $purchaser ); + + if ( $order ) { + $this->add_mail_in_meta( $order ); + } + + return $order; + } + + /** + * Add mail-in payment specific metadata + * + * @since 1.0.0 + * @param \WP_Post $order + * @return void + */ + protected function add_mail_in_meta( $order ) { + $order_id = $order->ID; + + // Store gateway identifier + update_post_meta( $order_id, '_tec_tc_gateway', 'mail-in' ); + + // Store payment method + update_post_meta( $order_id, '_tec_tc_payment_method', 'mail-in-check' ); + + // Store mailing instructions + $instructions = tribe_get_option( 'tickets-commerce-mail-in-instructions', '' ); + if ( $instructions ) { + update_post_meta( $order_id, '_mail_in_instructions', $instructions ); + } + + // Store mailing address + $address = tribe_get_option( 'tickets-commerce-mail-in-address', '' ); + if ( $address ) { + update_post_meta( $order_id, '_mail_in_mailing_address', $address ); + } + + // Store payable to + $payable_to = tribe_get_option( 'tickets-commerce-mail-in-payable-to', '' ); + if ( $payable_to ) { + update_post_meta( $order_id, '_mail_in_payable_to', $payable_to ); + } + + // Set check received flag to false initially + update_post_meta( $order_id, '_mail_in_check_received', false ); + + // Store creation date for tracking purposes + update_post_meta( $order_id, '_mail_in_created_date', current_time( 'mysql' ) ); + + // Add order note with payment instructions + $mailing_address = tribe_get_option( 'tickets-commerce-mail-in-address', '' ); + $payable_to_name = tribe_get_option( 'tickets-commerce-mail-in-payable-to', '' ); + + $note = __( 'Order created for mail-in payment. Customer should mail check to:', 'event-tickets-mail-in' ) . "\n"; + $note .= $mailing_address . "\n"; + $note .= __( 'Make payable to:', 'event-tickets-mail-in' ) . ' ' . $payable_to_name; + + $this->add_order_note( $order_id, $note ); + } + + /** + * Add order note + * + * @since 1.0.0 + * @param int $order_id Order ID + * @param string $note Note content + * @return void + */ + protected function add_order_note( $order_id, $note ) { + $existing_notes = get_post_meta( $order_id, '_tec_tc_order_notes', true ); + if ( ! is_array( $existing_notes ) ) { + $existing_notes = []; + } + + $existing_notes[] = [ + 'date' => current_time( 'mysql' ), + 'note' => $note, + 'type' => 'system', + ]; + + update_post_meta( $order_id, '_tec_tc_order_notes', $existing_notes ); + } + + /** + * Generate unique order ID for mail-in payments + * + * @since 1.0.0 + * @return string + */ + protected function generate_order_id() { + return 'mail-in-' . uniqid() . '-' . time(); + } + + /** + * Get success page URL + * + * @since 1.0.0 + * @param \WP_Post $order + * @return string + */ + protected function get_success_url( $order ) { + return add_query_arg( [ 'tc-order-id' => $order->ID ], tribe( \TEC\Tickets\Commerce\Success::class )->get_url() ); + } + + /** + * Handle refunds (not applicable for mail-in payments) + * + * @since 1.0.0 + * @param \TEC\Tickets\Commerce\Order $order + * @param Value $amount + * @return array + */ + public function process_refund( $order, Value $amount ) { + return [ + 'success' => false, + 'message' => __( 'Refunds for mail-in payments must be processed manually.', 'event-tickets-mail-in' ), + ]; + } + + /** + * Get checkout template object + * + * @since 1.0.0 + * @return \Tribe__Template + */ + protected function get_template() { + return new \Tribe__Template(); + } + + /** + * Validate checkout data + * + * @since 1.0.0 + * @param array $data Checkout data + * @return bool|\WP_Error + */ + public function validate_checkout_data( $data ) { + // Basic validation - ensure required fields are present + if ( empty( $data['purchaser_email'] ) ) { + return new \WP_Error( 'missing_email', __( 'Email address is required.', 'event-tickets-mail-in' ) ); + } + + if ( empty( $data['purchaser_name'] ) ) { + return new \WP_Error( 'missing_name', __( 'Name is required.', 'event-tickets-mail-in' ) ); + } + + return true; + } + + /** + * Get gateway-specific order notes + * + * @since 1.0.0 + * @param \TEC\Tickets\Commerce\Order $order + * @return array + */ + public function get_order_notes( $order ) { + $notes = []; + + $notes[] = sprintf( + __( 'Mail-in payment order created. Order ID: %s', 'event-tickets-mail-in' ), + $order->get_gateway_order_id() + ); + + $mailing_address = get_option( 'et_mail_in_mailing_address', '' ); + $payable_to = get_option( 'et_mail_in_check_payable_to', '' ); + + if ( $mailing_address ) { + $notes[] = sprintf( + __( 'Check should be mailed to: %s', 'event-tickets-mail-in' ), + $mailing_address + ); + } + + if ( $payable_to ) { + $notes[] = sprintf( + __( 'Check should be made payable to: %s', 'event-tickets-mail-in' ), + $payable_to + ); + } + + return $notes; + } + + /** + * Get admin notices for the gateway + * + * @since 1.0.0 + * @return array + */ + public function get_admin_notices() { + $notices = []; + + // Check if gateway is properly configured + if ( ! self::is_connected() ) { + $notices[] = [ + 'type' => 'warning', + 'message' => sprintf( + __( 'Mail-In Payment gateway is not properly configured. Please configure the required settings.', 'event-tickets-mail-in' ), + self::get_settings_url() + ), + ]; + } + + return $notices; + } + + /** + * Get order details link by order + * + * @since 1.0.0 + * @param \WP_Post $order Order post object + * @return string + */ + public function get_order_details_link_by_order( $order ): string { + // For mail-in payments, return the order ID as a simple string + return (string) $order->ID; + } + + /** + * Render checkout template + * + * @since 1.0.0 + * @param \Tribe__Template $template Template instance + * @return string + */ + public function render_checkout_template( \Tribe__Template $template ): string { + $instructions = tribe_get_option( 'tickets-commerce-mail-in-instructions', '' ); + $mailing_address = tribe_get_option( 'tickets-commerce-mail-in-address', '' ); + $payable_to = tribe_get_option( 'tickets-commerce-mail-in-payable-to', '' ); + + $context = [ + 'gateway' => $this, + 'instructions' => $instructions, + 'mailing_address' => $mailing_address, + 'payable_to' => $payable_to, + ]; + + // Use our template file + ob_start(); + $template_file = ET_MAIL_IN_PATH . 'templates/v2/commerce/gateway/mail-in/container.php'; + + if ( file_exists( $template_file ) ) { + // Extract context variables for the template + extract( $context ); + include $template_file; + } else { + // Fallback display if template file is missing + echo '
'; + echo '

' . esc_html( static::get_label() ) . '

'; + echo '

' . esc_html( static::get_description() ) . '

'; + if ( $mailing_address ) { + echo '

' . esc_html__( 'Mail your check to:', 'event-tickets-mail-in' ) . '
'; + echo esc_html( $mailing_address ) . '

'; + } + if ( $payable_to ) { + echo '

' . esc_html__( 'Make check payable to:', 'event-tickets-mail-in' ) . '
'; + echo esc_html( $payable_to ) . '

'; + } + echo '
'; + } + + $output = ob_get_clean(); + + // Output directly to handle any buffering issues with the shortcode + echo $output; + + return $output; + } + +} \ No newline at end of file diff --git a/src/Hooks.php b/src/Hooks.php new file mode 100644 index 0000000..87730b7 --- /dev/null +++ b/src/Hooks.php @@ -0,0 +1,405 @@ +get_gateway() !== 'mail-in' ) { + return; + } + + $this->send_instructions_email( $order ); + } + + /** + * Handle order status changes + * + * @since 1.0.0 + * @param \TEC\Tickets\Commerce\Order $order Order object + * @param string $old_status Previous status + * @param string $new_status New status + * @return void + */ + public function handle_status_change( $order, $old_status, $new_status ) { + // Only for mail-in payment orders + if ( $order->get_gateway() !== 'mail-in' ) { + return; + } + + // Send confirmation email when payment is completed + if ( $new_status === 'completed' ) { + $this->send_confirmation_email( $order ); + } + + // Send cancellation email if order is cancelled + if ( in_array( $new_status, [ 'denied', 'cancelled', 'refunded' ] ) ) { + $this->send_cancellation_email( $order ); + } + } + + /** + * Schedule cleanup of expired orders + * + * @since 1.0.0 + * @return void + */ + public function schedule_cleanup() { + if ( ! wp_next_scheduled( 'et_mail_in_cleanup_expired_orders' ) ) { + wp_schedule_event( time(), 'daily', 'et_mail_in_cleanup_expired_orders' ); + } + } + + /** + * Cleanup expired mail-in orders + * + * @since 1.0.0 + * @return void + */ + public function cleanup_expired_orders() { + // Only run if auto-cancel is enabled + if ( ! tribe_get_option( 'tickets-commerce-mail-in-auto-cancel', false ) ) { + return; + } + + $reservation_days = absint( tribe_get_option( 'tickets-commerce-mail-in-reservation-days', 7 ) ); + $cutoff_date = date( 'Y-m-d H:i:s', strtotime( "-{$reservation_days} days" ) ); + + $args = [ + 'post_type' => 'tec_tc_order', + 'meta_query' => [ + [ + 'key' => '_tec_tc_gateway', + 'value' => 'mail-in', + 'compare' => '=' + ], + [ + 'key' => '_mail_in_check_received', + 'value' => false, + 'compare' => '=' + ], + [ + 'key' => '_mail_in_created_date', + 'value' => $cutoff_date, + 'compare' => '<', + 'type' => 'DATETIME' + ] + ], + 'post_status' => [ 'tec-tc-pending', 'tec-tc-approved' ], + 'posts_per_page' => 50, // Process in batches + ]; + + $expired_orders = new \WP_Query( $args ); + $order_controller = new Order(); + + foreach ( $expired_orders->posts as $order_post ) { + $order = tribe( 'tickets.commerce.order' )->get_by_id( $order_post->ID ); + + if ( $order ) { + // Cancel the order + $order->update_status( 'denied' ); + + // Add order note + $order_controller->add_order_note( + $order_post->ID, + __( 'Order automatically cancelled due to expired payment deadline.', 'event-tickets-mail-in' ) + ); + } + } + } + + /** + * Add custom actions to order admin page + * + * @since 1.0.0 + * @param array $actions Existing actions + * @param \TEC\Tickets\Commerce\Order $order Order object + * @return array + */ + public function add_order_actions( $actions, $order ) { + // Only for mail-in payment orders + if ( $order->get_gateway() !== 'mail-in' ) { + return $actions; + } + + $order_controller = new Order(); + $details = $order_controller->get_mail_in_details( $order->get_id() ); + + // Add "Mark Check Received" action if check hasn't been received + if ( ! $details['check_received'] && ! $details['check_bounced'] ) { + $actions['mark_check_received'] = [ + 'label' => __( 'Mark Check Received', 'event-tickets-mail-in' ), + 'url' => wp_nonce_url( + admin_url( 'admin-post.php?action=mark_check_received&order_id=' . $order->get_id() ), + 'et_mail_in_admin' + ), + 'class' => 'button-secondary', + ]; + + $actions['mark_check_bounced'] = [ + 'label' => __( 'Mark Check Bounced', 'event-tickets-mail-in' ), + 'url' => wp_nonce_url( + admin_url( 'admin-post.php?action=mark_check_bounced&order_id=' . $order->get_id() ), + 'et_mail_in_admin' + ), + 'class' => 'button-secondary', + ]; + } + + return $actions; + } + + /** + * Get checkout template for mail-in payment + * + * @since 1.0.0 + * @param string $template Template name + * @param string $gateway Gateway key + * @return string + */ + public function get_checkout_template( $template, $gateway ) { + if ( $gateway === 'mail-in' ) { + return 'checkout/mail-in-form'; + } + return $template; + } + + /** + * Send payment instructions email + * + * @since 1.0.0 + * @param \TEC\Tickets\Commerce\Order $order Order object + * @return void + */ + protected function send_instructions_email( $order ) { + $purchaser_email = $order->get_purchaser_email(); + if ( ! $purchaser_email ) { + return; + } + + $subject = sprintf( + __( 'Payment Instructions for Order #%s', 'event-tickets-mail-in' ), + $order->get_order_number() + ); + + $order_controller = new Order(); + $details = $order_controller->get_mail_in_details( $order->get_id() ); + + $message = $this->get_email_template( 'payment-instructions', [ + 'order' => $order, + 'details' => $details, + ] ); + + wp_mail( $purchaser_email, $subject, $message, $this->get_email_headers() ); + } + + /** + * Send payment confirmation email + * + * @since 1.0.0 + * @param \TEC\Tickets\Commerce\Order $order Order object + * @return void + */ + protected function send_confirmation_email( $order ) { + $purchaser_email = $order->get_purchaser_email(); + if ( ! $purchaser_email ) { + return; + } + + $subject = sprintf( + __( 'Payment Confirmed for Order #%s', 'event-tickets-mail-in' ), + $order->get_order_number() + ); + + $message = $this->get_email_template( 'payment-confirmation', [ + 'order' => $order, + ] ); + + wp_mail( $purchaser_email, $subject, $message, $this->get_email_headers() ); + } + + /** + * Send order cancellation email + * + * @since 1.0.0 + * @param \TEC\Tickets\Commerce\Order $order Order object + * @return void + */ + protected function send_cancellation_email( $order ) { + $purchaser_email = $order->get_purchaser_email(); + if ( ! $purchaser_email ) { + return; + } + + $subject = sprintf( + __( 'Order Cancelled: #%s', 'event-tickets-mail-in' ), + $order->get_order_number() + ); + + $message = $this->get_email_template( 'order-cancelled', [ + 'order' => $order, + ] ); + + wp_mail( $purchaser_email, $subject, $message, $this->get_email_headers() ); + } + + /** + * Get email template content + * + * @since 1.0.0 + * @param string $template Template name + * @param array $args Template arguments + * @return string + */ + protected function get_email_template( $template, $args = [] ) { + $template_file = ET_MAIL_IN_PATH . "templates/emails/{$template}.php"; + + if ( ! file_exists( $template_file ) ) { + return $this->get_fallback_email_content( $template, $args ); + } + + ob_start(); + extract( $args ); + include $template_file; + return ob_get_clean(); + } + + /** + * Get fallback email content + * + * @since 1.0.0 + * @param string $template Template name + * @param array $args Template arguments + * @return string + */ + protected function get_fallback_email_content( $template, $args ) { + switch ( $template ) { + case 'payment-instructions': + return $this->get_instructions_fallback( $args ); + case 'payment-confirmation': + return $this->get_confirmation_fallback( $args ); + case 'order-cancelled': + return $this->get_cancellation_fallback( $args ); + default: + return ''; + } + } + + /** + * Get payment instructions fallback content + * + * @since 1.0.0 + * @param array $args Template arguments + * @return string + */ + protected function get_instructions_fallback( $args ) { + $order = $args['order']; + $details = $args['details']; + + $content = sprintf( __( 'Thank you for your order #%s!', 'event-tickets-mail-in' ), $order->get_order_number() ) . "\n\n"; + $content .= $details['instructions'] . "\n\n"; + $content .= sprintf( __( 'Mailing Address:\n%s', 'event-tickets-mail-in' ), $details['mailing_address'] ) . "\n\n"; + $content .= sprintf( __( 'Make Check Payable To: %s', 'event-tickets-mail-in' ), $details['payable_to'] ) . "\n\n"; + $content .= sprintf( __( 'Order Total: %s', 'event-tickets-mail-in' ), $order->get_total_value()->get_formatted() ) . "\n"; + + return $content; + } + + /** + * Get confirmation fallback content + * + * @since 1.0.0 + * @param array $args Template arguments + * @return string + */ + protected function get_confirmation_fallback( $args ) { + $order = $args['order']; + + return sprintf( + __( 'Your payment for order #%s has been received and processed. Your tickets are now confirmed!', 'event-tickets-mail-in' ), + $order->get_order_number() + ); + } + + /** + * Get cancellation fallback content + * + * @since 1.0.0 + * @param array $args Template arguments + * @return string + */ + protected function get_cancellation_fallback( $args ) { + $order = $args['order']; + + return sprintf( + __( 'Your order #%s has been cancelled. If you believe this is an error, please contact us.', 'event-tickets-mail-in' ), + $order->get_order_number() + ); + } + + /** + * Get email headers + * + * @since 1.0.0 + * @return array + */ + protected function get_email_headers() { + return [ + 'Content-Type: text/html; charset=UTF-8', + 'From: ' . get_option( 'blogname' ) . ' <' . get_option( 'admin_email' ) . '>', + ]; + } +} \ No newline at end of file diff --git a/src/Order.php b/src/Order.php new file mode 100644 index 0000000..91b7509 --- /dev/null +++ b/src/Order.php @@ -0,0 +1,251 @@ +create_from_cart( $gateway, $purchaser ); + + if ( ! $order ) { + return false; + } + + // Add mail-in specific meta data + $this->add_mail_in_meta( $order ); + + return $order; + } + + /** + * Add mail-in payment specific metadata + * + * @since 1.0.0 + * @param \TEC\Tickets\Commerce\Order $order + * @return void + */ + protected function add_mail_in_meta( $order ) { + $order_id = $order->get_id(); + + // Store payment method + update_post_meta( $order_id, '_tec_tc_payment_method', 'mail-in-check' ); + + // Store mailing instructions + $instructions = get_option( 'et_mail_in_instructions', '' ); + if ( $instructions ) { + update_post_meta( $order_id, '_mail_in_instructions', $instructions ); + } + + // Store mailing address + $address = get_option( 'et_mail_in_mailing_address', '' ); + if ( $address ) { + update_post_meta( $order_id, '_mail_in_mailing_address', $address ); + } + + // Store payable to + $payable_to = get_option( 'et_mail_in_check_payable_to', '' ); + if ( $payable_to ) { + update_post_meta( $order_id, '_mail_in_payable_to', $payable_to ); + } + + // Set check received flag to false initially + update_post_meta( $order_id, '_mail_in_check_received', false ); + + // Store creation date for tracking purposes + update_post_meta( $order_id, '_mail_in_created_date', current_time( 'mysql' ) ); + + // Add order note + $this->add_order_note( + $order_id, + __( 'Mail-in payment order created. Awaiting check payment.', 'event-tickets-mail-in' ) + ); + } + + /** + * Mark check as received + * + * @since 1.0.0 + * @param int $order_id Order ID + * @param string $notes Optional notes about the received check + * @return bool + */ + public function mark_check_received( $order_id, $notes = '' ) { + $order = $this->get_order( $order_id ); + + if ( ! $order ) { + return false; + } + + // Update check received status + update_post_meta( $order_id, '_mail_in_check_received', true ); + update_post_meta( $order_id, '_mail_in_check_received_date', current_time( 'mysql' ) ); + + // Add notes if provided + if ( $notes ) { + update_post_meta( $order_id, '_mail_in_check_notes', $notes ); + } + + // Update order status to completed + tribe( \TEC\Tickets\Commerce\Order::class )->modify_status( $order_id, Status\Completed::SLUG ); + + // Add order note + $note = __( 'Check payment received and processed.', 'event-tickets-mail-in' ); + if ( $notes ) { + $note .= ' ' . sprintf( __( 'Notes: %s', 'event-tickets-mail-in' ), $notes ); + } + + $this->add_order_note( $order_id, $note ); + + return true; + } + + /** + * Mark check as bounced/failed + * + * @since 1.0.0 + * @param int $order_id Order ID + * @param string $reason Reason for bounce/failure + * @return bool + */ + public function mark_check_bounced( $order_id, $reason = '' ) { + $order = $this->get_order( $order_id ); + + if ( ! $order ) { + return false; + } + + // Update order status to denied + tribe( \TEC\Tickets\Commerce\Order::class )->modify_status( $order_id, Status\Denied::SLUG ); + + // Add bounce information + update_post_meta( $order_id, '_mail_in_check_bounced', true ); + update_post_meta( $order_id, '_mail_in_check_bounce_date', current_time( 'mysql' ) ); + + if ( $reason ) { + update_post_meta( $order_id, '_mail_in_bounce_reason', $reason ); + } + + // Add order note + $note = __( 'Check payment bounced/failed.', 'event-tickets-mail-in' ); + if ( $reason ) { + $note .= ' ' . sprintf( __( 'Reason: %s', 'event-tickets-mail-in' ), $reason ); + } + + $this->add_order_note( $order_id, $note ); + + return true; + } + + /** + * Get mail-in payment details for an order + * + * @since 1.0.0 + * @param int $order_id Order ID + * @return array + */ + public function get_mail_in_details( $order_id ) { + return [ + 'instructions' => get_post_meta( $order_id, '_mail_in_instructions', true ), + 'mailing_address' => get_post_meta( $order_id, '_mail_in_mailing_address', true ), + 'payable_to' => get_post_meta( $order_id, '_mail_in_payable_to', true ), + 'check_received' => get_post_meta( $order_id, '_mail_in_check_received', true ), + 'check_received_date' => get_post_meta( $order_id, '_mail_in_check_received_date', true ), + 'check_notes' => get_post_meta( $order_id, '_mail_in_check_notes', true ), + 'check_bounced' => get_post_meta( $order_id, '_mail_in_check_bounced', true ), + 'bounce_reason' => get_post_meta( $order_id, '_mail_in_bounce_reason', true ), + 'bounce_date' => get_post_meta( $order_id, '_mail_in_check_bounce_date', true ), + 'created_date' => get_post_meta( $order_id, '_mail_in_created_date', true ), + ]; + } + + /** + * Add order note + * + * @since 1.0.0 + * @param int $order_id Order ID + * @param string $note Note content + * @return void + */ + public function add_order_note( $order_id, $note ) { + $existing_notes = get_post_meta( $order_id, '_tec_tc_order_notes', true ); + if ( ! is_array( $existing_notes ) ) { + $existing_notes = []; + } + + $existing_notes[] = [ + 'date' => current_time( 'mysql' ), + 'note' => $note, + 'type' => 'system', + ]; + + update_post_meta( $order_id, '_tec_tc_order_notes', $existing_notes ); + } + + /** + * Get order by ID + * + * @since 1.0.0 + * @param int $order_id Order ID + * @return \WP_Post|false + */ + protected function get_order( $order_id ) { + $order = get_post( $order_id ); + + // Verify this is actually an order post + if ( ! $order || $order->post_type !== 'tec_tc_order' ) { + return false; + } + + return $order; + } + + /** + * Get pending mail-in orders (for admin management) + * + * @since 1.0.0 + * @param array $args Query arguments + * @return \WP_Query + */ + public function get_pending_orders( $args = [] ) { + $default_args = [ + 'post_type' => 'tec_tc_order', + 'meta_query' => [ + [ + 'key' => '_tec_tc_gateway', + 'value' => 'mail-in', + 'compare' => '=' + ], + [ + 'key' => '_mail_in_check_received', + 'value' => false, + 'compare' => '=' + ] + ], + 'post_status' => [ 'tec-tc-pending', 'tec-tc-approved' ], + 'orderby' => 'date', + 'order' => 'DESC', + ]; + + $args = wp_parse_args( $args, $default_args ); + + return new \WP_Query( $args ); + } +} \ No newline at end of file diff --git a/src/Provider.php b/src/Provider.php new file mode 100644 index 0000000..4059c5c --- /dev/null +++ b/src/Provider.php @@ -0,0 +1,442 @@ +register_hooks(); + + // Register admin features + $this->register_admin_features(); + + // Register form processing + $this->register_form_processing(); + + // Register frontend assets + $this->register_frontend_assets(); + } + + /** + * Add this gateway to the list of available gateways + * + * @since 1.0.0 + * @param array $gateways Existing gateways + * @return array + */ + public function filter_add_gateway( array $gateways = [] ) { + // Create our gateway instance and register it + return $this->container->make( Gateway::class )->register_gateway( $gateways ); + } + + /** + * Register hooks and filters + * + * @since 1.0.0 + * @return void + */ + protected function register_hooks() { + $hooks = new Hooks(); + $hooks->register(); + } + + /** + * Register admin features + * + * @since 1.0.0 + * @return void + */ + protected function register_admin_features() { + if ( ! is_admin() ) { + return; + } + + // Add admin menu for managing mail-in orders + add_action( 'admin_menu', [ $this, 'add_admin_menu' ] ); + + // Add admin scripts and styles + add_action( 'admin_enqueue_scripts', [ $this, 'admin_scripts' ] ); + + // Add order meta box for mail-in payments + add_action( 'add_meta_boxes', [ $this, 'add_order_meta_box' ] ); + + // Handle admin actions + add_action( 'admin_post_mark_check_received', [ $this, 'handle_mark_check_received' ] ); + add_action( 'admin_post_mark_check_bounced', [ $this, 'handle_mark_check_bounced' ] ); + } + + /** + * Add admin menu for mail-in order management + * + * @since 1.0.0 + * @return void + */ + public function add_admin_menu() { + add_submenu_page( + 'edit.php?post_type=tec_tc_order', + __( 'Mail-In Payments', 'event-tickets-mail-in' ), + __( 'Mail-In Payments', 'event-tickets-mail-in' ), + 'manage_options', + 'mail-in-payments', + [ $this, 'admin_page' ] + ); + } + + /** + * Display admin page for managing mail-in payments + * + * @since 1.0.0 + * @return void + */ + public function admin_page() { + $order_controller = new Order(); + $pending_orders = $order_controller->get_pending_orders(); + + include ET_MAIL_IN_PATH . 'templates/admin/mail-in-orders.php'; + } + + /** + * Enqueue admin scripts and styles + * + * @since 1.0.0 + * @param string $hook Current admin page hook + * @return void + */ + public function admin_scripts( $hook ) { + // Only load on relevant admin pages + if ( ! in_array( $hook, [ 'edit.php', 'post.php', 'tickets_page_mail-in-payments' ] ) ) { + return; + } + + wp_enqueue_style( + 'et-mail-in-admin', + ET_MAIL_IN_URL . 'assets/css/admin.css', + [], + ET_MAIL_IN_VERSION + ); + + wp_enqueue_script( + 'et-mail-in-admin', + ET_MAIL_IN_URL . 'assets/js/admin.js', + [ 'jquery' ], + ET_MAIL_IN_VERSION, + true + ); + + wp_localize_script( 'et-mail-in-admin', 'etMailInAdmin', [ + 'nonce' => wp_create_nonce( 'et_mail_in_admin' ), + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'strings' => [ + 'confirm_received' => __( 'Are you sure you want to mark this check as received?', 'event-tickets-mail-in' ), + 'confirm_bounced' => __( 'Are you sure you want to mark this check as bounced?', 'event-tickets-mail-in' ), + ], + ] ); + } + + /** + * Add meta box to order edit screen + * + * @since 1.0.0 + * @return void + */ + public function add_order_meta_box() { + global $post; + + // Only add to mail-in orders + if ( get_post_meta( $post->ID, '_tec_tc_gateway', true ) !== 'mail-in' ) { + return; + } + + add_meta_box( + 'mail-in-payment-details', + __( 'Mail-In Payment Details', 'event-tickets-mail-in' ), + [ $this, 'order_meta_box_callback' ], + 'tec_tc_order', + 'normal', + 'high' + ); + } + + /** + * Render order meta box + * + * @since 1.0.0 + * @param \WP_Post $post Order post object + * @return void + */ + public function order_meta_box_callback( $post ) { + $order_controller = new Order(); + $details = $order_controller->get_mail_in_details( $post->ID ); + + include ET_MAIL_IN_PATH . 'templates/admin/order-meta-box.php'; + } + + /** + * Handle marking check as received + * + * @since 1.0.0 + * @return void + */ + public function handle_mark_check_received() { + // Verify nonce + if ( ! wp_verify_nonce( $_POST['nonce'] ?? '', 'et_mail_in_admin' ) ) { + wp_die( __( 'Security check failed.', 'event-tickets-mail-in' ) ); + } + + // Check permissions + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( __( 'You do not have permission to perform this action.', 'event-tickets-mail-in' ) ); + } + + $order_id = absint( $_POST['order_id'] ?? 0 ); + $notes = sanitize_textarea_field( $_POST['notes'] ?? '' ); + + if ( ! $order_id ) { + wp_die( __( 'Invalid order ID.', 'event-tickets-mail-in' ) ); + } + + $order_controller = new Order(); + $result = $order_controller->mark_check_received( $order_id, $notes ); + + if ( $result ) { + add_action( 'admin_notices', function() { + echo '

' . + esc_html__( 'Check marked as received successfully.', 'event-tickets-mail-in' ) . + '

'; + } ); + } else { + add_action( 'admin_notices', function() { + echo '

' . + esc_html__( 'Failed to mark check as received.', 'event-tickets-mail-in' ) . + '

'; + } ); + } + + // Redirect back + $redirect_url = $_POST['redirect_url'] ?? admin_url( 'edit.php?post_type=tec_tc_order&page=mail-in-payments' ); + wp_redirect( $redirect_url ); + exit; + } + + /** + * Handle marking check as bounced + * + * @since 1.0.0 + * @return void + */ + public function handle_mark_check_bounced() { + // Verify nonce + if ( ! wp_verify_nonce( $_POST['nonce'] ?? '', 'et_mail_in_admin' ) ) { + wp_die( __( 'Security check failed.', 'event-tickets-mail-in' ) ); + } + + // Check permissions + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( __( 'You do not have permission to perform this action.', 'event-tickets-mail-in' ) ); + } + + $order_id = absint( $_POST['order_id'] ?? 0 ); + $reason = sanitize_textarea_field( $_POST['reason'] ?? '' ); + + if ( ! $order_id ) { + wp_die( __( 'Invalid order ID.', 'event-tickets-mail-in' ) ); + } + + $order_controller = new Order(); + $result = $order_controller->mark_check_bounced( $order_id, $reason ); + + if ( $result ) { + add_action( 'admin_notices', function() { + echo '

' . + esc_html__( 'Check marked as bounced successfully.', 'event-tickets-mail-in' ) . + '

'; + } ); + } else { + add_action( 'admin_notices', function() { + echo '

' . + esc_html__( 'Failed to mark check as bounced.', 'event-tickets-mail-in' ) . + '

'; + } ); + } + + // Redirect back + $redirect_url = $_POST['redirect_url'] ?? admin_url( 'edit.php?post_type=tec_tc_order&page=mail-in-payments' ); + wp_redirect( $redirect_url ); + exit; + } + + /** + * Register form processing + * + * @since 1.0.0 + * @return void + */ + protected function register_form_processing() { + // Handle checkout form submission + add_action( 'template_redirect', [ $this, 'handle_checkout_form' ] ); + } + + /** + * Handle checkout form submission + * + * @since 1.0.0 + * @return void + */ + public function handle_checkout_form() { + // Check if this is our gateway form submission + if ( empty( $_POST['tec_tc_gateway'] ) || $_POST['tec_tc_gateway'] !== 'mail-in' ) { + return; + } + + // Verify nonce + if ( ! wp_verify_nonce( $_POST['tec_tickets_commerce_checkout_nonce'] ?? '', 'tec_tickets_commerce_checkout' ) ) { + wp_die( __( 'Security check failed.', 'event-tickets-mail-in' ) ); + } + + try { + // Get cart and process order directly + if ( ! class_exists( 'TEC\Tickets\Commerce\Module' ) ) { + wp_die( __( 'Commerce module not available.', 'event-tickets-mail-in' ) ); + } + + // Get cart instance + $cart = tribe( \TEC\Tickets\Commerce\Cart::class ); + + // Check if cart has items + if ( ! $cart->has_items() ) { + wp_redirect( add_query_arg( 'tec_tc_error', urlencode( __( 'Your cart is empty.', 'event-tickets-mail-in' ) ), wp_get_referer() ) ); + exit; + } + + // Get purchaser data from form + $purchaser_data = [ + 'purchaser_name' => sanitize_text_field( $_POST['purchaser_name'] ?? '' ), + 'purchaser_email' => sanitize_email( $_POST['purchaser_email'] ?? '' ), + ]; + + // Validate required fields + if ( empty( $purchaser_data['purchaser_name'] ) ) { + wp_redirect( add_query_arg( 'tec_tc_error', urlencode( __( 'Full name is required.', 'event-tickets-mail-in' ) ), wp_get_referer() ) ); + exit; + } + + if ( empty( $purchaser_data['purchaser_email'] ) ) { + wp_redirect( add_query_arg( 'tec_tc_error', urlencode( __( 'Email address is required.', 'event-tickets-mail-in' ) ), wp_get_referer() ) ); + exit; + } + + // Get the Mail-In gateway + $gateway = $this->container->make( Gateway::class ); + + // Process payment through gateway + $result = $gateway->process_payment( $purchaser_data ); + + if ( ! $result['success'] ) { + // Handle error + wp_redirect( add_query_arg( 'tec_tc_error', urlencode( $result['message'] ), wp_get_referer() ) ); + exit; + } + + // Success - redirect to success page + if ( isset( $result['redirect_url'] ) ) { + wp_redirect( $result['redirect_url'] ); + exit; + } + + } catch ( \Exception $e ) { + wp_redirect( add_query_arg( 'tec_tc_error', urlencode( $e->getMessage() ), wp_get_referer() ) ); + exit; + } + } + + /** + * Register frontend assets + * + * @since 1.0.0 + * @return void + */ + protected function register_frontend_assets() { + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_frontend_assets' ] ); + } + + /** + * Enqueue frontend assets + * + * @since 1.0.0 + * @return void + */ + public function enqueue_frontend_assets() { + // Only load on pages that might have the checkout shortcode + if ( ! $this->should_load_frontend_assets() ) { + return; + } + + wp_enqueue_style( + 'et-mail-in-checkout', + ET_MAIL_IN_URL . 'assets/css/checkout.css', + [], + ET_MAIL_IN_VERSION + ); + + wp_enqueue_script( + 'et-mail-in-checkout', + ET_MAIL_IN_URL . 'assets/js/checkout.js', + [], + ET_MAIL_IN_VERSION, + true + ); + } + + /** + * Check if we should load frontend assets + * + * @since 1.0.0 + * @return bool + */ + protected function should_load_frontend_assets() { + global $post; + + // Load on posts/pages with checkout shortcode + if ( $post && has_shortcode( $post->post_content, 'tec_tickets_checkout' ) ) { + return true; + } + + // Load on checkout pages (if using different URL structure) + if ( is_page() && ( + strpos( get_the_title(), 'checkout' ) !== false || + strpos( get_the_title(), 'Checkout' ) !== false + ) ) { + return true; + } + + // Load on pages that might contain Event Tickets Commerce elements + if ( is_page() && class_exists( 'TEC\Tickets\Commerce\Module' ) ) { + // Check if page content contains commerce-related content + if ( $post && ( + strpos( $post->post_content, 'tec_tickets' ) !== false || + strpos( $post->post_content, 'tribe_events' ) !== false + ) ) { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/Settings.php b/src/Settings.php new file mode 100644 index 0000000..c002ab7 --- /dev/null +++ b/src/Settings.php @@ -0,0 +1,410 @@ +get_settings_fields(); + } + + /** + * Get the HTML for the connection box in the admin + * + * @since 1.0.0 + * @return string + */ + public function get_connection_settings_html() { + $status = $this->get_connection_status(); + + $html = '
'; + $html .= '

' . esc_html__( 'Mail-In Payment (Check)', 'event-tickets-mail-in' ) . '

'; + $html .= '

' . esc_html( $status['message'] ) . '

'; + + if ( ! $status['is_active'] ) { + $html .= '

' . esc_html__( 'Configure the settings below to enable this payment method.', 'event-tickets-mail-in' ) . '

'; + } + + $html .= '
'; + + return $html; + } + + /** + * Get settings fields for internal use + * + * @since 1.0.0 + * @return array + */ + public function get_settings_fields() { + $fields = [ + 'mail_in_section' => [ + 'type' => 'html', + 'html' => '

' . __( 'Mail-In Payment (Check) Settings', 'event-tickets-mail-in' ) . '

', + ], + 'tickets-commerce-mail-in-enabled' => [ + 'type' => 'checkbox_bool', + 'label' => __( 'Enable Mail-In Payments', 'event-tickets-mail-in' ), + 'tooltip' => __( 'Enable this payment method to allow customers to pay by mailing checks.', 'event-tickets-mail-in' ), + 'default' => false, + 'validation_type' => 'boolean', + ], + 'tickets-commerce-mail-in-title' => [ + 'type' => 'text', + 'label' => __( 'Payment Method Title', 'event-tickets-mail-in' ), + 'tooltip' => __( 'This is the title customers will see during checkout.', 'event-tickets-mail-in' ), + 'default' => __( 'Mail-In Payment (Check)', 'event-tickets-mail-in' ), + 'validation_type' => 'html', + 'size' => 'large', + ], + 'tickets-commerce-mail-in-description' => [ + 'type' => 'textarea', + 'label' => __( 'Payment Method Description', 'event-tickets-mail-in' ), + 'tooltip' => __( 'Brief description shown to customers during checkout.', 'event-tickets-mail-in' ), + 'default' => __( 'Pay by mailing a check to our address.', 'event-tickets-mail-in' ), + 'validation_type' => 'html', + 'size' => 'large', + ], + 'tickets-commerce-mail-in-payable-to' => [ + 'type' => 'text', + 'label' => __( 'Make Check Payable To', 'event-tickets-mail-in' ), + 'tooltip' => __( 'The name or organization that checks should be made payable to.', 'event-tickets-mail-in' ), + 'default' => '', + 'validation_type' => 'html', + 'size' => 'large', + 'required' => true, + ], + 'tickets-commerce-mail-in-address' => [ + 'type' => 'textarea', + 'label' => __( 'Mailing Address', 'event-tickets-mail-in' ), + 'tooltip' => __( 'The complete mailing address where checks should be sent.', 'event-tickets-mail-in' ), + 'default' => '', + 'validation_type' => 'html', + 'size' => 'large', + 'required' => true, + ], + 'tickets-commerce-mail-in-instructions' => [ + 'type' => 'textarea', + 'label' => __( 'Payment Instructions', 'event-tickets-mail-in' ), + 'tooltip' => __( 'Detailed instructions for customers on how to complete their payment.', 'event-tickets-mail-in' ), + 'default' => __( 'Please mail your check to the address provided below. Your tickets will be reserved for 7 days while we wait for your payment to arrive. Include your order number on the check memo line.', 'event-tickets-mail-in' ), + 'validation_type' => 'html', + 'size' => 'large', + ], + 'tickets-commerce-mail-in-reservation-days' => [ + 'type' => 'text', + 'label' => __( 'Reservation Period (Days)', 'event-tickets-mail-in' ), + 'tooltip' => __( 'Number of days to hold tickets while waiting for check payment.', 'event-tickets-mail-in' ), + 'default' => '7', + 'validation_type' => 'int', + 'size' => 'small', + ], + 'tickets-commerce-mail-in-auto-cancel' => [ + 'type' => 'checkbox_bool', + 'label' => __( 'Auto-Cancel Expired Orders', 'event-tickets-mail-in' ), + 'tooltip' => __( 'Automatically cancel orders if payment is not received within the reservation period.', 'event-tickets-mail-in' ), + 'default' => false, + 'validation_type' => 'boolean', + ], + ]; + + return $fields; + } + + /** + * Get the settings section for the Payments tab + * + * @since 1.0.0 + * @return array + */ + public function get_settings_section() { + return [ + 'mail_in_payment_settings' => [ + 'priority' => 30, + 'fields' => $this->get_settings_fields(), + ], + ]; + } + + /** + * Validate settings before saving + * + * @since 1.0.0 + * @param array $input Settings input + * @param array $field Field configuration + * @return mixed + */ + public function validate_settings_field( $input, $field ) { + switch ( $field['validation_type'] ?? 'html' ) { + case 'boolean': + return (bool) $input; + + case 'int': + return max( 1, absint( $input ) ); + + case 'html': + default: + return wp_kses_post( $input ); + } + } + + /** + * Add custom validation for required fields + * + * @since 1.0.0 + * @param array $input All settings input + * @return array + */ + public function validate_settings( $input ) { + $validated = []; + $errors = []; + + foreach ( $this->get_settings_fields() as $key => $field ) { + $value = $input[ $key ] ?? $field['default'] ?? ''; + + // Check required fields + if ( ! empty( $field['required'] ) && empty( $value ) ) { + $errors[] = sprintf( + __( '%s is required.', 'event-tickets-mail-in' ), + $field['label'] + ); + continue; + } + + $validated[ $key ] = $this->validate_settings_field( $value, $field ); + } + + // Display errors if any + if ( ! empty( $errors ) ) { + add_settings_error( + 'et_mail_in_settings', + 'validation_error', + implode( ' ', $errors ), + 'error' + ); + } + + return $validated; + } + + /** + * Get connection status for admin display + * + * @since 1.0.0 + * @return array + */ + public function get_connection_status() { + $address = tribe_get_option( 'tickets-commerce-mail-in-address', '' ); + $payable_to = tribe_get_option( 'tickets-commerce-mail-in-payable-to', '' ); + $enabled = tribe_get_option( 'tickets-commerce-mail-in-enabled', false ); + + $is_connected = ! empty( $address ) && ! empty( $payable_to ); + + $status = [ + 'is_connected' => $is_connected, + 'is_enabled' => $enabled, + 'is_active' => $enabled && $is_connected, + ]; + + if ( $is_connected ) { + $status['message'] = __( 'Mail-in payment gateway is properly configured.', 'event-tickets-mail-in' ); + $status['status'] = 'success'; + } else { + $missing = []; + if ( empty( $address ) ) { + $missing[] = __( 'Mailing Address', 'event-tickets-mail-in' ); + } + if ( empty( $payable_to ) ) { + $missing[] = __( 'Check Payable To', 'event-tickets-mail-in' ); + } + + $status['message'] = sprintf( + __( 'Please configure the following required fields: %s', 'event-tickets-mail-in' ), + implode( ', ', $missing ) + ); + $status['status'] = 'error'; + } + + return $status; + } + + + /** + * Register settings with WordPress + * + * @since 1.0.0 + * @return void + */ + public function register_settings() { + foreach ( $this->get_settings_fields() as $key => $field ) { + if ( $field['type'] === 'html' ) { + continue; // Skip HTML fields + } + + register_setting( + 'et_mail_in_settings', + $key, + [ + 'type' => $this->get_wp_setting_type( $field['validation_type'] ?? 'string' ), + 'sanitize_callback' => [ $this, 'validate_settings_field' ], + 'default' => $field['default'] ?? '', + ] + ); + } + } + + /** + * Convert validation type to WordPress setting type + * + * @since 1.0.0 + * @param string $validation_type + * @return string + */ + protected function get_wp_setting_type( $validation_type ) { + switch ( $validation_type ) { + case 'boolean': + return 'boolean'; + case 'int': + return 'integer'; + default: + return 'string'; + } + } + + /** + * Render settings template + * + * @since 1.0.0 + * @param array $context Template context + * @return string + */ + protected function render_settings_template( $context ) { + $gateway = $context['gateway']; + $status = $context['connection_status']; + $fields = $this->get_settings_fields(); + + ob_start(); + ?> +
+
+ + +
+
+ + + + + +
+
+

+

+ +

+ +

+ +

+ +
+
+ + +
+ + + $field ) : ?> + + + + + + + + + + + + +
+ + + render_field( $field_key, $field ); ?> + +

+ +
+
+
+
+ '; + echo ''; + echo ' ' . esc_html( $field['label'] ); + echo ''; + break; + + case 'textarea': + echo ''; + break; + + case 'text': + default: + echo ''; + break; + } + } +} \ No newline at end of file diff --git a/templates/admin/mail-in-orders.php b/templates/admin/mail-in-orders.php new file mode 100644 index 0000000..7940456 --- /dev/null +++ b/templates/admin/mail-in-orders.php @@ -0,0 +1,315 @@ + + +
+

+ +
+

+
+ + have_posts() ) : ?> +
+ + + + + + + + + + + + + + have_posts() ) : + $pending_orders->the_post(); + $order_id = get_the_ID(); + $order = tribe( 'tickets.commerce.order' )->get_by_id( $order_id ); + $details = $order_controller->get_mail_in_details( $order_id ); + $created_date = get_post_meta( $order_id, '_mail_in_created_date', true ); + $event_id = get_post_meta( $order_id, '_tec_tc_order_event_id', true ); + $event_title = $event_id ? get_the_title( $event_id ) : __( 'N/A', 'event-tickets-mail-in' ); + ?> + + + + + + + + + + + +
+ + + #get_order_number() ); ?> + + + + get_purchaser_name() ); ?>
+ get_purchaser_email() ); ?> +
+ + + + + + + + + get_total_value()->get_formatted() ); ?> + + +
+
+ + + + +
+ + +
+
+
+ + $pending_orders->max_num_pages, + 'current' => max( 1, get_query_var( 'paged' ) ), + 'format' => '?paged=%#%', + 'prev_text' => __( '« Previous', 'event-tickets-mail-in' ), + 'next_text' => __( 'Next »', 'event-tickets-mail-in' ), + ] ); + + if ( $pagination ) { + echo '
'; + echo '
' . $pagination . '
'; + echo '
'; + } + ?> + + +
+

+
+ + + +
+ + + + + + + + + + \ No newline at end of file diff --git a/templates/admin/order-meta-box.php b/templates/admin/order-meta-box.php new file mode 100644 index 0000000..a9b05b7 --- /dev/null +++ b/templates/admin/order-meta-box.php @@ -0,0 +1,218 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + + +
+ + + + + + +
+ +
+
+
+ +
+
+ + +
+

+

+ +
+ + + +
+
+ +
+ + + + \ No newline at end of file diff --git a/templates/checkout/mail-in-form.php b/templates/checkout/mail-in-form.php new file mode 100644 index 0000000..52b06a2 --- /dev/null +++ b/templates/checkout/mail-in-form.php @@ -0,0 +1,151 @@ + + +
+
+

+

+ +

+
+ +
+ +
+

+

+
+ + +
+ +
+

+

+ +

+
+ + + +
+

+
+ +
+
+ +
+ +
+

+
    +
  • +
  • +
  • +
+
+
+ + +
\ No newline at end of file diff --git a/templates/emails/order-cancelled.php b/templates/emails/order-cancelled.php new file mode 100644 index 0000000..208bc15 --- /dev/null +++ b/templates/emails/order-cancelled.php @@ -0,0 +1,141 @@ + + + + + + <?php printf( __( 'Order Cancelled: #%s', 'event-tickets-mail-in' ), $order->get_order_number() ); ?> + + + +
+
+
+

+

' . esc_html( $order->get_order_number() ) . '' ); ?>

+
+ +
+

+ +

get_purchaser_name() ) + ); ?>

+ +
+

+

+
    +
  • +
  • +
  • +
  • +
+
+ +
+

+

get_order_number() ); ?>

+

get_total_value()->get_formatted() ); ?>

+

+

+
+ +

+
    +
  • +
  • +
  • +
  • +
+ +
+

+

+

+
+ +

+
+ + +
+ + \ No newline at end of file diff --git a/templates/emails/payment-confirmation.php b/templates/emails/payment-confirmation.php new file mode 100644 index 0000000..5e06dba --- /dev/null +++ b/templates/emails/payment-confirmation.php @@ -0,0 +1,122 @@ + + + + + + <?php printf( __( 'Payment Confirmed for Order #%s', 'event-tickets-mail-in' ), $order->get_order_number() ); ?> + + + +
+
+
+

+

' . esc_html( $order->get_order_number() ) . '' ); ?>

+
+ +
+

+ +

get_purchaser_name() ) + ); ?>

+ +
+

+

+
+ +
+

+

get_order_number() ); ?>

+

+

get_total_value()->get_formatted() ); ?>

+

+
+ +

+
    +
  • +
  • +
  • +
  • +
+ +

+
+ + +
+ + \ No newline at end of file diff --git a/templates/emails/payment-instructions.php b/templates/emails/payment-instructions.php new file mode 100644 index 0000000..76484dc --- /dev/null +++ b/templates/emails/payment-instructions.php @@ -0,0 +1,170 @@ + + + + + + <?php printf( __( 'Payment Instructions for Order #%s', 'event-tickets-mail-in' ), $order->get_order_number() ); ?> + + + +
+
+

+

' . esc_html( $order->get_order_number() ) . '' ); ?>

+
+ +
+

+ +

get_purchaser_name() ) + ); ?>

+ +

+ +
+

+

get_order_number() ); ?>

+

+

get_total_value()->get_formatted() ); ?>

+
+ +
+

+ + +

+ + + +

+

+ + + +

+
+ +
+ + +

get_total_value()->get_formatted() ); ?>

+
+ +
+

+
    +
  • get_order_number() ) ); ?>
  • +
  • +
  • +
  • +
+
+ +

+ +

+
+ + +
+ + \ No newline at end of file diff --git a/templates/v2/commerce/gateway/mail-in/container.php b/templates/v2/commerce/gateway/mail-in/container.php new file mode 100644 index 0000000..ab790ac --- /dev/null +++ b/templates/v2/commerce/gateway/mail-in/container.php @@ -0,0 +1,124 @@ + + +
+ +
+ +
+ + + +
\ No newline at end of file