From fc38d0da958323b994d2e4f8a8f2e9632865557f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 15 Jan 2023 17:48:41 -0300 Subject: query transaction status for deposit --- packages/taler-wallet-core/src/db.ts | 23 +++ .../taler-wallet-core/src/operations/deposits.ts | 200 ++++++++++++++------- .../src/operations/transactions.ts | 8 + 3 files changed, 163 insertions(+), 68 deletions(-) (limited to 'packages/taler-wallet-core') diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index adf704bc4..e6131334c 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -850,6 +850,13 @@ export enum RefreshOperationStatus { FinishedWithError = 51 /* DORMANT_START + 1 */, } +export enum TransactionStatus { + Unknown = 10, + Accepted = 20, + KycRequired = 30, + Wired = 40, +} + /** * Additional information about the reason of a refresh. */ @@ -1652,6 +1659,8 @@ export interface DepositGroupRecord { timestampFinished: TalerProtocolTimestamp | undefined; operationStatus: OperationStatus; + + transactionPerCoin: TransactionStatus[]; } /** @@ -2416,6 +2425,20 @@ export const walletDbFixups: FixupDescription[] = [ }); }, }, + { + name: "DepositGroupRecord_transactionPerCoin", + async fn(tx): Promise { + await tx.depositGroups.iter().forEachAsync(async (dg) => { + if (dg.transactionPerCoin) { + return; + } + dg.transactionPerCoin = dg.depositedPerCoin.map( + (c) => TransactionStatus.Unknown, + ); + await tx.depositGroups.put(dg); + }); + }, + }, ]; const logger = new Logger("db.ts"); diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 649621948..b529e5ead 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -24,7 +24,9 @@ import { CancellationToken, canonicalJson, codecForDepositSuccess, - MerchantContractTerms, + codecForTackTransactionAccepted, + codecForTackTransactionWired, + CoinDepositPermission, CreateDepositGroupRequest, CreateDepositGroupResponse, DepositGroupFees, @@ -34,23 +36,27 @@ import { GetFeeForDepositRequest, getRandomBytes, hashWire, + HttpStatusCode, Logger, + MerchantContractTerms, parsePaytoUri, PayCoinSelection, PrepareDepositRequest, PrepareDepositResponse, RefreshReason, + TalerErrorCode, TalerProtocolTimestamp, TrackDepositGroupRequest, TrackDepositGroupResponse, + TrackTransaction, TransactionType, URL, - TalerErrorCode, } from "@gnu-taler/taler-util"; import { DenominationRecord, DepositGroupRecord, OperationStatus, + TransactionStatus, } from "../db.js"; import { TalerError } from "../errors.js"; import { InternalWalletState } from "../internal-wallet-state.js"; @@ -111,43 +117,60 @@ export async function processDepositGroup( ); for (let i = 0; i < depositPermissions.length; i++) { - if (depositGroup.depositedPerCoin[i]) { - continue; - } const perm = depositPermissions[i]; - const requestBody: ExchangeDepositRequest = { - contribution: Amounts.stringify(perm.contribution), - merchant_payto_uri: depositGroup.wire.payto_uri, - wire_salt: depositGroup.wire.salt, - h_contract_terms: depositGroup.contractTermsHash, - ub_sig: perm.ub_sig, - timestamp: depositGroup.contractTermsRaw.timestamp, - wire_transfer_deadline: - depositGroup.contractTermsRaw.wire_transfer_deadline, - refund_deadline: depositGroup.contractTermsRaw.refund_deadline, - coin_sig: perm.coin_sig, - denom_pub_hash: perm.h_denom, - merchant_pub: depositGroup.merchantPub, - h_age_commitment: perm.h_age_commitment, - }; - // Check for cancellation before making network request. - options.cancellationToken?.throwIfCancelled(); - const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url); - logger.info(`depositing to ${url}`); - const httpResp = await ws.http.postJson(url.href, requestBody, { - cancellationToken: options.cancellationToken, - }); - await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess()); - await ws.db - .mktx((x) => [x.depositGroups]) - .runReadWrite(async (tx) => { - const dg = await tx.depositGroups.get(depositGroupId); - if (!dg) { - return; - } - dg.depositedPerCoin[i] = true; - await tx.depositGroups.put(dg); + + let updatedDeposit: boolean | undefined = undefined; + let updatedTxStatus: TransactionStatus | undefined = undefined; + + if (!depositGroup.depositedPerCoin[i]) { + const requestBody: ExchangeDepositRequest = { + contribution: Amounts.stringify(perm.contribution), + merchant_payto_uri: depositGroup.wire.payto_uri, + wire_salt: depositGroup.wire.salt, + h_contract_terms: depositGroup.contractTermsHash, + ub_sig: perm.ub_sig, + timestamp: depositGroup.contractTermsRaw.timestamp, + wire_transfer_deadline: + depositGroup.contractTermsRaw.wire_transfer_deadline, + refund_deadline: depositGroup.contractTermsRaw.refund_deadline, + coin_sig: perm.coin_sig, + denom_pub_hash: perm.h_denom, + merchant_pub: depositGroup.merchantPub, + h_age_commitment: perm.h_age_commitment, + }; + // Check for cancellation before making network request. + options.cancellationToken?.throwIfCancelled(); + const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url); + logger.info(`depositing to ${url}`); + const httpResp = await ws.http.postJson(url.href, requestBody, { + cancellationToken: options.cancellationToken, }); + await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess()); + updatedDeposit = true; + } + + if (depositGroup.transactionPerCoin[i] !== TransactionStatus.Wired) { + const track = await trackDepositPermission(ws, depositGroup, perm); + updatedTxStatus = txStatusFromTrack(track); + } + + if (updatedTxStatus !== undefined || updatedDeposit !== undefined) { + await ws.db + .mktx((x) => [x.depositGroups]) + .runReadWrite(async (tx) => { + const dg = await tx.depositGroups.get(depositGroupId); + if (!dg) { + return; + } + if (updatedDeposit !== undefined) { + dg.depositedPerCoin[i] = updatedDeposit; + } + if (updatedTxStatus !== undefined) { + dg.transactionPerCoin[i] = updatedTxStatus; + } + await tx.depositGroups.put(dg); + }); + } } await ws.db @@ -157,13 +180,17 @@ export async function processDepositGroup( if (!dg) { return; } - let allDeposited = true; - for (const d of depositGroup.depositedPerCoin) { - if (!d) { - allDeposited = false; + let allDepositedAndWired = true; + for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) { + if ( + !depositGroup.depositedPerCoin[i] || + depositGroup.transactionPerCoin[i] !== TransactionStatus.Wired + ) { + allDepositedAndWired = false; + break; } } - if (allDeposited) { + if (allDepositedAndWired) { dg.timestampFinished = TalerProtocolTimestamp.now(); dg.operationStatus = OperationStatus.Finished; await tx.depositGroups.put(dg); @@ -172,14 +199,24 @@ export async function processDepositGroup( return OperationAttemptResult.finishedEmpty(); } +function txStatusFromTrack(t: TrackTransaction): TransactionStatus { + if (t.type === "accepted") { + if (!t.kyc_ok && t.requirement_row !== undefined) { + return TransactionStatus.KycRequired; + } + return TransactionStatus.Accepted; + } + if (t.type === "wired") { + return TransactionStatus.Wired; + } + return TransactionStatus.Unknown; +} + export async function trackDepositGroup( ws: InternalWalletState, req: TrackDepositGroupRequest, ): Promise { - const responses: { - status: number; - body: any; - }[] = []; + const responses: TrackTransaction[] = []; const depositGroup = await ws.db .mktx((x) => [x.depositGroups]) .runReadOnly(async (tx) => { @@ -200,31 +237,55 @@ export async function trackDepositGroup( contractData, ); + for (const dp of depositPermissions) { + const track = await trackDepositPermission(ws, depositGroup, dp); + responses.push(track); + } + + return { responses }; +} + +async function trackDepositPermission( + ws: InternalWalletState, + depositGroup: DepositGroupRecord, + dp: CoinDepositPermission, +): Promise { const wireHash = depositGroup.contractTermsRaw.h_wire; - for (const dp of depositPermissions) { - const url = new URL( - `deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`, - dp.exchange_url, - ); - const sigResp = await ws.cryptoApi.signTrackTransaction({ - coinPub: dp.coin_pub, - contractTermsHash: depositGroup.contractTermsHash, - merchantPriv: depositGroup.merchantPriv, - merchantPub: depositGroup.merchantPub, - wireHash, - }); - url.searchParams.set("merchant_sig", sigResp.sig); - const httpResp = await ws.http.get(url.href); - const body = await httpResp.json(); - responses.push({ - body, - status: httpResp.status, - }); + const url = new URL( + `deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`, + dp.exchange_url, + ); + const sigResp = await ws.cryptoApi.signTrackTransaction({ + coinPub: dp.coin_pub, + contractTermsHash: depositGroup.contractTermsHash, + merchantPriv: depositGroup.merchantPriv, + merchantPub: depositGroup.merchantPub, + wireHash, + }); + url.searchParams.set("merchant_sig", sigResp.sig); + const httpResp = await ws.http.get(url.href); + switch (httpResp.status) { + case HttpStatusCode.Accepted: { + const accepted = await readSuccessResponseJsonOrThrow( + httpResp, + codecForTackTransactionAccepted(), + ); + return { type: "accepted", ...accepted }; + } + case HttpStatusCode.Ok: { + const wired = await readSuccessResponseJsonOrThrow( + httpResp, + codecForTackTransactionWired(), + ); + return { type: "wired", ...wired }; + } + default: { + throw Error( + `unexpected response from track-transaction (${httpResp.status})`, + ); + } } - return { - responses, - }; } export async function getFeeForDeposit( @@ -491,6 +552,9 @@ export async function createDepositGroup( noncePub: noncePair.pub, timestampCreated: AbsoluteTime.toTimestamp(now), timestampFinished: undefined, + transactionPerCoin: payCoinSel.coinSel.coinPubs.map( + () => TransactionStatus.Unknown, + ), payCoinSelection: payCoinSel.coinSel, payCoinSelectionUid: encodeCrock(getRandomBytes(32)), depositedPerCoin: payCoinSel.coinSel.coinPubs.map(() => false), diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index a702fab2f..0e86c77ed 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -53,6 +53,7 @@ import { WalletContractData, PeerPushPaymentInitiationStatus, PeerPullPaymentIncomingStatus, + TransactionStatus, } from "../db.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; @@ -552,6 +553,13 @@ function buildTransactionForDeposit( TransactionType.Deposit, dg.depositGroupId, ), + wireTransferProgress: + (100 * + dg.transactionPerCoin.reduce( + (prev, cur) => prev + (cur === TransactionStatus.Wired ? 1 : 0), + 0, + )) / + dg.transactionPerCoin.length, depositGroupId: dg.depositGroupId, ...(ort?.lastError ? { error: ort.lastError } : {}), }; -- cgit v1.2.3