commit eebb6e17f989329530f92d12cea9d0fbe2428445
parent 84c1b3cca0f54da0c33f25918df6c67c51ba4afa
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 14 Sep 2025 17:33:53 +0200
starting rewrite for 1.1
Diffstat:
15 files changed, 1680 insertions(+), 1136 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,47 @@
+**~
+
+# Editors
+project.xml
+project.properties
+/nbproject/private/
+.buildpath
+.project
+.settings*
+sftp-config.json
+.idea
+._*
+
+# Grunt
+/node_modules/
+/deploy/
+package-lock.json
+
+# Composer
+/vendor/
+composer.lock
+
+# Sass
+.sass-cache/
+
+# OS X metadata
+.DS_Store
+
+# Windows junk
+Thumbs.db
+
+# ApiGen
+/wc-apidocs/
+
+# Tests (Unit and E2E)
+/tmp
+/tests/coverage/
+/tests/_output/*
+!/tests/_output/.gitkeep
+
+# Logs
+/logs
+
+# Build assets.
+select2.scss
+/assets/
+/languages/
diff --git a/.nvmrc b/.nvmrc
@@ -0,0 +1 @@
+v20
diff --git a/bin/build_i18n.sh b/bin/build_i18n.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+
+# Check for required version.
+WPCLI_VERSION=`wp cli version | cut -f2 -d' '`
+if [ ${WPCLI_VERSION:0:1} -lt "2" -o ${WPCLI_VERSION:0:1} -eq "2" -a ${WPCLI_VERSION:2:1} -lt "1" ]; then
+ echo WP-CLI version 2.1.0 or greater is required to make JSON translation files
+ exit
+fi
+
+# HELPERS.
+GREEN='\033[0;32m'
+GREY='\033[0;38m'
+NC='\033[0m' # No Color
+UNDERLINE_START='\e[4m'
+UNDERLINE_STOP='\e[0m'
+
+# Substitute JS source references with build references.
+for T in `find languages -name "*.pot"`
+ do
+ echo -e "\n${GREY}${UNDERLINE_START}Fixing references for: ${T}${UNDERLINE_STOP}${NC}"
+ sed \
+ -e 's/ resources\/js\/frontend\/[^:]*:/ assets\/frontend\/blocks.js:/gp' \
+ $T | uniq > $T-build
+
+ rm $T
+ mv $T-build $T
+ echo -e "${GREEN}Done${NC}"
+ done
diff --git a/bin/install_phpcs.sh b/bin/install_phpcs.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+# This file is in the public domain.
+# Installs phpcs
+
+set -e
+
+echo "Installing PHP_CodeSniffer..."
+composer require --dev "squizlabs/php_codesniffer=*"
+
+# Install Composer Installer plugin (required for automatic sniff registration)
+composer require --dev "dealerdirect/phpcodesniffer-composer-installer:^1.0"
+
+echo "==> Making sure composer plugin is allowed"
+composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
+
+# Install WordPress Coding Standards (WPCS)
+echo "Installing WordPress Coding Standards..."
+composer require --dev "wp-coding-standards/wpcs=*"
+
+# Install WooCommerce Coding Standards
+echo "Installing WooCommerce Coding Standards..."
+composer require --dev "woocommerce/woocommerce-sniffs=*"
+composer require --dev "phpcsstandards/phpcsutils:*"
+composer require --dev "phpcompatibility/phpcompatibility-wp:*"
+
+# Install PHPCompatibility
+echo "Installing PHPCompatibility..."
+composer require --dev "phpcompatibility/phpcompatibility-all"
+
+# Set installed paths for PHPCS
+echo "Setting PHPCS installed paths..."
+./vendor/bin/phpcs --config-set installed_paths \
+"vendor/wp-coding-standards/wpcs, \
+vendor/woocommerce/woocommerce-sniffs, \
+vendor/phpcompatibility/phpcompatibility-php73"
+
+echo "==> Running composer install / update"
+composer update --with-all-dependencies
+
+echo "Verifying installed standards..."
+./vendor/bin/phpcs -i
+
+echo "Done! Your standards are installed and ready to use with phpcs.xml."
diff --git a/class-wc-gnutaler-gateway.php b/class-wc-gnutaler-gateway.php
@@ -1,1134 +0,0 @@
-<?php
-/**
- * Plugin to add support for the GNU Taler payment system to WooCommerce.
- *
- * @package GNUTalerPayment
- */
-
-/**
- * Plugin Name: GNU Taler Payment for WooCommerce
- * Plugin URI: https://git.taler.net/woocommerce-taler
- * Description: This plugin enables payments via the GNU Taler payment system
- * Version: 1.1.0
- * Author: Dominique Hofmann, Jan Strübin, Christian Grothoff
- * Author URI: https://taler.net/
- * License: GNU General Public License v3.0
- * License URI: http://www.gnu.org/licenses/gpl-3.0.html
- * Requires Plugins: woocommerce
- * WC requires at least: 9.6
- * WC tested up to: 10.1
- * Text Domain: gnutaler
- **/
-
-/*
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-*/
-
-/**
- * Which version of the Taler merchant protocol is implemented
- * by this implementation? Used to determine compatibility.
- */
-define( 'GNU_TALER_MERCHANT_PROTOCOL_CURRENT', 21 );
-
-/**
- * How many merchant protocol versions are we backwards compatible with?
- */
-define( 'GNU_TALER_MERCHANT_PROTOCOL_AGE', 5 );
-
-require_once ABSPATH . 'wp-admin/includes/plugin.php';
-
-// Exit if accessed directly.
-if ( ! defined( 'ABSPATH' ) ) {
- exit();
-}
-
-/*
- * This action hook registers our PHP class as a WooCommerce payment gateway
- */
-
-/**
- * Adds the GNU Taler payment method to the other payment gateways.
- *
- * @param array $gateways all the payment gateways.
- * @return array
- */
-function gnutaler_add_gateway_class( $gateways ) {
- $gateways[] = 'WC_GNUTaler_Gateway';
- return $gateways;
-}
-
-// Declare that we are compatible with custom order tables
-add_action('before_woocommerce_init', function() {
- if (class_exists('\Automattic\WooCommerce\Utilities\FeaturesUtil')) {
- \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
- 'custom_order_tables',
- __FILE__,
- true
- );
- }
-});
-
-// Make GNU Taler payment gateway available to WC
-add_filter( 'woocommerce_payment_gateways', 'gnutaler_add_gateway_class' );
-
-/**
- * The class itself, please note that it is inside plugins_loaded action hook
- */
-add_action( 'plugins_loaded', 'gnutaler_init_gateway_class' );
-
-
-/**
- * Wrapper for the GNU Taler payment method class. Sets up internationalization.
- */
-function gnutaler_init_gateway_class() {
- // Setup textdomain for gettext style translations.
- $plugin_rel_path = basename( dirname( __FILE__ ) ) . '/languages';
- load_plugin_textdomain( 'gnutaler', false, $plugin_rel_path );
-
- // Check if WooCommerce is active, if not then deactivate and show error message.
- if ( ! in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ), true ) ) {
- deactivate_plugins( plugin_basename( __FILE__ ) );
- wp_die(
- sprintf(
- wp_kses(
- /* translators: argument is the link to the plugin page */
- __(
- '<strong>GNU Taler</strong> requires <strong>WooCommerce</strong> plugin to work. Please activate it or install it from <a href="http://wordpress.org/plugins/woocommerce/" target="_blank">here</a>.<br /><br />Back to the WordPress <a href="%s">Plugins page</a>.',
- 'gnutaler'
- ),
- array(
- 'strong' => array(),
- 'a' => array(
- 'href' => array(),
- '_target' => array(),
- ),
- )
- ),
- esc_url( get_admin_url( null, 'plugins.php' ) )
- )
- );
- }
-
- /**
- * GNU Taler Payment Gateway class.
- *
- * Handles the payments from the Woocommerce Webshop and sends the transactions to the GNU Taler Backend and the GNU Taler Wallet.
- */
- class WC_GNUTaler_Gateway extends WC_Payment_Gateway {
-
- /**
- * Cached handle to logging class.
- *
- * @var Plugin loggger.
- */
- private static $log = false;
-
- /**
- * True if logging is enabled in our configuration.
- *
- * @var Is logging enabled?
- */
- private static $log_enabled = false;
-
- /**
- * Unique id for the gateway.
- * @var string
- */
- public $id = 'gnutaler';
-
- /**
- * Class constructor
- */
- public function __construct() {
-
- $this->icon = plugins_url( '/assets/images/taler.png', __FILE__ );
-
- // We cannot use custom fields to show the QR code / do the wallet integration as WC doesn't give us the order_id at that time. Bummer.
- $this->has_fields = false;
-
- // Setup logging.
- $this->debug = 'yes' === $this->get_option( 'debug', 'no' );
- self::$log_enabled = $this->debug;
-
- // This gateway can support refunds, saved payment methods.
- $this->supports = array(
- 'products',
- 'refunds',
- );
-
- $this->method_title = _x( 'GNU Taler', 'GNU Taler payment method', 'gnutaler' );
- $this->method_description = __( 'This plugin enables payments via the GNU Taler payment system', 'gnutaler' );
- $this->init_form_fields();
- $this->init_settings();
-
- $this->title = $this->get_option( 'title' );
- $this->description = $this->get_option( 'description' );
- $this->instructions = $this->get_option( 'instructions' );
- $this->enable_for_virtual = true;
-
- $this->enabled = $this->get_option( 'enabled' );
- $this->gnu_taler_backend_url = $this->get_option( 'gnu_taler_backend_url' );
- // Remove trailing '/', we add one always ourselves...
- if ( substr( $this->gnu_taler_backend_url, -1 ) === '/' ) {
- $this->gnu_taler_backend_url = substr( $this->gnu_taler_backend_url, 0, -1 );
- }
-
- // Make transaction ID a link. We use the public version
- // here, as a user clicking on the link could not supply
- // the authorization header.
- // See also: https://woocommerce.wordpress.com/2014/08/05/wc-2-2-payment-gateways-adding-refund-support-and-transaction-ids/.
- $this->view_transaction_url = $this->gnu_taler_backend_url . '/orders/%s';
-
- // Register handler for the fulfillment URL.
- add_action(
- 'woocommerce_api_' . strtolower( get_class( $this ) ),
- array( &$this, 'fulfillment_url_handler' )
- );
-
- // This action hook saves the settings.
- add_action(
- 'woocommerce_update_options_payment_gateways_' . $this->id,
- array( $this, 'process_admin_options' )
- );
-
- // Modify WC canonical refund e-mail notifications to add link to order status page.
- // (according to https://www.businessbloomer.com/woocommerce-add-extra-content-order-email/).
- add_action(
- 'woocommerce_email_before_order_table',
- array( $this, 'add_content_refund_email' ),
- 20,
- 4
- );
- }
-
- public function is_available() {
- $res = ('yes' === $this->enabled);
- $this->debug ( get_woocommerce_currency() );
- $this->debug( $res
- ? __( "Returning payment method is available", 'gnutaler' )
- : __( "Returning payment method is unavailable", 'gnutaler' )
- );
- return $res;
- }
-
-
- /**
- * Initialise Gateway Settings Form Fields.
- */
- public function init_form_fields() {
- $this->form_fields = array(
- 'enabled' => array(
- 'title' => __( 'Enable/Disable', 'gnutaler' ),
- 'label' => __( 'Enable GNU Taler Gateway', 'gnutaler' ),
- 'type' => 'checkbox',
- 'description' => '',
- 'default' => 'no',
- ),
- 'title' => array(
- 'title' => __( 'Title', 'gnutaler' ),
- 'type' => 'text',
- 'description' => __( 'This is what the customer will see when choosing payment methods.', 'gnutaler' ),
- 'default' => 'GNU Taler',
- 'desc_tip' => true,
- ),
- 'description' => array(
- 'title' => __( 'Description', 'gnutaler' ),
- 'type' => 'textarea',
- 'description' => __( 'Payment method description which the customer sees during checkout.', 'gnutaler' ),
- 'default' => __( 'Pay digitally with GNU Taler', 'gnutaler' ),
- 'desc_tip' => true,
- ),
- 'instructions' => array(
- 'title' => __( 'Instructions', 'gnutaler' ),
- 'type' => 'textarea',
- 'description' => __( 'Instructions that will be added to the thank you page.', 'gnutaler' ),
- 'default' => __( 'Thank you for paying with GNU Taler', 'gnutaler' ),
- 'desc_tip' => true,
- ),
- 'gnu_taler_backend_url' => array(
- 'title' => __( 'Taler backend URL', 'gnutaler' ),
- 'type' => 'text',
- 'description' => __( 'Set the URL of the Taler backend. (Example: https://backend.demo.taler.net/)', 'gnutaler' ),
- 'default' => 'http://backend.demo.taler.net/instances/sandbox/',
- ),
- 'GNU_Taler_Backend_API_Key' => array(
- 'title' => __( 'Taler Backend API Key', 'gnutaler' ),
- 'type' => 'text',
- 'description' => __( 'Enter your API key to authenticate with the Taler backend. Will be sent as a "Bearer" token using the HTTP "Authorization" header. Must be prefixed with "secret-token:" (RFC 8959). This is not your password but a token to be obtained via the /token endpoint from the REST service.', 'gnutaler' ),
- 'default' => 'secret-token:sandbox',
- ),
- 'Order_text' => array(
- 'title' => __( 'Summary Text of the Order', 'gnutaler' ),
- 'type' => 'text',
- 'description' => __( 'Set the text the customer will see when confirming payment. #%%s will be substituted with the order number. (Example: MyShop #%%s)', 'gnutaler' ),
- 'default' => 'WooTalerShop #%s',
- ),
- 'GNU_Taler_refund_delay' => array(
- 'title' => __( 'How long should refunds be possible', 'gnutaler' ),
- 'type' => 'number',
- 'description' => __( 'Set the number of days a customer has to request a refund', 'gnutaler' ),
- 'default' => '14',
- ),
- 'debug' => array(
- 'title' => __( 'Debug Log', 'woocommerce' ),
- 'label' => __( 'Enable logging', 'woocommerce' ),
- 'description' => sprintf(
- /* translators: placeholder will be replaced with the path to the log file */
- __( 'Log GNU Taler events inside %s.', 'gnutaler' ),
- '<code>' . WC_Log_Handler_File::get_log_file_path( 'gnutaler' ) . '</code>'
- ),
- 'type' => 'checkbox',
- 'default' => 'no',
- ),
- );
- }
-
- /**
- * Called when WC sends out the e-mail notification for refunds.
- * Adds a Taler-specific notice for where to click to obtain
- * the refund.
- *
- * @param WC_Order $wc_order The order.
- * @param bool $sent_to_admin Not well documented by WooCommerce.
- * @param string $plain_text The plain text of the email.
- * @param string $email Target email address.
- */
- public function add_content_refund_email( $wc_order, $sent_to_admin, $plain_text, $email ) {
- if ( 'customer_refunded_order' === $email->id ) {
- $backend_url = $this->gnu_taler_backend_url;
- $wc_order_id = $wc_order->get_order_key() . '-' . $wc_order->get_order_number();
- $refund_url = $wc_order->get_meta( 'GNU_TALER_REFUND_URL' );
- echo sprintf(
- /* translators: placeholder will be replaced with the refund URL */
- esc_html( __( 'Refund granted. Visit <a href="%1$s">%1$s</a> to obtain the refund.', 'gnutaler' ) ),
- esc_url( $refund_url )
- );
- }
- }
-
- /**
- * Processes and saves options.
- * If there is an error thrown, will continue to save and validate fields, but
- * will leave the erroring field out.
- *
- * @return bool was anything saved?
- */
- public function process_admin_options() {
- $saved = parent::process_admin_options();
-
- // Maybe clear logs.
- if ( 'yes' !== $this->get_option( 'debug', 'no' ) ) {
- if ( empty( self::$log ) ) {
- self::$log = wc_get_logger();
- }
- self::$log->clear( 'gnutaler' );
- }
-
- return $saved;
- }
-
- /**
- * Required function to add the fields we want to show in the
- * payment method selection dialog. We show none.
- */
- public function payment_fields() {
- // We do not have any.
- }
-
- /**
- * Callback is called, when the user goes to the fulfillment URL.
- *
- * We check that the payment actually was made, and update WC accordingly.
- * If the order ID is unknown and/or the payment did not succeed, we
- * redirect to the home page and/or the user's order page (for logged in users).
- */
- public function fulfillment_url_handler(): void {
- global $woocommerce;
-
- // We intentionally do NOT verify the nonce here, as this page
- // should work even if the deep link is shared with other users
- // or even non-users.
- // phpcs:disable WordPress.Security.NonceVerification
- if ( ! isset( $_GET['order_id'] ) ) {
- $this->debug( __( "Lacking 'order_id', forwarding user to neutral page", 'gnutaler' ) );
- if ( is_user_logged_in() ) {
- wp_safe_redirect( get_home_url() . wc_get_page_permalink( 'myaccount' ) );
- } else {
- wp_safe_redirect( get_home_url() . wc_get_page_permalink( 'shop' ) );
- }
- exit;
- }
-
- // Gets the order id from the fulfillment url.
- $taler_order_id = sanitize_text_field( wp_unslash( $_GET['order_id'] ) );
- // phpcs:enable
- $order_id_array = explode( '-', $taler_order_id );
- $order_id_name = $order_id_array[0];
- $order_id = $order_id_array[1];
- $wc_order = wc_get_order( $order_id );
- $backend_url = $this->gnu_taler_backend_url;
-
- $payment_confirmation = $this->call_api(
- 'GET',
- $backend_url . '/private/orders/' . $taler_order_id,
- false
- );
- $payment_body = $payment_confirmation['message'];
- $payment_http_status = $payment_confirmation['http_code'];
-
- switch ( $payment_http_status ) {
- case 200:
- // Here we check what kind of http code came back from the backend.
- $merchant_order_status_response = json_decode(
- $payment_body,
- $assoc = true
- );
- if ( ! $merchant_order_status_response ) {
- wc_add_notice(
- __( 'Payment error:', 'gnutaler' ) .
- __( 'backend did not respond', 'gnutaler' )
- );
- $this->notice( __( 'Payment failed: no reply from Taler backend', 'gnutaler' ) );
- wp_safe_redirect( $this->get_return_url( $order_id ) );
- exit;
- }
- if ( 'paid' === $merchant_order_status_response['order_status'] ) {
- $this->notice( __( 'Payment succeeded and the user was forwarded to the order confirmed page', 'gnutaler' ) );
- // Completes the order, storing a transaction ID.
- $wc_order->payment_complete( $taler_order_id );
- // Empties the shopping cart.
- WC()->cart->empty_cart();
- } else {
- wc_add_notice(
- __( 'Payment error:', 'gnutaler' ) .
- __( 'backend did not confirm payment', 'gnutaler' )
- );
- $this->notice( __( 'Backend did not confirm payment', 'gnutaler' ) );
- }
- wp_safe_redirect( $this->get_return_url( $wc_order ) );
- exit;
- default:
- $this->error(
- __( 'An error occurred during the second request to the GNU Taler backend: ', 'gnutaler' )
- . $payment_http_status . ' - ' . $payment_body
- );
- wc_add_notice( __( 'Payment error:', 'gnutaler' ) . $payment_http_status . ' - ' . $payment_body );
- wp_safe_redirect( $this->get_return_url( $order_id ) );
- break;
- }
- $cart_url = $woocommerce->cart->wc_get_cart_url();
- if ( is_set( $cart_url ) ) {
- wp_safe_redirect( get_home_url() . $cart_url );
- } else {
- wp_safe_redirect( wc_get_page_permalink( 'shop' ) );
- }
- exit;
- }
-
- /**
- * Sends a request to a url via HTTP.
- *
- * Sends a request to a GNU Taler Backend over HTTP and returns the result.
- * The request can be sent as POST or GET. PATCH is not supported.
- *
- * @param string $method POST or GET supported only. Thanks WordPress.
- * @param string $url URL for the request to make to the GNU Taler Backend.
- * @param string $body The content of the request (for POST).
- *
- * @return array The return array will either have the successful return value or a detailed error message.
- */
- private function call_api( $method, $url, $body ): array {
- $apikey = $this->get_option( 'GNU_Taler_Backend_API_Key' );
- $args = array(
- 'timeout' => 30, // In seconds.
- 'redirection' => 2, // How often.
- 'httpversion' => '1.1', // Taler will support.
- 'user-agent' => '', // Minimize information leakage.
- 'blocking' => true, // We do nothing without it.
- 'headers' => array(
- 'Authorization' => 'Bearer ' . $apikey,
- ),
- 'decompress' => true,
- 'limit_response_size' => 1024 * 1024, // More than enough.
- );
- if ( $body ) {
- $args['body'] = wp_json_encode( $body, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES, 16 );
- $args['headers']['Content-type'] = 'application/json';
- $args['compress'] = true;
- }
- $this->debug( 'Issuing HTTP ' . $method . ' request to ' . $url . ' with options ' . wp_json_encode( $args ) . ' and body ' . $body );
-
- switch ( $method ) {
- case 'POST':
- $response = wp_remote_post( $url, $args );
- break;
- case 'GET':
- $response = wp_remote_get( $url, $args );
- break;
- default:
- $this->debug( 'HTTP method ' . $method . ' not supported' );
- return null;
- }
- if ( is_wp_error( $response ) ) {
- $error_code = $response->get_error_code();
- $error_data = $response->get_error_data( $error_code );
- $this->warning(
- sprintf(
- /* translators: first placeholder is the error code, second the error data */
- __( 'HTTP failure %1$s with data %2$s', 'gnutaler' ),
- $error_code,
- $error_data
- )
- );
-
- return array(
- 'http_code' => 0,
- 'message' => $error_code,
- );
- }
- $http_code = wp_remote_retrieve_response_code( $response );
- $body = wp_remote_retrieve_body( $response );
- $this->debug(
- sprintf(
- /* translators: first placeholder is the HTTP status code, second the body of the HTTP reply */
- __( 'HTTP status %1$s with response body %2$s', 'gnutaler' ),
- $http_code,
- $body
- )
- );
- return array(
- 'http_code' => $http_code,
- 'message' => $body,
- );
- }
-
- /**
- * Checks if the configuration is valid. Used by WC to redirect
- * the admin to the setup dialog if they enable the backend and
- * it is not properly configured.
- */
- public function needs_setup() {
- $backend_url = $this->gnu_taler_backend_url;
- $verify_result = $this->verify_backend_url(
- $backend_url,
- null
- );
-
- $this->debug( $verify_result
- ? __( "Backend is ready", 'gnutaler' )
- : __( "Backend is not setup correctly", 'gnutaler' ) );
-
- return ! $verify_result;
- }
-
- /**
- * Verifying if the url to the backend given in the plugin options is valid or not.
- *
- * @param string $url URL to the backend.
- * @param string $ecurrency expected currency of the order.
- *
- * @return bool - Returns if valid or not.
- */
- private function verify_backend_url( $url, $ecurrency ): bool {
- $this->info( "Verifying backend URL " . $url );
-
- $config = $this->call_api( 'GET', $url . '/config', false );
- $config_http_status = $config['http_code'];
- $config_body = $config['message'];
- switch ( $config_http_status ) {
- case 200:
- $info = json_decode( $config_body, $assoc = true );
- if ( ! $info ) {
- $this->error(
- sprintf(
- /* translators: placeholder will be replaced with the URL of the backend */
- __( '/config response of backend at %s did not decode as JSON', 'gnutaler' ),
- $url
- )
- );
- return false;
- }
- $version = $info['version'];
- if ( ! $version ) {
- $this->error(
- sprintf(
- /* translators: placeholder will be replaced with the URL of the backend */
- __( "No 'version' field in /config reply from Taler backend at %s", 'gnutaler' ),
- $url
- )
- );
- return false;
- }
- $ver = explode( ':', $version, 3 );
- $current = $ver[0];
- $revision = $ver[1];
- $age = $ver[2];
- if ( ( ! is_numeric( $current ) )
- || ( ! is_numeric( $revision ) )
- || ( ! is_numeric( $age )
- || ( $age > $current ) )
- ) {
- $this->error(
- sprintf(
- /* translators: placeholder will be replaced with the (malformed) version number */
- __( "/config response at backend malformed: '%s' is not a valid version", 'gnutaler' ),
- $version
- )
- );
- return false;
- }
- if ( GNU_TALER_MERCHANT_PROTOCOL_CURRENT < $current - $age ) {
- // Our implementation is too old!
- $this->error(
- sprintf(
- /* translators: placeholder will be replaced with the version number */
- __( 'Backend protocol version %s is too new: please update the GNU Taler plugin', 'gnutaler' ),
- $version
- )
- );
- return false;
- }
- if ( GNU_TALER_MERCHANT_PROTOCOL_CURRENT - GNU_TALER_MERCHANT_PROTOCOL_AGE > $current ) {
- // Merchant implementation is too old!
- $this->error(
- sprintf(
- /* translators: placeholder will be replaced with the version number */
- __( 'Backend protocol version %s unsupported: please update the backend', 'gnutaler' ),
- $version
- )
- );
- return false;
- }
- $currency = $info['currency'];
- if ( ( ! is_null( $ecurrency ) ) &&
- ( 0 !== strcasecmp( $currency, $ecurrency ) ) ) {
- $this->error(
- sprintf(
- /* translators: first placeholder is the Taler backend currency, second the expected currency from WooCommerce */
- __( 'Backend currency %1$s does not match order currency %2$s', 'gnutaler' ),
- $currency,
- $ecurrency
- )
- );
- return false;
- }
- $this->debug(
- sprintf(
- /* translators: placeholder will be replaced with the URL of the backend */
- __( '/config check for Taler backend at %s succeeded', 'gnutaler' ),
- $url
- )
- );
- return true;
- default:
- $this->error(
- sprintf(
- /* translators: placeholder will be replaced with the HTTP status code returned by the backend */
- __( 'Backend failed /config request with unexpected HTTP status %s', 'gnutaler' ),
- $config_http_status
- )
- );
- return false;
- }
- }
-
- /**
- * Processes the payment after the checkout
- *
- * If the payment process finished successfully the user is being redirected to its GNU Taler Wallet.
- * If an error occurs it returns void and throws an error.
- *
- * @param string $order_id ID of the order to get the Order from the WooCommerce Webshop.
- *
- * @return array|void - Array with result => success and redirection url otherwise it returns void.
- */
- public function process_payment( $order_id ) {
- // We need the order ID to get any order detailes.
- $wc_order = wc_get_order( $order_id );
-
- // Gets the url of the backend from the WooCommerce Settings.
- $backend_url = $this->gnu_taler_backend_url;
-
- // Log entry that the customer started the payment process.
- $this->info( __( 'User started the payment process with GNU Taler.', 'gnutaler' ) );
-
- if ( ! $this->verify_backend_url( $backend_url, $wc_order->get_currency() ) ) {
- wc_add_notice( __( 'Something went wrong please contact the system administrator of the webshop and send the following error: GNU Taler backend URL invalid', 'gnutaler' ), 'error' );
- $this->error( __( 'Checkout process failed: Invalid backend url.', 'gnutaler' ) );
- return;
- }
- $order_json = $this->convert_to_checkout_json( $order_id );
-
- $this->info( __( 'Sending POST /private/orders request send to Taler backend', 'gnutaler' ) );
- $order_confirmation = $this->call_api(
- 'POST',
- $backend_url . '/private/orders',
- $order_json
- );
- $order_body = $order_confirmation['message'];
- $order_http_status = $order_confirmation['http_code'];
- switch ( $order_http_status ) {
- case 200:
- $post_order_response = json_decode( $order_body, $assoc = true );
- if ( ! $post_order_response ) {
- $this->error(
- __( 'POST /private/orders request to Taler backend returned 200 OK, but not a JSON body: ', 'gnutaler' )
- . $order_body
- );
- wc_add_notice( __( 'Malformed response from Taler backend. Please contact the system administrator.' ) );
- $wc_order->set_status( 'cancelled' );
- return;
- }
- $taler_order_id = $post_order_response ['order_id'];
- $taler_order_token = $post_order_response ['token'];
- if ( ! $taler_order_id ) {
- $this->error(
- __( 'Response to POST /private/orders request to Taler backend lacks order_id field: ', 'gnutaler' )
- . $order_body
- );
- wc_add_notice( __( 'Malformed response from Taler backend. Please contact the system administrator.', 'gnutaler' ) );
- $wc_order->set_status( 'cancelled' );
- return;
- }
- $this->info( __( 'POST /private/orders successful. Redirecting user to Taler Backend.', 'gnutaler' ) );
- return array(
- 'result' => 'success',
- 'redirect' => $backend_url . '/orders/' . $taler_order_id . '?token=' . $taler_order_token,
- );
- case 404:
- $post_order_error = json_decode( $order_body, $assoc = true );
- if ( ! $post_order_error ) {
- $this->error(
- __( 'POST /private/orders request to Taler backend returned 404, but not a JSON body: ', 'gnutaler' )
- . $order_body
- );
- wc_add_notice( __( 'Malformed response from Taler backend. Please contact the system administrator.', 'gnutaler' ) );
- $wc_order->set_status( 'cancelled' );
- return;
- }
- $this->error(
- __( 'POST /private/orders request to Taler backend failed: ', 'gnutaler' )
- . $post_order_error['code'] . '('
- . $order_http_status . '): ' . $order_body
- );
- wc_add_notice( __( 'Taler backend not configured correctly. Please contact the system administrator.', 'gnutaler' ) );
- $wc_order->set_status( 'cancelled' );
- return;
- case 410:
- // We don't use inventory management via GNU Taler's backend, so this error should never apply.
- // Handle with 'default' case below.
- default:
- $this->error(
- __( 'POST /private/orders request to Taler backend failed: ', 'gnutaler' )
- . $post_order_error['code'] . '('
- . $order_http_status . '): '
- . $order_body
- );
- wc_add_notice( __( 'Unexpected problem with the Taler backend. Please contact the system administrator.', 'gnutaler' ) );
- $wc_order->set_status( 'cancelled' );
- return;
- }
- }
-
- /**
- * Converts the order into a JSON format that can be send to the GNU Taler Backend.
- *
- * @param string $order_id ID of the order to get the Order from the WooCommerce Webshop.
- *
- * @return array - return the JSON Format.
- */
- public function convert_to_checkout_json( $order_id ): array {
- $wc_order = wc_get_order( $order_id );
- $wc_order_total_amount = $wc_order->get_total();
- $wc_order_currency = $wc_order->get_currency();
- $wc_cart = WC()->cart->get_cart();
- $wc_order_id = $wc_order->get_order_key() . '-' . $wc_order->get_order_number();
- $wc_order_products_array = $this->mutate_products_to_json_format( $wc_cart, $wc_order_currency );
- $refund_delay = $this->get_option( 'GNU_Taler_refund_delay' );
- $order_json = array(
- 'order' => array(
- 'amount' => $wc_order_currency . ':' . $wc_order_total_amount,
- 'summary' => sprintf(
- $this->get_option( 'Order_text' ),
- $wc_order->get_order_number()
- ),
- // NOTE: This interacts with the 'add_action' call
- // to invoke the 'fulfillment_url_handler' when the
- // user goes to this URL!
- 'fulfillment_url' => get_home_url()
- . '/?wc-api='
- . strtolower( get_class( $this ) )
- . '&order_id='
- . $wc_order_id,
- 'order_id' => $wc_order_id,
- 'products' => $wc_order_products_array,
- 'delivery_location' => $this->mutate_shipping_information_to_json_format( $wc_order ),
- ),
- );
- if ( isset( $refund_delay ) ) {
- $order_json['refund_delay'] = array(
- 'd_us' => 1000 * 1000 * 60 * 60 * 24 * intval( $refund_delay ),
- );
- }
- return $order_json;
- }
-
- /**
- * Mutates the products in the cart into a format which can be included in a JSON file.
- *
- * @param WC_Cart $wc_cart The content of the WooCommerce Cart.
- * @param string $wc_order_currency The currency the WooCommerce Webshop uses.
- *
- * @return array - Returns an array of products.
- */
- private function mutate_products_to_json_format( $wc_cart, $wc_order_currency ): array {
- $wc_order_products_array = array();
- foreach ( $wc_cart as $product ) {
- $wc_order_products_array[] = array(
- 'description' => $product['data']->get_title(),
- 'quantity' => $product['quantity'],
- 'price' => $wc_order_currency . ':' . $product['data']->get_price(),
- 'product_id' => strval( $product['data']->get_id() ),
- );
- }
- return $wc_order_products_array;
- }
-
- /**
- * Processes the refund transaction if requested by the system administrator of the webshop
- *
- * If the refund request is finished successfully it returns an refund url, which can be send to the customer to finish the refund transaction.
- * If an error it will throw a WP_Error message and inform the system administrator.
- *
- * @param WC_Order $wc_order The WooCommerce order object we are processing.
- *
- * @return array
- */
- private function mutate_shipping_information_to_json_format( $wc_order ): array {
- $whitechar_encounter = false;
- $shipping_address_street = '';
- $shipping_address_street_nr = '';
-
- $store_address = $wc_order->get_shipping_address_1( $context = 'view' );
- if ( is_null( $store_address ) || empty( $store_address ) )
- $store_address = $wc_order->get_billing_address_1( $context = 'view' );
- $store_address_inverted = strrev( $store_address );
- $store_address_array = str_split( $store_address_inverted );
- $country = $wc_order->get_shipping_country ($context = 'view');
- if ( is_null( $country ) || empty( $country ) )
- $country = $wc_order->get_billing_country ($context = 'view');
- $state = $wc_order->get_shipping_state ($context = 'view');
- if ( is_null( $state ) || empty( $state ) )
- $state = $wc_order->get_billing_state ($context = 'view');
- $city = $wc_order->get_shipping_city ($context = 'view');
- if ( is_null( $city ) || empty( $city ) )
- $city = $wc_order->get_billing_city ($context = 'view');
- $postcode = $wc_order->get_shipping_postcode ($context = 'view');
- if ( is_null( $postcode ) || empty( $postcode ) )
- $postcode = $wc_order->get_billing_postcode ($context = 'view');
-
- $this->info (
- sprintf(
- 'Shipping address is %s - %s - %s - %s - %s',
- $store_address,
- $wc_order->get_shipping_country ($context = 'view'),
- $wc_order->get_shipping_state ($context = 'view'),
- $wc_order->get_shipping_city ($context = 'view'),
- $wc_order->get_shipping_postcode ($context = 'view')) );
-
- $this->info (
- sprintf(
- 'Billing address is %s - %s - %s - %s - %s',
- $store_address,
- $wc_order->get_billing_country ($context = 'view'),
- $wc_order->get_billing_state ($context = 'view'),
- $wc_order->get_billing_city ($context = 'view'),
- $wc_order->get_billing_postcode ($context = 'view')) );
- $this->info (
- sprintf(
- 'Using address is %s - %s - %s - %s - %s',
- $store_address,
- $country,
- $state,
- $city,
- $postcode));
-
- // Split the address into street and street number.
- foreach ( $store_address_array as $char ) {
- if ( ! $whitechar_encounter ) {
- $shipping_address_street .= $char;
- } elseif ( ctype_space( $char ) ) {
- $whitechar_encounter = true;
- } else {
- $shipping_address_street .= $char;
- }
- }
- $ret = array(
- 'country' => $country,
- 'country_subdivision' => $state,
- 'town' => $city,
- 'post_code' => $postcode,
- 'street' => $shipping_address_street,
- 'building_number' => $shipping_address_street_nr,
- );
- if ( null !== $wc_order->get_shipping_address_2() ) {
- $address_lines = array(
- $wc_order->get_shipping_address_1(),
- $wc_order->get_shipping_address_2(),
- );
- $ret['address_lines'] = $address_lines;
- }
- return $ret;
- }
-
- /**
- * Processes the refund transaction if requested by the system administrator of the webshop
- *
- * If the refund request is finished successfully it returns an refund url, which can be send to the customer to finish the refund transaction.
- * If an error it will throw a WP_Error message and inform the system administrator.
- *
- * @param string $order_id Order id for logging.
- * @param string $amount Amount that is requested to be refunded.
- * @param string $reason Reason for the refund request.
- *
- * @return bool|WP_Error - Returns true or throws an WP_Error message in case of error.
- */
- public function process_refund( $order_id, $amount = null, $reason = '' ) {
- $wc_order = wc_get_order( $order_id );
-
- $this->info(
- sprintf(
- /* translators: first placeholder is the numeric amount, second the currency, and third the reason for the refund */
- __( 'Refund process started with the refunded amount %1$s %2$s and the reason %3$s.' ),
- $amount,
- $wc_order->get_currency(),
- $reason
- )
- );
-
- // Gets the url of the backend from the WooCommerce Settings.
- $backend_url = $this->gnu_taler_backend_url;
-
- // Get the current status of the order.
- $wc_order_status = $wc_order->get_status();
-
- // Checks if current status is already set as paid.
- if ( ! ( 'processing' === $wc_order_status
- || 'on hold' === $wc_order_status
- || 'completed' === $wc_order_status )
- ) {
- $this->error( __( 'The status of the order does not allow a refund', 'gnutaler' ) );
- return new WP_Error( 'error', __( 'The status of the order does not allow for a refund.', 'gnutaler' ) );
- }
-
- $refund_request = array(
- 'refund' => $wc_order->get_currency() . ':' . $amount,
- 'reason' => $reason,
- );
- $wc_order_id = $wc_order->get_order_key() . '-' . $wc_order->get_order_number();
- $refund_result = $this->call_api(
- 'POST',
- $backend_url . '/private/orders/' . $wc_order_id . '/refund',
- $refund_request
- );
-
- $refund_http_status = $refund_result['http_code'];
- $refund_body = $refund_result['message'];
- switch ( $refund_http_status ) {
- case 200:
- $refund_response = json_decode( $refund_body, $assoc = true );
- if ( ! $refund_response ) {
- $this->error( __( 'Malformed 200 response from Taler backend: not even in JSON', 'gnutaler' ) );
- return new WP_Error( 'error', __( 'Malformed response from Taler backend', 'gnutaler' ) );
- }
- $refund_uri = $refund_response['taler_refund_uri'];
- $h_contract = $refund_response['h_contract'];
- if ( ( ! $refund_uri ) || ( ! $h_contract ) ) {
- $this->error( __( 'Malformed 200 response from Taler backend: lacks taler_refund_uri', 'gnutaler' ) );
- return new WP_Error( 'error', __( 'Malformed response from Taler backend', 'gnutaler' ) );
- }
- $refund_url = $backend_url
- . '/orders/'
- . $wc_order_id
- . '?h_contract='
- . $h_contract;
- $wc_order->add_meta_data( 'GNU_TALER_REFUND_URL', $refund_url );
- $wc_order->update_status( 'refunded' );
- $this->debug(
- sprintf(
- /* translators: argument is the Taler refund URI */
-
- __( 'Received refund URI %s from Taler backend', 'gnutaler' ),
- $refund_uri
- )
- );
- $this->notice(
- sprintf(
- /* translators: argument is the Taler refund URL */
- __( 'The user must visit %s to obtain the refund', 'gnutaler' ),
- $refund_url
- )
- );
- return true;
- case 403:
- return new WP_Error(
- 'error',
- __( 'Refunds are disabled for this order. Check the refund_delay option for the Taler payment plugin.', 'gnutaler' )
- );
- case 404:
- $refund_error = json_decode( $refund_body, $assoc = true );
- if ( ! $refund_error ) {
- return new WP_Error(
- 'error',
- sprintf(
- /* translators: argument is the HTTP status returned by the backend */
- __( 'Unexpected failure %s without Taler error code from Taler backend', 'gnutaler' ),
- $refund_http_status
- )
- );
- }
- $ec = $refund_error['code'];
- switch ( $ec ) {
- case 2000: // TALER_EC_INSTANCE_UNKNOWN!
- return new WP_Error(
- 'error',
- __( 'Instance unknown reported by Taler backend', 'gnutaler' )
- );
- case 2601: // TALER_EC_REFUND_ORDER_ID_UNKNOWN!
- return new WP_Error(
- 'error',
- __( 'Order unknown reported by Taler backend', 'gnutaler' )
- );
- default:
- return new WP_Error(
- 'error',
- sprintf(
- /* translators: placeholder will be replaced with the numeric GNU Taler error code */
- __( 'Unexpected error %s reported by Taler backend', 'gnutaler' ),
- $ec
- )
- );
- }
- // This line is unreachable.
- case 409:
- return new WP_Error(
- 'error',
- __( 'Requested refund amount exceeds original payment. This is not allowed!', 'gnutaler' )
- );
- case 410:
- return new WP_Error(
- 'error',
- __( 'Wire transfer already happened. It is too late for a refund with Taler!', 'gnutaler' )
- );
- default:
- $refund_error = json_decode( $refund_body, $assoc = true );
- if ( ! $refund_error ) {
- $ec = $refund_error['code'];
- } else {
- $ec = 0;
- }
- return new WP_Error(
- 'error',
- sprintf(
- /* translators: first placeholder is the HTTP status code, second the numeric GNU Taler error code */
- __( 'Unexpected failure %1$s/%2$s from Taler backend', 'gnutaler' ),
- $refund_http_status,
- $ec
- )
- );
- }
- }
-
- /**
- * Log $msg for debugging
- *
- * @param string $msg message to log.
- */
- private function debug( $msg ) : void {
- $this->log( 'debug', $msg );
- }
-
- /**
- * Log $msg as a informational
- *
- * @param string $msg message to log.
- */
- private function info( $msg ) : void {
- $this->log( 'info', $msg );
- }
-
- /**
- * Log $msg as a notice
- *
- * @param string $msg message to log.
- */
- private function notice( $msg ) : void {
- $this->log( 'notice', $msg );
- }
-
- /**
- * Log $msg as a warning.
- *
- * @param string $msg message to log.
- */
- private function warning( $msg ) : void {
- $this->log( 'warning', $msg );
- }
-
- /**
- * Log $msg as an error
- *
- * @param string $msg message to log.
- */
- private function error( $msg ) : void {
- $this->log( 'error', $msg );
- }
-
- /**
- * Log $msg at log $level.
- *
- * @param string $level log level to use when logging.
- * @param string $msg message to log.
- */
- private function log( $level, $msg ) {
- if ( ! self::$log_enabled ) {
- return;
- }
- if ( function_exists( 'wp_get_current_user()' ) ) {
- $user_id = wp_get_current_user();
- if ( ! isset( $user_id ) ) {
- $user_id = __( '<user ID not set>', 'gnutaler' );
- }
- } else {
- $user_id = 'Guest';
- }
- // We intentionally do NOT verify the nonce here, as logging
- // should always work.
- // phpcs:disable WordPress.Security.NonceVerification
- if ( isset ($_GET['order_id'] ) ) {
- $order_id = sanitize_text_field( wp_unslash( $_GET['order_id'] ) );
- }
- else
- {
- $order_id = 'NONE';
- }
- // phpcs:enable
- if ( empty( self::$log ) ) {
- self::$log = wc_get_logger();
- }
- self::$log->log( $level, $user_id . '-' . $order_id . ': ' . $msg, array( 'source' => 'gnutaler' ) );
- }
-
- }
-}
diff --git a/composer.json b/composer.json
@@ -0,0 +1,16 @@
+{
+ "require-dev": {
+ "squizlabs/php_codesniffer": "*",
+ "wp-coding-standards/wpcs": "*",
+ "woocommerce/woocommerce-sniffs": "*",
+ "phpcompatibility/phpcompatibility-all": "^1.1",
+ "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+ "phpcsstandards/phpcsutils": "*",
+ "phpcompatibility/phpcompatibility-wp": "*"
+ },
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ }
+ }
+}
diff --git a/includes/blocks/class-wc-gnutaler-payments-blocks.php b/includes/blocks/class-wc-gnutaler-payments-blocks.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * GNU Taler payment method integration for WooCommerce Blocks
+ */
+
+/*
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType;
+
+final class WC_Gateway_Gnutaler_Blocks_Support extends AbstractPaymentMethodType {
+
+ /**
+ * The gateway instance.
+ *
+ * @var WC_Gateway_Gnutaler
+ */
+ private $gateway;
+
+ /**
+ * Payment method id.
+ */
+ protected $name = 'gnutaler';
+
+ /**
+ * Initializes the payment method type.
+ */
+ public function initialize() {
+ $this->settings = get_option('woocommerce_gnutaler_settings', array());
+ $gateways = WC()->payment_gateways->payment_gateways();
+ $this->gateway = $gateways[ $this->name ];
+ }
+
+ /**
+ * Returns if this payment method should be active.
+ *
+ * @return boolean
+ */
+ public function is_active() {
+ return $this->gateway->is_available();
+ }
+
+ /**
+ * Returns an array of scripts/handles to be registered for this payment method.
+ *
+ * @return array
+ */
+ public function get_payment_method_script_handles() {
+ $script_path = '/assets/js/frontend/blocks.js';
+ $script_asset_path = plugin_dir_path(__FILE__) . '../assets/js/frontend/blocks.asset.php';
+ $script_asset = file_exists($script_asset_path)
+ ? require $script_asset_path
+ : array(
+ 'dependencies' => array(),
+ 'version' => '1.0.0',
+ );
+ $script_url = WC_Gnutaler_Payments::plugin_url() . $script_path;
+
+ wp_register_script(
+ 'wc-gnutaler-payments-blocks',
+ $script_url,
+ $script_asset['dependencies'],
+ $script_asset['version'],
+ true
+ );
+
+ if ( function_exists( 'wp_set_script_translations' ) ) {
+ wp_set_script_translations( 'wc-gnutaler-payments-blocks', 'woocommerce-gateway-gnutaler', WC_Gnutaler_Payments::plugin_abspath() . 'languages/' );
+ }
+
+ return array( 'wc-gnutaler-payments-blocks' );
+ }
+
+ /**
+ * Returns an array of key=>value pairs of data made available to the payment methods script.
+ */
+ public function get_payment_method_data() {
+ $payment_gateways_class = WC()->payment_gateways();
+ $payment_gateways = $payment_gateways_class->payment_gateways();
+ $gateway = $payment_gateways['gnutaler'];
+
+ return array(
+ 'title' => $this->get_setting( 'title' ),
+ 'description' => $this->get_setting( 'description' ),
+ 'supports' => array_filter( $this->gateway->supports, array( $this->gateway, 'supports' ) ),
+ );
+ }
+}
diff --git a/includes/class-wc-gateway-gnutaler.php b/includes/class-wc-gateway-gnutaler.php
@@ -0,0 +1,1129 @@
+<?php
+/**
+ * Plugin to add support for the GNU Taler payment system to WooCommerce.
+ *
+ * @package GNUTalerPayment
+ */
+
+/**
+ * Plugin Name: GNU Taler Payment for WooCommerce
+ * Plugin URI: https://git.taler.net/gnu-taler-payment-for-woocommerce
+ * Description: This plugin enables payments via the GNU Taler payment system
+ * Version: 1.1.0
+ * Author: Dominique Hofmann, Jan Strübin, Christian Grothoff
+ * Author URI: https://taler.net/
+ * License: GNU General Public License v3.0
+ * License URI: http://www.gnu.org/licenses/gpl-3.0.html
+ * Requires Plugins: woocommerce
+ * WC requires at least: 9.6
+ * WC tested up to: 10.1
+ * Text Domain: gnutaler
+ **/
+
+/*
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+/**
+ * Which version of the Taler merchant protocol is implemented
+ * by this implementation? Used to determine compatibility.
+ */
+define( 'GNU_TALER_MERCHANT_PROTOCOL_CURRENT', 21 );
+
+/**
+ * How many merchant protocol versions are we backwards compatible with?
+ */
+define( 'GNU_TALER_MERCHANT_PROTOCOL_AGE', 5 );
+
+
+
+ /**
+ * GNU Taler Payment Gateway class.
+ *
+ * Handles the payments from the Woocommerce Webshop and sends the transactions to the GNU Taler Backend and the GNU Taler Wallet.
+ */
+ class WC_Gateway_Gnutaler extends WC_Payment_Gateway {
+
+ /**
+ * Cached handle to logging class.
+ *
+ * @var Plugin loggger.
+ */
+ private static $log = false;
+
+ /**
+ * True if logging is enabled in our configuration.
+ *
+ * @var Is logging enabled?
+ */
+ private static $log_enabled = false;
+
+ /**
+ * Base URL of the Taler merchant backend.
+ *
+ * @var string
+ */
+ private $gnu_taler_backend_url;
+
+ /**
+ * Unique id for the gateway.
+ *
+ * @var string
+ */
+ public $id = 'gnutaler';
+
+ /**
+ * FIXME: what does this do?
+ */
+ public $enable_for_virtual = true;
+
+ /**
+ * Instructions for the user.
+ */
+ protected $instructions;
+
+ /**
+ * Whether the gateway is visible for non-admin users.
+ *
+ * @var boolean
+ *
+ */
+ protected $hide_for_non_admin_users;
+
+ /**
+ * True if debugging is enabled.
+ */
+ public $debug;
+
+ /**
+ * Class constructor
+ */
+ public function __construct() {
+ $this->icon = plugins_url( '/assets/images/taler.png', __FILE__ );
+
+ // We cannot use custom fields to show the QR code / do the wallet integration as WC doesn't give us the order_id at that time. Bummer.
+ $this->has_fields = false;
+
+ // Setup logging.
+ $this->debug = 'yes' === $this->get_option( 'debug', 'no' );
+ self::$log_enabled = $this->debug;
+
+ // This gateway can support refunds, saved payment methods.
+ $this->supports = array(
+ 'products',
+ 'refunds',
+ );
+
+ $this->method_title = _x( 'GNU Taler', 'GNU Taler payment method', 'woocommerce-gateway-gnutaler' );
+ $this->method_description = __( 'This plugin enables payments via the GNU Taler payment system', 'woocommerce-gateway-gnutaler' );
+ $this->init_form_fields();
+ $this->init_settings();
+
+ $this->title = $this->get_option( 'title' );
+ $this->description = $this->get_option( 'description' );
+ $this->instructions = $this->get_option( 'instructions' );
+ $this->hide_for_non_admin_users = $this->get_option( 'hide_for_non_admin_users' );
+
+ $this->enabled = $this->get_option( 'enabled' );
+ $this->gnu_taler_backend_url = $this->get_option( 'gnu_taler_backend_url' );
+ // Remove trailing '/', we add one always ourselves...
+ if ( substr( $this->gnu_taler_backend_url, -1 ) === '/' ) {
+ $this->gnu_taler_backend_url = substr( $this->gnu_taler_backend_url, 0, -1 );
+ }
+
+ // Make transaction ID a link. We use the public version
+ // here, as a user clicking on the link could not supply
+ // the authorization header.
+ // See also: https://woocommerce.wordpress.com/2014/08/05/wc-2-2-payment-gateways-adding-refund-support-and-transaction-ids/.
+ $this->view_transaction_url = $this->gnu_taler_backend_url . '/orders/%s';
+
+ // Register handler for the fulfillment URL.
+ add_action(
+ 'woocommerce_api_' . strtolower( get_class( $this ) ),
+ array( &$this, 'fulfillment_url_handler' )
+ );
+
+ // This action hook saves the settings.
+ add_action(
+ 'woocommerce_update_options_payment_gateways_' . $this->id,
+ array( $this, 'process_admin_options' )
+ );
+
+ // Modify WC canonical refund e-mail notifications to add link to order status page.
+ // (according to https://www.businessbloomer.com/woocommerce-add-extra-content-order-email/).
+ add_action(
+ 'woocommerce_email_before_order_table',
+ array( $this, 'add_content_refund_email' ),
+ 20,
+ 4
+ );
+ }
+
+ public function is_available() {
+ if (!WC()->session || !WC()->cart) {
+ $this->debug('WC session or cart not available');
+ return false;
+ }
+ // FIXME: tons of debug logic in this function to be removed...
+ $needs_payment = WC()->cart ? WC()->cart->needs_payment() : false;
+ $cart_total = WC()->cart ? WC()->cart->get_total('edit') : 0;
+
+ $this->debug('Cart needs payment: ' . ($needs_payment ? 'yes' : 'no'));
+ $this->debug('Cart total: ' . $cart_total);
+
+ $res = ('yes' === $this->enabled);
+ // FIXME: deactivate payment method if currency does not match!
+ if ( $res && (! $this->verify_backend_url($this->gnu_taler_backend_url, get_woocommerce_currency()) ) ) {
+ $this->debug('Backend URL verification failed or currency mismatch between backend and shop!');
+ $res = false;
+ }
+ $this->debug( $res
+ ? __( 'Returning payment method is available', 'woocommerce-gateway-gnutaler' )
+ : __( 'Returning payment method is unavailable', 'woocommerce-gateway-gnutaler' )
+ );
+ $context = 'is_available() called from: ' . $caller . ' - Context: ';
+ if (is_admin()) {
+$context .= 'admin ';
+ }
+ if (is_checkout()) {
+$context .= 'checkout ';
+ }
+ if (WC()->cart && WC()->cart->is_empty()) {
+$context .= 'empty-cart ';
+ }
+ if (WC()->cart) {
+$context .= 'total-' . WC()->cart->get_total('edit') . ' ';
+ }
+ $this->debug ( $context . ' returning ' . $res);
+
+ return $res;
+ }
+
+
+ /**
+ * Initialise Gateway Settings Form Fields.
+ */
+ public function init_form_fields() {
+ $this->form_fields = array(
+ 'enabled' => array(
+ 'title' => __( 'Enable/Disable', 'woocommerce-gateway-gnutaler' ),
+ 'label' => __( 'Enable GNU Taler Gateway', 'woocommerce-gateway-gnutaler' ),
+ 'type' => 'checkbox',
+ 'description' => '',
+ 'default' => 'no',
+ ),
+ 'hide_for_non_admin_users' => array(
+ 'type' => 'checkbox',
+ 'label' => __( 'Hide at checkout for non-admin users', 'woocommerce-gateway-gnutaler' ),
+ 'default' => 'no',
+ ),
+ 'title' => array(
+ 'title' => __( 'Title', 'woocommerce-gateway-gnutaler' ),
+ 'type' => 'text',
+ 'description' => __( 'This is what the customer will see when choosing payment methods.', 'woocommerce-gateway-gnutaler' ),
+ 'default' => 'GNU Taler',
+ 'desc_tip' => true,
+ ),
+ 'description' => array(
+ 'title' => __( 'Description', 'woocommerce-gateway-gnutaler' ),
+ 'type' => 'textarea',
+ 'description' => __( 'Payment method description which the customer sees during checkout.', 'woocommerce-gateway-gnutaler' ),
+ 'default' => __( 'Pay digitally with GNU Taler', 'woocommerce-gateway-gnutaler' ),
+ 'desc_tip' => true,
+ ),
+ 'instructions' => array(
+ 'title' => __( 'Instructions', 'woocommerce-gateway-gnutaler' ),
+ 'type' => 'textarea',
+ 'description' => __( 'Instructions that will be added to the thank you page.', 'woocommerce-gateway-gnutaler' ),
+ 'default' => __( 'Thank you for paying with GNU Taler', 'woocommerce-gateway-gnutaler' ),
+ 'desc_tip' => true,
+ ),
+ 'gnu_taler_backend_url' => array(
+ 'title' => __( 'Taler backend URL', 'woocommerce-gateway-gnutaler' ),
+ 'type' => 'text',
+ 'description' => __( 'Set the URL of the Taler backend. (Example: https://backend.demo.taler.net/)', 'woocommerce-gateway-gnutaler' ),
+ 'default' => 'http://backend.demo.taler.net/instances/sandbox/',
+ ),
+ 'GNU_Taler_Backend_API_Key' => array(
+ 'title' => __( 'Taler Backend API Key', 'woocommerce-gateway-gnutaler' ),
+ 'type' => 'text',
+ 'description' => __( 'Enter your API key to authenticate with the Taler backend. Will be sent as a "Bearer" token using the HTTP "Authorization" header. Must be prefixed with "secret-token:" (RFC 8959). This is not your password but a token to be obtained via the /token endpoint from the REST service.', 'woocommerce-gateway-gnutaler' ),
+ 'default' => 'secret-token:sandbox',
+ ),
+ 'Order_text' => array(
+ 'title' => __( 'Summary Text of the Order', 'woocommerce-gateway-gnutaler' ),
+ 'type' => 'text',
+ 'description' => __( 'Set the text the customer will see when confirming payment. #%%s will be substituted with the order number. (Example: MyShop #%%s)', 'woocommerce-gateway-gnutaler' ),
+ 'default' => 'WooTalerShop #%s',
+ ),
+ 'GNU_Taler_refund_delay' => array(
+ 'title' => __( 'How long should refunds be possible', 'woocommerce-gateway-gnutaler' ),
+ 'type' => 'number',
+ 'description' => __( 'Set the number of days a customer has to request a refund', 'woocommerce-gateway-gnutaler' ),
+ 'default' => '14',
+ ),
+ 'debug' => array(
+ 'title' => __( 'Debug Log', 'woocommerce' ),
+ 'label' => __( 'Enable logging', 'woocommerce' ),
+ 'description' => sprintf(
+ /* translators: placeholder will be replaced with the path to the log file */
+ __( 'Log GNU Taler events inside %s.', 'woocommerce-gateway-gnutaler' ),
+ '<code>' . WC_Log_Handler_File::get_log_file_path( 'gnutaler' ) . '</code>'
+ ),
+ 'type' => 'checkbox',
+ 'default' => 'no',
+ ),
+ );
+ }
+
+ /**
+ * Called when WC sends out the e-mail notification for refunds.
+ * Adds a Taler-specific notice for where to click to obtain
+ * the refund.
+ *
+ * @param WC_Order $wc_order The order.
+ * @param bool $sent_to_admin Not well documented by WooCommerce.
+ * @param string $plain_text The plain text of the email.
+ * @param string $email Target email address.
+ */
+ public function add_content_refund_email( $wc_order, $sent_to_admin, $plain_text, $email ) {
+ if ( 'customer_refunded_order' === $email->id ) {
+ $backend_url = $this->gnu_taler_backend_url;
+ $wc_order_id = $wc_order->get_order_key() . '-' . $wc_order->get_order_number();
+ $refund_url = $wc_order->get_meta( 'GNU_TALER_REFUND_URL' );
+ printf(
+ /* translators: placeholder will be replaced with the refund URL */
+ esc_html( __( 'Refund granted. Visit <a href="%1$s">%1$s</a> to obtain the refund.', 'woocommerce-gateway-gnutaler' ) ),
+ esc_url( $refund_url )
+ );
+ }
+ }
+
+ /**
+ * Processes and saves options.
+ * If there is an error thrown, will continue to save and validate fields, but
+ * will leave the erroring field out.
+ *
+ * @return bool was anything saved?
+ */
+ public function process_admin_options() {
+ $saved = parent::process_admin_options();
+
+ // Maybe clear logs.
+ if ( 'yes' !== $this->get_option( 'debug', 'no' ) ) {
+ if ( empty( self::$log ) ) {
+ self::$log = wc_get_logger();
+ }
+ self::$log->clear( 'gnutaler' );
+ }
+
+ return $saved;
+ }
+
+ /**
+ * Required function to add the fields we want to show in the
+ * payment method selection dialog. We show none.
+ */
+ public function payment_fields() {
+ $this->debug( __( 'payment_fields() called for gnutaler', 'woocommerce-gateway-gnutaler' ) );
+ // We do not have any.
+ }
+
+ /**
+ * Callback is called, when the user goes to the fulfillment URL.
+ *
+ * We check that the payment actually was made, and update WC accordingly.
+ * If the order ID is unknown and/or the payment did not succeed, we
+ * redirect to the home page and/or the user's order page (for logged in users).
+ */
+ public function fulfillment_url_handler(): void {
+ global $woocommerce;
+
+ // We intentionally do NOT verify the nonce here, as this page
+ // should work even if the deep link is shared with other users
+ // or even non-users.
+ // phpcs:disable WordPress.Security.NonceVerification
+ if ( ! isset( $_GET['order_id'] ) ) {
+ $this->debug( __( "Lacking 'order_id', forwarding user to neutral page", 'woocommerce-gateway-gnutaler' ) );
+ if ( is_user_logged_in() ) {
+ wp_safe_redirect( get_home_url() . wc_get_page_permalink( 'myaccount' ) );
+ } else {
+ wp_safe_redirect( get_home_url() . wc_get_page_permalink( 'shop' ) );
+ }
+ exit;
+ }
+
+ // Gets the order id from the fulfillment url.
+ $taler_order_id = sanitize_text_field( wp_unslash( $_GET['order_id'] ) );
+ // phpcs:enable
+ $order_id_array = explode( '-', $taler_order_id );
+ $order_id_name = $order_id_array[0];
+ $order_id = $order_id_array[1];
+ $wc_order = wc_get_order( $order_id );
+ $backend_url = $this->gnu_taler_backend_url;
+
+ $payment_confirmation = $this->call_api(
+ 'GET',
+ $backend_url . '/private/orders/' . $taler_order_id,
+ false
+ );
+ $payment_body = $payment_confirmation['message'];
+ $payment_http_status = $payment_confirmation['http_code'];
+
+ switch ( $payment_http_status ) {
+ case 200:
+ // Here we check what kind of http code came back from the backend.
+ $merchant_order_status_response = json_decode(
+ $payment_body,
+ $assoc = true
+ );
+ if ( ! $merchant_order_status_response ) {
+ wc_add_notice(
+ __( 'Payment error:', 'woocommerce-gateway-gnutaler' ) .
+ __( 'backend did not respond', 'woocommerce-gateway-gnutaler' )
+ );
+ $this->notice( __( 'Payment failed: no reply from Taler backend', 'woocommerce-gateway-gnutaler' ) );
+ wp_safe_redirect( $this->get_return_url( $order_id ) );
+ exit;
+ }
+ if ( 'paid' === $merchant_order_status_response['order_status'] ) {
+ $this->notice( __( 'Payment succeeded and the user was forwarded to the order confirmed page', 'woocommerce-gateway-gnutaler' ) );
+ // Completes the order, storing a transaction ID.
+ $wc_order->payment_complete( $taler_order_id );
+ // Empties the shopping cart.
+ WC()->cart->empty_cart();
+ } else {
+ wc_add_notice(
+ __( 'Payment error:', 'woocommerce-gateway-gnutaler' ) .
+ __( 'backend did not confirm payment', 'woocommerce-gateway-gnutaler' )
+ );
+ $this->notice( __( 'Backend did not confirm payment', 'woocommerce-gateway-gnutaler' ) );
+ }
+ wp_safe_redirect( $this->get_return_url( $wc_order ) );
+ exit;
+ default:
+ $this->error(
+ __( 'An error occurred during the second request to the GNU Taler backend: ', 'woocommerce-gateway-gnutaler' )
+ . $payment_http_status . ' - ' . $payment_body
+ );
+ wc_add_notice( __( 'Payment error:', 'woocommerce-gateway-gnutaler' ) . $payment_http_status . ' - ' . $payment_body );
+ wp_safe_redirect( $this->get_return_url( $order_id ) );
+ break;
+ }
+ $cart_url = $woocommerce->cart->wc_get_cart_url();
+ if ( is_set( $cart_url ) ) {
+ wp_safe_redirect( get_home_url() . $cart_url );
+ } else {
+ wp_safe_redirect( wc_get_page_permalink( 'shop' ) );
+ }
+ exit;
+ }
+
+ /**
+ * Sends a request to a url via HTTP.
+ *
+ * Sends a request to a GNU Taler Backend over HTTP and returns the result.
+ * The request can be sent as POST or GET. PATCH is not supported.
+ *
+ * @param string $method POST or GET supported only. Thanks WordPress.
+ * @param string $url URL for the request to make to the GNU Taler Backend.
+ * @param string $body The content of the request (for POST).
+ *
+ * @return array The return array will either have the successful return value or a detailed error message.
+ */
+ private function call_api( $method, $url, $body ): array {
+ $apikey = $this->get_option( 'GNU_Taler_Backend_API_Key' );
+ $args = array(
+ 'timeout' => 30, // In seconds.
+ 'redirection' => 2, // How often.
+ 'httpversion' => '1.1', // Taler will support.
+ 'user-agent' => '', // Minimize information leakage.
+ 'blocking' => true, // We do nothing without it.
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $apikey,
+ ),
+ 'decompress' => true,
+ 'limit_response_size' => 1024 * 1024, // More than enough.
+ );
+ if ( $body ) {
+ $args['body'] = wp_json_encode( $body, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES, 16 );
+ $args['headers']['Content-type'] = 'application/json';
+ $args['compress'] = true;
+ }
+ $this->debug( 'Issuing HTTP ' . $method . ' request to ' . $url . ' with options ' . wp_json_encode( $args ) . ' and body ' . $body );
+
+ switch ( $method ) {
+ case 'POST':
+ $response = wp_remote_post( $url, $args );
+ break;
+ case 'GET':
+ $response = wp_remote_get( $url, $args );
+ break;
+ default:
+ $this->debug( 'HTTP method ' . $method . ' not supported' );
+ return null;
+ }
+ if ( is_wp_error( $response ) ) {
+ $error_code = $response->get_error_code();
+ $error_data = $response->get_error_data( $error_code );
+ $this->warning(
+ sprintf(
+ /* translators: first placeholder is the error code, second the error data */
+ __( 'HTTP failure %1$s with data %2$s', 'woocommerce-gateway-gnutaler' ),
+ $error_code,
+ $error_data
+ )
+ );
+
+ return array(
+ 'http_code' => 0,
+ 'message' => $error_code,
+ );
+ }
+ $http_code = wp_remote_retrieve_response_code( $response );
+ $body = wp_remote_retrieve_body( $response );
+ $this->debug(
+ sprintf(
+ /* translators: first placeholder is the HTTP status code, second the body of the HTTP reply */
+ __( 'HTTP status %1$s with response body %2$s', 'woocommerce-gateway-gnutaler' ),
+ $http_code,
+ $body
+ )
+ );
+ return array(
+ 'http_code' => $http_code,
+ 'message' => $body,
+ );
+ }
+
+ /**
+ * Checks if the configuration is valid. Used by WC to redirect
+ * the admin to the setup dialog if they enable the backend and
+ * it is not properly configured.
+ */
+ public function needs_setup() {
+ $backend_url = $this->gnu_taler_backend_url;
+ $verify_result = $this->verify_backend_url(
+ $backend_url,
+ null
+ );
+
+ $this->debug( $verify_result
+ ? __( 'Backend is ready', 'woocommerce-gateway-gnutaler' )
+ : __( 'Backend is not setup correctly', 'woocommerce-gateway-gnutaler' ) );
+
+ return ! $verify_result;
+ }
+
+ /**
+ * Verifying if the url to the backend given in the plugin options is valid or not.
+ *
+ * @param string $url URL to the backend.
+ * @param string $ecurrency expected currency of the order.
+ *
+ * @return bool - Returns if valid or not.
+ */
+ private function verify_backend_url( $url, $ecurrency ): bool {
+ $this->info( 'Verifying backend URL ' . $url );
+
+ $config = $this->call_api( 'GET', $url . '/config', false );
+ $config_http_status = $config['http_code'];
+ $config_body = $config['message'];
+ switch ( $config_http_status ) {
+ case 200:
+ $info = json_decode( $config_body, $assoc = true );
+ if ( ! $info ) {
+ $this->error(
+ sprintf(
+ /* translators: placeholder will be replaced with the URL of the backend */
+ __( '/config response of backend at %s did not decode as JSON', 'woocommerce-gateway-gnutaler' ),
+ $url
+ )
+ );
+ return false;
+ }
+ $version = $info['version'];
+ if ( ! $version ) {
+ $this->error(
+ sprintf(
+ /* translators: placeholder will be replaced with the URL of the backend */
+ __( "No 'version' field in /config reply from Taler backend at %s", 'woocommerce-gateway-gnutaler' ),
+ $url
+ )
+ );
+ return false;
+ }
+ $ver = explode( ':', $version, 3 );
+ $current = $ver[0];
+ $revision = $ver[1];
+ $age = $ver[2];
+ if ( ( ! is_numeric( $current ) )
+ || ( ! is_numeric( $revision ) )
+ || ( ! is_numeric( $age )
+ || ( $age > $current ) )
+ ) {
+ $this->error(
+ sprintf(
+ /* translators: placeholder will be replaced with the (malformed) version number */
+ __( "/config response at backend malformed: '%s' is not a valid version", 'woocommerce-gateway-gnutaler' ),
+ $version
+ )
+ );
+ return false;
+ }
+ if ( GNU_TALER_MERCHANT_PROTOCOL_CURRENT < $current - $age ) {
+ // Our implementation is too old!
+ $this->error(
+ sprintf(
+ /* translators: placeholder will be replaced with the version number */
+ __( 'Backend protocol version %s is too new: please update the GNU Taler plugin', 'woocommerce-gateway-gnutaler' ),
+ $version
+ )
+ );
+ return false;
+ }
+ if ( GNU_TALER_MERCHANT_PROTOCOL_CURRENT - GNU_TALER_MERCHANT_PROTOCOL_AGE > $current ) {
+ // Merchant implementation is too old!
+ $this->error(
+ sprintf(
+ /* translators: placeholder will be replaced with the version number */
+ __( 'Backend protocol version %s unsupported: please update the backend', 'woocommerce-gateway-gnutaler' ),
+ $version
+ )
+ );
+ return false;
+ }
+ $currency = $info['currency'];
+ if ( ( ! is_null( $ecurrency ) ) &&
+ ( 0 !== strcasecmp( $currency, $ecurrency ) ) ) {
+ $this->error(
+ sprintf(
+ /* translators: first placeholder is the Taler backend currency, second the expected currency from WooCommerce */
+ __( 'Backend currency %1$s does not match order currency %2$s', 'woocommerce-gateway-gnutaler' ),
+ $currency,
+ $ecurrency
+ )
+ );
+ return false;
+ }
+ $this->debug(
+ sprintf(
+ /* translators: placeholder will be replaced with the URL of the backend */
+ __( '/config check for Taler backend at %s succeeded', 'woocommerce-gateway-gnutaler' ),
+ $url
+ )
+ );
+ return true;
+ default:
+ $this->error(
+ sprintf(
+ /* translators: placeholder will be replaced with the HTTP status code returned by the backend */
+ __( 'Backend failed /config request with unexpected HTTP status %s', 'woocommerce-gateway-gnutaler' ),
+ $config_http_status
+ )
+ );
+ return false;
+ }
+ }
+
+ /**
+ * Processes the payment after the checkout
+ *
+ * If the payment process finished successfully the user is being redirected to its GNU Taler Wallet.
+ * If an error occurs it returns void and throws an error.
+ *
+ * @param string $order_id ID of the order to get the Order from the WooCommerce Webshop.
+ *
+ * @return array|void - Array with result => success and redirection url otherwise it returns void.
+ */
+ public function process_payment( $order_id ) {
+ // We need the order ID to get any order detailes.
+ $wc_order = wc_get_order( $order_id );
+
+ // Gets the url of the backend from the WooCommerce Settings.
+ $backend_url = $this->gnu_taler_backend_url;
+
+ // Log entry that the customer started the payment process.
+ $this->info( __( 'User started the payment process with GNU Taler.', 'woocommerce-gateway-gnutaler' ) );
+
+ if ( ! $this->verify_backend_url( $backend_url, $wc_order->get_currency() ) ) {
+ wc_add_notice( __( 'Something went wrong please contact the system administrator of the webshop and send the following error: GNU Taler backend URL invalid', 'woocommerce-gateway-gnutaler' ), 'error' );
+ $this->error( __( 'Checkout process failed: Invalid backend url.', 'woocommerce-gateway-gnutaler' ) );
+ return;
+ }
+ $order_json = $this->convert_to_checkout_json( $order_id );
+
+ $this->info( __( 'Sending POST /private/orders request send to Taler backend', 'woocommerce-gateway-gnutaler' ) );
+ $order_confirmation = $this->call_api(
+ 'POST',
+ $backend_url . '/private/orders',
+ $order_json
+ );
+ $order_body = $order_confirmation['message'];
+ $order_http_status = $order_confirmation['http_code'];
+ switch ( $order_http_status ) {
+ case 200:
+ $post_order_response = json_decode( $order_body, $assoc = true );
+ if ( ! $post_order_response ) {
+ $this->error(
+ __( 'POST /private/orders request to Taler backend returned 200 OK, but not a JSON body: ', 'woocommerce-gateway-gnutaler' )
+ . $order_body
+ );
+ wc_add_notice( __( 'Malformed response from Taler backend. Please contact the system administrator.' ) );
+ $wc_order->set_status( 'cancelled' );
+ return;
+ }
+ $taler_order_id = $post_order_response ['order_id'];
+ $taler_order_token = $post_order_response ['token'];
+ if ( ! $taler_order_id ) {
+ $this->error(
+ __( 'Response to POST /private/orders request to Taler backend lacks order_id field: ', 'woocommerce-gateway-gnutaler' )
+ . $order_body
+ );
+ wc_add_notice( __( 'Malformed response from Taler backend. Please contact the system administrator.', 'woocommerce-gateway-gnutaler' ) );
+ $wc_order->set_status( 'cancelled' );
+ return;
+ }
+ $this->info( __( 'POST /private/orders successful. Redirecting user to Taler Backend.', 'woocommerce-gateway-gnutaler' ) );
+ return array(
+ 'result' => 'success',
+ 'redirect' => $backend_url . '/orders/' . $taler_order_id . '?token=' . $taler_order_token,
+ );
+ case 404:
+ $post_order_error = json_decode( $order_body, $assoc = true );
+ if ( ! $post_order_error ) {
+ $this->error(
+ __( 'POST /private/orders request to Taler backend returned 404, but not a JSON body: ', 'woocommerce-gateway-gnutaler' )
+ . $order_body
+ );
+ wc_add_notice( __( 'Malformed response from Taler backend. Please contact the system administrator.', 'woocommerce-gateway-gnutaler' ) );
+ $wc_order->set_status( 'cancelled' );
+ return;
+ }
+ $this->error(
+ __( 'POST /private/orders request to Taler backend failed: ', 'woocommerce-gateway-gnutaler' )
+ . $post_order_error['code'] . '('
+ . $order_http_status . '): ' . $order_body
+ );
+ wc_add_notice( __( 'Taler backend not configured correctly. Please contact the system administrator.', 'woocommerce-gateway-gnutaler' ) );
+ $wc_order->set_status( 'cancelled' );
+ return;
+ case 410:
+ // We don't use inventory management via GNU Taler's backend, so this error should never apply.
+ // Handle with 'default' case below.
+ default:
+ $this->error(
+ __( 'POST /private/orders request to Taler backend failed: ', 'woocommerce-gateway-gnutaler' )
+ . $post_order_error['code'] . '('
+ . $order_http_status . '): '
+ . $order_body
+ );
+ wc_add_notice( __( 'Unexpected problem with the Taler backend. Please contact the system administrator.', 'woocommerce-gateway-gnutaler' ) );
+ $wc_order->set_status( 'cancelled' );
+ return;
+ }
+ }
+
+ /**
+ * Converts the order into a JSON format that can be send to the GNU Taler Backend.
+ *
+ * @param string $order_id ID of the order to get the Order from the WooCommerce Webshop.
+ *
+ * @return array - return the JSON Format.
+ */
+ public function convert_to_checkout_json( $order_id ): array {
+ $wc_order = wc_get_order( $order_id );
+ $wc_order_total_amount = $wc_order->get_total();
+ $wc_order_currency = $wc_order->get_currency();
+ $wc_cart = WC()->cart->get_cart();
+ $wc_order_id = $wc_order->get_order_key() . '-' . $wc_order->get_order_number();
+ $wc_order_products_array = $this->mutate_products_to_json_format( $wc_cart, $wc_order_currency );
+ $refund_delay = $this->get_option( 'GNU_Taler_refund_delay' );
+ $order_json = array(
+ 'order' => array(
+ 'amount' => $wc_order_currency . ':' . $wc_order_total_amount,
+ 'summary' => sprintf(
+ $this->get_option( 'Order_text' ),
+ $wc_order->get_order_number()
+ ),
+ // NOTE: This interacts with the 'add_action' call
+ // to invoke the 'fulfillment_url_handler' when the
+ // user goes to this URL!
+ 'fulfillment_url' => get_home_url()
+ . '/?wc-api='
+ . strtolower( get_class( $this ) )
+ . '&order_id='
+ . $wc_order_id,
+ 'order_id' => $wc_order_id,
+ 'products' => $wc_order_products_array,
+ 'delivery_location' => $this->mutate_shipping_information_to_json_format( $wc_order ),
+ ),
+ );
+ if ( isset( $refund_delay ) ) {
+ $order_json['refund_delay'] = array(
+ 'd_us' => 1000 * 1000 * 60 * 60 * 24 * intval( $refund_delay ),
+ );
+ }
+ return $order_json;
+ }
+
+ /**
+ * Mutates the products in the cart into a format which can be included in a JSON file.
+ *
+ * @param WC_Cart $wc_cart The content of the WooCommerce Cart.
+ * @param string $wc_order_currency The currency the WooCommerce Webshop uses.
+ *
+ * @return array - Returns an array of products.
+ */
+ private function mutate_products_to_json_format( $wc_cart, $wc_order_currency ): array {
+ $wc_order_products_array = array();
+ foreach ( $wc_cart as $product ) {
+ $wc_order_products_array[] = array(
+ 'description' => $product['data']->get_title(),
+ 'quantity' => $product['quantity'],
+ 'price' => $wc_order_currency . ':' . $product['data']->get_price(),
+ 'product_id' => strval( $product['data']->get_id() ),
+ );
+ }
+ return $wc_order_products_array;
+ }
+
+ /**
+ * Processes the refund transaction if requested by the system administrator of the webshop
+ *
+ * If the refund request is finished successfully it returns an refund url, which can be send to the customer to finish the refund transaction.
+ * If an error it will throw a WP_Error message and inform the system administrator.
+ *
+ * @param WC_Order $wc_order The WooCommerce order object we are processing.
+ *
+ * @return array
+ */
+ private function mutate_shipping_information_to_json_format( $wc_order ): array {
+ $whitechar_encounter = false;
+ $shipping_address_street = '';
+ $shipping_address_street_nr = '';
+
+ $store_address = $wc_order->get_shipping_address_1( );
+ if ( is_null( $store_address ) || empty( $store_address ) ) {
+ $store_address = $wc_order->get_billing_address_1( );
+ }
+ $store_address_inverted = strrev( $store_address );
+ $store_address_array = str_split( $store_address_inverted );
+ $country = $wc_order->get_shipping_country ();
+ if ( is_null( $country ) || empty( $country ) ) {
+ $country = $wc_order->get_billing_country ();
+ }
+ $state = $wc_order->get_shipping_state ();
+ if ( is_null( $state ) || empty( $state ) ) {
+ $state = $wc_order->get_billing_state ();
+ }
+ $city = $wc_order->get_shipping_city ();
+ if ( is_null( $city ) || empty( $city ) ) {
+ $city = $wc_order->get_billing_city ();
+ }
+ $postcode = $wc_order->get_shipping_postcode ();
+ if ( is_null( $postcode ) || empty( $postcode ) ) {
+ $postcode = $wc_order->get_billing_postcode ();
+ }
+
+ $this->info (
+ sprintf(
+ 'Shipping address is %s - %s - %s - %s - %s',
+ $store_address,
+ $wc_order->get_shipping_country (),
+ $wc_order->get_shipping_state (),
+ $wc_order->get_shipping_city (),
+ $wc_order->get_shipping_postcode ()) );
+
+ $this->info (
+ sprintf(
+ 'Billing address is %s - %s - %s - %s - %s',
+ $store_address,
+ $wc_order->get_billing_country (),
+ $wc_order->get_billing_state (),
+ $wc_order->get_billing_city (),
+ $wc_order->get_billing_postcode ()) );
+ $this->info (
+ sprintf(
+ 'Using address is %s - %s - %s - %s - %s',
+ $store_address,
+ $country,
+ $state,
+ $city,
+ $postcode));
+
+ // Split the address into street and street number.
+ foreach ( $store_address_array as $char ) {
+ if ( ! $whitechar_encounter ) {
+ $shipping_address_street .= $char;
+ } elseif ( ctype_space( $char ) ) {
+ $whitechar_encounter = true;
+ } else {
+ $shipping_address_street .= $char;
+ }
+ }
+ $ret = array(
+ 'country' => $country,
+ 'country_subdivision' => $state,
+ 'town' => $city,
+ 'post_code' => $postcode,
+ 'street' => $shipping_address_street,
+ 'building_number' => $shipping_address_street_nr,
+ );
+ if ( null !== $wc_order->get_shipping_address_2() ) {
+ $address_lines = array(
+ $wc_order->get_shipping_address_1(),
+ $wc_order->get_shipping_address_2(),
+ );
+ $ret['address_lines'] = $address_lines;
+ }
+ return $ret;
+ }
+
+ /**
+ * Processes the refund transaction if requested by the system administrator of the webshop
+ *
+ * If the refund request is finished successfully it returns an refund url, which can be send to the customer to finish the refund transaction.
+ * If an error it will throw a WP_Error message and inform the system administrator.
+ *
+ * @param string $order_id Order id for logging.
+ * @param string $amount Amount that is requested to be refunded.
+ * @param string $reason Reason for the refund request.
+ *
+ * @return bool|WP_Error - Returns true or throws an WP_Error message in case of error.
+ */
+ public function process_refund( $order_id, $amount = null, $reason = '' ) {
+ $wc_order = wc_get_order( $order_id );
+
+ $this->info(
+ sprintf(
+ /* translators: first placeholder is the numeric amount, second the currency, and third the reason for the refund */
+ __( 'Refund process started with the refunded amount %1$s %2$s and the reason %3$s.' ),
+ $amount,
+ $wc_order->get_currency(),
+ $reason
+ )
+ );
+
+ // Gets the url of the backend from the WooCommerce Settings.
+ $backend_url = $this->gnu_taler_backend_url;
+
+ // Get the current status of the order.
+ $wc_order_status = $wc_order->get_status();
+
+ // Checks if current status is already set as paid.
+ if ( ! ( 'processing' === $wc_order_status
+ || 'on hold' === $wc_order_status
+ || 'completed' === $wc_order_status )
+ ) {
+ $this->error( __( 'The status of the order does not allow a refund', 'woocommerce-gateway-gnutaler' ) );
+ return new WP_Error( 'error', __( 'The status of the order does not allow for a refund.', 'woocommerce-gateway-gnutaler' ) );
+ }
+
+ $refund_request = array(
+ 'refund' => $wc_order->get_currency() . ':' . $amount,
+ 'reason' => $reason,
+ );
+ $wc_order_id = $wc_order->get_order_key() . '-' . $wc_order->get_order_number();
+ $refund_result = $this->call_api(
+ 'POST',
+ $backend_url . '/private/orders/' . $wc_order_id . '/refund',
+ $refund_request
+ );
+
+ $refund_http_status = $refund_result['http_code'];
+ $refund_body = $refund_result['message'];
+ switch ( $refund_http_status ) {
+ case 200:
+ $refund_response = json_decode( $refund_body, $assoc = true );
+ if ( ! $refund_response ) {
+ $this->error( __( 'Malformed 200 response from Taler backend: not even in JSON', 'woocommerce-gateway-gnutaler' ) );
+ return new WP_Error( 'error', __( 'Malformed response from Taler backend', 'woocommerce-gateway-gnutaler' ) );
+ }
+ $refund_uri = $refund_response['taler_refund_uri'];
+ $h_contract = $refund_response['h_contract'];
+ if ( ( ! $refund_uri ) || ( ! $h_contract ) ) {
+ $this->error( __( 'Malformed 200 response from Taler backend: lacks taler_refund_uri', 'woocommerce-gateway-gnutaler' ) );
+ return new WP_Error( 'error', __( 'Malformed response from Taler backend', 'woocommerce-gateway-gnutaler' ) );
+ }
+ $refund_url = $backend_url
+ . '/orders/'
+ . $wc_order_id
+ . '?h_contract='
+ . $h_contract;
+ $wc_order->add_meta_data( 'GNU_TALER_REFUND_URL', $refund_url );
+ $wc_order->update_status( 'refunded' );
+ $this->debug(
+ sprintf(
+ /* translators: argument is the Taler refund URI */
+
+ __( 'Received refund URI %s from Taler backend', 'woocommerce-gateway-gnutaler' ),
+ $refund_uri
+ )
+ );
+ $this->notice(
+ sprintf(
+ /* translators: argument is the Taler refund URL */
+ __( 'The user must visit %s to obtain the refund', 'woocommerce-gateway-gnutaler' ),
+ $refund_url
+ )
+ );
+ return true;
+ case 403:
+ return new WP_Error(
+ 'error',
+ __( 'Refunds are disabled for this order. Check the refund_delay option for the Taler payment plugin.', 'woocommerce-gateway-gnutaler' )
+ );
+ case 404:
+ $refund_error = json_decode( $refund_body, $assoc = true );
+ if ( ! $refund_error ) {
+ return new WP_Error(
+ 'error',
+ sprintf(
+ /* translators: argument is the HTTP status returned by the backend */
+ __( 'Unexpected failure %s without Taler error code from Taler backend', 'woocommerce-gateway-gnutaler' ),
+ $refund_http_status
+ )
+ );
+ }
+ $ec = $refund_error['code'];
+ switch ( $ec ) {
+ case 2000: // TALER_EC_INSTANCE_UNKNOWN!
+ return new WP_Error(
+ 'error',
+ __( 'Instance unknown reported by Taler backend', 'woocommerce-gateway-gnutaler' )
+ );
+ case 2601: // TALER_EC_REFUND_ORDER_ID_UNKNOWN!
+ return new WP_Error(
+ 'error',
+ __( 'Order unknown reported by Taler backend', 'woocommerce-gateway-gnutaler' )
+ );
+ default:
+ return new WP_Error(
+ 'error',
+ sprintf(
+ /* translators: placeholder will be replaced with the numeric GNU Taler error code */
+ __( 'Unexpected error %s reported by Taler backend', 'woocommerce-gateway-gnutaler' ),
+ $ec
+ )
+ );
+ }
+ // This line is unreachable.
+ case 409:
+ return new WP_Error(
+ 'error',
+ __( 'Requested refund amount exceeds original payment. This is not allowed!', 'woocommerce-gateway-gnutaler' )
+ );
+ case 410:
+ return new WP_Error(
+ 'error',
+ __( 'Wire transfer already happened. It is too late for a refund with Taler!', 'woocommerce-gateway-gnutaler' )
+ );
+ default:
+ $refund_error = json_decode( $refund_body, $assoc = true );
+ if ( ! $refund_error ) {
+ $ec = $refund_error['code'];
+ } else {
+ $ec = 0;
+ }
+ return new WP_Error(
+ 'error',
+ sprintf(
+ /* translators: first placeholder is the HTTP status code, second the numeric GNU Taler error code */
+ __( 'Unexpected failure %1$s/%2$s from Taler backend', 'woocommerce-gateway-gnutaler' ),
+ $refund_http_status,
+ $ec
+ )
+ );
+ }
+ }
+
+ /**
+ * Log $msg for debugging
+ *
+ * @param string $msg message to log.
+ */
+ private function debug( $msg ): void {
+ $this->log( 'debug', $msg );
+ }
+
+ /**
+ * Log $msg as a informational
+ *
+ * @param string $msg message to log.
+ */
+ private function info( $msg ): void {
+ $this->log( 'info', $msg );
+ }
+
+ /**
+ * Log $msg as a notice
+ *
+ * @param string $msg message to log.
+ */
+ private function notice( $msg ): void {
+ $this->log( 'notice', $msg );
+ }
+
+ /**
+ * Log $msg as a warning.
+ *
+ * @param string $msg message to log.
+ */
+ private function warning( $msg ): void {
+ $this->log( 'warning', $msg );
+ }
+
+ /**
+ * Log $msg as an error
+ *
+ * @param string $msg message to log.
+ */
+ private function error( $msg ): void {
+ $this->log( 'error', $msg );
+ }
+
+ /**
+ * Log $msg at log $level.
+ *
+ * @param string $level log level to use when logging.
+ * @param string $msg message to log.
+ */
+ private function log( $level, $msg ) {
+ if ( ! self::$log_enabled ) {
+ return;
+ }
+ if ( function_exists( 'wp_get_current_user()' ) ) {
+ $user_id = wp_get_current_user();
+ if ( ! isset( $user_id ) ) {
+ $user_id = __( '<user ID not set>', 'woocommerce-gateway-gnutaler' );
+ }
+ } else {
+ $user_id = 'Guest';
+ }
+ // We intentionally do NOT verify the nonce here, as logging
+ // should always work.
+ // phpcs:disable WordPress.Security.NonceVerification
+ if ( isset ($_GET['order_id'] ) ) {
+ $order_id = sanitize_text_field( wp_unslash( $_GET['order_id'] ) );
+ } else {
+ $order_id = 'NONE';
+ }
+ // phpcs:enable
+ if ( empty( self::$log ) ) {
+ self::$log = wc_get_logger();
+ }
+ self::$log->log( $level, $user_id . '-' . $order_id . ': ' . $msg, array( 'source' => 'gnutaler' ) );
+ }
+ }
diff --git a/package.json b/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "woocommerce-gateway-gnutaler",
+ "title": "WooCommerce GNU Taler Payments",
+ "version": "1.1.0",
+ "author": "Taler Systems SA",
+ "license": "GPL-3.0+",
+ "keywords": [],
+ "engines": {
+ "node": "^20.12.0",
+ "npm": "^10.5.0"
+ },
+ "devDependencies": {
+ "@woocommerce/dependency-extraction-webpack-plugin": "2.2.0",
+ "@wordpress/scripts": "^27.8.0",
+ "cross-env": "7.0.3"
+ },
+ "scripts": {
+ "start": "wp-scripts start",
+ "build": "wp-scripts build && npm run i18n:build",
+ "i18n": "npm run i18n:build",
+ "i18n:build": "npm run i18n:pot && ./bin/build_i18n.sh",
+ "i18n:pot": "php -d xdebug.max_nesting_level=512 $(which wp) i18n make-pot --exclude=\"node_modules/,languages/,assets/\" --headers='{\"Report-Msgid-Bugs-To\":\"https://bugs.taler.net/\", \"language-team\":\"LANGUAGE <translations@taler.net>\"}' . languages/woocommerce-gateway-gnutaler.pot",
+ "i18n:json": "$(which wp) i18n make-json languages --no-purge",
+ "packages-update": "wp-scripts packages-update",
+ "check-engines": "wp-scripts check-engines"
+ }
+}
diff --git a/phpcs.xml b/phpcs.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<ruleset name="SomewhereWarm-cs">
+ <description>SomewhereWarm Coding Standards</description>
+
+ <!-- Exclude paths -->
+ <exclude-pattern>tests/</exclude-pattern>
+ <exclude-pattern>*/node_modules/*</exclude-pattern>
+ <exclude-pattern>*/assets/*</exclude-pattern>
+ <exclude-pattern>*/vendor/*</exclude-pattern>
+
+ <!-- Configs -->
+ <config name="minimum_supported_wp_version" value="6.0" />
+ <config name="testVersion" value="7.1-" />
+
+ <!-- Rules -->
+ <rule ref="WooCommerce-Core">
+ <exclude name="Core.Commenting.CommentTags.AuthorTag" />
+ <exclude name="WordPress.PHP.DontExtract" />
+ </rule>
+
+ <rule ref="WordPress-Extra">
+ <exclude name="Generic.Commenting.DocComment.SpacingAfter" />
+ <exclude name="Generic.Files.LineEndings.InvalidEOLChar" />
+ <exclude name="Generic.Functions.FunctionCallArgumentSpacing.SpaceBeforeComma" />
+ <exclude name="Generic.WhiteSpace" />
+ <exclude name="PEAR.Functions.FunctionCallSignature" />
+ <exclude name="Squiz.Commenting" />
+ <exclude name="Squiz.PHP.DisallowSizeFunctionsInLoops.Found" />
+ <exclude name="Squiz.WhiteSpace" />
+ <exclude name="WordPress.Arrays" />
+ <exclude name="WordPress.Files.FileName" />
+ <exclude name="WordPress.NamingConventions" />
+ <exclude name="WordPress.Security.ValidatedSanitizedInput.MissingUnslash" />
+ <exclude name="WordPress.WP.I18n.NonSingularStringLiteralText" />
+ <exclude name="WordPress.WhiteSpace" />
+ <exclude name="WordPress.Security.EscapeOutput" />
+ <exclude name="Squiz.PHP.EmbeddedPhp" />
+ </rule>
+
+ <rule ref="PHPCompatibility">
+ <exclude-pattern>tests/</exclude-pattern>
+ </rule>
+
+ <rule ref="WordPress.Security.EscapeOutput">
+ <properties>
+ <!-- e.g. body_class, the_content, the_excerpt -->
+ <property name="customAutoEscapedFunctions" type="array" value="0=>woocommerce_wp_select,1=>wcs_help_tip,2=>admin_url,3=>wc_price"/>
+ <!-- e.g. esc_attr, esc_html, esc_url-->
+ <property name="customEscapingFunctions" type="array" value="0=>wcs_json_encode,1=>htmlspecialchars,2=>wp_kses_allow_underscores"/>
+ <!-- e.g. _deprecated_argument, printf, _e-->
+ <property name="customPrintingFunctions" type="array" value=""/>
+ </properties>
+ </rule>
+
+</ruleset>
diff --git a/readme.txt b/readme.txt
@@ -26,7 +26,7 @@ For refunds, the plugin sends a refund request to the GNU Taler back-end and rec
== Installation ==
-1. Ensure you have latest version of WooCommerce plugin installed
+1. Ensure you have latest version of WooCommerce plugin installed and are running PHP 7.1 or later
2. Upload (or copy) the plugin directory (`GNU-Taler-Payment-Gateway`) to the `/wp-content/plugins/` directory of your WordPress/WooCommerce site, or install the plugin through the WordPress plugins interface at `<your-site>/wp-admin/plugins.php`.
3. Activate the GNU Taler Payment Gateway for WooCommerce plugin through the 'Plugins' screen in WordPress.
4. In the WordPress interface, navigate to WooCommerce -> Settings-> Payments tab. Locate the `GNU Taler Gateway` entry and click the `Set up` button to configure the plugin.
@@ -43,11 +43,17 @@ A: Yes, the customer needs the GNU Taler Wallet. The customer can get a wallet h
*Q: Can the plugin work without WooCommerce*
-A: For the plugin to work correctly you need to have the WooCommerce plugin installed on a WordPress site
+A: For the plugin to work correctly you need to have the WooCommerce plugin installed on a WordPress site.
== Changelog ==
+= 1.1.0 =
+
+* Adapt plugin to new "Blocks" API of WooCommerce taking inspiration
+ from the woocommerce-gateway-dummy (GPLv3+).
+* Added bin/install_phpcs.sh to easily install phpcs and dependencies
+
= 0.9.4 =
* Use billing address in contract if shipping address is not given
diff --git a/resources/js/frontend/index.js b/resources/js/frontend/index.js
@@ -0,0 +1,61 @@
+/*
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+import { sprintf, __ } from '@wordpress/i18n';
+import { registerPaymentMethod } from '@woocommerce/blocks-registry';
+import { decodeEntities } from '@wordpress/html-entities';
+import { getSetting } from '@woocommerce/settings';
+
+const settings = getSetting('gnutaler_data', {});
+
+const defaultLabel = __(
+ 'GNU Taler',
+ 'woocommerce-gateway-gnutaler'
+);
+const label = decodeEntities(settings.title) || defaultLabel;
+
+/**
+ * Content component
+ */
+const Content = () => {
+ return decodeEntities(settings.description || '');
+};
+
+/**
+ * Label component
+ *
+ * @param {*} props Props from payment API.
+ */
+const Label = (props) => {
+ const { PaymentMethodLabel } = props.components;
+ return <PaymentMethodLabel text ={ label } />;
+};
+
+/**
+ * GNU Taler payment method config object.
+ */
+const GnutalerPaymentMethod = {
+ name: 'gnutaler',
+ label: <Label />,
+ content: <Content />,
+ edit: <Content />,
+ canMakePayment: () => true,
+ ariaLabel: label,
+ supports: {
+ features: settings.supports,
+ },
+};
+
+registerPaymentMethod( GnutalerPaymentMethod );
diff --git a/snippets/kudos-currency.php b/snippets/kudos-currency.php
@@ -1,3 +1,5 @@
+<?php
+
/**
* License: Public domain.
*
diff --git a/webpack.config.js b/webpack.config.js
@@ -0,0 +1,47 @@
+const defaultConfig = require('@wordpress/scripts/config/webpack.config');
+const WooCommerceDependencyExtractionWebpackPlugin = require('@woocommerce/dependency-extraction-webpack-plugin');
+const path = require('path');
+
+const wcDepMap = {
+ '@woocommerce/blocks-registry': ['wc', 'wcBlocksRegistry'],
+ '@woocommerce/settings' : ['wc', 'wcSettings']
+};
+
+const wcHandleMap = {
+ '@woocommerce/blocks-registry': 'wc-blocks-registry',
+ '@woocommerce/settings' : 'wc-settings'
+};
+
+const requestToExternal = (request) => {
+ if (wcDepMap[request]) {
+ return wcDepMap[request];
+ }
+};
+
+const requestToHandle = (request) => {
+ if (wcHandleMap[request]) {
+ return wcHandleMap[request];
+ }
+};
+
+// Export configuration.
+module.exports = {
+ ...defaultConfig,
+ entry: {
+ 'frontend/blocks': '/resources/js/frontend/index.js',
+ },
+ output: {
+ path: path.resolve( __dirname, 'assets/js' ),
+ filename: '[name].js',
+ },
+ plugins: [
+ ...defaultConfig.plugins.filter(
+ (plugin) =>
+ plugin.constructor.name !== 'DependencyExtractionWebpackPlugin'
+ ),
+ new WooCommerceDependencyExtractionWebpackPlugin({
+ requestToExternal,
+ requestToHandle
+ })
+ ]
+};
diff --git a/woocommerce-gateway-gnutaler.php b/woocommerce-gateway-gnutaler.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Plugin Name: WooCommerce GNU Taler Payments Gateway
+ * Plugin URI: https://git.taler.net/gnu-taler-payment-for-woocommerce.git
+ * Description: Adds the GNU Taler Payments gateway to your WooCommerce website.
+ * Version: 1.1.0
+ *
+ * Author: Taler Systems SA
+ * Author URI: https://taler-systems.com/
+ *
+ * Text Domain: woocommerce-gateway-gnutaler
+ * Domain Path: /i18n/languages/
+ *
+ * Requires at least: 4.2
+ * Tested up to: 6.6
+ *
+ * Copyright: © 2009-2025 Taler Systems SA (using template from Automattic)
+ * License: GNU General Public License v3.0
+ * License URI: http://www.gnu.org/licenses/gpl-3.0.html
+ */
+
+// Exit if accessed directly.
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+/**
+ * WC Gnutaler Payment gateway plugin class.
+ *
+ * @class WC_Gnutaler_Payments
+ */
+class WC_Gnutaler_Payments {
+
+ /**
+ * Plugin bootstrapping.
+ */
+ public static function init() {
+
+ // Gnutaler Payments gateway class.
+ add_action( 'plugins_loaded', array( __CLASS__, 'includes' ), 0 );
+
+ // Make the Gnutaler Payments gateway available to WC.
+ add_filter( 'woocommerce_payment_gateways', array( __CLASS__, 'add_gateway' ) );
+
+ // Registers WooCommerce Blocks integration.
+ add_action( 'woocommerce_blocks_loaded', array( __CLASS__, 'woocommerce_gateway_gnutaler_woocommerce_block_support' ) );
+ }
+
+ /**
+ * Add the GNU Taler Payment gateway to the list of available gateways.
+ *
+ * @param array
+ */
+ public static function add_gateway( $gateways ) {
+
+ $options = get_option( 'woocommerce_gnutaler_settings', array() );
+
+ if ( isset( $options['hide_for_non_admin_users'] ) ) {
+ $hide_for_non_admin_users = $options['hide_for_non_admin_users'];
+ } else {
+ $hide_for_non_admin_users = 'no';
+ }
+
+ if ( ( 'yes' === $hide_for_non_admin_users && current_user_can( 'manage_options' ) ) || 'no' === $hide_for_non_admin_users ) {
+ $gateways[] = 'WC_Gateway_Gnutaler';
+ }
+ return $gateways;
+ }
+
+ /**
+ * Plugin includes.
+ */
+ public static function includes() {
+
+ // Make the WC_Gateway_Gnutaler class available.
+ if ( class_exists( 'WC_Payment_Gateway' ) ) {
+ require_once 'includes/class-wc-gateway-gnutaler.php';
+ }
+ }
+
+ /**
+ * Plugin url.
+ *
+ * @return string
+ */
+ public static function plugin_url() {
+ return untrailingslashit( plugins_url( '/', __FILE__ ) );
+ }
+
+ /**
+ * Plugin url.
+ *
+ * @return string
+ */
+ public static function plugin_abspath() {
+ return trailingslashit( plugin_dir_path( __FILE__ ) );
+ }
+
+ /**
+ * Registers WooCommerce Blocks integration.
+ *
+ */
+ public static function woocommerce_gateway_gnutaler_woocommerce_block_support() {
+ if ( class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' ) ) {
+ require_once 'includes/blocks/class-wc-gnutaler-payments-blocks.php';
+ add_action(
+ 'woocommerce_blocks_payment_method_type_registration',
+ function ( Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry $payment_method_registry ) {
+ $payment_method_registry->register( new WC_Gateway_Gnutaler_Blocks_Support() );
+ }
+ );
+ }
+ }
+}
+
+WC_Gnutaler_Payments::init();