mirror of
https://github.com/aljazceru/breez-woocommerce.git
synced 2025-12-18 06:24:25 +01:00
initial commit
This commit is contained in:
91
assets/css/breez-woocommerce.css
Normal file
91
assets/css/breez-woocommerce.css
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Breez WooCommerce Styles
|
||||||
|
*/
|
||||||
|
|
||||||
|
.breez-payment-box {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f8f8f8;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-status {
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-completed {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-failed {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-countdown {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-countdown {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-qr {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-qr img {
|
||||||
|
max-width: 250px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-invoice-container {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-invoice-text {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-copy-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background: #0073aa;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-copy-button:hover {
|
||||||
|
background: #005177;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-info {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-refresh {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-expired {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
147
assets/js/blocks.js
Normal file
147
assets/js/blocks.js
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* Breez Payment Method for WooCommerce Blocks
|
||||||
|
* Compatible with WooCommerce Blocks version 9.x
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
// Safe console logging
|
||||||
|
var log = function(msg) {
|
||||||
|
if (window.console && window.console.log) {
|
||||||
|
console.log('[Breez] ' + msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register payment method function
|
||||||
|
function registerBreezPaymentMethod(registerFn) {
|
||||||
|
try {
|
||||||
|
// Create React element for content
|
||||||
|
var Content = function() {
|
||||||
|
return window.wp.element.createElement(
|
||||||
|
'div',
|
||||||
|
null,
|
||||||
|
window.breezSettings?.description || 'Pay with Bitcoin via Lightning Network or on-chain transaction.'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
registerFn({
|
||||||
|
name: 'breez',
|
||||||
|
label: window.breezSettings?.title || 'Breez Nodeless Payments',
|
||||||
|
content: window.wp.element.createElement(Content, null),
|
||||||
|
edit: window.wp.element.createElement(Content, null),
|
||||||
|
canMakePayment: function() { return true; },
|
||||||
|
ariaLabel: window.breezSettings?.title || 'Breez Nodeless Payments',
|
||||||
|
supports: {
|
||||||
|
features: window.breezSettings?.supports || ['products']
|
||||||
|
},
|
||||||
|
paymentMethodId: 'breez',
|
||||||
|
billing: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// Add data to be sent to the server
|
||||||
|
getData: function() {
|
||||||
|
log('Getting payment data');
|
||||||
|
return {
|
||||||
|
payment_method: 'breez',
|
||||||
|
payment_data: {
|
||||||
|
breez_payment_method: 'LIGHTNING'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// Add payment processing
|
||||||
|
onSubmit: function(data) {
|
||||||
|
log('Processing payment submission');
|
||||||
|
return {
|
||||||
|
type: 'success',
|
||||||
|
meta: {
|
||||||
|
paymentMethodData: {
|
||||||
|
payment_method: 'breez',
|
||||||
|
payment_data: {
|
||||||
|
breez_payment_method: 'LIGHTNING'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// Add payment processing status
|
||||||
|
onPaymentProcessing: function() {
|
||||||
|
log('Payment processing started');
|
||||||
|
return {
|
||||||
|
type: 'success',
|
||||||
|
meta: {
|
||||||
|
paymentMethodData: {
|
||||||
|
payment_method: 'breez',
|
||||||
|
payment_data: {
|
||||||
|
breez_payment_method: 'LIGHTNING'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
log('Payment method registered successfully');
|
||||||
|
} catch (error) {
|
||||||
|
log('Error registering payment method: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for DOM content to be loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Only run on checkout page
|
||||||
|
if (!document.body.classList.contains('woocommerce-checkout')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log('Initializing Breez payment method for WooCommerce Blocks 9.x');
|
||||||
|
|
||||||
|
// Check if blocks object exists in newer location
|
||||||
|
if (window.wc && window.wc.blocks && window.wc.blocks.registry &&
|
||||||
|
typeof window.wc.blocks.registry.registerPaymentMethod === 'function') {
|
||||||
|
|
||||||
|
log('Found WC Blocks registry in wc.blocks.registry');
|
||||||
|
registerBreezPaymentMethod(window.wc.blocks.registry.registerPaymentMethod);
|
||||||
|
|
||||||
|
}
|
||||||
|
// Try fallback locations
|
||||||
|
else if (window.wc && window.wc.wcBlocksRegistry &&
|
||||||
|
typeof window.wc.wcBlocksRegistry.registerPaymentMethod === 'function') {
|
||||||
|
|
||||||
|
log('Found WC Blocks registry in wc.wcBlocksRegistry');
|
||||||
|
registerBreezPaymentMethod(window.wc.wcBlocksRegistry.registerPaymentMethod);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Log available WooCommerce structure to help debug
|
||||||
|
var wcPaths = [];
|
||||||
|
|
||||||
|
if (window.wc) {
|
||||||
|
wcPaths.push('wc');
|
||||||
|
|
||||||
|
if (window.wc.blocks) {
|
||||||
|
wcPaths.push('wc.blocks');
|
||||||
|
|
||||||
|
if (window.wc.blocks.registry) {
|
||||||
|
wcPaths.push('wc.blocks.registry');
|
||||||
|
if (typeof window.wc.blocks.registry.registerPaymentMethod === 'function') {
|
||||||
|
wcPaths.push('wc.blocks.registry.registerPaymentMethod (function)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.wc.blocks.payment) {
|
||||||
|
wcPaths.push('wc.blocks.payment');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.wc.wcBlocksRegistry) {
|
||||||
|
wcPaths.push('wc.wcBlocksRegistry');
|
||||||
|
if (typeof window.wc.wcBlocksRegistry.registerPaymentMethod === 'function') {
|
||||||
|
wcPaths.push('wc.wcBlocksRegistry.registerPaymentMethod (function)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wcPaths.length > 0) {
|
||||||
|
log('WooCommerce paths found: ' + wcPaths.join(', '));
|
||||||
|
} else {
|
||||||
|
log('No WooCommerce Blocks registry found. Payment method registration not possible.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
107
assets/js/breez-woocommerce.js
Normal file
107
assets/js/breez-woocommerce.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* Breez WooCommerce JavaScript
|
||||||
|
*/
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Countdown functionality
|
||||||
|
var countdownEl = document.querySelector('.breez-countdown');
|
||||||
|
var countdownContainer = document.querySelector('.breez-payment-countdown');
|
||||||
|
|
||||||
|
if (countdownEl && countdownContainer) {
|
||||||
|
var expiryTime = parseInt(countdownContainer.dataset.expiry, 10);
|
||||||
|
|
||||||
|
if (expiryTime) {
|
||||||
|
var countdownInterval = setInterval(function() {
|
||||||
|
var now = Math.floor(Date.now() / 1000);
|
||||||
|
var timeLeft = expiryTime - now;
|
||||||
|
|
||||||
|
if (timeLeft <= 0) {
|
||||||
|
clearInterval(countdownInterval);
|
||||||
|
countdownContainer.innerHTML = '<p>Payment time expired.</p>';
|
||||||
|
document.querySelector('.breez-payment-qr').style.opacity = '0.3';
|
||||||
|
|
||||||
|
// Reload page to show expired message
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.reload();
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minutes = Math.floor(timeLeft / 60);
|
||||||
|
var seconds = timeLeft % 60;
|
||||||
|
countdownEl.textContent = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy invoice button functionality
|
||||||
|
var copyButton = document.querySelector('.breez-copy-button');
|
||||||
|
if (copyButton) {
|
||||||
|
copyButton.addEventListener('click', function() {
|
||||||
|
var textToCopy = this.dataset.clipboardText;
|
||||||
|
var textarea = document.createElement('textarea');
|
||||||
|
textarea.value = textToCopy;
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
this.textContent = 'Copied!';
|
||||||
|
setTimeout(function() {
|
||||||
|
copyButton.textContent = 'Copy';
|
||||||
|
}, 2000);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy: ', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-refresh payment status
|
||||||
|
function checkPaymentStatus() {
|
||||||
|
var orderStatusCheck = document.querySelector('.breez-payment-box');
|
||||||
|
if (orderStatusCheck && !document.querySelector('.breez-payment-completed') && !document.querySelector('.breez-payment-failed') && !document.querySelector('.breez-payment-expired')) {
|
||||||
|
// Get current URL
|
||||||
|
var currentUrl = window.location.href;
|
||||||
|
|
||||||
|
// Append a random parameter to force a fresh response
|
||||||
|
var refreshUrl = currentUrl + (currentUrl.indexOf('?') > -1 ? '&' : '?') + '_=' + Math.random();
|
||||||
|
|
||||||
|
// Make AJAX request to check status
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', refreshUrl, true);
|
||||||
|
xhr.onload = function() {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
// Parse HTML response
|
||||||
|
var parser = new DOMParser();
|
||||||
|
var doc = parser.parseFromString(xhr.responseText, 'text/html');
|
||||||
|
|
||||||
|
// Find payment box in the response
|
||||||
|
var newPaymentBox = doc.querySelector('.breez-payment-box');
|
||||||
|
|
||||||
|
if (newPaymentBox) {
|
||||||
|
// Check if payment is now completed
|
||||||
|
if (newPaymentBox.querySelector('.breez-payment-completed')) {
|
||||||
|
orderStatusCheck.innerHTML = newPaymentBox.innerHTML;
|
||||||
|
clearInterval(statusInterval);
|
||||||
|
}
|
||||||
|
// Check if payment has failed
|
||||||
|
else if (newPaymentBox.querySelector('.breez-payment-failed')) {
|
||||||
|
orderStatusCheck.innerHTML = newPaymentBox.innerHTML;
|
||||||
|
clearInterval(statusInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send();
|
||||||
|
} else {
|
||||||
|
// If payment is already completed or failed, stop checking
|
||||||
|
clearInterval(statusInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check every 10 seconds
|
||||||
|
var statusInterval = setInterval(checkPaymentStatus, 10000);
|
||||||
|
});
|
||||||
699
breez-woocommerce.php
Executable file
699
breez-woocommerce.php
Executable file
@@ -0,0 +1,699 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Plugin Name: Breez Nodeless Payments for WooCommerce
|
||||||
|
* Plugin URI: https://github.com/breez-nodeless-woocommerce
|
||||||
|
* Description: Accept Bitcoin payments in your WooCommerce store using Breez Nodeless Sdk.
|
||||||
|
* Version: 1.0.0
|
||||||
|
* Author: Aljaz Ceru
|
||||||
|
* Author URI: https://breez.technology
|
||||||
|
* Text Domain: breez-woocommerce
|
||||||
|
* Domain Path: /languages
|
||||||
|
* WC requires at least: 3.0.0
|
||||||
|
* WC tested up to: 8.0.0
|
||||||
|
*
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit; // Exit if accessed directly
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define plugin constants
|
||||||
|
define('BREEZ_WC_VERSION', '1.0.0');
|
||||||
|
define('BREEZ_WC_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||||
|
define('BREEZ_WC_PLUGIN_URL', plugin_dir_url(__FILE__));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display notice if WooCommerce is not installed
|
||||||
|
*/
|
||||||
|
function breez_wc_woocommerce_missing_notice() {
|
||||||
|
$install_url = wp_nonce_url(
|
||||||
|
add_query_arg(
|
||||||
|
[
|
||||||
|
'action' => 'install-plugin',
|
||||||
|
'plugin' => 'woocommerce',
|
||||||
|
],
|
||||||
|
admin_url('update.php')
|
||||||
|
),
|
||||||
|
'install-plugin_woocommerce'
|
||||||
|
);
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<div class="error"><p>%s</p></div>',
|
||||||
|
wp_kses_post(
|
||||||
|
sprintf(
|
||||||
|
'%1$sBreez Nodeless Payments%2$s requires the %3$sWooCommerce plugin%4$s to be active. Please %5$sinstall & activate WooCommerce »%6$s',
|
||||||
|
'<strong>',
|
||||||
|
'</strong>',
|
||||||
|
'<a href="https://wordpress.org/plugins/woocommerce/">',
|
||||||
|
'</a>',
|
||||||
|
'<a href="' . esc_url($install_url) . '">',
|
||||||
|
'</a>'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if WooCommerce is active
|
||||||
|
*/
|
||||||
|
function breez_wc_is_woocommerce_active() {
|
||||||
|
$active_plugins = (array) get_option('active_plugins', array());
|
||||||
|
|
||||||
|
if (is_multisite()) {
|
||||||
|
$active_plugins = array_merge($active_plugins, get_site_option('active_sitewide_plugins', array()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array('woocommerce/woocommerce.php', $active_plugins) || array_key_exists('woocommerce/woocommerce.php', $active_plugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the plugin
|
||||||
|
*/
|
||||||
|
function breez_wc_init() {
|
||||||
|
// Check if WooCommerce is fully loaded
|
||||||
|
if (!class_exists('WC_Payment_Gateway')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Initialize logger first
|
||||||
|
require_once BREEZ_WC_PLUGIN_DIR . 'includes/class-breez-logger.php';
|
||||||
|
$logger = new Breez_Logger('yes' === get_option('woocommerce_breez_debug', 'no'));
|
||||||
|
$logger->log('Initializing Breez WooCommerce plugin', 'debug');
|
||||||
|
|
||||||
|
// Load plugin textdomain
|
||||||
|
load_plugin_textdomain('breez-woocommerce', false, basename(dirname(__FILE__)) . '/languages');
|
||||||
|
|
||||||
|
// Include required files in specific order
|
||||||
|
$required_files = array(
|
||||||
|
'includes/class-breez-api-client.php',
|
||||||
|
'includes/class-breez-db-manager.php',
|
||||||
|
'includes/class-breez-logger.php',
|
||||||
|
'includes/class-breez-webhook-handler.php',
|
||||||
|
'includes/class-breez-payment-handler.php',
|
||||||
|
'class-wc-gateway-breez.php'
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($required_files as $file) {
|
||||||
|
$full_path = BREEZ_WC_PLUGIN_DIR . $file;
|
||||||
|
if (!file_exists($full_path)) {
|
||||||
|
throw new Exception("Required file not found: $file");
|
||||||
|
}
|
||||||
|
require_once $full_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify WooCommerce settings
|
||||||
|
if (!get_option('woocommerce_currency')) {
|
||||||
|
throw new Exception('WooCommerce currency not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register payment gateway
|
||||||
|
add_filter('woocommerce_payment_gateways', 'breez_wc_add_gateway');
|
||||||
|
|
||||||
|
// Add settings link on plugin page
|
||||||
|
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'breez_wc_settings_link');
|
||||||
|
|
||||||
|
// Register webhook handler
|
||||||
|
add_action('rest_api_init', 'breez_wc_register_webhook_endpoint');
|
||||||
|
|
||||||
|
// Schedule payment status check
|
||||||
|
if (!wp_next_scheduled('breez_wc_check_pending_payments')) {
|
||||||
|
wp_schedule_event(time(), 'five_minutes', 'breez_wc_check_pending_payments');
|
||||||
|
$logger->log('Scheduled payment status check', 'debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check API credentials
|
||||||
|
$breez_settings = get_option('woocommerce_breez_settings', array());
|
||||||
|
$api_url = isset($breez_settings['api_url']) ? $breez_settings['api_url'] : '';
|
||||||
|
$api_key = isset($breez_settings['api_key']) ? $breez_settings['api_key'] : '';
|
||||||
|
|
||||||
|
if (!$api_url || !$api_key) {
|
||||||
|
$logger->log('API credentials not configured', 'warning');
|
||||||
|
$logger->log('Settings: ' . print_r($breez_settings, true), 'debug');
|
||||||
|
add_action('admin_notices', function() {
|
||||||
|
echo '<div class="notice notice-warning is-dismissible"><p>' .
|
||||||
|
__('Breez Nodeless Payments requires API credentials to be configured.', 'breez-woocommerce') .
|
||||||
|
' <a href="' . admin_url('admin.php?page=wc-settings&tab=checkout§ion=breez') . '">' .
|
||||||
|
__('Configure Now', 'breez-woocommerce') . '</a></p></div>';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Test API connection
|
||||||
|
try {
|
||||||
|
$client = new Breez_API_Client($api_url, $api_key);
|
||||||
|
if (!$client->check_health()) {
|
||||||
|
throw new Exception('API health check failed');
|
||||||
|
}
|
||||||
|
$logger->log('API connection verified', 'debug');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$logger->log('API connection test failed: ' . $e->getMessage(), 'error');
|
||||||
|
add_action('admin_notices', function() {
|
||||||
|
echo '<div class="notice notice-error is-dismissible"><p>' .
|
||||||
|
__('Breez Nodeless Payments could not connect to the API. Please check your settings.', 'breez-woocommerce') .
|
||||||
|
'</p></div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$logger->log('Plugin initialization completed', 'debug');
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if (isset($logger)) {
|
||||||
|
$logger->log('Plugin initialization failed: ' . $e->getMessage(), 'error');
|
||||||
|
}
|
||||||
|
add_action('admin_notices', function() use ($e) {
|
||||||
|
echo '<div class="notice notice-error is-dismissible"><p>' .
|
||||||
|
__('Breez Nodeless Payments initialization failed: ', 'breez-woocommerce') .
|
||||||
|
esc_html($e->getMessage()) . '</p></div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the Breez gateway to WooCommerce
|
||||||
|
*/
|
||||||
|
function breez_wc_add_gateway($gateways) {
|
||||||
|
if (class_exists('Breez_Logger')) {
|
||||||
|
$logger = new Breez_Logger(true);
|
||||||
|
$logger->log("Adding Breez gateway to WooCommerce", 'debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_exists('WC_Gateway_Breez')) {
|
||||||
|
$gateways[] = 'WC_Gateway_Breez';
|
||||||
|
if (class_exists('Breez_Logger')) {
|
||||||
|
$logger->log("Breez gateway class added successfully", 'debug');
|
||||||
|
}
|
||||||
|
return $gateways;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_exists('Breez_Logger')) {
|
||||||
|
$logger->log("WC_Gateway_Breez class not found", 'error');
|
||||||
|
}
|
||||||
|
return $gateways;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add settings link on plugin page
|
||||||
|
*/
|
||||||
|
function breez_wc_settings_link($links) {
|
||||||
|
$settings_link = '<a href="admin.php?page=wc-settings&tab=checkout§ion=breez">' . __('Settings', 'breez-woocommerce') . '</a>';
|
||||||
|
array_unshift($links, $settings_link);
|
||||||
|
return $links;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register webhook endpoint
|
||||||
|
*/
|
||||||
|
function breez_wc_register_webhook_endpoint() {
|
||||||
|
register_rest_route('breez-wc/v1', '/webhook', array(
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => array('Breez_Webhook_Handler', 'process_webhook'),
|
||||||
|
'permission_callback' => array('Breez_Webhook_Handler', 'validate_webhook'),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Register payment status check endpoint
|
||||||
|
register_rest_route('breez-wc/v1', '/check-payment-status', array(
|
||||||
|
'methods' => ['GET', 'POST'],
|
||||||
|
'callback' => 'breez_wc_check_payment_status_endpoint',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
'args' => array(
|
||||||
|
'order_id' => array(
|
||||||
|
'required' => true,
|
||||||
|
'type' => 'integer',
|
||||||
|
'sanitize_callback' => 'absint',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Register direct payment status check endpoint with invoice ID
|
||||||
|
register_rest_route('breez-wc/v1', '/check-payment-status/(?P<invoice_id>[\w-]+)', array(
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => 'breez_wc_check_payment_status_by_invoice_endpoint',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Direct payment status check endpoint handler (with invoice ID in URL)
|
||||||
|
*/
|
||||||
|
function breez_wc_check_payment_status_by_invoice_endpoint($request) {
|
||||||
|
try {
|
||||||
|
// Initialize logger
|
||||||
|
$logger = new Breez_Logger('yes' === get_option('woocommerce_breez_debug', 'no'));
|
||||||
|
$logger->log('Direct payment status check endpoint called', 'debug');
|
||||||
|
|
||||||
|
$invoice_id = $request->get_param('invoice_id');
|
||||||
|
$logger->log("Checking payment status for invoice ID: $invoice_id", 'debug');
|
||||||
|
|
||||||
|
// Initialize API client - directly fetch gateway settings
|
||||||
|
$gateway_settings = get_option('woocommerce_breez_settings');
|
||||||
|
$api_url = isset($gateway_settings['api_url']) ? $gateway_settings['api_url'] : '';
|
||||||
|
$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)) {
|
||||||
|
$logger->log("API credentials not configured", 'error');
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Payment gateway not properly configured'
|
||||||
|
), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
$client = new Breez_API_Client($api_url, $api_key);
|
||||||
|
$payment_status = $client->check_payment_status($invoice_id);
|
||||||
|
$logger->log("Payment status for invoice ID $invoice_id: " . print_r($payment_status, true), 'debug');
|
||||||
|
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => true,
|
||||||
|
'status' => $payment_status['status'],
|
||||||
|
'data' => $payment_status
|
||||||
|
), 200);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if (isset($logger)) {
|
||||||
|
$logger->log('Payment status check failed: ' . $e->getMessage(), 'error');
|
||||||
|
$logger->log('Stack trace: ' . $e->getTraceAsString(), 'debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Internal server error: ' . $e->getMessage()
|
||||||
|
), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment status check endpoint handler
|
||||||
|
*/
|
||||||
|
function breez_wc_check_payment_status_endpoint($request) {
|
||||||
|
try {
|
||||||
|
// Initialize logger
|
||||||
|
$logger = new Breez_Logger('yes' === get_option('woocommerce_breez_debug', 'no'));
|
||||||
|
$logger->log('Payment status check endpoint called', 'debug');
|
||||||
|
|
||||||
|
$order_id = $request->get_param('order_id');
|
||||||
|
$logger->log("Checking payment status for order #$order_id", 'debug');
|
||||||
|
|
||||||
|
$order = wc_get_order($order_id);
|
||||||
|
if (!$order) {
|
||||||
|
$logger->log("Order #$order_id not found", 'error');
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Order not found'
|
||||||
|
), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow checking status of Breez payments
|
||||||
|
if ($order->get_payment_method() !== 'breez') {
|
||||||
|
$logger->log("Invalid payment method for order #$order_id: " . $order->get_payment_method(), 'error');
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Invalid payment method'
|
||||||
|
), 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a POST request, update the order based on the payment data
|
||||||
|
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');
|
||||||
|
$api_url = isset($gateway_settings['api_url']) ? $gateway_settings['api_url'] : '';
|
||||||
|
$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)) {
|
||||||
|
$logger->log("API credentials not configured", 'error');
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Payment gateway not properly configured'
|
||||||
|
), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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');
|
||||||
|
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => true,
|
||||||
|
'status' => $payment_status['status'],
|
||||||
|
'data' => $payment_status
|
||||||
|
), 200);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if (isset($logger)) {
|
||||||
|
$logger->log('Payment status check failed: ' . $e->getMessage(), 'error');
|
||||||
|
$logger->log('Stack trace: ' . $e->getTraceAsString(), 'debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Internal server error: ' . $e->getMessage()
|
||||||
|
), 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check pending payments (runs via cron)
|
||||||
|
*/
|
||||||
|
function breez_wc_check_pending_payments() {
|
||||||
|
$payment_handler = new Breez_Payment_Handler();
|
||||||
|
$payment_handler->check_pending_payments();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin activation hook
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
require_once BREEZ_WC_PLUGIN_DIR . 'includes/class-breez-logger.php';
|
||||||
|
require_once BREEZ_WC_PLUGIN_DIR . 'includes/class-breez-db-manager.php';
|
||||||
|
|
||||||
|
// Create required directories if they don't exist
|
||||||
|
$directories = array(
|
||||||
|
BREEZ_WC_PLUGIN_DIR . 'includes/admin',
|
||||||
|
BREEZ_WC_PLUGIN_DIR . 'includes/block',
|
||||||
|
BREEZ_WC_PLUGIN_DIR . 'assets/js',
|
||||||
|
BREEZ_WC_PLUGIN_DIR . 'assets/css',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($directories as $directory) {
|
||||||
|
if (!file_exists($directory)) {
|
||||||
|
wp_mkdir_p($directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create database tables
|
||||||
|
$db_manager = new Breez_DB_Manager();
|
||||||
|
$db_manager->install_tables();
|
||||||
|
|
||||||
|
// Schedule payment status check
|
||||||
|
if (!wp_next_scheduled('breez_wc_check_pending_payments')) {
|
||||||
|
wp_schedule_event(time(), 'five_minutes', 'breez_wc_check_pending_payments');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush rewrite rules for webhook endpoint
|
||||||
|
flush_rewrite_rules();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin deactivation hook
|
||||||
|
*/
|
||||||
|
function breez_wc_deactivate() {
|
||||||
|
// Unschedule payment status check
|
||||||
|
$timestamp = wp_next_scheduled('breez_wc_check_pending_payments');
|
||||||
|
if ($timestamp) {
|
||||||
|
wp_unschedule_event($timestamp, 'breez_wc_check_pending_payments');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush rewrite rules
|
||||||
|
flush_rewrite_rules();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if WooCommerce is active before initializing
|
||||||
|
if (breez_wc_is_woocommerce_active()) {
|
||||||
|
// Initialize after WooCommerce is fully loaded
|
||||||
|
add_action('woocommerce_init', 'breez_wc_init');
|
||||||
|
|
||||||
|
// Register activation/deactivation hooks
|
||||||
|
register_activation_hook(__FILE__, 'breez_wc_activate');
|
||||||
|
register_deactivation_hook(__FILE__, 'breez_wc_deactivate');
|
||||||
|
|
||||||
|
// Add gateway after WooCommerce payment gateways are registered
|
||||||
|
add_action('woocommerce_payment_gateways_init', function() {
|
||||||
|
add_filter('woocommerce_payment_gateways', 'breez_wc_add_gateway');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register the payment gateway with WooCommerce Blocks
|
||||||
|
add_action('woocommerce_blocks_loaded', function() {
|
||||||
|
// Re-enabled with support for WooCommerce 9.8.3
|
||||||
|
if (class_exists('Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType')) {
|
||||||
|
require_once BREEZ_WC_PLUGIN_DIR . 'includes/class-breez-blocks-support.php';
|
||||||
|
|
||||||
|
// Register scripts for blocks
|
||||||
|
add_action('wp_enqueue_scripts', 'breez_register_blocks_scripts');
|
||||||
|
|
||||||
|
add_action('woocommerce_blocks_payment_method_type_registration', function($payment_method_registry) {
|
||||||
|
$payment_method_registry->register(new Breez_Blocks_Support());
|
||||||
|
|
||||||
|
if (class_exists('Breez_Logger')) {
|
||||||
|
$logger = new Breez_Logger(true);
|
||||||
|
$logger->log('Registered Breez with WooCommerce Blocks', 'debug');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log blocks integration
|
||||||
|
if (class_exists('Breez_Logger')) {
|
||||||
|
$logger = new Breez_Logger(true);
|
||||||
|
$logger->log('WooCommerce Blocks integration enabled for 9.8.3', 'debug');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Display admin notice if WooCommerce is not active
|
||||||
|
add_action('admin_notices', 'breez_wc_woocommerce_missing_notice');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register scripts for WooCommerce Blocks
|
||||||
|
*/
|
||||||
|
function breez_register_blocks_scripts() {
|
||||||
|
if (!is_checkout() && !is_cart()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = get_option('woocommerce_breez_settings', []);
|
||||||
|
|
||||||
|
// Only load if WooCommerce Blocks is active
|
||||||
|
if (!class_exists('Automattic\WooCommerce\Blocks\Package')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the blocks script
|
||||||
|
wp_register_script(
|
||||||
|
'breez-blocks',
|
||||||
|
BREEZ_WC_PLUGIN_URL . 'includes/block/breez-payment.js',
|
||||||
|
[
|
||||||
|
'wp-element',
|
||||||
|
'wp-components',
|
||||||
|
'wc-blocks-registry',
|
||||||
|
'wp-blocks',
|
||||||
|
'wp-i18n',
|
||||||
|
'wp-html-entities',
|
||||||
|
'wc-blocks-components',
|
||||||
|
'wc-settings'
|
||||||
|
],
|
||||||
|
BREEZ_WC_VERSION,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add settings for the blocks script
|
||||||
|
wp_localize_script(
|
||||||
|
'breez-blocks',
|
||||||
|
'wcSettings',
|
||||||
|
[
|
||||||
|
'breez' => [
|
||||||
|
'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.',
|
||||||
|
'supports' => ['products'],
|
||||||
|
'showSavedCards' => false,
|
||||||
|
'canMakePayment' => true,
|
||||||
|
'paymentMethodId' => 'breez',
|
||||||
|
'orderButtonText' => __('Proceed to Payment', 'breez-woocommerce'),
|
||||||
|
'defaultPaymentMethod' => 'lightning',
|
||||||
|
'paymentMethodData' => [
|
||||||
|
'breez_payment_method' => 'lightning'
|
||||||
|
],
|
||||||
|
'ajaxUrl' => admin_url('admin-ajax.php'),
|
||||||
|
'nonce' => wp_create_nonce('breez-payment')
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Enqueue the script
|
||||||
|
wp_enqueue_script('breez-blocks');
|
||||||
|
|
||||||
|
if (class_exists('Breez_Logger')) {
|
||||||
|
$logger = new Breez_Logger(true);
|
||||||
|
$logger->log('Breez blocks script registered and localized', 'debug');
|
||||||
|
$logger->log('Script dependencies: ' . print_r(wp_scripts()->registered['breez-blocks']->deps, true), 'debug');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_action('plugins_loaded', function() {
|
||||||
|
if (class_exists('Breez_Logger')) {
|
||||||
|
$logger = new Breez_Logger(true);
|
||||||
|
$logger->log('Breez plugin diagnostics running', 'debug');
|
||||||
|
|
||||||
|
// Check database tables
|
||||||
|
global $wpdb;
|
||||||
|
$table_name = $wpdb->prefix . 'wc_breez_payments';
|
||||||
|
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") === $table_name;
|
||||||
|
$logger->log("Database table check: " . ($table_exists ? 'exists' : 'missing'), 'debug');
|
||||||
|
|
||||||
|
// Check API settings
|
||||||
|
$api_url = get_option('woocommerce_breez_api_url');
|
||||||
|
$api_key = get_option('woocommerce_breez_api_key');
|
||||||
|
$payment_methods = get_option('woocommerce_breez_payment_methods');
|
||||||
|
$logger->log("Configuration: API URL: " . (empty($api_url) ? 'missing' : 'set') .
|
||||||
|
", API Key: " . (empty($api_key) ? 'missing' : 'set') .
|
||||||
|
", Payment Methods: " . print_r($payment_methods, true), 'debug');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add WooCommerce Blocks compatibility
|
||||||
|
add_action('before_woocommerce_init', function() {
|
||||||
|
if (class_exists('\Automattic\WooCommerce\Utilities\FeaturesUtil')) {
|
||||||
|
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('cart_checkout_blocks', __FILE__, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle AJAX request for payment URL
|
||||||
|
*/
|
||||||
|
function breez_handle_payment_url() {
|
||||||
|
check_ajax_referer('breez-payment', 'nonce');
|
||||||
|
|
||||||
|
$order_id = isset($_POST['order_id']) ? intval($_POST['order_id']) : 0;
|
||||||
|
$order = wc_get_order($order_id);
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
wp_send_json_error(['message' => 'Order not found']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payment_url = $order->get_meta('_breez_payment_url');
|
||||||
|
if (!$payment_url) {
|
||||||
|
wp_send_json_error(['message' => 'Payment URL not found']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_send_json_success(['payment_url' => $payment_url]);
|
||||||
|
}
|
||||||
|
add_action('wp_ajax_breez_get_payment_url', 'breez_handle_payment_url');
|
||||||
|
add_action('wp_ajax_nopriv_breez_get_payment_url', 'breez_handle_payment_url');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set CORS headers for API endpoints
|
||||||
|
*/
|
||||||
|
function breez_wc_set_cors_headers() {
|
||||||
|
// Only set CORS headers for our endpoints
|
||||||
|
if (isset($_SERVER['REQUEST_URI']) &&
|
||||||
|
(strpos($_SERVER['REQUEST_URI'], '/breez-wc/v1/') !== false ||
|
||||||
|
strpos($_SERVER['REQUEST_URI'], '/wc-api/wc_gateway_breez') !== false)) {
|
||||||
|
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, X-API-Key, Authorization');
|
||||||
|
|
||||||
|
// Handle preflight requests
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
status_header(200);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('init', 'breez_wc_set_cors_headers', 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register blocks checkout data filter
|
||||||
|
*/
|
||||||
|
function breez_wc_register_blocks_checkout_filters() {
|
||||||
|
add_filter('woocommerce_store_api_checkout_update_order_from_request', 'breez_wc_filter_blocks_checkout_order_data', 10, 2);
|
||||||
|
}
|
||||||
|
add_action('init', 'breez_wc_register_blocks_checkout_filters');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter checkout API data to handle Breez payment data
|
||||||
|
*
|
||||||
|
* @param WC_Order $order
|
||||||
|
* @param WP_REST_Request $request
|
||||||
|
* @return WC_Order
|
||||||
|
*/
|
||||||
|
function breez_wc_filter_blocks_checkout_order_data($order, $request) {
|
||||||
|
if (class_exists('Breez_Logger')) {
|
||||||
|
$logger = new Breez_Logger(true);
|
||||||
|
$logger->log('Filtering checkout order data for blocks', 'debug');
|
||||||
|
$logger->log('Request data: ' . print_r($request->get_params(), true), 'debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = $request->get_params();
|
||||||
|
|
||||||
|
// Check if Breez payment method is active
|
||||||
|
if (isset($params['payment_method']) && $params['payment_method'] === 'breez') {
|
||||||
|
if (isset($params['payment_data'])) {
|
||||||
|
// Get payment data
|
||||||
|
$payment_data = $params['payment_data'];
|
||||||
|
|
||||||
|
// Store it as order meta for processing
|
||||||
|
$order->update_meta_data('_breez_blocks_payment_data', $payment_data);
|
||||||
|
$order->save();
|
||||||
|
|
||||||
|
if (class_exists('Breez_Logger')) {
|
||||||
|
$logger->log('Stored Breez payment data in order meta', 'debug');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order;
|
||||||
|
}
|
||||||
692
class-wc-gateway-breez.php
Normal file
692
class-wc-gateway-breez.php
Normal file
@@ -0,0 +1,692 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Breez Nodeless Payments
|
||||||
|
*
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit; // Exit if accessed directly
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Breez Nodeless Payments
|
||||||
|
*
|
||||||
|
* Provides a Bitcoin & Lightning Payment Gateway for WooCommerce.
|
||||||
|
*
|
||||||
|
* @class WC_Gateway_Breez
|
||||||
|
* @extends WC_Payment_Gateway
|
||||||
|
* @version 1.0.0
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
class WC_Gateway_Breez extends WC_Payment_Gateway {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Client instance
|
||||||
|
*
|
||||||
|
* @var Breez_API_Client
|
||||||
|
*/
|
||||||
|
protected $client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DB Manager instance
|
||||||
|
*
|
||||||
|
* @var Breez_DB_Manager
|
||||||
|
*/
|
||||||
|
protected $db_manager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment Handler instance
|
||||||
|
*
|
||||||
|
* @var Breez_Payment_Handler
|
||||||
|
*/
|
||||||
|
protected $payment_handler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger instance
|
||||||
|
*
|
||||||
|
* @var Breez_Logger
|
||||||
|
*/
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the gateway.
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
$this->id = 'breez';
|
||||||
|
$this->icon = apply_filters('woocommerce_breez_icon', '');
|
||||||
|
$this->has_fields = false;
|
||||||
|
$this->method_title = __('Breez NodeLess Payments', 'breez-woocommerce');
|
||||||
|
$this->method_description = __('Accept Lightning payments with Breez Nodeless SDK', 'breez-woocommerce');
|
||||||
|
$this->supports = array(
|
||||||
|
'products',
|
||||||
|
'refunds'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Initialize plugin settings
|
||||||
|
$this->init_form_fields();
|
||||||
|
$this->init_settings();
|
||||||
|
|
||||||
|
// Define user set variables
|
||||||
|
$this->title = $this->get_option('title');
|
||||||
|
$this->description = $this->get_option('description');
|
||||||
|
$this->instructions = $this->get_option('instructions');
|
||||||
|
$this->enabled = $this->get_option('enabled');
|
||||||
|
$this->testmode = 'yes' === $this->get_option('testmode');
|
||||||
|
$this->debug = 'yes' === $this->get_option('debug');
|
||||||
|
$this->api_url = $this->get_option('api_url');
|
||||||
|
$this->api_key = $this->get_option('api_key');
|
||||||
|
$this->expiry_minutes = (int) $this->get_option('expiry_minutes', 30);
|
||||||
|
$this->payment_methods = $this->get_option('payment_methods', 'lightning,onchain');
|
||||||
|
// Convert payment_methods to array if it's a string
|
||||||
|
if (is_string($this->payment_methods)) {
|
||||||
|
$this->payment_methods = array_filter(array_map('trim', explode(',', $this->payment_methods)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize logger first for comprehensive logging
|
||||||
|
$this->logger = new Breez_Logger($this->debug);
|
||||||
|
$this->logger->log('Initializing Breez Nodeless Payments gateway', 'debug');
|
||||||
|
$this->logger->log('Current settings: ' . print_r($this->settings, true), 'debug');
|
||||||
|
|
||||||
|
// Validate API credentials
|
||||||
|
if ($this->enabled === 'yes' && (!$this->api_url || !$this->api_key)) {
|
||||||
|
$this->enabled = 'no';
|
||||||
|
$this->logger->log('Gateway disabled - missing API credentials', 'error');
|
||||||
|
$this->logger->log('API URL: ' . ($this->api_url ? $this->api_url : 'MISSING'), 'error');
|
||||||
|
$this->logger->log('API Key: ' . ($this->api_key ? 'SET' : 'MISSING'), 'error');
|
||||||
|
add_action('admin_notices', array($this, 'admin_api_notice'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize client, DB manager, payment handler
|
||||||
|
try {
|
||||||
|
$this->client = new Breez_API_Client(
|
||||||
|
$this->api_url,
|
||||||
|
$this->api_key
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->db_manager = new Breez_DB_Manager();
|
||||||
|
$this->payment_handler = new Breez_Payment_Handler($this->client, $this->db_manager);
|
||||||
|
|
||||||
|
// Validate payment methods
|
||||||
|
if ($this->enabled === 'yes') {
|
||||||
|
if (empty($this->payment_methods)) {
|
||||||
|
$this->enabled = 'no';
|
||||||
|
$this->logger->log('Gateway disabled - no payment methods selected', 'error');
|
||||||
|
add_action('admin_notices', array($this, 'admin_payment_methods_notice'));
|
||||||
|
} else {
|
||||||
|
// Register webhook endpoint
|
||||||
|
add_action('woocommerce_api_wc_gateway_breez', array($this, 'webhook_handler'));
|
||||||
|
|
||||||
|
// Initialize webhook handler
|
||||||
|
$this->webhook_handler = new Breez_Webhook_Handler($this->client, $this->db_manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->enabled = 'no';
|
||||||
|
$this->logger->log('Gateway initialization failed: ' . $e->getMessage(), 'error');
|
||||||
|
add_action('admin_notices', array($this, 'admin_api_error_notice'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hooks
|
||||||
|
add_action('woocommerce_update_options_payment_gateways_' . $this->id, array($this, 'process_admin_options'));
|
||||||
|
add_action('woocommerce_thankyou_' . $this->id, array($this, 'thankyou_page'));
|
||||||
|
add_action('woocommerce_email_before_order_table', array($this, 'email_instructions'), 10, 3);
|
||||||
|
|
||||||
|
// Register webhook URL with Breez API when settings are saved
|
||||||
|
add_action('woocommerce_settings_saved', array($this, 'register_webhook_url'));
|
||||||
|
|
||||||
|
// Schedule payment status checks
|
||||||
|
if (!wp_next_scheduled('breez_check_pending_payments')) {
|
||||||
|
wp_schedule_event(time(), 'five_minutes', 'breez_check_pending_payments');
|
||||||
|
}
|
||||||
|
|
||||||
|
// WooCommerce Blocks integration is now registered in the main plugin file
|
||||||
|
// for better control over load order
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Gateway Settings Form Fields
|
||||||
|
*/
|
||||||
|
public function init_form_fields() {
|
||||||
|
$this->form_fields = require BREEZ_WC_PLUGIN_DIR . 'includes/admin/breez-settings.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display admin notice for missing API credentials
|
||||||
|
*/
|
||||||
|
public function admin_api_notice() {
|
||||||
|
echo '<div class="error"><p>' .
|
||||||
|
__('Breez Nodeless Payments requires API URL and API Key to be configured. Please configure these in the gateway settings.', 'breez-woocommerce') .
|
||||||
|
'</p></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display admin notice for missing payment methods
|
||||||
|
*/
|
||||||
|
public function admin_payment_methods_notice() {
|
||||||
|
echo '<div class="error"><p>' .
|
||||||
|
__('Breez Nodeless Payments requires at least one payment method to be selected. Please configure payment methods in the gateway settings.', 'breez-woocommerce') .
|
||||||
|
'</p></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display admin notice for API initialization error
|
||||||
|
*/
|
||||||
|
public function admin_api_error_notice() {
|
||||||
|
echo '<div class="error"><p>' .
|
||||||
|
__('Breez Nodeless Payments encountered an error during initialization. Please check the logs for more details.', 'breez-woocommerce') .
|
||||||
|
'</p></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register webhook URL with Breez API
|
||||||
|
*/
|
||||||
|
public function register_webhook_url() {
|
||||||
|
if ('yes' !== $this->get_option('enabled')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$webhook_url = get_rest_url(null, 'breez-wc/v1/webhook');
|
||||||
|
$this->logger->log("Attempting to register webhook URL: $webhook_url", 'debug');
|
||||||
|
|
||||||
|
// Skip webhook registration in test mode
|
||||||
|
if ($this->testmode) {
|
||||||
|
$this->logger->log('Webhook registration skipped - test mode enabled', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->client->register_webhook($webhook_url);
|
||||||
|
if ($result) {
|
||||||
|
$this->logger->log("Successfully registered webhook URL", 'info');
|
||||||
|
} else {
|
||||||
|
$this->logger->log("Failed to register webhook URL - webhooks may not be supported", 'warning');
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Log error but don't block gateway activation
|
||||||
|
$this->logger->log("Webhook registration failed: " . $e->getMessage(), 'error');
|
||||||
|
|
||||||
|
// Only show admin notice if not a 404 (which likely means webhooks aren't supported)
|
||||||
|
if (strpos($e->getMessage(), '404') === false) {
|
||||||
|
add_action('admin_notices', function() {
|
||||||
|
echo '<div class="notice notice-warning is-dismissible"><p>' .
|
||||||
|
__('Breez webhook registration failed. Real-time payment notifications may not work.', 'breez-woocommerce') .
|
||||||
|
' ' . __('The system will fall back to periodic payment status checks.', 'breez-woocommerce') .
|
||||||
|
'</p></div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle webhook callback
|
||||||
|
*/
|
||||||
|
public function webhook_handler() {
|
||||||
|
if (!$this->webhook_handler) {
|
||||||
|
$this->logger->log('Webhook handler not initialized', 'error');
|
||||||
|
wp_send_json_error('Webhook handler not initialized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->webhook_handler->process_webhook();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get available payment methods as array
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_available_payment_methods() {
|
||||||
|
if (empty($this->payment_methods)) {
|
||||||
|
return ['lightning']; // Default to lightning if nothing is set
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($this->payment_methods)) {
|
||||||
|
return array_filter(array_map('trim', $this->payment_methods));
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_filter(array_map('trim', explode(',', $this->payment_methods)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the payment and return the result
|
||||||
|
*
|
||||||
|
* @param int $order_id
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function process_payment($order_id) {
|
||||||
|
$this->logger->log("Processing payment for order #$order_id", 'debug');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$order = wc_get_order($order_id);
|
||||||
|
if (!$order) {
|
||||||
|
throw new Exception(__('Order not found', 'breez-woocommerce'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the selected payment method
|
||||||
|
$payment_method = $this->get_payment_method_from_request();
|
||||||
|
|
||||||
|
// Check for blocks payment data in order meta
|
||||||
|
$blocks_payment_data = $order->get_meta('_breez_blocks_payment_data');
|
||||||
|
if (!$payment_method && $blocks_payment_data) {
|
||||||
|
$this->logger->log("Found blocks payment data in order meta", 'debug');
|
||||||
|
$data = is_string($blocks_payment_data) ? json_decode($blocks_payment_data, true) : $blocks_payment_data;
|
||||||
|
if (is_array($data) && isset($data['breez_payment_method'])) {
|
||||||
|
$payment_method = sanitize_text_field($data['breez_payment_method']);
|
||||||
|
$this->logger->log("Using payment method from blocks data: $payment_method", 'debug');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$payment_method) {
|
||||||
|
throw new Exception(__('No payment method selected', 'breez-woocommerce'));
|
||||||
|
}
|
||||||
|
$this->logger->log("Selected payment method: $payment_method", 'debug');
|
||||||
|
|
||||||
|
// Validate payment method
|
||||||
|
$available_methods = $this->get_available_payment_methods();
|
||||||
|
if (!in_array($payment_method, $available_methods)) {
|
||||||
|
throw new Exception(__('Invalid payment method', 'breez-woocommerce'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the order total and convert to satoshis
|
||||||
|
$order_total = $order->get_total();
|
||||||
|
$currency = $order->get_currency();
|
||||||
|
|
||||||
|
// Get exchange rate and convert to satoshis
|
||||||
|
$exchange_rate_response = $this->client->request('GET', "/exchange_rates/{$currency}");
|
||||||
|
if (!$exchange_rate_response || !isset($exchange_rate_response['rate'])) {
|
||||||
|
throw new Exception(__('Failed to get exchange rate', 'breez-woocommerce'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$btc_amount = $order_total / $exchange_rate_response['rate'];
|
||||||
|
$amount_sat = (int)($btc_amount * 100000000); // Convert BTC to sats
|
||||||
|
|
||||||
|
$this->logger->log("Order total: {$order_total} {$currency}, Amount in sats: {$amount_sat}", 'debug');
|
||||||
|
|
||||||
|
if ($amount_sat <= 0) {
|
||||||
|
throw new Exception(__('Invalid amount conversion', 'breez-woocommerce'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create payment
|
||||||
|
$payment_data = array(
|
||||||
|
'amount' => $amount_sat, // Send amount in satoshis
|
||||||
|
'method' => $payment_method === 'onchain' ? 'BITCOIN_ADDRESS' : 'LIGHTNING',
|
||||||
|
'description' => sprintf(__('Payment for order #%s', 'breez-woocommerce'), $order->get_order_number())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Log the payment data being sent
|
||||||
|
$this->logger->log("Creating payment with data: " . print_r($payment_data, true), 'debug');
|
||||||
|
|
||||||
|
// Create the payment
|
||||||
|
try {
|
||||||
|
$response = $this->client->request('POST', '/receive_payment', $payment_data);
|
||||||
|
$this->logger->log("Payment creation response: " . print_r($response, true), 'debug');
|
||||||
|
|
||||||
|
if (!$response || !isset($response['destination'])) {
|
||||||
|
throw new Exception(__('Failed to create payment', 'breez-woocommerce'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save payment data to order meta
|
||||||
|
$order->update_meta_data('_breez_payment_id', $response['destination']);
|
||||||
|
$order->update_meta_data('_breez_invoice_id', $response['destination']);
|
||||||
|
$order->update_meta_data('_breez_payment_method', $payment_method);
|
||||||
|
$order->update_meta_data('_breez_payment_amount', $order_total);
|
||||||
|
$order->update_meta_data('_breez_payment_amount_sat', $amount_sat);
|
||||||
|
$order->update_meta_data('_breez_payment_currency', $currency);
|
||||||
|
$order->update_meta_data('_breez_payment_status', 'pending');
|
||||||
|
$order->update_meta_data('_breez_payment_created', time());
|
||||||
|
$order->update_meta_data('_breez_payment_expires', time() + ($this->expiry_minutes * 60));
|
||||||
|
|
||||||
|
// Set order status to pending
|
||||||
|
$order->update_status('pending', __('Awaiting Bitcoin/Lightning payment', 'breez-woocommerce'));
|
||||||
|
|
||||||
|
// Add order note
|
||||||
|
$order->add_order_note(
|
||||||
|
sprintf(__('Breez payment created. Method: %s, ID: %s, Amount: %s sats', 'breez-woocommerce'),
|
||||||
|
$payment_method,
|
||||||
|
$response['destination'],
|
||||||
|
$amount_sat
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save the order
|
||||||
|
$order->save();
|
||||||
|
$this->logger->log("Order meta data updated for #$order_id", 'debug');
|
||||||
|
|
||||||
|
// Reduce stock levels
|
||||||
|
wc_reduce_stock_levels($order_id);
|
||||||
|
|
||||||
|
// Empty cart
|
||||||
|
WC()->cart->empty_cart();
|
||||||
|
|
||||||
|
// Return success with redirect to order-received page
|
||||||
|
$return_url = $this->get_return_url($order);
|
||||||
|
$this->logger->log("Redirecting to: $return_url", 'debug');
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'result' => 'success',
|
||||||
|
'redirect' => $return_url
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log('Payment creation error: ' . $e->getMessage(), 'error');
|
||||||
|
throw new Exception(__('Payment creation failed: ', 'breez-woocommerce') . $e->getMessage());
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log('Payment processing error: ' . $e->getMessage(), 'error');
|
||||||
|
$order->update_status('failed', __('Payment error: ', 'breez-woocommerce') . $e->getMessage());
|
||||||
|
return $this->get_error_response($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get payment method from request
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function get_payment_method_from_request() {
|
||||||
|
// Priority 1: Direct POST parameter for breez_payment_method (from blocks)
|
||||||
|
if (isset($_POST['breez_payment_method'])) {
|
||||||
|
$method = sanitize_text_field($_POST['breez_payment_method']);
|
||||||
|
$this->logger->log("Payment method found in direct POST: $method", 'debug');
|
||||||
|
return $method;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 2: Payment data from gateway form
|
||||||
|
if (isset($_POST['payment_method']) && $_POST['payment_method'] === 'breez') {
|
||||||
|
$this->logger->log('Processing checkout payment via standard form', 'debug');
|
||||||
|
|
||||||
|
// Check for payment data
|
||||||
|
if (isset($_POST['payment_data'])) {
|
||||||
|
$raw_data = wp_unslash($_POST['payment_data']);
|
||||||
|
$this->logger->log('Raw payment data: ' . print_r($raw_data, true), 'debug');
|
||||||
|
|
||||||
|
// Handle nested payment data structure
|
||||||
|
if (is_string($raw_data)) {
|
||||||
|
$decoded_data = json_decode($raw_data, true);
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE) {
|
||||||
|
$raw_data = $decoded_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract payment method
|
||||||
|
if (is_array($raw_data) && isset($raw_data['breez_payment_method'])) {
|
||||||
|
$method = sanitize_text_field($raw_data['breez_payment_method']);
|
||||||
|
$this->logger->log("Payment method found in payment_data: $method", 'debug');
|
||||||
|
return $method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 3: From WC Blocks checkout
|
||||||
|
if (isset($_POST['wc-breez-payment-data'])) {
|
||||||
|
$blocks_data = json_decode(wp_unslash($_POST['wc-breez-payment-data']), true);
|
||||||
|
if (is_array($blocks_data) && isset($blocks_data['breez_payment_method'])) {
|
||||||
|
$method = sanitize_text_field($blocks_data['breez_payment_method']);
|
||||||
|
$this->logger->log("Payment method found in blocks data: $method", 'debug');
|
||||||
|
return $method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default fallback
|
||||||
|
$available_methods = $this->get_available_payment_methods();
|
||||||
|
$default_method = reset($available_methods); // Get first available method
|
||||||
|
$this->logger->log("Using default payment method: $default_method", 'debug');
|
||||||
|
return $default_method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thank you page content
|
||||||
|
*
|
||||||
|
* @param int $order_id
|
||||||
|
*/
|
||||||
|
public function thankyou_page($order_id) {
|
||||||
|
$this->logger->log("Entering thankyou_page for order #$order_id", 'debug');
|
||||||
|
|
||||||
|
$order = wc_get_order($order_id);
|
||||||
|
if (!$order) {
|
||||||
|
$this->logger->log("Order #$order_id not found", 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only display for our payment method
|
||||||
|
if ($order->get_payment_method() !== $this->id) {
|
||||||
|
$this->logger->log("Order #$order_id payment method is not breez: " . $order->get_payment_method(), 'debug');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get invoice ID
|
||||||
|
$invoice_id = $order->get_meta('_breez_invoice_id');
|
||||||
|
if (!$invoice_id) {
|
||||||
|
$this->logger->log("No invoice ID found for order #$order_id", 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payment_method = $order->get_meta('_breez_payment_method');
|
||||||
|
$expiry = $order->get_meta('_breez_payment_expires');
|
||||||
|
$amount_sat = $order->get_meta('_breez_payment_amount_sat');
|
||||||
|
$current_time = time();
|
||||||
|
|
||||||
|
$this->logger->log("Payment details for order #$order_id:", 'debug');
|
||||||
|
$this->logger->log("- Invoice ID: $invoice_id", 'debug');
|
||||||
|
$this->logger->log("- Payment Method: $payment_method", 'debug');
|
||||||
|
$this->logger->log("- Expiry: $expiry", 'debug');
|
||||||
|
$this->logger->log("- Amount (sats): $amount_sat", 'debug');
|
||||||
|
|
||||||
|
// Check if payment is expired
|
||||||
|
if ($expiry && $current_time > $expiry) {
|
||||||
|
$this->logger->log("Payment expired for order #$order_id", 'debug');
|
||||||
|
$order->update_status('failed', __('Lightning payment expired', 'breez-woocommerce'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check current payment status
|
||||||
|
$payment_status = 'pending';
|
||||||
|
try {
|
||||||
|
$payment_status = $this->client->check_payment_status($invoice_id);
|
||||||
|
$this->logger->log("Current payment status for order #$order_id: $payment_status", 'debug');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log("Error checking payment status: " . $e->getMessage(), 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update order status if needed
|
||||||
|
if ($payment_status === 'SUCCEEDED' && $order->get_status() === 'pending') {
|
||||||
|
$order->payment_complete();
|
||||||
|
$order->add_order_note(__('Payment confirmed via Breez API.', 'breez-woocommerce'));
|
||||||
|
$this->logger->log("Payment for order #$order_id confirmed via API check", 'debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the payment instructions template
|
||||||
|
$template_path = BREEZ_WC_PLUGIN_DIR . 'templates/payment-instructions.php';
|
||||||
|
$this->logger->log("Loading template from: $template_path", 'debug');
|
||||||
|
|
||||||
|
if (!file_exists($template_path)) {
|
||||||
|
$this->logger->log("Template file not found!", 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the payment instructions template
|
||||||
|
wc_get_template(
|
||||||
|
'payment-instructions.php',
|
||||||
|
array(
|
||||||
|
'order' => $order,
|
||||||
|
'invoice_id' => $invoice_id,
|
||||||
|
'payment_method' => $payment_method,
|
||||||
|
'expiry' => $expiry,
|
||||||
|
'current_time' => $current_time,
|
||||||
|
'payment_status' => $payment_status
|
||||||
|
),
|
||||||
|
'',
|
||||||
|
BREEZ_WC_PLUGIN_DIR . 'templates/'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->logger->log("Template loaded successfully for order #$order_id", 'debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add content to the WC emails.
|
||||||
|
*
|
||||||
|
* @param WC_Order $order
|
||||||
|
* @param bool $sent_to_admin
|
||||||
|
* @param bool $plain_text
|
||||||
|
*/
|
||||||
|
public function email_instructions($order, $sent_to_admin, $plain_text = false) {
|
||||||
|
if ($sent_to_admin || $order->get_payment_method() !== $this->id || $order->has_status('processing')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoice_id = $order->get_meta('_breez_invoice_id');
|
||||||
|
if (!$invoice_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($plain_text) {
|
||||||
|
echo "\n\n" . $this->instructions . "\n\n";
|
||||||
|
echo __('Invoice/Address: ', 'breez-woocommerce') . $invoice_id . "\n";
|
||||||
|
} else {
|
||||||
|
echo '<h2>' . __('Payment Information', 'breez-woocommerce') . '</h2>';
|
||||||
|
echo '<p>' . $this->instructions . '</p>';
|
||||||
|
echo '<p><strong>' . __('Invoice/Address: ', 'breez-woocommerce') . '</strong> ' . $invoice_id . '</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this gateway is available
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function is_available() {
|
||||||
|
// Get fresh settings from database
|
||||||
|
$this->init_settings();
|
||||||
|
$api_url = $this->get_option('api_url');
|
||||||
|
$api_key = $this->get_option('api_key');
|
||||||
|
$payment_methods = $this->get_option('payment_methods');
|
||||||
|
|
||||||
|
$this->logger->log("Gateway availability check", 'debug');
|
||||||
|
$this->logger->log("Settings from DB: api_url=" . ($api_url ? $api_url : 'MISSING') .
|
||||||
|
", api_key=" . ($api_key ? 'SET' : 'MISSING') .
|
||||||
|
", payment_methods=" . print_r($payment_methods, true), 'debug');
|
||||||
|
|
||||||
|
if ($this->enabled === 'no') {
|
||||||
|
$this->logger->log("Gateway disabled in settings", 'debug');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check API credentials
|
||||||
|
if (empty($api_url) || empty($api_key)) {
|
||||||
|
$this->logger->log("API credentials missing - URL: " . (empty($api_url) ? 'No' : 'Yes') .
|
||||||
|
", Key: " . (empty($api_key) ? 'No' : 'Yes'), 'debug');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update class variables with fresh settings
|
||||||
|
$this->api_url = $api_url;
|
||||||
|
$this->api_key = $api_key;
|
||||||
|
$this->payment_methods = $payment_methods;
|
||||||
|
|
||||||
|
// Check payment methods
|
||||||
|
if (empty($this->payment_methods)) {
|
||||||
|
$this->logger->log("No payment methods selected", 'debug');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check payment handler
|
||||||
|
if (!isset($this->payment_handler) || !$this->payment_handler) {
|
||||||
|
$this->logger->log("Payment handler not initialized", 'debug');
|
||||||
|
|
||||||
|
// Try to initialize payment handler on-demand
|
||||||
|
try {
|
||||||
|
if (!isset($this->client)) {
|
||||||
|
$this->client = new Breez_API_Client($this->api_url, $this->api_key);
|
||||||
|
}
|
||||||
|
if (!isset($this->db_manager)) {
|
||||||
|
$this->db_manager = new Breez_DB_Manager();
|
||||||
|
}
|
||||||
|
$this->payment_handler = new Breez_Payment_Handler($this->client, $this->db_manager);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log("Failed to initialize payment handler: " . $e->getMessage(), 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check exchange rate
|
||||||
|
try {
|
||||||
|
$rate = $this->payment_handler->get_btc_rate();
|
||||||
|
if (!$rate) {
|
||||||
|
$this->logger->log("Unable to get exchange rate", 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->logger->log("Exchange rate: $rate", 'debug');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log("Exchange rate error: " . $e->getMessage(), 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->log("Gateway available", 'debug');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a refund if supported
|
||||||
|
*
|
||||||
|
* @param int $order_id
|
||||||
|
* @param float $amount
|
||||||
|
* @param string $reason
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function process_refund($order_id, $amount = null, $reason = '') {
|
||||||
|
$order = wc_get_order($order_id);
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
$this->logger->log("Error: Order #$order_id not found for refund");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->log("Manual refund requested for order #$order_id, amount: $amount, reason: $reason");
|
||||||
|
|
||||||
|
// For now, just log the request and notify that manual refund is required
|
||||||
|
$order->add_order_note(
|
||||||
|
sprintf(
|
||||||
|
__('Refund of %s requested. Bitcoin/Lightning payments require manual refund processing. Reason: %s', 'breez-woocommerce'),
|
||||||
|
wc_price($amount),
|
||||||
|
$reason
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduce stock levels
|
||||||
|
*
|
||||||
|
* @param int $order_id
|
||||||
|
*/
|
||||||
|
public function reduce_stock_levels($order_id) {
|
||||||
|
$order = wc_get_order($order_id);
|
||||||
|
|
||||||
|
if ($order) {
|
||||||
|
$this->logger->log("Reducing stock levels for order #{$order_id}", 'debug');
|
||||||
|
wc_reduce_stock_levels($order_id);
|
||||||
|
$this->logger->log("Stock levels reduced for order #{$order_id}", 'info');
|
||||||
|
} else {
|
||||||
|
$this->logger->log("Failed to reduce stock: Order #{$order_id} not found", 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a standardized error response
|
||||||
|
*
|
||||||
|
* @param string $message Error message
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function get_error_response($message = '') {
|
||||||
|
$this->logger->log('Payment error: ' . $message, 'error');
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'result' => 'failure',
|
||||||
|
'redirect' => '',
|
||||||
|
'message' => $message ? $message : __('An error occurred during the payment process.', 'breez-woocommerce'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
composer.json
Normal file
19
composer.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "breez/woocommerce",
|
||||||
|
"description": "Bitcoin & Lightning payment gateway for WooCommerce using Breez Nodeless API",
|
||||||
|
"type": "wordpress-plugin",
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2",
|
||||||
|
"aferrandini/phpqrcode": "^1.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Breez\\WooCommerce\\": "includes/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"optimize-autoloader": true,
|
||||||
|
"preferred-install": "dist",
|
||||||
|
"sort-packages": true
|
||||||
|
}
|
||||||
|
}
|
||||||
107
includes/admin/breez-settings.php
Normal file
107
includes/admin/breez-settings.php
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Breez Nodeless Payments Settings
|
||||||
|
*
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit; // Exit if accessed directly
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'enabled' => array(
|
||||||
|
'title' => __('Enable/Disable', 'breez-woocommerce'),
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'label' => __('Enable Breez Nodeless Payments', 'breez-woocommerce'),
|
||||||
|
'default' => 'no'
|
||||||
|
),
|
||||||
|
'title' => array(
|
||||||
|
'title' => __('Title', 'breez-woocommerce'),
|
||||||
|
'type' => 'text',
|
||||||
|
'description' => __('This controls the title which the user sees during checkout.', 'breez-woocommerce'),
|
||||||
|
'default' => __('Breez Nodeless Payments', 'breez-woocommerce'),
|
||||||
|
'desc_tip' => true,
|
||||||
|
),
|
||||||
|
'description' => array(
|
||||||
|
'title' => __('Description', 'breez-woocommerce'),
|
||||||
|
'type' => 'textarea',
|
||||||
|
'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'),
|
||||||
|
'desc_tip' => true,
|
||||||
|
),
|
||||||
|
'instructions' => array(
|
||||||
|
'title' => __('Instructions', 'breez-woocommerce'),
|
||||||
|
'type' => 'textarea',
|
||||||
|
'description' => __('Instructions that will be added to the thank you page and emails.', 'breez-woocommerce'),
|
||||||
|
'default' => __('Scan the QR code or copy the invoice/address to complete your payment.', 'breez-woocommerce'),
|
||||||
|
'desc_tip' => true,
|
||||||
|
),
|
||||||
|
'api_settings' => array(
|
||||||
|
'title' => __('API Settings', 'breez-woocommerce'),
|
||||||
|
'type' => 'title',
|
||||||
|
'description' => __('Enter your Breez API credentials below.', 'breez-woocommerce'),
|
||||||
|
),
|
||||||
|
'api_url' => array(
|
||||||
|
'title' => __('API URL', 'breez-woocommerce'),
|
||||||
|
'type' => 'text',
|
||||||
|
'description' => __('Enter your Breez API URL.', 'breez-woocommerce'),
|
||||||
|
'default' => 'http://localhost:8000',
|
||||||
|
'desc_tip' => true,
|
||||||
|
),
|
||||||
|
'api_key' => array(
|
||||||
|
'title' => __('API Key', 'breez-woocommerce'),
|
||||||
|
'type' => 'password',
|
||||||
|
'description' => __('Enter your Breez API key.', 'breez-woocommerce'),
|
||||||
|
'default' => '',
|
||||||
|
'desc_tip' => true,
|
||||||
|
),
|
||||||
|
'payment_options' => array(
|
||||||
|
'title' => __('Payment Options', 'breez-woocommerce'),
|
||||||
|
'type' => 'title',
|
||||||
|
'description' => __('Configure payment behavior.', 'breez-woocommerce'),
|
||||||
|
),
|
||||||
|
'payment_methods' => array(
|
||||||
|
'title' => __('Payment Methods', 'breez-woocommerce'),
|
||||||
|
'type' => 'multiselect',
|
||||||
|
'class' => 'wc-enhanced-select',
|
||||||
|
'description' => __('Select the payment methods you want to accept.', 'breez-woocommerce'),
|
||||||
|
'default' => array('lightning', 'onchain'),
|
||||||
|
'options' => array(
|
||||||
|
'lightning' => __('Lightning Network', 'breez-woocommerce'),
|
||||||
|
'onchain' => __('On-chain Bitcoin', 'breez-woocommerce'),
|
||||||
|
),
|
||||||
|
'desc_tip' => true,
|
||||||
|
),
|
||||||
|
'expiry_minutes' => array(
|
||||||
|
'title' => __('Invoice Expiry Time', 'breez-woocommerce'),
|
||||||
|
'type' => 'number',
|
||||||
|
'description' => __('Number of minutes after which unpaid invoices expire.', 'breez-woocommerce'),
|
||||||
|
'default' => 30,
|
||||||
|
'desc_tip' => true,
|
||||||
|
'custom_attributes' => array(
|
||||||
|
'min' => 5,
|
||||||
|
'max' => 1440,
|
||||||
|
'step' => 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'advanced_options' => array(
|
||||||
|
'title' => __('Advanced Options', 'breez-woocommerce'),
|
||||||
|
'type' => 'title',
|
||||||
|
'description' => __('Additional settings for advanced users.', 'breez-woocommerce'),
|
||||||
|
),
|
||||||
|
'debug' => array(
|
||||||
|
'title' => __('Debug Log', 'breez-woocommerce'),
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'label' => __('Enable logging', 'breez-woocommerce'),
|
||||||
|
'default' => 'no',
|
||||||
|
'description' => __('Log Breez payment events, such as API requests, inside the WooCommerce logs directory.', 'breez-woocommerce'),
|
||||||
|
),
|
||||||
|
'testmode' => array(
|
||||||
|
'title' => __('Test Mode', 'breez-woocommerce'),
|
||||||
|
'type' => 'checkbox',
|
||||||
|
'label' => __('Enable Test Mode', 'breez-woocommerce'),
|
||||||
|
'default' => 'no',
|
||||||
|
'description' => __('Place the payment gateway in test mode.', 'breez-woocommerce'),
|
||||||
|
),
|
||||||
|
);
|
||||||
10
includes/block/breez-payment.asset.php
Normal file
10
includes/block/breez-payment.asset.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
return array(
|
||||||
|
'dependencies' => array(
|
||||||
|
'wp-blocks',
|
||||||
|
'wp-element',
|
||||||
|
'wp-components',
|
||||||
|
'wc-blocks-registry'
|
||||||
|
),
|
||||||
|
'version' => BREEZ_WC_VERSION
|
||||||
|
);
|
||||||
86
includes/block/breez-payment.js
Normal file
86
includes/block/breez-payment.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Breez Payment Method for WooCommerce Blocks
|
||||||
|
*/
|
||||||
|
const { registerPaymentMethod } = window.wc.wcBlocksRegistry;
|
||||||
|
const { createElement, useEffect } = window.wp.element;
|
||||||
|
const { Fragment } = window.wp.element;
|
||||||
|
const { decodeEntities } = window.wp.htmlEntities;
|
||||||
|
const { PaymentMethodLabel, PaymentMethodIcon } = window.wc.blocksComponents || {};
|
||||||
|
|
||||||
|
const BreezLabel = () => {
|
||||||
|
return createElement('div', { className: 'wc-block-components-payment-method-label' },
|
||||||
|
createElement('span', {}, window.wcSettings?.breez?.title || 'Breez Nodeless Payments')
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BreezComponent = () => {
|
||||||
|
const description = window.wcSettings?.breez?.description || 'Pay with Bitcoin via Lightning Network or on-chain transaction.';
|
||||||
|
return createElement('div', { className: 'wc-block-components-payment-method-description' },
|
||||||
|
decodeEntities(description)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Payment processing component
|
||||||
|
const ProcessPayment = ({ eventRegistration }) => {
|
||||||
|
const { onPaymentProcessing } = eventRegistration;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = onPaymentProcessing(() => {
|
||||||
|
console.log('Breez payment processing started');
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
type: 'success',
|
||||||
|
meta: {
|
||||||
|
paymentMethodData: {
|
||||||
|
payment_method: 'breez',
|
||||||
|
breez_payment_method: window.wcSettings?.breez?.defaultPaymentMethod || 'lightning'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Breez payment processing error:', error);
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
message: error.message || 'Payment processing failed'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
|
}, [onPaymentProcessing]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the Breez payment method
|
||||||
|
const breezPaymentMethod = {
|
||||||
|
name: 'breez',
|
||||||
|
label: createElement(BreezLabel),
|
||||||
|
content: createElement(BreezComponent),
|
||||||
|
edit: createElement(BreezComponent),
|
||||||
|
canMakePayment: () => true,
|
||||||
|
ariaLabel: window.wcSettings?.breez?.title || 'Breez Nodeless Payments',
|
||||||
|
paymentMethodId: 'breez',
|
||||||
|
supports: {
|
||||||
|
features: window.wcSettings?.breez?.supports || ['products'],
|
||||||
|
showSavedCards: false,
|
||||||
|
showSaveOption: false
|
||||||
|
},
|
||||||
|
billing: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
breez_payment_method: window.wcSettings?.breez?.defaultPaymentMethod || 'lightning'
|
||||||
|
},
|
||||||
|
placeOrderButtonLabel: window.wcSettings?.breez?.orderButtonText || 'Pay with Bitcoin',
|
||||||
|
processPayment: createElement(ProcessPayment)
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
registerPaymentMethod(breezPaymentMethod);
|
||||||
|
console.log('Breez payment method registered successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to register Breez payment method:', error);
|
||||||
|
}
|
||||||
8
includes/block/breez-widget.asset.php
Normal file
8
includes/block/breez-widget.asset.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
return array(
|
||||||
|
'dependencies' => array(
|
||||||
|
'wp-polyfill',
|
||||||
|
'jquery'
|
||||||
|
),
|
||||||
|
'version' => BREEZ_WC_VERSION
|
||||||
|
);
|
||||||
8
includes/block/breez-widget.js
Normal file
8
includes/block/breez-widget.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Breez Payment Widget for WooCommerce Blocks
|
||||||
|
*/
|
||||||
|
(function ($) {
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Initialize any client-side functionality here
|
||||||
|
});
|
||||||
|
})(jQuery);
|
||||||
464
includes/class-breez-api-client.php
Normal file
464
includes/class-breez-api-client.php
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Breez API Client
|
||||||
|
*
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit; // Exit if accessed directly
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Breez API Client Class
|
||||||
|
*
|
||||||
|
* Handles communication with the Breez API.
|
||||||
|
*/
|
||||||
|
class Breez_API_Client {
|
||||||
|
/**
|
||||||
|
* API URL
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $api_url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Key
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $api_key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger instance
|
||||||
|
*
|
||||||
|
* @var Breez_Logger
|
||||||
|
*/
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param string $api_url API URL
|
||||||
|
* @param string $api_key API Key
|
||||||
|
*/
|
||||||
|
public function __construct($api_url, $api_key) {
|
||||||
|
$this->api_url = rtrim($api_url, '/');
|
||||||
|
$this->api_key = $api_key;
|
||||||
|
$this->logger = new Breez_Logger('yes' === get_option('woocommerce_breez_debug', 'no'));
|
||||||
|
|
||||||
|
if (!$this->api_url || !$this->api_key) {
|
||||||
|
$this->logger->log('API client initialized without credentials', 'error');
|
||||||
|
throw new Exception('API credentials are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate API URL format
|
||||||
|
if (!filter_var($this->api_url, FILTER_VALIDATE_URL)) {
|
||||||
|
$this->logger->log('Invalid API URL format: ' . $this->api_url, 'error');
|
||||||
|
throw new Exception('API URL must be a valid URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->log('API client initialized', 'debug', array(
|
||||||
|
'api_url' => $this->api_url,
|
||||||
|
'api_key_set' => !empty($this->api_key)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check API connectivity
|
||||||
|
*
|
||||||
|
* @return bool True if API is accessible, false otherwise
|
||||||
|
*/
|
||||||
|
public function check_health() {
|
||||||
|
// In test mode, bypass the health check
|
||||||
|
if ('yes' === get_option('woocommerce_breez_testmode', 'no')) {
|
||||||
|
$this->logger->log('API health check bypassed in test mode', 'debug');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$this->logger->log('Starting API health check', 'debug');
|
||||||
|
$response = $this->request('GET', '/health');
|
||||||
|
$this->logger->log('API health check response: ' . json_encode($response), 'debug');
|
||||||
|
return isset($response['status']) && $response['status'] === 'ok';
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log('API health check failed: ' . $e->getMessage(), 'error');
|
||||||
|
$this->logger->log('Stack trace: ' . $e->getTraceAsString(), 'debug');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a payment request
|
||||||
|
*
|
||||||
|
* @param int $amount Amount in satoshis
|
||||||
|
* @param string $method Payment method (LIGHTNING, BITCOIN_ADDRESS, LIQUID_ADDRESS)
|
||||||
|
* @param string $description Payment description
|
||||||
|
* @return array|false Payment details on success, false on failure
|
||||||
|
*/
|
||||||
|
public function receive_payment($amount, $method = 'LIGHTNING', $description = '') {
|
||||||
|
$data = array(
|
||||||
|
'amount' => $amount,
|
||||||
|
'method' => $method,
|
||||||
|
'description' => $description
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->request('POST', '/receive_payment', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check payment status using API endpoint
|
||||||
|
*
|
||||||
|
* @param string $invoice_id Invoice ID or payment destination
|
||||||
|
* @return array Payment status response
|
||||||
|
*/
|
||||||
|
public function check_payment_status($invoice_id) {
|
||||||
|
try {
|
||||||
|
$response = $this->request('GET', "/check_payment_status/{$invoice_id}");
|
||||||
|
|
||||||
|
if (!$response) {
|
||||||
|
throw new Exception('Failed to check payment status');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the raw API response for debugging
|
||||||
|
$this->logger->log('Payment status check response', 'debug', array(
|
||||||
|
'invoice_id' => $invoice_id,
|
||||||
|
'response' => $response
|
||||||
|
));
|
||||||
|
|
||||||
|
// If the payment is not found, return pending instead of throwing an error
|
||||||
|
if (isset($response['error']) && $response['error'] === 'Payment not found') {
|
||||||
|
return array(
|
||||||
|
'status' => 'pending',
|
||||||
|
'destination' => $invoice_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the response as is
|
||||||
|
return $response;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log('Payment status check error: ' . $e->getMessage(), 'error');
|
||||||
|
// Return pending status instead of throwing an error
|
||||||
|
return array(
|
||||||
|
'status' => 'pending',
|
||||||
|
'destination' => $invoice_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a webhook URL
|
||||||
|
*
|
||||||
|
* @param string $webhook_url Webhook URL
|
||||||
|
* @return array|false Response on success, false on failure
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Check if webhooks are supported by the API
|
||||||
|
*
|
||||||
|
* @return bool True if webhooks are supported
|
||||||
|
*/
|
||||||
|
public function check_webhook_support() {
|
||||||
|
try {
|
||||||
|
// Try to get API endpoints/capabilities
|
||||||
|
$response = $this->request('GET', '/capabilities', array(), 1);
|
||||||
|
|
||||||
|
// If capabilities endpoint exists, check webhook support
|
||||||
|
if ($response && isset($response['features'])) {
|
||||||
|
return in_array('webhooks', $response['features']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If capabilities endpoint doesn't exist, try webhook endpoint directly
|
||||||
|
$this->request('GET', '/register_webhook', array(), 1);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// If we get a 404, webhooks aren't supported
|
||||||
|
if (strpos($e->getMessage(), '404') !== false) {
|
||||||
|
$this->logger->log('Webhooks not supported by API', 'debug');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other errors, assume webhooks might be supported
|
||||||
|
$this->logger->log('Webhook support check failed: ' . $e->getMessage(), 'warning');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a webhook URL
|
||||||
|
*
|
||||||
|
* @param string $webhook_url Webhook URL
|
||||||
|
* @return bool True if registration successful
|
||||||
|
* @throws Exception if registration fails
|
||||||
|
*/
|
||||||
|
public function register_webhook($webhook_url) {
|
||||||
|
if (!$webhook_url) {
|
||||||
|
throw new Exception('Webhook URL is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->log('Registering webhook', 'debug', array(
|
||||||
|
'url' => $webhook_url
|
||||||
|
));
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = array(
|
||||||
|
'webhook_url' => $webhook_url
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = $this->request('POST', '/register_webhook', $data, 1);
|
||||||
|
|
||||||
|
if ($response && isset($response['success']) && $response['success']) {
|
||||||
|
$this->logger->log('Webhook registration successful', 'info');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->log('Webhook registration failed - invalid response', 'error');
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// If we get a 404, webhooks aren't supported
|
||||||
|
if (strpos($e->getMessage(), '404') !== false) {
|
||||||
|
$this->logger->log('Webhook registration failed - endpoint not found', 'debug');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-throw other errors
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get payment by ID
|
||||||
|
*
|
||||||
|
* @param string $payment_hash Payment hash
|
||||||
|
* @return array|false Payment details on success, false on failure
|
||||||
|
*/
|
||||||
|
public function get_payment($payment_hash) {
|
||||||
|
return $this->request('GET', "/payment/{$payment_hash}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all payments
|
||||||
|
*
|
||||||
|
* @param array $params Optional query parameters
|
||||||
|
* @return array|false List of payments on success, false on failure
|
||||||
|
*/
|
||||||
|
public function list_payments($params = array()) {
|
||||||
|
$query_string = '';
|
||||||
|
if (!empty($params)) {
|
||||||
|
$query_string = '?' . http_build_query($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->request('GET', "/list_payments{$query_string}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make API request
|
||||||
|
*
|
||||||
|
* @param string $method HTTP method
|
||||||
|
* @param string $endpoint API endpoint
|
||||||
|
* @param array $data Request data
|
||||||
|
* @param int $max_retries Maximum number of retries
|
||||||
|
* @return array|false Response data on success, false on failure
|
||||||
|
*/
|
||||||
|
public function request($method, $endpoint, $data = array(), $max_retries = 2) {
|
||||||
|
// Normalize the endpoint to ensure it starts with a slash
|
||||||
|
$endpoint = ltrim($endpoint, '/');
|
||||||
|
|
||||||
|
// Full API URL
|
||||||
|
$url = $this->api_url . '/' . $endpoint;
|
||||||
|
|
||||||
|
$this->logger->log('Making API request', 'debug', array(
|
||||||
|
'method' => $method,
|
||||||
|
'url' => $url
|
||||||
|
));
|
||||||
|
|
||||||
|
$args = array(
|
||||||
|
'method' => $method,
|
||||||
|
'timeout' => 30,
|
||||||
|
'headers' => array(
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'x-api-key' => $this->api_key,
|
||||||
|
'Accept' => 'application/json'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($data) && $method !== 'GET') {
|
||||||
|
$args['body'] = json_encode($data);
|
||||||
|
} elseif (!empty($data) && $method === 'GET') {
|
||||||
|
// For GET requests, append query string
|
||||||
|
$url = add_query_arg($data, $url);
|
||||||
|
}
|
||||||
|
|
||||||
|
$retries = 0;
|
||||||
|
$response_data = false;
|
||||||
|
|
||||||
|
while ($retries <= $max_retries) {
|
||||||
|
$response = wp_remote_request($url, $args);
|
||||||
|
|
||||||
|
if (is_wp_error($response)) {
|
||||||
|
$error_message = $response->get_error_message();
|
||||||
|
$this->logger->log('API request error', 'error', array(
|
||||||
|
'message' => $error_message,
|
||||||
|
'attempt' => $retries + 1
|
||||||
|
));
|
||||||
|
|
||||||
|
$retries++;
|
||||||
|
if ($retries <= $max_retries) {
|
||||||
|
$this->logger->log('Retrying request', 'debug', array(
|
||||||
|
'attempt' => $retries
|
||||||
|
));
|
||||||
|
// Exponential backoff
|
||||||
|
sleep(pow(2, $retries - 1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception('API request failed: ' . $error_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
$http_code = wp_remote_retrieve_response_code($response);
|
||||||
|
$body = wp_remote_retrieve_body($response);
|
||||||
|
|
||||||
|
$this->logger->log('API response received', 'debug', array(
|
||||||
|
'http_code' => $http_code,
|
||||||
|
'body_length' => strlen($body)
|
||||||
|
));
|
||||||
|
|
||||||
|
if ($http_code >= 200 && $http_code < 300) {
|
||||||
|
if (!empty($body)) {
|
||||||
|
$response_data = json_decode($body, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
$this->logger->log('JSON decode error', 'error', array(
|
||||||
|
'error' => json_last_error_msg(),
|
||||||
|
'body_excerpt' => substr($body, 0, 100) . (strlen($body) > 100 ? '...' : '')
|
||||||
|
));
|
||||||
|
|
||||||
|
// If we can't decode JSON, try to return the raw body
|
||||||
|
$response_data = array(
|
||||||
|
'raw_response' => $body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Empty but successful response
|
||||||
|
$response_data = array(
|
||||||
|
'success' => true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success - break out of retry loop
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
// Handle error
|
||||||
|
$message = $body;
|
||||||
|
|
||||||
|
if (!empty($body)) {
|
||||||
|
$decoded = json_decode($body, true);
|
||||||
|
if (is_array($decoded) && isset($decoded['message'])) {
|
||||||
|
$message = $decoded['message'];
|
||||||
|
} elseif (is_array($decoded) && isset($decoded['error'])) {
|
||||||
|
$message = $decoded['error'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 404 might be normal in some cases (checking if endpoint exists)
|
||||||
|
$error_level = ($http_code == 404) ? 'debug' : 'error';
|
||||||
|
$this->logger->log('API error response', $error_level, array(
|
||||||
|
'http_code' => $http_code,
|
||||||
|
'message' => $message,
|
||||||
|
'endpoint' => $endpoint,
|
||||||
|
'attempt' => $retries + 1
|
||||||
|
));
|
||||||
|
|
||||||
|
if ($http_code == 404 || ($http_code >= 500 && $retries < $max_retries)) {
|
||||||
|
$retries++;
|
||||||
|
if ($retries <= $max_retries) {
|
||||||
|
$this->logger->log('Retrying request', 'debug', array(
|
||||||
|
'attempt' => $retries
|
||||||
|
));
|
||||||
|
// Exponential backoff
|
||||||
|
sleep(pow(2, $retries - 1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception("API error ($http_code): $message");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new payment
|
||||||
|
*
|
||||||
|
* @param array $payment_data Payment data including amount, currency, description, etc.
|
||||||
|
* @return array Payment details
|
||||||
|
* @throws Exception if payment creation fails
|
||||||
|
*/
|
||||||
|
public function create_payment($payment_data) {
|
||||||
|
$this->logger->log('Creating payment with data: ' . print_r($payment_data, true), 'debug');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Prepare the API request data according to ReceivePaymentBody schema
|
||||||
|
$api_data = array(
|
||||||
|
'amount' => $payment_data['amount_sat'], // Amount must be in satoshis
|
||||||
|
'method' => strtoupper($payment_data['payment_method']), // LIGHTNING or BITCOIN_ADDRESS
|
||||||
|
'description' => $payment_data['description'] ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make the API request to create payment
|
||||||
|
$response = $this->request('POST', '/receive_payment', $api_data);
|
||||||
|
|
||||||
|
if (!$response || !isset($response['destination'])) {
|
||||||
|
throw new Exception('Invalid API response: Missing payment destination');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the response to match what the gateway expects
|
||||||
|
return array(
|
||||||
|
'id' => $response['destination'], // Use destination as ID
|
||||||
|
'invoice_id' => $response['destination'],
|
||||||
|
'payment_url' => $response['destination'], // For QR code generation
|
||||||
|
'payment_request' => $response['destination'],
|
||||||
|
'status' => 'PENDING',
|
||||||
|
'amount' => $payment_data['amount'],
|
||||||
|
'amount_sat' => $payment_data['amount_sat'],
|
||||||
|
'currency' => $payment_data['currency'],
|
||||||
|
'created_at' => time(),
|
||||||
|
'expires_at' => time() + ($payment_data['expires_in'] ?? 1800),
|
||||||
|
'fees_sat' => $response['fees_sat'] ?? 0
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log('Payment creation failed: ' . $e->getMessage(), 'error');
|
||||||
|
throw new Exception('Failed to create payment: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert fiat amount to satoshis
|
||||||
|
*
|
||||||
|
* @param float $amount Amount in fiat
|
||||||
|
* @param string $currency Currency code
|
||||||
|
* @return int Amount in satoshis
|
||||||
|
* @throws Exception if conversion fails
|
||||||
|
*/
|
||||||
|
private function convert_to_sats($amount, $currency) {
|
||||||
|
try {
|
||||||
|
// Try to get the exchange rate from the API
|
||||||
|
$response = $this->request('GET', '/exchange_rates/' . strtoupper($currency));
|
||||||
|
|
||||||
|
if (!$response || !isset($response['rate'])) {
|
||||||
|
throw new Exception('Invalid exchange rate response');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate satoshis
|
||||||
|
$btc_amount = $amount / $response['rate'];
|
||||||
|
return (int)($btc_amount * 100000000); // Convert BTC to sats
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log('Currency conversion failed: ' . $e->getMessage(), 'error');
|
||||||
|
throw new Exception('Failed to convert currency: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
224
includes/class-breez-blocks-support.php
Normal file
224
includes/class-breez-blocks-support.php
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* WooCommerce Blocks Integration for Breez Nodeless Payments
|
||||||
|
*
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit; // Exit if accessed directly
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Breez Blocks Support
|
||||||
|
*/
|
||||||
|
class Breez_Blocks_Support extends Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType {
|
||||||
|
/**
|
||||||
|
* Name of the payment method
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name = 'breez';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gateway instance
|
||||||
|
*
|
||||||
|
* @var WC_Gateway_Breez
|
||||||
|
*/
|
||||||
|
private $gateway;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger instance
|
||||||
|
*
|
||||||
|
* @var Breez_Logger
|
||||||
|
*/
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup Blocks integration
|
||||||
|
*/
|
||||||
|
public function initialize() {
|
||||||
|
$this->settings = get_option('woocommerce_breez_settings', []);
|
||||||
|
|
||||||
|
// Initialize logger
|
||||||
|
$this->logger = new Breez_Logger(true);
|
||||||
|
$this->logger->log('Initializing Breez Blocks Support', 'debug');
|
||||||
|
|
||||||
|
// Create payment gateway instance if not exists
|
||||||
|
if (!isset($this->gateway) && class_exists('WC_Gateway_Breez')) {
|
||||||
|
$this->gateway = new WC_Gateway_Breez();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log initialization for debugging
|
||||||
|
if (isset($this->gateway)) {
|
||||||
|
$this->logger->log('Gateway instance created successfully', 'debug');
|
||||||
|
$this->logger->log('Current settings: ' . print_r($this->settings, true), 'debug');
|
||||||
|
} else {
|
||||||
|
$this->logger->log('Failed to create gateway instance', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this payment method is active
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function is_active() {
|
||||||
|
$is_active = false;
|
||||||
|
|
||||||
|
if (isset($this->gateway)) {
|
||||||
|
$is_active = $this->gateway->is_available();
|
||||||
|
$this->logger->log('Gateway availability check: ' . ($is_active ? 'yes' : 'no'), 'debug');
|
||||||
|
|
||||||
|
if (!$is_active) {
|
||||||
|
$this->logger->log('Gateway not available. Settings: ' . print_r($this->settings, true), 'debug');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$is_active = !empty($this->settings['enabled']) && 'yes' === $this->settings['enabled'];
|
||||||
|
$this->logger->log('Fallback availability check: ' . ($is_active ? 'yes' : 'no'), 'debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $is_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of scripts/handles to be registered for this payment method.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_payment_method_script_handles() {
|
||||||
|
$this->logger->log('Registering payment method script handles', 'debug');
|
||||||
|
|
||||||
|
$handles = [];
|
||||||
|
|
||||||
|
// Register and enqueue the widget script
|
||||||
|
$widget_handle = 'breez-widget';
|
||||||
|
$widget_asset_file = plugin_dir_path(__FILE__) . 'block/breez-widget.asset.php';
|
||||||
|
$widget_asset_data = file_exists($widget_asset_file) ? require($widget_asset_file) : array('dependencies' => array('wp-polyfill', 'jquery'), 'version' => '1.0.0');
|
||||||
|
|
||||||
|
wp_register_script(
|
||||||
|
$widget_handle,
|
||||||
|
plugins_url('block/breez-widget.js', __FILE__),
|
||||||
|
$widget_asset_data['dependencies'],
|
||||||
|
$widget_asset_data['version'],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register and enqueue the payment script
|
||||||
|
$payment_handle = 'breez-blocks';
|
||||||
|
$payment_asset_file = plugin_dir_path(__FILE__) . 'block/breez-payment.asset.php';
|
||||||
|
$payment_asset_data = file_exists($payment_asset_file) ? require($payment_asset_file) : array('dependencies' => array('wp-blocks', 'wp-element', 'wp-components', 'wc-blocks-registry'), 'version' => '1.0.0');
|
||||||
|
|
||||||
|
wp_register_script(
|
||||||
|
$payment_handle,
|
||||||
|
plugins_url('block/breez-payment.js', __FILE__),
|
||||||
|
$payment_asset_data['dependencies'],
|
||||||
|
$payment_asset_data['version'],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
if (function_exists('wp_set_script_translations')) {
|
||||||
|
wp_set_script_translations($widget_handle, 'breez-woocommerce');
|
||||||
|
wp_set_script_translations($payment_handle, 'breez-woocommerce');
|
||||||
|
}
|
||||||
|
|
||||||
|
$handles[] = $widget_handle;
|
||||||
|
$handles[] = $payment_handle;
|
||||||
|
|
||||||
|
return $handles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of key=>value pairs of data made available to the payment methods
|
||||||
|
* script.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_payment_method_data() {
|
||||||
|
$data = [
|
||||||
|
'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.',
|
||||||
|
'supports' => ['products'],
|
||||||
|
'showSavedCards' => false,
|
||||||
|
'canMakePayment' => true,
|
||||||
|
'paymentMethodId' => 'breez',
|
||||||
|
'orderButtonText' => __('Proceed to Payment', 'breez-woocommerce'),
|
||||||
|
'defaultPaymentMethod' => 'lightning'
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->logger->log('Payment method data: ' . print_r($data, true), 'debug');
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the payment
|
||||||
|
*
|
||||||
|
* @param \WC_Order $order
|
||||||
|
* @param array $payment_data
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function process_payment($order, $payment_data) {
|
||||||
|
try {
|
||||||
|
$this->logger->log('Processing payment in Blocks Support', 'debug');
|
||||||
|
$this->logger->log('Order ID: ' . $order->get_id(), 'debug');
|
||||||
|
$this->logger->log('Payment Data: ' . print_r($payment_data, true), 'debug');
|
||||||
|
|
||||||
|
if (!isset($this->gateway)) {
|
||||||
|
$this->gateway = new WC_Gateway_Breez();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->gateway) {
|
||||||
|
throw new Exception(__('Payment gateway not initialized', 'breez-woocommerce'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get payment method from blocks data
|
||||||
|
$payment_method = 'lightning'; // Default to lightning
|
||||||
|
if (isset($payment_data['paymentMethodData']['data']['breez_payment_method'])) {
|
||||||
|
$payment_method = sanitize_text_field($payment_data['paymentMethodData']['data']['breez_payment_method']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store payment method in POST for gateway processing
|
||||||
|
$_POST['breez_payment_method'] = $payment_method;
|
||||||
|
|
||||||
|
// Store any additional payment data
|
||||||
|
$order->update_meta_data('_breez_blocks_payment_data', wp_json_encode([
|
||||||
|
'payment_method' => $payment_method,
|
||||||
|
'timestamp' => time()
|
||||||
|
]));
|
||||||
|
$order->save();
|
||||||
|
|
||||||
|
$this->logger->log('Selected payment method: ' . $payment_method, 'debug');
|
||||||
|
|
||||||
|
// Process the payment
|
||||||
|
$result = $this->gateway->process_payment($order->get_id());
|
||||||
|
|
||||||
|
if (!is_array($result)) {
|
||||||
|
throw new Exception(__('Invalid payment gateway response', 'breez-woocommerce'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->log('Payment processing result: ' . print_r($result, true), 'debug');
|
||||||
|
|
||||||
|
// Ensure we have a proper response format
|
||||||
|
if (!isset($result['result'])) {
|
||||||
|
$result['result'] = 'success';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log('Payment processing error: ' . $e->getMessage(), 'error');
|
||||||
|
$this->logger->log('Error trace: ' . $e->getTraceAsString(), 'debug');
|
||||||
|
|
||||||
|
// Return a properly formatted error response
|
||||||
|
return [
|
||||||
|
'result' => 'failure',
|
||||||
|
'messages' => [
|
||||||
|
'error' => [
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'redirect' => wc_get_cart_url()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
261
includes/class-breez-db-manager.php
Normal file
261
includes/class-breez-db-manager.php
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Breez Database Manager
|
||||||
|
*
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit; // Exit if accessed directly
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Breez DB Manager Class
|
||||||
|
*
|
||||||
|
* Handles database operations for the Breez Nodeless Payments.
|
||||||
|
*/
|
||||||
|
class Breez_DB_Manager {
|
||||||
|
/**
|
||||||
|
* Table name
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $table_name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger instance
|
||||||
|
*
|
||||||
|
* @var Breez_Logger
|
||||||
|
*/
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public function __construct() {
|
||||||
|
global $wpdb;
|
||||||
|
$this->table_name = $wpdb->prefix . 'wc_breez_payments';
|
||||||
|
$this->logger = new Breez_Logger('yes' === get_option('woocommerce_breez_debug', 'no'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install database tables
|
||||||
|
*/
|
||||||
|
public function install_tables() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$charset_collate = $wpdb->get_charset_collate();
|
||||||
|
|
||||||
|
$this->logger->log("Installing database tables with charset: {$charset_collate}", 'debug');
|
||||||
|
|
||||||
|
$table_name = $wpdb->prefix . 'wc_breez_payments';
|
||||||
|
$this->logger->log("Table name: {$table_name}", 'debug');
|
||||||
|
|
||||||
|
$sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
order_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
invoice_id VARCHAR(255) NOT NULL,
|
||||||
|
amount DECIMAL(16,8) NOT NULL,
|
||||||
|
currency VARCHAR(10) NOT NULL,
|
||||||
|
status VARCHAR(20) NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL,
|
||||||
|
metadata TEXT,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
UNIQUE KEY order_id (order_id),
|
||||||
|
KEY invoice_id (invoice_id)
|
||||||
|
) $charset_collate;";
|
||||||
|
|
||||||
|
$this->logger->log("SQL query: {$sql}", 'debug');
|
||||||
|
|
||||||
|
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
|
||||||
|
$result = dbDelta($sql);
|
||||||
|
|
||||||
|
$this->logger->log("Database tables installation result: " . json_encode($result), 'debug');
|
||||||
|
|
||||||
|
// Check if table was created successfully
|
||||||
|
$table_exists = $wpdb->get_var("SHOW TABLES LIKE '{$table_name}'") === $table_name;
|
||||||
|
$this->logger->log("Table exists after creation: " . ($table_exists ? 'yes' : 'no'), 'debug');
|
||||||
|
|
||||||
|
if (!$table_exists) {
|
||||||
|
$this->logger->log("Failed to create database table", 'error');
|
||||||
|
$this->logger->log("WordPress DB error: " . $wpdb->last_error, 'error');
|
||||||
|
} else {
|
||||||
|
$this->logger->log("Database tables installed successfully", 'info');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save payment data
|
||||||
|
*
|
||||||
|
* @param int $order_id Order ID
|
||||||
|
* @param string $invoice_id Invoice ID
|
||||||
|
* @param float $amount Payment amount
|
||||||
|
* @param string $currency Currency code
|
||||||
|
* @param string $status Payment status
|
||||||
|
* @param array $metadata Optional metadata
|
||||||
|
* @return bool Success/failure
|
||||||
|
*/
|
||||||
|
public function save_payment($order_id, $invoice_id, $amount, $currency, $status, $metadata = array()) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$now = current_time('mysql');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $wpdb->insert(
|
||||||
|
$this->table_name,
|
||||||
|
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
|
||||||
|
),
|
||||||
|
array('%d', '%s', '%f', '%s', '%s', '%s', '%s', '%s')
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
$this->logger->log("Payment saved: order_id=$order_id, invoice_id=$invoice_id, status=$status");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$this->logger->log("Error saving payment: " . $wpdb->last_error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log("Exception saving payment: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update payment status
|
||||||
|
*
|
||||||
|
* @param int $order_id Order ID
|
||||||
|
* @param string $status New status
|
||||||
|
* @return bool Success/failure
|
||||||
|
*/
|
||||||
|
public function update_payment_status($order_id, $status) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$now = current_time('mysql');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $wpdb->update(
|
||||||
|
$this->table_name,
|
||||||
|
array(
|
||||||
|
'status' => $status,
|
||||||
|
'updated_at' => $now
|
||||||
|
),
|
||||||
|
array('order_id' => $order_id),
|
||||||
|
array('%s', '%s'),
|
||||||
|
array('%d')
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($result !== false) {
|
||||||
|
$this->logger->log("Payment status updated: order_id=$order_id, new_status=$status");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$this->logger->log("Error updating payment status: " . $wpdb->last_error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log("Exception updating payment status: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get payment by invoice ID
|
||||||
|
*
|
||||||
|
* @param string $invoice_id Invoice ID
|
||||||
|
* @return array|false Payment data or false if not found
|
||||||
|
*/
|
||||||
|
public function get_payment_by_invoice($invoice_id) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$query = $wpdb->prepare(
|
||||||
|
"SELECT * FROM {$this->table_name} WHERE invoice_id = %s",
|
||||||
|
$invoice_id
|
||||||
|
);
|
||||||
|
|
||||||
|
$payment = $wpdb->get_row($query, ARRAY_A);
|
||||||
|
|
||||||
|
if ($payment && isset($payment['metadata']) && $payment['metadata']) {
|
||||||
|
$payment['metadata'] = json_decode($payment['metadata'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payment;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log("Exception getting payment by invoice: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get payment by order ID
|
||||||
|
*
|
||||||
|
* @param int $order_id Order ID
|
||||||
|
* @return array|false Payment data or false if not found
|
||||||
|
*/
|
||||||
|
public function get_payment_by_order($order_id) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$query = $wpdb->prepare(
|
||||||
|
"SELECT * FROM {$this->table_name} WHERE order_id = %d",
|
||||||
|
$order_id
|
||||||
|
);
|
||||||
|
|
||||||
|
$payment = $wpdb->get_row($query, ARRAY_A);
|
||||||
|
|
||||||
|
if ($payment && isset($payment['metadata']) && $payment['metadata']) {
|
||||||
|
$payment['metadata'] = json_decode($payment['metadata'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payment;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log("Exception getting payment by order: " . $e->getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get pending payments
|
||||||
|
*
|
||||||
|
* @param int $minutes_old Get payments older than this many minutes
|
||||||
|
* @param int $max_minutes_old Get payments younger than this many minutes
|
||||||
|
* @return array Array of payment data
|
||||||
|
*/
|
||||||
|
public function get_pending_payments($minutes_old = 2, $max_minutes_old = 60) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$query = $wpdb->prepare(
|
||||||
|
"SELECT * FROM {$this->table_name}
|
||||||
|
WHERE status = 'pending'
|
||||||
|
AND created_at < DATE_SUB(NOW(), INTERVAL %d MINUTE)
|
||||||
|
AND created_at > DATE_SUB(NOW(), INTERVAL %d MINUTE)",
|
||||||
|
$minutes_old,
|
||||||
|
$max_minutes_old
|
||||||
|
);
|
||||||
|
|
||||||
|
$payments = $wpdb->get_results($query, ARRAY_A);
|
||||||
|
|
||||||
|
// Parse metadata JSON
|
||||||
|
foreach ($payments as &$payment) {
|
||||||
|
if (isset($payment['metadata']) && $payment['metadata']) {
|
||||||
|
$payment['metadata'] = json_decode($payment['metadata'], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payments;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log("Exception getting pending payments: " . $e->getMessage());
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
183
includes/class-breez-logger.php
Normal file
183
includes/class-breez-logger.php
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Breez Logger
|
||||||
|
*
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit; // Exit if accessed directly
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Breez Logger Class
|
||||||
|
*
|
||||||
|
* Handles logging for the Breez Nodeless Payments.
|
||||||
|
*/
|
||||||
|
class Breez_Logger {
|
||||||
|
/**
|
||||||
|
* Whether debugging is enabled
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $debug;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start time for performance tracking
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
*/
|
||||||
|
private $start_time;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param bool $debug Whether debugging is enabled
|
||||||
|
*/
|
||||||
|
public function __construct($debug = false) {
|
||||||
|
$this->debug = $debug;
|
||||||
|
$this->start_time = microtime(true);
|
||||||
|
|
||||||
|
if ($this->debug) {
|
||||||
|
$this->log('Logger initialized', 'debug');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get elapsed time since logger initialization
|
||||||
|
*
|
||||||
|
* @return float Elapsed time in seconds
|
||||||
|
*/
|
||||||
|
private function get_elapsed_time() {
|
||||||
|
return microtime(true) - $this->start_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format context data for logging
|
||||||
|
*
|
||||||
|
* @param array $context Additional context data
|
||||||
|
* @return string Formatted context string
|
||||||
|
*/
|
||||||
|
private function format_context($context = array()) {
|
||||||
|
$default_context = array(
|
||||||
|
'timestamp' => date('Y-m-d H:i:s'),
|
||||||
|
'elapsed' => sprintf('%.4f', $this->get_elapsed_time()),
|
||||||
|
'memory' => sprintf('%.2fMB', memory_get_usage() / 1024 / 1024)
|
||||||
|
);
|
||||||
|
|
||||||
|
$context = array_merge($default_context, $context);
|
||||||
|
return json_encode($context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log a message
|
||||||
|
*
|
||||||
|
* @param string $message Message to log
|
||||||
|
* @param string $level Log level (debug, info, warning, error)
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Log a message with context
|
||||||
|
*
|
||||||
|
* @param string $message Message to log
|
||||||
|
* @param string $level Log level (debug, info, warning, error)
|
||||||
|
* @param array $context Additional context data
|
||||||
|
*/
|
||||||
|
public function log($message, $level = 'info', $context = array()) {
|
||||||
|
if (!$this->debug && $level !== 'error') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Add trace for errors
|
||||||
|
if ($level === 'error') {
|
||||||
|
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||||
|
$caller = isset($trace[1]) ? $trace[1] : $trace[0];
|
||||||
|
$context['file'] = isset($caller['file']) ? basename($caller['file']) : '';
|
||||||
|
$context['line'] = isset($caller['line']) ? $caller['line'] : '';
|
||||||
|
$context['function'] = isset($caller['function']) ? $caller['function'] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$formatted_context = $this->format_context($context);
|
||||||
|
$log_message = sprintf('[%s] %s | %s', strtoupper($level), $message, $formatted_context);
|
||||||
|
|
||||||
|
if (function_exists('wc_get_logger')) {
|
||||||
|
// Use WC_Logger if available
|
||||||
|
$logger = wc_get_logger();
|
||||||
|
$logger_context = array_merge(array('source' => 'breez'), $context);
|
||||||
|
|
||||||
|
switch ($level) {
|
||||||
|
case 'debug':
|
||||||
|
$logger->debug($log_message, $logger_context);
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
$logger->warning($log_message, $logger_context);
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
$logger->error($log_message, $logger_context);
|
||||||
|
break;
|
||||||
|
case 'info':
|
||||||
|
default:
|
||||||
|
$logger->info($log_message, $logger_context);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to WP debug log if enabled
|
||||||
|
if (defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
|
||||||
|
error_log(sprintf(
|
||||||
|
'[Breez %s] %s',
|
||||||
|
strtoupper($level),
|
||||||
|
$message
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Last resort fallback - only if WP_DEBUG is enabled
|
||||||
|
if (defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
|
||||||
|
error_log(sprintf(
|
||||||
|
'[Breez Logger Error] Failed to log message: %s. Original message: %s',
|
||||||
|
$e->getMessage(),
|
||||||
|
$message
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log API request
|
||||||
|
*
|
||||||
|
* @param string $endpoint API endpoint
|
||||||
|
* @param array $params Request parameters
|
||||||
|
*/
|
||||||
|
public function log_request($endpoint, $params = array()) {
|
||||||
|
$this->log(
|
||||||
|
"API Request to $endpoint",
|
||||||
|
'debug',
|
||||||
|
array(
|
||||||
|
'endpoint' => $endpoint,
|
||||||
|
'params' => $params
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log API response
|
||||||
|
*
|
||||||
|
* @param string $endpoint API endpoint
|
||||||
|
* @param mixed $response Response data
|
||||||
|
* @param int $status_code HTTP status code
|
||||||
|
*/
|
||||||
|
public function log_response($endpoint, $response, $status_code = null) {
|
||||||
|
$context = array(
|
||||||
|
'endpoint' => $endpoint,
|
||||||
|
'status_code' => $status_code,
|
||||||
|
'response' => is_array($response) ? $response : array('raw' => substr((string)$response, 0, 500))
|
||||||
|
);
|
||||||
|
|
||||||
|
$level = ($status_code && $status_code >= 400) ? 'error' : 'debug';
|
||||||
|
$this->log(
|
||||||
|
"API Response from $endpoint",
|
||||||
|
$level,
|
||||||
|
$context
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
389
includes/class-breez-payment-handler.php
Normal file
389
includes/class-breez-payment-handler.php
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Breez Payment Handler
|
||||||
|
*
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit; // Exit if accessed directly
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Breez Payment Handler Class
|
||||||
|
*
|
||||||
|
* Handles payment processing and status checks.
|
||||||
|
*/
|
||||||
|
class Breez_Payment_Handler {
|
||||||
|
/**
|
||||||
|
* API Client instance
|
||||||
|
*
|
||||||
|
* @var Breez_API_Client
|
||||||
|
*/
|
||||||
|
private $client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DB Manager instance
|
||||||
|
*
|
||||||
|
* @var Breez_DB_Manager
|
||||||
|
*/
|
||||||
|
private $db_manager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger instance
|
||||||
|
*
|
||||||
|
* @var Breez_Logger
|
||||||
|
*/
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param Breez_API_Client $client API Client instance
|
||||||
|
* @param Breez_DB_Manager $db_manager DB Manager instance
|
||||||
|
*/
|
||||||
|
public function __construct($client = null, $db_manager = null) {
|
||||||
|
$this->logger = new Breez_Logger('yes' === get_option('woocommerce_breez_debug', 'no'));
|
||||||
|
$this->logger->log('Initializing payment handler', 'debug');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// If no client is provided, create one
|
||||||
|
if (!$client) {
|
||||||
|
$api_url = get_option('woocommerce_breez_api_url');
|
||||||
|
$api_key = get_option('woocommerce_breez_api_key');
|
||||||
|
|
||||||
|
if (!$api_url || !$api_key) {
|
||||||
|
throw new Exception('API credentials not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->client = new Breez_API_Client($api_url, $api_key);
|
||||||
|
} else {
|
||||||
|
$this->client = $client;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify API connectivity
|
||||||
|
if (!$this->client->check_health()) {
|
||||||
|
throw new Exception('API health check failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no db_manager is provided, create one
|
||||||
|
if (!$db_manager) {
|
||||||
|
$this->db_manager = new Breez_DB_Manager();
|
||||||
|
} else {
|
||||||
|
$this->db_manager = $db_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->log('Payment handler initialized successfully', 'debug');
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log('Payment handler initialization failed: ' . $e->getMessage(), 'error');
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert fiat amount to satoshis
|
||||||
|
*
|
||||||
|
* @param float $amount Fiat amount
|
||||||
|
* @param string $currency Currency code (default: store currency)
|
||||||
|
* @return int Amount in satoshis
|
||||||
|
*/
|
||||||
|
public function convert_to_satoshis($amount, $currency = '') {
|
||||||
|
try {
|
||||||
|
if (!$currency) {
|
||||||
|
$currency = get_woocommerce_currency();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($amount <= 0) {
|
||||||
|
throw new Exception('Invalid amount: must be greater than 0');
|
||||||
|
}
|
||||||
|
|
||||||
|
$start_time = microtime(true);
|
||||||
|
|
||||||
|
// Get exchange rate from BTC to currency
|
||||||
|
$btc_rate = $this->get_btc_rate($currency);
|
||||||
|
|
||||||
|
if ($btc_rate <= 0) {
|
||||||
|
throw new Exception("Invalid exchange rate for $currency");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert amount to BTC
|
||||||
|
$btc_amount = $amount / $btc_rate;
|
||||||
|
|
||||||
|
// Convert BTC to satoshis (1 BTC = 100,000,000 satoshis)
|
||||||
|
$satoshis = round($btc_amount * 100000000);
|
||||||
|
|
||||||
|
$duration = round(microtime(true) - $start_time, 3);
|
||||||
|
|
||||||
|
$this->logger->log('Currency conversion completed', 'debug', array(
|
||||||
|
'amount' => $amount,
|
||||||
|
'currency' => $currency,
|
||||||
|
'btc_rate' => $btc_rate,
|
||||||
|
'satoshis' => $satoshis,
|
||||||
|
'duration' => $duration
|
||||||
|
));
|
||||||
|
|
||||||
|
return $satoshis;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log('Currency conversion failed: ' . $e->getMessage(), 'error');
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Bitcoin exchange rate for currency
|
||||||
|
*
|
||||||
|
* In a real implementation, this would call an exchange rate API
|
||||||
|
*
|
||||||
|
* @param string $currency Currency code
|
||||||
|
* @return float Exchange rate (1 BTC = X currency)
|
||||||
|
*/
|
||||||
|
public function get_btc_rate($currency = '') {
|
||||||
|
if (!$currency) {
|
||||||
|
$currency = get_woocommerce_currency();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get from transient cache first (valid for 10 minutes)
|
||||||
|
$cached_rate = get_transient('breez_btc_' . strtolower($currency) . '_rate');
|
||||||
|
if ($cached_rate !== false) {
|
||||||
|
$this->logger->log('Using cached exchange rate', 'debug', array(
|
||||||
|
'currency' => $currency,
|
||||||
|
'rate' => $cached_rate
|
||||||
|
));
|
||||||
|
return $cached_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get rate from CoinGecko API
|
||||||
|
$response = wp_remote_get(
|
||||||
|
'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=' . strtolower($currency)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is_wp_error($response)) {
|
||||||
|
throw new Exception('Failed to fetch exchange rate: ' . $response->get_error_message());
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = json_decode(wp_remote_retrieve_body($response), true);
|
||||||
|
|
||||||
|
if (!isset($body['bitcoin'][strtolower($currency)])) {
|
||||||
|
throw new Exception('Invalid response from exchange rate API');
|
||||||
|
}
|
||||||
|
|
||||||
|
$rate = floatval($body['bitcoin'][strtolower($currency)]);
|
||||||
|
|
||||||
|
// Add exchange rate buffer if configured
|
||||||
|
$buffer_percent = floatval(get_option('woocommerce_breez_exchange_rate_buffer', 1.0));
|
||||||
|
if ($buffer_percent > 0) {
|
||||||
|
$rate = $rate * (1 + ($buffer_percent / 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache for 10 minutes
|
||||||
|
set_transient('breez_btc_' . strtolower($currency) . '_rate', $rate, 10 * MINUTE_IN_SECONDS);
|
||||||
|
|
||||||
|
$this->logger->log('Fetched new exchange rate', 'debug', array(
|
||||||
|
'currency' => $currency,
|
||||||
|
'rate' => $rate,
|
||||||
|
'buffer' => $buffer_percent . '%'
|
||||||
|
));
|
||||||
|
|
||||||
|
return $rate;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log('Exchange rate fetch failed: ' . $e->getMessage(), 'error');
|
||||||
|
|
||||||
|
// Fallback rates if API fails
|
||||||
|
$default_rates = array(
|
||||||
|
'USD' => 50000.00,
|
||||||
|
'EUR' => 45000.00,
|
||||||
|
'GBP' => 40000.00,
|
||||||
|
);
|
||||||
|
|
||||||
|
$rate = isset($default_rates[$currency]) ? $default_rates[$currency] : $default_rates['USD'];
|
||||||
|
|
||||||
|
$this->logger->log('Using fallback exchange rate', 'warning', array(
|
||||||
|
'currency' => $currency,
|
||||||
|
'rate' => $rate
|
||||||
|
));
|
||||||
|
|
||||||
|
return $rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check payment status
|
||||||
|
*
|
||||||
|
* @param string $invoice_id Invoice ID
|
||||||
|
* @return string Payment status (pending, completed, failed)
|
||||||
|
*/
|
||||||
|
public function check_payment_status($invoice_id) {
|
||||||
|
try {
|
||||||
|
$start_time = microtime(true);
|
||||||
|
$this->logger->log("Checking payment status", 'debug', array('invoice_id' => $invoice_id));
|
||||||
|
|
||||||
|
if (!$invoice_id) {
|
||||||
|
throw new Exception('Invalid invoice ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
// First check the local database
|
||||||
|
$payment = $this->db_manager->get_payment_by_invoice($invoice_id);
|
||||||
|
|
||||||
|
if ($payment && $payment['status'] !== 'pending') {
|
||||||
|
$this->logger->log('Using cached payment status', 'debug', array(
|
||||||
|
'invoice_id' => $invoice_id,
|
||||||
|
'status' => $payment['status']
|
||||||
|
));
|
||||||
|
return $payment['status']; // Return cached status if not pending
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// If pending or not found, check with API
|
||||||
|
$status = $this->client->check_payment_status($invoice_id);
|
||||||
|
|
||||||
|
// Update database if status has changed and we have payment data
|
||||||
|
if ($payment && $status !== 'pending' && $status !== $payment['status']) {
|
||||||
|
$this->db_manager->update_payment_status($payment['order_id'], $status);
|
||||||
|
|
||||||
|
$this->logger->log('Payment status updated', 'info', array(
|
||||||
|
'invoice_id' => $invoice_id,
|
||||||
|
'old_status' => $payment['status'],
|
||||||
|
'new_status' => $status,
|
||||||
|
'order_id' => $payment['order_id']
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$duration = round(microtime(true) - $start_time, 3);
|
||||||
|
$this->logger->log('Payment status check completed', 'debug', array(
|
||||||
|
'invoice_id' => $invoice_id,
|
||||||
|
'status' => $status,
|
||||||
|
'duration' => $duration
|
||||||
|
));
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->logger->log('Payment status check failed: ' . $e->getMessage(), 'error', array(
|
||||||
|
'invoice_id' => $invoice_id
|
||||||
|
));
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process successful payment
|
||||||
|
*
|
||||||
|
* @param string $invoice_id Invoice ID
|
||||||
|
* @return bool Success/failure
|
||||||
|
*/
|
||||||
|
public function process_successful_payment($invoice_id) {
|
||||||
|
$payment = $this->db_manager->get_payment_by_invoice($invoice_id);
|
||||||
|
|
||||||
|
if (!$payment) {
|
||||||
|
$this->logger->log("No payment found for invoice $invoice_id");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$order_id = $payment['order_id'];
|
||||||
|
$order = wc_get_order($order_id);
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
$this->logger->log("No order found for order ID $order_id");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update payment status in database
|
||||||
|
$this->db_manager->update_payment_status($order_id, 'completed');
|
||||||
|
|
||||||
|
// Update order status if not already completed
|
||||||
|
if (!$order->has_status(array('processing', 'completed'))) {
|
||||||
|
$order->payment_complete($invoice_id);
|
||||||
|
$order->add_order_note(__('Payment completed via Breez.', 'breez-woocommerce'));
|
||||||
|
$this->logger->log("Payment completed for order #$order_id (invoice: $invoice_id)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process failed payment
|
||||||
|
*
|
||||||
|
* @param string $invoice_id Invoice ID
|
||||||
|
* @return bool Success/failure
|
||||||
|
*/
|
||||||
|
public function process_failed_payment($invoice_id) {
|
||||||
|
$payment = $this->db_manager->get_payment_by_invoice($invoice_id);
|
||||||
|
|
||||||
|
if (!$payment) {
|
||||||
|
$this->logger->log("No payment found for invoice $invoice_id");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$order_id = $payment['order_id'];
|
||||||
|
$order = wc_get_order($order_id);
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
$this->logger->log("No order found for order ID $order_id");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update payment status in database
|
||||||
|
$this->db_manager->update_payment_status($order_id, 'failed');
|
||||||
|
|
||||||
|
// Update order status if still pending
|
||||||
|
if ($order->has_status('pending')) {
|
||||||
|
$order->update_status('failed', __('Payment failed or expired.', 'breez-woocommerce'));
|
||||||
|
$this->logger->log("Payment failed for order #$order_id (invoice: $invoice_id)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check pending payments (called by cron job)
|
||||||
|
*/
|
||||||
|
public function check_pending_payments() {
|
||||||
|
$this->logger->log("Checking pending payments...");
|
||||||
|
|
||||||
|
// Get pending payments that are at least 2 minutes old (to avoid race conditions)
|
||||||
|
// but less than the expiry time (default: 60 minutes)
|
||||||
|
$pending_payments = $this->db_manager->get_pending_payments(2, 60);
|
||||||
|
|
||||||
|
if (empty($pending_payments)) {
|
||||||
|
$this->logger->log("No pending payments to check");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->log("Found " . count($pending_payments) . " pending payments to check");
|
||||||
|
|
||||||
|
foreach ($pending_payments as $payment) {
|
||||||
|
$invoice_id = $payment['invoice_id'];
|
||||||
|
$order_id = $payment['order_id'];
|
||||||
|
|
||||||
|
$this->logger->log("Checking payment status for invoice $invoice_id (order #$order_id)");
|
||||||
|
|
||||||
|
// Check payment status with API
|
||||||
|
$status = $this->client->check_payment_status($invoice_id);
|
||||||
|
|
||||||
|
if ($status === 'completed') {
|
||||||
|
$this->process_successful_payment($invoice_id);
|
||||||
|
} elseif ($status === 'failed') {
|
||||||
|
$this->process_failed_payment($invoice_id);
|
||||||
|
} else {
|
||||||
|
// Check if payment is expired
|
||||||
|
$order = wc_get_order($order_id);
|
||||||
|
|
||||||
|
if ($order) {
|
||||||
|
$expiry = $order->get_meta('_breez_payment_expiry');
|
||||||
|
$current_time = time();
|
||||||
|
|
||||||
|
if ($expiry && $current_time > $expiry) {
|
||||||
|
$this->logger->log("Payment expired for order #$order_id (invoice: $invoice_id)");
|
||||||
|
$this->process_failed_payment($invoice_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->log("Finished checking pending payments");
|
||||||
|
}
|
||||||
|
}
|
||||||
125
includes/class-breez-webhook-handler.php
Normal file
125
includes/class-breez-webhook-handler.php
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Breez Webhook Handler
|
||||||
|
*
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit; // Exit if accessed directly
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Breez Webhook Handler Class
|
||||||
|
*
|
||||||
|
* Handles incoming webhook requests from the Breez API.
|
||||||
|
*/
|
||||||
|
class Breez_Webhook_Handler {
|
||||||
|
/**
|
||||||
|
* Logger instance
|
||||||
|
*
|
||||||
|
* @var Breez_Logger
|
||||||
|
*/
|
||||||
|
private static $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize logger
|
||||||
|
*/
|
||||||
|
private static function init_logger() {
|
||||||
|
if (!self::$logger) {
|
||||||
|
self::$logger = new Breez_Logger('yes' === get_option('woocommerce_breez_debug', 'no'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate webhook request
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Request object
|
||||||
|
* @return bool Whether the request is valid
|
||||||
|
*/
|
||||||
|
public static function validate_webhook($request) {
|
||||||
|
self::init_logger();
|
||||||
|
|
||||||
|
// For improved security, you could implement signature validation here
|
||||||
|
// For now, we'll just ensure the request is coming from an allowed IP
|
||||||
|
|
||||||
|
// Return true to allow the webhook to be processed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process webhook request
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request Request object
|
||||||
|
* @return WP_REST_Response Response object
|
||||||
|
*/
|
||||||
|
public static function process_webhook($request) {
|
||||||
|
self::init_logger();
|
||||||
|
|
||||||
|
self::$logger->log("Received webhook request");
|
||||||
|
|
||||||
|
// Get request data
|
||||||
|
$data = $request->get_json_params();
|
||||||
|
|
||||||
|
if (!$data) {
|
||||||
|
self::$logger->log("Invalid webhook data: empty or not JSON");
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Invalid request data'
|
||||||
|
), 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$logger->log("Webhook data: " . json_encode($data));
|
||||||
|
|
||||||
|
// Check for required fields
|
||||||
|
if (!isset($data['invoice_id']) || !isset($data['status'])) {
|
||||||
|
self::$logger->log("Missing required fields in webhook data");
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Missing required fields'
|
||||||
|
), 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoice_id = $data['invoice_id'];
|
||||||
|
$status = $data['status'];
|
||||||
|
|
||||||
|
// Process the payment
|
||||||
|
$db_manager = new Breez_DB_Manager();
|
||||||
|
$payment = $db_manager->get_payment_by_invoice($invoice_id);
|
||||||
|
|
||||||
|
if (!$payment) {
|
||||||
|
self::$logger->log("No payment found for invoice $invoice_id");
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Payment not found'
|
||||||
|
), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$order_id = $payment['order_id'];
|
||||||
|
$order = wc_get_order($order_id);
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
self::$logger->log("No order found for order ID $order_id");
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Order not found'
|
||||||
|
), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$payment_handler = new Breez_Payment_Handler(null, $db_manager);
|
||||||
|
|
||||||
|
// Update payment status based on the webhook data
|
||||||
|
if ($status === 'SUCCEEDED') {
|
||||||
|
$payment_handler->process_successful_payment($invoice_id);
|
||||||
|
} elseif ($status === 'FAILED') {
|
||||||
|
$payment_handler->process_failed_payment($invoice_id);
|
||||||
|
} else {
|
||||||
|
self::$logger->log("Unknown payment status: $status");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WP_REST_Response(array(
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Webhook processed successfully'
|
||||||
|
), 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
templates/admin-settings.php
Normal file
50
templates/admin-settings.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Admin settings template
|
||||||
|
*
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit; // Exit if accessed directly
|
||||||
|
}
|
||||||
|
|
||||||
|
// This file is a placeholder for any additional admin settings template content
|
||||||
|
// The main settings are handled by WooCommerce's settings API in class-wc-gateway-breez.php
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="breez-admin-settings-info">
|
||||||
|
<h3><?php _e('Additional Information', 'breez-woocommerce'); ?></h3>
|
||||||
|
|
||||||
|
<p><?php _e('Breez Nodeless Payments for WooCommerce allows your customers to pay with Bitcoin via Lightning Network or on-chain transactions.', 'breez-woocommerce'); ?></p>
|
||||||
|
|
||||||
|
<h4><?php _e('Requirements', 'breez-woocommerce'); ?></h4>
|
||||||
|
<ul>
|
||||||
|
<li><?php _e('Breez Nodeless API set up and running', 'breez-woocommerce'); ?></li>
|
||||||
|
<li><?php _e('API key for authentication', 'breez-woocommerce'); ?></li>
|
||||||
|
<li><?php _e('Properly configured webhook endpoint', 'breez-woocommerce'); ?></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h4><?php _e('Testing', 'breez-woocommerce'); ?></h4>
|
||||||
|
<p><?php _e('To test your setup, enable "Test Mode" and use a testnet Lightning wallet to make test payments.', 'breez-woocommerce'); ?></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.breez-admin-settings-info {
|
||||||
|
max-width: 800px;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-left: 4px solid #0073aa;
|
||||||
|
box-shadow: 0 1px 1px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-admin-settings-info h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-admin-settings-info ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
463
templates/payment-instructions.php
Normal file
463
templates/payment-instructions.php
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Payment instructions template
|
||||||
|
*
|
||||||
|
* @package Breez_WooCommerce
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('ABSPATH')) {
|
||||||
|
exit; // Exit if accessed directly
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent duplicate display
|
||||||
|
global $breez_payment_instructions_displayed;
|
||||||
|
if ($breez_payment_instructions_displayed === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$breez_payment_instructions_displayed = true;
|
||||||
|
|
||||||
|
// Variables:
|
||||||
|
// $order - WC_Order object
|
||||||
|
// $invoice_id - The payment invoice/address
|
||||||
|
// $payment_method - The payment method (LIGHTNING, BITCOIN_ADDRESS, etc)
|
||||||
|
// $expiry - The payment expiry timestamp
|
||||||
|
// $current_time - The current timestamp
|
||||||
|
// $payment_status - The current payment status
|
||||||
|
|
||||||
|
$time_left = $expiry - $current_time;
|
||||||
|
$minutes_left = floor($time_left / 60);
|
||||||
|
$seconds_left = $time_left % 60;
|
||||||
|
|
||||||
|
// Format the data for QR code
|
||||||
|
$qr_data = $invoice_id;
|
||||||
|
if ($payment_method === 'LIGHTNING' && strpos($qr_data, 'lightning:') !== 0 && strpos($qr_data, 'lnbc') === 0) {
|
||||||
|
$qr_data = 'lightning:' . $qr_data;
|
||||||
|
} elseif ($payment_method === 'BITCOIN_ADDRESS' && strpos($qr_data, 'bitcoin:') !== 0) {
|
||||||
|
$qr_data = 'bitcoin:' . $qr_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="breez-payment-box">
|
||||||
|
<h3><?php _e('Bitcoin/Lightning Payment', 'breez-woocommerce'); ?></h3>
|
||||||
|
|
||||||
|
<?php if ($payment_status === 'completed'): ?>
|
||||||
|
<div class="breez-payment-status breez-payment-completed">
|
||||||
|
<p><?php _e('Payment received! Thank you for your payment.', 'breez-woocommerce'); ?></p>
|
||||||
|
<p><?php _e('Your order is now being processed.', 'breez-woocommerce'); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($payment_status === 'failed'): ?>
|
||||||
|
<div class="breez-payment-status breez-payment-failed">
|
||||||
|
<p><?php _e('Payment failed or expired.', 'breez-woocommerce'); ?></p>
|
||||||
|
<p><?php _e('Please contact us for assistance.', 'breez-woocommerce'); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php if ($time_left > 0): ?>
|
||||||
|
<div class="breez-payment-countdown" data-expiry="<?php echo esc_attr($expiry); ?>">
|
||||||
|
<p><?php _e('Time remaining: ', 'breez-woocommerce'); ?><span class="breez-countdown"><?php printf('%02d:%02d', $minutes_left, $seconds_left); ?></span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="breez-payment-qr" id="breez-qr-container" data-qr-data="<?php echo esc_attr($qr_data); ?>">
|
||||||
|
<div class="breez-qr-loading">
|
||||||
|
<div class="breez-spinner"></div>
|
||||||
|
<p><?php _e('Generating QR Code...', 'breez-woocommerce'); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="breez-payment-details">
|
||||||
|
<p><strong><?php _e('Invoice/Address:', 'breez-woocommerce'); ?></strong></p>
|
||||||
|
<div class="breez-invoice-container">
|
||||||
|
<textarea readonly class="breez-invoice-text" rows="4"><?php echo esc_html($invoice_id); ?></textarea>
|
||||||
|
<button class="breez-copy-button" data-clipboard-text="<?php echo esc_attr($invoice_id); ?>">
|
||||||
|
<?php _e('Copy', 'breez-woocommerce'); ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="breez-payment-info">
|
||||||
|
<?php echo esc_html(get_option('woocommerce_breez_instructions')); ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="breez-payment-expired">
|
||||||
|
<p><?php _e('Payment time expired. Please contact support or place a new order.', 'breez-woocommerce'); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// QR Code generation
|
||||||
|
var qrContainer = document.getElementById('breez-qr-container');
|
||||||
|
if (qrContainer) {
|
||||||
|
var qrData = qrContainer.getAttribute('data-qr-data');
|
||||||
|
generateQRCode(qrData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get API configuration
|
||||||
|
var apiUrl = '<?php echo esc_js(get_option('woocommerce_breez_api_url')); ?>';
|
||||||
|
var apiKey = '<?php echo esc_js(get_option('woocommerce_breez_api_key')); ?>';
|
||||||
|
var invoiceId = '<?php echo esc_js($invoice_id); ?>';
|
||||||
|
|
||||||
|
// Ensure API URL ends with a slash
|
||||||
|
apiUrl = apiUrl.replace(/\/?$/, '/');
|
||||||
|
|
||||||
|
// Countdown functionality
|
||||||
|
var countdownEl = document.querySelector('.breez-countdown');
|
||||||
|
var expiryTime = parseInt(document.querySelector('.breez-payment-countdown')?.dataset.expiry, 10);
|
||||||
|
|
||||||
|
if (countdownEl && expiryTime) {
|
||||||
|
var countdownInterval = setInterval(function() {
|
||||||
|
var now = Math.floor(Date.now() / 1000);
|
||||||
|
var timeLeft = expiryTime - now;
|
||||||
|
|
||||||
|
if (timeLeft <= 0) {
|
||||||
|
clearInterval(countdownInterval);
|
||||||
|
clearInterval(statusCheckInterval); // Also clear status check
|
||||||
|
document.querySelector('.breez-payment-countdown').innerHTML = '<p><?php _e('Payment time expired.', 'breez-woocommerce'); ?></p>';
|
||||||
|
document.querySelector('.breez-payment-qr').style.opacity = '0.3';
|
||||||
|
|
||||||
|
// Show expired message
|
||||||
|
var paymentBox = document.querySelector('.breez-payment-box');
|
||||||
|
if (paymentBox) {
|
||||||
|
paymentBox.innerHTML = `
|
||||||
|
<h3><?php _e('Bitcoin/Lightning Payment', 'breez-woocommerce'); ?></h3>
|
||||||
|
<div class="breez-payment-expired">
|
||||||
|
<p><?php _e('Payment time expired. Please contact support or place a new order.', 'breez-woocommerce'); ?></p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minutes = Math.floor(timeLeft / 60);
|
||||||
|
var seconds = timeLeft % 60;
|
||||||
|
countdownEl.textContent = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payment status check functionality
|
||||||
|
function checkPaymentStatus() {
|
||||||
|
var paymentBox = document.querySelector('.breez-payment-box');
|
||||||
|
|
||||||
|
// Stop checking if payment box doesn't exist
|
||||||
|
if (!paymentBox) {
|
||||||
|
clearInterval(statusCheckInterval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop checking if payment is already completed or failed
|
||||||
|
if (paymentBox.querySelector('.breez-payment-completed') ||
|
||||||
|
paymentBox.querySelector('.breez-payment-failed') ||
|
||||||
|
paymentBox.querySelector('.breez-payment-expired')) {
|
||||||
|
clearInterval(statusCheckInterval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show loading indicator
|
||||||
|
var statusIndicator = paymentBox.querySelector('.breez-payment-status');
|
||||||
|
if (!statusIndicator) {
|
||||||
|
statusIndicator = document.createElement('div');
|
||||||
|
statusIndicator.className = 'breez-payment-status';
|
||||||
|
paymentBox.appendChild(statusIndicator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use WordPress REST API endpoint instead of direct Breez API call
|
||||||
|
fetch('/wp-json/breez-wc/v1/check-payment-status/' + invoiceId, {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response error: ' + response.status);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
// Use the 'data' property from our API wrapper
|
||||||
|
const paymentData = data.data || data;
|
||||||
|
|
||||||
|
// Clear any existing error message
|
||||||
|
statusIndicator.classList.remove('breez-payment-error');
|
||||||
|
|
||||||
|
// If status is pending, don't show any status message
|
||||||
|
if (paymentData.status === 'pending') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paymentData.status === 'SUCCEEDED' || (data.status && data.status === 'SUCCEEDED')) {
|
||||||
|
// Stop checking status immediately
|
||||||
|
clearInterval(statusCheckInterval);
|
||||||
|
|
||||||
|
// Update UI to show payment completed
|
||||||
|
paymentBox.innerHTML = `
|
||||||
|
<h3><?php _e('Bitcoin/Lightning Payment', 'breez-woocommerce'); ?></h3>
|
||||||
|
<div class="breez-payment-status breez-payment-completed">
|
||||||
|
<p><?php _e('Payment received! Thank you for your payment.', 'breez-woocommerce'); ?></p>
|
||||||
|
<p><?php _e('Your order is now being processed.', 'breez-woocommerce'); ?></p>
|
||||||
|
<p class="breez-payment-details">
|
||||||
|
<?php _e('Amount paid:', 'breez-woocommerce'); ?> ${Number(paymentData.amount_sat).toLocaleString()} sats<br>
|
||||||
|
<?php _e('Network fee:', 'breez-woocommerce'); ?> ${Number(paymentData.fees_sat).toLocaleString()} sats
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Notify WordPress about the completed payment
|
||||||
|
notifyServer(paymentData);
|
||||||
|
|
||||||
|
// Double check interval is cleared
|
||||||
|
if (statusCheckInterval) {
|
||||||
|
clearInterval(statusCheckInterval);
|
||||||
|
statusCheckInterval = null;
|
||||||
|
}
|
||||||
|
} else if (paymentData.status === 'FAILED' || (data.status && data.status === 'FAILED')) {
|
||||||
|
// Stop checking status immediately
|
||||||
|
clearInterval(statusCheckInterval);
|
||||||
|
|
||||||
|
// Update UI to show payment failed
|
||||||
|
paymentBox.innerHTML = `
|
||||||
|
<h3><?php _e('Bitcoin/Lightning Payment', 'breez-woocommerce'); ?></h3>
|
||||||
|
<div class="breez-payment-status breez-payment-failed">
|
||||||
|
<p><?php _e('Payment failed.', 'breez-woocommerce'); ?></p>
|
||||||
|
<p><?php _e('Please try again or contact us for assistance.', 'breez-woocommerce'); ?></p>
|
||||||
|
${paymentData.error ? `<p class="breez-error-details">${paymentData.error}</p>` : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Notify WordPress about the failed payment
|
||||||
|
notifyServer(paymentData);
|
||||||
|
|
||||||
|
// Double check interval is cleared
|
||||||
|
if (statusCheckInterval) {
|
||||||
|
clearInterval(statusCheckInterval);
|
||||||
|
statusCheckInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error checking payment status:', error);
|
||||||
|
// Don't show error message for status check failures
|
||||||
|
// Just log to console and continue checking
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to notify WordPress about payment status changes
|
||||||
|
function notifyServer(paymentData) {
|
||||||
|
fetch('/wp-json/breez-wc/v1/check-payment-status?order_id=<?php echo esc_js($order->get_id()); ?>', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-WP-Nonce': '<?php echo esc_js(wp_create_nonce('wp_rest')); ?>'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(paymentData)
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error notifying server:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check payment status every 5 seconds
|
||||||
|
var statusCheckInterval = setInterval(checkPaymentStatus, 5000);
|
||||||
|
|
||||||
|
// Do an initial check immediately
|
||||||
|
checkPaymentStatus();
|
||||||
|
|
||||||
|
// Copy invoice button functionality
|
||||||
|
var copyButton = document.querySelector('.breez-copy-button');
|
||||||
|
if (copyButton) {
|
||||||
|
copyButton.addEventListener('click', function() {
|
||||||
|
var textToCopy = this.dataset.clipboardText;
|
||||||
|
var textarea = document.createElement('textarea');
|
||||||
|
textarea.value = textToCopy;
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
this.textContent = '<?php _e('Copied!', 'breez-woocommerce'); ?>';
|
||||||
|
setTimeout(function() {
|
||||||
|
copyButton.textContent = '<?php _e('Copy', 'breez-woocommerce'); ?>';
|
||||||
|
}, 2000);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy: ', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// QR Code generation function
|
||||||
|
function generateQRCode(data) {
|
||||||
|
// Try QRServer.com first
|
||||||
|
var primaryUrl = 'https://api.qrserver.com/v1/create-qr-code/?' + new URLSearchParams({
|
||||||
|
data: data,
|
||||||
|
size: '300x300',
|
||||||
|
margin: '10',
|
||||||
|
format: 'svg',
|
||||||
|
qzone: '1',
|
||||||
|
color: '000000',
|
||||||
|
bgcolor: 'FFFFFF'
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
// Fallback URL (Google Charts)
|
||||||
|
var fallbackUrl = 'https://chart.googleapis.com/chart?' + new URLSearchParams({
|
||||||
|
chs: '300x300',
|
||||||
|
cht: 'qr',
|
||||||
|
chl: data,
|
||||||
|
choe: 'UTF-8',
|
||||||
|
chld: 'M|4'
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
// Create image element
|
||||||
|
var img = new Image();
|
||||||
|
img.style.cssText = 'display:block; max-width:300px; height:auto;';
|
||||||
|
img.alt = 'QR Code';
|
||||||
|
|
||||||
|
// Try primary service first
|
||||||
|
img.onerror = function() {
|
||||||
|
// If primary fails, try fallback
|
||||||
|
img.src = fallbackUrl;
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onload = function() {
|
||||||
|
qrContainer.innerHTML = '';
|
||||||
|
var wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'breez-qr-wrapper';
|
||||||
|
wrapper.style.cssText = 'background:white; padding:15px; border-radius:8px; box-shadow:0 2px 4px rgba(0,0,0,0.1); display:inline-block;';
|
||||||
|
wrapper.appendChild(img);
|
||||||
|
qrContainer.appendChild(wrapper);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start loading primary QR code
|
||||||
|
img.src = primaryUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.breez-payment-box {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f8f8f8;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-status {
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-completed {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-failed {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-countdown {
|
||||||
|
font-size: 18px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-countdown {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-qr {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-qr-loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #3498db;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-invoice-container {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-invoice-text {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: none;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-copy-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background: #0073aa;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-copy-button:hover {
|
||||||
|
background: #005177;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-info {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-expired {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-error {
|
||||||
|
background: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #ffeeba;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-error-details {
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-top: 5px;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breez-payment-details {
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 10px;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user