commit 7fe0c88e35e3cb9e3d8619f9334c64b71aa1b270
parent 062357e318d578aa024595cd5bd384fbfca520fa
Author: Florian Dold <florian@dold.me>
Date: Tue, 19 Aug 2025 21:37:40 +0200
wallet-core,cli: feature flag for v1 contracts, cli support for v1 contracts, remove deprecated requests
Diffstat:
7 files changed, 360 insertions(+), 272 deletions(-)
diff --git a/packages/taler-harness/src/harness/environments.ts b/packages/taler-harness/src/harness/environments.ts
@@ -158,6 +158,8 @@ export interface EnvOptions {
*/
forceLibeufin?: boolean;
+ walletConfig?: PartialWalletRunConfig;
+
additionalExchangeConfig?(e: ExchangeService): void;
additionalMerchantConfig?(m: MerchantService): void;
additionalBankConfig?(b: BankService): void;
@@ -291,32 +293,40 @@ export async function useSharedTestkudosEnvironment(t: GlobalTestState) {
await merchant.pingUntilAvailable();
if (!prevSetupDone) {
- const { accessToken: adminAccessToken } = await merchant.addInstanceWithWireAccount({
- id: "admin",
- name: "Default Instance",
- paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
- defaultWireTransferDelay: Duration.toTalerProtocolDuration(
- Duration.fromSpec({ minutes: 1 }),
- ),
- });
-
- await merchant.addInstanceWithWireAccount({
- id: "minst1",
- name: "minst1",
- paytoUris: [getTestHarnessPaytoForLabel("minst1")],
- defaultWireTransferDelay: Duration.toTalerProtocolDuration(
- Duration.fromSpec({ minutes: 1 }),
- ),
- }, { adminAccessToken });
+ const { accessToken: adminAccessToken } =
+ await merchant.addInstanceWithWireAccount({
+ id: "admin",
+ name: "Default Instance",
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
+
+ await merchant.addInstanceWithWireAccount(
+ {
+ id: "minst1",
+ name: "minst1",
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ },
+ { adminAccessToken },
+ );
}
const merchApi = new TalerMerchantInstanceHttpClient(
- merchant.makeInstanceBaseUrl()
- )
+ merchant.makeInstanceBaseUrl(),
+ );
const { access_token: merchantAdminAccessToken } = succeedOrThrow(
- await merchApi.createAccessToken("admin", MERCHANT_DEFAULT_AUTH.password, MERCHANT_DEFAULT_LOGIN_SCOPE)
- )
- return { merchantAdminAccessToken }
+ await merchApi.createAccessToken(
+ "admin",
+ MERCHANT_DEFAULT_AUTH.password,
+ MERCHANT_DEFAULT_LOGIN_SCOPE,
+ ),
+ );
+ return { merchantAdminAccessToken };
};
await bankStart();
@@ -328,7 +338,7 @@ export async function useSharedTestkudosEnvironment(t: GlobalTestState) {
walletStartProm,
]);
- const merchantAdminAccessToken = res[1].merchantAdminAccessToken
+ const merchantAdminAccessToken = res[1].merchantAdminAccessToken;
const walletClient = res[3].walletClient;
const walletService = res[3].walletService;
@@ -432,23 +442,27 @@ export async function createSimpleTestkudosEnvironmentV2(
await merchant.start();
await merchant.pingUntilAvailable();
- const { accessToken: adminAccessToken } = await merchant.addInstanceWithWireAccount({
- id: "admin",
- name: "Default Instance",
- paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
- defaultWireTransferDelay: Duration.toTalerProtocolDuration(
- Duration.fromSpec({ minutes: 1 }),
- ),
- });
+ const { accessToken: adminAccessToken } =
+ await merchant.addInstanceWithWireAccount({
+ id: "admin",
+ name: "Default Instance",
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
- await merchant.addInstanceWithWireAccount({
- id: "minst1",
- name: "minst1",
- paytoUris: [getTestHarnessPaytoForLabel("minst1")],
- defaultWireTransferDelay: Duration.toTalerProtocolDuration(
- Duration.fromSpec({ minutes: 1 }),
- ),
- }, { adminAccessToken });
+ await merchant.addInstanceWithWireAccount(
+ {
+ id: "minst1",
+ name: "minst1",
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ },
+ { adminAccessToken },
+ );
const { walletClient, walletService } = await createWalletDaemonWithClient(
t,
@@ -589,23 +603,27 @@ export async function createSimpleTestkudosEnvironmentV3(
await merchant.start();
await merchant.pingUntilAvailable();
- const { accessToken: merchantAdminAccessToken } = await merchant.addInstanceWithWireAccount({
- id: "admin",
- name: "Default Instance",
- paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
- defaultWireTransferDelay: Duration.toTalerProtocolDuration(
- Duration.fromSpec({ minutes: 1 }),
- ),
- });
+ const { accessToken: merchantAdminAccessToken } =
+ await merchant.addInstanceWithWireAccount({
+ id: "admin",
+ name: "Default Instance",
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
- await merchant.addInstanceWithWireAccount({
- id: "minst1",
- name: "minst1",
- paytoUris: [getTestHarnessPaytoForLabel("minst1")],
- defaultWireTransferDelay: Duration.toTalerProtocolDuration(
- Duration.fromSpec({ minutes: 1 }),
- ),
- }, { adminAccessToken: merchantAdminAccessToken });
+ await merchant.addInstanceWithWireAccount(
+ {
+ id: "minst1",
+ name: "minst1",
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ },
+ { adminAccessToken: merchantAdminAccessToken },
+ );
const { walletClient, walletService } = await createWalletDaemonWithClient(
t,
@@ -613,6 +631,7 @@ export async function createSimpleTestkudosEnvironmentV3(
name: "wallet",
persistent: true,
emitObservabilityEvents: !!opts.walletTestObservability,
+ config: opts.walletConfig,
},
);
@@ -772,17 +791,21 @@ export async function createFaultInjectedMerchantTestkudosEnvironment(
await merchant.start();
await merchant.pingUntilAvailable();
- const { accessToken: adminAccessToken } = await merchant.addInstanceWithWireAccount({
- id: "admin",
- name: "Default Instance",
- paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
- });
+ const { accessToken: adminAccessToken } =
+ await merchant.addInstanceWithWireAccount({
+ id: "admin",
+ name: "Default Instance",
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
+ });
- await merchant.addInstanceWithWireAccount({
- id: "minst1",
- name: "minst1",
- paytoUris: [getTestHarnessPaytoForLabel("minst1")],
- }, { adminAccessToken });
+ await merchant.addInstanceWithWireAccount(
+ {
+ id: "minst1",
+ name: "minst1",
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
+ },
+ { adminAccessToken },
+ );
console.log("setup done!");
@@ -953,7 +976,10 @@ export async function makeTestPaymentV2(
);
let orderStatus = succeedOrThrow(
- await merchantClient.getOrderDetails(merchantAdminAccessToken, orderResp.order_id),
+ await merchantClient.getOrderDetails(
+ merchantAdminAccessToken,
+ orderResp.order_id,
+ ),
);
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -980,7 +1006,10 @@ export async function makeTestPaymentV2(
// Check if payment was successful.
orderStatus = succeedOrThrow(
- await merchantClient.getOrderDetails(merchantAdminAccessToken, orderResp.order_id),
+ await merchantClient.getOrderDetails(
+ merchantAdminAccessToken,
+ orderResp.order_id,
+ ),
);
t.assertDeepEqual(orderStatus.order_status, "paid");
@@ -1247,23 +1276,27 @@ export async function createKycTestkudosEnvironment(
await merchant.start();
- const { accessToken: adminAccessToken } = await merchant.addInstanceWithWireAccount({
- id: "admin",
- name: "Default Instance",
- paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
- defaultWireTransferDelay: Duration.toTalerProtocolDuration(
- Duration.fromSpec({ minutes: 1 }),
- ),
- });
+ const { accessToken: adminAccessToken } =
+ await merchant.addInstanceWithWireAccount({
+ id: "admin",
+ name: "Default Instance",
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
- await merchant.addInstanceWithWireAccount({
- id: "minst1",
- name: "minst1",
- paytoUris: [getTestHarnessPaytoForLabel("minst1")],
- defaultWireTransferDelay: Duration.toTalerProtocolDuration(
- Duration.fromSpec({ minutes: 1 }),
- ),
- }, { adminAccessToken });
+ await merchant.addInstanceWithWireAccount(
+ {
+ id: "minst1",
+ name: "minst1",
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ },
+ { adminAccessToken },
+ );
const exchangeBankAccount: HarnessExchangeBankAccount = {
wireGatewayAuth: {
@@ -1333,7 +1366,7 @@ export async function registerHarnessBankTestUser(
const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase();
const createRes = succeedOrThrow(
- await bankClient.createAccount(/**/undefined, {
+ await bankClient.createAccount(/**/ undefined, {
name: username,
username,
password,
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-tokens.ts b/packages/taler-harness/src/integrationtests/test-wallet-tokens.ts
@@ -18,12 +18,6 @@
* Imports.
*/
import {
- createSimpleTestkudosEnvironmentV3,
- withdrawViaBankV3,
-} from "harness/environments.js";
-import { GlobalTestState } from "../harness/harness.js";
-import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import {
AbsoluteTime,
ChoiceSelectionDetailType,
Duration,
@@ -37,11 +31,33 @@ import {
TokenAvailabilityHint,
TokenFamilyKind,
} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import {
+ createSimpleTestkudosEnvironmentV3,
+ withdrawViaBankV3,
+} from "harness/environments.js";
+import { defaultCoinConfig } from "../harness/denomStructures.js";
+import { GlobalTestState } from "../harness/harness.js";
import { logger } from "./test-tops-challenger-twice.js";
export async function runWalletTokensTest(t: GlobalTestState) {
- let { bankClient, exchange, merchant, walletClient, merchantAdminAccessToken } =
- await createSimpleTestkudosEnvironmentV3(t);
+ let {
+ bankClient,
+ exchange,
+ merchant,
+ walletClient,
+ merchantAdminAccessToken,
+ } = await createSimpleTestkudosEnvironmentV3(
+ t,
+ defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ {
+ walletConfig: {
+ features: {
+ enableV1Contracts: true,
+ },
+ },
+ },
+ );
const merchantApi = new TalerMerchantInstanceHttpClient(
merchant.makeInstanceBaseUrl(),
@@ -67,14 +83,14 @@ export async function runWalletTokensTest(t: GlobalTestState) {
valid_before: AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.addDuration(
AbsoluteTime.now(),
- Duration.fromSpec({years: 1}),
+ Duration.fromSpec({ years: 1 }),
),
),
duration: Duration.toTalerProtocolDuration(
- Duration.fromSpec({days: 90}),
+ Duration.fromSpec({ days: 90 }),
),
validity_granularity: Duration.toTalerProtocolDuration(
- Duration.fromSpec({days: 1}),
+ Duration.fromSpec({ days: 1 }),
),
}),
);
@@ -90,14 +106,14 @@ export async function runWalletTokensTest(t: GlobalTestState) {
valid_before: AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.addDuration(
AbsoluteTime.now(),
- Duration.fromSpec({years: 1}),
+ Duration.fromSpec({ years: 1 }),
),
),
duration: Duration.toTalerProtocolDuration(
- Duration.fromSpec({days: 90}),
+ Duration.fromSpec({ days: 90 }),
),
validity_granularity: Duration.toTalerProtocolDuration(
- Duration.fromSpec({days: 1}),
+ Duration.fromSpec({ days: 1 }),
),
}),
);
@@ -109,24 +125,28 @@ export async function runWalletTokensTest(t: GlobalTestState) {
pay_deadline: AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.addDuration(
AbsoluteTime.now(),
- Duration.fromSpec({days: 1}),
+ Duration.fromSpec({ days: 1 }),
),
),
choices: [
{
amount: "TESTKUDOS:2",
inputs: [],
- outputs: [{
- type: OrderOutputType.Token,
- token_family_slug: "test_discount",
- }],
+ outputs: [
+ {
+ type: OrderOutputType.Token,
+ token_family_slug: "test_discount",
+ },
+ ],
},
{
amount: "TESTKUDOS:1",
- inputs: [{
- type: OrderInputType.Token,
- token_family_slug: "test_discount",
- }],
+ inputs: [
+ {
+ type: OrderInputType.Token,
+ token_family_slug: "test_discount",
+ },
+ ],
outputs: [],
},
],
@@ -139,34 +159,40 @@ export async function runWalletTokensTest(t: GlobalTestState) {
pay_deadline: AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.addDuration(
AbsoluteTime.now(),
- Duration.fromSpec({days: 1}),
+ Duration.fromSpec({ days: 1 }),
),
),
choices: [
{
amount: "TESTKUDOS:10",
inputs: [],
- outputs: [{
- type: OrderOutputType.Token,
- token_family_slug: "test_subscription",
- count: 1,
- }],
+ outputs: [
+ {
+ type: OrderOutputType.Token,
+ token_family_slug: "test_subscription",
+ count: 1,
+ },
+ ],
},
{
amount: "TESTKUDOS:0",
- inputs: [{
- type: OrderInputType.Token,
- token_family_slug: "test_subscription",
- count: 1,
- }],
- outputs: [{
- type: OrderOutputType.Token,
- token_family_slug: "test_subscription",
- count: 1,
- }],
+ inputs: [
+ {
+ type: OrderInputType.Token,
+ token_family_slug: "test_subscription",
+ count: 1,
+ },
+ ],
+ outputs: [
+ {
+ type: OrderOutputType.Token,
+ token_family_slug: "test_subscription",
+ count: 1,
+ },
+ ],
},
],
- }
+ };
{
logger.info("Payment with discount token output...");
@@ -179,7 +205,10 @@ export async function runWalletTokensTest(t: GlobalTestState) {
);
let orderStatus = succeedOrThrow(
- await merchantApi.getOrderDetails(merchantAdminAccessToken, orderResp.order_id),
+ await merchantApi.getOrderDetails(
+ merchantAdminAccessToken,
+ orderResp.order_id,
+ ),
);
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -221,7 +250,10 @@ export async function runWalletTokensTest(t: GlobalTestState) {
);
let orderStatus = succeedOrThrow(
- await merchantApi.getOrderDetails(merchantAdminAccessToken, orderResp.order_id),
+ await merchantApi.getOrderDetails(
+ merchantAdminAccessToken,
+ orderResp.order_id,
+ ),
);
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -233,7 +265,7 @@ export async function runWalletTokensTest(t: GlobalTestState) {
WalletApiOperation.PreparePayForUri,
{
talerPayUri,
- }
+ },
);
t.assertTrue(
@@ -241,16 +273,20 @@ export async function runWalletTokensTest(t: GlobalTestState) {
);
{
- const choicesRes = await walletClient.call(WalletApiOperation.GetChoicesForPayment, {
- transactionId: preparePayResult.transactionId,
- });
+ const choicesRes = await walletClient.call(
+ WalletApiOperation.GetChoicesForPayment,
+ {
+ transactionId: preparePayResult.transactionId,
+ },
+ );
t.assertTrue(choicesRes.defaultChoiceIndex === 1);
t.assertTrue(choicesRes.automaticExecution === false);
t.assertTrue(choicesRes.automaticExecutableIndex === undefined);
- t.assertTrue(choicesRes.choices[choiceIndex].status ===
- ChoiceSelectionDetailType.PaymentPossible,
+ t.assertTrue(
+ choicesRes.choices[choiceIndex].status ===
+ ChoiceSelectionDetailType.PaymentPossible,
);
const tokenDetails = choicesRes.choices[choiceIndex].tokenDetails;
@@ -286,7 +322,10 @@ export async function runWalletTokensTest(t: GlobalTestState) {
);
let orderStatus = succeedOrThrow(
- await merchantApi.getOrderDetails(merchantAdminAccessToken, orderResp.order_id),
+ await merchantApi.getOrderDetails(
+ merchantAdminAccessToken,
+ orderResp.order_id,
+ ),
);
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -306,16 +345,20 @@ export async function runWalletTokensTest(t: GlobalTestState) {
);
{
- const choicesRes = await walletClient.call(WalletApiOperation.GetChoicesForPayment, {
- transactionId: preparePayResult.transactionId,
- });
+ const choicesRes = await walletClient.call(
+ WalletApiOperation.GetChoicesForPayment,
+ {
+ transactionId: preparePayResult.transactionId,
+ },
+ );
t.assertTrue(choicesRes.defaultChoiceIndex === 0);
t.assertTrue(choicesRes.automaticExecution === false);
t.assertTrue(choicesRes.automaticExecutableIndex === undefined);
- t.assertTrue(choicesRes.choices[choiceIndex].status ===
- ChoiceSelectionDetailType.InsufficientBalance,
+ t.assertTrue(
+ choicesRes.choices[choiceIndex].status ===
+ ChoiceSelectionDetailType.InsufficientBalance,
);
const tokenDetails = choicesRes.choices[choiceIndex].tokenDetails;
@@ -325,8 +368,10 @@ export async function runWalletTokensTest(t: GlobalTestState) {
for (const tf in tokenDetails?.perTokenFamily ?? t.fail()) {
t.assertTrue(tokenDetails?.perTokenFamily[tf].available === 0);
t.assertTrue(tokenDetails?.perTokenFamily[tf].requested === 1);
- t.assertTrue(tokenDetails?.perTokenFamily[tf].causeHint ===
- TokenAvailabilityHint.WalletTokensAvailableInsufficient);
+ t.assertTrue(
+ tokenDetails?.perTokenFamily[tf].causeHint ===
+ TokenAvailabilityHint.WalletTokensAvailableInsufficient,
+ );
break;
}
}
@@ -347,7 +392,9 @@ export async function runWalletTokensTest(t: GlobalTestState) {
}
{
- logger.info("Payment with subscription token input and output, insufficient balance...");
+ logger.info(
+ "Payment with subscription token input and output, insufficient balance...",
+ );
const choiceIndex = 1;
const orderResp = succeedOrThrow(
@@ -357,7 +404,10 @@ export async function runWalletTokensTest(t: GlobalTestState) {
);
let orderStatus = succeedOrThrow(
- await merchantApi.getOrderDetails(merchantAdminAccessToken, orderResp.order_id),
+ await merchantApi.getOrderDetails(
+ merchantAdminAccessToken,
+ orderResp.order_id,
+ ),
);
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -369,7 +419,7 @@ export async function runWalletTokensTest(t: GlobalTestState) {
WalletApiOperation.PreparePayForUri,
{
talerPayUri,
- }
+ },
);
t.assertTrue(
@@ -377,16 +427,20 @@ export async function runWalletTokensTest(t: GlobalTestState) {
);
{
- const choicesRes = await walletClient.call(WalletApiOperation.GetChoicesForPayment, {
- transactionId: preparePayResult.transactionId,
- });
+ const choicesRes = await walletClient.call(
+ WalletApiOperation.GetChoicesForPayment,
+ {
+ transactionId: preparePayResult.transactionId,
+ },
+ );
t.assertTrue(choicesRes.defaultChoiceIndex === 0);
t.assertTrue(choicesRes.automaticExecution === false);
t.assertTrue(choicesRes.automaticExecutableIndex === choiceIndex);
- t.assertTrue(choicesRes.choices[choiceIndex].status ===
- ChoiceSelectionDetailType.InsufficientBalance,
+ t.assertTrue(
+ choicesRes.choices[choiceIndex].status ===
+ ChoiceSelectionDetailType.InsufficientBalance,
);
const tokenDetails = choicesRes.choices[choiceIndex].tokenDetails;
@@ -396,8 +450,10 @@ export async function runWalletTokensTest(t: GlobalTestState) {
for (const tf in tokenDetails?.perTokenFamily ?? t.fail()) {
t.assertTrue(tokenDetails?.perTokenFamily[tf].available === 0);
t.assertTrue(tokenDetails?.perTokenFamily[tf].requested === 1);
- t.assertTrue(tokenDetails?.perTokenFamily[tf].causeHint ===
- TokenAvailabilityHint.WalletTokensAvailableInsufficient);
+ t.assertTrue(
+ tokenDetails?.perTokenFamily[tf].causeHint ===
+ TokenAvailabilityHint.WalletTokensAvailableInsufficient,
+ );
break;
}
}
@@ -414,7 +470,10 @@ export async function runWalletTokensTest(t: GlobalTestState) {
);
let orderStatus = succeedOrThrow(
- await merchantApi.getOrderDetails(merchantAdminAccessToken, orderResp.order_id),
+ await merchantApi.getOrderDetails(
+ merchantAdminAccessToken,
+ orderResp.order_id,
+ ),
);
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -456,7 +515,10 @@ export async function runWalletTokensTest(t: GlobalTestState) {
);
let orderStatus = succeedOrThrow(
- await merchantApi.getOrderDetails(merchantAdminAccessToken, orderResp.order_id),
+ await merchantApi.getOrderDetails(
+ merchantAdminAccessToken,
+ orderResp.order_id,
+ ),
);
t.assertTrue(orderStatus.order_status === "unpaid");
@@ -468,7 +530,7 @@ export async function runWalletTokensTest(t: GlobalTestState) {
WalletApiOperation.PreparePayForUri,
{
talerPayUri,
- }
+ },
);
t.assertTrue(
@@ -476,16 +538,20 @@ export async function runWalletTokensTest(t: GlobalTestState) {
);
{
- const choicesRes = await walletClient.call(WalletApiOperation.GetChoicesForPayment, {
- transactionId: preparePayResult.transactionId,
- });
+ const choicesRes = await walletClient.call(
+ WalletApiOperation.GetChoicesForPayment,
+ {
+ transactionId: preparePayResult.transactionId,
+ },
+ );
t.assertTrue(choicesRes.defaultChoiceIndex === choiceIndex);
t.assertTrue(choicesRes.automaticExecution === true);
t.assertTrue(choicesRes.automaticExecutableIndex === choiceIndex);
- t.assertTrue(choicesRes.choices[choiceIndex].status ===
- ChoiceSelectionDetailType.PaymentPossible,
+ t.assertTrue(
+ choicesRes.choices[choiceIndex].status ===
+ ChoiceSelectionDetailType.PaymentPossible,
);
const tokenDetails = choicesRes.choices[choiceIndex].tokenDetails;
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
@@ -2338,16 +2338,6 @@ export const codecForForgetBankAccount = (): Codec<ForgetBankAccountRequest> =>
.property("bankAccountId", codecForString())
.build("ForgetBankAccountsRequest");
-export interface GetContractTermsDetailsRequest {
- transactionId: string;
-}
-
-export const codecForGetContractTermsDetails =
- (): Codec<GetContractTermsDetailsRequest> =>
- buildCodecForObject<GetContractTermsDetailsRequest>()
- .property("transactionId", codecForString())
- .build("GetContractTermsDetails");
-
export interface PreparePayRequest {
talerPayUri: string;
}
diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts
@@ -24,6 +24,7 @@ import {
Amounts,
AmountString,
assertUnreachable,
+ ChoiceSelectionDetailType,
codecForList,
codecForString,
ContractTermsUtil,
@@ -113,6 +114,7 @@ async function doPay(
processExit(1);
return;
}
+ let choiceIndex: number | undefined;
if (result.status === PreparePayResultType.AlreadyConfirmed) {
if (result.paid) {
console.log("already paid!");
@@ -121,42 +123,74 @@ async function doPay(
}
processExit(0);
return;
- }
- if (result.status === "payment-possible") {
+ } else if (result.status === PreparePayResultType.ChoiceSelection) {
+ console.log(`choices:`);
+ const choices = await wallet.call(WalletApiOperation.GetChoicesForPayment, {
+ transactionId: result.transactionId,
+ });
+ console.log(j2s(choices));
+ choiceIndex = await askChoice(choices.choices.length);
+ const myChoice = choices.choices[choiceIndex];
+ if (myChoice.status !== ChoiceSelectionDetailType.PaymentPossible) {
+ console.log("insufficient balance for choice");
+ processExit(1);
+ }
console.log("paying ...");
+ console.log("contract", result.contractTerms);
+ console.log("raw amount:", myChoice.amountRaw);
+ console.log("effective amount:", myChoice.amountEffective);
+ } else if (result.status === "payment-possible") {
+ console.log("paying ...");
+ console.log("contract", result.contractTerms);
+ console.log("raw amount:", result.amountRaw);
+ console.log("effective amount:", result.amountEffective);
} else {
throw Error("not reached");
}
- console.log("contract", result.contractTerms);
- console.log("raw amount:", result.amountRaw);
- console.log("effective amount:", result.amountEffective);
- let pay;
+ let pay: boolean;
if (options.alwaysYes) {
pay = true;
} else {
- while (true) {
- const yesNoResp = (await clk.prompt("Pay? [Y/n]")).toLowerCase();
- if (yesNoResp === "" || yesNoResp === "y" || yesNoResp === "yes") {
- pay = true;
- break;
- } else if (yesNoResp === "n" || yesNoResp === "no") {
- pay = false;
- break;
- } else {
- console.log("please answer y/n");
- }
- }
+ pay = await askYesNo();
}
if (pay) {
await wallet.call(WalletApiOperation.ConfirmPay, {
transactionId: result.transactionId,
+ choiceIndex: choiceIndex,
});
} else {
console.log("not paying");
}
}
+async function askChoice(n: number): Promise<number> {
+ while (true) {
+ const choice = Number.parseInt(
+ await clk.prompt(`Select choice (0-${n - 1}):`),
+ );
+ if (choice >= 0 && choice < n) {
+ return choice;
+ } else {
+ console.log("Please enter a valid choice.");
+ }
+ }
+}
+
+async function askYesNo(): Promise<boolean> {
+ while (true) {
+ const yesNoResp = (await clk.prompt("Pay? [Y/n]")).toLowerCase();
+ if (yesNoResp === "" || yesNoResp === "y" || yesNoResp === "yes") {
+ return true;
+ } else if (yesNoResp === "n" || yesNoResp === "no") {
+ return false;
+ break;
+ } else {
+ console.log("please answer y/n");
+ }
+ }
+}
+
function applyVerbose(verbose: boolean): void {
// TODO
}
@@ -175,6 +209,9 @@ export const walletCli = clk
.maybeOption("walletDbFile", ["--wallet-db"], clk.STRING, {
help: "Location of the wallet database file",
})
+ .maybeOption("features", ["--features"], clk.STRING, {
+ help: "Comma-separated list of feature flags to enable.",
+ })
.maybeOption("walletConnection", ["--wallet-connection"], clk.STRING, {
help: "Connect to an RPC wallet",
})
@@ -278,6 +315,8 @@ async function createLocalWallet(
applyVerbose(walletCliArgs.wallet.verbose);
const res = { wallet: wh.wallet };
+ const features = (walletCliArgs.wallet.features ?? "").split(",");
+
if (args.noInit) {
return res;
}
@@ -293,6 +332,11 @@ async function createLocalWallet(
emitObservabilityEvents: observabilityEventFile != null,
skipDefaults: walletCliArgs.wallet.skipDefaults,
},
+ features: {
+ enableV1Contracts: features.includes("enableV1Contracts")
+ ? true
+ : undefined,
+ },
},
} satisfies InitRequest,
);
@@ -568,12 +612,14 @@ transactionsCli
.requiredArgument("transactionId", clk.STRING, {
help: "Identifier of the transaction to delete",
})
+ .flag("includeContractTerms", ["--include-contract-terms"])
.action(async (args) => {
await withWallet(args, { lazyTaskLoop: true }, async (wallet) => {
const tx = await wallet.client.call(
WalletApiOperation.GetTransactionById,
{
transactionId: args.lookup.transactionId,
+ includeContractTerms: args.lookup.includeContractTerms ?? false,
},
);
console.log(j2s(tx));
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -340,6 +340,17 @@ export class PayMerchantTransactionContext implements TransactionContext {
];
}
+ let contractTerms: MerchantContractTerms | undefined;
+ if (req?.includeContractTerms) {
+ if (!this.wex.ws.config.features.enableV1Contracts) {
+ contractTerms = ContractTermsUtil.downgradeContractTerms(
+ download.contractTerms,
+ );
+ } else {
+ contractTerms = download.contractTerms;
+ }
+ }
+
return {
type: TransactionType.Payment,
txState,
@@ -362,9 +373,7 @@ export class PayMerchantTransactionContext implements TransactionContext {
}),
abortReason: purchaseRec.abortReason,
info,
- contractTerms: req?.includeContractTerms
- ? download.contractTermsRaw
- : undefined,
+ contractTerms,
refundQueryActive:
purchaseRec.purchaseStatus === PurchaseStatus.PendingQueryingRefund,
...(payRetryRec?.lastError ? { error: payRetryRec.lastError } : {}),
@@ -1885,9 +1894,10 @@ async function checkPaymentByProposalId(
}
}
}
- const contractDl = await expectProposalDownload(wex, proposal);
- const contractTerms = contractDl.contractTerms;
-
+ let { contractTerms, contractTermsHash } = await expectProposalDownload(
+ wex,
+ proposal,
+ );
proposalId = proposal.proposalId;
const ctx = new PayMerchantTransactionContext(wex, proposalId);
@@ -1916,13 +1926,27 @@ async function checkPaymentByProposalId(
purchase.purchaseStatus === PurchaseStatus.DialogShared
) {
if (contractTerms.version === MerchantContractVersion.V1) {
- return {
- status: PreparePayResultType.ChoiceSelection,
- transactionId,
- contractTerms: contractTerms,
- contractTermsHash: contractDl.contractTermsHash,
- talerUri,
- };
+ if (!wex.ws.config.features.enableV1Contracts) {
+ let v0Contract =
+ ContractTermsUtil.downgradeContractTerms(contractTerms);
+ logger.warn(
+ `contract uses v1 features, trying to downgrade, as enableV1Contracts is false`,
+ );
+ if (!v0Contract) {
+ throw Error(
+ "payment not possible (uses v1 contract features and downgrade failed)",
+ );
+ }
+ contractTerms = v0Contract;
+ } else {
+ return {
+ status: PreparePayResultType.ChoiceSelection,
+ transactionId,
+ contractTerms: contractTerms,
+ contractTermsHash: contractTermsHash,
+ talerUri,
+ };
+ }
}
const instructedAmount = Amounts.parseOrThrow(contractTerms.amount);
@@ -1995,7 +2019,7 @@ async function checkPaymentByProposalId(
amountEffective: Amounts.stringify(totalCost),
amountRaw: Amounts.stringify(instructedAmount),
scopes,
- contractTermsHash: contractDl.contractTermsHash,
+ contractTermsHash,
talerUri,
};
}
@@ -2109,24 +2133,6 @@ function isPurchasePaid(purchase: PurchaseRecord): boolean {
);
}
-export async function getContractTermsDetails(
- wex: WalletExecutionContext,
- proposalId: string,
-): Promise<DownloadedContractData> {
- const proposal = await wex.db.runReadOnlyTx(
- { storeNames: ["purchases"] },
- async (tx) => {
- return tx.purchases.get(proposalId);
- },
- );
-
- if (!proposal) {
- throw Error(`proposal with id ${proposalId} not found`);
- }
-
- return await expectProposalDownload(wex, proposal);
-}
-
/**
* Check if a payment for the given taler://pay/ URI is possible.
*
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -82,7 +82,6 @@ import {
GetBankingChoicesForPaytoResponse,
GetChoicesForPaymentRequest,
GetChoicesForPaymentResult,
- GetContractTermsDetailsRequest,
GetCurrencySpecificationRequest,
GetCurrencySpecificationResponse,
GetDepositWireTypesForCurrencyRequest,
@@ -170,7 +169,6 @@ import {
UserAttentionsResponse,
ValidateIbanRequest,
ValidateIbanResponse,
- DownloadedContractData,
WalletCoreVersion,
WithdrawTestBalanceRequest,
WithdrawUriInfoResponse,
@@ -327,18 +325,6 @@ export enum WalletApiOperation {
* Use {@link WalletApiOperation.PrepareBankIntegratedWithdrawal} instead.
*/
GetWithdrawalDetailsForUri = "getWithdrawalDetailsForUri",
-
- /**
- * @deprecated (2024-12-04)
- * Use checkDeposit instead
- */
- PrepareDeposit = "prepareDeposit",
-
- /**
- * @deprecated (2024-12-04)
- * Use getTransactionById with includeContractTerms: true instead
- */
- GetContractTermsDetails = "getContractTermsDetails",
}
// group: Initialization
@@ -643,12 +629,6 @@ export type PreparePayForTemplateOp = {
response: PreparePayResult;
};
-export type GetContractTermsDetailsOp = {
- op: WalletApiOperation.GetContractTermsDetails;
- request: GetContractTermsDetailsRequest;
- response: DownloadedContractData;
-};
-
/**
* Confirm a payment that was previously prepared with
* {@link PreparePayForUriOp}
@@ -919,15 +899,6 @@ export type CheckDepositOp = {
response: CheckDepositResponse;
};
-/**
- * @deprecated use CheckDepositOp instead
- */
-export type PrepareDepositOp = {
- op: WalletApiOperation.PrepareDeposit;
- request: CheckDepositRequest;
- response: CheckDepositResponse;
-};
-
// group: Backups
/**
@@ -1426,7 +1397,6 @@ export type WalletOperations = {
[WalletApiOperation.SharePayment]: SharePaymentOp;
[WalletApiOperation.CheckPayForTemplate]: CheckPayForTemplateOp;
[WalletApiOperation.PreparePayForTemplate]: PreparePayForTemplateOp;
- [WalletApiOperation.GetContractTermsDetails]: GetContractTermsDetailsOp;
[WalletApiOperation.WithdrawTestkudos]: WithdrawTestkudosOp;
[WalletApiOperation.GetChoicesForPayment]: GetChoicesForPaymentOp;
[WalletApiOperation.ConfirmPay]: ConfirmPayOp;
@@ -1470,7 +1440,6 @@ export type WalletOperations = {
[WalletApiOperation.GetExchangeTos]: GetExchangeTosOp;
[WalletApiOperation.GetExchangeDetailedInfo]: GetExchangeDetailedInfoOp;
[WalletApiOperation.GetExchangeEntryByUrl]: GetExchangeEntryByUrlOp;
- [WalletApiOperation.PrepareDeposit]: PrepareDepositOp;
[WalletApiOperation.CheckDeposit]: CheckDepositOp;
[WalletApiOperation.GenerateDepositGroupTxId]: GenerateDepositGroupTxIdOp;
[WalletApiOperation.CreateDepositGroup]: CreateDepositGroupOp;
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -72,7 +72,6 @@ import {
GetBankingChoicesForPaytoResponse,
GetChoicesForPaymentRequest,
GetChoicesForPaymentResult,
- GetContractTermsDetailsRequest,
GetCurrencySpecificationRequest,
GetCurrencySpecificationResponse,
GetDepositWireTypesForCurrencyRequest,
@@ -174,7 +173,6 @@ import {
codecForGetBankAccountByIdRequest,
codecForGetBankingChoicesForPaytoRequest,
codecForGetChoicesForPaymentRequest,
- codecForGetContractTermsDetails,
codecForGetCurrencyInfoRequest,
codecForGetDepositWireTypesForCurrencyRequest,
codecForGetDepositWireTypesRequest,
@@ -326,7 +324,6 @@ import {
checkPayForTemplate,
confirmPay,
getChoicesForPayment,
- getContractTermsDetails,
preparePayForTemplate,
preparePayForUri,
sharePayment,
@@ -1155,17 +1152,6 @@ async function handleGetExchangeTos(
);
}
-async function handleGetContractTermsDetails(
- wex: WalletExecutionContext,
- req: GetContractTermsDetailsRequest,
-): Promise<DownloadedContractData> {
- const parsedTx = parseTransactionIdentifier(req.transactionId);
- if (parsedTx?.tag !== TransactionType.Payment) {
- throw Error("transactionId is not a payment transaction");
- }
- return getContractTermsDetails(wex, parsedTx.proposalId);
-}
-
async function handleGetQrCodesForPayto(
wex: WalletExecutionContext,
req: GetQrCodesForPaytoRequest,
@@ -2015,10 +2001,6 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
codec: codecForGetExchangeTosRequest(),
handler: handleGetExchangeTos,
},
- [WalletApiOperation.GetContractTermsDetails]: {
- codec: codecForGetContractTermsDetails(),
- handler: handleGetContractTermsDetails,
- },
[WalletApiOperation.RetryPendingNow]: {
codec: codecForEmptyObject(),
handler: handleRetryPendingNow,
@@ -2157,10 +2139,6 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
codec: codecForEmptyObject(),
handler: getBackupInfo,
},
- [WalletApiOperation.PrepareDeposit]: {
- codec: codecForCheckDepositRequest(),
- handler: checkDepositGroup,
- },
[WalletApiOperation.CheckDeposit]: {
codec: codecForCheckDepositRequest(),
handler: checkDepositGroup,
@@ -2574,7 +2552,7 @@ function applyRunConfigDefaults(wcp?: PartialWalletRunConfig): WalletRunConfig {
},
features: {
allowHttp: true,
- enableV1Contracts: false,
+ enableV1Contracts: wcp?.features?.enableV1Contracts ?? false,
},
testing: {
devModeActive: wcp?.testing?.devModeActive ?? false,