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:
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.');