diff options
Diffstat (limited to 'packages/taler-wallet-core/src')
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 10 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/dbless.ts | 24 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/deposits.ts | 114 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/transactions.ts | 56 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/util/retries.ts | 6 |
5 files changed, 136 insertions, 74 deletions
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index f5342b4cd..0bfe11aaa 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -865,8 +865,10 @@ export enum DepositGroupOperationStatus { AbortingWithRefresh = 11 /* ACTIVE_START + 1 */, } -// FIXME: Improve name! This enum is very specific to deposits. -export enum TransactionStatus { +/** + * Status of a single element of a deposit group. + */ +export enum DepositElementStatus { Unknown = 10, Accepted = 20, KycRequired = 30, @@ -1686,7 +1688,7 @@ export interface DepositGroupRecord { operationStatus: OperationStatus; - transactionPerCoin: TransactionStatus[]; + transactionPerCoin: DepositElementStatus[]; trackingState?: { [signature: string]: { @@ -2605,7 +2607,7 @@ export const walletDbFixups: FixupDescription[] = [ return; } dg.transactionPerCoin = dg.depositedPerCoin.map( - (c) => TransactionStatus.Unknown, + (c) => DepositElementStatus.Unknown, ); await tx.depositGroups.put(dg); }); diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index 30c4247a8..3fb56924d 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -109,14 +109,26 @@ export async function checkReserve( } } +export interface TopupReserveWithDemobankArgs { + http: HttpRequestLibrary; + reservePub: string; + bankBaseUrl: string; + bankAccessApiBaseUrl: string; + exchangeInfo: ExchangeInfo; + amount: AmountString; +} + export async function topupReserveWithDemobank( - http: HttpRequestLibrary, - reservePub: string, - bankBaseUrl: string, - bankAccessApiBaseUrl: string, - exchangeInfo: ExchangeInfo, - amount: AmountString, + args: TopupReserveWithDemobankArgs, ) { + const { + bankBaseUrl, + http, + bankAccessApiBaseUrl, + amount, + exchangeInfo, + reservePub, + } = args; const bankHandle: BankServiceHandle = { baseUrl: bankBaseUrl, bankAccessApiBaseUrl: bankAccessApiBaseUrl, diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index f5ea41e01..6e56b0897 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -40,6 +40,7 @@ import { j2s, Logger, MerchantContractTerms, + NotificationType, parsePaytoUri, PayCoinSelection, PrepareDepositRequest, @@ -49,9 +50,9 @@ import { TalerErrorCode, TalerProtocolTimestamp, TrackTransaction, + TransactionMajorState, + TransactionMinorState, TransactionState, - TransactionStateInfo, - TransactionSubstate, TransactionType, URL, WireFee, @@ -60,13 +61,16 @@ import { DenominationRecord, DepositGroupRecord, OperationStatus, - TransactionStatus, + DepositElementStatus, } from "../db.js"; import { TalerError } from "@gnu-taler/taler-util"; import { getTotalRefreshCost, KycPendingInfo, KycUserType } from "../index.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; -import { OperationAttemptResult } from "../util/retries.js"; +import { + OperationAttemptResult, + OperationAttemptResultType, +} from "../util/retries.js"; import { spendCoins } from "./common.js"; import { getExchangeDetails } from "./exchanges.js"; import { @@ -89,15 +93,13 @@ const logger = new Logger("deposits.ts"); * Get the (DD37-style) transaction status based on the * database record of a deposit group. */ -export async function computeDepositTransactionStatus( - ws: InternalWalletState, +export function computeDepositTransactionStatus( dg: DepositGroupRecord, -): Promise<TransactionStateInfo> { +): TransactionState { switch (dg.operationStatus) { case OperationStatus.Finished: { return { - txState: TransactionState.Done, - txSubstate: TransactionSubstate.None, + major: TransactionMajorState.Done, }; } case OperationStatus.Pending: { @@ -110,10 +112,10 @@ export async function computeDepositTransactionStatus( numDeposited++; } switch (dg.transactionPerCoin[i]) { - case TransactionStatus.KycRequired: + case DepositElementStatus.KycRequired: numKycRequired++; break; - case TransactionStatus.Wired: + case DepositElementStatus.Wired: numWired++; break; } @@ -121,21 +123,21 @@ export async function computeDepositTransactionStatus( if (numKycRequired > 0) { return { - txState: TransactionState.Pending, - txSubstate: TransactionSubstate.DepositKycRequired, + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycRequired, }; } if (numDeposited == numTotal) { return { - txState: TransactionState.Pending, - txSubstate: TransactionSubstate.DepositPendingTrack, + major: TransactionMajorState.Pending, + minor: TransactionMinorState.Track, }; } return { - txState: TransactionState.Pending, - txSubstate: TransactionSubstate.DepositPendingInitial, + major: TransactionMajorState.Pending, + minor: TransactionMinorState.Deposit, }; } default: @@ -221,6 +223,13 @@ export async function processDepositGroup( return OperationAttemptResult.finishedEmpty(); } + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.Deposit, + depositGroupId, + }); + + const txStateOld = computeDepositTransactionStatus(depositGroup); + const contractData = extractContractData( depositGroup.contractTermsRaw, depositGroup.contractTermsHash, @@ -239,7 +248,7 @@ export async function processDepositGroup( for (let i = 0; i < depositPermissions.length; i++) { const perm = depositPermissions[i]; - let updatedDeposit: boolean | undefined = undefined; + let updatedDeposit: boolean = false; if (!depositGroup.depositedPerCoin[i]) { const requestBody: ExchangeDepositRequest = { @@ -270,7 +279,7 @@ export async function processDepositGroup( updatedDeposit = true; } - let updatedTxStatus: TransactionStatus | undefined = undefined; + let updatedTxStatus: DepositElementStatus | undefined = undefined; type ValueOf<T> = T[keyof T]; let newWiredTransaction: @@ -280,12 +289,12 @@ export async function processDepositGroup( } | undefined; - if (depositGroup.transactionPerCoin[i] !== TransactionStatus.Wired) { - const track = await trackDepositPermission(ws, depositGroup, perm); + if (depositGroup.transactionPerCoin[i] !== DepositElementStatus.Wired) { + const track = await trackDeposit(ws, depositGroup, perm); if (track.type === "accepted") { if (!track.kyc_ok && track.requirement_row !== undefined) { - updatedTxStatus = TransactionStatus.KycRequired; + updatedTxStatus = DepositElementStatus.KycRequired; const { requirement_row: requirementRow } = track; const paytoHash = encodeCrock( hashTruncate32(stringToBytes(depositGroup.wire.payto_uri + "\0")), @@ -297,10 +306,10 @@ export async function processDepositGroup( "individual", ); } else { - updatedTxStatus = TransactionStatus.Accepted; + updatedTxStatus = DepositElementStatus.Accepted; } } else if (track.type === "wired") { - updatedTxStatus = TransactionStatus.Wired; + updatedTxStatus = DepositElementStatus.Wired; const payto = parsePaytoUri(depositGroup.wire.payto_uri); if (!payto) { @@ -327,11 +336,11 @@ export async function processDepositGroup( id: track.exchange_sig, }; } else { - updatedTxStatus = TransactionStatus.Unknown; + updatedTxStatus = DepositElementStatus.Unknown; } } - if (updatedTxStatus !== undefined || updatedDeposit !== undefined) { + if (updatedTxStatus !== undefined || updatedDeposit) { await ws.db .mktx((x) => [x.depositGroups]) .runReadWrite(async (tx) => { @@ -358,18 +367,18 @@ export async function processDepositGroup( } } - await ws.db + const txStatusNew = await ws.db .mktx((x) => [x.depositGroups]) .runReadWrite(async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); if (!dg) { - return; + return undefined; } let allDepositedAndWired = true; for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) { if ( !depositGroup.depositedPerCoin[i] || - depositGroup.transactionPerCoin[i] !== TransactionStatus.Wired + depositGroup.transactionPerCoin[i] !== DepositElementStatus.Wired ) { allDepositedAndWired = false; break; @@ -380,8 +389,36 @@ export async function processDepositGroup( dg.operationStatus = OperationStatus.Finished; await tx.depositGroups.put(dg); } + return computeDepositTransactionStatus(dg); + }); + + if (!txStatusNew) { + // Doesn't exist anymore! + return OperationAttemptResult.finishedEmpty(); + } + + // Notify if state transitioned + if ( + txStateOld.major !== txStatusNew.major || + txStateOld.minor !== txStatusNew.minor + ) { + ws.notify({ + type: NotificationType.TransactionStateTransition, + transactionId, + oldTxState: txStateOld, + newTxState: txStatusNew, }); - return OperationAttemptResult.finishedEmpty(); + } + + // FIXME: consider other cases like aborting, suspend, ... + if ( + txStatusNew.major === TransactionMajorState.Pending || + txStatusNew.major === TransactionMajorState.Aborting + ) { + return OperationAttemptResult.pendingEmpty(); + } else { + return OperationAttemptResult.finishedEmpty(); + } } async function getExchangeWireFee( @@ -428,7 +465,7 @@ async function getExchangeWireFee( return fee; } -async function trackDepositPermission( +async function trackDeposit( ws: InternalWalletState, depositGroup: DepositGroupRecord, dp: CoinDepositPermission, @@ -448,6 +485,7 @@ async function trackDepositPermission( }); url.searchParams.set("merchant_sig", sigResp.sig); const httpResp = await ws.http.fetch(url.href, { method: "GET" }); + logger.trace(`deposits response status: ${httpResp.status}`); switch (httpResp.status) { case HttpStatusCode.Accepted: { const accepted = await readSuccessResponseJsonOrThrow( @@ -710,7 +748,7 @@ export async function createDepositGroup( timestampCreated: AbsoluteTime.toTimestamp(now), timestampFinished: undefined, transactionPerCoin: payCoinSel.coinSel.coinPubs.map( - () => TransactionStatus.Unknown, + () => DepositElementStatus.Unknown, ), payCoinSelection: payCoinSel.coinSel, payCoinSelectionUid: encodeCrock(getRandomBytes(32)), @@ -733,7 +771,7 @@ export async function createDepositGroup( depositGroupId, }); - await ws.db + const newTxState = await ws.db .mktx((x) => [ x.depositGroups, x.coins, @@ -752,8 +790,18 @@ export async function createDepositGroup( refreshReason: RefreshReason.PayDeposit, }); await tx.depositGroups.put(depositGroup); + return computeDepositTransactionStatus(depositGroup); }); + ws.notify({ + type: NotificationType.TransactionStateTransition, + transactionId, + oldTxState: { + major: TransactionMajorState.None, + }, + newTxState, + }); + return { depositGroupId, transactionId, diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 6a71b5c1e..884844ba6 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -35,10 +35,9 @@ import { Transaction, TransactionByIdRequest, TransactionIdStr, + TransactionMajorState, TransactionsRequest, TransactionsResponse, - TransactionState, - TransactionSubstate, TransactionType, WithdrawalType, } from "@gnu-taler/taler-util"; @@ -58,7 +57,7 @@ import { WalletContractData, PeerPushPaymentInitiationStatus, PeerPullPaymentIncomingStatus, - TransactionStatus, + DepositElementStatus, WithdrawalGroupStatus, RefreshGroupRecord, RefreshOperationStatus, @@ -79,7 +78,10 @@ import { runOperationWithErrorReporting, TombstoneTag, } from "./common.js"; -import { processDepositGroup } from "./deposits.js"; +import { + computeDepositTransactionStatus, + processDepositGroup, +} from "./deposits.js"; import { getExchangeDetails } from "./exchanges.js"; import { abortPay, @@ -425,6 +427,11 @@ export async function getTransactionById( } } +// FIXME: Just a marker helper for unknown states until DD37 is fully implemented. +const mkTxStateUnknown = () => ({ + major: TransactionMajorState.Unknown, +}); + function buildTransactionForPushPaymentDebit( pi: PeerPushPaymentInitiationRecord, contractTerms: PeerContractTerms, @@ -432,8 +439,7 @@ function buildTransactionForPushPaymentDebit( ): Transaction { return { type: TransactionType.PeerPushDebit, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), amountEffective: pi.totalCost, amountRaw: pi.amount, exchangeBaseUrl: pi.exchangeBaseUrl, @@ -466,8 +472,7 @@ function buildTransactionForPullPaymentDebit( ): Transaction { return { type: TransactionType.PeerPullDebit, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), amountEffective: pi.coinSel?.totalCost ? pi.coinSel?.totalCost : Amounts.stringify(pi.contractTerms.amount), @@ -517,8 +522,7 @@ function buildTransactionForPeerPullCredit( }); return { type: TransactionType.PeerPullCredit, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.instructedAmount), exchangeBaseUrl: wsr.exchangeBaseUrl, @@ -553,8 +557,7 @@ function buildTransactionForPeerPullCredit( return { type: TransactionType.PeerPullCredit, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), amountEffective: Amounts.stringify(pullCredit.estimatedAmountEffective), amountRaw: Amounts.stringify(peerContractTerms.amount), exchangeBaseUrl: pullCredit.exchangeBaseUrl, @@ -593,8 +596,7 @@ function buildTransactionForPeerPushCredit( return { type: TransactionType.PeerPushCredit, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.instructedAmount), exchangeBaseUrl: wsr.exchangeBaseUrl, @@ -618,8 +620,7 @@ function buildTransactionForPeerPushCredit( return { type: TransactionType.PeerPushCredit, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), // FIXME: This is wrong, needs to consider fees! amountEffective: Amounts.stringify(peerContractTerms.amount), amountRaw: Amounts.stringify(peerContractTerms.amount), @@ -649,8 +650,7 @@ function buildTransactionForBankIntegratedWithdraw( return { type: TransactionType.Withdrawal, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.instructedAmount), withdrawalDetails: { @@ -696,8 +696,7 @@ function buildTransactionForManualWithdraw( return { type: TransactionType.Withdrawal, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), amountEffective: Amounts.stringify( withdrawalGroup.denomsSel.totalCoinValue, ), @@ -748,8 +747,7 @@ function buildTransactionForRefresh( ).amount; return { type: TransactionType.Refresh, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), refreshReason: refreshGroupRecord.reason, amountEffective: Amounts.stringify( Amounts.zeroOfCurrency(refreshGroupRecord.currency), @@ -791,8 +789,7 @@ function buildTransactionForDeposit( return { type: TransactionType.Deposit, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: computeDepositTransactionStatus(dg), amountRaw: Amounts.stringify(dg.effectiveDepositAmount), amountEffective: Amounts.stringify(dg.totalPayCost), extendedStatus: dg.timestampFinished @@ -810,7 +807,7 @@ function buildTransactionForDeposit( wireTransferProgress: (100 * dg.transactionPerCoin.reduce( - (prev, cur) => prev + (cur === TransactionStatus.Wired ? 1 : 0), + (prev, cur) => prev + (cur === DepositElementStatus.Wired ? 1 : 0), 0, )) / dg.transactionPerCoin.length, @@ -829,8 +826,7 @@ function buildTransactionForTip( return { type: TransactionType.Tip, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), amountEffective: Amounts.stringify(tipRecord.tipAmountEffective), amountRaw: Amounts.stringify(tipRecord.tipAmountRaw), extendedStatus: tipRecord.pickedUpTimestamp @@ -926,8 +922,7 @@ async function buildTransactionForRefund( return { type: TransactionType.Refund, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), info, refundedTransactionId: makeTransactionId( TransactionType.Payment, @@ -1030,8 +1025,7 @@ async function buildTransactionForPurchase( return { type: TransactionType.Payment, - txState: TransactionState.Unknown, - txSubstate: TransactionSubstate.Unknown, + txState: mkTxStateUnknown(), amountRaw: Amounts.stringify(contractData.amount), amountEffective: Amounts.stringify(purchaseRecord.payInfo.totalPayCost), totalRefundRaw: Amounts.stringify(totalRefund.raw), diff --git a/packages/taler-wallet-core/src/util/retries.ts b/packages/taler-wallet-core/src/util/retries.ts index 5b6645924..a021087be 100644 --- a/packages/taler-wallet-core/src/util/retries.ts +++ b/packages/taler-wallet-core/src/util/retries.ts @@ -70,6 +70,12 @@ export namespace OperationAttemptResult { result: undefined, }; } + export function pendingEmpty(): OperationAttemptResult<unknown, unknown> { + return { + type: OperationAttemptResultType.Pending, + result: undefined, + }; + } } export interface OperationAttemptFinishedResult<T> { |