turnstile

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

commit c6a52d2e30aa8fad93b7fb96444909a5f8eab0c5
parent 9a27c8a6f9d619349459c12d0afe9f489908ad02
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 11 Oct 2025 17:35:04 +0200

get currencies and token families from backend

Diffstat:
Msrc/Form/PriceCategoryForm.php | 2+-
Msrc/TalerMerchantApiService.php | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 136 insertions(+), 24 deletions(-)

diff --git a/src/Form/PriceCategoryForm.php b/src/Form/PriceCategoryForm.php @@ -112,7 +112,7 @@ class PriceCategoryForm extends EntityForm { '#title' => $currency_label, '#default_value' => $existing_prices[$subscription_id][$currency_code] ?? '', '#min' => 0, - // '#step' => 0.01, // FIXME: should this be set? + '#step' => $currency['step'] ?? 0.01, '#size' => 20, '#description' => $this->t('Leave empty for no price.'), ]; diff --git a/src/TalerMerchantApiService.php b/src/TalerMerchantApiService.php @@ -4,9 +4,7 @@ * @file * Location: src/TalerMerchantApiService.php * - * - * Service for interacting with the Turnstile API. - * FIXME: rename to merchant API service? + * Service for interacting with the Taler Merchant Backend. */ namespace Drupal\turnstile; @@ -47,40 +45,154 @@ class TalerMerchantApiService { } /** - * Gets the list of available subscriptions. + * Gets the list of available subscriptions. Always includes a special + * entry for "No reduction" with ID "". * * @return array - * Array of subscriptions. + * Array of subscriptions each with an 'id' (the slug), 'name' and 'label' (again the slug). */ public function getSubscriptions() { - return [ - ["id" => "none", + // Per default, we always have "no subscription" as an option. + $subscriptions = [ + ["id" => "", "name" => "none", - "label" => "No Subscription" ], - [ "id" => "monthly", - "name" => "monthly", - "label" => "Monthly" ], - ["id" => "yearly", - "name" => "yearly", - "label" => "Yearly"] + "label" => "No reduction" ], ]; + $config = \Drupal::config('turnstile.settings'); + $backend_url = $config->get('payment_backend_url'); + $access_token = $config->get('access_token'); + + if (empty($backend_url) || + empty($access_token)) { + \Drupal::logger('turnstile')->debug('No Turnstile backend configured, returning "none" for subscriptions.'); + return $subscriptions; + } + + try { + $http_client = \Drupal::httpClient(); + $response = $http_client->post($backend_url . 'private/tokenfamilies', [ + 'json' => $order_data, + 'headers' => [ + 'Authorization' => 'Bearer ' . $access_token, + 'Content-Type' => 'application/json', + ], + // Do not throw exceptions on 4xx/5xx status codes + 'http_errors' => false, + ]); + /* Get JSON result parsed as associative array */ + $http_status = $response->getStatusCode(); + $body = $response->getBody(); + $result = json_decode($body, TRUE); + switch ($http_status) + { + case 200: + if (! isset($result['token_families'])) { + \Drupal::logger('turnstile')->error('Failed to obtain token family list: HTTP success response unexpectedly lacks "token_families" field.'); + return $subscriptions; + } + /* Success, handled below */ + break; + case 204: + // empty list + return $subscriptions; + case 403: + \Drupal::logger('turnstile')->warning('Access denied by the merchant backend. Did your credentials change or expire? Check your Turnstile configuration!'); + return $subscriptions; + case 404: + $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + \Drupal::logger('turnstile')->error('Failed to fetch token family list: @hint (@ec): @body', ['@hint' => $result['hint'] ?? 'N/A', '@ec' => $result['code'] ?? 'N/A', '@body' => $body_log ?? 'N/A']); + return $subscriptions; + default: + $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + \Drupal::logger('turnstile')->error('Unexpected HTTP status code @status trying to fetch token family list: @hint (@detail, #@ec): @body', ['@status' => $http_status, '@hint' => $result['hint'] ?? 'N/A', '@ec' => $result['code'] ?? 'N/A', '@detail' => $result['detail'] ?? 'N/A', '@body' => $body_log ?? 'N/A']); + return $subscriptions; + } // end switch on HTTP status + + $tokenFamilies = $result['token_families']; + $transformed = array_map(function ($family) { + return [ + 'id' => $family['slug'], + 'name' => $family['name'], + 'label' => $family['slug'], + ]; + }, $tokenFamilies); + return array_merge ($subscriptions, $transformed); + } + catch (RequestException $e) { + $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + \Drupal::logger('turnstile')->error('Failed to obtain list of token families: @message: @body', ['@message' => $e->getMessage(), '@body' => $body_log ?? 'N/A']); + } + return $subscriptions; } /** * Gets the list of available currencies. * * @return array - * Array of currencies. + * Array of currencies with 'code' (currency code), 'name' and 'label' + * and 'step' (typically 0 for JPY or 0.01 for EUR/USD). */ public function getCurrencies() { - return [ - [ "code" => "USD", - "name" => "USD", - "label" => "US Dollar" ], - [ "code" => "EUR", - "name" => "EUR", - "label" => "Euro"], - ]; + + $config = \Drupal::config('turnstile.settings'); + $payment_backend_url = $config->get('payment_backend_url'); + + if (empty($payment_backend_url)) { + \Drupal::logger('turnstile')->error('Taler merchant backend not configured; cannot obtain currency list'); + return []; + } + + try { + // Fetch backend configuration. + $client = \Drupal::httpClient(); + + $config_url = $payment_backend_url . 'config'; + $response = $client->get($config_url, [ + 'allow_redirects' => TRUE, + 'http_errors' => FALSE, + 'timeout' => 5, + ]); + + if ($response->getStatusCode() !== 200) { + \Drupal::logger('turnstile')->error('Taler merchant backend did not respond; cannot obtain currency list'); + return []; + } + + $backend_config = json_decode($response->getBody(), TRUE); + if (!$backend_config || !is_array($backend_config)) { + // Invalid response, fallback to grant_access_on_error setting. + \Drupal::logger('turnstile')->error('Taler merchant backend returned invalid /config response; cannot obtain currency list'); + return []; + } + + if (! isset($backend_config['currencies'])) + { + \Drupal::logger('turnstile')->error('Backend returned malformed response for /config'); + return []; + } + + // Parse and validate each amount in the comma-separated list. + $currencies = $backend_config['currencies']; + + return array_map(function ($currency) { + return [ + 'id' => $currency['currency'], + 'name' => $currency['name'], + 'label' => $currency['alt_unit_names'][0] ?? $currency['id'], + 'step' => pow (0.1, $currency['num_fractional_input_digits'] ?? 2), + ]; + }, + $currencies + ); + + } catch (\Exception $e) { + + // On exception, fall back to grant_access_on_error setting. + \Drupal::logger('turnstile')->error('Failed to validate obtain configuration from backend: @error', [ + '@error' => $e->getMessage(), + ]); + return []; + } } } \ No newline at end of file