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:
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