query transaction status for deposit

This commit is contained in:
Sebastian 2023-01-15 17:48:41 -03:00
parent e034f1045c
commit fc38d0da95
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
3 changed files with 163 additions and 68 deletions

View File

@ -850,6 +850,13 @@ export enum RefreshOperationStatus {
FinishedWithError = 51 /* DORMANT_START + 1 */, FinishedWithError = 51 /* DORMANT_START + 1 */,
} }
export enum TransactionStatus {
Unknown = 10,
Accepted = 20,
KycRequired = 30,
Wired = 40,
}
/** /**
* Additional information about the reason of a refresh. * Additional information about the reason of a refresh.
*/ */
@ -1652,6 +1659,8 @@ export interface DepositGroupRecord {
timestampFinished: TalerProtocolTimestamp | undefined; timestampFinished: TalerProtocolTimestamp | undefined;
operationStatus: OperationStatus; operationStatus: OperationStatus;
transactionPerCoin: TransactionStatus[];
} }
/** /**
@ -2416,6 +2425,20 @@ export const walletDbFixups: FixupDescription[] = [
}); });
}, },
}, },
{
name: "DepositGroupRecord_transactionPerCoin",
async fn(tx): Promise<void> {
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"); const logger = new Logger("db.ts");

View File

@ -24,7 +24,9 @@ import {
CancellationToken, CancellationToken,
canonicalJson, canonicalJson,
codecForDepositSuccess, codecForDepositSuccess,
MerchantContractTerms, codecForTackTransactionAccepted,
codecForTackTransactionWired,
CoinDepositPermission,
CreateDepositGroupRequest, CreateDepositGroupRequest,
CreateDepositGroupResponse, CreateDepositGroupResponse,
DepositGroupFees, DepositGroupFees,
@ -34,23 +36,27 @@ import {
GetFeeForDepositRequest, GetFeeForDepositRequest,
getRandomBytes, getRandomBytes,
hashWire, hashWire,
HttpStatusCode,
Logger, Logger,
MerchantContractTerms,
parsePaytoUri, parsePaytoUri,
PayCoinSelection, PayCoinSelection,
PrepareDepositRequest, PrepareDepositRequest,
PrepareDepositResponse, PrepareDepositResponse,
RefreshReason, RefreshReason,
TalerErrorCode,
TalerProtocolTimestamp, TalerProtocolTimestamp,
TrackDepositGroupRequest, TrackDepositGroupRequest,
TrackDepositGroupResponse, TrackDepositGroupResponse,
TrackTransaction,
TransactionType, TransactionType,
URL, URL,
TalerErrorCode,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { import {
DenominationRecord, DenominationRecord,
DepositGroupRecord, DepositGroupRecord,
OperationStatus, OperationStatus,
TransactionStatus,
} from "../db.js"; } from "../db.js";
import { TalerError } from "../errors.js"; import { TalerError } from "../errors.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
@ -111,10 +117,12 @@ export async function processDepositGroup(
); );
for (let i = 0; i < depositPermissions.length; i++) { for (let i = 0; i < depositPermissions.length; i++) {
if (depositGroup.depositedPerCoin[i]) {
continue;
}
const perm = depositPermissions[i]; const perm = depositPermissions[i];
let updatedDeposit: boolean | undefined = undefined;
let updatedTxStatus: TransactionStatus | undefined = undefined;
if (!depositGroup.depositedPerCoin[i]) {
const requestBody: ExchangeDepositRequest = { const requestBody: ExchangeDepositRequest = {
contribution: Amounts.stringify(perm.contribution), contribution: Amounts.stringify(perm.contribution),
merchant_payto_uri: depositGroup.wire.payto_uri, merchant_payto_uri: depositGroup.wire.payto_uri,
@ -138,6 +146,15 @@ export async function processDepositGroup(
cancellationToken: options.cancellationToken, cancellationToken: options.cancellationToken,
}); });
await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess()); 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 await ws.db
.mktx((x) => [x.depositGroups]) .mktx((x) => [x.depositGroups])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
@ -145,10 +162,16 @@ export async function processDepositGroup(
if (!dg) { if (!dg) {
return; return;
} }
dg.depositedPerCoin[i] = true; if (updatedDeposit !== undefined) {
dg.depositedPerCoin[i] = updatedDeposit;
}
if (updatedTxStatus !== undefined) {
dg.transactionPerCoin[i] = updatedTxStatus;
}
await tx.depositGroups.put(dg); await tx.depositGroups.put(dg);
}); });
} }
}
await ws.db await ws.db
.mktx((x) => [x.depositGroups]) .mktx((x) => [x.depositGroups])
@ -157,13 +180,17 @@ export async function processDepositGroup(
if (!dg) { if (!dg) {
return; return;
} }
let allDeposited = true; let allDepositedAndWired = true;
for (const d of depositGroup.depositedPerCoin) { for (let i = 0; i < depositGroup.depositedPerCoin.length; i++) {
if (!d) { if (
allDeposited = false; !depositGroup.depositedPerCoin[i] ||
depositGroup.transactionPerCoin[i] !== TransactionStatus.Wired
) {
allDepositedAndWired = false;
break;
} }
} }
if (allDeposited) { if (allDepositedAndWired) {
dg.timestampFinished = TalerProtocolTimestamp.now(); dg.timestampFinished = TalerProtocolTimestamp.now();
dg.operationStatus = OperationStatus.Finished; dg.operationStatus = OperationStatus.Finished;
await tx.depositGroups.put(dg); await tx.depositGroups.put(dg);
@ -172,14 +199,24 @@ export async function processDepositGroup(
return OperationAttemptResult.finishedEmpty(); 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( export async function trackDepositGroup(
ws: InternalWalletState, ws: InternalWalletState,
req: TrackDepositGroupRequest, req: TrackDepositGroupRequest,
): Promise<TrackDepositGroupResponse> { ): Promise<TrackDepositGroupResponse> {
const responses: { const responses: TrackTransaction[] = [];
status: number;
body: any;
}[] = [];
const depositGroup = await ws.db const depositGroup = await ws.db
.mktx((x) => [x.depositGroups]) .mktx((x) => [x.depositGroups])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
@ -200,9 +237,21 @@ export async function trackDepositGroup(
contractData, 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<TrackTransaction> {
const wireHash = depositGroup.contractTermsRaw.h_wire; const wireHash = depositGroup.contractTermsRaw.h_wire;
for (const dp of depositPermissions) {
const url = new URL( const url = new URL(
`deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`, `deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`,
dp.exchange_url, dp.exchange_url,
@ -216,15 +265,27 @@ export async function trackDepositGroup(
}); });
url.searchParams.set("merchant_sig", sigResp.sig); url.searchParams.set("merchant_sig", sigResp.sig);
const httpResp = await ws.http.get(url.href); const httpResp = await ws.http.get(url.href);
const body = await httpResp.json(); switch (httpResp.status) {
responses.push({ case HttpStatusCode.Accepted: {
body, const accepted = await readSuccessResponseJsonOrThrow(
status: httpResp.status, 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( export async function getFeeForDeposit(
@ -491,6 +552,9 @@ export async function createDepositGroup(
noncePub: noncePair.pub, noncePub: noncePair.pub,
timestampCreated: AbsoluteTime.toTimestamp(now), timestampCreated: AbsoluteTime.toTimestamp(now),
timestampFinished: undefined, timestampFinished: undefined,
transactionPerCoin: payCoinSel.coinSel.coinPubs.map(
() => TransactionStatus.Unknown,
),
payCoinSelection: payCoinSel.coinSel, payCoinSelection: payCoinSel.coinSel,
payCoinSelectionUid: encodeCrock(getRandomBytes(32)), payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
depositedPerCoin: payCoinSel.coinSel.coinPubs.map(() => false), depositedPerCoin: payCoinSel.coinSel.coinPubs.map(() => false),

View File

@ -53,6 +53,7 @@ import {
WalletContractData, WalletContractData,
PeerPushPaymentInitiationStatus, PeerPushPaymentInitiationStatus,
PeerPullPaymentIncomingStatus, PeerPullPaymentIncomingStatus,
TransactionStatus,
} from "../db.js"; } from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
import { assertUnreachable } from "../util/assertUnreachable.js"; import { assertUnreachable } from "../util/assertUnreachable.js";
@ -552,6 +553,13 @@ function buildTransactionForDeposit(
TransactionType.Deposit, TransactionType.Deposit,
dg.depositGroupId, dg.depositGroupId,
), ),
wireTransferProgress:
(100 *
dg.transactionPerCoin.reduce(
(prev, cur) => prev + (cur === TransactionStatus.Wired ? 1 : 0),
0,
)) /
dg.transactionPerCoin.length,
depositGroupId: dg.depositGroupId, depositGroupId: dg.depositGroupId,
...(ort?.lastError ? { error: ort.lastError } : {}), ...(ort?.lastError ? { error: ort.lastError } : {}),
}; };