summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-04-22 14:17:49 +0200
committerFlorian Dold <florian@dold.me>2023-04-22 14:17:49 +0200
commit15feebecfeeda4758a96d1da99a98d9494c4bd2b (patch)
tree54a7536b93673202137927e097d4e5b5dcbc85eb /packages
parente331012c9f8efef86c6a8a9297b44a67ba8cda66 (diff)
downloadwallet-core-15feebecfeeda4758a96d1da99a98d9494c4bd2b.tar.gz
wallet-core-15feebecfeeda4758a96d1da99a98d9494c4bd2b.tar.bz2
wallet-core-15feebecfeeda4758a96d1da99a98d9494c4bd2b.zip
wallet-core: towards DD37 for deposits
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-harness/src/harness/harness.ts48
-rw-r--r--packages/taler-harness/src/index.ts70
-rw-r--r--packages/taler-harness/src/integrationtests/test-deposit.ts22
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-dbless.ts27
-rw-r--r--packages/taler-util/src/notifications.ts4
-rw-r--r--packages/taler-util/src/transaction-test-data.ts13
-rw-r--r--packages/taler-util/src/transactions-types.ts29
-rw-r--r--packages/taler-wallet-core/src/db.ts10
-rw-r--r--packages/taler-wallet-core/src/dbless.ts24
-rw-r--r--packages/taler-wallet-core/src/operations/deposits.ts114
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts56
-rw-r--r--packages/taler-wallet-core/src/util/retries.ts6
12 files changed, 313 insertions, 110 deletions
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts
index 0a898414d..8b74c9e9f 100644
--- a/packages/taler-harness/src/harness/harness.ts
+++ b/packages/taler-harness/src/harness/harness.ts
@@ -970,6 +970,19 @@ export class ExchangeService implements ExchangeServiceInterface {
);
}
+ async runAggregatorOnceWithTimetravel(opts: {
+ timetravelMicroseconds: number;
+ }) {
+ let timetravelArgArr = [];
+ timetravelArgArr.push(`--timetravel=${opts.timetravelMicroseconds}`);
+ await runCommand(
+ this.globalState,
+ `exchange-${this.name}-aggregator-once`,
+ "taler-exchange-aggregator",
+ [...timetravelArgArr, "-c", this.configFilename, "-t"],
+ );
+ }
+
async runAggregatorOnce() {
try {
await runCommand(
@@ -1147,6 +1160,9 @@ export class ExchangeService implements ExchangeServiceInterface {
exchangeHttpProc: ProcessWrapper | undefined;
exchangeWirewatchProc: ProcessWrapper | undefined;
+ exchangeTransferProc: ProcessWrapper | undefined;
+ exchangeAggregatorProc: ProcessWrapper | undefined;
+
helperCryptoRsaProc: ProcessWrapper | undefined;
helperCryptoEddsaProc: ProcessWrapper | undefined;
helperCryptoCsProc: ProcessWrapper | undefined;
@@ -1200,6 +1216,18 @@ export class ExchangeService implements ExchangeServiceInterface {
await wirewatch.wait();
this.exchangeWirewatchProc = undefined;
}
+ const aggregatorProc = this.exchangeAggregatorProc;
+ if (aggregatorProc) {
+ aggregatorProc.proc.kill("SIGTERM");
+ await aggregatorProc.wait();
+ this.exchangeAggregatorProc = undefined;
+ }
+ const transferProc = this.exchangeTransferProc;
+ if (transferProc) {
+ transferProc.proc.kill("SIGTERM");
+ await transferProc.wait();
+ this.exchangeTransferProc = undefined;
+ }
const httpd = this.exchangeHttpProc;
if (httpd) {
httpd.proc.kill("SIGTERM");
@@ -1369,6 +1397,22 @@ export class ExchangeService implements ExchangeServiceInterface {
);
}
+ private internalCreateAggregatorProc() {
+ this.exchangeAggregatorProc = this.globalState.spawnService(
+ "taler-exchange-aggregator",
+ ["-c", this.configFilename, ...this.timetravelArgArr],
+ `exchange-aggregator-${this.name}`,
+ );
+ }
+
+ private internalCreateTransferProc() {
+ this.exchangeTransferProc = this.globalState.spawnService(
+ "taler-exchange-transfer",
+ ["-c", this.configFilename, ...this.timetravelArgArr],
+ `exchange-transfer-${this.name}`,
+ );
+ }
+
async start(): Promise<void> {
if (this.isRunning()) {
throw Error("exchange is already running");
@@ -1398,6 +1442,8 @@ export class ExchangeService implements ExchangeServiceInterface {
);
this.internalCreateWirewatchProc();
+ this.internalCreateTransferProc();
+ this.internalCreateAggregatorProc();
this.exchangeHttpProc = this.globalState.spawnService(
"taler-exchange-httpd",
@@ -2062,7 +2108,7 @@ export class WalletService {
[
"--wallet-db",
dbPath,
- "-LDEBUG", // FIXME: Make this configurable?
+ "-LTRACE", // FIXME: Make this configurable?
"--no-throttle", // FIXME: Optionally do throttling for some tests?
"advanced",
"serve",
diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts
index 59fa80411..287e1f5be 100644
--- a/packages/taler-harness/src/index.ts
+++ b/packages/taler-harness/src/index.ts
@@ -47,7 +47,14 @@ import { lintExchangeDeployment } from "./lint.js";
import { runEnvFull } from "./env-full.js";
import { clk } from "@gnu-taler/taler-util/clk";
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
-import { BankAccessApiClient } from "@gnu-taler/taler-wallet-core";
+import {
+ BankAccessApiClient,
+ checkReserve,
+ CryptoDispatcher,
+ downloadExchangeInfo,
+ SynchronousCryptoWorkerFactoryPlain,
+ topupReserveWithDemobank,
+} from "@gnu-taler/taler-wallet-core";
const logger = new Logger("taler-harness:index.ts");
@@ -162,7 +169,6 @@ advancedCli
await runTestWithState(testState, runEnv1, "env1", true);
});
-
const sandcastleCli = testingCli.subcommand("sandcastleArgs", "sandcastle", {
help: "Subcommands for handling GNU Taler sandcastle deployments.",
});
@@ -261,6 +267,66 @@ deploymentCli
});
deploymentCli
+ .subcommand("testTalerdotnetDemo", "test-demo-talerdotnet")
+ .action(async (args) => {
+ const http = createPlatformHttpLib();
+ const cryptiDisp = new CryptoDispatcher(
+ new SynchronousCryptoWorkerFactoryPlain(),
+ );
+ const cryptoApi = cryptiDisp.cryptoApi;
+ const reserveKeyPair = await cryptoApi.createEddsaKeypair({});
+ const exchangeBaseUrl = "https://exchange.demo.taler.net/";
+ const exchangeInfo = await downloadExchangeInfo(exchangeBaseUrl, http);
+ await topupReserveWithDemobank({
+ amount: "KUDOS:10",
+ bankAccessApiBaseUrl:
+ "https://bank.demo.taler.net/demobanks/default/access-api/",
+ bankBaseUrl: "",
+ exchangeInfo,
+ http,
+ reservePub: reserveKeyPair.pub,
+ });
+ let reserveUrl = new URL(`reserves/${reserveKeyPair.pub}`, exchangeBaseUrl);
+ reserveUrl.searchParams.set("timeout_ms", "30000");
+ console.log("requesting", reserveUrl.href);
+ const longpollReq = http.fetch(reserveUrl.href, {
+ method: "GET",
+ });
+ const reserveStatusResp = await longpollReq;
+ console.log("reserve status", reserveStatusResp.status);
+ });
+
+deploymentCli
+ .subcommand("testLocalhostDemo", "test-demo-localhost")
+ .action(async (args) => {
+ // Run checks against the "env-full" demo deployment on localhost
+ const http = createPlatformHttpLib();
+ const cryptiDisp = new CryptoDispatcher(
+ new SynchronousCryptoWorkerFactoryPlain(),
+ );
+ const cryptoApi = cryptiDisp.cryptoApi;
+ const reserveKeyPair = await cryptoApi.createEddsaKeypair({});
+ const exchangeBaseUrl = "http://localhost:8081/";
+ const exchangeInfo = await downloadExchangeInfo(exchangeBaseUrl, http);
+ await topupReserveWithDemobank({
+ amount: "TESTKUDOS:10",
+ bankAccessApiBaseUrl: "http://localhost:8082/taler-bank-access/",
+ bankBaseUrl: "",
+ exchangeInfo,
+ http,
+ reservePub: reserveKeyPair.pub,
+ });
+ let reserveUrl = new URL(`reserves/${reserveKeyPair.pub}`, exchangeBaseUrl);
+ reserveUrl.searchParams.set("timeout_ms", "30000");
+ console.log("requesting", reserveUrl.href);
+ const longpollReq = http.fetch(reserveUrl.href, {
+ method: "GET",
+ });
+ const reserveStatusResp = await longpollReq;
+ console.log("reserve status", reserveStatusResp.status);
+ });
+
+deploymentCli
.subcommand("tipStatus", "tip-status")
.requiredOption("merchantBaseUrl", ["--merchant-url"], clk.STRING)
.requiredOption("merchantApikey", ["--merchant-apikey"], clk.STRING)
diff --git a/packages/taler-harness/src/integrationtests/test-deposit.ts b/packages/taler-harness/src/integrationtests/test-deposit.ts
index 6aa086107..8ea3fc12e 100644
--- a/packages/taler-harness/src/integrationtests/test-deposit.ts
+++ b/packages/taler-harness/src/integrationtests/test-deposit.ts
@@ -17,7 +17,11 @@
/**
* Imports.
*/
-import { NotificationType, TransactionState } from "@gnu-taler/taler-util";
+import {
+ NotificationType,
+ TransactionMajorState,
+ TransactionMinorState,
+} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { GlobalTestState, getPayto } from "../harness/harness.js";
import {
@@ -52,11 +56,19 @@ export async function runDepositTest(t: GlobalTestState) {
const depositTxId = dgIdResp.transactionId;
+ const depositTrack = walletClient.waitForNotificationCond(
+ (n) =>
+ n.type == NotificationType.TransactionStateTransition &&
+ n.transactionId == depositTxId &&
+ n.newTxState.major == TransactionMajorState.Pending &&
+ n.newTxState.minor == TransactionMinorState.Track,
+ );
+
const depositDone = walletClient.waitForNotificationCond(
(n) =>
n.type == NotificationType.TransactionStateTransition &&
n.transactionId == depositTxId &&
- n.newTxState == TransactionState.Done,
+ n.newTxState.major == TransactionMajorState.Done,
);
const depositGroupResult = await walletClient.client.call(
@@ -70,6 +82,12 @@ export async function runDepositTest(t: GlobalTestState) {
t.assertDeepEqual(depositGroupResult.transactionId, depositTxId);
+ await depositTrack;
+
+ await exchange.runAggregatorOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60 * 3,
+ });
+
await depositDone;
const transactions = await walletClient.client.call(
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts
index a1de7617b..35c60a89d 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts
@@ -59,16 +59,29 @@ export async function runWalletDblessTest(t: GlobalTestState) {
const reserveKeyPair = await cryptoApi.createEddsaKeypair({});
- await topupReserveWithDemobank(
+ let reserveUrl = new URL(
+ `reserves/${reserveKeyPair.pub}`,
+ exchange.baseUrl,
+ );
+ reserveUrl.searchParams.set("timeout_ms", "30000");
+ const longpollReq = http.fetch(reserveUrl.href, {
+ method: "GET",
+ });
+
+ await topupReserveWithDemobank({
+ amount: "TESTKUDOS:10",
http,
- reserveKeyPair.pub,
- bank.baseUrl,
- bank.bankAccessApiBaseUrl,
+ reservePub: reserveKeyPair.pub,
+ bankAccessApiBaseUrl: bank.bankAccessApiBaseUrl,
+ bankBaseUrl: bank.baseUrl,
exchangeInfo,
- "TESTKUDOS:10",
- );
+ });
+
+ console.log("waiting for longpoll request");
+ const resp = await longpollReq;
+ console.log(`got response, status ${resp.status}`);
- await exchange.runWirewatchOnce();
+ console.log(exchangeInfo);
await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub);
diff --git a/packages/taler-util/src/notifications.ts b/packages/taler-util/src/notifications.ts
index ff1017cd1..f0683b31b 100644
--- a/packages/taler-util/src/notifications.ts
+++ b/packages/taler-util/src/notifications.ts
@@ -22,7 +22,7 @@
/**
* Imports.
*/
-import { TransactionState, TransactionSubstate } from "./transactions-types.js";
+import { TransactionState } from "./transactions-types.js";
import { TalerErrorDetail } from "./wallet-types.js";
export enum NotificationType {
@@ -75,9 +75,7 @@ export interface TransactionStateTransitionNotification {
type: NotificationType.TransactionStateTransition;
transactionId: string;
oldTxState: TransactionState;
- oldTxSubstate: TransactionSubstate;
newTxState: TransactionState;
- newTxSubstate: TransactionSubstate;
}
export interface ProposalAcceptedNotification {
diff --git a/packages/taler-util/src/transaction-test-data.ts b/packages/taler-util/src/transaction-test-data.ts
index d0c609147..dc0903f53 100644
--- a/packages/taler-util/src/transaction-test-data.ts
+++ b/packages/taler-util/src/transaction-test-data.ts
@@ -16,10 +16,9 @@
import {
TransactionType,
- TransactionState,
- TransactionSubstate,
PaymentStatus,
ExtendedStatus,
+ TransactionMajorState,
} from "./transactions-types.js";
import { RefreshReason } from "./wallet-types.js";
@@ -29,8 +28,9 @@ import { RefreshReason } from "./wallet-types.js";
export const sampleWalletCoreTransactions = [
{
type: TransactionType.Payment,
- txState: TransactionState.Done,
- txSubstate: TransactionSubstate.None,
+ txState: {
+ major: TransactionMajorState.Done,
+ },
amountRaw: "KUDOS:10",
amountEffective: "KUDOS:10",
totalRefundRaw: "KUDOS:0",
@@ -75,8 +75,9 @@ export const sampleWalletCoreTransactions = [
},
{
type: TransactionType.Refresh,
- txState: TransactionState.Pending,
- txSubstate: TransactionSubstate.None,
+ txState: {
+ major: TransactionMajorState.Pending,
+ },
refreshReason: RefreshReason.PayMerchant,
amountEffective: "KUDOS:0",
amountRaw: "KUDOS:0",
diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts
index 474372c4f..29ddb22a3 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -59,11 +59,6 @@ export enum ExtendedStatus {
KycRequired = "kyc-required",
}
-export interface TransactionStateInfo {
- txState: TransactionState;
- txSubstate: TransactionSubstate;
-}
-
export interface TransactionsRequest {
/**
* return only transactions in the given currency
@@ -81,7 +76,12 @@ export interface TransactionsRequest {
includeRefreshes?: boolean;
}
-export enum TransactionState {
+export interface TransactionState {
+ major: TransactionMajorState;
+ minor?: TransactionMinorState;
+}
+
+export enum TransactionMajorState {
// No state, only used when reporting transitions into the initial state
None = "none",
Pending = "pending",
@@ -96,15 +96,13 @@ export enum TransactionState {
Unknown = "unknown",
}
-export enum TransactionSubstate {
+export enum TransactionMinorState {
// Placeholder until D37 is fully implemented
Unknown = "unknown",
- // No substate
- None = "none",
- DepositPendingInitial = "initial",
- DepositKycRequired = "kyc-required",
- DepositPendingTrack = "track",
- DepositAbortingRefresh = "refresh",
+ Deposit = "deposit",
+ KycRequired = "kyc-required",
+ Track = "track",
+ Refresh = "refresh",
}
export interface TransactionsResponse {
@@ -126,10 +124,11 @@ export interface TransactionCommon {
// main timestamp of the transaction
timestamp: TalerProtocolTimestamp;
+ /**
+ * Transaction state, as per DD37.
+ */
txState: TransactionState;
- txSubstate: TransactionSubstate;
-
/**
* @deprecated in favor of statusMajor and statusMinor
*/
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> {