Compare commits

...

4 Commits

8 changed files with 229 additions and 28 deletions

6
assets/css/admin.css Normal file
View File

@ -0,0 +1,6 @@
.btcpay-connection-success {
color: green;
}
.btcpay-connection-error {
color: red;
}

View File

@ -32,4 +32,23 @@ jQuery(document).ready(function($) {
alert('Please enter a valid url including https:// in the BTCPay Server URL input field.')
}
});
// Handle manual connection settings.
const showDetails = $('#btcpay_gf_connection_details');
const detailFields = $('#btcpay_gf_store_id, #btcpay_gf_whsecret, #btcpay_gf_api_key, #btcpay_gf_whstatus');
toggleFields(showDetails.is(':checked'));
showDetails.on('change', function() {
toggleFields($(this).is(':checked'));
});
function toggleFields(isChecked) {
if (isChecked) {
detailFields.closest('tr').show();
} else {
detailFields.closest('tr').hide();
}
}
});

View File

@ -7,12 +7,12 @@
* Author URI: https://btcpayserver.org
* Text Domain: btcpay-greenfield-for-woocommerce
* Domain Path: /languages
* Version: 2.1.0
* Version: 2.2.0
* Requires PHP: 7.4
* Tested up to: 6.2
* Tested up to: 6.3
* Requires at least: 5.2
* WC requires at least: 6.0
* WC tested up to: 7.5
* WC tested up to: 8.0
*/
use BTCPayServer\WC\Admin\Notice;
@ -25,7 +25,7 @@ use BTCPayServer\WC\Helper\Logger;
defined( 'ABSPATH' ) || exit();
define( 'BTCPAYSERVER_VERSION', '2.1.0' );
define( 'BTCPAYSERVER_VERSION', '2.2.0' );
define( 'BTCPAYSERVER_VERSION_KEY', 'btcpay_gf_version' );
define( 'BTCPAYSERVER_PLUGIN_FILE_PATH', plugin_dir_path( __FILE__ ) );
define( 'BTCPAYSERVER_PLUGIN_URL', plugin_dir_url(__FILE__ ) );
@ -373,6 +373,7 @@ add_action( 'template_redirect', function() {
if ($apiData->hasSingleStore() && $apiData->hasRequiredPermissions()) {
update_option('btcpay_gf_api_key', $apiData->getApiKey());
update_option('btcpay_gf_store_id', $apiData->getStoreID());
update_option('btcpay_gf_connection_details', 'yes');
Notice::addNotice('success', __('Successfully received api key and store id from BTCPay Server API. Please finish setup by saving this settings form.', 'btcpay-greenfield-for-woocommerce'));
wp_redirect($btcPaySettingsUrl);
} else {

View File

@ -3,9 +3,9 @@ Contributors: ndeet, kukks, nicolasdorier
Donate link: https://btcpayserver.org/donate/
Tags: bitcoin, btcpay, BTCPay Server, btcpayserver, WooCommerce, payment gateway, accept bitcoin, bitcoin plugin, bitcoin payment processor, bitcoin e-commerce, Lightning Network, Litecoin, cryptocurrency
Requires at least: 5.2
Tested up to: 6.2
Tested up to: 6.3
Requires PHP: 7.4
Stable tag: 2.1.0
Stable tag: 2.2.0
License: MIT
License URI: https://github.com/btcpayserver/woocommerce-greenfield-plugin/blob/master/license.txt
@ -103,6 +103,9 @@ You'll find extensive documentation and answers to many of your questions on [BT
6. Example of the PoS app you can launch.
== Changelog ==
= 2.2.0 :: 2023-08-17 =
* Refactor settings UI and allow manual webhook secret entry
= 2.1.0 :: 2023-04-03 =
* New feature: Modal / Overlay checkout mode (no redirect to BTCPay Server)

View File

@ -17,13 +17,15 @@ use BTCPayServer\WC\Helper\OrderStates;
* todo: add validation of host/url
*/
class GlobalSettings extends \WC_Settings_Page {
private GreenfieldApiHelper $apiHelper;
public function __construct()
{
$this->id = 'btcpay_settings';
$this->label = __( 'BTCPay Settings', 'btcpay-greenfield-for-woocommerce' );
$this->apiHelper = new GreenfieldApiHelper();
// Register custom field type order_states with OrderStatesField class.
add_action('woocommerce_admin_field_order_states', [(new OrderStates()), 'renderOrderStatesHtml']);
add_action('woocommerce_admin_field_custom_markup', [$this, 'output_custom_markup_field']);
if (is_admin()) {
// Register and include JS.
@ -34,13 +36,20 @@ class GlobalSettings extends \WC_Settings_Page {
[
'url' => admin_url( 'admin-ajax.php' ),
'apiNonce' => wp_create_nonce( 'btcpaygf-api-url-nonce' ),
]);
]
);
// Register and include CSS.
wp_register_style( 'btcpay_gf_admin_styles', BTCPAYSERVER_PLUGIN_URL . 'assets/css/admin.css', array(), BTCPAYSERVER_VERSION );
wp_enqueue_style( 'btcpay_gf_admin_styles' );
}
parent::__construct();
}
public function output(): void
{
echo '<h1>' . _x('BTCPay Server Payments settings', 'global_settings', 'btcpay-greenfield-for-woocommerce') . '</h1>';
$settings = $this->get_settings_for_default_section();
\WC_Admin_Settings::output_fields($settings);
}
@ -53,16 +62,44 @@ class GlobalSettings extends \WC_Settings_Page {
public function getGlobalSettings(): array
{
Logger::debug('Entering Global Settings form.');
// Check setup status and prepare output.
$setupStatus = '';
if ($this->apiHelper->configured) {
$setupStatus = '<p class="btcpay-connection-success">' . _x('BTCPay Server connected.', 'global_settings', 'btcpay-greenfield-for-woocommerce') . '</p>';
} else {
$setupStatus = '<p class="btcpay-connection-error">' . _x('Not connected. Please use the setup wizard above or check advanced settings to manually enter connection settings.', 'global_settings', 'btcpay-greenfield-for-woocommerce') . '</p>';
}
// Check webhook status and prepare output.
$whStatus = '';
$whId = '';
// Can't use apiHelper because of caching.
if ($webhookConfig = get_option('btcpay_gf_webhook')) {
$whId = $webhookConfig['id'];
}
if ($this->apiHelper->webhookIsSetup()) {
$whStatus = '<p class="btcpay-connection-success">' . _x('Webhook setup automatically.', 'global_settings', 'btcpay-greenfield-for-woocommerce') . ' ID: ' . $whId . '</p>';
} else {
$whStatus = '<p class="btcpay-connection-error">' . _x('No webhook setup, yet.', 'global_settings', 'btcpay-greenfield-for-woocommerce') . '</p>';
}
if ($this->apiHelper->webhookIsSetupManual()) {
$whStatus = '<p class="btcpay-connection-success">' . _x('Webhook setup manually with webhook secret.', 'global_settings', 'btcpay-greenfield-for-woocommerce') . ' ID: ' . $whId . '</p>';
}
return [
'title' => [
// Section connection.
'title_connection' => [
'title' => esc_html_x(
'BTCPay Server Payments Settings',
'Connection settings',
'global_settings',
'btcpay-greenfield-for-woocommerce'
),
'type' => 'title',
'desc' => sprintf( _x( 'This plugin version is %s and your PHP version is %s. Check out our <a href="https://docs.btcpayserver.org/WooCommerce/" target="_blank">installation instructions</a>. If you need assistance, please come on our <a href="https://chat.btcpayserver.org" target="_blank">chat</a>. Thank you for using BTCPay!', 'global_settings', 'btcpay-greenfield-for-woocommerce' ), BTCPAYSERVER_VERSION, PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION ),
'id' => 'btcpay_gf'
'id' => 'btcpay_gf_connection'
],
'url' => [
'title' => esc_html_x(
@ -76,10 +113,29 @@ class GlobalSettings extends \WC_Settings_Page {
'desc_tip' => true,
'id' => 'btcpay_gf_url'
],
'wizard' => [
'title' => esc_html_x( 'Setup wizard', 'global_settings','btcpay-greenfield-for-woocommerce' ),
'type' => 'custom_markup',
'markup' => '<button class="button button-primary btcpay-api-key-link" target="_blank">Generate API key</button>',
'id' => 'btcpay_gf_wizard_button' // a unique ID
],
'status' => [
'title' => esc_html_x( 'Setup status', 'global_settings','btcpay-greenfield-for-woocommerce' ),
'type' => 'custom_markup',
'markup' => $setupStatus,
'id' => 'btcpay_gf_status'
],
'connection_details' => [
'title' => __( 'Advanced settings', 'btcpay-greenfield-for-woocommerce' ),
'type' => 'checkbox',
'default' => 'no',
'desc' => _x( 'Show all connection settings / manual setup.', 'global_settings', 'btcpay-greenfield-for-woocommerce' ),
'id' => 'btcpay_gf_connection_details'
],
'api_key' => [
'title' => esc_html_x( 'BTCPay API Key', 'global_settings','btcpay-greenfield-for-woocommerce' ),
'type' => 'text',
'desc' => _x( 'Your BTCPay API Key. If you do not have any yet <a href="#" class="btcpay-api-key-link" target="_blank">click here to generate API keys.</a>', 'global_settings', 'btcpay-greenfield-for-woocommerce' ),
'desc' => _x( 'Your BTCPay API Key. If you do not have any yet use the setup wizard above.', 'global_settings', 'btcpay-greenfield-for-woocommerce' ),
'default' => '',
'id' => 'btcpay_gf_api_key'
],
@ -90,6 +146,33 @@ class GlobalSettings extends \WC_Settings_Page {
'default' => '',
'id' => 'btcpay_gf_store_id'
],
'whsecret' => [
'title' => esc_html_x( 'Webhook secret (optional)', 'global_settings','btcpay-greenfield-for-woocommerce' ),
'type' => 'text',
'desc' => _x( 'If left empty an webhook will created automatically on save. Only fill out if you know the webhook secret and the webhook was created manually on BTCPay Server.', 'global_settings', 'btcpay-greenfield-for-woocommerce' ),
'default' => '',
'id' => 'btcpay_gf_whsecret'
],
'whstatus' => [
'title' => esc_html_x( 'Webhook status', 'global_settings','btcpay-greenfield-for-woocommerce' ),
'type' => 'custom_markup',
'markup' => $whStatus,
'id' => 'btcpay_gf_whstatus'
],
'sectionend_connection' => [
'type' => 'sectionend',
'id' => 'btcpay_gf_connection',
],
// Section general.
'title' => [
'title' => esc_html_x(
'General settings',
'global_settings',
'btcpay-greenfield-for-woocommerce'
),
'type' => 'title',
'id' => 'btcpay_gf'
],
'default_description' => [
'title' => esc_html_x( 'Default Customer Message', 'btcpay-greenfield-for-woocommerce' ),
'type' => 'textarea',
@ -170,6 +253,7 @@ class GlobalSettings extends \WC_Settings_Page {
$apiUrl = esc_url_raw( $_POST['btcpay_gf_url'] );
$apiKey = sanitize_text_field( $_POST['btcpay_gf_api_key'] );
$storeId = sanitize_text_field( $_POST['btcpay_gf_store_id'] );
$manualWhSecret = sanitize_text_field( $_POST['btcpay_gf_whsecret'] );
// todo: fix change of url + key + storeid not leading to recreation of webhook.
// Check if the provided API key has the right scope and permissions.
@ -227,20 +311,58 @@ class GlobalSettings extends \WC_Settings_Page {
// Continue creating the webhook if the API key permissions are OK.
if ( false === $hasError ) {
// Check if we already have a webhook registered for that store.
if ( GreenfieldApiWebhook::webhookExists( $apiUrl, $apiKey, $storeId ) ) {
$messageReuseWebhook = __( 'Webhook already exists, skipping webhook creation.', 'btcpay-greenfield-for-woocommerce' );
Notice::addNotice('info', $messageReuseWebhook, true);
Logger::debug($messageReuseWebhook);
} else {
// Register a new webhook.
if ( GreenfieldApiWebhook::registerWebhook( $apiUrl, $apiKey, $storeId ) ) {
$messageWebhookSuccess = __( 'Successfully registered a new webhook on BTCPay Server.', 'btcpay-greenfield-for-woocommerce' );
Notice::addNotice('success', $messageWebhookSuccess, true );
Logger::debug( $messageWebhookSuccess );
if ( GreenfieldApiWebhook::webhookExists( $apiUrl, $apiKey, $storeId, $manualWhSecret ) ) {
if ( $manualWhSecret && $this->apiHelper->webhook['secret'] !== $manualWhSecret) {
// Store manual webhook in options table.
update_option(
'btcpay_gf_webhook',
[
'id' => 'manual',
'secret' => $manualWhSecret,
'url' => 'manual'
]
);
$messageWebhookManual = __( 'Successfully setup manual webhook.', 'btcpay-greenfield-for-woocommerce' );
Notice::addNotice('success', $messageWebhookManual, true );
Logger::debug( $messageWebhookManual );
} else {
$messageWebhookError = __( 'Could not register a new webhook on the store.', 'btcpay-greenfield-for-woocommerce' );
Notice::addNotice('error', $messageWebhookError );
Logger::debug($messageWebhookError, true);
$messageReuseWebhook = __( 'Webhook already exists, skipping webhook creation.', 'btcpay-greenfield-for-woocommerce' );
Notice::addNotice('info', $messageReuseWebhook, true);
Logger::debug($messageReuseWebhook);
}
} else {
// When the webhook secret was set manually we just store it and not try to create it.
if ( $manualWhSecret ) {
// Store manual webhook in options table.
update_option(
'btcpay_gf_webhook',
[
'id' => 'manual',
'secret' => $manualWhSecret,
'url' => 'manual'
]
);
$messageWebhookManual = __( 'Successfully setup manual webhook.', 'btcpay-greenfield-for-woocommerce' );
Notice::addNotice('success', $messageWebhookManual, true );
Logger::debug( $messageWebhookManual );
}
// Register a new webhook automatically.
if ( empty($manualWhSecret) ) {
if ( GreenfieldApiWebhook::registerWebhook( $apiUrl, $apiKey, $storeId ) ) {
$messageWebhookSuccess = __( 'Successfully registered a new webhook on BTCPay Server.', 'btcpay-greenfield-for-woocommerce' );
Notice::addNotice('success', $messageWebhookSuccess, true );
Logger::debug( $messageWebhookSuccess );
} else {
$messageWebhookError = __( 'Could not register a new webhook on the store.', 'btcpay-greenfield-for-woocommerce' );
Notice::addNotice('error', $messageWebhookError );
Logger::debug($messageWebhookError, true);
// Cleanup existing conf.
delete_option('btcpay_gf_webhook');
}
}
}
@ -293,4 +415,19 @@ class GlobalSettings extends \WC_Settings_Page {
}
return false;
}
public function output_custom_markup_field($value) {
echo '<tr valign="top">';
if (!empty($value['title'])) {
echo '<th scope="row" class="titledesc">' . esc_html($value['title']) . '</th>';
} else {
echo '<th scope="row" class="titledesc">&nbsp;</th>';
}
echo '<td class="forminp" id="' . $value['id'] . '">';
echo $value['markup'];
echo '</td>';
echo '</tr>';
}
}

View File

@ -8,12 +8,12 @@ class GreenfieldApiAuthorization {
public const REQUIRED_PERMISSIONS = [
'btcpay.store.canviewinvoices',
'btcpay.store.cancreateinvoice',
'btcpay.store.webhooks.canmodifywebhooks',
'btcpay.store.canviewstoresettings',
'btcpay.store.canmodifyinvoices'
];
public const OPTIONAL_PERMISSIONS = [
'btcpay.store.cancreatenonapprovedpullpayments'
'btcpay.store.cancreatenonapprovedpullpayments',
'btcpay.store.webhooks.canmodifywebhooks',
];
private $apiKey;
@ -83,4 +83,12 @@ class GreenfieldApiAuthorization {
return in_array('btcpay.store.cancreatenonapprovedpullpayments', $permissions, true);
}
public function hasWebhookPermission(): bool {
$permissions = array_reduce($this->permissions, static function (array $carry, string $permission) {
return array_merge($carry, [explode(':', $permission)[0]]);
}, []);
return in_array('btcpay.store.webhooks.canmodifywebhooks', $permissions, true);
}
}

View File

@ -161,6 +161,24 @@ class GreenfieldApiHelper {
return false;
}
public static function webhookIsSetup(): bool {
if ($config = self::getConfig()) {
return !empty($config['webhook']['secret']);
}
return false;
}
public static function webhookIsSetupManual(): bool {
if ($config = self::getConfig()) {
return !empty($config['webhook']['secret']) && $config['webhook']['id'] === 'manual';
}
return false;
}
/**
* Checks if a given invoice id has status of fully paid (settled) or paid late.
*/

View File

@ -20,8 +20,16 @@ class GreenfieldApiWebhook {
/**
* Get locally stored webhook data and check if it exists on the store.
*/
public static function webhookExists(string $apiUrl, string $apiKey, string $storeId): bool {
public static function webhookExists(string $apiUrl, string $apiKey, string $storeId, $manualWebhookSecret = null): bool {
if ( $storedWebhook = get_option( 'btcpay_gf_webhook' ) ) {
// Handle case of manually entered webhook (secret). We can't query webhooks endpoint at all without permission.
if ($storedWebhook['id'] === 'manual' && $storedWebhook['secret'] === $manualWebhookSecret) {
Logger::debug('Detected existing and manually set webhook.');
return true;
}
// Check automatically created webhook.
try {
$whClient = new Webhook( $apiUrl, $apiKey );
$existingWebhook = $whClient->getWebhook( $storeId, $storedWebhook['id'] );
@ -30,6 +38,7 @@ class GreenfieldApiWebhook {
$existingWebhook->getData()['id'] === $storedWebhook['id'] &&
strpos( $existingWebhook->getData()['url'], $storedWebhook['url'] ) !== false
) {
Logger::debug('Detected existing automatically set webhook.');
return true;
}
} catch (\Throwable $e) {