getTransactionById is introduced:
 with that we move all transaction information building into a function
transactionId was added in every response that creates a tx
This commit is contained in:
Sebastian 2022-09-16 11:06:55 -03:00
parent a66b636dee
commit 5d08379139
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
16 changed files with 703 additions and 327 deletions

View File

@ -56,6 +56,16 @@ export namespace TalerProtocolTimestamp {
t_s: s,
};
}
export function min(t1: TalerProtocolTimestamp, t2: TalerProtocolTimestamp): TalerProtocolTimestamp {
if (t1.t_s === "never") {
return { t_s: t2.t_s };
}
if (t2.t_s === "never") {
return { t_s: t2.t_s };
}
return { t_s: Math.min(t1.t_s, t2.t_s) };
}
}
export interface Duration {

View File

@ -505,6 +505,15 @@ export interface TransactionDeposit extends TransactionCommon {
amountEffective: AmountString;
}
export interface TransactionByIdRequest {
transactionId: string;
}
export const codecForTransactionByIdRequest = (): Codec<TransactionByIdRequest> =>
buildCodecForObject<TransactionByIdRequest>()
.property("transactionId", codecForString())
.build("TransactionByIdRequest");
export const codecForTransactionsRequest = (): Codec<TransactionsRequest> =>
buildCodecForObject<TransactionsRequest>()
.property("currency", codecOptional(codecForString()))

View File

@ -138,11 +138,12 @@ export enum ConfirmPayResultType {
export interface ConfirmPayResultDone {
type: ConfirmPayResultType.Done;
contractTerms: ContractTerms;
transactionId: string;
}
export interface ConfirmPayResultPending {
type: ConfirmPayResultType.Pending;
transactionId: string;
lastError: TalerErrorDetail | undefined;
}
@ -152,12 +153,14 @@ export const codecForConfirmPayResultPending =
(): Codec<ConfirmPayResultPending> =>
buildCodecForObject<ConfirmPayResultPending>()
.property("lastError", codecForAny())
.property("transactionId", codecForString())
.property("type", codecForConstString(ConfirmPayResultType.Pending))
.build("ConfirmPayResultPending");
export const codecForConfirmPayResultDone = (): Codec<ConfirmPayResultDone> =>
buildCodecForObject<ConfirmPayResultDone>()
.property("type", codecForConstString(ConfirmPayResultType.Done))
.property("transactionId", codecForString())
.property("contractTerms", codecForContractTerms())
.build("ConfirmPayResultDone");
@ -334,6 +337,10 @@ export interface PrepareTipResult {
expirationTimestamp: TalerProtocolTimestamp;
}
export interface AcceptTipResponse {
transactionId: string;
}
export const codecForPrepareTipResult = (): Codec<PrepareTipResult> =>
buildCodecForObject<PrepareTipResult>()
.property("accepted", codecForBoolean())
@ -462,6 +469,7 @@ export interface BankWithdrawDetails {
export interface AcceptWithdrawalResponse {
reservePub: string;
confirmTransferUrl?: string;
transactionId: string;
}
/**
@ -864,6 +872,8 @@ export interface AcceptManualWithdrawalResult {
* Public key of the newly created reserve.
*/
reservePub: string;
transactionId: string;
}
export interface ManualWithdrawalDetails {
@ -1252,6 +1262,8 @@ export const codecForWithdrawTestBalance =
export interface ApplyRefundResponse {
contractTermsHash: string;
transactionId: string;
proposalId: string;
amountEffectivePaid: AmountString;
@ -1273,6 +1285,7 @@ export const codecForApplyRefundResponse = (): Codec<ApplyRefundResponse> =>
.property("contractTermsHash", codecForString())
.property("pendingAtExchange", codecForBoolean())
.property("proposalId", codecForString())
.property("transactionId", codecForString())
.property("info", codecForOrderShortInfo())
.build("ApplyRefundResponse");
@ -1374,6 +1387,7 @@ export const codecForCreateDepositGroupRequest =
export interface CreateDepositGroupResponse {
depositGroupId: string;
transactionId: string;
}
export interface TrackDepositGroupRequest {
@ -1539,6 +1553,7 @@ export interface InitiatePeerPushPaymentResponse {
mergePriv: string;
contractPriv: string;
talerUri: string;
transactionId: string;
}
export const codecForInitiatePeerPushPaymentRequest =
@ -1586,6 +1601,13 @@ export interface AcceptPeerPushPaymentRequest {
*/
peerPushPaymentIncomingId: string;
}
export interface AcceptPeerPushPaymentResponse {
transactionId: string;
}
export interface AcceptPeerPullPaymentResponse {
transactionId: string;
}
export const codecForAcceptPeerPushPaymentRequest =
(): Codec<AcceptPeerPushPaymentRequest> =>
@ -1629,4 +1651,6 @@ export interface InitiatePeerPullPaymentResponse {
* that was requested.
*/
talerUri: string;
transactionId: string;
}

View File

@ -41,6 +41,7 @@ import {
TalerProtocolTimestamp,
TrackDepositGroupRequest,
TrackDepositGroupResponse,
TransactionType,
URL,
} from "@gnu-taler/taler-util";
import {
@ -62,6 +63,7 @@ import {
getTotalPaymentCost,
} from "./pay.js";
import { getTotalRefreshCost } from "./refresh.js";
import { makeEventId } from "./transactions.js";
/**
* Logger.
@ -531,7 +533,10 @@ export async function createDepositGroup(
await tx.depositGroups.put(depositGroup);
});
return { depositGroupId };
return {
depositGroupId: depositGroupId,
transactionId: makeEventId(TransactionType.Deposit, depositGroupId)
};
}
/**

View File

@ -103,6 +103,7 @@ import { RetryInfo, RetryTags, scheduleRetry } from "../util/retries.js";
import { spendCoins } from "../wallet.js";
import { getExchangeDetails } from "./exchanges.js";
import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js";
import { makeEventId } from "./transactions.js";
/**
* Logger.
@ -511,7 +512,7 @@ export function extractContractData(
export async function processDownloadProposal(
ws: InternalWalletState,
proposalId: string,
options: {} = {},
options: object = {},
): Promise<OperationAttemptResult> {
const proposal = await ws.db
.mktx((x) => [x.proposals])
@ -1312,6 +1313,7 @@ export async function runPayForConfirmPay(
return {
type: ConfirmPayResultType.Done,
contractTerms: purchase.download.contractTermsRaw,
transactionId: makeEventId(TransactionType.Payment, proposalId)
};
}
case OperationAttemptResultType.Error:
@ -1320,6 +1322,7 @@ export async function runPayForConfirmPay(
case OperationAttemptResultType.Pending:
return {
type: ConfirmPayResultType.Pending,
transactionId: makeEventId(TransactionType.Payment, proposalId),
lastError: undefined,
};
case OperationAttemptResultType.Longpoll:

View File

@ -20,11 +20,11 @@
import {
AbsoluteTime,
AcceptPeerPullPaymentRequest,
AcceptPeerPullPaymentResponse,
AcceptPeerPushPaymentRequest,
AcceptPeerPushPaymentResponse,
AgeCommitmentProof,
AmountJson,
AmountLike,
Amounts,
AmountJson, Amounts,
AmountString,
buildCodecForObject,
CheckPeerPullPaymentRequest,
@ -34,9 +34,7 @@ import {
Codec,
codecForAmountString,
codecForAny,
codecForExchangeGetContractResponse,
CoinPublicKey,
constructPayPullUri,
codecForExchangeGetContractResponse, constructPayPullUri,
constructPayPushUri,
ContractTermsUtil,
decodeCrock,
@ -58,25 +56,25 @@ import {
RefreshReason,
strcmp,
TalerProtocolTimestamp,
TransactionType,
UnblindedSignature,
WalletAccountMergeFlags,
WalletAccountMergeFlags
} from "@gnu-taler/taler-util";
import {
CoinStatus,
MergeReserveInfo,
ReserveRecordStatus,
WalletStoresV1,
WithdrawalRecordType,
WithdrawalRecordType
} from "../db.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
import { checkDbInvariant } from "../util/invariants.js";
import { internalCreateWithdrawalGroup } from "./withdraw.js";
import { GetReadOnlyAccess } from "../util/query.js";
import { createRefreshGroup } from "./refresh.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import { spendCoins } from "../wallet.js";
import { RetryTags } from "../util/retries.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import { makeEventId } from "./transactions.js";
import { internalCreateWithdrawalGroup } from "./withdraw.js";
const logger = new Logger("operations/peer-to-peer.ts");
@ -338,6 +336,7 @@ export async function initiatePeerToPeerPush(
exchangeBaseUrl: coinSelRes.exchangeBaseUrl,
contractPriv: econtractResp.contractPriv,
}),
transactionId: makeEventId(TransactionType.PeerPushDebit, pursePair.pub)
};
}
@ -472,7 +471,7 @@ async function getMergeReserveInfo(
export async function acceptPeerPushPayment(
ws: InternalWalletState,
req: AcceptPeerPushPaymentRequest,
): Promise<void> {
): Promise<AcceptPeerPushPaymentResponse> {
const peerInc = await ws.db
.mktx((x) => [x.peerPushPaymentIncoming])
.runReadOnly(async (tx) => {
@ -533,7 +532,7 @@ export async function acceptPeerPushPayment(
const res = await readSuccessResponseJsonOrThrow(mergeHttpReq, codecForAny());
logger.info(`merge response: ${j2s(res)}`);
await internalCreateWithdrawalGroup(ws, {
const wg = await internalCreateWithdrawalGroup(ws, {
amount,
wgInfo: {
withdrawalType: WithdrawalRecordType.PeerPushCredit,
@ -546,6 +545,13 @@ export async function acceptPeerPushPayment(
pub: mergeReserveInfo.reservePub,
},
});
return {
transactionId: makeEventId(
TransactionType.PeerPushCredit,
wg.withdrawalGroupId
)
}
}
/**
@ -554,7 +560,7 @@ export async function acceptPeerPushPayment(
export async function acceptPeerPullPayment(
ws: InternalWalletState,
req: AcceptPeerPullPaymentRequest,
): Promise<void> {
): Promise<AcceptPeerPullPaymentResponse> {
const peerPullInc = await ws.db
.mktx((x) => [x.peerPullPaymentIncoming])
.runReadOnly(async (tx) => {
@ -630,6 +636,13 @@ export async function acceptPeerPullPayment(
const httpResp = await ws.http.postJson(purseDepositUrl.href, depositPayload);
const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
logger.trace(`purse deposit response: ${j2s(resp)}`);
return {
transactionId: makeEventId(
TransactionType.PeerPullDebit,
req.peerPullPaymentIncomingId,
)
}
}
export async function checkPeerPullPayment(
@ -801,7 +814,7 @@ export async function initiatePeerRequestForPay(
logger.info(`reserve merge response: ${j2s(resp)}`);
await internalCreateWithdrawalGroup(ws, {
const wg = await internalCreateWithdrawalGroup(ws, {
amount: Amounts.parseOrThrow(req.amount),
wgInfo: {
withdrawalType: WithdrawalRecordType.PeerPullCredit,
@ -821,5 +834,9 @@ export async function initiatePeerRequestForPay(
exchangeBaseUrl: req.exchangeBaseUrl,
contractPriv: econtractResp.contractPriv,
}),
transactionId: makeEventId(
TransactionType.PeerPullCredit,
wg.withdrawalGroupId
)
};
}

View File

@ -46,6 +46,7 @@ import {
TalerErrorCode,
TalerErrorDetail,
TalerProtocolTimestamp,
TransactionType,
URL,
} from "@gnu-taler/taler-util";
import {
@ -63,6 +64,7 @@ import { readSuccessResponseJsonOrThrow } from "../util/http.js";
import { checkDbInvariant } from "../util/invariants.js";
import { GetReadWriteAccess } from "../util/query.js";
import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js";
import { makeEventId } from "./transactions.js";
const logger = new Logger("refund.ts");
@ -573,6 +575,7 @@ export async function applyRefundFromPurchaseId(
return {
contractTermsHash: purchase.download.contractData.contractTermsHash,
proposalId: purchase.proposalId,
transactionId: makeEventId(TransactionType.Payment, proposalId), //FIXME: can we have the tx id of the refund
amountEffectivePaid: Amounts.stringify(summary.amountEffectivePaid),
amountRefundGone: Amounts.stringify(summary.amountRefundGone),
amountRefundGranted: Amounts.stringify(summary.amountRefundGranted),

View File

@ -18,6 +18,7 @@
* Imports.
*/
import {
AcceptTipResponse,
Amounts,
BlindedDenominationSignature,
codecForMerchantTipResponseV2,
@ -32,6 +33,7 @@ import {
TalerErrorCode,
TalerProtocolTimestamp,
TipPlanchetDetail,
TransactionType,
URL,
} from "@gnu-taler/taler-util";
import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";
@ -53,6 +55,7 @@ import {
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
import { makeCoinAvailable } from "../wallet.js";
import { updateExchangeFromUrl } from "./exchanges.js";
import { makeEventId } from "./transactions.js";
import {
getCandidateWithdrawalDenoms,
getExchangeWithdrawalInfo,
@ -341,7 +344,7 @@ export async function processTip(
export async function acceptTip(
ws: InternalWalletState,
tipId: string,
): Promise<void> {
): Promise<AcceptTipResponse> {
const found = await ws.db
.mktx((x) => [x.tips])
.runReadWrite(async (tx) => {
@ -357,4 +360,10 @@ export async function acceptTip(
if (found) {
await processTip(ws, tipId);
}
return {
transactionId: makeEventId(
TransactionType.Tip,
tipId
)
}
}

View File

@ -19,13 +19,16 @@
*/
import {
AbsoluteTime,
addPaytoQueryParams, Amounts,
addPaytoQueryParams, AmountJson, Amounts,
constructPayPullUri,
constructPayPushUri,
Logger,
OrderShortInfo, PaymentStatus,
RefundInfoShort,
TalerProtocolTimestamp,
Transaction,
TransactionByIdRequest,
TransactionRefund,
TransactionsRequest,
TransactionsResponse,
TransactionType,
@ -34,8 +37,16 @@ import {
} from "@gnu-taler/taler-util";
import {
AbortStatus,
DepositGroupRecord,
ExchangeDetailsRecord,
OperationRetryRecord,
PeerPullPaymentIncomingRecord,
PeerPushPaymentInitiationRecord,
PurchaseRecord,
RefundState,
TipRecord,
WalletRefundItem,
WithdrawalGroupRecord,
WithdrawalRecordType
} from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js";
@ -44,6 +55,7 @@ import { processDepositGroup } from "./deposits.js";
import { getExchangeDetails } from "./exchanges.js";
import { processPurchasePay } from "./pay.js";
import { processRefreshGroup } from "./refresh.js";
import { applyRefundFromPurchaseId } from "./refund.js";
import { processTip } from "./tip.js";
import { processWithdrawalGroup } from "./withdraw.js";
@ -114,6 +126,500 @@ const txOrder: { [t in TransactionType]: number } = {
[TransactionType.Tip]: 11,
};
export async function getTransactionById(
ws: InternalWalletState,
req: TransactionByIdRequest,
): Promise<Transaction> {
const [typeStr, ...rest] = req.transactionId.split(":");
const type = typeStr as TransactionType;
if (
type === TransactionType.Withdrawal ||
type === TransactionType.PeerPullCredit ||
type === TransactionType.PeerPushCredit
) {
const withdrawalGroupId = rest[0];
return await ws.db
.mktx((x) => [x.withdrawalGroups, x.exchangeDetails, x.exchanges, x.operationRetries])
.runReadWrite(async (tx) => {
const withdrawalGroupRecord = await tx.withdrawalGroups.get(
withdrawalGroupId,
);
if (!withdrawalGroupRecord) throw Error("not found")
const opId = RetryTags.forWithdrawal(withdrawalGroupRecord);
const ort = await tx.operationRetries.get(opId);
if (withdrawalGroupRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated) {
return buildTransactionForBankIntegratedWithdraw(withdrawalGroupRecord, ort);
}
if (withdrawalGroupRecord.wgInfo.withdrawalType === WithdrawalRecordType.PeerPullCredit) {
return buildTransactionForPullPaymentCredit(withdrawalGroupRecord, ort);
}
if (withdrawalGroupRecord.wgInfo.withdrawalType === WithdrawalRecordType.PeerPushCredit) {
return buildTransactionForPushPaymentCredit(withdrawalGroupRecord, ort);
}
const exchangeDetails = await getExchangeDetails(tx, withdrawalGroupRecord.exchangeBaseUrl,);
if (!exchangeDetails) throw Error('not exchange details')
return buildTransactionForManualWithdraw(withdrawalGroupRecord, exchangeDetails, ort);
});
} else if (type === TransactionType.Payment) {
const proposalId = rest[0];
return await ws.db
.mktx((x) => [x.purchases, x.tombstones, x.operationRetries])
.runReadWrite(async (tx) => {
const purchase = await tx.purchases.get(proposalId);
if (!purchase) throw Error("not found")
const filteredRefunds = await Promise.all(Object.values(purchase.refunds).map(async r => {
const t = await tx.tombstones.get(makeEventId(
TombstoneTag.DeleteRefund,
purchase.proposalId,
`${r.executionTime.t_s}`,
))
if (!t) return r
return undefined
}));
const cleanRefunds = filteredRefunds.filter((x): x is WalletRefundItem => !!x);
const contractData = purchase.download.contractData;
const refunds = mergeRefundByExecutionTime(cleanRefunds, Amounts.getZero(contractData.amount.currency));
const payOpId = RetryTags.forPay(purchase);
const refundQueryOpId = RetryTags.forRefundQuery(purchase);
const payRetryRecord = await tx.operationRetries.get(payOpId);
const refundQueryRetryRecord = await tx.operationRetries.get(
refundQueryOpId,
);
const err = payRetryRecord !== undefined ? payRetryRecord : refundQueryRetryRecord
return buildTransactionForPurchase(purchase, refunds, err);
});
} else if (type === TransactionType.Refresh) {
const refreshGroupId = rest[0];
throw Error(`no tx for refresh`);
} else if (type === TransactionType.Tip) {
const tipId = rest[0];
return await ws.db
.mktx((x) => [x.tips, x.operationRetries])
.runReadWrite(async (tx) => {
const tipRecord = await tx.tips.get(tipId);
if (!tipRecord) throw Error("not found")
const retries = await tx.operationRetries.get(RetryTags.forTipPickup(tipRecord));
return buildTransactionForTip(tipRecord, retries)
});
} else if (type === TransactionType.Deposit) {
const depositGroupId = rest[0];
return await ws.db
.mktx((x) => [x.depositGroups, x.operationRetries])
.runReadWrite(async (tx) => {
const depositRecord = await tx.depositGroups.get(depositGroupId);
if (!depositRecord) throw Error("not found")
const retries = await tx.operationRetries.get(RetryTags.forDeposit(depositRecord));
return buildTransactionForDeposit(depositRecord, retries)
});
} else if (type === TransactionType.Refund) {
const proposalId = rest[0];
const executionTimeStr = rest[1];
return await ws.db
.mktx((x) => [x.operationRetries, x.purchases, x.tombstones])
.runReadWrite(async (tx) => {
const purchase = await tx.purchases.get(proposalId);
if (!purchase) throw Error("not found")
const theRefund = Object.values(purchase.refunds).find(r => `${r.executionTime.t_s}` === executionTimeStr)
if (!theRefund) throw Error("not found")
const t = await tx.tombstones.get(makeEventId(
TombstoneTag.DeleteRefund,
purchase.proposalId,
executionTimeStr,
))
if (t) throw Error("deleted")
const contractData = purchase.download.contractData;
const refunds = mergeRefundByExecutionTime([theRefund], Amounts.getZero(contractData.amount.currency))
const refundQueryOpId = RetryTags.forRefundQuery(purchase);
const refundQueryRetryRecord = await tx.operationRetries.get(
refundQueryOpId,
);
return buildTransactionForRefund(purchase, refunds[0], refundQueryRetryRecord);
});
} else if (type === TransactionType.PeerPullDebit) {
const peerPullPaymentIncomingId = rest[0];
return await ws.db
.mktx((x) => [x.peerPullPaymentIncoming])
.runReadWrite(async (tx) => {
const debit = await tx.peerPullPaymentIncoming.get(
peerPullPaymentIncomingId,
);
if (!debit) throw Error("not found");
return buildTransactionForPullPaymentDebit(debit)
});
} else if (type === TransactionType.PeerPushDebit) {
const pursePub = rest[0];
return await ws.db
.mktx((x) => [x.peerPushPaymentInitiations])
.runReadWrite(async (tx) => {
const debit = await tx.peerPushPaymentInitiations.get(pursePub);
if (!debit) throw Error("not found");
return buildTransactionForPushPaymentDebit(debit)
});
} else {
const unknownTxType: never = type;
throw Error(`can't delete a '${unknownTxType}' transaction`);
}
}
function buildTransactionForPushPaymentDebit(pi: PeerPushPaymentInitiationRecord, ort?: OperationRetryRecord): Transaction {
return {
type: TransactionType.PeerPushDebit,
amountEffective: pi.amount,
amountRaw: pi.amount,
exchangeBaseUrl: pi.exchangeBaseUrl,
info: {
expiration: pi.contractTerms.purse_expiration,
summary: pi.contractTerms.summary,
},
frozen: false,
pending: !pi.purseCreated,
timestamp: pi.timestampCreated,
talerUri: constructPayPushUri({
exchangeBaseUrl: pi.exchangeBaseUrl,
contractPriv: pi.contractPriv,
}),
transactionId: makeEventId(
TransactionType.PeerPushDebit,
pi.pursePub,
),
...(ort?.lastError ? { error: ort.lastError } : {}),
};
}
function buildTransactionForPullPaymentDebit(pi: PeerPullPaymentIncomingRecord, ort?: OperationRetryRecord): Transaction {
return {
type: TransactionType.PeerPullDebit,
amountEffective: Amounts.stringify(pi.contractTerms.amount),
amountRaw: Amounts.stringify(pi.contractTerms.amount),
exchangeBaseUrl: pi.exchangeBaseUrl,
frozen: false,
pending: false,
info: {
expiration: pi.contractTerms.purse_expiration,
summary: pi.contractTerms.summary,
},
timestamp: pi.timestampCreated,
transactionId: makeEventId(
TransactionType.PeerPullDebit,
pi.peerPullPaymentIncomingId,
),
...(ort?.lastError ? { error: ort.lastError } : {}),
}
}
function buildTransactionForPullPaymentCredit(wsr: WithdrawalGroupRecord, ort?: OperationRetryRecord): Transaction {
if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPullCredit) throw Error("")
return {
type: TransactionType.PeerPullCredit,
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
exchangeBaseUrl: wsr.exchangeBaseUrl,
pending: !wsr.timestampFinish,
timestamp: wsr.timestampStart,
info: {
expiration: wsr.wgInfo.contractTerms.purse_expiration,
summary: wsr.wgInfo.contractTerms.summary,
},
talerUri: constructPayPullUri({
exchangeBaseUrl: wsr.exchangeBaseUrl,
contractPriv: wsr.wgInfo.contractPriv,
}),
transactionId: makeEventId(
TransactionType.PeerPullCredit,
wsr.withdrawalGroupId,
),
frozen: false,
...(ort?.lastError ? { error: ort.lastError } : {}),
}
}
function buildTransactionForPushPaymentCredit(wsr: WithdrawalGroupRecord, ort?: OperationRetryRecord): Transaction {
if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) throw Error("")
return {
type: TransactionType.PeerPushCredit,
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
exchangeBaseUrl: wsr.exchangeBaseUrl,
info: {
expiration: wsr.wgInfo.contractTerms.purse_expiration,
summary: wsr.wgInfo.contractTerms.summary,
},
pending: !wsr.timestampFinish,
timestamp: wsr.timestampStart,
transactionId: makeEventId(
TransactionType.PeerPushCredit,
wsr.withdrawalGroupId,
),
frozen: false,
...(ort?.lastError ? { error: ort.lastError } : {}),
}
}
function buildTransactionForBankIntegratedWithdraw(wsr: WithdrawalGroupRecord, ort?: OperationRetryRecord): Transaction {
if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) throw Error("")
return {
type: TransactionType.Withdrawal,
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
withdrawalDetails: {
type: WithdrawalType.TalerBankIntegrationApi,
confirmed: wsr.wgInfo.bankInfo.timestampBankConfirmed
? true
: false,
reservePub: wsr.reservePub,
bankConfirmationUrl: wsr.wgInfo.bankInfo.confirmUrl,
},
exchangeBaseUrl: wsr.exchangeBaseUrl,
pending: !wsr.timestampFinish,
timestamp: wsr.timestampStart,
transactionId: makeEventId(
TransactionType.Withdrawal,
wsr.withdrawalGroupId,
),
frozen: false,
...(ort?.lastError ? { error: ort.lastError } : {}),
}
}
function buildTransactionForManualWithdraw(wsr: WithdrawalGroupRecord, exchangeDetails: ExchangeDetailsRecord, ort?: OperationRetryRecord): Transaction {
if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual) throw Error("")
return {
type: TransactionType.Withdrawal,
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
withdrawalDetails: {
type: WithdrawalType.ManualTransfer,
reservePub: wsr.reservePub,
exchangePaytoUris:
exchangeDetails.wireInfo?.accounts.map(
(x) => addPaytoQueryParams(x.payto_uri, { subject: wsr.reservePub }),
) ?? [],
},
exchangeBaseUrl: wsr.exchangeBaseUrl,
pending: !wsr.timestampFinish,
timestamp: wsr.timestampStart,
transactionId: makeEventId(
TransactionType.Withdrawal,
wsr.withdrawalGroupId,
),
frozen: false,
...(ort?.lastError ? { error: ort.lastError } : {}),
}
}
function buildTransactionForDeposit(dg: DepositGroupRecord, ort?: OperationRetryRecord): Transaction {
return {
type: TransactionType.Deposit,
amountRaw: Amounts.stringify(dg.effectiveDepositAmount),
amountEffective: Amounts.stringify(dg.totalPayCost),
pending: !dg.timestampFinished,
frozen: false,
timestamp: dg.timestampCreated,
targetPaytoUri: dg.wire.payto_uri,
transactionId: makeEventId(
TransactionType.Deposit,
dg.depositGroupId,
),
depositGroupId: dg.depositGroupId,
...(ort?.lastError ? { error: ort.lastError } : {}),
}
}
function buildTransactionForTip(tipRecord: TipRecord, ort?: OperationRetryRecord): Transaction {
if (!tipRecord.acceptedTimestamp) throw Error("")
return {
type: TransactionType.Tip,
amountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
amountRaw: Amounts.stringify(tipRecord.tipAmountRaw),
pending: !tipRecord.pickedUpTimestamp,
frozen: false,
timestamp: tipRecord.acceptedTimestamp,
transactionId: makeEventId(
TransactionType.Tip,
tipRecord.walletTipId,
),
merchantBaseUrl: tipRecord.merchantBaseUrl,
...(ort?.lastError ? { error: ort.lastError } : {}),
}
}
/**
* For a set of refund with the same executionTime.
*
*/
interface MergedRefundInfo {
executionTime: TalerProtocolTimestamp;
amountAppliedRaw: AmountJson;
amountAppliedEffective: AmountJson;
firstTimestamp: TalerProtocolTimestamp;
}
function mergeRefundByExecutionTime(rs: WalletRefundItem[], zero: AmountJson): MergedRefundInfo[] {
const refundByExecTime = rs.reduce((prev, refund) => {
const key = `${refund.executionTime.t_s}`;
//refunds counts if applied
const effective = refund.type === RefundState.Applied ? Amounts.sub(
refund.refundAmount,
refund.refundFee,
refund.totalRefreshCostBound,
).amount : zero
const raw = refund.type === RefundState.Applied ? refund.refundAmount : zero
const v = prev.get(key)
if (!v) {
prev.set(key, {
executionTime: refund.executionTime,
amountAppliedEffective: effective,
amountAppliedRaw: raw,
firstTimestamp: refund.obtainedTime
})
} else {
//v.executionTime is the same
v.amountAppliedEffective = Amounts.add(v.amountAppliedEffective, effective).amount;
v.amountAppliedRaw = Amounts.add(v.amountAppliedRaw).amount
v.firstTimestamp = TalerProtocolTimestamp.min(v.firstTimestamp, refund.obtainedTime);
}
return prev
}, {} as Map<string, MergedRefundInfo>);
return Array.from(refundByExecTime.values());
}
function buildTransactionForRefund(purchaseRecord: PurchaseRecord, refundInfo: MergedRefundInfo, ort?: OperationRetryRecord): Transaction {
const contractData = purchaseRecord.download.contractData;
const info: OrderShortInfo = {
merchant: contractData.merchant,
orderId: contractData.orderId,
products: contractData.products,
summary: contractData.summary,
summary_i18n: contractData.summaryI18n,
contractTermsHash: contractData.contractTermsHash,
};
if (contractData.fulfillmentUrl !== "") {
info.fulfillmentUrl = contractData.fulfillmentUrl;
}
return {
type: TransactionType.Refund,
info,
refundedTransactionId: makeEventId(
TransactionType.Payment,
purchaseRecord.proposalId,
),
transactionId: makeEventId(
TransactionType.Refund,
purchaseRecord.proposalId,
`${refundInfo.executionTime.t_s}`,
),
timestamp: refundInfo.firstTimestamp,
amountEffective: Amounts.stringify(refundInfo.amountAppliedEffective),
amountRaw: Amounts.stringify(refundInfo.amountAppliedRaw),
refundPending:
purchaseRecord.refundAwaiting === undefined
? undefined
: Amounts.stringify(purchaseRecord.refundAwaiting),
pending: false,
frozen: false,
...(ort?.lastError ? { error: ort.lastError } : {}),
}
}
function buildTransactionForPurchase(purchaseRecord: PurchaseRecord, refundsInfo: MergedRefundInfo[], ort?: OperationRetryRecord): Transaction {
const contractData = purchaseRecord.download.contractData;
const zero = Amounts.getZero(contractData.amount.currency)
const info: OrderShortInfo = {
merchant: contractData.merchant,
orderId: contractData.orderId,
products: contractData.products,
summary: contractData.summary,
summary_i18n: contractData.summaryI18n,
contractTermsHash: contractData.contractTermsHash,
};
if (contractData.fulfillmentUrl !== "") {
info.fulfillmentUrl = contractData.fulfillmentUrl;
}
const totalRefund = refundsInfo.reduce((prev, cur) => {
return {
raw: Amounts.add(prev.raw, cur.amountAppliedRaw).amount,
effective: Amounts.add(prev.effective, cur.amountAppliedEffective).amount,
}
}, {
raw: zero, effective: zero
} as { raw: AmountJson, effective: AmountJson })
const refunds: RefundInfoShort[] = refundsInfo.map(r => ({
amountEffective: Amounts.stringify(r.amountAppliedEffective),
amountRaw: Amounts.stringify(r.amountAppliedRaw),
timestamp: r.executionTime,
transactionId: makeEventId(
TransactionType.Refund,
purchaseRecord.proposalId,
`${r.executionTime.t_s}`
),
}))
return {
type: TransactionType.Payment,
amountRaw: Amounts.stringify(contractData.amount),
amountEffective: Amounts.stringify(purchaseRecord.totalPayCost),
totalRefundRaw: Amounts.stringify(totalRefund.raw),
totalRefundEffective: Amounts.stringify(totalRefund.effective),
refundPending:
purchaseRecord.refundAwaiting === undefined
? undefined
: Amounts.stringify(purchaseRecord.refundAwaiting),
status: purchaseRecord.timestampFirstSuccessfulPay
? PaymentStatus.Paid
: PaymentStatus.Accepted,
pending:
!purchaseRecord.timestampFirstSuccessfulPay &&
purchaseRecord.abortStatus === AbortStatus.None,
refunds,
timestamp: purchaseRecord.timestampAccept,
transactionId: makeEventId(
TransactionType.Payment,
purchaseRecord.proposalId,
),
proposalId: purchaseRecord.proposalId,
info,
frozen: purchaseRecord.payFrozen ?? false,
...(ort?.lastError ? { error: ort.lastError } : {}),
}
}
/**
* Retrieve the full event history for this wallet.
*/
@ -137,7 +643,6 @@ export async function getTransactions(
x.proposals,
x.purchases,
x.recoupGroups,
x.recoupGroups,
x.tips,
x.tombstones,
x.withdrawalGroups,
@ -152,27 +657,7 @@ export async function getTransactions(
if (shouldSkipSearch(transactionsRequest, [])) {
return;
}
transactions.push({
type: TransactionType.PeerPushDebit,
amountEffective: pi.amount,
amountRaw: pi.amount,
exchangeBaseUrl: pi.exchangeBaseUrl,
info: {
expiration: pi.contractTerms.purse_expiration,
summary: pi.contractTerms.summary,
},
frozen: false,
pending: !pi.purseCreated,
timestamp: pi.timestampCreated,
talerUri: constructPayPushUri({
exchangeBaseUrl: pi.exchangeBaseUrl,
contractPriv: pi.contractPriv,
}),
transactionId: makeEventId(
TransactionType.PeerPushDebit,
pi.pursePub,
),
});
transactions.push(buildTransactionForPushPaymentDebit(pi));
});
tx.peerPullPaymentIncoming.iter().forEachAsync(async (pi) => {
@ -187,23 +672,7 @@ export async function getTransactions(
return;
}
transactions.push({
type: TransactionType.PeerPullDebit,
amountEffective: Amounts.stringify(amount),
amountRaw: Amounts.stringify(amount),
exchangeBaseUrl: pi.exchangeBaseUrl,
frozen: false,
pending: false,
info: {
expiration: pi.contractTerms.purse_expiration,
summary: pi.contractTerms.summary,
},
timestamp: pi.timestampCreated,
transactionId: makeEventId(
TransactionType.PeerPullDebit,
pi.peerPullPaymentIncomingId,
),
});
transactions.push(buildTransactionForPullPaymentDebit(pi));
});
tx.withdrawalGroups.iter().forEachAsync(async (wsr) => {
@ -223,64 +692,18 @@ export async function getTransactions(
const opId = RetryTags.forWithdrawal(wsr);
const ort = await tx.operationRetries.get(opId);
let withdrawalDetails: WithdrawalDetails;
if (wsr.wgInfo.withdrawalType === WithdrawalRecordType.PeerPullCredit) {
transactions.push({
type: TransactionType.PeerPullCredit,
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
exchangeBaseUrl: wsr.exchangeBaseUrl,
pending: !wsr.timestampFinish,
timestamp: wsr.timestampStart,
info: {
expiration: wsr.wgInfo.contractTerms.purse_expiration,
summary: wsr.wgInfo.contractTerms.summary,
},
talerUri: constructPayPullUri({
exchangeBaseUrl: wsr.exchangeBaseUrl,
contractPriv: wsr.wgInfo.contractPriv,
}),
transactionId: makeEventId(
TransactionType.PeerPullCredit,
wsr.withdrawalGroupId,
),
frozen: false,
...(ort?.lastError ? { error: ort.lastError } : {}),
});
transactions.push(buildTransactionForPullPaymentCredit(wsr, ort));
return;
} else if (
wsr.wgInfo.withdrawalType === WithdrawalRecordType.PeerPushCredit
) {
transactions.push({
type: TransactionType.PeerPushCredit,
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
exchangeBaseUrl: wsr.exchangeBaseUrl,
info: {
expiration: wsr.wgInfo.contractTerms.purse_expiration,
summary: wsr.wgInfo.contractTerms.summary,
},
pending: !wsr.timestampFinish,
timestamp: wsr.timestampStart,
transactionId: makeEventId(
TransactionType.PeerPushCredit,
wsr.withdrawalGroupId,
),
frozen: false,
...(ort?.lastError ? { error: ort.lastError } : {}),
});
transactions.push(buildTransactionForPushPaymentCredit(wsr, ort));
return;
} else if (
wsr.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated
) {
withdrawalDetails = {
type: WithdrawalType.TalerBankIntegrationApi,
confirmed: wsr.wgInfo.bankInfo.timestampBankConfirmed
? true
: false,
reservePub: wsr.reservePub,
bankConfirmationUrl: wsr.wgInfo.bankInfo.confirmUrl,
};
transactions.push(buildTransactionForBankIntegratedWithdraw(wsr, ort));
} else {
const exchangeDetails = await getExchangeDetails(
tx,
@ -290,31 +713,9 @@ export async function getTransactions(
// FIXME: report somehow
return;
}
withdrawalDetails = {
type: WithdrawalType.ManualTransfer,
reservePub: wsr.reservePub,
exchangePaytoUris:
exchangeDetails.wireInfo?.accounts.map(
(x) => addPaytoQueryParams(x.payto_uri, { subject: wsr.reservePub }),
) ?? [],
};
}
transactions.push({
type: TransactionType.Withdrawal,
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
amountRaw: Amounts.stringify(wsr.rawWithdrawalAmount),
withdrawalDetails,
exchangeBaseUrl: wsr.exchangeBaseUrl,
pending: !wsr.timestampFinish,
timestamp: wsr.timestampStart,
transactionId: makeEventId(
TransactionType.Withdrawal,
wsr.withdrawalGroupId,
),
frozen: false,
...(ort?.lastError ? { error: ort.lastError } : {}),
});
transactions.push(buildTransactionForManualWithdraw(wsr, exchangeDetails, ort));
}
});
tx.depositGroups.iter().forEachAsync(async (dg) => {
@ -324,21 +725,8 @@ export async function getTransactions(
}
const opId = RetryTags.forDeposit(dg);
const retryRecord = await tx.operationRetries.get(opId);
transactions.push({
type: TransactionType.Deposit,
amountRaw: Amounts.stringify(dg.effectiveDepositAmount),
amountEffective: Amounts.stringify(dg.totalPayCost),
pending: !dg.timestampFinished,
frozen: false,
timestamp: dg.timestampCreated,
targetPaytoUri: dg.wire.payto_uri,
transactionId: makeEventId(
TransactionType.Deposit,
dg.depositGroupId,
),
depositGroupId: dg.depositGroupId,
...(retryRecord?.lastError ? { error: retryRecord.lastError } : {}),
});
transactions.push(buildTransactionForDeposit(dg, retryRecord));
});
tx.purchases.iter().forEachAsync(async (pr) => {
@ -358,107 +746,31 @@ export async function getTransactions(
if (!proposal) {
return;
}
const info: OrderShortInfo = {
merchant: contractData.merchant,
orderId: contractData.orderId,
products: contractData.products,
summary: contractData.summary,
summary_i18n: contractData.summaryI18n,
contractTermsHash: contractData.contractTermsHash,
};
if (contractData.fulfillmentUrl !== "") {
info.fulfillmentUrl = contractData.fulfillmentUrl;
}
const paymentTransactionId = makeEventId(
TransactionType.Payment,
pr.proposalId,
);
const refundGroupKeys = new Set<string>();
for (const rk of Object.keys(pr.refunds)) {
const refund = pr.refunds[rk];
const groupKey = `${refund.executionTime.t_s}`;
refundGroupKeys.add(groupKey);
}
let totalRefundRaw = Amounts.getZero(contractData.amount.currency);
let totalRefundEffective = Amounts.getZero(
contractData.amount.currency,
);
const refunds: RefundInfoShort[] = [];
for (const groupKey of refundGroupKeys.values()) {
const refundTombstoneId = makeEventId(
const filteredRefunds = await Promise.all(Object.values(pr.refunds).map(async r => {
const t = await tx.tombstones.get(makeEventId(
TombstoneTag.DeleteRefund,
pr.proposalId,
groupKey,
`${r.executionTime.t_s}`,
))
if (!t) return r
return undefined
}));
const cleanRefunds = filteredRefunds.filter((x): x is WalletRefundItem => !!x);
const refunds = mergeRefundByExecutionTime(cleanRefunds, Amounts.getZero(contractData.amount.currency));
refunds.forEach(async (refundInfo) => {
const refundQueryOpId = RetryTags.forRefundQuery(pr);
const refundQueryRetryRecord = await tx.operationRetries.get(
refundQueryOpId,
);
const tombstone = await tx.tombstones.get(refundTombstoneId);
if (tombstone) {
continue;
}
const refundTransactionId = makeEventId(
TransactionType.Refund,
pr.proposalId,
groupKey,
);
let r0: WalletRefundItem | undefined;
let amountRaw = Amounts.getZero(contractData.amount.currency);
let amountEffective = Amounts.getZero(contractData.amount.currency);
for (const rk of Object.keys(pr.refunds)) {
const refund = pr.refunds[rk];
const myGroupKey = `${refund.executionTime.t_s}`;
if (myGroupKey !== groupKey) {
continue;
}
if (!r0) {
r0 = refund;
}
if (refund.type === RefundState.Applied) {
amountRaw = Amounts.add(amountRaw, refund.refundAmount).amount;
amountEffective = Amounts.add(
amountEffective,
Amounts.sub(
refund.refundAmount,
refund.refundFee,
refund.totalRefreshCostBound,
).amount,
).amount;
refunds.push({
transactionId: refundTransactionId,
timestamp: r0.obtainedTime,
amountEffective: Amounts.stringify(amountEffective),
amountRaw: Amounts.stringify(amountRaw),
});
}
}
if (!r0) {
throw Error("invariant violated");
}
totalRefundRaw = Amounts.add(totalRefundRaw, amountRaw).amount;
totalRefundEffective = Amounts.add(
totalRefundEffective,
amountEffective,
).amount;
transactions.push({
type: TransactionType.Refund,
info,
refundedTransactionId: paymentTransactionId,
transactionId: refundTransactionId,
timestamp: r0.obtainedTime,
amountEffective: Amounts.stringify(amountEffective),
amountRaw: Amounts.stringify(amountRaw),
refundPending:
pr.refundAwaiting === undefined
? undefined
: Amounts.stringify(pr.refundAwaiting),
pending: false,
frozen: false,
});
}
transactions.push(
buildTransactionForRefund(pr, refundInfo, refundQueryRetryRecord)
)
})
const payOpId = RetryTags.forPay(pr);
const refundQueryOpId = RetryTags.forRefundQuery(pr);
@ -467,32 +779,9 @@ export async function getTransactions(
refundQueryOpId,
);
const err =
refundQueryRetryRecord?.lastError ?? payRetryRecord?.lastError;
transactions.push({
type: TransactionType.Payment,
amountRaw: Amounts.stringify(contractData.amount),
amountEffective: Amounts.stringify(pr.totalPayCost),
totalRefundRaw: Amounts.stringify(totalRefundRaw),
totalRefundEffective: Amounts.stringify(totalRefundEffective),
refundPending:
pr.refundAwaiting === undefined
? undefined
: Amounts.stringify(pr.refundAwaiting),
status: pr.timestampFirstSuccessfulPay
? PaymentStatus.Paid
: PaymentStatus.Accepted,
pending:
!pr.timestampFirstSuccessfulPay &&
pr.abortStatus === AbortStatus.None,
refunds,
timestamp: pr.timestampAccept,
transactionId: paymentTransactionId,
proposalId: pr.proposalId,
info,
frozen: pr.payFrozen ?? false,
...(err ? { error: err } : {}),
});
const err = payRetryRecord !== undefined ? payRetryRecord : refundQueryRetryRecord
transactions.push(buildTransactionForPurchase(pr, refunds, err));
});
tx.tips.iter().forEachAsync(async (tipRecord) => {
@ -509,20 +798,7 @@ export async function getTransactions(
}
const opId = RetryTags.forTipPickup(tipRecord);
const retryRecord = await tx.operationRetries.get(opId);
transactions.push({
type: TransactionType.Tip,
amountEffective: Amounts.stringify(tipRecord.tipAmountEffective),
amountRaw: Amounts.stringify(tipRecord.tipAmountRaw),
pending: !tipRecord.pickedUpTimestamp,
frozen: false,
timestamp: tipRecord.acceptedTimestamp,
transactionId: makeEventId(
TransactionType.Tip,
tipRecord.walletTipId,
),
merchantBaseUrl: tipRecord.merchantBaseUrl,
error: retryRecord?.lastError,
});
transactions.push(buildTransactionForTip(tipRecord, retryRecord));
});
});

View File

@ -50,6 +50,7 @@ import {
TalerErrorCode,
TalerErrorDetail,
TalerProtocolTimestamp,
TransactionType,
UnblindedSignature,
URL,
VersionMatchResult,
@ -104,6 +105,7 @@ import {
getExchangeTrust,
updateExchangeFromUrl,
} from "./exchanges.js";
import { makeEventId } from "./transactions.js";
/**
* Logger for this file.
@ -256,7 +258,7 @@ export function selectWithdrawalDenominations(
DenominationRecord.getValue(d),
d.fees.feeWithdraw,
).amount;
for (;;) {
for (; ;) {
if (Amounts.cmp(remaining, cost) < 0) {
break;
}
@ -890,8 +892,7 @@ export async function updateWithdrawalDenoms(
denom.verificationStatus === DenominationVerificationStatus.Unverified
) {
logger.trace(
`Validating denomination (${current + 1}/${
denominations.length
`Validating denomination (${current + 1}/${denominations.length
}) signature of ${denom.denomPubHash}`,
);
let valid = false;
@ -974,7 +975,7 @@ async function queryReserve(
if (
resp.status === 404 &&
result.talerErrorResponse.code ===
TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN
TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN
) {
ws.notify({
type: NotificationType.ReserveNotYetFound,
@ -1003,10 +1004,16 @@ async function queryReserve(
return { ready: true };
}
enum BankStatusResultCode {
Done = "done",
Waiting = "waiting",
Aborted = "aborted",
}
export async function processWithdrawalGroup(
ws: InternalWalletState,
withdrawalGroupId: string,
options: {} = {},
options: object = {},
): Promise<OperationAttemptResult> {
logger.trace("processing withdrawal group", withdrawalGroupId);
const withdrawalGroup = await ws.db
@ -1053,13 +1060,15 @@ export async function processWithdrawalGroup(
};
}
}
break;
}
case ReserveRecordStatus.BankAborted:
case ReserveRecordStatus.BankAborted: {
// FIXME
return {
type: OperationAttemptResultType.Pending,
result: undefined,
};
}
case ReserveRecordStatus.Dormant:
// We can try to withdraw, nothing needs to be done with the reserve.
break;
@ -1288,7 +1297,7 @@ export async function getExchangeWithdrawalInfo(
) {
logger.warn(
`wallet's support for exchange protocol version ${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` +
`(exchange has ${exchangeDetails.protocolVersion}), checking for updates`,
`(exchange has ${exchangeDetails.protocolVersion}), checking for updates`,
);
}
}
@ -1540,12 +1549,6 @@ async function registerReserveWithBank(
ws.notify({ type: NotificationType.ReserveRegisteredWithBank });
}
enum BankStatusResultCode {
Done = "done",
Waiting = "waiting",
Aborted = "aborted",
}
interface BankStatusResult {
status: BankStatusResultCode;
}
@ -1790,6 +1793,10 @@ export async function acceptWithdrawalFromUri(
return {
reservePub: existingWithdrawalGroup.reservePub,
confirmTransferUrl: url,
transactionId: makeEventId(
TransactionType.Withdrawal,
existingWithdrawalGroup.withdrawalGroupId,
)
};
}
@ -1847,6 +1854,10 @@ export async function acceptWithdrawalFromUri(
return {
reservePub: withdrawalGroup.reservePub,
confirmTransferUrl: withdrawInfo.confirmTransferUrl,
transactionId: makeEventId(
TransactionType.Withdrawal,
withdrawalGroupId,
)
};
}
@ -1901,5 +1912,9 @@ export async function createManualWithdrawal(
return {
reservePub: withdrawalGroup.reservePub,
exchangePaytoUris: exchangePaytoUris,
transactionId: makeEventId(
TransactionType.Withdrawal,
withdrawalGroupId,
)
};
}

View File

@ -91,6 +91,7 @@ import {
OperationMap,
FeeDescription,
TalerErrorDetail,
codecForTransactionByIdRequest,
} from "@gnu-taler/taler-util";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import {
@ -198,6 +199,7 @@ import {
import { acceptTip, prepareTip, processTip } from "./operations/tip.js";
import {
deleteTransaction,
getTransactionById,
getTransactions,
retryTransaction,
} from "./operations/transactions.js";
@ -1080,6 +1082,10 @@ async function dispatchRequestInternal(
const req = codecForTransactionsRequest().decode(payload);
return await getTransactions(ws, req);
}
case "getTransactionById": {
const req = codecForTransactionByIdRequest().decode(payload);
return await getTransactionById(ws, req)
}
case "addExchange": {
const req = codecForAddExchangeRequest().decode(payload);
await updateExchangeFromUrl(ws, req.exchangeBaseUrl, {
@ -1227,8 +1233,7 @@ async function dispatchRequestInternal(
}
case "acceptTip": {
const req = codecForAcceptTipRequest().decode(payload);
await acceptTip(ws, req.walletTipId);
return {};
return await acceptTip(ws, req.walletTipId);
}
case "exportBackupPlain": {
return exportBackup(ws);

View File

@ -73,10 +73,17 @@ export function BankDetailsByPaytoType({
</p>
<table>
<tr>
<td>{payto.targetPath}</td>
<td>
<Amount value={amount} hideCurrency /> BTC
<div>
{payto.targetPath} <Amount value={amount} hideCurrency /> BTC
</div>
{payto.segwitAddrs.map((addr, i) => (
<div key={i}>
{addr} <Amount value={min} hideCurrency /> BTC
</div>
))}
</td>
<td></td>
<td>
<CopyButton
getContent={() =>
@ -85,21 +92,6 @@ export function BankDetailsByPaytoType({
/>
</td>
</tr>
{payto.segwitAddrs.map((addr, i) => (
<tr key={i}>
<td>{addr}</td>
<td>
<Amount value={min} hideCurrency /> BTC
</td>
<td>
<CopyButton
getContent={() =>
`${addr} ${Amounts.stringifyValue(min)} BTC`
}
/>
</td>
</tr>
))}
</table>
<p>
<i18n.Translate>

View File

@ -348,6 +348,7 @@ export const AlreadyPaidWithoutFulfillment = createExample(BaseView, {
payResult: {
type: ConfirmPayResultType.Done,
contractTerms: {} as any,
transactionId: "",
},
payStatus: {
status: PreparePayResultType.AlreadyConfirmed,
@ -386,6 +387,7 @@ export const AlreadyPaidWithFulfillment = createExample(BaseView, {
fulfillment_message: "thanks for buying!",
fulfillment_url: "https://demo.taler.net",
} as Partial<ContractTerms> as any,
transactionId: "",
},
payStatus: {
status: PreparePayResultType.AlreadyConfirmed,

View File

@ -35,6 +35,7 @@ export const AllOff = createExample(TestedComponent, {
onDownloadDatabase: async () => "this is the content of the database",
operations: [
{
id: "",
type: PendingTaskType.ExchangeUpdate,
exchangeBaseUrl: "http://exchange.url.",
givesLifeness: false,

View File

@ -70,13 +70,8 @@ interface Props {
}
async function getTransaction(tid: string): Promise<Transaction> {
const res = await wxApi.getTransactions();
const ts = res.transactions.filter((t) => t.transactionId === tid);
if (ts.length > 1) throw Error("more than one transaction with this id");
if (ts.length === 1) {
return ts[0];
}
throw Error("no transaction found");
const res = await wxApi.getTransactionById(tid);
return res;
}
export function TransactionPage({ tid, goToWalletHistory }: Props): VNode {

View File

@ -68,6 +68,10 @@ import {
WalletCoreVersion,
WithdrawUriInfoResponse,
ExchangeFullDetails,
Transaction,
AcceptTipResponse,
AcceptPeerPullPaymentResponse,
AcceptPeerPushPaymentResponse,
} from "@gnu-taler/taler-util";
import {
AddBackupProviderRequest,
@ -476,7 +480,7 @@ export function prepareTip(req: PrepareTipRequest): Promise<PrepareTipResult> {
return callBackend("prepareTip", req);
}
export function acceptTip(req: AcceptTipRequest): Promise<void> {
export function acceptTip(req: AcceptTipRequest): Promise<AcceptTipResponse> {
return callBackend("acceptTip", req);
}
@ -513,7 +517,7 @@ export function checkPeerPushPayment(
}
export function acceptPeerPushPayment(
req: AcceptPeerPushPaymentRequest,
): Promise<void> {
): Promise<AcceptPeerPushPaymentResponse> {
return callBackend("acceptPeerPushPayment", req);
}
export function initiatePeerPullPayment(
@ -528,6 +532,12 @@ export function checkPeerPullPayment(
}
export function acceptPeerPullPayment(
req: AcceptPeerPullPaymentRequest,
): Promise<void> {
): Promise<AcceptPeerPullPaymentResponse> {
return callBackend("acceptPeerPullPayment", req);
}
export function getTransactionById(tid: string): Promise<Transaction> {
return callBackend("getTransactionById", {
transactionId: tid
})
}