commit cc3ed8cc3c3adaa4164faa6e99192ec2311676d5
parent 1196fd18dc60aac0755f773d4214e8f60587d3b5
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 3 May 2026 09:56:23 +0200
add merchant backend protocol version compatibility check
Diffstat:
2 files changed, 71 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
@@ -179,7 +179,7 @@ taler_turnstile/
- Drupal 9 or 10
- PHP 8.1 or higher (the module uses native enums)
-- A GNU Taler merchant backend supporting the v25 (or newer)
+- A GNU Taler merchant backend supporting the v29 (or newer)
`paivana` template type and the public `/sessions/$SESSION_ID`
endpoint
- The Drupal `path_alias` module (used by the confirmation endpoint
diff --git a/src/TalerMerchantApiService.php b/src/TalerMerchantApiService.php
@@ -51,6 +51,15 @@ class TalerMerchantApiService {
const CACHE_BACKEND_DATA_SECONDS = 60;
/**
+ * Merchant backend protocol version (libtool "current") required by
+ * this module. The backend's /config "version" string is libtool-style
+ * "CURRENT:REVISION:AGE": the backend supports interfaces in the range
+ * [CURRENT-AGE, CURRENT], so we require this number to fall in that
+ * range.
+ */
+ const REQUIRED_PROTOCOL_VERSION = 29;
+
+ /**
* The HTTP client factory.
*
* @var \Drupal\Core\Http\ClientFactory
@@ -111,6 +120,7 @@ class TalerMerchantApiService {
* Backend URL to check, may include '/instances/$ID' path
* @return bool
* TRUE if this is a valid backend URL for a Taler backend
+ * that speaks a protocol compatible with this module
*/
public function checkConfig(string $backend_url) {
$base_url = $this->getBaseURL($backend_url);
@@ -128,7 +138,14 @@ class TalerMerchantApiService {
return FALSE;
}
$body = json_decode($response->getBody(), TRUE);
- return isset($body['name']) && $body['name'] === 'taler-merchant';
+ if (!isset($body['name']) || $body['name'] !== 'taler-merchant') {
+ return FALSE;
+ }
+ if (!isset($body['version']) || !is_string($body['version'])) {
+ $this->logger->error('Taler merchant backend /config response is missing the "version" field; cannot verify protocol compatibility.');
+ return FALSE;
+ }
+ return $this->checkVersion($body['version']);
} catch (\Exception $e) {
return FALSE;
}
@@ -136,6 +153,49 @@ class TalerMerchantApiService {
/**
+ * Verify that a libtool-style "CURRENT:REVISION:AGE" version string
+ * advertises support for self::REQUIRED_PROTOCOL_VERSION. The backend
+ * supports interfaces in [CURRENT-AGE, CURRENT]; we require the
+ * required version to lie within that range. Logs an error when it
+ * does not.
+ *
+ * @param string $version
+ * The "version" field from the backend's /config response.
+ * @return bool
+ * TRUE iff the backend speaks a compatible protocol version.
+ */
+ private function checkVersion(string $version): bool {
+ $parts = explode(':', $version);
+ if (count($parts) !== 3
+ || !ctype_digit($parts[0])
+ || !ctype_digit($parts[1])
+ || !ctype_digit($parts[2])) {
+ $this->logger->error('Taler merchant backend reported malformed version "@version" (expected libtool-style CURRENT:REVISION:AGE).', [
+ '@version' => $version,
+ ]);
+ return FALSE;
+ }
+ $current = (int) $parts[0];
+ $age = (int) $parts[2];
+ $required = self::REQUIRED_PROTOCOL_VERSION;
+ if ($current < $required) {
+ $this->logger->error('Taler merchant backend protocol version "@version is too old; this module requires protocol v@required or newer.', [
+ '@version' => $version,
+ '@required' => $required,
+ ]);
+ return FALSE;
+ }
+ if ($current - $age > $required) {
+ $this->logger->warning('Taler merchant backend protocol version "@version" MAY no longer support v@required required by this module. Proceed with caution.', [
+ '@version' => $version,
+ '@required' => $required,
+ ]);
+ return TRUE;
+ }
+ return TRUE;
+ }
+
+ /**
* Checks if the given backend URL points to a Taler merchant backend.
*
* @param string $backend_url
@@ -326,6 +386,15 @@ class TalerMerchantApiService {
return [];
}
+ if (!isset($backend_config['version']) || !is_string($backend_config['version'])) {
+ $this->logger->error('Taler merchant backend /config response is missing the "version" field; cannot obtain currency list.');
+ return [];
+ }
+ if (!$this->checkVersion($backend_config['version'])) {
+ // checkVersion() already logged the specific reason.
+ return [];
+ }
+
if (! isset($backend_config['currencies']))
{
$this->logger->error('Backend returned malformed response for /config');