commit 6279849d9330f686d9d1287e2255c6e190eb6490
parent 0ca7d31605a979d79d62ffcdb65a6eed1074271c
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 11 Oct 2025 18:01:26 +0200
centralize HTTP logic in TalerMerchantApiService
Diffstat:
3 files changed, 280 insertions(+), 271 deletions(-)
diff --git a/README.md b/README.md
@@ -47,7 +47,7 @@ Navigate to `/admin/config/content/Turnstile` to configure:
## TODO
- actually *use* price categories when determining article price!
-- keep or remove price field?
+ => keep or remove price field?
- Make truncation logic work with tiles / cards
diff --git a/src/TalerMerchantApiService.php b/src/TalerMerchantApiService.php
@@ -12,6 +12,20 @@ namespace Drupal\turnstile;
use Drupal\Core\Http\ClientFactory;
use Psr\Log\LoggerInterface;
+
+
+/**
+ * Taler error codes used in this module. We do not define
+ * the full list here as that would be excessive and could
+ * just slow down PHP unnecessarily.
+ */
+enum TalerErrorCode: int {
+ case TALER_EC_NONE = 0;
+ case TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN = 2000;
+ case TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN = 2005;
+}
+
+
/**
* Service for fetching subscriptions and currencies from external API.
*/
@@ -64,7 +78,7 @@ class TalerMerchantApiService {
if (empty($backend_url) ||
empty($access_token)) {
- \Drupal::logger('turnstile')->debug('No Turnstile backend configured, returning "none" for subscriptions.');
+ $this->logger->debug('No Turnstile backend configured, returning "none" for subscriptions.');
return $subscriptions;
}
@@ -87,7 +101,7 @@ class TalerMerchantApiService {
{
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.');
+ $this->logger->error('Failed to obtain token family list: HTTP success response unexpectedly lacks "token_families" field.');
return $subscriptions;
}
/* Success, handled below */
@@ -96,15 +110,15 @@ class TalerMerchantApiService {
// 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!');
+ $this->logger->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']);
+ $this->logger->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']);
+ $this->logger->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
@@ -120,7 +134,7 @@ class TalerMerchantApiService {
}
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']);
+ $this->logger->error('Failed to obtain list of token families: @message: @body', ['@message' => $e->getMessage(), '@body' => $body_log ?? 'N/A']);
}
return $subscriptions;
}
@@ -138,7 +152,7 @@ class TalerMerchantApiService {
$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');
+ $this->logger->error('Taler merchant backend not configured; cannot obtain currency list');
return [];
}
@@ -154,20 +168,20 @@ class TalerMerchantApiService {
]);
if ($response->getStatusCode() !== 200) {
- \Drupal::logger('turnstile')->error('Taler merchant backend did not respond; cannot obtain currency list');
+ $this->logger->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');
+ $this->logger->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');
+ $this->logger->error('Backend returned malformed response for /config');
return [];
}
@@ -188,11 +202,261 @@ class TalerMerchantApiService {
} 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', [
+ $this->logger->error('Failed to validate obtain configuration from backend: @error', [
'@error' => $e->getMessage(),
]);
return [];
}
}
+
+ /**
+ * Check order status with Taler backend.
+ *
+ * @param string $order_id
+ * The order ID to check.
+ *
+ * @return array|FALSE
+ * Order status information or FALSE on failure.
+ */
+ public function checkOrderStatus($order_id) {
+ $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)) {
+ $this->logger->debug('No Turnstile backend configured, cannot check order status!');
+ return FALSE;
+ }
+
+ try {
+ $http_client = \Drupal::httpClient();
+ $response = $http_client->get($backend_url . 'private/orders/' . $order_id, [
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $access_token,
+ ],
+ // Do not throw exceptions on 4xx/5xx status codes
+ 'http_errors' => false,
+ ]);
+
+ $http_status = $response->getStatusCode();
+ $body = $response->getBody();
+ $result = json_decode($body, TRUE);
+ switch ($http_status)
+ {
+ case 200:
+ // Success, handled below
+ break;
+ case 403:
+ $this->logger->warning('Access denied by the merchant backend. Did your credentials change or expire? Check your Turnstile configuration!');
+ return FALSE;
+ case 404:
+ // Order unknown or instance unknown
+ /** @var TalerErrorCode $ec */
+ $ec = $result['code'] ?? TALER_EC_NONE;
+ switch ($ec)
+ {
+ case TALER_EC_NONE:
+ // Protocol violation. Could happen if the backend domain was
+ // taken over by someone else.
+ $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ $this->logger->error('Invalid response from merchant backend when trying to obtain order status. Check your Turnstile configuration! @body', ['@body' => $body_log ?? 'N/A']);
+ return FALSE;
+ case TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN:
+ // This could happen if our instance was deleted after the configuration was
+ // checked. Very bad, log serious error.
+ $this->logger->error('Configured instance "@detail" unknown to merchant backend. Check your Turnstile configuration!', ['@detail' => $result['detail'] ?? 'N/A']);
+ return FALSE;
+ case TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN:
+ // This could happen if the instance owner manually deleted
+ // an order while the customer was looking at the article.
+ $this->logger->warning('Order "@order" disappeared in the backend.', ['@order' => $order_id]);
+ return FALSE;
+ default:
+ $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ $this->logger->error('Unexpected error code @ec with HTTP status code @status from Taler merchant backend when trying to get order status: @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 FALSE;
+ }
+ default:
+ // Internal server errors and the like...
+ $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ $this->logger->error('Unexpected HTTP status code @status from Taler merchant backend when trying to get order status: @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 FALSE;
+ }
+
+ $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ $this->logger->error('Got existing contract: @body', ['@body' => $body_log ?? 'N/A']);
+
+ $order_status = $result['order_status'] ?? 'unknown';
+ $subscription_expiration = 0;
+ $pay_deadline = 0;
+ $paid = FALSE;
+ switch ($order_status)
+ {
+ case 'unpaid':
+ // 'pay_deadline' is only available since v21 rev 1, so for now we
+ // fall back to creation_time + offset.
+ $pay_deadline = $result['pay_deadline']['t_s'] ??
+ 60 * 60 * 24 + $result['creation_time']['t_s'] ?? 0;
+ break;
+ case 'claimed':
+ $contract_terms = $result['contract_terms'];
+ $pay_deadline = $contract_terms['pay_deadline']['t_s'] ?? 0;
+ break;
+ case 'paid':
+ $paid = TRUE;
+ $contract_terms = $result['contract_terms'];
+ $contract_version = $result['version'] ?? 0;
+ switch ($contract_version) {
+ case 0:
+ break;
+ case 1:
+ $choice_index = $result['choice_index'] ?? 0;
+ $contract_choice = $contract_terms['choices'][$choice_index];
+ $outputs = $contract_choice['outputs'];
+ // FIXME: add logic to detect subscriptions here and
+ // update $subscription_expiration if one was found!
+ break;
+ default:
+ break;
+ } // switch on contract version
+ break;
+ default:
+ $this->logger->error('Got unexpected order status "@status"', ['@status' => $order_status]);
+ break;
+ } // switch on $order_status
+ return [
+ 'order_id' => $order_id,
+ 'paid' => $paid,
+ 'subscription_expiration' => $subscription_expiration,
+ 'order_expiration' => $pay_deadline,
+ ];
+ }
+ catch (RequestException $e) {
+ // Any kind of error that is outside of the spec.
+ $this->logger->error('Failed to check order status: @message', ['@message' => $e->getMessage()]);
+ return FALSE;
+ }
+ }
+
+
+ /**
+ * Create a new Taler order.
+ *
+ * @param \Drupal\node\NodeInterface $node
+ * The node to create an order for.
+ *
+ * @return array|FALSE
+ * Order information or FALSE on failure.
+ */
+ public function createOrder(NodeInterface $node) {
+ $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)) {
+ $this->logger->debug('No backend, cannot setup new order');
+ return FALSE;
+ }
+
+ // FIXME: transition away from price to price categories...
+ $price = $node->get('field_price')->value;
+ if (empty($price)) {
+ $this->logger->debug('No price, cannot setup new order');
+ return FALSE;
+ }
+
+ // FIXME: support v1 contract terms and use it
+ // if we have multiple currencies in $price!
+ // FIXME: add support for subscriptions
+ $fulfillment_url = $node->toUrl('canonical', ['absolute' => TRUE])->toString();
+ /* one day from now */
+ // FIXME: after Merchant v1.1 we can use the returned
+ // the expiration time and then rely on the default already set in
+ // the merchant backend instead of hard-coding 1 day here!
+ $order_expiration = time() + 60 * 60 * 24;
+ $order_data = [
+ 'order' => [
+ 'amount' => $price,
+ 'summary' => 'Access to: ' . $node->getTitle(),
+ 'fulfillment_url' => $fulfillment_url,
+ 'pay_deadline' => [
+ 't_s' => $order_expiration,
+ ],
+ ],
+ 'session_id' => session_id (),
+ 'create_token' => FALSE,
+ ];
+
+ try {
+ $http_client = \Drupal::httpClient();
+ $response = $http_client->post($backend_url . 'private/orders', [
+ '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:
+ case 201: /* 201 is not in-spec, but tolerated for now */
+ if (! isset($result['order_id'])) {
+ $this->logger->error('Failed to create order: HTTP success response unexpectedly lacks "order_id" field.');
+ return FALSE;
+ }
+ /* Success, handled below */
+ break;
+ case 403:
+ $this->logger->warning('Access denied by the merchant backend. Did your credentials change or expire? Check your Turnstile configuration!');
+ return FALSE;
+ case 404:
+ // FIXME: go into details on why we could get 404 here...
+ $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ $this->logger->error('Failed to create order: @hint (@ec): @body', ['@hint' => $result['hint'] ?? 'N/A', '@ec' => $result['code'] ?? 'N/A', '@body' => $body_log ?? 'N/A']);
+ return FALSE;
+ case 409:
+ case 410:
+ /* 409: We didn't specify an order, so this should be "wrong currency", which again Turnstile tries to prevent. So this shouldn't be possible. */
+ /* 410: We didn't specify a product, so out-of-stock should also be impossible for Turnstile */
+ $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ $this->logger->error('Unexpected HTTP status code @status trying to create order: @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 FALSE;
+ case 451:
+ /* KYC required, can happen, warn */
+ $this->logger->warning('Failed to create order as legitimization is required first. Please check legitimization status in your merchant backend.');
+ return FALSE;
+ default:
+ $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ $this->logger->error('Unexpected HTTP status code @status trying to create order: @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 FALSE;
+ } // end switch on HTTP status
+
+ $order_id = $result['order_id'];
+ return [
+ 'order_id' => $result['order_id'],
+ 'payment_url' => $backend_url . 'orders/' . $result['order_id'],
+ 'order_expiration' => $order_expiration,
+ 'paid' => FALSE,
+ ];
+ }
+ catch (RequestException $e) {
+ $body_log = json_encode($result ?? [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ $this->logger->error('Failed to create Taler order: @message: @body', ['@message' => $e->getMessage(), '@body' => $body_log ?? 'N/A']);
+ }
+
+ return FALSE;
+ }
+
+
+
+
+
}
\ No newline at end of file
diff --git a/turnstile.module b/turnstile.module
@@ -13,17 +13,6 @@ use GuzzleHttp\Exception\RequestException;
/**
- * Taler error codes used in this module. We do not define
- * the full list here as that would be excessive and could
- * just slow down PHP unnecessarily.
- */
-enum TalerErrorCode: int {
- case TALER_EC_NONE = 0;
- case TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN = 2000;
- case TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN = 2005;
-}
-
-/**
* Implements hook_entity_bundle_field_info_alter().
* Adds the TalerPriceListFormat constraint to 'field_price' fields.
* Probably can be removed entirely once we have only price categories.
@@ -179,7 +168,8 @@ function turnstile_entity_view_alter(array &$build, EntityInterface $entity, Ent
// Check order status with backend if not marked as paid.
\Drupal::logger('turnstile')->debug('Checking order status ...');
- $status = turnstile_check_order_status($order['order_id']);
+ $api_service = \Drupal::service('turnstile.api_service');
+ $status = $api_service->checkOrderStatus($order['order_id']);
if ($status && $status['paid']) {
// Update session with new status.
\Drupal::logger('turnstile')->debug('Updating order status to paid.');
@@ -335,7 +325,8 @@ function turnstile_create_or_get_order(NodeInterface $node) {
}
// Create new order
- $order_info = turnstile_create_order($node);
+ $api_service = \Drupal::service('turnstile.api_service');
+ $order_info = $api_service->createOrder($node);
if (! $order_info) {
return FALSE;
}
@@ -347,252 +338,6 @@ function turnstile_create_or_get_order(NodeInterface $node) {
/**
- * Check order status with Taler backend.
- *
- * @param string $order_id
- * The order ID to check.
- *
- * @return array|FALSE
- * Order status information or FALSE on failure.
- */
-function turnstile_check_order_status($order_id) {
- $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, cannot check order status!');
- return FALSE;
- }
-
- try {
- $http_client = \Drupal::httpClient();
- $response = $http_client->get($backend_url . 'private/orders/' . $order_id, [
- 'headers' => [
- 'Authorization' => 'Bearer ' . $access_token,
- ],
- // Do not throw exceptions on 4xx/5xx status codes
- 'http_errors' => false,
- ]);
-
- $http_status = $response->getStatusCode();
- $body = $response->getBody();
- $result = json_decode($body, TRUE);
- switch ($http_status)
- {
- case 200:
- // Success, handled below
- break;
- case 403:
- \Drupal::logger('turnstile')->warning('Access denied by the merchant backend. Did your credentials change or expire? Check your Turnstile configuration!');
- return FALSE;
- case 404:
- // Order unknown or instance unknown
- /** @var TalerErrorCode $ec */
- $ec = $result['code'] ?? TALER_EC_NONE;
- switch ($ec)
- {
- case TALER_EC_NONE:
- // Protocol violation. Could happen if the backend domain was
- // taken over by someone else.
- $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
- \Drupal::logger('turnstile')->error('Invalid response from merchant backend when trying to obtain order status. Check your Turnstile configuration! @body', ['@body' => $body_log ?? 'N/A']);
- return FALSE;
- case TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN:
- // This could happen if our instance was deleted after the configuration was
- // checked. Very bad, log serious error.
- \Drupal::logger('turnstile')->error('Configured instance "@detail" unknown to merchant backend. Check your Turnstile configuration!', ['@detail' => $result['detail'] ?? 'N/A']);
- return FALSE;
- case TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN:
- // This could happen if the instance owner manually deleted
- // an order while the customer was looking at the article.
- \Drupal::logger('turnstile')->warning('Order "@order" disappeared in the backend.', ['@order' => $order_id]);
- return FALSE;
- default:
- $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
- \Drupal::logger('turnstile')->error('Unexpected error code @ec with HTTP status code @status from Taler merchant backend when trying to get order status: @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 FALSE;
- }
- default:
- // Internal server errors and the like...
- $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
- \Drupal::logger('turnstile')->error('Unexpected HTTP status code @status from Taler merchant backend when trying to get order status: @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 FALSE;
- }
-
- $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
- \Drupal::logger('turnstile')->error('Got existing contract: @body', ['@body' => $body_log ?? 'N/A']);
-
- $order_status = $result['order_status'] ?? 'unknown';
- $subscription_expiration = 0;
- $pay_deadline = 0;
- $paid = FALSE;
- switch ($order_status)
- {
- case 'unpaid':
- // 'pay_deadline' is only available since v21 rev 1, so for now we
- // fall back to creation_time + offset.
- $pay_deadline = $result['pay_deadline']['t_s'] ??
- 60 * 60 * 24 + $result['creation_time']['t_s'] ?? 0;
- break;
- case 'claimed':
- $contract_terms = $result['contract_terms'];
- $pay_deadline = $contract_terms['pay_deadline']['t_s'] ?? 0;
- break;
- case 'paid':
- $paid = TRUE;
- $contract_terms = $result['contract_terms'];
- $contract_version = $result['version'] ?? 0;
- switch ($contract_version) {
- case 0:
- break;
- case 1:
- $choice_index = $result['choice_index'] ?? 0;
- $contract_choice = $contract_terms['choices'][$choice_index];
- $outputs = $contract_choice['outputs'];
- // FIXME: add logic to detect subscriptions here and
- // update $subscription_expiration if one was found!
- break;
- default:
- break;
- } // switch on contract version
- break;
- default:
- \Drupal::logger('turnstile')->error('Got unexpected order status "@status"', ['@status' => $order_status]);
- break;
- } // switch on $order_status
- return [
- 'order_id' => $order_id,
- 'paid' => $paid,
- 'subscription_expiration' => $subscription_expiration,
- 'order_expiration' => $pay_deadline,
- ];
- }
- catch (RequestException $e) {
- // Any kind of error that is outside of the spec.
- \Drupal::logger('turnstile')->error('Failed to check order status: @message', ['@message' => $e->getMessage()]);
- return FALSE;
- }
-}
-
-
-/**
- * Create a new Taler order.
- *
- * @param \Drupal\node\NodeInterface $node
- * The node to create an order for.
- *
- * @return array|FALSE
- * Order information or FALSE on failure.
- */
-function turnstile_create_order(NodeInterface $node) {
- $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 backend, cannot setup new order');
- return FALSE;
- }
-
- // FIXME: transition away from price to price categories...
- $price = $node->get('field_price')->value;
- if (empty($price)) {
- \Drupal::logger('turnstile')->debug('No price, cannot setup new order');
- return FALSE;
- }
-
- // FIXME: support v1 contract terms and use it
- // if we have multiple currencies in $price!
- // FIXME: add support for subscriptions
- $fulfillment_url = $node->toUrl('canonical', ['absolute' => TRUE])->toString();
- /* one day from now */
- // FIXME: after Merchant v1.1 we can use the returned
- // the expiration time and then rely on the default already set in
- // the merchant backend instead of hard-coding 1 day here!
- $order_expiration = time() + 60 * 60 * 24;
- $order_data = [
- 'order' => [
- 'amount' => $price,
- 'summary' => 'Access to: ' . $node->getTitle(),
- 'fulfillment_url' => $fulfillment_url,
- 'pay_deadline' => [
- 't_s' => $order_expiration,
- ],
- ],
- 'session_id' => session_id (),
- 'create_token' => FALSE,
- ];
-
- try {
- $http_client = \Drupal::httpClient();
- $response = $http_client->post($backend_url . 'private/orders', [
- '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:
- case 201: /* 201 is not in-spec, but tolerated for now */
- if (! isset($result['order_id'])) {
- \Drupal::logger('turnstile')->error('Failed to create order: HTTP success response unexpectedly lacks "order_id" field.');
- return FALSE;
- }
- /* Success, handled below */
- break;
- case 403:
- \Drupal::logger('turnstile')->warning('Access denied by the merchant backend. Did your credentials change or expire? Check your Turnstile configuration!');
- return FALSE;
- case 404:
- // FIXME: go into details on why we could get 404 here...
- $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
- \Drupal::logger('turnstile')->error('Failed to create order: @hint (@ec): @body', ['@hint' => $result['hint'] ?? 'N/A', '@ec' => $result['code'] ?? 'N/A', '@body' => $body_log ?? 'N/A']);
- return FALSE;
- case 409:
- case 410:
- /* 409: We didn't specify an order, so this should be "wrong currency", which again Turnstile tries to prevent. So this shouldn't be possible. */
- /* 410: We didn't specify a product, so out-of-stock should also be impossible for Turnstile */
- $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
- \Drupal::logger('turnstile')->error('Unexpected HTTP status code @status trying to create order: @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 FALSE;
- case 451:
- /* KYC required, can happen, warn */
- \Drupal::logger('turnstile')->warning('Failed to create order as legitimization is required first. Please check legitimization status in your merchant backend.');
- return FALSE;
- default:
- $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
- \Drupal::logger('turnstile')->error('Unexpected HTTP status code @status trying to create order: @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 FALSE;
- } // end switch on HTTP status
-
- $order_id = $result['order_id'];
- return [
- 'order_id' => $result['order_id'],
- 'payment_url' => $backend_url . 'orders/' . $result['order_id'],
- 'order_expiration' => $order_expiration,
- 'paid' => FALSE,
- ];
- }
- catch (RequestException $e) {
- $body_log = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
- \Drupal::logger('turnstile')->error('Failed to create Taler order: @message: @body', ['@message' => $e->getMessage(), '@body' => $body_log ?? 'N/A']);
- }
-
- return FALSE;
-}
-
-
-/**
* Implements hook_theme().
*/
function turnstile_theme() {