taler-typescript-core

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

commit 1311b559dcacfe3acb8dd7739bd365fcffb426c2
parent 9ced5e05bea5f7d0d664d95c89ff9608ea447b75
Author: Florian Dold <florian@dold.me>
Date:   Wed,  4 Dec 2024 23:45:00 +0100

wallet-core: emit notifications for refund transactions, test

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-refund.ts | 20+++++++++++++++++++-
Mpackages/taler-util/src/types-taler-wallet.ts | 46+++++++++++++++++++++++-----------------------
Mpackages/taler-wallet-core/src/pay-merchant.ts | 33+++++++++++++++++++++++++++++++--
Mpackages/taler-wallet-core/src/wallet.ts | 2+-
4 files changed, 74 insertions(+), 27 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-refund.ts b/packages/taler-harness/src/integrationtests/test-refund.ts @@ -24,13 +24,14 @@ import { NotificationType, TransactionMajorState, TransactionType, + WalletNotification, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV3, withdrawViaBankV3, } from "../harness/environments.js"; +import { GlobalTestState } from "../harness/harness.js"; export async function runRefundTest(t: GlobalTestState) { // Set up test environment @@ -42,6 +43,12 @@ export async function runRefundTest(t: GlobalTestState) { merchant, } = await createSimpleTestkudosEnvironmentV3(t); + const notifs: WalletNotification[] = []; + + wallet.addNotificationListener((x) => { + notifs.push(x); + }); + const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl()); // Withdraw digital cash into the wallet. @@ -144,6 +151,17 @@ export async function runRefundTest(t: GlobalTestState) { tx.type === TransactionType.Payment && tx.refundPending === undefined, ); } + + //console.log(j2s(notifs)); + + const refundDoneNotif = notifs.find( + (x) => + x.type === NotificationType.TransactionStateTransition && + x.transactionId.startsWith("txn:refund:") && + x.newTxState.major === TransactionMajorState.Done, + ); + + t.assertTrue(!!refundDoneNotif); } runRefundTest.suites = ["wallet"]; diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -172,6 +172,28 @@ export function codecForCanonBaseUrl(): Codec<string> { }; } +export enum ScopeType { + Global = "global", + Exchange = "exchange", + Auditor = "auditor", +} + +export type ScopeInfoGlobal = { type: ScopeType.Global; currency: string }; + +export type ScopeInfoExchange = { + type: ScopeType.Exchange; + currency: string; + url: string; +}; + +export type ScopeInfoAuditor = { + type: ScopeType.Auditor; + currency: string; + url: string; +}; + +export type ScopeInfo = ScopeInfoGlobal | ScopeInfoExchange | ScopeInfoAuditor; + export const codecForScopeInfo = (): Codec<ScopeInfo> => buildCodecForUnion<ScopeInfo>() .discriminateOn("type") @@ -254,7 +276,7 @@ export interface GetMaxDepositAmountRequest { restrictScope?: ScopeInfo; } -export const codecForGetMaxDepositAmountRequest = +export const codecForGetMaxDepositAmountRequest = () => buildCodecForObject<GetMaxDepositAmountRequest>() .property("currency", codecForString()) .property("depositPaytoUri", codecOptional(codecForString())) @@ -441,28 +463,6 @@ export interface InitResponse { versionInfo: WalletCoreVersion; } -export enum ScopeType { - Global = "global", - Exchange = "exchange", - Auditor = "auditor", -} - -export type ScopeInfoGlobal = { type: ScopeType.Global; currency: string }; - -export type ScopeInfoExchange = { - type: ScopeType.Exchange; - currency: string; - url: string; -}; - -export type ScopeInfoAuditor = { - type: ScopeType.Auditor; - currency: string; - url: string; -}; - -export type ScopeInfo = ScopeInfoGlobal | ScopeInfoExchange | ScopeInfoAuditor; - /** * Shorter version of stringifyScopeInfo */ diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -159,6 +159,7 @@ import { isUnsuccessfulTransaction, notifyTransition, parseTransactionIdentifier, + TransitionInfo, } from "./transactions.js"; import { EXCHANGE_COINS_LOCK, @@ -219,7 +220,7 @@ export class PayMerchantTransactionContext implements TransactionContext { async lookupFullTransaction( tx: WalletDbAllStoresReadOnlyTransaction, - req: LookupFullTransactionOpts, + req?: LookupFullTransactionOpts, ): Promise<Transaction | undefined> { const proposalId = this.proposalId; const purchaseRec = await tx.purchases.get(proposalId); @@ -307,7 +308,7 @@ export class PayMerchantTransactionContext implements TransactionContext { }), abortReason: purchaseRec.abortReason, info, - contractTerms: req.includeContractTerms + contractTerms: req?.includeContractTerms ? download.contractTermsRaw : undefined, refundQueryActive: @@ -3683,6 +3684,11 @@ async function storeRefunds( const download = await expectProposalDownload(wex, purchase); const currency = Amounts.currencyOf(download.contractData.amount); + const transitions: { + transactionId: string; + transitionInfo: TransitionInfo; + }[] = []; + const result = await wex.db.runReadWriteTx( { storeNames: [ @@ -3729,6 +3735,7 @@ async function storeRefunds( rf.coin_pub, rf.rtransaction_id, ]); + let oldTxState: TransactionState | undefined = undefined; if (oldItem) { logger.info("already have refund in database"); if (oldItem.status === RefundItemStatus.Done) { @@ -3804,6 +3811,13 @@ async function storeRefunds( ); await tx.refundGroups.put(newGroup); await refundCtx.updateTransactionMeta(tx); + transitions.push({ + transactionId: refundCtx.transactionId, + transitionInfo: { + oldTxState: { major: TransactionMajorState.None }, + newTxState: computeRefundTransactionState(newGroup), + }, + }); } const refundGroups = await tx.refundGroups.indexes.byProposalId.getAll( @@ -3839,6 +3853,8 @@ async function storeRefunds( numFailed++; } } + const oldTxState: TransactionState = + computeRefundTransactionState(refundGroup); if (numPending === 0) { // We're done for this refund group! if (numFailed === 0) { @@ -3849,6 +3865,15 @@ async function storeRefunds( await tx.refundGroups.put(refundGroup); await refundCtx.updateTransactionMeta(tx); const refreshCoins = await computeRefreshRequest(wex, tx, items); + const newTxState: TransactionState = + computeRefundTransactionState(refundGroup); + transitions.push({ + transactionId: refundCtx.transactionId, + transitionInfo: { + oldTxState, + newTxState, + }, + }); await createRefreshGroup( wex, tx, @@ -3904,6 +3929,10 @@ async function storeRefunds( return TaskRunResult.finished(); } + for (const trs of transitions) { + notifyTransition(wex, trs.transactionId, trs.transitionInfo); + } + notifyTransition(wex, ctx.transactionId, result.transitionInfo); if (result.numPendingItemsTotal > 0) { diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -1882,7 +1882,7 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = { handler: convertDepositAmount, }, [WalletApiOperation.GetMaxDepositAmount]: { - codec: codecForGetMaxDepositAmountRequest, + codec: codecForGetMaxDepositAmountRequest(), handler: getMaxDepositAmount, }, [WalletApiOperation.GetMaxPeerPushDebitAmount]: {