aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core
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/taler-wallet-core
parente331012c9f8efef86c6a8a9297b44a67ba8cda66 (diff)
downloadwallet-core-15feebecfeeda4758a96d1da99a98d9494c4bd2b.tar.xz
wallet-core: towards DD37 for deposits
Diffstat (limited to 'packages/taler-wallet-core')
-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
5 files changed, 136 insertions, 74 deletions
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> {