turnstile

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

commit d391fabc513d2e9421b67e962723b9c935515dd0
parent 76b3a1c19be47be5ad5d50d03e007fa89152b21c
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 11 Oct 2025 22:25:59 +0200

get v1 contracts to work

Diffstat:
MREADME.md | 5++---
Msrc/Entity/TurnstilePriceCategory.php | 41+++++++++++++++++++++++++++++++++++++++++
Msrc/Form/PriceCategoryForm.php | 1-
Msrc/TalerMerchantApiService.php | 29++++++++++++++++++-----------
Mturnstile.module | 2+-
5 files changed, 62 insertions(+), 16 deletions(-)

diff --git a/README.md b/README.md @@ -46,14 +46,13 @@ Navigate to `/admin/config/content/Turnstile` to configure: ## TODO -- actually *use* price categories when determining article price! - => make v1 multi-currency orders work - actually get subscriptions to work: v1 contracts, etc. note that the logic is now more funky, customers could have a 50%-off subscription. So need to check if we have a subscription cookie set for a subscription that for the current category would give us a price of zero! - +- use order expiration from merchant backend (with new v1.1 implementation) + instead of hard-coding 1 day! ## How it Works diff --git a/src/Entity/TurnstilePriceCategory.php b/src/Entity/TurnstilePriceCategory.php @@ -95,6 +95,47 @@ class TurnstilePriceCategory extends ConfigEntityBase { } /** + * Return the different payment choices in a way suitable + * for GNU Taler v1 contracts. + * + * @return structure suitable for the choices array in the v1 contract + */ + public function getPaymentChoices() { + $choices = []; + foreach ($this->prices as $tokenFamilySlug => $currencyMap) { + foreach ($currencyMap as $currencyCode => $price) { + $inputs = []; + if ("%none%" !== $tokenFamilySlug) + { + $inputs[] = [ + 'type' => 'token', + 'token_family_slug' => $tokenFamilySlug, + 'count' => 1, + ]; + $description = 'Pay in ' . $currencyCode . 'with subscription'; + // FIXME: i18n!? + $description_i18n = NULL; + } + else + { + $description = 'Pay in ' . $currencyCode; + // FIXME: i18n!? + $description_i18n = NULL; + } + $choices[] = [ + 'amount' => 'KUDOS:1', + // 'amount' => $currencyCode . ':' . $price, + // 'description' => $description, + // 'description_i18n' => $description_i18n, + // 'inputs' => $inputs, + ]; + // FIXME: lacks choices which buy subscriptions! + } + } + return $choices; + } + + /** * Gets the price for a specific subscription and currency. * FIXME: just an example for now, to be removed! * diff --git a/src/Form/PriceCategoryForm.php b/src/Form/PriceCategoryForm.php @@ -163,7 +163,6 @@ class PriceCategoryForm extends EntityForm { // Filter out empty prices. $prices = $form_state->getValue('prices'); $filtered_prices = []; - foreach ($prices as $subscription_id => $currencies) { foreach ($currencies as $currency_code => $price) { if ($price !== '') { diff --git a/src/TalerMerchantApiService.php b/src/TalerMerchantApiService.php @@ -70,7 +70,7 @@ class TalerMerchantApiService { public function getSubscriptions() { // Per default, we always have "no subscription" as an option. $subscriptions = [ - ["id" => "", + ["id" => "%none%", "name" => "none", "label" => "No reduction" ], ]; @@ -86,8 +86,7 @@ class TalerMerchantApiService { try { $http_client = \Drupal::httpClient(); - $response = $http_client->post($backend_url . 'private/tokenfamilies', [ - 'json' => $order_data, + $response = $http_client->get($backend_url . 'private/tokenfamilies', [ 'headers' => [ 'Authorization' => 'Bearer ' . $access_token, 'Content-Type' => 'application/json', @@ -192,7 +191,7 @@ class TalerMerchantApiService { return array_map(function ($currency) { return [ - 'id' => $currency['currency'], + 'code' => $currency['currency'], 'name' => $currency['name'], 'label' => $currency['alt_unit_names'][0] ?? $currency['id'], 'step' => pow (0.1, $currency['num_fractional_input_digits'] ?? 2), @@ -362,19 +361,26 @@ class TalerMerchantApiService { return FALSE; } + /** @var \Drupal\Core\Field\EntityReferenceFieldItemList $field */ + $field = $node->get('field_turnstile_price_category'); + if ($field->isEmpty()) { + $this->logger->debug('No price category selected'); + return FALSE; + } + /** @var TurnstilePriceCategory $price_category */ - $price_category = $node->get('field_turnstile_price_category'); + $price_category = $field->entity; if (! $price_category) { $this->logger->debug('No price category, cannot setup new order'); return FALSE; } - // FIXME: map price category to price(s)! - $price = "KUDOS:1"; + $choices = $price_category->getPaymentChoices(); + if (empty ($choices)) { + $this->logger->debug('Price list is empty, 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 @@ -383,7 +389,8 @@ class TalerMerchantApiService { $order_expiration = time() + 60 * 60 * 24; $order_data = [ 'order' => [ - 'amount' => $price, + 'version' => 1, + 'choices' => $choices, 'summary' => 'Access to: ' . $node->getTitle(), 'fulfillment_url' => $fulfillment_url, 'pay_deadline' => [ diff --git a/turnstile.module b/turnstile.module @@ -112,7 +112,7 @@ function turnstile_entity_view_alter(array &$build, EntityInterface $entity, Ent } if (!$order_info) { \Drupal::logger('turnstile')->warning('Failed to setup order with Taler merchant backend!'); - + $config = \Drupal::config('turnstile.settings'); $grant_access_on_error = $config->get('grant_access_on_error') ?: true; if ($grant_access_on_error) { \Drupal::logger('turnstile')->debug('Could not setup order, disabling Turnstile.');