aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-11-23 12:57:18 +0100
committerFlorian Dold <florian@dold.me>2023-11-23 12:57:18 +0100
commita0d746ad8d80490f9c2f1e017ff0c6a56b7d435c (patch)
treedc1f8ecebb07325c626ee06d1a7311faf09a0847
parent305c513c2bcc2b25fe57cf0ed9723781944f9f3f (diff)
downloadwallet-core-a0d746ad8d80490f9c2f1e017ff0c6a56b7d435c.tar.xz
wallet-core: implement balance flags for UI badges
-rw-r--r--packages/taler-util/src/wallet-types.ts17
-rw-r--r--packages/taler-wallet-core/src/db.ts14
-rw-r--r--packages/taler-wallet-core/src/operations/balance.ts120
-rw-r--r--packages/taler-wallet-core/src/operations/pending.ts23
4 files changed, 123 insertions, 51 deletions
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index 346528029..ba6f53cca 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -374,19 +374,31 @@ export const codecForAmountResponse = (): Codec<AmountResponse> =>
.property("rawAmount", codecForAmountString())
.build("AmountResponse");
+export enum BalanceFlag {
+ IncomingKyc = "incoming-kyc",
+ IncomingAml = "incoming-aml",
+ IncomingConfirmation = "incoming-confirmation",
+ OutgoingKyc = "outgoing-kyc",
+}
export interface WalletBalance {
scopeInfo: ScopeInfo;
available: AmountString;
pendingIncoming: AmountString;
pendingOutgoing: AmountString;
- // Does the balance for this currency have a pending
- // transaction?
+ /**
+ * Does the balance for this currency have a pending
+ * transaction?
+ *
+ * FIXME: Represent as a flag!
+ */
hasPendingTransactions: boolean;
// Is there a pending transaction that would affect the balance
// and requires user input?
requiresUserInput: boolean;
+
+ flags: BalanceFlag[];
}
export const codecForScopeInfoGlobal = (): Codec<ScopeInfoGlobal> =>
@@ -481,6 +493,7 @@ export const codecForBalance = (): Codec<WalletBalance> =>
.property("pendingIncoming", codecForAmountString())
.property("pendingOutgoing", codecForAmountString())
.property("requiresUserInput", codecForBoolean())
+ .property("flags", codecForAny()) // FIXME
.build("Balance");
export const codecForBalancesResponse = (): Codec<BalancesResponse> =>
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 0cafae2a1..279b75bf5 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -19,6 +19,7 @@
*/
import {
Event,
+ GlobalIDB,
IDBDatabase,
IDBFactory,
IDBObjectStore,
@@ -326,6 +327,14 @@ export enum WithdrawalGroupStatus {
}
/**
+ * Status range of nonfinal withdrawal groups.
+ */
+export const withdrawalGroupNonfinalRange = GlobalIDB.KeyRange.bound(
+ WithdrawalGroupStatus.PendingRegisteringBank,
+ WithdrawalGroupStatus.PendingAml,
+);
+
+/**
* Extra info about a withdrawal that is used
* with a bank-integrated withdrawal.
*/
@@ -1686,6 +1695,11 @@ export enum DepositOperationStatus {
Aborted = 0x0503_0000,
}
+export const depositOperationNonfinalStatusRange = GlobalIDB.KeyRange.bound(
+ DepositOperationStatus.PendingDeposit,
+ DepositOperationStatus.PendingKyc,
+);
+
export interface DepositTrackingInfo {
// Raw wire transfer identifier of the deposit.
wireTransferId: string;
diff --git a/packages/taler-wallet-core/src/operations/balance.ts b/packages/taler-wallet-core/src/operations/balance.ts
index 8034f78ea..6d396f6dd 100644
--- a/packages/taler-wallet-core/src/operations/balance.ts
+++ b/packages/taler-wallet-core/src/operations/balance.ts
@@ -54,6 +54,7 @@ import {
AllowedExchangeInfo,
AmountJson,
Amounts,
+ BalanceFlag,
BalancesResponse,
canonicalizeBaseUrl,
GetBalanceDetailRequest,
@@ -62,8 +63,11 @@ import {
ScopeType,
} from "@gnu-taler/taler-util";
import {
+ depositOperationNonfinalStatusRange,
+ DepositOperationStatus,
RefreshGroupRecord,
WalletStoresV1,
+ withdrawalGroupNonfinalRange,
WithdrawalGroupStatus,
} from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js";
@@ -81,6 +85,10 @@ interface WalletBalance {
available: AmountJson;
pendingIncoming: AmountJson;
pendingOutgoing: AmountJson;
+ flagIncomingKyc: boolean;
+ flagIncomingAml: boolean;
+ flagIncomingConfirmation: boolean;
+ flagOutgoingKyc: boolean;
}
/**
@@ -109,6 +117,7 @@ export async function getBalancesInsideTransaction(
coinAvailability: typeof WalletStoresV1.coinAvailability;
refreshGroups: typeof WalletStoresV1.refreshGroups;
withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
+ depositGroups: typeof WalletStoresV1.depositGroups;
}>,
): Promise<BalancesResponse> {
const balanceStore: Record<string, WalletBalance> = {};
@@ -124,6 +133,10 @@ export async function getBalancesInsideTransaction(
available: Amounts.zeroOfCurrency(currency),
pendingIncoming: Amounts.zeroOfCurrency(currency),
pendingOutgoing: Amounts.zeroOfCurrency(currency),
+ flagIncomingAml: false,
+ flagIncomingConfirmation: false,
+ flagIncomingKyc: false,
+ flagOutgoingKyc: false,
};
}
return balanceStore[currency];
@@ -145,42 +158,65 @@ export async function getBalancesInsideTransaction(
).amount;
});
+ await tx.withdrawalGroups.indexes.byStatus
+ .iter(withdrawalGroupNonfinalRange)
+ .forEach((wgRecord) => {
+ const b = initBalance(
+ Amounts.currencyOf(wgRecord.denomsSel.totalWithdrawCost),
+ );
+ switch (wgRecord.status) {
+ case WithdrawalGroupStatus.AbortedBank:
+ case WithdrawalGroupStatus.AbortedExchange:
+ case WithdrawalGroupStatus.FailedAbortingBank:
+ case WithdrawalGroupStatus.FailedBankAborted:
+ case WithdrawalGroupStatus.Done:
+ // Does not count as pendingIncoming
+ return;
+ case WithdrawalGroupStatus.PendingReady:
+ case WithdrawalGroupStatus.AbortingBank:
+ case WithdrawalGroupStatus.PendingAml:
+ b.flagIncomingAml = true;
+ break;
+ case WithdrawalGroupStatus.PendingKyc:
+ b.flagIncomingKyc = true;
+ break;
+ case WithdrawalGroupStatus.PendingQueryingStatus:
+ case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
+ case WithdrawalGroupStatus.SuspendedReady:
+ case WithdrawalGroupStatus.SuspendedRegisteringBank:
+ case WithdrawalGroupStatus.SuspendedKyc:
+ b.flagIncomingKyc = true;
+ break;
+ case WithdrawalGroupStatus.SuspendedAbortingBank:
+ case WithdrawalGroupStatus.SuspendedAml:
+ b.flagIncomingAml = true;
+ break;
+ case WithdrawalGroupStatus.PendingRegisteringBank:
+ case WithdrawalGroupStatus.PendingWaitConfirmBank:
+ b.flagIncomingConfirmation = true;
+ break;
+ case WithdrawalGroupStatus.SuspendedQueryingStatus:
+ break;
+ default:
+ assertUnreachable(wgRecord.status);
+ }
+ b.pendingIncoming = Amounts.add(
+ b.pendingIncoming,
+ wgRecord.denomsSel.totalCoinValue,
+ ).amount;
+ });
+
// FIXME: Use indexing to filter out final transactions.
- await tx.withdrawalGroups.iter().forEach((wgRecord) => {
- switch (wgRecord.status) {
- case WithdrawalGroupStatus.AbortedBank:
- case WithdrawalGroupStatus.AbortedExchange:
- case WithdrawalGroupStatus.FailedAbortingBank:
- case WithdrawalGroupStatus.FailedBankAborted:
- case WithdrawalGroupStatus.Done:
- // Does not count as pendingIncoming
- return;
- case WithdrawalGroupStatus.PendingReady:
- case WithdrawalGroupStatus.AbortingBank:
- case WithdrawalGroupStatus.PendingAml:
- case WithdrawalGroupStatus.PendingKyc:
- case WithdrawalGroupStatus.PendingQueryingStatus:
- case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
- case WithdrawalGroupStatus.SuspendedReady:
- case WithdrawalGroupStatus.SuspendedRegisteringBank:
- case WithdrawalGroupStatus.SuspendedKyc:
- case WithdrawalGroupStatus.SuspendedAbortingBank:
- case WithdrawalGroupStatus.SuspendedAml:
- case WithdrawalGroupStatus.PendingRegisteringBank:
- case WithdrawalGroupStatus.PendingWaitConfirmBank:
- case WithdrawalGroupStatus.SuspendedQueryingStatus:
- break;
- default:
- assertUnreachable(wgRecord.status);
- }
- const b = initBalance(
- Amounts.currencyOf(wgRecord.denomsSel.totalWithdrawCost),
- );
- b.pendingIncoming = Amounts.add(
- b.pendingIncoming,
- wgRecord.denomsSel.totalCoinValue,
- ).amount;
- });
+ await tx.depositGroups.indexes.byStatus
+ .iter(depositOperationNonfinalStatusRange)
+ .forEach((dgRecord) => {
+ const b = initBalance(Amounts.currencyOf(dgRecord.amount));
+ switch (dgRecord.operationStatus) {
+ case DepositOperationStatus.SuspendedKyc:
+ case DepositOperationStatus.PendingKyc:
+ b.flagOutgoingKyc = true;
+ }
+ });
const balancesResponse: BalancesResponse = {
balances: [],
@@ -190,6 +226,16 @@ export async function getBalancesInsideTransaction(
.sort()
.forEach((c) => {
const v = balanceStore[c];
+ const flags: BalanceFlag[] = [];
+ if (v.flagIncomingAml) {
+ flags.push(BalanceFlag.IncomingAml);
+ }
+ if (v.flagIncomingKyc) {
+ flags.push(BalanceFlag.IncomingKyc);
+ }
+ if (v.flagIncomingConfirmation) {
+ flags.push(BalanceFlag.IncomingConfirmation);
+ }
balancesResponse.balances.push({
scopeInfo: {
type: ScopeType.Global,
@@ -198,8 +244,11 @@ export async function getBalancesInsideTransaction(
available: Amounts.stringify(v.available),
pendingIncoming: Amounts.stringify(v.pendingIncoming),
pendingOutgoing: Amounts.stringify(v.pendingOutgoing),
+ // FIXME: This field is basically not implemented, do we even need it?
hasPendingTransactions: false,
+ // FIXME: This field is basically not implemented, do we even need it?
requiresUserInput: false,
+ flags,
});
});
@@ -221,6 +270,7 @@ export async function getBalances(
x.refreshGroups,
x.purchases,
x.withdrawalGroups,
+ x.depositGroups,
])
.runReadOnly(async (tx) => {
return getBalancesInsideTransaction(ws, tx);
diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts
index 7641dcf33..282f84ad7 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -49,10 +49,12 @@ import {
WalletStoresV1,
WithdrawalGroupRecord,
WithdrawalGroupStatus,
+ depositOperationNonfinalStatusRange,
timestampAbsoluteFromDb,
timestampOptionalAbsoluteFromDb,
timestampPreciseFromDb,
timestampPreciseToDb,
+ withdrawalGroupNonfinalRange,
} from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import {
@@ -205,12 +207,8 @@ export async function iterRecordsForWithdrawal(
): Promise<void> {
let withdrawalGroupRecords: WithdrawalGroupRecord[];
if (filter.onlyState === "nonfinal") {
- const range = GlobalIDB.KeyRange.bound(
- WithdrawalGroupStatus.PendingRegisteringBank,
- WithdrawalGroupStatus.PendingAml,
- );
withdrawalGroupRecords = await tx.withdrawalGroups.indexes.byStatus.getAll(
- range,
+ withdrawalGroupNonfinalRange,
);
} else {
withdrawalGroupRecords =
@@ -238,7 +236,7 @@ async function gatherWithdrawalPending(
* kyc pending operation don't give lifeness
* since the user need to complete kyc procedure
*/
- const userNeedToCompleteKYC = wsr.kycUrl !== undefined
+ const userNeedToCompleteKYC = wsr.kycUrl !== undefined;
const now = AbsoluteTime.now();
if (!opr) {
opr = {
@@ -256,7 +254,7 @@ async function gatherWithdrawalPending(
ws,
opTag,
timestampOptionalAbsoluteFromDb(opr.retryInfo?.nextRetry) ??
- AbsoluteTime.now(),
+ AbsoluteTime.now(),
),
givesLifeness: !userNeedToCompleteKYC,
withdrawalGroupId: wsr.withdrawalGroupId,
@@ -276,10 +274,7 @@ export async function iterRecordsForDeposit(
let dgs: DepositGroupRecord[];
if (filter.onlyState === "nonfinal") {
dgs = await tx.depositGroups.indexes.byStatus.getAll(
- GlobalIDB.KeyRange.bound(
- DepositOperationStatus.PendingDeposit,
- DepositOperationStatus.PendingKyc,
- ),
+ depositOperationNonfinalStatusRange,
);
} else {
dgs = await tx.depositGroups.indexes.byStatus.getAll();
@@ -310,7 +305,7 @@ async function gatherDepositPending(
* kyc pending operation don't give lifeness
* since the user need to complete kyc procedure
*/
- const userNeedToCompleteKYC = dg.kycInfo !== undefined
+ const userNeedToCompleteKYC = dg.kycInfo !== undefined;
const opId = TaskIdentifiers.forDeposit(dg);
const retryRecord = await tx.operationRetries.get(opId);
const timestampDue =
@@ -554,7 +549,7 @@ async function gatherPeerPullInitiationPending(
* kyc pending operation don't give lifeness
* since the user need to complete kyc procedure
*/
- const userNeedToCompleteKYC = pi.kycUrl !== undefined
+ const userNeedToCompleteKYC = pi.kycUrl !== undefined;
resp.pendingOperations.push({
type: PendingTaskType.PeerPullCredit,
@@ -712,7 +707,7 @@ async function gatherPeerPushCreditPending(
* kyc pending operation don't give lifeness
* since the user need to complete kyc procedure
*/
- const userNeedToCompleteKYC = pi.kycUrl !== undefined
+ const userNeedToCompleteKYC = pi.kycUrl !== undefined;
resp.pendingOperations.push({
type: PendingTaskType.PeerPushCredit,