improved webhooks and status tracking in database

This commit is contained in:
2025-05-22 18:46:34 +02:00
parent 04eeac5075
commit d5c6d7bdeb
6 changed files with 270 additions and 140 deletions

View File

@@ -307,6 +307,18 @@ function breez_wc_check_payment_status_endpoint($request) {
$order_id = $request->get_param('order_id'); $order_id = $request->get_param('order_id');
$logger->log("Checking payment status for order #$order_id", 'debug'); $logger->log("Checking payment status for order #$order_id", 'debug');
// Initialize DB manager
$db_manager = new Breez_DB_Manager();
$payment = $db_manager->get_payment_by_order($order_id);
if (!$payment) {
$logger->log("No payment found for order #$order_id", 'error');
return new WP_REST_Response(array(
'success' => false,
'message' => 'Payment not found'
), 404);
}
$order = wc_get_order($order_id); $order = wc_get_order($order_id);
if (!$order) { if (!$order) {
$logger->log("Order #$order_id not found", 'error'); $logger->log("Order #$order_id not found", 'error');
@@ -325,69 +337,11 @@ function breez_wc_check_payment_status_endpoint($request) {
), 400); ), 400);
} }
// If this is a POST request, update the order based on the payment data // Initialize API client
if ($request->get_method() === 'POST') {
$payment_data = $request->get_json_params();
$logger->log("Received payment data for order #$order_id: " . print_r($payment_data, true), 'debug');
if ($payment_data['status'] === 'SUCCEEDED') {
if ($order->get_status() === 'pending') {
// Add payment details to order meta
$order->update_meta_data('_breez_payment_amount_sat', $payment_data['amount_sat']);
$order->update_meta_data('_breez_payment_fees_sat', $payment_data['fees_sat']);
$order->update_meta_data('_breez_payment_time', $payment_data['payment_time']);
$order->update_meta_data('_breez_payment_hash', $payment_data['payment_hash']);
// Complete the order
$order->payment_complete($payment_data['payment_hash']);
$order->add_order_note(sprintf(
__('Payment confirmed. Amount: %d sats, Fees: %d sats, Hash: %s', 'breez-woocommerce'),
$payment_data['amount_sat'],
$payment_data['fees_sat'],
$payment_data['payment_hash']
));
$order->save();
$logger->log("Order #$order_id marked as complete", 'info');
}
return new WP_REST_Response(array(
'success' => true,
'status' => 'completed'
), 200);
} elseif ($payment_data['status'] === 'FAILED') {
if ($order->get_status() === 'pending') {
$order->update_status('failed', $payment_data['error'] ?? __('Payment failed', 'breez-woocommerce'));
$order->save();
$logger->log("Order #$order_id marked as failed: " . ($payment_data['error'] ?? 'No error message'), 'info');
}
return new WP_REST_Response(array(
'success' => true,
'status' => 'failed'
), 200);
}
}
// For GET requests, check payment status via API
$invoice_id = $order->get_meta('_breez_invoice_id');
if (!$invoice_id) {
$logger->log("No payment invoice found for order #$order_id", 'error');
return new WP_REST_Response(array(
'success' => false,
'message' => 'No payment invoice found'
), 404);
}
// Initialize API client - directly fetch gateway settings
$gateway_settings = get_option('woocommerce_breez_settings'); $gateway_settings = get_option('woocommerce_breez_settings');
$api_url = isset($gateway_settings['api_url']) ? $gateway_settings['api_url'] : ''; $api_url = isset($gateway_settings['api_url']) ? $gateway_settings['api_url'] : '';
$api_key = isset($gateway_settings['api_key']) ? $gateway_settings['api_key'] : ''; $api_key = isset($gateway_settings['api_key']) ? $gateway_settings['api_key'] : '';
$logger->log("API settings: URL=" . (!empty($api_url) ? 'Set' : 'Missing') .
", Key=" . (!empty($api_key) ? 'Set' : 'Missing'), 'debug');
if (empty($api_url) || empty($api_key)) { if (empty($api_url) || empty($api_key)) {
$logger->log("API credentials not configured", 'error'); $logger->log("API credentials not configured", 'error');
return new WP_REST_Response(array( return new WP_REST_Response(array(
@@ -397,13 +351,40 @@ function breez_wc_check_payment_status_endpoint($request) {
} }
$client = new Breez_API_Client($api_url, $api_key); $client = new Breez_API_Client($api_url, $api_key);
$payment_status = $client->check_payment_status($invoice_id);
$logger->log("Payment status for order #$order_id: " . print_r($payment_status, true), 'debug'); // Check payment status from API
$api_status = $client->check_payment_status($payment['invoice_id']);
$logger->log("API payment status for invoice {$payment['invoice_id']}: " . print_r($api_status, true), 'debug');
// Update payment status in database if it has changed
if ($api_status['status'] !== $payment['status']) {
$db_manager->update_payment_status($order_id, $api_status['status']);
// Update order status if needed
if ($api_status['status'] === 'SUCCEEDED' && $order->get_status() === 'pending') {
$order->payment_complete($payment['invoice_id']);
$order->add_order_note(sprintf(
__('Payment confirmed. Amount: %d sats, Hash: %s', 'breez-woocommerce'),
$payment['metadata']['amount_sat'],
$payment['invoice_id']
));
$order->save();
$logger->log("Order #$order_id marked as complete", 'info');
} else if ($api_status['status'] === 'FAILED' && $order->get_status() === 'pending') {
$order->update_status('failed', __('Payment failed or expired.', 'breez-woocommerce'));
$logger->log("Order #$order_id marked as failed", 'info');
}
}
return new WP_REST_Response(array( return new WP_REST_Response(array(
'success' => true, 'success' => true,
'status' => $payment_status['status'], 'status' => $api_status['status'],
'data' => $payment_status 'data' => array_merge($api_status, array(
'order_status' => $order->get_status(),
'payment_method' => $payment['metadata']['payment_method'],
'amount_sat' => $payment['metadata']['amount_sat'],
'expires_at' => $payment['metadata']['expires_at']
))
), 200); ), 200);
} catch (Exception $e) { } catch (Exception $e) {
@@ -431,41 +412,31 @@ function breez_wc_check_pending_payments() {
* Plugin activation hook * Plugin activation hook
*/ */
function breez_wc_activate() { function breez_wc_activate() {
if (!breez_wc_is_woocommerce_active()) {
deactivate_plugins(plugin_basename(__FILE__));
wp_die(__('Breez Nodeless Payments requires WooCommerce to be installed and active.', 'breez-woocommerce'));
}
// Include required files before using them // Include required files before using them
require_once BREEZ_WC_PLUGIN_DIR . 'includes/class-breez-logger.php'; require_once BREEZ_WC_PLUGIN_DIR . 'includes/class-breez-logger.php';
require_once BREEZ_WC_PLUGIN_DIR . 'includes/class-breez-db-manager.php'; require_once BREEZ_WC_PLUGIN_DIR . 'includes/class-breez-db-manager.php';
// Create required directories if they don't exist // Initialize logger
$directories = array( $logger = new Breez_Logger(true);
BREEZ_WC_PLUGIN_DIR . 'includes/admin', $logger->log('Plugin activation started', 'info');
BREEZ_WC_PLUGIN_DIR . 'includes/block',
BREEZ_WC_PLUGIN_DIR . 'assets/js',
BREEZ_WC_PLUGIN_DIR . 'assets/css',
);
foreach ($directories as $directory) { // Initialize DB manager
if (!file_exists($directory)) {
wp_mkdir_p($directory);
}
}
// Create database tables
$db_manager = new Breez_DB_Manager(); $db_manager = new Breez_DB_Manager();
$db_manager->install_tables();
// Schedule payment status check // Force drop and recreate the payments table with new schema
if (!wp_next_scheduled('breez_wc_check_pending_payments')) { $logger->log('Forcing table recreation to update schema', 'info');
wp_schedule_event(time(), 'five_minutes', 'breez_wc_check_pending_payments'); $db_manager->recreate_table();
// Create required directories
$upload_dir = wp_upload_dir();
$breez_dir = $upload_dir['basedir'] . '/breez-wc';
if (!file_exists($breez_dir)) {
wp_mkdir_p($breez_dir);
} }
// Flush rewrite rules for webhook endpoint $logger->log('Plugin activation completed', 'info');
flush_rewrite_rules();
} }
register_activation_hook(__FILE__, 'breez_wc_activate');
/** /**
* Plugin deactivation hook * Plugin deactivation hook
@@ -701,6 +672,7 @@ function breez_wc_filter_blocks_checkout_order_data($order, $request) {
$order->save(); $order->save();
if (class_exists('Breez_Logger')) { if (class_exists('Breez_Logger')) {
$logger = new Breez_Logger(true);
$logger->log('Stored Breez payment data in order meta', 'debug'); $logger->log('Stored Breez payment data in order meta', 'debug');
} }
} }
@@ -708,3 +680,33 @@ function breez_wc_filter_blocks_checkout_order_data($order, $request) {
return $order; return $order;
} }
/**
* Display payment instructions on the payment page
*/
function breez_wc_display_payment_instructions() {
if (!isset($_GET['show_payment']) || !isset($_GET['order_id'])) {
return;
}
$order_id = absint($_GET['order_id']);
$order = wc_get_order($order_id);
if (!$order || $order->get_payment_method() !== 'breez') {
return;
}
// Verify order key
if (!isset($_GET['key']) || $_GET['key'] !== $order->get_order_key()) {
return;
}
// Get gateway instance
$gateways = WC()->payment_gateways->payment_gateways();
$gateway = isset($gateways['breez']) ? $gateways['breez'] : null;
if ($gateway) {
$gateway->payment_page($order_id);
}
}
add_action('woocommerce_receipt_breez', 'breez_wc_display_payment_instructions');

View File

@@ -271,14 +271,31 @@ class WC_Gateway_Breez extends WC_Payment_Gateway {
* @return array * @return array
*/ */
public function process_payment($order_id) { public function process_payment($order_id) {
$this->logger->log("Processing payment for order #$order_id", 'debug');
try { try {
$this->logger->log("Processing payment for order #$order_id", 'debug');
$order = wc_get_order($order_id); $order = wc_get_order($order_id);
if (!$order) { if (!$order) {
throw new Exception(__('Order not found', 'breez-woocommerce')); throw new Exception(__('Order not found', 'breez-woocommerce'));
} }
// Check for existing payment in database
$existing_payment = $this->db_manager->get_payment_by_order($order_id);
$current_time = time();
if ($existing_payment && $existing_payment['status'] === 'pending') {
$payment_created = strtotime($existing_payment['created_at']);
$expiry_time = $payment_created + ($this->expiry_minutes * 60);
if ($current_time < $expiry_time) {
$this->logger->log("Using existing valid payment for order #$order_id", 'debug');
return array(
'result' => 'success',
'redirect' => $this->get_return_url($order)
);
}
}
// Get the selected payment method // Get the selected payment method
$payment_method = $this->get_payment_method_from_request(); $payment_method = $this->get_payment_method_from_request();
@@ -323,9 +340,9 @@ class WC_Gateway_Breez extends WC_Payment_Gateway {
throw new Exception(__('Invalid amount conversion', 'breez-woocommerce')); throw new Exception(__('Invalid amount conversion', 'breez-woocommerce'));
} }
// Create payment // Create payment request
$payment_data = array( $payment_data = array(
'amount' => $amount_sat, // Send amount in satoshis 'amount' => $amount_sat,
'method' => $payment_method === 'onchain' ? 'BITCOIN_ADDRESS' : 'LIGHTNING', 'method' => $payment_method === 'onchain' ? 'BITCOIN_ADDRESS' : 'LIGHTNING',
'description' => sprintf(__('Payment for order #%s', 'breez-woocommerce'), $order->get_order_number()) 'description' => sprintf(__('Payment for order #%s', 'breez-woocommerce'), $order->get_order_number())
); );
@@ -342,16 +359,26 @@ class WC_Gateway_Breez extends WC_Payment_Gateway {
throw new Exception(__('Failed to create payment', 'breez-woocommerce')); throw new Exception(__('Failed to create payment', 'breez-woocommerce'));
} }
// Save payment data to order meta // Save payment in database
$order->update_meta_data('_breez_payment_id', $response['destination']); $metadata = array(
$order->update_meta_data('_breez_invoice_id', $response['destination']); 'payment_method' => $payment_method,
$order->update_meta_data('_breez_payment_method', $payment_method); 'exchange_rate' => $exchange_rate_response['rate'],
$order->update_meta_data('_breez_payment_amount', $order_total); 'amount_sat' => $amount_sat,
$order->update_meta_data('_breez_payment_amount_sat', $amount_sat); 'expires_at' => time() + ($this->expiry_minutes * 60)
$order->update_meta_data('_breez_payment_currency', $currency); );
$order->update_meta_data('_breez_payment_status', 'pending');
$order->update_meta_data('_breez_payment_created', time()); $saved = $this->db_manager->save_payment(
$order->update_meta_data('_breez_payment_expires', time() + ($this->expiry_minutes * 60)); $order_id,
$response['destination'],
$order_total,
$currency,
'pending',
$metadata
);
if (!$saved) {
throw new Exception(__('Failed to save payment in database', 'breez-woocommerce'));
}
// Set order status to pending // Set order status to pending
$order->update_status('pending', __('Awaiting Bitcoin/Lightning payment', 'breez-woocommerce')); $order->update_status('pending', __('Awaiting Bitcoin/Lightning payment', 'breez-woocommerce'));
@@ -367,7 +394,7 @@ class WC_Gateway_Breez extends WC_Payment_Gateway {
// Save the order // Save the order
$order->save(); $order->save();
$this->logger->log("Order meta data updated for #$order_id", 'debug'); $this->logger->log("Payment saved for order #$order_id", 'debug');
// Reduce stock levels // Reduce stock levels
wc_reduce_stock_levels($order_id); wc_reduce_stock_levels($order_id);
@@ -375,13 +402,21 @@ class WC_Gateway_Breez extends WC_Payment_Gateway {
// Empty cart // Empty cart
WC()->cart->empty_cart(); WC()->cart->empty_cart();
// Return success with redirect to order-received page // Return success with redirect to payment instructions page
$return_url = $this->get_return_url($order); $redirect_url = add_query_arg(
$this->logger->log("Redirecting to: $return_url", 'debug'); array(
'order_id' => $order->get_id(),
'key' => $order->get_order_key(),
'show_payment' => true
),
$order->get_checkout_payment_url(true)
);
$this->logger->log("Redirecting to payment page: $redirect_url", 'debug');
return array( return array(
'result' => 'success', 'result' => 'success',
'redirect' => $return_url 'redirect' => $redirect_url
); );
} catch (Exception $e) { } catch (Exception $e) {
@@ -705,4 +740,39 @@ class WC_Gateway_Breez extends WC_Payment_Gateway {
'message' => $message ? $message : __('An error occurred during the payment process.', 'breez-woocommerce'), 'message' => $message ? $message : __('An error occurred during the payment process.', 'breez-woocommerce'),
); );
} }
/**
* Display payment instructions on the payment page
*/
public function payment_page($order_id) {
$this->logger->log("Displaying payment page for order #$order_id", 'debug');
$order = wc_get_order($order_id);
if (!$order) {
$this->logger->log("Order not found: #$order_id", 'error');
return;
}
// Get payment details from database
$payment = $this->db_manager->get_payment_by_order($order_id);
if (!$payment) {
$this->logger->log("Payment not found for order #$order_id", 'error');
return;
}
// Load the payment instructions template
wc_get_template(
'payment-instructions.php',
array(
'order' => $order,
'invoice_id' => $payment['invoice_id'],
'payment_method' => $payment['metadata']['payment_method'],
'expiry' => $payment['metadata']['expires_at'],
'current_time' => time(),
'payment_status' => $payment['status']
),
'',
BREEZ_WC_PLUGIN_DIR . 'templates/'
);
}
} }

View File

@@ -54,7 +54,7 @@ public function install_tables() {
$sql = "CREATE TABLE IF NOT EXISTS {$table_name} ( $sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
order_id BIGINT UNSIGNED NOT NULL, order_id BIGINT UNSIGNED NOT NULL,
invoice_id VARCHAR(255) NOT NULL, invoice_id TEXT NOT NULL,
amount DECIMAL(16,8) NOT NULL, amount DECIMAL(16,8) NOT NULL,
currency VARCHAR(10) NOT NULL, currency VARCHAR(10) NOT NULL,
status VARCHAR(20) NOT NULL, status VARCHAR(20) NOT NULL,
@@ -62,8 +62,8 @@ public function install_tables() {
updated_at DATETIME NOT NULL, updated_at DATETIME NOT NULL,
metadata TEXT, metadata TEXT,
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE KEY order_id (order_id), KEY order_id (order_id),
KEY invoice_id (invoice_id) KEY invoice_id (invoice_id(255))
) $charset_collate;"; ) $charset_collate;";
$this->logger->log("SQL query: {$sql}", 'debug'); $this->logger->log("SQL query: {$sql}", 'debug');
@@ -102,30 +102,55 @@ public function install_tables() {
$now = current_time('mysql'); $now = current_time('mysql');
try { try {
// Log input parameters
$this->logger->log("Attempting to save payment with parameters:", 'debug');
$this->logger->log("order_id: $order_id", 'debug');
$this->logger->log("invoice_id: $invoice_id", 'debug');
$this->logger->log("amount: $amount", 'debug');
$this->logger->log("currency: $currency", 'debug');
$this->logger->log("status: $status", 'debug');
$this->logger->log("metadata: " . print_r($metadata, true), 'debug');
// Verify table exists
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$this->table_name}'") === $this->table_name;
if (!$table_exists) {
$this->logger->log("Table {$this->table_name} does not exist!", 'error');
return false;
}
// Prepare data for insert
$data = array(
'order_id' => $order_id,
'invoice_id' => $invoice_id,
'amount' => $amount,
'currency' => $currency,
'status' => $status,
'created_at' => $now,
'updated_at' => $now,
'metadata' => $metadata ? json_encode($metadata) : null
);
// Log the SQL query that will be executed
$this->logger->log("Inserting with data: " . print_r($data, true), 'debug');
$result = $wpdb->insert( $result = $wpdb->insert(
$this->table_name, $this->table_name,
array( $data,
'order_id' => $order_id,
'invoice_id' => $invoice_id,
'amount' => $amount,
'currency' => $currency,
'status' => $status,
'created_at' => $now,
'updated_at' => $now,
'metadata' => $metadata ? json_encode($metadata) : null
),
array('%d', '%s', '%f', '%s', '%s', '%s', '%s', '%s') array('%d', '%s', '%f', '%s', '%s', '%s', '%s', '%s')
); );
if ($result) { if ($result === false) {
$this->logger->log("Payment saved: order_id=$order_id, invoice_id=$invoice_id, status=$status"); $this->logger->log("Database error: " . $wpdb->last_error, 'error');
return true; $this->logger->log("Last query: " . $wpdb->last_query, 'debug');
} else {
$this->logger->log("Error saving payment: " . $wpdb->last_error);
return false; return false;
} }
$this->logger->log("Payment saved successfully: order_id=$order_id, invoice_id=$invoice_id, status=$status");
return true;
} catch (Exception $e) { } catch (Exception $e) {
$this->logger->log("Exception saving payment: " . $e->getMessage()); $this->logger->log("Exception saving payment: " . $e->getMessage(), 'error');
$this->logger->log("Stack trace: " . $e->getTraceAsString(), 'debug');
return false; return false;
} }
} }
@@ -258,4 +283,21 @@ public function install_tables() {
return array(); return array();
} }
} }
/**
* Drop and recreate the payments table
* This is needed when changing the table schema
*/
public function recreate_table() {
global $wpdb;
$this->logger->log("Dropping and recreating payments table", 'info');
// Drop the existing table
$table_name = $wpdb->prefix . 'wc_breez_payments';
$wpdb->query("DROP TABLE IF EXISTS {$table_name}");
// Create the table with new schema
$this->install_tables();
}
} }

View File

@@ -360,8 +360,16 @@ class Breez_Payment_Handler {
// Update order status if still pending // Update order status if still pending
if ($order->has_status('pending')) { if ($order->has_status('pending')) {
// Clear the payment data to allow new payment creation
$order->delete_meta_data('_breez_payment_id');
$order->delete_meta_data('_breez_invoice_id');
$order->delete_meta_data('_breez_payment_expires');
$order->update_status('failed', __('Payment failed or expired.', 'breez-woocommerce')); $order->update_status('failed', __('Payment failed or expired.', 'breez-woocommerce'));
$this->logger->log("Payment failed for order #$order_id (invoice: $invoice_id)"); $this->logger->log("Payment failed for order #$order_id (invoice: $invoice_id)");
// Save the order
$order->save();
} }
return true; return true;

View File

@@ -165,20 +165,33 @@ class Breez_Webhook_Handler {
), 404); ), 404);
} }
$payment_handler = new Breez_Payment_Handler(null, $db_manager); // Update payment status in database
$db_manager->update_payment_status($order_id, $status);
// Update payment status based on the webhook data // Update order status based on payment status
if ($status === 'SUCCEEDED') { if ($status === 'SUCCEEDED') {
$payment_handler->process_successful_payment($invoice_id); if ($order->get_status() === 'pending') {
} elseif ($status === 'FAILED') { // Complete the order
$payment_handler->process_failed_payment($invoice_id); $order->payment_complete($invoice_id);
} else { $order->add_order_note(sprintf(
self::$logger->log("Unknown payment status: $status"); __('Payment confirmed. Amount: %d sats, Hash: %s', 'breez-woocommerce'),
$payment['metadata']['amount_sat'],
$invoice_id
));
$order->save();
self::$logger->log("Order #$order_id marked as complete", 'info');
}
} else if ($status === 'FAILED') {
if ($order->get_status() === 'pending') {
$order->update_status('failed', __('Payment failed or expired.', 'breez-woocommerce'));
self::$logger->log("Order #$order_id marked as failed", 'info');
}
} }
return new WP_REST_Response(array( return new WP_REST_Response(array(
'success' => true, 'success' => true,
'message' => 'Webhook processed successfully' 'status' => $status
), 200); ), 200);
} }
} }

View File

@@ -216,11 +216,6 @@ document.addEventListener('DOMContentLoaded', function() {
<div class="breez-payment-status breez-payment-completed"> <div class="breez-payment-status breez-payment-completed">
<p>${paymentData.status_description || '<?php _e('Payment confirmed! Thank you for your payment.', 'breez-woocommerce'); ?>'}</p> <p>${paymentData.status_description || '<?php _e('Payment confirmed! Thank you for your payment.', 'breez-woocommerce'); ?>'}</p>
<p><?php _e('Your order is now being processed.', 'breez-woocommerce'); ?></p> <p><?php _e('Your order is now being processed.', 'breez-woocommerce'); ?></p>
<div class="breez-payment-details">
<p><strong><?php _e('Payment Details:', 'breez-woocommerce'); ?></strong></p>
<p><?php _e('Amount paid:', 'breez-woocommerce'); ?> ${amountSats} sats</p>
<p><?php _e('Network fee:', 'breez-woocommerce'); ?> ${feesSats} sats</p>
</div>
</div> </div>
`; `;