commit 65359049ef9c5f2d8af1223a0f81c21975d24a46
parent 347dc5850fe47351c023baa1537111e99e1abdfc
Author: Florian Dold <florian@dold.me>
Date: Mon, 19 May 2025 19:06:40 +0200
wallet-cli: handle pay-push URIs
Issue: https://bugs.taler.net/n/9972
Diffstat:
1 file changed, 131 insertions(+), 0 deletions(-)
diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts
@@ -30,6 +30,7 @@ import {
CoreApiResponse,
Duration,
encodeCrock,
+ ExchangeTosStatus,
getErrorDetailFromException,
getRandomBytes,
InitRequest,
@@ -46,6 +47,7 @@ import {
TalerUriAction,
TransactionIdStr,
TransactionMajorState,
+ TransactionMinorState,
WalletNotification,
} from "@gnu-taler/taler-util";
import { clk } from "@gnu-taler/taler-util/clk";
@@ -695,6 +697,131 @@ withdrawCli
});
});
+async function cliHandleTos(
+ wallet: WalletContext,
+ exchangeBaseUrl: string,
+): Promise<boolean> {
+ while (1) {
+ const exch = await wallet.client.call(
+ WalletApiOperation.GetExchangeEntryByUrl,
+ {
+ exchangeBaseUrl,
+ },
+ );
+ if (exch.tosStatus === ExchangeTosStatus.Proposed) {
+ const res = await readlinePrompt(
+ `Accept terms of service of exchange ${exchangeBaseUrl}? [y/N/info]: `,
+ );
+ switch (res.toLowerCase()) {
+ case "":
+ case "n": {
+ return false;
+ }
+ case "y": {
+ await wallet.client.call(WalletApiOperation.SetExchangeTosAccepted, {
+ exchangeBaseUrl,
+ });
+ return true;
+ }
+ case "info": {
+ const tosText = await wallet.client.call(
+ WalletApiOperation.GetExchangeTos,
+ {
+ exchangeBaseUrl,
+ },
+ );
+ console.log(tosText.content);
+ }
+ }
+ }
+ }
+ throw Error("not reached");
+}
+
+/**
+ * Handle the user action of scanning a taler://pay-push/ URI.
+ */
+async function cliPeerPushCredit(
+ wallet: WalletContext,
+ uri: string,
+): Promise<void> {
+ const prepRes = await wallet.client.call(
+ WalletApiOperation.PreparePeerPushCredit,
+ {
+ talerUri: uri,
+ },
+ );
+ const txDet = await wallet.client.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: prepRes.transactionId,
+ includeContractTerms: true,
+ },
+ );
+ if (txDet.txState.major === TransactionMajorState.Done) {
+ console.log("Payment already done in transaction history.");
+ return;
+ }
+ if (
+ txDet.txState.major === TransactionMajorState.Dialog &&
+ txDet.txState.minor === TransactionMinorState.Proposed
+ ) {
+ while (true) {
+ const res = await readlinePrompt(
+ `Accept payment of ${prepRes.amountEffective}? [y/N/info/delete]: `,
+ );
+ let done = false;
+ switch (res.toLowerCase()) {
+ case "y": {
+ const tosOk = await cliHandleTos(wallet, prepRes.exchangeBaseUrl);
+ if (!tosOk) {
+ console.log(
+ "ToS needs to be accepted before payment can be accepted",
+ );
+ done = true;
+ break;
+ }
+ await wallet.client.call(WalletApiOperation.ConfirmPeerPushCredit, {
+ transactionId: prepRes.transactionId,
+ });
+ console.log(
+ "peer-push-credit confirmed, waiting for transaction to be final...",
+ );
+ await wallet.client.call(
+ WalletApiOperation.TestingWaitTransactionState,
+ {
+ transactionId: prepRes.transactionId,
+ txState: [
+ { major: TransactionMajorState.Done },
+ { major: TransactionMajorState.Failed, minor: "*" },
+ { major: TransactionMajorState.Aborted, minor: "*" },
+ ],
+ },
+ );
+ done = true;
+ break;
+ }
+ case "":
+ case "n": {
+ done = true;
+ break;
+ }
+ case "info": {
+ console.log(`${j2s(txDet)}`);
+ break;
+ }
+ default: {
+ console.log("not understood");
+ break;
+ }
+ }
+ if (done) {
+ break;
+ }
+ }
+ }
+}
+
walletCli
.subcommand("handleUri", "handle-uri", {
help: "Handle a taler:// URI.",
@@ -728,6 +855,10 @@ walletCli
talerRefundUri: uri,
});
break;
+ case TalerUriAction.PayPush: {
+ await cliPeerPushCredit(wallet, uri);
+ break;
+ }
case TalerUriAction.Withdraw: {
const withdrawInfo = await wallet.client.call(
WalletApiOperation.GetWithdrawalDetailsForUri,