This commit is contained in:
2025-05-22 10:33:13 +02:00
parent 217562414f
commit 04eeac5075
10 changed files with 247 additions and 47 deletions

View File

@@ -13,17 +13,31 @@
.breez-payment-status { .breez-payment-status {
padding: 15px; padding: 15px;
border-radius: 5px; margin: 10px 0;
margin-bottom: 20px; border-radius: 4px;
text-align: center;
}
.breez-payment-pending,
.breez-payment-confirming,
.breez-payment-unknown {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
padding: 15px;
margin: 10px 0;
border-radius: 4px;
text-align: center;
} }
.breez-payment-completed { .breez-payment-completed {
background: #d4edda; background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724; color: #155724;
} }
.breez-payment-failed { .breez-payment-failed {
background: #f8d7da; background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24; color: #721c24;
} }
@@ -89,3 +103,61 @@
border-radius: 5px; border-radius: 5px;
margin-bottom: 20px; margin-bottom: 20px;
} }
.breez-payment-details {
margin-top: 15px;
padding: 10px;
background-color: rgba(255, 255, 255, 0.5);
border-radius: 4px;
}
.breez-error-details {
font-family: monospace;
background-color: rgba(0, 0, 0, 0.05);
padding: 10px;
margin-top: 10px;
border-radius: 4px;
}
/* Loading Spinner */
.breez-spinner {
display: inline-block;
width: 20px;
height: 20px;
margin: 10px auto;
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: breez-spin 1s linear infinite;
}
@keyframes breez-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Status Icons */
.breez-payment-status::before {
font-family: dashicons;
font-size: 24px;
display: block;
margin-bottom: 10px;
}
.breez-payment-completed::before {
content: "\f147"; /* Dashicon: yes */
color: #155724;
}
.breez-payment-failed::before {
content: "\f335"; /* Dashicon: no */
color: #721c24;
}
.breez-payment-pending::before,
.breez-payment-confirming::before,
.breez-payment-unknown::before {
content: "\f463"; /* Dashicon: update */
color: #856404;
animation: breez-spin 2s linear infinite;
}

View File

@@ -18,7 +18,7 @@
return window.wp.element.createElement( return window.wp.element.createElement(
'div', 'div',
null, null,
window.breezSettings?.description || 'Pay with Bitcoin via Lightning Network or on-chain transaction.' window.breezSettings?.description || 'Pay with Lightning.'
); );
}; };

View File

@@ -565,7 +565,7 @@ function breez_register_blocks_scripts() {
[ [
'breez' => [ 'breez' => [
'title' => !empty($settings['title']) ? $settings['title'] : 'Breez Nodeless Payments', 'title' => !empty($settings['title']) ? $settings['title'] : 'Breez Nodeless Payments',
'description' => !empty($settings['description']) ? $settings['description'] : 'Pay with Bitcoin via Lightning Network or on-chain transaction.', 'description' => !empty($settings['description']) ? $settings['description'] : 'Pay with Lightning',
'supports' => ['products'], 'supports' => ['products'],
'showSavedCards' => false, 'showSavedCards' => false,
'canMakePayment' => true, 'canMakePayment' => true,

View File

@@ -20,14 +20,14 @@ return array(
'title' => __('Title', 'breez-woocommerce'), 'title' => __('Title', 'breez-woocommerce'),
'type' => 'text', 'type' => 'text',
'description' => __('This controls the title which the user sees during checkout.', 'breez-woocommerce'), 'description' => __('This controls the title which the user sees during checkout.', 'breez-woocommerce'),
'default' => __('Breez Nodeless Payments', 'breez-woocommerce'), 'default' => __('Lightning Payments', 'breez-woocommerce'),
'desc_tip' => true, 'desc_tip' => true,
), ),
'description' => array( 'description' => array(
'title' => __('Description', 'breez-woocommerce'), 'title' => __('Description', 'breez-woocommerce'),
'type' => 'textarea', 'type' => 'textarea',
'description' => __('This controls the description which the user sees during checkout.', 'breez-woocommerce'), 'description' => __('This controls the description which the user sees during checkout.', 'breez-woocommerce'),
'default' => __('Pay with Bitcoin via Lightning Network or on-chain transaction.', 'breez-woocommerce'), 'default' => __('Pay with Lightning', 'breez-woocommerce'),
'desc_tip' => true, 'desc_tip' => true,
), ),
'instructions' => array( 'instructions' => array(

View File

@@ -14,7 +14,7 @@ const BreezLabel = () => {
}; };
const BreezComponent = () => { const BreezComponent = () => {
const description = window.wcSettings?.breez?.description || 'Pay with Bitcoin via Lightning Network or on-chain transaction.'; const description = window.wcSettings?.breez?.description || 'Pay with Lightning';
return createElement('div', { className: 'wc-block-components-payment-method-description' }, return createElement('div', { className: 'wc-block-components-payment-method-description' },
decodeEntities(description) decodeEntities(description)
); );

View File

@@ -108,7 +108,7 @@ class Breez_API_Client {
/** /**
* Check payment status using API endpoint * Check payment status using API endpoint
* *
* @param string $invoice_id Invoice ID or payment destination * @param string $invoice_id Invoice ID or payment identifier
* @return array Payment status response * @return array Payment status response
*/ */
public function check_payment_status($invoice_id) { public function check_payment_status($invoice_id) {
@@ -126,26 +126,101 @@ class Breez_API_Client {
)); ));
// If the payment is not found, return pending instead of throwing an error // If the payment is not found, return pending instead of throwing an error
if (isset($response['error']) && $response['error'] === 'Payment not found') { if ($response['status'] === 'UNKNOWN') {
return array( return array(
'status' => 'pending', 'status' => 'pending',
'destination' => $invoice_id 'destination' => $invoice_id,
'sdk_status' => 'UNKNOWN'
); );
} }
// Return the response as is // Map SDK payment states to WooCommerce states
return $response; $status = $this->map_payment_status($response['status']);
// Build response with all available details
$result = array(
'status' => $status,
'sdk_status' => $response['status'], // Include original SDK status
'destination' => $invoice_id,
'amount_sat' => $response['amount_sat'] ?? null,
'fees_sat' => $response['fees_sat'] ?? null,
'timestamp' => $response['timestamp'] ?? null,
'error' => $response['error'] ?? null
);
// Include payment details if available
if (isset($response['payment_details'])) {
$result['payment_details'] = $response['payment_details'];
}
// Add human-readable status description
$result['status_description'] = $this->get_status_description($response['status']);
return $result;
} catch (Exception $e) { } catch (Exception $e) {
$this->logger->log('Payment status check error: ' . $e->getMessage(), 'error'); $this->logger->log('Payment status check error: ' . $e->getMessage(), 'error');
// Return pending status instead of throwing an error // Return pending status instead of throwing an error
return array( return array(
'status' => 'pending', 'status' => 'pending',
'destination' => $invoice_id 'sdk_status' => 'UNKNOWN',
'destination' => $invoice_id,
'error' => $e->getMessage()
); );
} }
} }
/**
* Map SDK payment states to WooCommerce payment states
*
* @param string $sdk_status The status from the SDK
* @return string WooCommerce payment status
*/
private function map_payment_status($sdk_status) {
switch ($sdk_status) {
case 'SUCCEEDED':
case 'WAITING_CONFIRMATION': // Consider payment complete when claim tx is broadcast
return 'completed';
case 'PENDING': // Lockup transaction broadcast
return 'pending';
case 'WAITING_FEE_ACCEPTANCE': // Needs fee approval
return 'pending';
case 'FAILED': // Swap failed (expired or lockup tx failed)
return 'failed';
case 'UNKNOWN': // Payment not found or error
default:
return 'pending';
}
}
/**
* Get human-readable status description
*
* @param string $sdk_status The SDK status
* @return string Human-readable description
*/
private function get_status_description($sdk_status) {
switch ($sdk_status) {
case 'SUCCEEDED':
return __('Payment confirmed and completed.', 'breez-woocommerce');
case 'WAITING_CONFIRMATION':
return __('Payment received and being confirmed.', 'breez-woocommerce');
case 'PENDING':
return __('Payment initiated, waiting for completion.', 'breez-woocommerce');
case 'WAITING_FEE_ACCEPTANCE':
return __('Waiting for fee approval.', 'breez-woocommerce');
case 'FAILED':
return __('Payment failed or expired.', 'breez-woocommerce');
case 'UNKNOWN':
default:
return __('Payment status unknown.', 'breez-woocommerce');
}
}
/** /**
* Register a webhook URL * Register a webhook URL
* *

View File

@@ -137,7 +137,7 @@ class Breez_Blocks_Support extends Automattic\WooCommerce\Blocks\Payments\Integr
public function get_payment_method_data() { public function get_payment_method_data() {
$data = [ $data = [
'title' => !empty($this->settings['title']) ? $this->settings['title'] : 'Breez Nodeless Payments', 'title' => !empty($this->settings['title']) ? $this->settings['title'] : 'Breez Nodeless Payments',
'description' => !empty($this->settings['description']) ? $this->settings['description'] : 'Pay with Bitcoin via Lightning Network or on-chain transaction.', 'description' => !empty($this->settings['description']) ? $this->settings['description'] : 'Pay with Lightning',
'supports' => ['products'], 'supports' => ['products'],
'showSavedCards' => false, 'showSavedCards' => false,
'canMakePayment' => true, 'canMakePayment' => true,

View File

@@ -213,6 +213,18 @@ class Breez_Payment_Handler {
/** /**
* Check payment status * Check payment status
* *
* Payment states from SDK are mapped to WooCommerce states as follows:
* - SUCCEEDED -> completed (claim tx confirmed)
* - WAITING_CONFIRMATION -> completed (claim tx broadcast but not confirmed)
* - PENDING -> pending (lockup tx broadcast)
* - WAITING_FEE_ACCEPTANCE -> pending (needs fee approval)
* - FAILED -> failed (expired or lockup tx failed)
* - UNKNOWN -> pending (not found or error)
*
* Note: WAITING_CONFIRMATION is considered completed because the claim transaction
* has been broadcast or a direct Liquid transaction has been seen, making the
* payment effectively irreversible at this point.
*
* @param string $invoice_id Invoice ID * @param string $invoice_id Invoice ID
* @return string Payment status (pending, completed, failed) * @return string Payment status (pending, completed, failed)
*/ */
@@ -228,20 +240,30 @@ class Breez_Payment_Handler {
// First check the local database // First check the local database
$payment = $this->db_manager->get_payment_by_invoice($invoice_id); $payment = $this->db_manager->get_payment_by_invoice($invoice_id);
if ($payment && $payment['status'] !== 'pending') { if ($payment && $payment['status'] === 'completed') {
$this->logger->log('Using cached payment status', 'debug', array( $this->logger->log('Using cached completed payment status', 'debug', array(
'invoice_id' => $invoice_id, 'invoice_id' => $invoice_id,
'status' => $payment['status'] 'status' => $payment['status']
)); ));
return $payment['status']; // Return cached status if not pending return $payment['status']; // Only return cached status if completed
} }
// Check with API
$response = $this->client->check_payment_status($invoice_id);
$status = $response['status'];
// If pending or not found, check with API // Log detailed payment state information
$status = $this->client->check_payment_status($invoice_id); $this->logger->log('Payment status details', 'debug', array(
'invoice_id' => $invoice_id,
'status' => $status,
'sdk_status' => $response['payment_details']['status'] ?? 'unknown',
'amount_sat' => $response['amount_sat'],
'timestamp' => $response['timestamp'],
'error' => $response['error']
));
// Update database if status has changed and we have payment data // Update database if status has changed and we have payment data
if ($payment && $status !== 'pending' && $status !== $payment['status']) { if ($payment && $status !== $payment['status']) {
$this->db_manager->update_payment_status($payment['order_id'], $status); $this->db_manager->update_payment_status($payment['order_id'], $status);
$this->logger->log('Payment status updated', 'info', array( $this->logger->log('Payment status updated', 'info', array(
@@ -250,6 +272,13 @@ class Breez_Payment_Handler {
'new_status' => $status, 'new_status' => $status,
'order_id' => $payment['order_id'] 'order_id' => $payment['order_id']
)); ));
// Process status changes
if ($status === 'completed') {
$this->process_successful_payment($invoice_id);
} else if ($status === 'failed') {
$this->process_failed_payment($invoice_id);
}
} }
$duration = round(microtime(true) - $start_time, 3); $duration = round(microtime(true) - $start_time, 3);

View File

@@ -181,25 +181,46 @@ document.addEventListener('DOMContentLoaded', function() {
// Clear any existing error message // Clear any existing error message
statusIndicator.classList.remove('breez-payment-error'); statusIndicator.classList.remove('breez-payment-error');
// If status is pending, don't show any status message // Update status message based on SDK status
if (paymentData.status === 'pending') { if (paymentData.sdk_status === 'PENDING' || paymentData.sdk_status === 'WAITING_FEE_ACCEPTANCE') {
statusIndicator.innerHTML = `
<div class="breez-payment-pending">
<p>${paymentData.status_description || '<?php _e('Payment is being processed...', 'breez-woocommerce'); ?>'}</p>
<div class="breez-spinner"></div>
</div>
`;
return; return;
} }
if (paymentData.status === 'SUCCEEDED' || (data.status && data.status === 'SUCCEEDED')) { if (paymentData.sdk_status === 'WAITING_CONFIRMATION') {
statusIndicator.innerHTML = `
<div class="breez-payment-confirming">
<p>${paymentData.status_description || '<?php _e('Payment received! Waiting for confirmation...', 'breez-woocommerce'); ?>'}</p>
<div class="breez-spinner"></div>
</div>
`;
return;
}
if (paymentData.sdk_status === 'SUCCEEDED') {
// Stop checking status immediately // Stop checking status immediately
clearInterval(statusCheckInterval); clearInterval(statusCheckInterval);
// Update UI to show payment completed // Format amounts with commas for better readability
const amountSats = paymentData.amount_sat ? Number(paymentData.amount_sat).toLocaleString() : '0';
const feesSats = paymentData.fees_sat ? Number(paymentData.fees_sat).toLocaleString() : '0';
// Update UI to show payment completed with amount details
paymentBox.innerHTML = ` paymentBox.innerHTML = `
<h3><?php _e('Bitcoin/Lightning Payment', 'breez-woocommerce'); ?></h3> <h3><?php _e('Bitcoin/Lightning Payment', 'breez-woocommerce'); ?></h3>
<div class="breez-payment-status breez-payment-completed"> <div class="breez-payment-status breez-payment-completed">
<p><?php _e('Payment received! 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>
<p class="breez-payment-details"> <div class="breez-payment-details">
<?php _e('Amount paid:', 'breez-woocommerce'); ?> ${Number(paymentData.amount_sat).toLocaleString()} sats<br> <p><strong><?php _e('Payment Details:', 'breez-woocommerce'); ?></strong></p>
<?php _e('Network fee:', 'breez-woocommerce'); ?> ${Number(paymentData.fees_sat).toLocaleString()} sats <p><?php _e('Amount paid:', 'breez-woocommerce'); ?> ${amountSats} sats</p>
</p> <p><?php _e('Network fee:', 'breez-woocommerce'); ?> ${feesSats} sats</p>
</div>
</div> </div>
`; `;
@@ -211,29 +232,32 @@ document.addEventListener('DOMContentLoaded', function() {
clearInterval(statusCheckInterval); clearInterval(statusCheckInterval);
statusCheckInterval = null; statusCheckInterval = null;
} }
} else if (paymentData.status === 'FAILED' || (data.status && data.status === 'FAILED')) { return;
}
if (paymentData.sdk_status === 'FAILED') {
// Stop checking status immediately // Stop checking status immediately
clearInterval(statusCheckInterval); clearInterval(statusCheckInterval);
// Update UI to show payment failed // Show error message
paymentBox.innerHTML = ` paymentBox.innerHTML = `
<h3><?php _e('Bitcoin/Lightning Payment', 'breez-woocommerce'); ?></h3> <h3><?php _e('Bitcoin/Lightning Payment', 'breez-woocommerce'); ?></h3>
<div class="breez-payment-status breez-payment-failed"> <div class="breez-payment-status breez-payment-failed">
<p><?php _e('Payment failed.', 'breez-woocommerce'); ?></p> <p>${paymentData.status_description || '<?php _e('Payment failed or expired.', 'breez-woocommerce'); ?>'}</p>
<p><?php _e('Please try again or contact us for assistance.', 'breez-woocommerce'); ?></p> <p>${paymentData.error || ''}</p>
${paymentData.error ? `<p class="breez-error-details">${paymentData.error}</p>` : ''} <p><?php _e('Please try again or contact support if the problem persists.', 'breez-woocommerce'); ?></p>
</div> </div>
`; `;
return;
// Notify WordPress about the failed payment
notifyServer(paymentData);
// Double check interval is cleared
if (statusCheckInterval) {
clearInterval(statusCheckInterval);
statusCheckInterval = null;
}
} }
// For unknown status, show generic message
statusIndicator.innerHTML = `
<div class="breez-payment-unknown">
<p>${paymentData.status_description || '<?php _e('Checking payment status...', 'breez-woocommerce'); ?>'}</p>
<div class="breez-spinner"></div>
</div>
`;
}) })
.catch(error => { .catch(error => {
console.error('Error checking payment status:', error); console.error('Error checking payment status:', error);

View File

@@ -37,7 +37,7 @@ class Breez_Unit_Test_Case extends WP_UnitTestCase {
update_option('woocommerce_breez_settings', [ update_option('woocommerce_breez_settings', [
'enabled' => 'yes', 'enabled' => 'yes',
'title' => 'Breez Nodeless Payments', 'title' => 'Breez Nodeless Payments',
'description' => 'Pay with Bitcoin via Lightning Network or on-chain transaction.', 'description' => 'Pay with Lightning',
'api_url' => 'https://api.test.breez.com', 'api_url' => 'https://api.test.breez.com',
'api_key' => 'test_api_key', 'api_key' => 'test_api_key',
'webhook_secret' => 'test_webhook_secret', 'webhook_secret' => 'test_webhook_secret',