wordpress-turnstile

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

payment-button.js (6289B)


      1 /**
      2  * JavaScript for GNU Taler Turnstile payment button functionality.
      3  */
      4 
      5 (function($) {
      6     'use strict';
      7 
      8     /**
      9      * Convert HTTP(S) payment URL to Taler URI format
     10      *
     11      * @param {string} paymentUrl - The HTTP(S) payment URL
     12      * @param {string} sessionId - The hashed session ID
     13      * @returns {string} The Taler 'pay' URI including session ID
     14      */
     15     function convertToTalerUri(paymentUrl, sessionId) {
     16         try {
     17             var url = new URL(paymentUrl);
     18             var protocol = url.protocol; // 'https:' or 'http:'
     19             var host = url.host; // includes port if specified
     20             var pathname = url.pathname; // e.g., '/something/orders/12345'
     21 
     22             // Extract the path components, removing '/orders/' part
     23             // Expected input: [/instance/$ID]/orders/$ORDER_ID
     24             // Expected output: [/instance/$ID]/$ORDER_ID
     25             var pathParts = pathname.split('/').filter(function(part) {
     26                 return part.length > 0;
     27             });
     28 
     29             // Find 'orders' in the path and reconstruct without it
     30             var ordersIndex = pathParts.indexOf('orders');
     31             var talerPath = '';
     32 
     33             if (ordersIndex !== -1 && ordersIndex < pathParts.length - 1) {
     34                 // Get parts before 'orders' and after 'orders'
     35                 var beforeOrders = pathParts.slice(0, ordersIndex);
     36                 var afterOrders = pathParts.slice(ordersIndex + 1);
     37                 talerPath = beforeOrders.concat(afterOrders).join('/');
     38             } else {
     39                 console.error('Error converting to Taler URI: "/orders/" not found');
     40                 return paymentUrl;
     41             }
     42 
     43             if (protocol === 'https:') {
     44                 return 'taler://pay/' + host + '/' + talerPath +
     45                        '/' + encodeURIComponent(sessionId);
     46             } else if (protocol === 'http:') {
     47                 return 'taler+http://pay/' + host + '/' + talerPath +
     48                        '/' + encodeURIComponent(sessionId);
     49             }
     50 
     51             console.error('Error converting to Taler URI: unsupported protocol');
     52             return paymentUrl;
     53         } catch (e) {
     54             console.error('Error converting to Taler URI:', e);
     55             return paymentUrl;
     56         }
     57     }
     58 
     59     /**
     60      * Long-poll the payment URL to check if payment was completed
     61      *
     62      * @param {string} paymentUrl - The payment URL to poll
     63      * @param {string} sessionId - The session ID
     64      */
     65     function pollPaymentStatus(paymentUrl, sessionId) {
     66         var separator = paymentUrl.indexOf('?') !== -1 ? '&' : '?';
     67         var timeoutMs = 30000;
     68         var pollUrl = paymentUrl + separator + 'timeout_ms=' + timeoutMs +
     69                      '&session_id=' + encodeURIComponent(sessionId);
     70         var startTime = Date.now(); // in milliseconds since Epoch
     71 
     72         $.ajax({
     73             url: pollUrl,
     74             method: 'GET',
     75             timeout: timeoutMs + 5000, // Slightly longer than server timeout
     76             headers: {
     77                 'Accept': 'application/json'
     78             },
     79             success: function(data, textStatus, xhr) {
     80                 if (xhr.status === 200 || xhr.status === 202) {
     81                     console.log('Payment completed! Reloading page...');
     82                     window.location.reload();
     83                 } else if (xhr.status === 402) {
     84                     console.log('Payment still pending, continuing to poll...');
     85                     var endTime = Date.now();
     86                     // Prevent looping faster than the long-poll timeout
     87                      // (useful in case a bug causes the 402 to be returned
     88                     // immediately instead of long-polling properly).
     89                     var delay = (startTime + timeoutMs > endTime)
     90                         ? (startTime + timeoutMs - endTime)
     91                         : 0;
     92                     setTimeout(function() {
     93                         pollPaymentStatus(paymentUrl, sessionId);
     94                     }, delay);
     95                 }
     96             },
     97             error: function(xhr, textStatus, errorThrown) {
     98                 if (xhr.status === 402) {
     99                     console.log('Payment still required (402), continuing to poll...');
    100                     pollPaymentStatus(paymentUrl, sessionId);
    101                 } else if (textStatus === 'timeout') {
    102                     console.log('Poll timeout, retrying...');
    103                     var endTime = Date.now();
    104                     // Prevent looping faster than the long-poll timeout
    105                     var delay = (startTime + timeoutMs > endTime)
    106                         ? (startTime + timeoutMs - endTime)
    107                         : 0;
    108                     setTimeout(function() {
    109                         pollPaymentStatus(paymentUrl, sessionId);
    110                     }, delay);
    111                 } else {
    112                     // Other errors - wait a bit before retrying
    113                     console.log('Poll error: ' + textStatus + ', retrying in 5 seconds...');
    114                     setTimeout(function() {
    115                         pollPaymentStatus(paymentUrl, sessionId);
    116                     }, 5000);
    117                 }
    118             }
    119         });
    120     }
    121 
    122     /**
    123      * Initialize payment button functionality
    124      */
    125     $(document).ready(function() {
    126         $('.taler-turnstile-qr-code-container').each(function() {
    127             var $container = $(this);
    128             var paymentUrl = $container.data('payment-url');
    129             var sessionId = $container.data('session-id');
    130 
    131             if (paymentUrl && typeof QRCode !== 'undefined') {
    132                 $container.empty();
    133                 var talerUri = convertToTalerUri(paymentUrl, sessionId);
    134                 new QRCode($container[0], {
    135                     text: talerUri,
    136                     width: 200,
    137                     height: 200,
    138                     colorDark: '#000000',
    139                     colorLight: '#ffffff',
    140                     correctLevel: QRCode.CorrectLevel.M
    141                 });
    142 
    143                 console.log('Starting payment status polling for: ' + paymentUrl);
    144                 pollPaymentStatus(paymentUrl, sessionId);
    145             } else if (!window.QRCode) {
    146                 console.error('QRCode library not loaded');
    147             }
    148         });
    149 
    150     });
    151 
    152 })(jQuery);