aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2023-03-29 15:14:02 -0300
committerSebastian <sebasjm@gmail.com>2023-03-29 15:14:24 -0300
commit74dba9506dba104d918c5386e67146f71f07436c (patch)
tree2d6434f66e2a14a56f0a748733462a0089bdee44 /packages
parent329b766ae78405e086e7b6f078168bc0c136d317 (diff)
downloadwallet-core-74dba9506dba104d918c5386e67146f71f07436c.tar.xz
show wire details when the deposit has been wired
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-util/src/transactions-types.ts13
-rw-r--r--packages/taler-wallet-core/src/db.ts14
-rw-r--r--packages/taler-wallet-core/src/operations/deposits.ts93
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts2
-rw-r--r--packages/taler-wallet-webextension/src/components/TransactionItem.tsx54
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Transaction.tsx68
6 files changed, 226 insertions, 18 deletions
diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts
index 9623e25a9..1d7e6ef30 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -535,7 +535,7 @@ export interface TransactionRefresh extends TransactionCommon {
/**
* Fees, i.e. the effective, negative effect of the refresh
* on the balance.
- *
+ *
* Only applicable for stand-alone refreshes, and zero for
* other refreshes where the transaction itself accounts for the
* refresh fee.
@@ -578,6 +578,17 @@ export interface TransactionDeposit extends TransactionCommon {
* Did all the deposit requests succeed?
*/
deposited: boolean;
+
+ trackingState: Array<{
+ // Raw wire transfer identifier of the deposit.
+ wireTransferId: string;
+ // When was the wire transfer given to the bank.
+ timestampExecuted: TalerProtocolTimestamp;
+ // Total amount transfer for this wtid (including fees)
+ amountRaw: AmountString;
+ // Total amount received for this wtid (without fees)
+ amountEffective: AmountString;
+ }>;
}
export interface TransactionByIdRequest {
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index cd676b7ca..fb5ea025a 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1671,6 +1671,20 @@ export interface DepositGroupRecord {
operationStatus: OperationStatus;
transactionPerCoin: TransactionStatus[];
+
+ trackingState?: {
+ [signature: string]: {
+ // Raw wire transfer identifier of the deposit.
+ wireTransferId: string;
+ // When was the wire transfer given to the bank.
+ timestampExecuted: TalerProtocolTimestamp;
+ // Total amount transfer for this wtid (including fees)
+ amountRaw: AmountString;
+ // Total amount received for this wtid (without fees)
+ amountEffective: AmountString;
+ exchangePub: string;
+ };
+ };
}
/**
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts
index 22283b7a8..c6cd4732c 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -53,12 +53,15 @@ import {
TrackDepositGroupRequest,
TrackDepositGroupResponse,
TrackTransaction,
+ TrackTransactionWired,
TransactionType,
URL,
+ WireFee,
} from "@gnu-taler/taler-util";
import {
DenominationRecord,
DepositGroupRecord,
+ ExchangeDetailsRecord,
OperationStatus,
TransactionStatus,
} from "../db.js";
@@ -157,7 +160,6 @@ export async function processDepositGroup(
const perm = depositPermissions[i];
let updatedDeposit: boolean | undefined = undefined;
- let updatedTxStatus: TransactionStatus | undefined = undefined;
if (!depositGroup.depositedPerCoin[i]) {
const requestBody: ExchangeDepositRequest = {
@@ -186,6 +188,17 @@ export async function processDepositGroup(
updatedDeposit = true;
}
+ let updatedTxStatus: TransactionStatus | undefined = undefined;
+ type ValueOf<T> = T[keyof T];
+
+ let newWiredTransaction:
+ | {
+ id: string;
+ value: ValueOf<NonNullable<DepositGroupRecord["trackingState"]>>;
+ }
+ | undefined;
+
+ let signature: string | undefined;
if (depositGroup.transactionPerCoin[i] !== TransactionStatus.Wired) {
const track = await trackDepositPermission(ws, depositGroup, perm);
@@ -207,6 +220,32 @@ export async function processDepositGroup(
}
} else if (track.type === "wired") {
updatedTxStatus = TransactionStatus.Wired;
+
+ const payto = parsePaytoUri(depositGroup.wire.payto_uri);
+ if (!payto) {
+ throw Error(`unparsable payto: ${depositGroup.wire.payto_uri}`);
+ }
+
+ const fee = await getExchangeWireFee(
+ ws,
+ payto.targetType,
+ perm.exchange_url,
+ track.execution_time,
+ );
+ const raw = Amounts.parseOrThrow(track.coin_contribution);
+ const wireFee = Amounts.parseOrThrow(fee.wireFee);
+ const effective = Amounts.sub(raw, wireFee).amount;
+
+ newWiredTransaction = {
+ value: {
+ amountRaw: Amounts.stringify(raw),
+ amountEffective: Amounts.stringify(effective),
+ exchangePub: track.exchange_pub,
+ timestampExecuted: track.execution_time,
+ wireTransferId: track.wtid,
+ },
+ id: track.exchange_sig,
+ };
} else {
updatedTxStatus = TransactionStatus.Unknown;
}
@@ -226,6 +265,14 @@ export async function processDepositGroup(
if (updatedTxStatus !== undefined) {
dg.transactionPerCoin[i] = updatedTxStatus;
}
+ if (newWiredTransaction) {
+ if (!dg.trackingState) {
+ dg.trackingState = {};
+ }
+
+ dg.trackingState[newWiredTransaction.id] =
+ newWiredTransaction.value;
+ }
await tx.depositGroups.put(dg);
});
}
@@ -257,6 +304,50 @@ export async function processDepositGroup(
return OperationAttemptResult.finishedEmpty();
}
+async function getExchangeWireFee(
+ ws: InternalWalletState,
+ wireType: string,
+ baseUrl: string,
+ time: TalerProtocolTimestamp,
+): Promise<WireFee> {
+ const exchangeDetails = await ws.db
+ .mktx((x) => [x.exchanges, x.exchangeDetails])
+ .runReadOnly(async (tx) => {
+ const ex = await tx.exchanges.get(baseUrl);
+ if (!ex || !ex.detailsPointer) return undefined;
+ return await tx.exchangeDetails.indexes.byPointer.get([
+ baseUrl,
+ ex.detailsPointer.currency,
+ ex.detailsPointer.masterPublicKey,
+ ]);
+ });
+
+ if (!exchangeDetails) {
+ throw Error(`exchange missing: ${baseUrl}`);
+ }
+
+ const fees = exchangeDetails.wireInfo.feesForType[wireType];
+ if (!fees || fees.length === 0) {
+ throw Error(
+ `exchange ${baseUrl} doesn't have fees for wire type ${wireType}`,
+ );
+ }
+ const fee = fees.find((x) => {
+ return AbsoluteTime.isBetween(
+ AbsoluteTime.fromTimestamp(time),
+ AbsoluteTime.fromTimestamp(x.startStamp),
+ AbsoluteTime.fromTimestamp(x.endStamp),
+ );
+ });
+ if (!fee) {
+ throw Error(
+ `exchange ${exchangeDetails.exchangeBaseUrl} doesn't have fees for wire type ${wireType} at ${time.t_s}`,
+ );
+ }
+
+ return fee;
+}
+
export async function trackDepositGroup(
ws: InternalWalletState,
req: TrackDepositGroupRequest,
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
index 3e396098e..c0045aced 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -764,6 +764,7 @@ function buildTransactionForDeposit(
deposited = false;
}
}
+
return {
type: TransactionType.Deposit,
amountRaw: Amounts.stringify(dg.effectiveDepositAmount),
@@ -788,6 +789,7 @@ function buildTransactionForDeposit(
)) /
dg.transactionPerCoin.length,
depositGroupId: dg.depositGroupId,
+ trackingState: Object.values(dg.trackingState ?? {}),
deposited,
...(ort?.lastError ? { error: ort.lastError } : {}),
};
diff --git a/packages/taler-wallet-webextension/src/components/TransactionItem.tsx b/packages/taler-wallet-webextension/src/components/TransactionItem.tsx
index 934a0fe52..7ddaee9f3 100644
--- a/packages/taler-wallet-webextension/src/components/TransactionItem.tsx
+++ b/packages/taler-wallet-webextension/src/components/TransactionItem.tsx
@@ -76,7 +76,11 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
subtitle={tx.info.summary}
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"P"}
- // pending={tx.pending}
+ pending={
+ tx.extendedStatus === ExtendedStatus.Pending
+ ? i18n.str`Payment in progress`
+ : undefined
+ }
/>
);
case TransactionType.Refund:
@@ -89,7 +93,11 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
title={tx.info.merchant.name}
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"R"}
- // pending={tx.pending}
+ pending={
+ tx.extendedStatus === ExtendedStatus.Pending
+ ? i18n.str`Executing refund...`
+ : undefined
+ }
/>
);
case TransactionType.Tip:
@@ -101,7 +109,11 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
title={new URL(tx.merchantBaseUrl).hostname}
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"T"}
- // pending={tx.pending}
+ pending={
+ tx.extendedStatus === ExtendedStatus.Pending
+ ? i18n.str`Grabbing the tipping...`
+ : undefined
+ }
/>
);
case TransactionType.Refresh:
@@ -113,7 +125,11 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
title={"Refresh"}
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"R"}
- // pending={tx.pending}
+ pending={
+ tx.extendedStatus === ExtendedStatus.Pending
+ ? i18n.str`Refreshing coins...`
+ : undefined
+ }
/>
);
case TransactionType.Deposit:
@@ -125,7 +141,11 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
title={tx.targetPaytoUri}
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"D"}
- // pending={tx.pending}
+ pending={
+ tx.extendedStatus === ExtendedStatus.Pending
+ ? i18n.str`Deposit in progress`
+ : undefined
+ }
/>
);
case TransactionType.PeerPullCredit:
@@ -137,7 +157,11 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
title={tx.info.summary || "Invoice"}
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"I"}
- // pending={tx.pending}
+ pending={
+ tx.extendedStatus === ExtendedStatus.Pending
+ ? i18n.str`Waiting to be paid`
+ : undefined
+ }
/>
);
case TransactionType.PeerPullDebit:
@@ -149,7 +173,11 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
title={tx.info.summary || "Invoice"}
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"I"}
- // pending={tx.pending}
+ pending={
+ tx.extendedStatus === ExtendedStatus.Pending
+ ? i18n.str`Payment in progress`
+ : undefined
+ }
/>
);
case TransactionType.PeerPushCredit:
@@ -161,7 +189,11 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
title={tx.info.summary || "Transfer"}
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"T"}
- // pending={tx.pending}
+ pending={
+ tx.extendedStatus === ExtendedStatus.Pending
+ ? i18n.str`Receiving the transfer`
+ : undefined
+ }
/>
);
case TransactionType.PeerPushDebit:
@@ -173,7 +205,11 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
title={tx.info.summary || "Transfer"}
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"T"}
- // pending={tx.pending}
+ pending={
+ tx.extendedStatus === ExtendedStatus.Pending
+ ? i18n.str`Waiting to be received`
+ : undefined
+ }
/>
);
default: {
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index 217a77575..a9683f680 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -28,6 +28,7 @@ import {
stringifyPaytoUri,
TalerProtocolTimestamp,
Transaction,
+ TransactionDeposit,
TransactionType,
TranslatedString,
WithdrawalType,
@@ -714,13 +715,24 @@ export function TransactionView({
}}
/>
) : transaction.wireTransferProgress === 100 ? (
- <AlertView
- alert={{
- type: "success",
- message: i18n.str`Wire transfer completed`,
- description: i18n.str` `,
- }}
- />
+ <Fragment>
+ <AlertView
+ alert={{
+ type: "success",
+ message: i18n.str`Wire transfer completed`,
+ description: i18n.str` `,
+ }}
+ />
+ <Part
+ title={i18n.str`Transfer details`}
+ text={
+ <TrackingDepositDetails
+ trackingState={transaction.trackingState}
+ />
+ }
+ kind="neutral"
+ />
+ </Fragment>
) : (
<AlertView
alert={{
@@ -1559,6 +1571,48 @@ function RefundDetails({ amount }: { amount: AmountWithFee }): VNode {
);
}
+function TrackingDepositDetails({
+ trackingState,
+}: {
+ trackingState: TransactionDeposit["trackingState"];
+}): VNode {
+ const { i18n } = useTranslationContext();
+
+ const trackByWtid = Object.values(trackingState ?? {}).reduce((prev, cur) => {
+ const am = Amounts.parseOrThrow(cur.amountEffective);
+ const sum = !prev[cur.wireTransferId]
+ ? am
+ : Amounts.add(prev[cur.wireTransferId], am).amount;
+ prev[cur.wireTransferId] = sum;
+ return prev;
+ }, {} as Record<string, AmountJson>);
+ const wireTransfers = Object.entries(trackByWtid).map(([id, amountJson]) => ({
+ id,
+ amount: Amounts.stringify(amountJson),
+ }));
+
+ return (
+ <PurchaseDetailsTable>
+ <tr>
+ <td>
+ <i18n.Translate>Transfer identification</i18n.Translate>
+ </td>
+ <td>
+ <i18n.Translate>Amount</i18n.Translate>
+ </td>
+ </tr>
+
+ {wireTransfers.map((wire) => (
+ <tr>
+ <td>{wire.id}</td>
+ <td>
+ <Amount value={wire.amount} />
+ </td>
+ </tr>
+ ))}
+ </PurchaseDetailsTable>
+ );
+}
function DepositDetails({ amount }: { amount: AmountWithFee }): VNode {
const { i18n } = useTranslationContext();