wordpress-turnstile

Wordpress paywall plugin
Log | Files | Refs | README | LICENSE

commit b36819e17f09384551a3c7b4566b8d276e692c29
parent 6f38c6bc653a664391fb9bf543f5ae95b8b38eec
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  2 Nov 2025 13:26:39 +0100

clean up code a bit

Diffstat:
MREADME.md | 67+++++++++++++++++++++++++++++++++++++++++++------------------------
MWORKFLOW.md | 47++++++++++++++++++++---------------------------
Madmin/class-admin-settings.php | 7++++---
Madmin/class-subscription-prices-admin.php | 8++++----
4 files changed, 71 insertions(+), 58 deletions(-)

diff --git a/README.md b/README.md @@ -1,10 +1,12 @@ # GNU Taler Turnstile for WordPress -A WordPress plugin that adds price fields to posts and requires payment via GNU Taler for access. +A WordPress plugin that adds price fields to posts and requires +payment via GNU Taler for access. ## Description -GNU Taler Turnstile enables content creators to monetize their WordPress content using the GNU Taler payment system. The plugin allows you to: +GNU Taler Turnstile enables authors to monetize their WordPress +articles using the GNU Taler payment system. The plugin allows you to: - Set prices for individual posts/pages - Create price categories with different pricing tiers @@ -14,44 +16,52 @@ GNU Taler Turnstile enables content creators to monetize their WordPress content ## Installation -1. Download `qrcode.min.js` from https://github.com/davidshimjs/qrcodejs and place it in `assets/js/qrcode.min.js` -2. Upload the `taler-turnstile` folder to the `/wp-content/plugins/` directory -3. Activate the plugin through the 'Plugins' menu in WordPress -4. Configure your GNU Taler merchant backend in Settings > Taler Turnstile -5. Create price categories under Taler Prices +1. Upload the `taler-turnstile` folder to the `/wp-content/plugins/` directory +2. Activate the plugin through the `Plugins` menu in WordPress +3. Configure your GNU Taler merchant backend in `Settings` > `Taler Turnstile` +4. Create price categories under `Taler Prices` ## Configuration ### Basic Settings -1. Navigate to **Settings > Taler Turnstile** +0. Make sure you have the credentials of your Taler merchant backend at hand +1. Navigate to `Settings` > `Taler Turnstile` 2. Select which post types should support paid content 3. Configure your payment backend URL (must end with `/`) 4. Enter your access token (must start with `secret-token:`) 5. Optionally enable "Disable Turnstile on Error" for graceful degradation 6. Save settings - this will automatically add price category fields to selected post types -### Subscription Prices +### Subscription Prices (optional) After configuring the basic settings: -1. Navigate to **Settings > Taler Subscriptions** -2. Set prices for each subscription type in different currencies +0. Make sure you have subscriptions configured in the Taler merchant backend +1. Navigate to `Settings` > `Taler Subscriptions` +2. Set prices for buying each subscription type in different currencies 3. Leave fields empty to prevent purchasing that subscription with that currency 4. Save subscription prices ### Price Categories -1. Navigate to **Taler Prices** +1. Navigate to `Taler Prices` 2. Click "Add New" to create a price category -3. Set prices for different subscription types and currencies +3. Set prices for users without a subscription and for different subscription types and currencies + (leave blank to not allow buying with that currency, use 0 if the article is 100% included + in the subscription). 4. Save the category -5. Assign price categories to individual posts/pages via the meta box in the editor + +### Sell articles + +1. Select `Edit` on an individual post or page you want to monetize +2. Assign a price category to the individual post/page via the meta box in the editor +3. Save the article ## Requirements - WordPress 5.0 or higher -- PHP 7.4 or higher +- PHP 8.2 or higher - GNU Taler merchant backend instance ## File Structure @@ -83,20 +93,20 @@ taler-turnstile/ ## Features - **Content Paywall**: Automatically restricts access to paid content, showing excerpt + payment button +- **Payment Link**: Opens the payment dialog in the merchant backend, which can automatically trigger the Taler WebExtension wallet - **QR Code Payment**: Generates QR codes for easy mobile wallet payments - **Payment Polling**: Real-time payment status checking with automatic page reload on completion -- **Session Management**: Tracks paid access and active subscriptions per visitor session +- **Session Management**: Tracks paid access and active subscriptions in per visitor session - **Flexible Pricing**: Set prices per subscription type and currency -- **Multiple Currencies**: Support for EUR, USD, CHF, and more (fetched from backend) +- **Multiple Currencies**: Support for EUR, USD, CHF, and more (fetched from GNU Taler merchant backend) - **Subscription Support**: Offer subscription-based access with token families -- **Subscription Pricing**: Configure prices for purchasing subscriptions +- **Subscription Pricing**: Configure prices for purchasing subscriptions (user can buy article or subscribe and pay discounted price for the article) - **Zero-Price Subscriptions**: Automatically grant access for subscriptions with zero price - **Post Type Support**: Enable paid content for any public post type -- **Dynamic Field Management**: Automatically adds/removes price category fields when post types are enabled/disabled +- **Dynamic Field Management**: Automatically adds/removes price category fields when post types are enabled/disabled2 - **Meta Box Integration**: Easy price category selection in the post editor - **Error Handling**: Graceful degradation when backend is unavailable - **Cache Control**: Automatically disables caching for protected content -- **WordPress Standards**: Follows WordPress coding standards and best practices - **Caching**: Efficient caching of backend data using WordPress transients ## How It Works @@ -105,19 +115,27 @@ taler-turnstile/ 1. **View Protected Content**: When a visitor views a post with a price category assigned, they see: - An excerpt/teaser of the content - - A payment button with QR code - - Payment options for different currencies and subscriptions + - A payment button with QR code and a link for in-browser payments with GNU Taler 2. **Make Payment**: The visitor can: - Scan the QR code with their Taler wallet app - Click the payment button to open their wallet + - They will see payment options: + + pay full amount for individual article + + pay discounted amount with subscriptions (if they already have + a subscription that results in a discount) + + buy subscription - Choose their preferred payment option (currency and subscription type) + - If they are already a subscriber with a discounted price of zero, + the wallet will automatically choose and execute that option 3. **Automatic Access**: Once payment is completed: - The page automatically refreshes (via polling) - Full content is displayed - Access is stored in the session - If a subscription was purchased, it's tracked for future visits + in this session and user only needs the wallet again after the + session ends ### For Administrators @@ -157,8 +175,9 @@ For issues and questions: ## License -This plugin is licensed under GPL v3 or later. +This plugin is licensed under GNU GPL v3 or later. ## Credits -Based on the GNU Taler Turnstile module for Drupal. +The code is based on an initial AI-assisted transformation of +the GNU Taler Turnstile module for Drupal. diff --git a/WORKFLOW.md b/WORKFLOW.md @@ -1,6 +1,7 @@ # GNU Taler Turnstile Workflow -This document explains the complete workflow of the Taler Turnstile WordPress plugin. +This document explains the complete workflow of the Taler Turnstile +WordPress plugin. ## Setup Phase @@ -13,7 +14,9 @@ This document explains the complete workflow of the Taler Turnstile WordPress pl - Admin enters payment backend URL (must end with `/`) - Admin enters access token (must start with `secret-token:`) - Settings are validated against the actual backend -- Backend connection is tested via HTTP requests +- Backend connection is tested via HTTP requests to public + /config endpoint to see if we have a merchant backend, and to + "GET /private/orders" to see if the access token is valid. ### 3. Post Type Selection - Admin selects which post types should support paid content @@ -23,7 +26,8 @@ This document explains the complete workflow of the Taler Turnstile WordPress pl ### 4. Subscription Price Configuration **Settings > Taler Subscriptions** -- Page only accessible after backend is configured +- Page only accessible after backend is configured as we need + to download the list of subscription types from the backend - Lists all subscription types from the backend - Admin sets purchase price for each subscription in each currency - Empty price = subscription cannot be purchased in that currency @@ -51,18 +55,17 @@ When a visitor views a protected post: ``` 1. WordPress loads the post -2. the_content filter is triggered -3. Taler_Content_Filter::filter_content() runs +2. Content filter is triggered +3. Taler_Content_Filter::filter_content() checks: - Checks performed: - Is this a singular post view? (not archive/search) - - Is this post type enabled? - - Does the post have a price category? - - Does visitor have a subscription granting zero-price access? - - Does visitor's session already have access? + - Is Turnstile enabled for this post type? + - Is the post have a price category and is thus not gratis? + - Does visitor lack a subscription granting zero-price access? + - Does visitor's session not already have access? - If all checks pass → show full content - If any check fails → proceed to paywall logic + If all checks pass → proceed to paywall logic + If any check fails → show full content ``` ### 8. Order Management @@ -119,7 +122,7 @@ Content is replaced with: <!-- Button section --> <div class="taler-button-section"> - <a href="taler://pay/..." class="taler-pay-button"> + <a href="https://$BACKEND/orders/$ORDER_ID" class="taler-pay-button"> Pay with GNU Taler </a> </div> @@ -134,10 +137,9 @@ Content is replaced with: 1. Find all .taler-turnstile-qr-code-container elements 2. Extract payment-url and session-id from data attributes 3. Convert HTTP URL to Taler URI format: - - https://backend/orders/ABC → taler://pay/backend/ABC/session-id - - http://backend/orders/ABC → taler+http://pay/backend/ABC/session-id + - https://$BACKEND/orders/$ORDER_ID → taler://pay/$BACKEND/$ORDER_ID/$SESSION_ID + - http://$BACKEND/orders/$ORDER_ID → taler+http://pay/$BACKEND/$ORDER_ID/$SESSION_ID 4. Generate QR code using QRCode.js -5. Update button href to use Taler URI ``` ### 11. Payment Polling @@ -167,9 +169,9 @@ function pollPaymentStatus(paymentUrl, sessionId) { ``` 1. Taler wallet communicates with backend 2. Backend marks order as paid -3. Backend may issue subscription token +3. Backend may issue subscription token to wallet 4. Next poll request returns 200 OK -5. JavaScript detects 200 and reloads page +5. JavaScript detects 200 OK and reloads page ``` **On page reload:** @@ -227,13 +229,6 @@ If grant_access_on_error = false: - Do not show content ``` -### Order Creation Fails -``` -- Check grant_access_on_error setting -- Either show error or grant access -- Log detailed error with HTTP status -``` - ### Payment Polling Errors ``` - Non-402 HTTP errors: Wait 5 seconds, retry @@ -247,4 +242,3 @@ If grant_access_on_error = false: ### Session Security - Session IDs are hashed (SHA-256) before sending to backend - Backend cannot correlate sessions across sites -- Session data i -\ No newline at end of file diff --git a/admin/class-admin-settings.php b/admin/class-admin-settings.php @@ -16,7 +16,6 @@ class Taler_Turnstile_Admin_Settings { } public function register_settings() { - // Register settings register_setting('taler_turnstile_settings', 'taler_turnstile_enabled_post_types', array( 'type' => 'array', 'sanitize_callback' => array($this, 'sanitize_post_types') @@ -56,7 +55,6 @@ class Taler_Turnstile_Admin_Settings { 'taler_turnstile_settings' ); - // Add settings fields add_settings_field( 'enabled_post_types', __('Enabled Post Types', 'taler-turnstile'), @@ -264,6 +262,9 @@ class Taler_Turnstile_Admin_Settings { } private function validate_http_status($http_status) { + if ($http_status !== 200 && $http_status !== 204) { + return; + } $messages = array( 502 => __('Bad gateway (502) trying to access the merchant backend', 'taler-turnstile'), 500 => __('Internal server error (500) of the merchant backend reported', 'taler-turnstile'), @@ -280,7 +281,7 @@ class Taler_Turnstile_Admin_Settings { $messages[$http_status], 'error' ); - } elseif ($http_status !== 200 && $http_status !== 204) { + } else { add_settings_error( 'taler_turnstile_settings', 'backend_error', diff --git a/admin/class-subscription-prices-admin.php b/admin/class-subscription-prices-admin.php @@ -58,11 +58,12 @@ class Taler_Subscription_Prices_Admin { return; } - // Check if backend is configured $backend_url = get_option('taler_turnstile_payment_backend_url'); $access_token = get_option('taler_turnstile_access_token'); - if (empty($backend_url) || empty($access_token)) { + if (empty($backend_url) || empty($access_token) || + (0 == Taler_Merchant_API::check_access($backend_url, + $access_token))) { ?> <div class="wrap"> <h1><?php echo esc_html(get_admin_page_title()); ?></h1> @@ -82,7 +83,6 @@ class Taler_Subscription_Prices_Admin { return; } - // Get subscriptions and currencies $subscriptions = Taler_Merchant_API::get_subscriptions(); $currencies = Taler_Merchant_API::get_currencies(); @@ -182,7 +182,7 @@ class Taler_Subscription_Prices_Admin { <?php endforeach; ?> <p class="submit"> - <input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_attr_e('Save Subscription Prices', 'taler-turnstile'); ?>"> + <input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_attr_e('Save', 'taler-turnstile'); ?>"> </p> </form> </div>