commit 16712a2c4530602277cb2ceaa1b2987420abb1ce
parent b627aa39c88575c51cb3f388aeab77353a0f5983
Author: Florian Dold <florian@dold.me>
Date: Tue, 26 May 2026 23:53:48 +0200
basic support for prepared wire transfers
There will still be some small protocol changes, so this is just a
prototype version.
The harness test only tests with libeufin-bank, not with libeufin-nexus,
thus CH-QR-Bill isn't fully tested yet.
Diffstat:
10 files changed, 401 insertions(+), 135 deletions(-)
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts
@@ -698,6 +698,7 @@ export interface HarnessExchangeBankAccount {
wireGatewayApiBaseUrl: string;
wireGatewayAuth: BasicAuth;
conversionUrl?: string;
+ preparedTransferUrl?: string;
/**
* If set, the harness will not automatically configure the wire fee for this account.
*/
@@ -1801,6 +1802,10 @@ export class ExchangeService implements ExchangeServiceInterface {
if (acct.conversionUrl != null) {
optArgs.push("conversion-url", acct.conversionUrl);
}
+ if (acct.preparedTransferUrl) {
+ // FIXME: The exchange tooling has the wrong name for this option!
+ optArgs.push("wire-transfer-gateway", acct.preparedTransferUrl);
+ }
if (acct.accountRestrictions != null) {
optArgs.push(...acct.accountRestrictions.flat(1));
}
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-shorten.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-shorten.ts
@@ -0,0 +1,146 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+ AmountString,
+ j2s,
+ Logger,
+ succeedOrThrow,
+ TalerCorebankApiClient,
+ TalerExchangeHttpClient,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { defaultCoinConfig } from "../harness/denomStructures.js";
+import { createWalletDaemonWithClient } from "../harness/environments.js";
+import {
+ BankService,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ LibeufinBankService,
+ setupDb,
+} from "../harness/harness.js";
+
+const logger = new Logger("test-withdrawal-shorten.ts");
+
+export async function runWithdrawalShortenTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const db = await setupDb(t);
+
+ const bc = {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ };
+
+ const bank: BankService = await LibeufinBankService.create(t, bc);
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ const receiverName = "Exchange";
+ const exchangeBankUsername = "exchange";
+ const exchangeBankPassword = "mypw-password";
+ const exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+ const wireGatewayApiBaseUrl = new URL(
+ `accounts/${exchangeBankUsername}/taler-wire-gateway/`,
+ bank.corebankApiBaseUrl,
+ ).href;
+
+ const prepBaseUrl =
+ bank.corebankApiBaseUrl + "/accounts/exchange/taler-prepared-transfer/";
+
+ const exchangeBankAccount: HarnessExchangeBankAccount = {
+ wireGatewayApiBaseUrl,
+ wireGatewayAuth: {
+ type: "basic",
+ username: exchangeBankUsername,
+ password: exchangeBankPassword,
+ },
+ accountPaytoUri: exchangePaytoUri,
+ preparedTransferUrl: prepBaseUrl,
+ };
+
+ await exchange.addBankAccount("1", exchangeBankAccount);
+
+ bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri);
+
+ await bank.start();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "admin-password",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ exchange.addOfferedCoins(defaultCoinConfig);
+
+ await exchange.start();
+
+ const exchangeClient = new TalerExchangeHttpClient(exchange.baseUrl);
+
+ const keys = succeedOrThrow(await exchangeClient.getKeys());
+
+ console.log(j2s(keys.accounts));
+
+ const { walletClient, walletService } = await createWalletDaemonWithClient(
+ t,
+ {
+ name: "wallet",
+ persistent: true,
+ },
+ );
+
+ await walletClient.call(WalletApiOperation.AddExchange, {
+ exchangeBaseUrl: exchange.baseUrl,
+ });
+
+ const wres = await walletClient.call(
+ WalletApiOperation.AcceptManualWithdrawal,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ amount: "TESTKUDOS:10" as AmountString,
+ },
+ );
+
+ console.log(j2s(wres));
+
+ const acc0 = wres.withdrawalAccountsList[0];
+ t.assertTrue(!!acc0);
+ t.assertTrue(!!acc0.transferOptions.find((x) => x.type === "payto"));
+ t.assertTrue(!!acc0.transferOptions.find((x) => x.type === "uri"));
+}
+
+runWithdrawalShortenTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -227,6 +227,7 @@ import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js";
import { runWithdrawalIdempotentTest } from "./test-withdrawal-idempotent.js";
import { runWithdrawalManualTest } from "./test-withdrawal-manual.js";
import { runWithdrawalPrepareTest } from "./test-withdrawal-prepare.js";
+import { runWithdrawalShortenTest } from "./test-withdrawal-shorten.js";
/**
* Test runner.
@@ -438,6 +439,7 @@ const allTests: TestMainFunction[] = [
runPaivanaRepurchaseTest,
runKycFormValidationTest,
runKycMerchantWalletReuseTest,
+ runWithdrawalShortenTest,
];
export interface TestRunSpec {
diff --git a/packages/taler-util/src/qr.ts b/packages/taler-util/src/qr.ts
@@ -33,6 +33,12 @@ function encodePaytoAsSwissQrBill(paytoUri: string): EncodeResult {
if (parsedPayto.targetType !== "iban") {
return { type: "skip" };
}
+ let referenceType = "NON";
+ let referenceValue = "";
+ if (parsedPayto.params["ch-qrr"] != null) {
+ referenceType = "QRR";
+ referenceValue = parsedPayto.params["ch-qrr"];
+ }
const amountStr = parsedPayto.params["amount"];
const targetPathParts = parsedPayto.targetPath.split("/");
const beneficiaryIban = targetPathParts[targetPathParts.length - 1];
@@ -74,8 +80,8 @@ function encodePaytoAsSwissQrBill(paytoUri: string): EncodeResult {
"", // Debtor town
"", // Debtor country
// Group: Reference
- "NON", // reference type
- "", // Reference
+ referenceType, // reference type
+ referenceValue, // Reference
// Group: Additional Information
parsedPayto.params["message"], // Unstructured data
"EPD", // End of payment data
diff --git a/packages/taler-util/src/types-taler-prepared-transfer.ts b/packages/taler-util/src/types-taler-prepared-transfer.ts
@@ -16,6 +16,7 @@
SPDX-License-Identifier: AGPL-3.0-or-later
*/
+import { codecForAmountString } from "./amounts.js";
import {
Codec,
buildCodecForObject,
@@ -29,7 +30,6 @@ import {
codecOptional,
} from "./codec.js";
import { TalerPreparedTransferApi } from "./index.js";
-import { codecForAmountString } from "./amounts.js";
import { codecForTimestamp } from "./time.js";
import {
AmountString,
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
@@ -1943,20 +1943,20 @@ export const codecForExchangeFullDetails = (): Codec<ExchangeFullDetails> =>
export interface AcceptManualWithdrawalResult {
/**
- * Payto URIs that can be used to fund the withdrawal.
- *
- * @deprecated in favor of withdrawalAccountsList
+ * Transaction ID of the newly created withdrawal transaction.
*/
- exchangePaytoUris: string[];
+ transactionId: TransactionIdStr;
/**
* Public key of the newly created reserve.
*/
reservePub: string;
+ /**
+ * Bank accounts of the exchange that can be used
+ * to fund the withdrawal.
+ */
withdrawalAccountsList: WithdrawalExchangeAccountDetails[];
-
- transactionId: TransactionIdStr;
}
export interface WithdrawalDetailsForAmount {
@@ -3964,6 +3964,40 @@ export interface WithdrawalExchangeAccountDetails {
* Error that happened when attempting to request the conversion rate.
*/
conversionError?: TalerErrorDetail;
+
+ /**
+ * Timestamp that indicates when the transfer options expire.
+ *
+ * If missing, options do not expire.
+ */
+ transferExpiry?: TalerProtocolTimestamp;
+
+ /**
+ * Options for transfering funds to the exchange for the withdrawal.
+ */
+ transferOptions: TransferOption[];
+}
+
+export type TransferOption =
+ | TransferOptionPayto
+ | TransferOptionUri
+ | TransferOptionSwissQrBill;
+
+export interface TransferOptionPayto {
+ type: "payto";
+ paytoUri: string;
+ qrCodes: QrCodeSpec[];
+}
+
+export interface TransferOptionUri {
+ type: "uri";
+ uri: string;
+}
+
+export interface TransferOptionSwissQrBill {
+ type: "ch-qr-bill";
+ qrReferenceNumber: string;
+ qrCodes: QrCodeSpec[];
}
export interface PrepareWithdrawExchangeRequest {
diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts
@@ -479,6 +479,7 @@ async function addFakeTx(
{
paytoUri: "payto://x-taler-bank/test/test",
status: "ok",
+ transferOptions: [],
},
],
},
diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts
@@ -680,6 +680,7 @@ async function validateWireInfo(
conversionUrl: a.conversion_url,
creditRestrictions: a.credit_restrictions,
debitRestrictions: a.debit_restrictions,
+ wireTransferGatewayUrl: a.wire_transfer_gateway,
});
isValid = v;
}
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -202,76 +202,100 @@ import { PaymentBalanceDetails } from "./balance.js";
import { WithdrawTestBalanceResult } from "./testing.js";
export enum WalletApiOperation {
+ // Initialization and wallet lifecycle
InitWallet = "initWallet",
SetWalletRunConfig = "setWalletRunConfig",
- WithdrawTestkudos = "withdrawTestkudos",
- WithdrawTestBalance = "withdrawTestBalance",
- PreparePayForUri = "preparePayForUri",
- SharePayment = "sharePayment",
- CheckPayForTemplate = "checkPayForTemplate",
- PreparePayForTemplate = "preparePayForTemplate",
- RunIntegrationTest = "runIntegrationTest",
- RunIntegrationTestV2 = "runIntegrationTestV2",
- TestCrypto = "testCrypto",
- TestPay = "testPay",
- AddExchange = "addExchange",
+ GetVersion = "getVersion",
+ Shutdown = "shutdown",
+
+ // Balances
+ GetBalances = "getBalances",
+ GetBalanceDetail = "getBalanceDetail",
+
+ // Misc.
+
+ GetActiveTasks = "getActiveTasks",
+ ValidateIban = "validateIban",
+ GetCurrencySpecification = "getCurrencySpecification",
+ ListGlobalCurrencyExchanges = "listGlobalCurrencyExchanges",
+ ListGlobalCurrencyAuditors = "listGlobalCurrencyAuditors",
+ AddGlobalCurrencyExchange = "addGlobalCurrencyExchange",
+ RemoveGlobalCurrencyExchange = "removeGlobalCurrencyExchange",
+ AddGlobalCurrencyAuditor = "addGlobalCurrencyAuditor",
+ RemoveGlobalCurrencyAuditor = "removeGlobalCurrencyAuditor",
+ CanonicalizeBaseUrl = "canonicalizeBaseUrl",
+ StartExchangeWalletKyc = "startExchangeWalletKyc",
+ GetBankingChoicesForPayto = "getBankingChoicesForPayto",
+ ConvertIbanAccountFieldToPayto = "convertIbanAccountFieldToPayto",
+ ConvertIbanPaytoToAccountField = "convertIbanPaytoToAccountField",
+
+ // Generic transaction management
+
GetTransactions = "getTransactions",
GetTransactionsV2 = "getTransactionsV2",
GetTransactionById = "getTransactionById",
- TestingGetSampleTransactions = "testingGetSampleTransactions",
- ListExchanges = "listExchanges",
- GetDefaultExchanges = "getDefaultExchanges",
- GetExchangeEntryByUrl = "getExchangeEntryByUrl",
+ RetryPendingNow = "retryPendingNow",
+ AbortTransaction = "abortTransaction",
+ FailTransaction = "failTransaction",
+ SuspendTransaction = "suspendTransaction",
+ ResumeTransaction = "resumeTransaction",
+ DeleteTransaction = "deleteTransaction",
+ RetryTransaction = "retryTransaction",
+ ListAssociatedRefreshes = "listAssociatedRefreshes",
+
+ // Bank account management
+
ListBankAccounts = "listBankAccounts",
GetBankAccountById = "getBankAccountById",
AddBankAccount = "addBankAccount",
ForgetBankAccount = "forgetBankAccount",
- GetWithdrawalDetailsForAmount = "getWithdrawalDetailsForAmount",
- AcceptManualWithdrawal = "acceptManualWithdrawal",
- GetBalances = "getBalances",
- GetBalanceDetail = "getBalanceDetail",
- ConvertDepositAmount = "convertDepositAmount",
- GetMaxDepositAmount = "getMaxDepositAmount",
- GetMaxPeerPushDebitAmount = "getMaxPeerPushDebitAmount",
- GetActiveTasks = "getActiveTasks",
+
+ // Exchange entry management
+
+ AddExchange = "addExchange",
+ ListExchanges = "listExchanges",
+ GetDefaultExchanges = "getDefaultExchanges",
+ GetExchangeEntryByUrl = "getExchangeEntryByUrl",
+ UpdateExchangeEntry = "updateExchangeEntry",
+ GetExchangeResources = "getExchangeResources",
+ CompleteExchangeBaseUrl = "completeExchangeBaseUrl",
+ DeleteExchange = "deleteExchange",
SetExchangeTosAccepted = "setExchangeTosAccepted",
SetExchangeTosForgotten = "setExchangeTosForgotten",
- StartRefundQueryForUri = "startRefundQueryForUri",
- StartRefundQuery = "startRefundQuery",
+ GetExchangeTos = "getExchangeTos",
+ GetExchangeDetailedInfo = "getExchangeDetailedInfo",
+
+ // Withdrawals
+
+ PrepareWithdrawExchange = "prepareWithdrawExchange",
PrepareBankIntegratedWithdrawal = "prepareBankIntegratedWithdrawal",
ConfirmWithdrawal = "confirmWithdrawal",
AcceptBankIntegratedWithdrawal = "acceptBankIntegratedWithdrawal",
- GetExchangeTos = "getExchangeTos",
- GetExchangeDetailedInfo = "getExchangeDetailedInfo",
- AddContact = "addContact",
- DeleteContact = "deleteContact",
- GetContacts = "getContacts",
- GetMailbox = "getMailbox",
- InitializeMailbox = "initializeMailbox",
- GetMailboxMessages = "getMailboxMessage",
- AddMailboxMessage = "addMailboxMessage",
- DeleteMailboxMessage = "deleteMailboxMessage",
- SendTalerUriMailboxMessage = "sendTalerUriMailboxMessage",
- RefreshMailbox = "refreshMailbox",
- RetryPendingNow = "retryPendingNow",
- AbortTransaction = "abortTransaction",
- FailTransaction = "failTransaction",
- SuspendTransaction = "suspendTransaction",
- ResumeTransaction = "resumeTransaction",
- DeleteTransaction = "deleteTransaction",
- RetryTransaction = "retryTransaction",
+ GetWithdrawalDetailsForAmount = "getWithdrawalDetailsForAmount",
+ AcceptManualWithdrawal = "acceptManualWithdrawal",
+
+ // Merchant Payments
+
GetChoicesForPayment = "getChoicesForPayment",
+ PreparePayForUri = "preparePayForUri",
+ SharePayment = "sharePayment",
+ CheckPayForTemplate = "checkPayForTemplate",
+ PreparePayForTemplate = "preparePayForTemplate",
+ StartRefundQueryForUri = "startRefundQueryForUri",
+ StartRefundQuery = "startRefundQuery",
ConfirmPay = "confirmPay",
- DumpCoins = "dumpCoins",
- SetCoinSuspended = "setCoinSuspended",
- ForceRefresh = "forceRefresh",
+
+ // Deposits
+
CheckDeposit = "checkDeposit",
- GetVersion = "getVersion",
CreateDepositGroup = "createDepositGroup",
- ImportDb = "importDb",
- ExportDb = "exportDb",
- ExportDbToFile = "exportDbToFile",
- ImportDbFromFile = "importDbFromFile",
+ ConvertDepositAmount = "convertDepositAmount",
+ GetMaxDepositAmount = "getMaxDepositAmount",
+ GetDepositWireTypes = "getDepositWireTypes",
+ GetDepositWireTypesForCurrency = "getDepositWireTypesForCurrency",
+
+ // P2P Payments
+
PreparePeerPushCredit = "preparePeerPushCredit",
CheckPeerPushDebit = "checkPeerPushDebit",
CheckPeerPushDebitV2 = "checkPeerPushDebitV2",
@@ -281,32 +305,7 @@ export enum WalletApiOperation {
InitiatePeerPullCredit = "initiatePeerPullCredit",
PreparePeerPullDebit = "preparePeerPullDebit",
ConfirmPeerPullDebit = "confirmPeerPullDebit",
- ClearDb = "clearDb",
- Recycle = "recycle",
- ApplyDevExperiment = "applyDevExperiment",
- ValidateIban = "validateIban",
- GetCurrencySpecification = "getCurrencySpecification",
- UpdateExchangeEntry = "updateExchangeEntry",
- PrepareWithdrawExchange = "prepareWithdrawExchange",
- GetExchangeResources = "getExchangeResources",
- CompleteExchangeBaseUrl = "completeExchangeBaseUrl",
- DeleteExchange = "deleteExchange",
- ListGlobalCurrencyExchanges = "listGlobalCurrencyExchanges",
- ListGlobalCurrencyAuditors = "listGlobalCurrencyAuditors",
- AddGlobalCurrencyExchange = "addGlobalCurrencyExchange",
- RemoveGlobalCurrencyExchange = "removeGlobalCurrencyExchange",
- AddGlobalCurrencyAuditor = "addGlobalCurrencyAuditor",
- RemoveGlobalCurrencyAuditor = "removeGlobalCurrencyAuditor",
- ListAssociatedRefreshes = "listAssociatedRefreshes",
- Shutdown = "shutdown",
- CanonicalizeBaseUrl = "canonicalizeBaseUrl",
- GetDepositWireTypes = "getDepositWireTypes",
- GetDepositWireTypesForCurrency = "getDepositWireTypesForCurrency",
- GetQrCodesForPayto = "getQrCodesForPayto",
- StartExchangeWalletKyc = "startExchangeWalletKyc",
- GetBankingChoicesForPayto = "getBankingChoicesForPayto",
- ConvertIbanAccountFieldToPayto = "convertIbanAccountFieldToPayto",
- ConvertIbanPaytoToAccountField = "convertIbanPaytoToAccountField",
+ GetMaxPeerPushDebitAmount = "getMaxPeerPushDebitAmount",
// Tokens and token families
ListDiscounts = "listDiscounts",
@@ -319,6 +318,19 @@ export enum WalletApiOperation {
GetDonau = "getDonau",
GetDonauStatements = "getDonauStatements",
+ // Mailbox
+
+ AddContact = "addContact",
+ DeleteContact = "deleteContact",
+ GetContacts = "getContacts",
+ GetMailbox = "getMailbox",
+ InitializeMailbox = "initializeMailbox",
+ GetMailboxMessages = "getMailboxMessage",
+ AddMailboxMessage = "addMailboxMessage",
+ DeleteMailboxMessage = "deleteMailboxMessage",
+ SendTalerUriMailboxMessage = "sendTalerUriMailboxMessage",
+ RefreshMailbox = "refreshMailbox",
+
// Stored backups
ListStoredBackups = "listStoredBackups",
@@ -326,8 +338,27 @@ export enum WalletApiOperation {
DeleteStoredBackup = "deleteStoredBackup",
RecoverStoredBackup = "recoverStoredBackup",
- // Testing
+ // Wallet database management
+ ImportDb = "importDb",
+ ExportDb = "exportDb",
+ ExportDbToFile = "exportDbToFile",
+ ImportDbFromFile = "importDbFromFile",
+ ClearDb = "clearDb",
+ Recycle = "recycle",
+
+ // Testing
+ ApplyDevExperiment = "applyDevExperiment",
+ TestingGetSampleTransactions = "testingGetSampleTransactions",
+ WithdrawTestkudos = "withdrawTestkudos",
+ WithdrawTestBalance = "withdrawTestBalance",
+ RunIntegrationTest = "runIntegrationTest",
+ RunIntegrationTestV2 = "runIntegrationTestV2",
+ DumpCoins = "dumpCoins",
+ TestCrypto = "testCrypto",
+ TestPay = "testPay",
+ SetCoinSuspended = "setCoinSuspended",
+ ForceRefresh = "forceRefresh",
TestingWaitTransactionsFinal = "testingWaitTransactionsFinal",
TestingWaitRefreshesFinal = "testingWaitRefreshesFinal",
TestingWaitTransactionState = "testingWaitTransactionState",
@@ -359,6 +390,11 @@ export enum WalletApiOperation {
* Use {@link WalletApiOperation.PrepareBankIntegratedWithdrawal} instead.
*/
GetWithdrawalDetailsForUri = "getWithdrawalDetailsForUri",
+
+ /**
+ * @deprecated(2026-05-26) Consult withdrawal transaction details instead.
+ */
+ GetQrCodesForPayto = "getQrCodesForPayto",
}
// group: Initialization
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -65,6 +65,7 @@ import {
TalerErrorCode,
TalerErrorDetail,
TalerPreciseTimestamp,
+ TalerPreparedTransferHttpClient,
TalerProtocolTimestamp,
TalerUriAction,
Transaction,
@@ -75,6 +76,7 @@ import {
TransactionState,
TransactionType,
TransactionWithdrawal,
+ TransferOption,
URL,
UnblindedDenominationSignature,
WithdrawUriInfoResponse,
@@ -95,8 +97,11 @@ import {
codecForExchangeWithdrawResponse,
codecForLegitimizationNeededResponse,
codecForReserveStatus,
+ decodeCrock,
+ eddsaSign,
encodeCrock,
getErrorDetailFromException,
+ getQrCodesForPayto,
getRandomBytes,
j2s,
makeErrorDetail,
@@ -3071,46 +3076,6 @@ export function augmentPaytoUrisForKycTransfer(
);
}
-/**
- * Get payto URIs that can be used to fund a withdrawal operation.
- */
-export async function getFundingPaytoUris(
- tx: WalletIndexedDbTransaction,
- withdrawalGroupId: string,
-): Promise<string[]> {
- const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
- checkDbInvariant(!!withdrawalGroup, `no withdrawal for ${withdrawalGroupId}`);
- checkDbInvariant(
- withdrawalGroup.exchangeBaseUrl !== undefined,
- "can't get funding uri from uninitialized wg",
- );
- checkDbInvariant(
- withdrawalGroup.instructedAmount !== undefined,
- "can't get funding uri from uninitialized wg",
- );
- const exchangeDetails = await getExchangeDetailsInTx(
- tx,
- withdrawalGroup.exchangeBaseUrl,
- );
- if (!exchangeDetails) {
- logger.error(`exchange ${withdrawalGroup.exchangeBaseUrl} not found`);
- return [];
- }
- const plainPaytoUris =
- exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ?? [];
- if (!plainPaytoUris) {
- logger.error(
- `exchange ${withdrawalGroup.exchangeBaseUrl} has no wire info`,
- );
- return [];
- }
- return augmentPaytoUrisForWithdrawal(
- plainPaytoUris,
- withdrawalGroup.reservePub,
- withdrawalGroup.instructedAmount,
- );
-}
-
async function getWithdrawalGroupRecordTx(
wex: WalletExecutionContext,
req: {
@@ -4165,7 +4130,8 @@ async function fetchAccount(
instructedAmount: AmountJson,
scopeInfo: ScopeInfo,
acct: ExchangeWireAccount,
- reservePub: string | undefined,
+ reservePub?: string,
+ reservePriv?: string,
): Promise<WithdrawalExchangeAccountDetails> {
let paytoUri: string;
let transferAmount: AmountString | undefined;
@@ -4186,6 +4152,7 @@ async function fetchAccount(
status: "error",
paytoUri: acct.payto_uri,
conversionError: respOrErr.talerErrorResponse,
+ transferOptions: [],
};
}
const resp = respOrErr.response;
@@ -4202,6 +4169,7 @@ async function fetchAccount(
status: "error",
paytoUri: acct.payto_uri,
conversionError: configRespOrError.talerErrorResponse,
+ transferOptions: [],
};
}
const configParsed = configRespOrError.response;
@@ -4227,6 +4195,74 @@ async function fetchAccount(
message: `Taler ${reservePub}`,
});
}
+ const options: TransferOption[] = [];
+ let transferExpiry: TalerProtocolTimestamp | undefined = undefined;
+ if (
+ reservePub != null &&
+ reservePriv != null &&
+ acct.wire_transfer_gateway != null
+ ) {
+ const prepClient = new TalerPreparedTransferHttpClient(
+ acct.wire_transfer_gateway,
+ );
+ const mySig = eddsaSign(decodeCrock(reservePub), decodeCrock(reservePriv));
+ const resp = succeedOrThrow(
+ await prepClient.register({
+ account_pub: reservePub,
+ alg: "EdDSA",
+ authorization_pub: reservePub,
+ authorization_sig: encodeCrock(mySig),
+ credit_amount: transferAmount,
+ recurrent: false,
+ type: "reserve",
+ }),
+ );
+ for (const opt of resp.subjects) {
+ switch (opt.type) {
+ case "SIMPLE": {
+ const myPayto = addPaytoQueryParams(acct.payto_uri, {
+ message: opt.subject,
+ amount: transferAmount,
+ });
+ options.push({
+ type: "payto",
+ paytoUri: myPayto,
+ qrCodes: getQrCodesForPayto(myPayto),
+ });
+ break;
+ }
+ case "URI": {
+ options.push({
+ type: "uri",
+ uri: opt.uri,
+ });
+ break;
+ }
+ case "CH_QR_BILL": {
+ const myPayto = addPaytoQueryParams(acct.payto_uri, {
+ "ch-qrr": opt.qr_reference_number,
+ });
+ options.push({
+ type: "ch-qr-bill",
+ qrReferenceNumber: opt.qr_reference_number,
+ qrCodes: getQrCodesForPayto(myPayto),
+ });
+ break;
+ }
+ default:
+ assertUnreachable;
+ logger.warn(`Unsupported transfer subject: ${j2s(opt)}`);
+ break;
+ }
+ }
+ transferExpiry = resp.expiration;
+ } else {
+ options.push({
+ type: "payto",
+ paytoUri,
+ qrCodes: getQrCodesForPayto(paytoUri),
+ });
+ }
const acctInfo: WithdrawalExchangeAccountDetails = {
status: "ok",
paytoUri,
@@ -4235,6 +4271,8 @@ async function fetchAccount(
priority: acct.priority,
currencySpecification,
creditRestrictions: acct.credit_restrictions,
+ transferOptions: options,
+ transferExpiry,
};
acctInfo.transferAmount = transferAmount;
return acctInfo;
@@ -4251,6 +4289,7 @@ async function fetchWithdrawalAccountInfo(
exchange: ReadyExchangeSummary;
instructedAmount: AmountJson;
reservePub?: string;
+ reservePriv?: string;
withdrawalType: WithdrawalRecordType;
},
): Promise<WithdrawalExchangeAccountDetails[]> {
@@ -4269,6 +4308,7 @@ async function fetchWithdrawalAccountInfo(
req.exchange.scopeInfo,
acct,
req.reservePub,
+ req.reservePriv,
);
withdrawalAccounts.push(acctInfo);
}
@@ -4331,6 +4371,7 @@ export async function createManualWithdrawal(
exchange,
instructedAmount: amount,
reservePub: reserveKeyPair.pub,
+ reservePriv: reserveKeyPair.priv,
withdrawalType: WithdrawalRecordType.BankManual,
});
@@ -4352,11 +4393,6 @@ export async function createManualWithdrawal(
withdrawalGroup.withdrawalGroupId,
);
- const exchangePaytoUris = await wex.runLegacyWalletDbTx(
- async (tx) =>
- await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId),
- );
-
wex.ws.notify({
type: NotificationType.BalanceChange,
hintTransactionId: ctx.transactionId,
@@ -4366,7 +4402,6 @@ export async function createManualWithdrawal(
return {
reservePub: withdrawalGroup.reservePub,
- exchangePaytoUris: exchangePaytoUris,
withdrawalAccountsList: withdrawalAccountsList,
transactionId: ctx.transactionId,
};