wordpress-turnstile

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

WORKFLOW.md (6951B)


      1 # GNU Taler Turnstile Workflow
      2 
      3 This document explains the complete workflow of the Taler Turnstile
      4 WordPress plugin.
      5 
      6 ## Setup Phase
      7 
      8 ### 1. Plugin Installation
      9 - Admin uploads and activates the plugin
     10 - Default options are created (post type = 'post', grant_access_on_error = false)
     11 
     12 ### 2. Backend Configuration
     13 **Settings > Taler Turnstile**
     14 - Admin enters payment backend URL (must end with `/`)
     15 - Admin enters access token (must start with `secret-token:`)
     16 - Settings are validated against the actual backend
     17 - Backend connection is tested via HTTP requests to public
     18   /config endpoint to see if we have a merchant backend, and to
     19   "GET /private/orders" to see if the access token is valid.
     20 
     21 ### 3. Post Type Selection
     22 - Admin selects which post types should support paid content
     23 - When enabled: price category meta field is automatically added
     24 - When disabled: price category meta field is automatically removed
     25 - Field appears as a meta box in the post editor sidebar
     26 
     27 ### 4. Subscription Price Configuration
     28 **Settings > Taler Subscriptions**
     29 - Page only accessible after backend is configured as we need
     30   to download the list of subscription types from the backend
     31 - Lists all subscription types from the backend
     32 - Admin sets purchase price for each subscription in each currency
     33 - Empty price = subscription cannot be purchased in that currency
     34 
     35 ### 5. Price Category Creation
     36 **Taler Prices > Add New**
     37 - Admin creates named price categories (e.g., "Premium", "Standard")
     38 - For each subscription type and currency combination, set the access price
     39 - Empty price = cannot pay with that combination
     40 - Zero price = subscription grants full access without payment
     41 
     42 ## Content Publishing Phase
     43 
     44 ### 6. Content Creation
     45 - Editor creates/edits a post of an enabled post type
     46 - In the sidebar meta box, selects a price category
     47 - Saves the post
     48 - The price category ID is stored as post meta
     49 
     50 ## Visitor Access Phase
     51 
     52 ### 7. Initial Page View
     53 
     54 When a visitor views a protected post:
     55 
     56 ```
     57 1. WordPress loads the post
     58 2. Content filter is triggered
     59 3. Taler_Content_Filter::filter_content() checks:
     60 
     61    - Is this a singular post view? (not archive/search)
     62    - Is Turnstile enabled for this post type?
     63    - Is the post have a price category and is thus not gratis?
     64    - Does visitor lack a subscription granting zero-price access?
     65    - Does visitor's session not already have access?
     66 
     67    If all checks pass → proceed to paywall logic
     68    If any check fails → show full content
     69 ```
     70 
     71 ### 8. Order Management
     72 
     73 **Check for existing order:**
     74 ```
     75 - Look in session: $_SESSION['taler_turnstile_node_orders'][$post_id]
     76 - If found:
     77   - Query backend for order status
     78   - If paid → grant session access, show full content
     79   - If expired → ignore, create new order
     80   - If still valid → reuse existing order
     81 - If not found or invalid:
     82   - Create new order via backend API
     83   - Store order info in session
     84 ```
     85 
     86 **Order Creation:**
     87 ```
     88 POST /private/orders
     89 {
     90   "order": {
     91     "version": 1,
     92     "choices": [...], // From price category
     93     "summary": "Access to: Post Title",
     94     "fulfillment_url": "https://example.com/post-slug/",
     95     "pay_deadline": {...}
     96   },
     97   "session_id": "hashed-session-id",
     98   "create_token": false
     99 }
    100 
    101 Response: { "order_id": "..." }
    102 ```
    103 
    104 ### 9. Paywall Display
    105 
    106 Content is replaced with:
    107 ```html
    108 <div class="taler-turnstile-paywall">
    109   <!-- Post excerpt -->
    110   <div class="taler-turnstile-excerpt">...</div>
    111 
    112   <!-- Payment interface -->
    113   <div class="taler-payment-container">
    114     <!-- QR Code section -->
    115     <div class="taler-qr-section">
    116       <div class="taler-turnstile-qr-code-container"
    117            data-payment-url="..."
    118            data-session-id="...">
    119         <!-- QR code generated by JavaScript -->
    120       </div>
    121     </div>
    122 
    123     <!-- Button section -->
    124     <div class="taler-button-section">
    125       <a href="https://$BACKEND/orders/$ORDER_ID" class="taler-pay-button">
    126         Pay with GNU Taler
    127       </a>
    128     </div>
    129   </div>
    130 </div>
    131 ```
    132 
    133 ### 10. QR Code Generation
    134 
    135 **Client-side (payment-button.js):**
    136 ```javascript
    137 1. Find all .taler-turnstile-qr-code-container elements
    138 2. Extract payment-url and session-id from data attributes
    139 3. Convert HTTP URL to Taler URI format:
    140    - https://$BACKEND/orders/$ORDER_ID → taler://pay/$BACKEND/$ORDER_ID/$SESSION_ID
    141    - http://$BACKEND/orders/$ORDER_ID → taler+http://pay/$BACKEND/$ORDER_ID/$SESSION_ID
    142 4. Generate QR code using QRCode.js
    143 ```
    144 
    145 ### 11. Payment Polling
    146 
    147 **Automatic polling starts:**
    148 ```javascript
    149 function pollPaymentStatus(paymentUrl, sessionId) {
    150   // Long-poll with 30-second timeout
    151   GET paymentUrl?timeout_ms=30000&session_id=sessionId
    152 
    153   Responses:
    154   - 200/202: Payment complete → reload page
    155   - 402: Payment pending → continue polling
    156   - Timeout: Network timeout → retry polling
    157   - Error: Wait 5 seconds → retry polling
    158 }
    159 ```
    160 
    161 **Polling prevents:**
    162 - Rapid loops (enforces minimum delay between requests)
    163 - Server hammering (uses long-polling with timeout_ms)
    164 - Infinite errors (retries with backoff on non-402 errors)
    165 
    166 ### 12. Payment Completion
    167 
    168 **When visitor pays via wallet:**
    169 ```
    170 1. Taler wallet communicates with backend
    171 2. Backend marks order as paid
    172 3. Backend may issue subscription token to wallet
    173 4. Next poll request returns 200 OK
    174 5. JavaScript detects 200 OK and reloads page
    175 ```
    176 
    177 **On page reload:**
    178 ```
    179 1. filter_content() runs again
    180 2. Finds existing order in session
    181 3. Checks order status with backend
    182 4. Backend returns: paid=true, subscription info
    183 5. Plugin grants session access: $_SESSION['taler_turnstile_access'][$post_id] = true
    184 6. If subscription purchased: $_SESSION['taler_turnstile_subscriptions'][$slug] = expiration
    185 7. Full content is displayed
    186 ```
    187 
    188 ## Session State Management
    189 
    190 ### Access Tracking
    191 ```php
    192 $_SESSION['taler_turnstile_access'] = [
    193   123 => true,  // Post ID 123 has been paid for
    194   456 => true,  // Post ID 456 has been paid for
    195 ];
    196 ```
    197 
    198 ### Subscription Tracking
    199 ```php
    200 $_SESSION['taler_turnstile_subscriptions'] = [
    201   'premium' => 1735689600,  // Expires at Unix timestamp
    202   'basic' => 1733097600,
    203 ];
    204 ```
    205 
    206 ### Order Tracking
    207 ```php
    208 $_SESSION['taler_turnstile_node_orders'] = [
    209   123 => [
    210     'order_id' => 'ABC123',
    211     'payment_url' => 'https://...',
    212     'session_id' => 'hashed-id',
    213     'order_expiration' => 1733011200,
    214     'paid' => false,
    215   ],
    216 ];
    217 ```
    218 
    219 ## Error Handling
    220 
    221 ### Backend Unavailable
    222 ```
    223 If grant_access_on_error = true:
    224   - Show full content (fail open)
    225   - Log warning
    226 
    227 If grant_access_on_error = false:
    228   - Show error message (fail closed)
    229   - Do not show content
    230 ```
    231 
    232 ### Payment Polling Errors
    233 ```
    234 - Non-402 HTTP errors: Wait 5 seconds, retry
    235 - Network timeouts: Wait for full timeout period, retry
    236 - 402 responses: Continue polling immediately
    237 - Never give up polling (visitor may pay at any time)
    238 ```
    239 
    240 ## Security Considerations
    241 
    242 ### Session Security
    243 - Session IDs are hashed (SHA-256) before sending to backend
    244 - Backend cannot correlate sessions across sites