aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-01-15 17:48:41 -0300
committerSebastian <sebasjm@gmail.com>2023-01-15 17:48:41 -0300
commitfc38d0da958323b994d2e4f8a8f2e9632865557f (patch)
tree46a03b93095fe6f69b78fd3d15b20926c65f94a8
parente034f1045c48a4965490e600122e38f5304ad8da (diff)
downloadwallet-core-fc38d0da958323b994d2e4f8a8f2e9632865557f.tar.xz
query transaction status for deposit
-rw-r--r--packages/taler-wallet-core/src/db.ts23
-rw-r--r--packages/taler-wallet-core/src/operations/deposits.ts200
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts8
3 files changed, 163 insertions, 68 deletions
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<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");
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<TrackDepositGroupResponse> {
- 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<TrackTransaction> {
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 } : {}),
};