working on #7357
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:
parent
a66b636dee
commit
5d08379139
@ -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 {
|
||||
|
@ -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()))
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
)
|
||||
};
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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.
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user