commit f082dfd7ab4a90bf410bf973228892693f729a15
parent 8c142162591487ca02173c3b9c1ac62205b26e7e
Author: Sebastian <sebasjm@gmail.com>
Date: Sun, 6 Jul 2025 00:30:49 -0300
fix #9511
Diffstat:
6 files changed, 114 insertions(+), 5 deletions(-)
diff --git a/packages/merchant-backoffice-ui/src/components/product/NonInventoryProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/NonInventoryProductForm.tsx
@@ -51,6 +51,7 @@ export function NonInventoryProductFrom({
if (result) {
setShowCreateProduct(false);
return onAddProduct({
+ product_name: result.product_name || "",
quantity: result.quantity || 0,
taxes: result.taxes || [],
description: result.description || "",
diff --git a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx
@@ -115,6 +115,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
const errors = undefinedIfEmpty({
product_id: !value.product_id ? i18n.str`Required` : undefined,
+ product_name: !value.product_name ? i18n.str`Required` : undefined,
description: !value.description ? i18n.str`Required` : undefined,
unit: !value.unit ? i18n.str`Required` : undefined,
price: !value.price
@@ -202,6 +203,11 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) {
tooltip={i18n.str`Illustration of the product for customers.`}
/>
<Input<Entity>
+ name="product_name"
+ label={i18n.str`Name`}
+ tooltip={i18n.str`Product name.`}
+ />
+ <Input<Entity>
name="description"
inputType="multiline"
label={i18n.str`Description`}
diff --git a/packages/merchant-backoffice-ui/src/components/product/ProductList.tsx b/packages/merchant-backoffice-ui/src/components/product/ProductList.tsx
@@ -37,7 +37,7 @@ export function ProductList({ list, actions = [] }: Props): VNode {
<i18n.Translate>Image</i18n.Translate>
</th>
<th>
- <i18n.Translate>Description</i18n.Translate>
+ <i18n.Translate>Name</i18n.Translate>
</th>
<th>
<i18n.Translate>Quantity</i18n.Translate>
@@ -71,7 +71,7 @@ export function ProductList({ list, actions = [] }: Props): VNode {
src={entry.image ? entry.image : emptyImage}
/>
</td>
- <td>{entry.description}</td>
+ <td>{entry.product_name}</td>
<td>
{entry.quantity === 0
? "--"
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx
@@ -808,6 +808,7 @@ export function CreatePage({
function asProduct(p: ProductAndQuantity): TalerMerchantApi.Product {
return {
product_id: p.product.id,
+ product_name: p.product.product_name,
image: p.product.image,
price: p.product.price,
unit: p.product.unit,
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx
@@ -152,6 +152,9 @@ function Table({
<i18n.Translate>Image</i18n.Translate>
</th>
<th>
+ <i18n.Translate>Name</i18n.Translate>
+ </th>
+ <th>
<i18n.Translate>Description</i18n.Translate>
</th>
<th>
@@ -223,6 +226,16 @@ function Table({
</td>
<td
class="has-tooltip-right"
+ data-tooltip={i.product_name}
+ onClick={() =>
+ rowSelection !== i.id && rowSelectionHandler(i.id)
+ }
+ style={{ cursor: "pointer" }}
+ >
+ {i.product_name}
+ </td>
+ <td
+ class="has-tooltip-right"
data-tooltip={i.description}
onClick={() =>
rowSelection !== i.id && rowSelectionHandler(i.id)
diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts
@@ -360,7 +360,11 @@ export interface AbortingCoin {
coin_pub: EddsaPublicKeyString;
// The amount to be refunded (matches the original contribution)
- contribution: AmountString;
+ // @deprecated since **v18**.
+ /**
+ * @deprecated
+ */
+ contribution?: AmountString;
// URL of the exchange this coin was withdrawn from.
exchange_url: string;
@@ -1604,16 +1608,71 @@ export interface InstanceAuthConfigurationMessage {
// "external": The mechant backend does not do
// any authentication checks. Instead an API
// gateway must do the authentication.
- // "token": The merchant checks an auth token.
+ // "token": (deprecated) The merchant checks an auth token.
// See "token" for details.
+ // Since **v19**: APIs use login tokens retrieved from the /private/token
+ // endpoint.
+ // See "password" for details.
method: MerchantAuthMethod;
- // For method "token", this field is mandatory.
+ // Deprecated: For method "token", this field is mandatory.
// The token MUST begin with the string "secret-token:".
// After the auth token has been set (with method "token"),
// the value must be provided in a "Authorization: Bearer $token"
// header.
token?: AccessToken;
+
+ // Since **v19**: For method "token", this field is mandatory.
+ // Authentication against the /private/token endpoint
+ // is done using basic authentication with the configured password
+ // in the "password" field. Tokens are passed to other endpoints for
+ // authorization using RFC 8959 bearer tokens.
+ password?: string;
+}
+
+export type LoginTokenScope =
+ | "readonly"
+ | "write"
+ | "all"
+ | "order-simple"
+ | "order-pos"
+ | "order-mgmt"
+ | "order-full";
+
+export interface LoginTokenRequest {
+ // Scope of the token (which kinds of operations it will allow)
+ scope: LoginTokenScope;
+
+ // Server may impose its own upper bound
+ // on the token validity duration
+ duration?: RelativeTime;
+
+ // Can this token be refreshed?
+ // Defaults to false. Deprecated since **v19**.
+ // Use ":refreshable" scope prefix instead.
+ refreshable?: boolean;
+}
+
+export interface LoginTokenSuccessResponse {
+ // @deprecated since v19. See access_token
+ // token: string;
+
+ // The login token that can be used to access resources
+ // that are in scope for some time. Must be prefixed
+ // with "Bearer " when used in the "Authorization" HTTP header.
+ // Will already begin with the RFC 8959 prefix.
+ // **Since v19**
+ access_token: AccessToken;
+
+ // Scope of the token (which kinds of operations it will allow)
+ scope: LoginTokenScope;
+
+ // Server may impose its own upper bound
+ // on the token validity duration
+ expiration: Timestamp;
+
+ // Can this token be refreshed?
+ refreshable: boolean;
}
export interface InstanceReconfigurationMessage {
@@ -2065,6 +2124,12 @@ export interface ProductAddDetail {
// Product ID to use.
product_id: string;
+ // Human-readable product name.
+ // Since API version **v20**. Optional only for
+ // backwards-compatibility, should be considered mandatory
+ // moving forward!
+ product_name: string;
+
// Human-readable product description.
description: string;
@@ -2109,6 +2174,12 @@ export interface ProductAddDetail {
}
export interface ProductPatchDetail {
+ // Human-readable product name.
+ // Since API version **v20**. Optional only for
+ // backwards-compatibility, should be considered mandatory
+ // moving forward!
+ product_name: string;
+
// Human-readable product description.
description: string;
@@ -2182,6 +2253,10 @@ export interface MerchantPosProductDetail {
// A merchant-internal unique identifier for the product
product_id?: string;
+ // Human-readable product name.
+ // Since API version **v20**.
+ product_name: string;
+
// A list of category IDs this product belongs to.
// Typically, a product only belongs to one category, but more than one is supported.
categories: number[];
@@ -2230,6 +2305,10 @@ export interface MerchantCategory {
}
export interface ProductDetail {
+ // Human-readable product name.
+ // Since API version **v20**.
+ product_name: string;
+
// Human-readable product description.
description: string;
@@ -3260,6 +3339,12 @@ export interface Product {
// Merchant-internal identifier for the product.
product_id?: string;
+ // Name of the product.
+ // Since API version **v20**. Optional only for
+ // backwards-compatibility, should be considered mandatory
+ // moving forward!
+ product_name: string;
+
// Human-readable product description.
description: string;
@@ -3731,6 +3816,7 @@ export const codecForMerchantPosProductDetail =
buildCodecForObject<MerchantPosProductDetail>()
.property("product_serial", codecForNumber())
.property("product_id", codecOptional(codecForString()))
+ .property("product_name", codecForString())
.property("categories", codecForList(codecForNumber()))
.property("description", codecForString())
.property("description_i18n", codecForInternationalizedString())
@@ -3761,6 +3847,7 @@ export const codecForProductDetail = (): Codec<ProductDetail> =>
.property("description", codecForString())
.property("description_i18n", codecForInternationalizedString())
.property("unit", codecForString())
+ .property("product_name", codecForString())
.property("price", codecForAmountString())
.property("image", codecForString())
.property("categories", codecForList(codecForNumber()))
@@ -3919,6 +4006,7 @@ export const codecForOrderOutputTaxReceipt = (): Codec<OrderOutputTaxReceipt> =>
export const codecForProduct = (): Codec<Product> =>
buildCodecForObject<Product>()
.property("product_id", codecOptional(codecForString()))
+ .property("product_name", codecForString())
.property("description", codecForString())
.property(
"description_i18n",