Initial working version.
This commit is contained in:
commit
6131230042
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
phpcs.xml
|
||||
phpunit.xml
|
||||
Thumbs.db
|
||||
composer.lock
|
||||
/vendor/
|
||||
44
assets/js/btcpay-gateway.js
Normal file
44
assets/js/btcpay-gateway.js
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
(() => {
|
||||
let settings = {};
|
||||
/**
|
||||
* Example of rendering gateway fields (without jsx).
|
||||
*
|
||||
* This renders a simple div with a label and input.
|
||||
*
|
||||
* @see https://react.dev/reference/react/createElement
|
||||
*/
|
||||
function BtcpayGatewayFields() {
|
||||
return window.wp.element.createElement(
|
||||
"div",
|
||||
{
|
||||
className: 'btpcay-gateway-help-text'
|
||||
},
|
||||
window.wp.element.createElement(
|
||||
"p",
|
||||
{
|
||||
style: {marginBottom: 0}
|
||||
},
|
||||
settings.message,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of a front-end gateway object.
|
||||
*/
|
||||
const BtcpayGateway = {
|
||||
id: "btcpay-gateway",
|
||||
initialize() {
|
||||
settings = this.settings
|
||||
},
|
||||
Fields() {
|
||||
return window.wp.element.createElement(BtcpayGatewayFields);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The final step is to register the front-end gateway with GiveWP.
|
||||
*/
|
||||
window.givewp.gateways.register(BtcpayGateway);
|
||||
})();
|
||||
136
btcpay-for-givewp.php
Normal file
136
btcpay-for-givewp.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: BTCPay Server for GiveWP
|
||||
* Plugin URI: https://docs.btcpayserver.org/GiveWP/
|
||||
* Description: BTCPay Server Bitcoin / Lightning Network payment gateway integration for GiveWP
|
||||
* Version: 1.0.0
|
||||
* Author: BTCPay Server integrations team
|
||||
* Author URI: https://btcpayserver.org
|
||||
* Text Domain: btcpay-for-givewp
|
||||
* Domain Path: /languages
|
||||
* License: MIT
|
||||
* License URI: https://opensource.org/licenses/MIT
|
||||
* Requires PHP: 8.1
|
||||
* Requires at least: 6.0
|
||||
* Tested up to: 6.7
|
||||
* Requires Plugins: give
|
||||
* GiveWP tested up to: 3.22.2
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Define plugin constants
|
||||
define('BTCPAY_FOR_GIVEWP_VERSION', '1.0.0');
|
||||
define('BTCPAY_FOR_GIVEWP_DIR', plugin_dir_path(__FILE__));
|
||||
define('BTCPAY_FOR_GIVEWP_URL', plugin_dir_url(__FILE__));
|
||||
|
||||
// Composer autoloader
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* Main plugin class
|
||||
*/
|
||||
final class BTCPayForGiveWP {
|
||||
/**
|
||||
* @var BTCPayForGiveWP The single instance of this class
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Main Plugin Instance
|
||||
*
|
||||
* Ensures only one instance of the plugin exists in memory at any one time.
|
||||
*
|
||||
* @return BTCPayForGiveWP
|
||||
*/
|
||||
public static function instance() {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin Constructor
|
||||
*/
|
||||
private function __construct() {
|
||||
// Load text domain
|
||||
add_action('init', [$this, 'loadTextdomain']);
|
||||
|
||||
// Admin notice if Give is not active
|
||||
add_action('admin_notices', [$this, 'adminNotice']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the plugin on give_init
|
||||
* This is the main initialization method that sets up the gateway
|
||||
*/
|
||||
public function setupGateway() {
|
||||
// Check if Give is active
|
||||
if (!$this->isGiveActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register the payment gateway with GiveWP
|
||||
add_action('givewp_register_payment_gateway', function($paymentGatewayRegister) {
|
||||
$paymentGatewayRegister->registerGateway(BTCPayServer\Give\Gateway\BtcpayGateway::class);
|
||||
});
|
||||
|
||||
// Initialize the settings class
|
||||
if (is_admin()) {
|
||||
BTCPayServer\Give\Admin\Settings::instance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Give is active
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isGiveActive() {
|
||||
return class_exists('Give');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plugin text domain
|
||||
*/
|
||||
public function loadTextdomain() {
|
||||
load_plugin_textdomain(
|
||||
'btcpay-for-givewp',
|
||||
false,
|
||||
dirname(plugin_basename(__FILE__)) . '/languages'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin notice for when Give is not active
|
||||
*/
|
||||
public function adminNotice() {
|
||||
if (!$this->is_give_active()) {
|
||||
?>
|
||||
<div class="error">
|
||||
<p><?php _e('Give must be installed and activated for the BTCPay for GiveWP Gateway add-on to work.', 'btcpay-for-givewp'); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main instance of BTCPayForGiveWP
|
||||
*
|
||||
* @return BTCPayForGiveWP
|
||||
*/
|
||||
function BTCPayForGiveWP() {
|
||||
return BTCPayForGiveWP::instance();
|
||||
}
|
||||
|
||||
// Initialize the plugin
|
||||
BTCPayForGiveWP();
|
||||
|
||||
// Setup the gateway at the proper time
|
||||
add_action('give_init', [BTCPayForGiveWP(), 'setupGateway']);
|
||||
29
composer.json
Normal file
29
composer.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "btcpayserver/givewp",
|
||||
"description": "BTCPay Server Bitcoin/Lightning Network payment gateway integration for Give-WP",
|
||||
"type": "wordpress-plugin",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"authors": [
|
||||
{
|
||||
"name": "BTCPay Server integrations team",
|
||||
"email": "integrations@btcpayserver.org"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"composer/installers": "~1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"BTCPayServer\\Give\\": "src/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"composer/installers": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
21
license.txt
Normal file
21
license.txt
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 BTCPay Server
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
19
readme.txt
Normal file
19
readme.txt
Normal file
@ -0,0 +1,19 @@
|
||||
=== BTCPay Server for GiveWP ===
|
||||
Contributors: ndeet
|
||||
Tags: Bitcoin, BTCPay Server, cryptocurrency, GiveWP, donations, fundraising, gateway
|
||||
Requires at least: 5.0
|
||||
Tested up to: 6.7
|
||||
Stable tag: 1.0.0
|
||||
Requires Give: 2.24.0
|
||||
Requires PHP: 8.1
|
||||
License: MIT
|
||||
License URI: https://opensource.org/licenses/MIT
|
||||
|
||||
A BTCPay Server Bitcoin / Lightning Network (and other cryptocurrencies) payment gateway for GiveWP.
|
||||
|
||||
== Description ==
|
||||
|
||||
This plugin requires the GiveWP core plugin activated to function properly.
|
||||
|
||||
== Installation ==
|
||||
|
||||
306
src/Admin/Settings.php
Normal file
306
src/Admin/Settings.php
Normal file
@ -0,0 +1,306 @@
|
||||
<?php
|
||||
namespace BTCPayServer\Give\Admin;
|
||||
|
||||
use BTCPayServer\Client\Webhook;
|
||||
use BTCPayServer\Give\Gateway\BtcpayGateway;
|
||||
|
||||
/**
|
||||
* BTCPay Settings Class
|
||||
*/
|
||||
class Settings {
|
||||
/**
|
||||
* @var Settings
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Get the singleton instance
|
||||
*/
|
||||
public static function instance() {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
private function __construct() {
|
||||
// Register the gateway settings section
|
||||
add_filter('give_get_sections_gateways', [$this, 'registerSections']);
|
||||
|
||||
// Register the gateway settings fields
|
||||
add_filter('give_get_settings_gateways', [$this, 'registerSettings']);
|
||||
|
||||
// Add validation for API credentials
|
||||
add_action('admin_init', [$this, 'handleSettingsSave']);
|
||||
add_action('admin_notices', [$this, 'displayApiValidationMessages']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Gateway Section
|
||||
*/
|
||||
public function registerSections($sections) {
|
||||
$sections['btcpay-gateway'] = __('BTCPay Gateway', 'btcpay-for-givewp');
|
||||
return $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Gateway Settings
|
||||
*/
|
||||
public function registerSettings($settings) {
|
||||
$current_section = give_get_current_setting_section();
|
||||
|
||||
if ($current_section !== 'btcpay-gateway') {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'id' => 'btcpay_settings_title',
|
||||
'type' => 'title',
|
||||
'title' => __('BTCPay Server Gateway Settings', 'btcpay-for-givewp'),
|
||||
],
|
||||
[
|
||||
'id' => 'btcpay_url',
|
||||
'name' => __('BTCPay Server URL', 'btcpay-for-givewp'),
|
||||
'desc' => __('Enter the URL where you log into your BTCPay Server. e.g. https://mainnet.demo.btcpayserver.org', 'btcpay-for-givewp'),
|
||||
'type' => 'text',
|
||||
'default' => '',
|
||||
'sanitize_callback' => 'esc_url_raw',
|
||||
],
|
||||
[
|
||||
'id' => 'btcpay_store_id',
|
||||
'name' => __('Store ID', 'btcpay-for-givewp'),
|
||||
'desc' => __('Enter your BTCPay Server Store ID.', 'btcpay-for-givewp'),
|
||||
'type' => 'text',
|
||||
'default' => '',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
[
|
||||
'id' => 'btcpay_api_key',
|
||||
'name' => __('API Key', 'btcpay-for-givewp'),
|
||||
'desc' => __('Enter your BTCPay API key.', 'btcpay-for-givewp'),
|
||||
'type' => 'password',
|
||||
'default' => '',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
],
|
||||
[
|
||||
'id' => 'btcpay_settings',
|
||||
'type' => 'sectionend',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle settings save and API validation
|
||||
*/
|
||||
public function handleSettingsSave() {
|
||||
if (!$this->isSettingsSaveRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify nonce
|
||||
if (!wp_verify_nonce($_POST['_give-save-settings'], 'give-save-settings')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we're in the BTCPay settings section
|
||||
if (empty($_GET['section']) || $_GET['section'] !== 'btcpay-gateway') {
|
||||
return;
|
||||
}
|
||||
|
||||
$btcpay_url = isset($_POST['btcpay_url']) ? esc_url_raw($_POST['btcpay_url']) : '';
|
||||
$store_id = isset($_POST['btcpay_store_id']) ? sanitize_text_field($_POST['btcpay_store_id']) : '';
|
||||
$api_key = isset($_POST['btcpay_api_key']) ? sanitize_text_field($_POST['btcpay_api_key']) : '';
|
||||
|
||||
// Only validate if all fields are filled
|
||||
if ($btcpay_url && $store_id && $api_key) {
|
||||
$validation_result = $this->validateApiCredentials($btcpay_url, $store_id, $api_key);
|
||||
|
||||
if (is_wp_error($validation_result)) {
|
||||
set_transient('btcpay_api_validation_error', $validation_result->get_error_message(), 45);
|
||||
} else {
|
||||
set_transient('btcpay_api_validation_success', __('BTCPay for GiveWP: BTCPay Server API credentials verified successfully!', 'btcpay-for-givewp'), 45);
|
||||
}
|
||||
|
||||
// Setup webhook if it does not exist.
|
||||
if ($this->webhookExists($btcpay_url, $store_id, $api_key)) {
|
||||
set_transient('btcpay_webhook_exists', __('BTCPay for GiveWP: Webhook already exists, no need to create it.', 'btcpay-for-givewp'), 45);
|
||||
} else {
|
||||
$webhook = $this->createWebhook($btcpay_url, $store_id, $api_key);
|
||||
// Store webhook secret and other data for later use
|
||||
give_update_option('btcpay_wh_id', $webhook->getId());
|
||||
give_update_option('btcpay_wh_secret', $webhook->getSecret());
|
||||
give_update_option('btcpay_wh_url', $webhook->getUrl());
|
||||
set_transient('btcpay_webhook_created', __('BTCPay for GiveWP: Webhook successfully created.', 'btcpay-for-givewp'), 45);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display API validation messages
|
||||
*/
|
||||
public function displayApiValidationMessages() {
|
||||
$error = get_transient('btcpay_api_validation_error');
|
||||
$whError = get_transient('btcpay_webhook_error');
|
||||
$success = get_transient('btcpay_api_validation_success');
|
||||
$whExists = get_transient('btcpay_webhook_exists');
|
||||
$whCreated = get_transient('btcpay_webhook_created');
|
||||
|
||||
if ($error) {
|
||||
?>
|
||||
<div class="error">
|
||||
<p><?php echo esc_html($error); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
delete_transient('btcpay_api_validation_error');
|
||||
}
|
||||
|
||||
if ($whError) {
|
||||
?>
|
||||
<div class="error">
|
||||
<p><?php echo esc_html($whError); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
delete_transient('btcpay_webhook_error');
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
?>
|
||||
<div class="updated">
|
||||
<p><?php echo esc_html($success); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
delete_transient('btcpay_api_validation_success');
|
||||
}
|
||||
|
||||
if ($whExists) {
|
||||
?>
|
||||
<div class="updated">
|
||||
<p><?php echo esc_html($whExists); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
delete_transient('btcpay_webhook_exists');
|
||||
}
|
||||
|
||||
if ($whCreated) {
|
||||
?>
|
||||
<div class="updated">
|
||||
<p><?php echo esc_html($whCreated); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
delete_transient('btcpay_webhook_created');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current request is for saving settings
|
||||
*/
|
||||
private function isSettingsSaveRequest(): bool {
|
||||
return (
|
||||
isset($_POST['_give-save-settings']) &&
|
||||
isset($_GET['page']) &&
|
||||
$_GET['page'] === 'give-settings'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate BTCPay API credentials
|
||||
*/
|
||||
private function validateApiCredentials(string $url, string $store_id, string $api_key) {
|
||||
// Remove trailing slashes
|
||||
$url = rtrim($url, '/');
|
||||
|
||||
// Test endpoint
|
||||
$test_endpoint = "$url/api/v1/stores/$store_id";
|
||||
|
||||
$response = wp_remote_get($test_endpoint, [
|
||||
'headers' => [
|
||||
'Authorization' => "token $api_key",
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'timeout' => 30,
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return new \WP_Error(
|
||||
'btcpay_api_error',
|
||||
sprintf(
|
||||
__('Failed to connect to BTCPay Server: %s', 'btcpay-for-givewp'),
|
||||
$response->get_error_message()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code($response);
|
||||
if ($response_code !== 200) {
|
||||
return new \WP_Error(
|
||||
'btcpay_api_error',
|
||||
sprintf(
|
||||
__('Invalid API credentials. BTCPay Server returned: %s', 'btcpay-for-givewp'),
|
||||
wp_remote_retrieve_response_message($response)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the webhook exists.
|
||||
*/
|
||||
public function webhookExists(string $btcpay_url, string $store_id, string $api_key) {
|
||||
try {
|
||||
$existingWebhook = give_get_option('btcpay_wh_id');
|
||||
if (!$existingWebhook) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$client = new Webhook($btcpay_url, $api_key);
|
||||
$webhooks = $client->getStoreWebhooks($store_id);
|
||||
foreach ($webhooks->all() as $webhook) {
|
||||
if ($webhook->getId() === $existingWebhook) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a webhook on BTCPay Server
|
||||
*/
|
||||
public function createWebhook(string $url, string $store_id, string $api_key) {
|
||||
|
||||
// Which webhook events to subscribe to
|
||||
$events = [
|
||||
'InvoiceReceivedPayment',
|
||||
'InvoicePaymentSettled',
|
||||
'InvoiceProcessing',
|
||||
'InvoiceExpired',
|
||||
'InvoiceSettled',
|
||||
'InvoiceInvalid'
|
||||
];
|
||||
|
||||
try {
|
||||
// Create the webhook
|
||||
$client = new Webhook($url, $api_key);
|
||||
$webhook = $client->createWebhook(
|
||||
$store_id,
|
||||
BtcpayGateway::webhookUrl(),
|
||||
$events,
|
||||
null
|
||||
);
|
||||
|
||||
return $webhook;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
284
src/Gateway/BtcpayGateway.php
Normal file
284
src/Gateway/BtcpayGateway.php
Normal file
@ -0,0 +1,284 @@
|
||||
<?php
|
||||
namespace BTCPayServer\Give\Gateway;
|
||||
|
||||
use BTCPayServer\Client\Invoice;
|
||||
use BTCPayServer\Client\InvoiceCheckoutOptions;
|
||||
use BTCPayServer\Client\Webhook;
|
||||
use BTCPayServer\Util\PreciseNumber;
|
||||
use Give\Donations\Models\Donation;
|
||||
use Give\Donations\Models\DonationNote;
|
||||
use Give\Donations\ValueObjects\DonationStatus;
|
||||
use Give\Framework\Http\Response\Types\RedirectResponse;
|
||||
use Give\Framework\PaymentGateways\Commands\PaymentRefunded;
|
||||
use Give\Framework\PaymentGateways\Commands\RedirectOffsite;
|
||||
use Give\Framework\PaymentGateways\PaymentGateway;
|
||||
|
||||
/**
|
||||
* BTCPay Gateway for GiveWP
|
||||
*/
|
||||
class BtcpayGateway extends PaymentGateway
|
||||
{
|
||||
/**
|
||||
* Secure route methods for gateway callbacks
|
||||
*/
|
||||
public $secureRouteMethods = [
|
||||
'handlePaymentRedirect'
|
||||
];
|
||||
|
||||
/**
|
||||
* Route methods for gateway callbacks
|
||||
*/
|
||||
public $routeMethods = [
|
||||
'processWebhook'
|
||||
];
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function id(): string
|
||||
{
|
||||
return 'btcpay-gateway';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getId(): string
|
||||
{
|
||||
return self::id();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return __('BTCPay Server Gateway', 'btcpay-for-givewp');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getPaymentMethodLabel(): string
|
||||
{
|
||||
return __('Pay with Bitcoin / Lightning Network (BTCPay)', 'btcpay-for-givewp');
|
||||
}
|
||||
|
||||
public static function webhookUrl(): string
|
||||
{
|
||||
$instance = new static();
|
||||
return $instance->generateGatewayRouteUrl($instance->routeMethods[0]);
|
||||
}
|
||||
|
||||
public static function redirectUrl(): string
|
||||
{
|
||||
$instance = new static();
|
||||
return $instance->generateSecureGatewayRouteUrl($instance->secureRouteMethods[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register scripts for the gateway
|
||||
*/
|
||||
public function enqueueScript(int $formId)
|
||||
{
|
||||
wp_enqueue_script(
|
||||
'btcpay-gateway',
|
||||
BTCPAY_FOR_GIVEWP_URL . 'assets/js/btcpay-gateway.js',
|
||||
['react', 'wp-element'],
|
||||
BTCPAY_FOR_GIVEWP_VERSION,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings for the gateway form
|
||||
*/
|
||||
public function formSettings(int $formId): array
|
||||
{
|
||||
return [
|
||||
'message' => __('You will be redirected to a payment page to complete the donation.', 'btcpay-for-givewp'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy form field markup
|
||||
*/
|
||||
public function getLegacyFormFieldMarkup(int $formId, array $args): string
|
||||
{
|
||||
return "<div class='btcpay-gateway-help-text'>
|
||||
<p>" . __('You will be redirected to a payment page to complete the donation.', 'btcpay-for-givewp') . "</p>
|
||||
</div>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a payment
|
||||
*/
|
||||
public function createPayment(Donation $donation, $gatewayData)
|
||||
{
|
||||
// Get BTCPay Server credentials from settings
|
||||
$btcpayUrl = give_get_option('btcpay_url');
|
||||
$storeId = give_get_option('btcpay_store_id');
|
||||
$apiKey = give_get_option('btcpay_api_key');
|
||||
|
||||
// Generate return URL for after payment
|
||||
$returnUrl = $this->generateSecureGatewayRouteUrl(
|
||||
'handlePaymentRedirect',
|
||||
$donation->id,
|
||||
[
|
||||
'givewp-donation-id' => $donation->id,
|
||||
'givewp-success-url' => urlencode(give_get_success_page_uri()),
|
||||
]
|
||||
);
|
||||
|
||||
// Create the invoice checkout options
|
||||
$checkoutOptions = new InvoiceCheckoutOptions();
|
||||
$checkoutOptions->setRedirectUrl($returnUrl);
|
||||
|
||||
try {
|
||||
$client = new Invoice($btcpayUrl, $apiKey);
|
||||
|
||||
$invoice = $client->createInvoice(
|
||||
$storeId,
|
||||
$donation->amount->getCurrency()->getCode(),
|
||||
PreciseNumber::ParseFloat($donation->amount->formatToDecimal()),
|
||||
$donation->id,
|
||||
null,
|
||||
null,
|
||||
$checkoutOptions
|
||||
);
|
||||
|
||||
// Store the invoice ID in the donation as reference.
|
||||
$donation->gatewayTransactionId = 'invoice id: ' . $invoice->getId();
|
||||
$donation->save();
|
||||
|
||||
return new RedirectOffsite($invoice->getCheckoutLink());
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception('Failed to create invoice on BTCPay: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the return from BTCPay Server
|
||||
*/
|
||||
protected function handlePaymentRedirect(array $queryParams): RedirectResponse
|
||||
{
|
||||
$donationId = (int) $queryParams['givewp-donation-id'];
|
||||
|
||||
$successUrl = urldecode($queryParams['givewp-success-url']);
|
||||
|
||||
DonationNote::create([
|
||||
'donationId' => $donationId,
|
||||
'content' => 'Donor returned via redirect link from BTCPay invoice payment page.'
|
||||
]);
|
||||
|
||||
return new RedirectResponse($successUrl);
|
||||
}
|
||||
|
||||
public function processWebhook()
|
||||
{
|
||||
$rawData = file_get_contents('php://input');
|
||||
$payload = json_decode($rawData, false);
|
||||
|
||||
// Validate webhook payload data
|
||||
// Note: getallheaders() CamelCases all headers for PHP-FPM/Nginx but for others maybe not, so "BTCPay-Sig" may becomes "Btcpay-Sig".
|
||||
$headers = getallheaders();
|
||||
foreach ($headers as $key => $value) {
|
||||
if (strtolower($key) === 'btcpay-sig') {
|
||||
$signature = $value;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$webhookClient = new Webhook(give_get_option('btcpay_url'), give_get_option('btcpay_api_key'));
|
||||
// Validate the webhook request.
|
||||
if (!$webhookClient->isIncomingWebhookRequestValid($rawData, $signature, give_get_option('btcpay_wh_secret'))) {
|
||||
throw new \RuntimeException(
|
||||
'Invalid BTCPay Server payment webhook message received - signature did not match.'
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
error_log('BTCPay for GiveWP: ' . $e->getMessage());
|
||||
wp_die('Webhook request validation failed.');
|
||||
}
|
||||
|
||||
// Load the donation reference from the payload
|
||||
$invoice = $this->loadInvoice($payload->invoiceId);
|
||||
$donationId = (int) $invoice->getData()['metadata']['orderId'];
|
||||
|
||||
if ($donationId) {
|
||||
$donation = Donation::find($donationId);
|
||||
|
||||
// Process webhook events
|
||||
switch ($payload->type) {
|
||||
case 'InvoiceReceivedPayment':
|
||||
// As soon as we receive a payment, we update donation status.
|
||||
$donation->status = DonationStatus::PROCESSING();
|
||||
|
||||
DonationNote::create([
|
||||
'donationId' => $donationId,
|
||||
'content' => 'BTCPay Webhook: Payment received but not confirmed. Invoice ID: ' . $payload->invoiceId
|
||||
]);
|
||||
break;
|
||||
case 'InvoiceSettled':
|
||||
// Handle invoice settled event
|
||||
$donation->status = DonationStatus::COMPLETE();
|
||||
|
||||
DonationNote::create([
|
||||
'donationId' => $donationId,
|
||||
'content' => 'BTCPay Webhook: Payment complete (settled).'
|
||||
]);
|
||||
break;
|
||||
case 'InvoiceExpired':
|
||||
// Handle invoice expired event
|
||||
$donation->status = DonationStatus::ABANDONED();
|
||||
|
||||
DonationNote::create([
|
||||
'donationId' => $donationId,
|
||||
'content' => 'BTCPay Webhook: Invoice expired without any payment.'
|
||||
]);
|
||||
|
||||
break;
|
||||
case 'InvoiceInvalid':
|
||||
// Handle invoice invalid event
|
||||
$donation->status = DonationStatus::FAILED();
|
||||
|
||||
DonationNote::create([
|
||||
'donationId' => $donationId,
|
||||
'content' => 'Payment was set invalid manually on BTCPay.'
|
||||
]);
|
||||
|
||||
break;
|
||||
default:
|
||||
error_log('Unhandled BTCPay Server webhook event: ' . $payload->eventType);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Save the donation
|
||||
$donation->save();
|
||||
} else {
|
||||
// Do not throw error here as we don't want to break the webhook delivery on BTCPay Server side.
|
||||
error_log('Invalid donation ID in webhook payload.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load invoice from BTCPay Server
|
||||
*/
|
||||
public function loadInvoice(string $invoiceId): \BTCPayServer\Result\Invoice {
|
||||
try {
|
||||
$client = new Invoice(give_get_option('btcpay_url'), give_get_option('btcpay_api_key'));
|
||||
$invoice = $client->getInvoice(give_get_option('btcpay_store_id'), $invoiceId);
|
||||
return $invoice;
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception('Failed to load invoice from BTCPay: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function refundDonation(Donation $donation): PaymentRefunded
|
||||
{
|
||||
throw new \Exception('Refunds are not supported for BTCPay Server payments.');
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user