summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/transactions.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/transactions.ts')
-rw-r--r--packages/taler-wallet-core/src/transactions.ts234
1 files changed, 158 insertions, 76 deletions
diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts
index f6216d641..0f7b1c3ca 100644
--- a/packages/taler-wallet-core/src/transactions.ts
+++ b/packages/taler-wallet-core/src/transactions.ts
@@ -243,21 +243,29 @@ export async function getTransactionById(
const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord);
const ort = await tx.operationRetries.get(opId);
+ const exchangeDetails =
+ withdrawalGroupRecord.exchangeBaseUrl === undefined
+ ? undefined
+ : await getExchangeWireDetailsInTx(
+ tx,
+ withdrawalGroupRecord.exchangeBaseUrl,
+ );
+ // if (!exchangeDetails) throw Error("not exchange details");
+
if (
withdrawalGroupRecord.wgInfo.withdrawalType ===
WithdrawalRecordType.BankIntegrated
) {
return buildTransactionForBankIntegratedWithdraw(
withdrawalGroupRecord,
+ exchangeDetails,
ort,
);
}
- const exchangeDetails = await getExchangeWireDetailsInTx(
- tx,
- withdrawalGroupRecord.exchangeBaseUrl,
+ checkDbInvariant(
+ exchangeDetails !== undefined,
+ "manual withdrawal without exchange",
);
- if (!exchangeDetails) throw Error("not exchange details");
-
return buildTransactionForManualWithdraw(
withdrawalGroupRecord,
exchangeDetails,
@@ -402,7 +410,10 @@ export async function getTransactionById(
const debit = await tx.peerPushDebit.get(parsedTx.pursePub);
if (!debit) throw Error("not found");
const ct = await tx.contractTerms.get(debit.contractTermsHash);
- checkDbInvariant(!!ct);
+ checkDbInvariant(
+ !!ct,
+ `no contract terms for p2p push ${parsedTx.pursePub}`,
+ );
return buildTransactionForPushPaymentDebit(
debit,
ct.contractTermsRaw,
@@ -426,7 +437,10 @@ export async function getTransactionById(
const pushInc = await tx.peerPushCredit.get(peerPushCreditId);
if (!pushInc) throw Error("not found");
const ct = await tx.contractTerms.get(pushInc.contractTermsHash);
- checkDbInvariant(!!ct);
+ checkDbInvariant(
+ !!ct,
+ `no contract terms for p2p push ${peerPushCreditId}`,
+ );
let wg: WithdrawalGroupRecord | undefined = undefined;
let wgOrt: OperationRetryRecord | undefined = undefined;
@@ -438,7 +452,7 @@ export async function getTransactionById(
}
}
const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pushInc);
- let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
+ const pushIncOrt = await tx.operationRetries.get(pushIncOpId);
return buildTransactionForPeerPushCredit(
pushInc,
@@ -466,7 +480,7 @@ export async function getTransactionById(
const pushInc = await tx.peerPullCredit.get(pursePub);
if (!pushInc) throw Error("not found");
const ct = await tx.contractTerms.get(pushInc.contractTermsHash);
- checkDbInvariant(!!ct);
+ checkDbInvariant(!!ct, `no contract terms for p2p push ${pursePub}`);
let wg: WithdrawalGroupRecord | undefined = undefined;
let wgOrt: OperationRetryRecord | undefined = undefined;
@@ -589,6 +603,9 @@ function buildTransactionForPeerPullCredit(
);
});
const txState = computePeerPullCreditTransactionState(pullCredit);
+ checkDbInvariant(wsr.instructedAmount !== undefined, "wg uninitialized");
+ checkDbInvariant(wsr.denomsSel !== undefined, "wg uninitialized");
+ checkDbInvariant(wsr.exchangeBaseUrl !== undefined, "wg uninitialized");
return {
type: TransactionType.PeerPullCredit,
txState,
@@ -654,13 +671,16 @@ function buildTransactionForPeerPushCredit(
pushInc: PeerPushPaymentIncomingRecord,
pushOrt: OperationRetryRecord | undefined,
peerContractTerms: PeerContractTerms,
- wsr: WithdrawalGroupRecord | undefined,
+ wg: WithdrawalGroupRecord | undefined,
wsrOrt: OperationRetryRecord | undefined,
): Transaction {
- if (wsr) {
- if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) {
+ if (wg) {
+ if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) {
throw Error("invalid withdrawal group type for push payment credit");
}
+ checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized");
+ checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized");
+ checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized");
const txState = computePeerPushCreditTransactionState(pushInc);
return {
@@ -668,15 +688,15 @@ function buildTransactionForPeerPushCredit(
txState,
txActions: computePeerPushCreditTransactionActions(pushInc),
amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(wsr.instructedAmount))
- : Amounts.stringify(wsr.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(wsr.instructedAmount),
- exchangeBaseUrl: wsr.exchangeBaseUrl,
+ ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount))
+ : Amounts.stringify(wg.denomsSel.totalCoinValue),
+ amountRaw: Amounts.stringify(wg.instructedAmount),
+ exchangeBaseUrl: wg.exchangeBaseUrl,
info: {
expiration: peerContractTerms.purse_expiration,
summary: peerContractTerms.summary,
},
- timestamp: timestampPreciseFromDb(wsr.timestampStart),
+ timestamp: timestampPreciseFromDb(wg.timestampStart),
transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit,
peerPushCreditId: pushInc.peerPushCreditId,
@@ -712,37 +732,49 @@ function buildTransactionForPeerPushCredit(
}
function buildTransactionForBankIntegratedWithdraw(
- wgRecord: WithdrawalGroupRecord,
+ wg: WithdrawalGroupRecord,
+ exchangeDetails: ExchangeWireDetails | undefined,
ort?: OperationRetryRecord,
): TransactionWithdrawal {
- if (wgRecord.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated)
+ if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) {
throw Error("");
-
- const txState = computeWithdrawalTransactionStatus(wgRecord);
+ }
+ const instructedCurrency =
+ wg.instructedAmount === undefined
+ ? undefined
+ : Amounts.currencyOf(wg.instructedAmount);
+ const currency = wg.wgInfo.bankInfo.currency ?? instructedCurrency;
+ checkDbInvariant(currency !== undefined, "wg uninitialized (missing currency)");
+ const txState = computeWithdrawalTransactionStatus(wg);
+
+ const zero = Amounts.stringify(Amounts.zeroOfCurrency(currency));
return {
type: TransactionType.Withdrawal,
txState,
- txActions: computeWithdrawalTransactionActions(wgRecord),
- amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(Amounts.zeroOfAmount(wgRecord.instructedAmount))
- : Amounts.stringify(wgRecord.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(wgRecord.instructedAmount),
+ txActions: computeWithdrawalTransactionActions(wg),
+ exchangeBaseUrl: wg.exchangeBaseUrl,
+ amountEffective:
+ isUnsuccessfulTransaction(txState) || !wg.denomsSel
+ ? zero
+ : Amounts.stringify(wg.denomsSel.totalCoinValue),
+ amountRaw: !wg.instructedAmount
+ ? zero
+ : Amounts.stringify(wg.instructedAmount),
withdrawalDetails: {
type: WithdrawalType.TalerBankIntegrationApi,
- confirmed: wgRecord.wgInfo.bankInfo.timestampBankConfirmed ? true : false,
- exchangeCreditAccountDetails: wgRecord.wgInfo.exchangeCreditAccounts,
- reservePub: wgRecord.reservePub,
- bankConfirmationUrl: wgRecord.wgInfo.bankInfo.confirmUrl,
+ confirmed: wg.wgInfo.bankInfo.timestampBankConfirmed ? true : false,
+ exchangeCreditAccountDetails: wg.wgInfo.exchangeCreditAccounts,
+ reservePub: wg.reservePub,
+ bankConfirmationUrl: wg.wgInfo.bankInfo.confirmUrl,
reserveIsReady:
- wgRecord.status === WithdrawalGroupStatus.Done ||
- wgRecord.status === WithdrawalGroupStatus.PendingReady,
+ wg.status === WithdrawalGroupStatus.Done ||
+ wg.status === WithdrawalGroupStatus.PendingReady,
},
- kycUrl: wgRecord.kycUrl,
- exchangeBaseUrl: wgRecord.exchangeBaseUrl,
- timestamp: timestampPreciseFromDb(wgRecord.timestampStart),
+ kycUrl: wg.kycUrl,
+ timestamp: timestampPreciseFromDb(wg.timestampStart),
transactionId: constructTransactionIdentifier({
tag: TransactionType.Withdrawal,
- withdrawalGroupId: wgRecord.withdrawalGroupId,
+ withdrawalGroupId: wg.withdrawalGroupId,
}),
...(ort?.lastError ? { error: ort.lastError } : {}),
};
@@ -759,50 +791,50 @@ export function isUnsuccessfulTransaction(state: TransactionState): boolean {
}
function buildTransactionForManualWithdraw(
- withdrawalGroup: WithdrawalGroupRecord,
+ wg: WithdrawalGroupRecord,
exchangeDetails: ExchangeWireDetails,
ort?: OperationRetryRecord,
): TransactionWithdrawal {
- if (withdrawalGroup.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual)
+ if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankManual)
throw Error("");
const plainPaytoUris =
exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ?? [];
+ checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized");
+ checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized");
+ checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized");
const exchangePaytoUris = augmentPaytoUrisForWithdrawal(
plainPaytoUris,
- withdrawalGroup.reservePub,
- withdrawalGroup.instructedAmount,
+ wg.reservePub,
+ wg.instructedAmount,
);
- const txState = computeWithdrawalTransactionStatus(withdrawalGroup);
+ const txState = computeWithdrawalTransactionStatus(wg);
return {
type: TransactionType.Withdrawal,
txState,
- txActions: computeWithdrawalTransactionActions(withdrawalGroup),
+ txActions: computeWithdrawalTransactionActions(wg),
amountEffective: isUnsuccessfulTransaction(txState)
- ? Amounts.stringify(
- Amounts.zeroOfAmount(withdrawalGroup.instructedAmount),
- )
- : Amounts.stringify(withdrawalGroup.denomsSel.totalCoinValue),
- amountRaw: Amounts.stringify(withdrawalGroup.instructedAmount),
+ ? Amounts.stringify(Amounts.zeroOfAmount(wg.instructedAmount))
+ : Amounts.stringify(wg.denomsSel.totalCoinValue),
+ amountRaw: Amounts.stringify(wg.instructedAmount),
withdrawalDetails: {
type: WithdrawalType.ManualTransfer,
- reservePub: withdrawalGroup.reservePub,
+ reservePub: wg.reservePub,
exchangePaytoUris,
- exchangeCreditAccountDetails:
- withdrawalGroup.wgInfo.exchangeCreditAccounts,
+ exchangeCreditAccountDetails: wg.wgInfo.exchangeCreditAccounts,
reserveIsReady:
- withdrawalGroup.status === WithdrawalGroupStatus.Done ||
- withdrawalGroup.status === WithdrawalGroupStatus.PendingReady,
+ wg.status === WithdrawalGroupStatus.Done ||
+ wg.status === WithdrawalGroupStatus.PendingReady,
},
- kycUrl: withdrawalGroup.kycUrl,
- exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl,
- timestamp: timestampPreciseFromDb(withdrawalGroup.timestampStart),
+ kycUrl: wg.kycUrl,
+ exchangeBaseUrl: wg.exchangeBaseUrl,
+ timestamp: timestampPreciseFromDb(wg.timestampStart),
transactionId: constructTransactionIdentifier({
tag: TransactionType.Withdrawal,
- withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
+ withdrawalGroupId: wg.withdrawalGroupId,
}),
...(ort?.lastError ? { error: ort.lastError } : {}),
};
@@ -983,16 +1015,22 @@ async function lookupMaybeContractData(
return contractData;
}
-async function buildTransactionForPurchase(
+function buildTransactionForPurchase(
purchaseRecord: PurchaseRecord,
contractData: WalletContractData,
refundsInfo: RefundGroupRecord[],
ort?: OperationRetryRecord,
-): Promise<Transaction> {
+): Transaction {
const zero = Amounts.zeroOfAmount(contractData.amount);
const info: OrderShortInfo = {
- merchant: contractData.merchant,
+ merchant: {
+ name: contractData.merchant.name,
+ address: contractData.merchant.address,
+ email: contractData.merchant.email,
+ jurisdiction: contractData.merchant.jurisdiction,
+ website: contractData.merchant.website,
+ },
orderId: contractData.orderId,
summary: contractData.summary,
summary_i18n: contractData.summaryI18n,
@@ -1016,8 +1054,14 @@ async function buildTransactionForPurchase(
}));
const timestamp = purchaseRecord.timestampAccept;
- checkDbInvariant(!!timestamp);
- checkDbInvariant(!!purchaseRecord.payInfo);
+ checkDbInvariant(
+ !!timestamp,
+ `purchase ${purchaseRecord.orderId} without accepted time`,
+ );
+ checkDbInvariant(
+ !!purchaseRecord.payInfo,
+ `purchase ${purchaseRecord.orderId} without payinfo`,
+ );
const txState = computePayMerchantTransactionState(purchaseRecord);
return {
@@ -1071,24 +1115,30 @@ export async function getWithdrawalTransactionByUri(
if (!withdrawalGroupRecord) {
return undefined;
}
+ if (withdrawalGroupRecord.exchangeBaseUrl === undefined) {
+ // prepared and unconfirmed withdrawals are hidden
+ return undefined;
+ }
const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord);
const ort = await tx.operationRetries.get(opId);
+ const exchangeDetails = await getExchangeWireDetailsInTx(
+ tx,
+ withdrawalGroupRecord.exchangeBaseUrl,
+ );
+ if (!exchangeDetails) throw Error("not exchange details");
+
if (
withdrawalGroupRecord.wgInfo.withdrawalType ===
WithdrawalRecordType.BankIntegrated
) {
return buildTransactionForBankIntegratedWithdraw(
withdrawalGroupRecord,
+ exchangeDetails,
ort,
);
}
- const exchangeDetails = await getExchangeWireDetailsInTx(
- tx,
- withdrawalGroupRecord.exchangeBaseUrl,
- );
- if (!exchangeDetails) throw Error("not exchange details");
return buildTransactionForManualWithdraw(
withdrawalGroupRecord,
@@ -1155,7 +1205,7 @@ export async function getTransactions(
return;
}
const ct = await tx.contractTerms.get(pi.contractTermsHash);
- checkDbInvariant(!!ct);
+ checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
transactions.push(
buildTransactionForPushPaymentDebit(pi, ct.contractTermsRaw),
);
@@ -1229,9 +1279,9 @@ export async function getTransactions(
}
}
const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pi);
- let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
+ const pushIncOrt = await tx.operationRetries.get(pushIncOpId);
- checkDbInvariant(!!ct);
+ checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
transactions.push(
buildTransactionForPeerPushCredit(
pi,
@@ -1263,9 +1313,9 @@ export async function getTransactions(
}
}
const pushIncOpId = TaskIdentifiers.forPeerPullPaymentInitiation(pi);
- let pushIncOrt = await tx.operationRetries.get(pushIncOpId);
+ const pushIncOrt = await tx.operationRetries.get(pushIncOpId);
- checkDbInvariant(!!ct);
+ checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`);
transactions.push(
buildTransactionForPeerPullCredit(
pi,
@@ -1331,6 +1381,13 @@ export async function getTransactions(
});
await iterRecordsForWithdrawal(tx, filter, async (wsr) => {
+ if (
+ wsr.rawWithdrawalAmount === undefined ||
+ wsr.exchangeBaseUrl == undefined
+ ) {
+ // skip prepared withdrawals which has not been confirmed
+ return;
+ }
const exchangesInTx = [wsr.exchangeBaseUrl];
if (
shouldSkipCurrency(
@@ -1360,11 +1417,26 @@ export async function getTransactions(
// FIXME: If this is an orphan withdrawal, still report it as a withdrawal!
// FIXME: Still report if requested with verbose option?
return;
- case WithdrawalRecordType.BankIntegrated:
+ case WithdrawalRecordType.BankIntegrated: {
+ const exchangeDetails = await getExchangeWireDetailsInTx(
+ tx,
+ wsr.exchangeBaseUrl,
+ );
+ if (!exchangeDetails) {
+ // FIXME: report somehow
+ return;
+ }
+
transactions.push(
- buildTransactionForBankIntegratedWithdraw(wsr, ort),
+ buildTransactionForBankIntegratedWithdraw(
+ wsr,
+ exchangeDetails,
+ ort,
+ ),
);
return;
+ }
+
case WithdrawalRecordType.BankManual: {
const exchangeDetails = await getExchangeWireDetailsInTx(
tx,
@@ -1374,7 +1446,6 @@ export async function getTransactions(
// FIXME: report somehow
return;
}
-
transactions.push(
buildTransactionForManualWithdraw(wsr, exchangeDetails, ort),
);
@@ -1475,7 +1546,7 @@ export async function getTransactions(
);
transactions.push(
- await buildTransactionForPurchase(
+ buildTransactionForPurchase(
purchase,
contractData,
refunds,
@@ -1726,7 +1797,18 @@ export async function retryTransaction(
logger.info(`resetting retry timeout for ${transactionId}`);
const taskId = maybeTaskFromTransaction(transactionId);
if (taskId) {
- wex.taskScheduler.resetTaskRetries(taskId);
+ await wex.taskScheduler.resetTaskRetries(taskId);
+ }
+}
+
+/**
+ * Reset the task retry counter for all tasks.
+ */
+export async function retryAll(wex: WalletExecutionContext): Promise<void> {
+ await wex.taskScheduler.ensureRunning();
+ const tasks = wex.taskScheduler.getActiveTasks();
+ for (const task of tasks) {
+ await wex.taskScheduler.resetTaskRetries(task);
}
}