taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit aec34138e692fe6c267027be7dc27e5c49e398ba
parent 87ae2e9211053ea0b16bb1b33dfbc1231a51a2d2
Author: Florian Dold <dold@taler.net>
Date:   Fri, 24 Jul 2026 12:44:44 +0200

wallet-core: remove preparePay v1 implementation

Diffstat:
Mpackages/taler-util/src/types-taler-wallet.ts | 194-------------------------------------------------------------------------------
Mpackages/taler-wallet-cli/src/index.ts | 45+++++++++++++++++++++++----------------------
Mpackages/taler-wallet-core/src/pay-merchant.ts | 431+------------------------------------------------------------------------------
Mpackages/taler-wallet-core/src/pay-template.ts | 20--------------------
Mpackages/taler-wallet-core/src/requests.ts | 10----------
Mpackages/taler-wallet-core/src/testing.ts | 46+++++++++++++++++++++++++++++++++++++++-------
Mpackages/taler-wallet-core/src/versions.ts | 2+-
Mpackages/taler-wallet-core/src/wallet-api-types.ts | 25+------------------------
Mpackages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts | 1-
9 files changed, 65 insertions(+), 709 deletions(-)

diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -104,7 +104,6 @@ import { TokenEnvelope, TokenIssuePublicKey, WalletTemplateDetailsResponse, - codecForMerchantContractTerms, codecForMerchantContractTermsV0, } from "./types-taler-merchant.js"; import { BackupRecovery } from "./types-taler-sync.js"; @@ -774,29 +773,6 @@ export interface SenderWireInfos { senderWires: string[]; } -export enum PreparePayResultType { - PaymentPossible = "payment-possible", - InsufficientBalance = "insufficient-balance", - AlreadyConfirmed = "already-confirmed", - ChoiceSelection = "choice-selection", -} - -export const codecForPreparePayResultPaymentPossible = - (): Codec<PreparePayResultPaymentPossible> => - buildCodecForObject<PreparePayResultPaymentPossible>() - .property("amountEffective", codecForAmountString()) - .property("amountRaw", codecForAmountString()) - .property("contractTerms", codecForMerchantContractTermsV0()) - .property("transactionId", codecForTransactionIdStr()) - .property("contractTermsHash", codecForString()) - .property("scopes", codecForList(codecForScopeInfo())) - .property("talerUri", codecForString()) - .property( - "status", - codecForConstString(PreparePayResultType.PaymentPossible), - ) - .build("PreparePayResultPaymentPossible"); - export enum InsufficientBalanceHint { /** * Merchant doesn't accept money from exchange(s) that the wallet supports. @@ -1020,180 +996,10 @@ export const codecForPayMerchantInsufficientBalanceDetails = .deprecatedProperty("balanceReceiverAcceptable") .build("PayMerchantInsufficientBalanceDetails"); -export const codecForPreparePayResultInsufficientBalance = - (): Codec<PreparePayResultInsufficientBalance> => - buildCodecForObject<PreparePayResultInsufficientBalance>() - .property("amountRaw", codecForAmountString()) - .property("contractTerms", codecForMerchantContractTermsV0()) - .property("talerUri", codecForString()) - .property("transactionId", codecForTransactionIdStr()) - .property( - "status", - codecForConstString(PreparePayResultType.InsufficientBalance), - ) - .property("scopes", codecForList(codecForScopeInfo())) - .property( - "balanceDetails", - codecForPayMerchantInsufficientBalanceDetails(), - ) - .build("PreparePayResultInsufficientBalance"); - -export const codecForPreparePayResultAlreadyConfirmed = - (): Codec<PreparePayResultAlreadyConfirmed> => - buildCodecForObject<PreparePayResultAlreadyConfirmed>() - .property( - "status", - codecForConstString(PreparePayResultType.AlreadyConfirmed), - ) - .property("amountEffective", codecOptional(codecForAmountString())) - .property("amountRaw", codecForAmountString()) - .property("scopes", codecForList(codecForScopeInfo())) - .property("paid", codecForBoolean()) - .property("talerUri", codecForString()) - .property("contractTerms", codecForMerchantContractTerms()) - .property("contractTermsHash", codecForString()) - .property("transactionId", codecForTransactionIdStr()) - .build("PreparePayResultAlreadyConfirmed"); - -export const codecForPreparePayResultChoiceSelection = - (): Codec<PreparePayResultChoiceSelection> => - buildCodecForObject<PreparePayResultChoiceSelection>() - .property( - "status", - codecForConstString(PreparePayResultType.ChoiceSelection), - ) - .property("transactionId", codecForTransactionIdStr()) - .property("contractTerms", codecForMerchantContractTerms()) - .property("contractTermsHash", codecForString()) - .property("talerUri", codecForString()) - .build("PreparePayResultChoiceSelection"); - -export const codecForPreparePayResult = (): Codec<PreparePayResult> => - buildCodecForUnion<PreparePayResult>() - .discriminateOn("status") - .alternative( - PreparePayResultType.AlreadyConfirmed, - codecForPreparePayResultAlreadyConfirmed(), - ) - .alternative( - PreparePayResultType.InsufficientBalance, - codecForPreparePayResultInsufficientBalance(), - ) - .alternative( - PreparePayResultType.PaymentPossible, - codecForPreparePayResultPaymentPossible(), - ) - .alternative( - PreparePayResultType.ChoiceSelection, - codecForPreparePayResultChoiceSelection(), - ) - .build("PreparePayResult"); - export interface PreparePayV2Result { transactionId: TransactionIdStr; } -/** - * Result of a prepare pay operation. - */ -export type PreparePayResult = - | PreparePayResultInsufficientBalance - | PreparePayResultAlreadyConfirmed - | PreparePayResultPaymentPossible - | PreparePayResultChoiceSelection; - -/** - * Payment is possible. - * - * This response is only returned for v0 contracts - * or when v1 are not enabled yet. - */ -export interface PreparePayResultPaymentPossible { - status: PreparePayResultType.PaymentPossible; - - transactionId: TransactionIdStr; - - contractTerms: MerchantContractTermsV0; - - /** - * Scopes involved in this transaction. - */ - scopes: ScopeInfo[]; - - amountRaw: AmountString; - - amountEffective: AmountString; - - /** - * FIXME: Unclear why this is needed. Remove? - */ - contractTermsHash: string; - - /** - * FIXME: Unclear why this is needed! Remove? - */ - talerUri: string; -} - -export interface PreparePayResultInsufficientBalance { - status: PreparePayResultType.InsufficientBalance; - transactionId: TransactionIdStr; - - /** - * Scopes involved in this transaction. - * - * For the insufficient balance response, contains scopes - * of *possible* payment providers. - */ - scopes: ScopeInfo[]; - - contractTerms: MerchantContractTermsV0; - - amountRaw: AmountString; - - talerUri: string; - - balanceDetails: PaymentInsufficientBalanceDetails; -} - -export interface PreparePayResultAlreadyConfirmed { - status: PreparePayResultType.AlreadyConfirmed; - - transactionId: TransactionIdStr; - - contractTerms: MerchantContractTerms; - - paid: boolean; - - amountRaw: AmountString; - - amountEffective?: AmountString | undefined; - - /** - * Scopes involved in this transaction. - */ - scopes: ScopeInfo[]; - - contractTermsHash: string; - - talerUri: string; -} - -/** - * Unconfirmed contract v1 payment. - */ -export interface PreparePayResultChoiceSelection { - status: PreparePayResultType.ChoiceSelection; - - transactionId: TransactionIdStr; - - contractTerms: MerchantContractTerms; - - contractTermsHash: string; - - talerUri: string; -} - export interface BankWithdrawDetails { status: WithdrawalOperationStatusFlag; currency: string; diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts @@ -23,7 +23,6 @@ import { AgeRestriction, Amounts, AmountString, - assertUnreachable, ChoiceSelectionDetailType, codecForList, codecForString, @@ -39,7 +38,6 @@ import { Logger, NotificationType, Paytos, - PreparePayResultType, Result, getSampleWalletCoreTransactions, setDangerousTimetravel, @@ -2019,30 +2017,33 @@ advancedCli .action(async (args) => { await withWallet(args, { lazyTaskLoop: true }, async (wallet) => { const res = await wallet.client.call( - WalletApiOperation.PreparePayForUri, + WalletApiOperation.PreparePayForUriV2, { talerPayUri: args.payPrepare.url, }, ); - switch (res.status) { - case PreparePayResultType.InsufficientBalance: - console.log("insufficient balance"); - break; - case PreparePayResultType.AlreadyConfirmed: - if (res.paid) { - console.log("already paid!"); - } else { - console.log("payment in progress"); - } - break; - case PreparePayResultType.PaymentPossible: - console.log("payment possible"); - break; - case PreparePayResultType.ChoiceSelection: - console.log("choice selection"); - break; - default: - assertUnreachable(res); + // The transaction starts out in the "downloading proposal" state, so + // wait for it to settle before reporting what the user can do with it. + await wallet.client.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: res.transactionId, + txState: "nonpending", + }); + const tx = await wallet.client.call( + WalletApiOperation.GetTransactionById, + { + transactionId: res.transactionId, + }, + ); + console.log(`transaction ${res.transactionId}`); + console.log(`state: ${j2s(tx.txState)}`); + const choicesRes = await wallet.client.callForResult( + WalletApiOperation.GetChoicesForPayment, + { + transactionId: res.transactionId, + }, + ); + if (choicesRes.tag === "ok") { + console.log(`choices: ${j2s(choicesRes.value.choices)}`); } }); }); diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -71,8 +71,6 @@ import { PayCoinSelection, PaymentInsufficientBalanceDetails, PayWalletData, - PreparePayResult, - PreparePayResultType, PreparePayTemplateRequest, PreparePayV2Result, randomBytes, @@ -115,7 +113,6 @@ import { } from "./coinSelection.js"; import { constructTaskIdentifier, - genericWaitForState, genericWaitForStateVal, getGenericRecordHandle, LookupFullTransactionOpts, @@ -157,7 +154,7 @@ import { getScopeForAllCoins, getScopeForAllExchanges, } from "./exchanges.js"; -import { instantiateTemplate, instantiateTemplateRaw } from "./pay-template.js"; +import { instantiateTemplateRaw } from "./pay-template.js"; import { runWithMaybeProgressContext } from "./progress.js"; import { calculateRefreshOutput, @@ -1850,364 +1847,6 @@ async function handleInsufficientFunds( return TaskRunResult.progress(); } -/** - * @deprecated only used for legacy compat - */ -async function lookupProposalOrRepurchase( - wex: WalletExecutionContext, - proposalId: string, -): Promise< - | { - proposalId: string; - purchaseRec: WalletPurchase; - contractTerms: MerchantContractTerms; - contractTermsHash: string; - } - | undefined -> { - return await wex.runWalletDbTx(async (tx) => { - let purchaseRec = await tx.getPurchase(proposalId); - - if (!purchaseRec) { - return undefined; - } - - if (purchaseRec.purchaseStatus === PurchaseStatus.DoneRepurchaseDetected) { - const existingProposalId = purchaseRec.repurchaseProposalId; - if (existingProposalId) { - logger.trace("using existing purchase for same product"); - const oldProposal = await tx.getPurchase(existingProposalId); - if (oldProposal) { - purchaseRec = oldProposal; - } - } - } - - let { contractTerms, contractTermsHash } = - await expectProposalDownloadByIdInTx(wex, tx, proposalId); - - proposalId = purchaseRec.proposalId; - - return { - proposalId, - purchaseRec: purchaseRec, - contractTerms, - contractTermsHash, - }; - }); -} - -/** - * @deprecated only used for legacy compat - */ -async function checkPaymentByProposalId( - wex: WalletExecutionContext, - proposalId: string, - sessionId?: string, -): Promise<PreparePayResult> { - // FIXME: Should take a transaction ID instead of a proposal ID - // FIXME: Does way more than checking the payment - // FIXME: Should return immediately. - const lookupRes = await lookupProposalOrRepurchase(wex, proposalId); - if (!lookupRes) { - throw Error(`could not get proposal ${proposalId}`); - } - // Might be redirected to another proposal ID due to repurchase detection. - proposalId = lookupRes.proposalId; - - const purchaseRec = lookupRes.purchaseRec; - let contractTerms = lookupRes.contractTerms; - const contractTermsHash = lookupRes.contractTermsHash; - - const ctx = new PayMerchantTransactionContext(wex, proposalId); - - const transactionId = ctx.transactionId; - - const talerUri = TalerUris.stringify({ - type: TalerUriAction.Pay, - merchantBaseUrl: purchaseRec.merchantBaseUrl as HostPortPath, // FIXME: change record type - orderId: purchaseRec.orderId, - sessionId: - sessionId ?? - purchaseRec.lastSessionId ?? - purchaseRec.downloadSessionId ?? - "", - claimToken: purchaseRec.claimToken, - }); - - const scopes = await wex.runWalletDbTx(async (tx) => { - let exchangeUrls = contractTerms.exchanges.map((x) => x.url); - return await getScopeForAllExchanges(tx, exchangeUrls); - }); - - const handleUnconfirmed = async (): Promise<PreparePayResult> => { - if (contractTerms.version === MerchantContractVersion.V1) { - return { - status: PreparePayResultType.ChoiceSelection, - transactionId, - contractTerms: contractTerms, - contractTermsHash: contractTermsHash, - talerUri, - }; - } - - const instructedAmount = Amounts.parseOrThrow(contractTerms.amount); - // If not already paid, check if we could pay for it. - const res = await selectPayCoins(wex, { - restrictExchanges: { - auditors: [], - exchanges: contractTerms.exchanges.map((ex) => ({ - exchangeBaseUrl: ex.url, - exchangePub: ex.master_pub, - })), - }, - contractTermsAmount: instructedAmount, - depositFeeLimit: Amounts.parseOrThrow(contractTerms.max_fee), - prevPayCoins: [], - requiredMinimumAge: contractTerms.minimum_age, - restrictWireMethod: contractTerms.wire_method, - }); - - let coins: SelectedProspectiveCoin[] | undefined = undefined; - - const allowedExchangeUrls = contractTerms.exchanges.map((x) => x.url); - - switch (res.type) { - case "failure": { - logger.info("not allowing payment, insufficient coins"); - logger.info( - `insufficient balance details: ${j2s( - res.insufficientBalanceDetails, - )}`, - ); - let scopes = await wex.runWalletDbTx(async (tx) => { - return getScopeForAllExchanges(tx, allowedExchangeUrls); - }); - return { - status: PreparePayResultType.InsufficientBalance, - contractTerms, - transactionId, - amountRaw: Amounts.stringify(contractTerms.amount), - scopes, - talerUri, - balanceDetails: res.insufficientBalanceDetails, - }; - } - case "success": - coins = res.coinSel.coins; - break; - default: - assertUnreachable(res); - } - - const currency = Amounts.currencyOf(contractTerms.amount); - const totalCost = await getTotalPaymentCost(wex, currency, coins); - logger.trace("costInfo", totalCost); - logger.trace("coinsForPayment", res); - - const exchanges = new Set<string>(coins.map((x) => x.exchangeBaseUrl)); - - const scopes = await wex.runWalletDbTx(async (tx) => { - return await getScopeForAllExchanges(tx, [...exchanges]); - }); - - return { - status: PreparePayResultType.PaymentPossible, - contractTerms, - transactionId, - amountEffective: Amounts.stringify(totalCost), - amountRaw: Amounts.stringify(instructedAmount), - scopes, - contractTermsHash, - talerUri, - }; - }; - - const handlePaid = async (): Promise<PreparePayResult> => { - logger.trace( - "automatically re-submitting payment with different session ID", - ); - logger.trace(`last: ${purchaseRec.lastSessionId}, current: ${sessionId}`); - await wex.runWalletDbTx(async (tx) => { - const [p, h] = await ctx.getRecordHandle(tx); - if (!p) { - return; - } - p.lastSessionId = sessionId; - p.purchaseStatus = PurchaseStatus.PendingPayingReplay; - await h.update(p); - }); - - await wex.taskScheduler.resetTask(ctx.taskId); - - // FIXME: Consider changing the API here so that we don't have to - // wait inline for the repurchase. - - await waitPaymentResult(wex, proposalId, sessionId); - const download = await expectProposalDownload(wex, purchaseRec); - const { available, amountRaw } = ContractTermsUtil.extractAmounts( - download.contractTerms, - purchaseRec.choiceIndex, - ); - if (!available) { - throw Error("choice index not specified for contract v1"); - } - - return { - status: PreparePayResultType.AlreadyConfirmed, - contractTerms, - contractTermsHash: download.contractTermsHash, - paid: true, - amountRaw: Amounts.stringify(amountRaw), - amountEffective: purchaseRec.payInfo - ? Amounts.stringify(purchaseRec.payInfo.totalPayCost) - : undefined, - scopes, - transactionId, - talerUri, - }; - }; - - const handleConfirmed = async (): Promise<PreparePayResult> => { - const paid = isPurchasePaid(purchaseRec); - - const { available, amountRaw } = ContractTermsUtil.extractAmounts( - contractTerms, - purchaseRec.choiceIndex, - ); - if (!available) { - throw Error("choice index not specified for contract v1"); - } - - return { - status: PreparePayResultType.AlreadyConfirmed, - contractTerms: contractTerms, - contractTermsHash: contractTermsHash, - paid, - amountRaw: Amounts.stringify(amountRaw), - amountEffective: purchaseRec.payInfo - ? Amounts.stringify(purchaseRec.payInfo.totalPayCost) - : undefined, - ...(paid ? { nextUrl: contractTerms.order_id } : {}), - scopes, - transactionId, - talerUri, - }; - }; - - const handlePaidByOther = async (): Promise<PreparePayResult> => { - const { amountRaw } = ContractTermsUtil.extractAmounts( - contractTerms, - purchaseRec.choiceIndex, - ); - return { - status: PreparePayResultType.AlreadyConfirmed, - contractTerms, - contractTermsHash: contractTermsHash, - paid: true, - amountRaw: amountRaw ? Amounts.stringify(amountRaw) : "UNKNOWN:0", - amountEffective: purchaseRec.payInfo - ? Amounts.stringify(purchaseRec.payInfo.totalPayCost) - : undefined, - scopes, - transactionId, - talerUri, - }; - }; - - if (!purchaseRec) { - return handleUnconfirmed(); - } - - switch (purchaseRec?.purchaseStatus) { - case PurchaseStatus.DialogProposed: - case PurchaseStatus.DialogShared: - return await handleUnconfirmed(); - case PurchaseStatus.Done: - case PurchaseStatus.PendingPayingReplay: - case PurchaseStatus.DoneRepurchaseDetected: - return handlePaid(); - case PurchaseStatus.AbortedIncompletePayment: - case PurchaseStatus.AbortedOrderDeleted: - case PurchaseStatus.AbortedProposalRefused: - case PurchaseStatus.AbortedRefunded: - case PurchaseStatus.AbortingWithRefund: - case PurchaseStatus.FailedAbort: - case PurchaseStatus.FailedClaim: - case PurchaseStatus.Failed: - case PurchaseStatus.FinalizingQueryingAutoRefund: - case PurchaseStatus.PendingAcceptRefund: - case PurchaseStatus.PendingPaying: - case PurchaseStatus.PendingQueryingAutoRefund: - case PurchaseStatus.PendingQueryingRefund: - case PurchaseStatus.SuspendedAbortingWithRefund: - case PurchaseStatus.SuspendedFinalizingQueryingAutoRefund: - case PurchaseStatus.SuspendedPaying: - case PurchaseStatus.SuspendedPayingReplay: - case PurchaseStatus.SuspendedPendingAcceptRefund: - case PurchaseStatus.SuspendedQueryingAutoRefund: - case PurchaseStatus.SuspendedQueryingRefund: - case PurchaseStatus.Expired: - return handleConfirmed(); - case PurchaseStatus.FailedPaidByOther: - return handlePaidByOther(); - case PurchaseStatus.PendingDownloadingProposal: - case PurchaseStatus.SuspendedDownloadingProposal: - throw Error("expected proposal to be downloaded"); - default: - assertUnreachable(purchaseRec.purchaseStatus); - } -} - -function isPurchasePaid(purchase: WalletPurchase): boolean { - return ( - purchase.purchaseStatus === PurchaseStatus.Done || - purchase.purchaseStatus === PurchaseStatus.PendingQueryingRefund || - purchase.purchaseStatus === PurchaseStatus.FinalizingQueryingAutoRefund || - purchase.purchaseStatus === PurchaseStatus.PendingQueryingAutoRefund - ); -} - -/** - * Check if a payment for the given taler://pay/ URI is possible. - */ -export async function preparePayForUri( - wex: WalletExecutionContext, - talerPayUri: string, -): Promise<PreparePayResult> { - const uriResult = Result.orUndefined( - TalerUris.parseRestricted(talerPayUri, TalerUriAction.Pay), - ); - - if (!uriResult) { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_INVALID_TALER_PAY_URI, - { - talerPayUri, - }, - `invalid taler://pay URI (${talerPayUri})`, - ); - } - - const proposalRes = await createOrReusePurchase( - wex, - uriResult.merchantBaseUrl, - uriResult.orderId, - uriResult.sessionId, - uriResult.claimToken, - uriResult.noncePriv, - talerPayUri, - ); - - await waitProposalDownloaded(wex, proposalRes.proposalId); - - return checkPaymentByProposalId( - wex, - proposalRes.proposalId, - uriResult.sessionId, - ); -} - export async function preparePayForUriV2( wex: WalletExecutionContext, talerPayUri: string, @@ -2241,74 +1880,6 @@ export async function preparePayForUriV2( }; } -/** - * Wait until a proposal is at least downloaded. - */ -async function waitProposalDownloaded( - wex: WalletExecutionContext, - proposalId: string, -): Promise<void> { - // FIXME: This doesn't support cancellation yet - const ctx = new PayMerchantTransactionContext(wex, proposalId); - - logger.info(`waiting for ${ctx.transactionId} to be downloaded`); - - wex.taskScheduler.startShepherdTask(ctx.taskId); - - await genericWaitForState(wex, { - filterNotification(notif) { - return ( - notif.type === NotificationType.TransactionStateTransition && - notif.transactionId === ctx.transactionId - ); - }, - async checkState() { - const { purchase, retryInfo } = await ctx.wex.runWalletDbTx( - async (tx) => { - return { - purchase: await tx.getPurchase(ctx.proposalId), - retryInfo: await tx.getOperationRetry(ctx.taskId), - }; - }, - ); - if (!purchase) { - throw Error("purchase does not exist anymore"); - } - if (purchase.download) { - return true; - } - const exc = purchase.failReason ?? purchase.abortReason; - if (exc) { - throw TalerError.fromUncheckedDetail(exc); - } - if (retryInfo) { - // FIXME: This should really be a return status of `preparePay`. - if (retryInfo.lastError) { - throw TalerError.fromUncheckedDetail(retryInfo.lastError); - } else { - throw Error("transient error while waiting for proposal download"); - } - } - return false; - }, - }); -} - -export async function preparePayForTemplate( - wex: WalletExecutionContext, - req: PreparePayTemplateRequest, -): Promise<PreparePayResult> { - return runWithMaybeProgressContext( - wex, - "preparePayForTemplate", - req.progressToken, - async () => { - const payUri = await instantiateTemplate(wex, req); - return await preparePayForUri(wex, payUri); - }, - ); -} - export async function preparePayForTemplateV2( wex: WalletExecutionContext, req: PreparePayTemplateRequest, diff --git a/packages/taler-wallet-core/src/pay-template.ts b/packages/taler-wallet-core/src/pay-template.ts @@ -20,7 +20,6 @@ import { assertUnreachable, CheckPayTemplateReponse, CheckPayTemplateRequest, - HostPortPath, j2s, Logger, PreparePayTemplateRequest, @@ -315,22 +314,3 @@ export async function instantiateTemplateRaw( claimToken: resp.token, }; } - -/** - * Instantiate a pay template. - * - * @returns A taler://pay/ URI pointing to the order - */ -export async function instantiateTemplate( - wex: WalletExecutionContext, - req: PreparePayTemplateRequest, -): Promise<string> { - const res = await instantiateTemplateRaw(wex, req); - return TalerUris.stringify({ - type: TalerUriAction.Pay, - merchantBaseUrl: res.merchantBaseUrl as HostPortPath, - orderId: res.orderId, - sessionId: res.sessionId ?? "", - claimToken: res.claimToken, - }); -} diff --git a/packages/taler-wallet-core/src/requests.ts b/packages/taler-wallet-core/src/requests.ts @@ -325,9 +325,7 @@ import { import { confirmPay, getChoicesForPayment, - preparePayForTemplate, preparePayForTemplateV2, - preparePayForUri, preparePayForUriV2, sharePayment, startQueryRefund, @@ -2375,14 +2373,6 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = { codec: codecForCheckPayTemplateRequest(), handler: checkPayForTemplate, }, - [WalletApiOperation.PreparePayForUri]: { - codec: codecForPreparePayRequest(), - handler: (wex, req) => preparePayForUri(wex, req.talerPayUri), - }, - [WalletApiOperation.PreparePayForTemplate]: { - codec: codecForPreparePayTemplateRequest(), - handler: preparePayForTemplate, - }, [WalletApiOperation.PreparePayForUriV2]: { codec: codecForPreparePayRequest(), handler: (wex, req) => preparePayForUriV2(wex, req.talerPayUri), diff --git a/packages/taler-wallet-core/src/testing.ts b/packages/taler-wallet-core/src/testing.ts @@ -31,6 +31,7 @@ import { AmountString, checkLogicInvariant, CheckPaymentResponse, + ChoiceSelectionDetailType, ConfirmPayResultType, Duration, IntegrationTestArgs, @@ -40,7 +41,6 @@ import { NotificationType, Paytos, Result, - PreparePayResultType, succeedOrThrow, TalerCorebankApiClient, TalerMerchantInstanceHttpClient, @@ -63,7 +63,8 @@ import { createDepositGroup } from "./deposits.js"; import { fetchFreshExchange } from "./exchanges.js"; import { confirmPay, - preparePayForUri, + getChoicesForPayment, + preparePayForUriV2, startRefundQueryForUri, } from "./pay-merchant.js"; import { initiatePeerPullPayment } from "./pay-peer-pull-credit.js"; @@ -272,16 +273,35 @@ async function makePayment( throw Error("no taler://pay/ URI in payment response"); } - const preparePayResult = await preparePayForUri(wex, talerPayUri); + const preparePayResult = await preparePayForUriV2(wex, talerPayUri); logger.trace("prepare pay result", preparePayResult); - if (preparePayResult.status != "payment-possible") { + // V2 does not wait for the proposal to be downloaded, so wait for the + // transaction to reach the dialog state before inspecting the choices. + await waitTransactionState(wex, { + transactionId: preparePayResult.transactionId, + txState: { + major: TransactionMajorState.Dialog, + minor: "*", + }, + }); + + const choicesResult = await getChoicesForPayment( + wex, + preparePayResult.transactionId, + ); + + if ( + choicesResult.choices[0]?.status !== + ChoiceSelectionDetailType.PaymentPossible + ) { throw Error("payment not possible"); } const confirmPayResult = await confirmPay(wex, { transactionId: preparePayResult.transactionId, + choiceIndex: 0, }); logger.trace("confirmPayResult", confirmPayResult); @@ -941,12 +961,24 @@ export async function testPay( process.exit(1); } logger.trace("taler pay URI:", talerPayUri); - const result = await preparePayForUri(wex, talerPayUri); - if (result.status !== PreparePayResultType.PaymentPossible) { - throw Error(`unexpected prepare pay status: ${result.status}`); + const result = await preparePayForUriV2(wex, talerPayUri); + // V2 does not wait for the proposal to be downloaded, so wait for the + // transaction to reach the dialog state before inspecting the choices. + await waitTransactionState(wex, { + transactionId: result.transactionId, + txState: { + major: TransactionMajorState.Dialog, + minor: "*", + }, + }); + const choicesResult = await getChoicesForPayment(wex, result.transactionId); + const choice = choicesResult.choices[0]; + if (choice?.status !== ChoiceSelectionDetailType.PaymentPossible) { + throw Error(`unexpected prepare pay status: ${choice?.status}`); } const r = await confirmPay(wex, { transactionId: result.transactionId, + choiceIndex: 0, forcedCoinSel: args.forcedCoinSel, }); if (r.type != ConfirmPayResultType.Done) { diff --git a/packages/taler-wallet-core/src/versions.ts b/packages/taler-wallet-core/src/versions.ts @@ -48,7 +48,7 @@ export const WALLET_BANK_CONVERSION_API_PROTOCOL_VERSION = "2:0:0"; /** * Libtool version of the wallet-core API. */ -export const WALLET_CORE_API_PROTOCOL_VERSION = "7:0:0"; +export const WALLET_CORE_API_PROTOCOL_VERSION = "8:0:0"; /** * Libtool rules: diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -153,7 +153,6 @@ import { PrepareBankIntegratedWithdrawalRequest, PrepareBankIntegratedWithdrawalResponse, PreparePayRequest, - PreparePayResult, PreparePayTemplateRequest, PreparePayV2Result, PreparePeerPullDebitRequest, @@ -294,8 +293,6 @@ export enum WalletApiOperation { // Merchant Payments GetChoicesForPayment = "getChoicesForPayment", - PreparePayForUri = "preparePayForUri", - PreparePayForTemplate = "preparePayForTemplate", PreparePayForUriV2 = "preparePayForUriV2", PreparePayForTemplateV2 = "preparePayForTemplateV2", SharePayment = "sharePayment", @@ -845,24 +842,6 @@ export type AcceptManualWithdrawalOp = { /** * Prepare to make a payment based on a taler://pay/ URI. */ -export type PreparePayForUriOp = { - op: WalletApiOperation.PreparePayForUri; - request: PreparePayRequest; - response: PreparePayResult; -}; - -/** - * Prepare to make a payment based on a taler://pay-template/ URI. - */ -export type PreparePayForTemplateOp = { - op: WalletApiOperation.PreparePayForTemplate; - request: PreparePayTemplateRequest; - response: PreparePayResult; -}; - -/** - * Prepare to make a payment based on a taler://pay/ URI. - */ export type PreparePayForUriV2Op = { op: WalletApiOperation.PreparePayForUriV2; request: PreparePayRequest; @@ -910,7 +889,7 @@ export type CheckPayForTemplateOp = { /** * Confirm a payment that was previously prepared with - * {@link PreparePayForUriOp} + * {@link PreparePayForUriV2Op} */ export type ConfirmPayOp = { op: WalletApiOperation.ConfirmPay; @@ -1705,8 +1684,6 @@ export type WalletOperations = { [WalletApiOperation.InitWallet]: InitWalletOp; [WalletApiOperation.SetWalletRunConfig]: SetWalletRunConfigOp; [WalletApiOperation.GetVersion]: GetVersionOp; - [WalletApiOperation.PreparePayForUri]: PreparePayForUriOp; - [WalletApiOperation.PreparePayForTemplate]: PreparePayForTemplateOp; [WalletApiOperation.PreparePayForUriV2]: PreparePayForUriV2Op; [WalletApiOperation.PreparePayForTemplateV2]: PreparePayForTemplateV2Op; [WalletApiOperation.SharePayment]: SharePaymentOp; diff --git a/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts b/packages/taler-wallet-webextension/src/cta/PaymentTemplate/state.ts @@ -16,7 +16,6 @@ import { Amounts, - PreparePayResult, PreparePayV2Result, TemplateType, Transaction,