commit 067dc16866716539c4fd03284de9b64b190b7255
parent eda86e5f4883513a3586cc35ee355968d18c335a
Author: Christian Grothoff <christian@grothoff.org>
Date: Tue, 4 Aug 2026 22:22:32 +0200
introduce SessionID separate from Slug
Diffstat:
7 files changed, 55 insertions(+), 17 deletions(-)
diff --git a/core/api-common.rst b/core/api-common.rst
@@ -292,8 +292,8 @@ without any escaping. We call such an identifier a *slug*.
// Non-empty string that can be used as-is as a single path
// component of a URL. A slug must only use ASCII alphanumeric
- // characters ("A-Z", "a-z", "0-9") and the six punctuation
- // characters "-", ".", "_", ":", "=" and "~". Consequently a slug
+ // characters ("A-Z", "a-z", "0-9") and the five punctuation
+ // characters "-", ".", "_", ":" and "~". Consequently a slug
// never contains a "/" and never requires percent-encoding.
// The strings "." and ".." are not valid slugs, as they have a
// special meaning in URL paths.
@@ -302,12 +302,11 @@ without any escaping. We call such an identifier a *slug*.
// documented with the respective endpoint or field.
type Slug = string;
-A slug is thus the set of RFC 3986 *unreserved* characters, plus ``:`` and
-``=``. The character set is deliberately narrower than what RFC 3986 permits
-in a path segment: it excludes ``@`` and all sub-delimiters except ``=``,
-which are legal in a URL path but cause trouble once a slug is embedded in
-other syntax, such as a shell command, a query string or a filename. ``=`` is
-permitted because base64-encoded identifiers may end in padding.
+A slug is thus the set of RFC 3986 *unreserved* characters, plus ``:``. The
+character set is deliberately narrower than what RFC 3986 permits in a path
+segment: it excludes ``@`` and all sub-delimiters, which are legal in a URL
+path but cause trouble once a slug is embedded in other syntax, such as a
+shell command, a query string or a filename.
Note that a slug is *not* the same as an opaque identifier that merely happens
to be a string: identifiers that may contain arbitrary characters (and thus
diff --git a/core/api-merchant.rst b/core/api-merchant.rst
@@ -151,6 +151,39 @@ Examples:
Unavailabe endpoints (will return 404):
https://merchant-backend.example.com/instances/myinst/private/instances
+.. _merchant-session-id:
+
+-----------
+Session IDs
+-----------
+
+Payments can be bound to a *session*, which is identified by a session ID.
+Session IDs are chosen by the merchant's frontend (or by the client device,
+as is the case for the Paivana IDs given to
+:http:post:`[/instances/$INSTANCE]/templates/$TEMPLATE_ID`); the backend
+merely stores them and uses them to match a payment against the session it
+was made for.
+
+.. ts:def:: SessionID
+
+ // Identifier of the session a payment is bound to. Like a `Slug`,
+ // a session ID must only use ASCII alphanumeric characters
+ // ("A-Z", "a-z", "0-9") and the punctuation characters "-", ".",
+ // "_", ":" and "~", and must not be "." or "..". Unlike a slug, a
+ // session ID may also contain "=" and may be empty.
+ // Session IDs are case-sensitive.
+ type SessionID = string;
+
+A session ID is thus *not* a slug. It additionally permits ``=`` because
+session IDs are frequently base64-encoded identifiers, which may end in
+padding. And it may be empty: the empty session ID means that a payment is
+not bound to any session. Clients must expect to encounter it, as the
+backend itself puts an empty session ID into the ``taler://pay/`` URI of an
+order that has no session (the session is the last path component of that
+URI and thus cannot simply be omitted), and wallets send back what they were
+given. Endpoints for which only an actual session makes sense say so
+explicitly and reject the empty session ID.
+
-----------------
Generic Responses
-----------------
@@ -1117,7 +1150,8 @@ open or deferred.
// template, the total of the selected products for an
// "inventory-cart" template, and the amount of an editable choice
// for a "paivana" template. Requires "currency" to be given and
- // to match, and must not exceed "max_amount".
+ // to match, must be in the same currency as "max_amount", and
+ // must not exceed it.
// Since protocol **v34**.
min_amount?: Amount;
diff --git a/core/corebank/post-accounts.rst b/core/corebank/post-accounts.rst
@@ -13,7 +13,7 @@
// Must be at most 126 characters long. Implementations
// may further restrict the character set; libeufin-bank
// accepts only the RFC 3986 unreserved characters (that
- // is, a slug without ":" and "=").
+ // is, a slug without ":").
username: Slug;
// Password of the account used for authentication
diff --git a/core/merchant/post-orders-ORDER_ID-paid.rst b/core/merchant/post-orders-ORDER_ID-paid.rst
@@ -68,6 +68,8 @@
// database access).
h_contract: HashCode;
- // Session id for which the payment is proven.
- session_id: Slug;
+ // Session ID for which the payment is proven. Must not be
+ // empty: proving a payment only makes sense for an actual
+ // session.
+ session_id: SessionID;
}
diff --git a/core/merchant/post-orders-ORDER_ID-pay.rst b/core/merchant/post-orders-ORDER_ID-pay.rst
@@ -252,8 +252,10 @@
wallet_data?: PayWalletData;
// The session for which the payment is made (or replayed).
- // Only set for session-based payments.
- session_id?: Slug;
+ // Only set for session-based payments. The empty session ID
+ // is treated like the field being absent (wallets do send it
+ // that way).
+ session_id?: SessionID;
}
diff --git a/core/merchant/post-private-orders-ORDER_ID-collect.rst b/core/merchant/post-private-orders-ORDER_ID-collect.rst
@@ -78,7 +78,7 @@
// Session ID to store with the payment, like the ``session_id`` of
// a wallet payment. Allows a point-of-sale device to tag and later
// list the orders it collected. Absent for no session.
- session_id?: Slug;
+ session_id?: SessionID;
// For v1 contracts, the index of the choice to complete within
// the ``choices`` array of the contract terms. Mandatory for v1
diff --git a/core/merchant/post-private-orders.rst b/core/merchant/post-private-orders.rst
@@ -116,9 +116,10 @@
payment_target?: string;
// The session for which the payment is made (or replayed).
- // Only set for session-based payments.
+ // Only set for session-based payments. The empty session ID
+ // is treated like the field being absent.
// Since protocol **v6**.
- session_id?: Slug;
+ session_id?: SessionID;
// Specifies that some products are to be included in the
// order from the inventory. For these inventory management