wallet-core: towards DD37 for deposits
This commit is contained in:
parent
e331012c9f
commit
15feebecfe
@ -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",
|
||||
|
@ -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.",
|
||||
});
|
||||
@ -260,6 +266,66 @@ deploymentCli
|
||||
// FIXME: Now delete reserves that are not filled yet
|
||||
});
|
||||
|
||||
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)
|
||||
|
@ -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(
|
||||
|
@ -59,16 +59,29 @@ export async function runWalletDblessTest(t: GlobalTestState) {
|
||||
|
||||
const reserveKeyPair = await cryptoApi.createEddsaKeypair({});
|
||||
|
||||
await topupReserveWithDemobank(
|
||||
http,
|
||||
reserveKeyPair.pub,
|
||||
bank.baseUrl,
|
||||
bank.bankAccessApiBaseUrl,
|
||||
exchangeInfo,
|
||||
"TESTKUDOS:10",
|
||||
let reserveUrl = new URL(
|
||||
`reserves/${reserveKeyPair.pub}`,
|
||||
exchange.baseUrl,
|
||||
);
|
||||
reserveUrl.searchParams.set("timeout_ms", "30000");
|
||||
const longpollReq = http.fetch(reserveUrl.href, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
await exchange.runWirewatchOnce();
|
||||
await topupReserveWithDemobank({
|
||||
amount: "TESTKUDOS:10",
|
||||
http,
|
||||
reservePub: reserveKeyPair.pub,
|
||||
bankAccessApiBaseUrl: bank.bankAccessApiBaseUrl,
|
||||
bankBaseUrl: bank.baseUrl,
|
||||
exchangeInfo,
|
||||
});
|
||||
|
||||
console.log("waiting for longpoll request");
|
||||
const resp = await longpollReq;
|
||||
console.log(`got response, status ${resp.status}`);
|
||||
|
||||
console.log(exchangeInfo);
|
||||
|
||||
await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
// 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,6 +790,16 @@ 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 {
|
||||
|
@ -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),
|
||||
|
@ -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> {
|
||||
|
Loading…
Reference in New Issue
Block a user