commit c4ce5227b13fb241ed037f7d486aa91ef05c6a7b
parent 5c3313e86637c2bb8edc0b367e2c43a78c08e50b
Author: Sebastian <sebasjm@taler-systems.com>
Date: Fri, 24 Apr 2026 17:46:38 -0300
fix #11356
Diffstat:
7 files changed, 116 insertions(+), 21 deletions(-)
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/use/UsePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/use/UsePage.tsx
@@ -26,7 +26,7 @@ import {
TalerErrorCode,
TalerMerchantApi,
TemplateType,
- UsingTemplateDetails,
+ UsingTemplateDetailsRequest,
} from "@gnu-taler/taler-util";
import {
ButtonBetterBulma,
@@ -35,7 +35,7 @@ import {
useLocalNotificationBetter,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
-import { Fragment, VNode, h } from "preact";
+import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import {
FormErrors,
@@ -43,8 +43,8 @@ import {
} from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js";
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
-import { useSessionContext } from "../../../../context/session.js";
import { Tooltip } from "../../../../components/Tooltip.js";
+import { useSessionContext } from "../../../../context/session.js";
const TALER_SCREEN_ID = 66;
@@ -121,7 +121,8 @@ function UseFixedOrderPage({
/**
* If the template already has fields then do not send those.
*/
- const details: UsingTemplateDetails = {
+ const details: UsingTemplateDetailsRequest = {
+ template_type: TemplateType.FIXED_ORDER,
amount: template.template_contract.amount ? undefined : state.amount,
summary: template.template_contract.summary ? undefined : state.summary,
};
diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts
@@ -2473,11 +2473,11 @@ export class TalerMerchantInstanceHttpClient {
}
/**
- * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCES]-templates-$TEMPLATE_ID
+ * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-templates-$TEMPLATE_ID
*/
async useTemplateCreateOrder(
templateId: string,
- body: TalerMerchantApi.UsingTemplateDetails,
+ body: TalerMerchantApi.UsingTemplateDetailsRequest,
) {
const url = new URL(`templates/${templateId}`, this.baseUrl);
diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts
@@ -181,11 +181,17 @@ export namespace TalerUris {
export function createTalerPayTemplate(
merchantBaseUrl: HostPortPath,
templateId: string,
+ opts: {
+ sessionId?: string;
+ fulfillmenURL?: string;
+ } = {},
): PayTemplateUriResult {
return {
type: TalerUriAction.PayTemplate,
merchantBaseUrl,
templateId,
+ fulfillmentUrl: opts.fulfillmenURL,
+ sessionId: opts.sessionId,
};
}
export function createTalerRestore(
@@ -464,7 +470,7 @@ export namespace TalerUris {
> {
// check prefix
let isHttp = false;
- const prefixCheck = opts.ignoreUppercase ? s.toLowerCase() : s
+ const prefixCheck = opts.ignoreUppercase ? s.toLowerCase() : s;
if (
!prefixCheck.startsWith(TALER_PREFIX) &&
!(isHttp = prefixCheck.startsWith(TALER_HTTP_PREFIX))
@@ -484,7 +490,9 @@ export namespace TalerUris {
firstSlashPos === -1 ? path : path.slice(0, firstSlashPos)
) as TalerUriAction;
- const uriType = opts.ignoreUppercase ? uriTypeUncased.toLowerCase() as TalerUriAction : uriTypeUncased;
+ const uriType = opts.ignoreUppercase
+ ? (uriTypeUncased.toLowerCase() as TalerUriAction)
+ : uriTypeUncased;
if (!supported_targets[uriType]) {
return Result.errorWithDetail(TalerUriParseError.UNSUPPORTED, {
@@ -956,6 +964,8 @@ export interface PayTemplateUriResult {
type: TalerUriAction.PayTemplate;
merchantBaseUrl: HostPortPath;
templateId: string;
+ sessionId?: string;
+ fulfillmentUrl?: string;
}
export interface WithdrawUriResult {
@@ -1451,6 +1461,8 @@ export function parsePayTemplateUri(
type: TalerUriAction.PayTemplate,
merchantBaseUrl,
templateId,
+ fulfillmentUrl: q.get("fulfillment_url") ?? undefined,
+ sessionId: q.get("session_id") ?? undefined,
};
}
@@ -1803,8 +1815,13 @@ export function stringifyDevExperimentUri({
export function stringifyPayTemplateUri({
merchantBaseUrl,
templateId,
+ fulfillmentUrl,
+ sessionId,
}: Omit<PayTemplateUriResult, "type">): string {
- const { proto, path, query } = getUrlInfo(merchantBaseUrl);
+ const { proto, path, query } = getUrlInfo(merchantBaseUrl, {
+ session_id: sessionId,
+ fulfillment_url: fulfillmentUrl,
+ });
return `${proto}://pay-template/${path}${templateId}${query}`;
}
diff --git a/packages/taler-util/src/types-taler-common.ts b/packages/taler-util/src/types-taler-common.ts
@@ -269,11 +269,6 @@ export type HashCodeString = string;
export type WireSalt = string;
-export interface MerchantUsingTemplateDetails {
- summary?: string;
- amount?: AmountString;
-}
-
export type Integer = number;
export interface BankConversionInfoConfig {
diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts
@@ -3709,14 +3709,69 @@ export interface TemplateDetails {
// Since protocol **v13**.
required_currency?: string;
}
-export interface UsingTemplateDetails {
- // Summary of the template
+
+export type UsingTemplateDetailsRequest = (UsingTemplateFixedOrderRequest | UsingTemplateInventoryCartRequest | UsingTemplatePaivanaRequest) & UsingTemplateCommonRequest;
+
+export interface UsingTemplateCommonRequest {
+
+ // Type of the template being instantiated.
+ // Possible values include "fixed-order",
+ // "inventory-cart" and "paivana".
+ // Since protocol **v25**.
+ // Defaults to "fixed-order" while supporting previous
+ // protocol versions.
+ template_type: string;
+
+ // Summary to use in the contract. Only if
+ // not already specified by the template.
summary?: string;
- // The amount entered by the customer.
+ // The amount to be paid, including tip.
amount?: AmountString;
+
+ // Optional tip amount. Must match the currency of amount or the
+ // fixed template currency.
+ // Since protocol **v25**.
+ tip?: AmountString;
+
+}
+
+export interface UsingTemplateFixedOrderRequest {
+ template_type: TemplateType.FIXED_ORDER;
+
}
+export interface UsingTemplateInventoryCartRequest {
+ template_type: TemplateType.INVENTORY_CART;
+
+ // Inventory-cart: selected products and quantities.
+ // Since protocol **v25**.
+ inventory_selection?: InventorySelectionEntry[];
+}
+
+export interface InventorySelectionEntry {
+ // Inventory product to add.
+ product_id: string;
+
+ // Quantity in "<integer>[.<fraction>]" form using the product unit rules.
+ quantity: DecimalQuantity;
+}
+
+export interface UsingTemplatePaivanaRequest {
+ template_type: TemplateType.PAIVANA;
+
+ // Website to which access is being sold.
+ // Will become the fulfillment URL in the contract.
+ website: string;
+
+ // Client Paivana ID to grant access to.
+ // This becomes the "session_id" for session-based
+ // access control.
+ paivana_id: string;
+
+}
+
+
export interface WebhookAddDetails {
// Webhook ID to use.
webhook_id: string;
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -75,7 +75,6 @@ import {
MerchantContractTermsV1,
MerchantContractVersion,
MerchantPayResponse,
- MerchantUsingTemplateDetails,
NotificationType,
OrderShortInfo,
parsePayTemplateUri,
@@ -106,6 +105,8 @@ import {
TalerMerchantInstanceHttpClient,
TalerPreciseTimestamp,
TalerUriAction,
+ TalerUris,
+ TemplateType,
TokenUseSig,
Transaction,
TransactionAction,
@@ -115,6 +116,7 @@ import {
TransactionState,
TransactionType,
URL,
+ UsingTemplateDetailsRequest,
WalletNotification,
} from "@gnu-taler/taler-util";
import {
@@ -2215,18 +2217,44 @@ export async function preparePayForTemplate(
req: PreparePayTemplateRequest,
): Promise<PreparePayResult> {
const parsedUri = parsePayTemplateUri(req.talerPayTemplateUri);
+
if (!parsedUri) {
throw Error("invalid taler-template URI");
}
logger.trace(`parsed URI: ${j2s(parsedUri)}`);
- const templateDetails: MerchantUsingTemplateDetails = {};
-
const templateInfo = await downloadTemplate(
wex,
parsedUri.merchantBaseUrl,
parsedUri.templateId,
);
+ let templateDetails: UsingTemplateDetailsRequest;
+ switch (templateInfo.template_contract.template_type) {
+ case TalerMerchantApi.TemplateType.FIXED_ORDER: {
+ templateDetails = {
+ template_type: TemplateType.FIXED_ORDER,
+ };
+ break;
+ }
+
+ case TalerMerchantApi.TemplateType.INVENTORY_CART: {
+ templateDetails = {
+ template_type: TemplateType.INVENTORY_CART,
+ };
+ break;
+ }
+ case TalerMerchantApi.TemplateType.PAIVANA: {
+ templateDetails = {
+ template_type: TemplateType.PAIVANA,
+ paivana_id: parsedUri.sessionId!,
+ website: parsedUri.fulfillmentUrl!,
+ };
+ break;
+ }
+ default:
+ assertUnreachable(templateInfo.template_contract);
+ }
+
const templateParamsAmount = req.templateParams?.amount as
| AmountString
| undefined;
diff --git a/packages/taler-wallet-webextension/clean_and_build.sh b/packages/taler-wallet-webextension/clean_and_build.sh
@@ -14,5 +14,4 @@ echo testing...
pnpm test -- -R dot
echo packing...
-rm -rf extension/
./pack.sh dev