diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations')
-rw-r--r-- | packages/taler-wallet-core/src/operations/pay-peer.ts | 24 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/transactions.ts | 280 |
2 files changed, 226 insertions, 78 deletions
diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts index ef2c19c33..6e5f1b89b 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer.ts @@ -31,7 +31,7 @@ import { PreparePeerPullDebitRequest, PreparePeerPullDebitResponse, PreparePeerPushCredit, - CheckPeerPushPaymentResponse, + PreparePeerPushCreditResponse, Codec, codecForAmountString, codecForAny, @@ -100,7 +100,10 @@ import { import { getPeerPaymentBalanceDetailsInTx } from "./balance.js"; import { updateExchangeFromUrl } from "./exchanges.js"; import { getTotalRefreshCost } from "./refresh.js"; -import { internalCreateWithdrawalGroup } from "./withdraw.js"; +import { + getExchangeWithdrawalInfo, + internalCreateWithdrawalGroup, +} from "./withdraw.js"; const logger = new Logger("operations/peer-to-peer.ts"); @@ -623,7 +626,7 @@ export const codecForExchangePurseStatus = (): Codec<ExchangePurseStatus> => export async function preparePeerPushCredit( ws: InternalWalletState, req: PreparePeerPushCredit, -): Promise<CheckPeerPushPaymentResponse> { +): Promise<PreparePeerPushCreditResponse> { const uri = parsePayPushUri(req.talerUri); if (!uri) { @@ -658,6 +661,8 @@ export async function preparePeerPushCredit( if (existing) { return { amount: existing.existingContractTerms.amount, + amountEffective: existing.existingPushInc.estimatedAmountEffective, + amountRaw: existing.existingContractTerms.amount, contractTerms: existing.existingContractTerms, peerPushPaymentIncomingId: existing.existingPushInc.peerPushPaymentIncomingId, @@ -705,6 +710,13 @@ export async function preparePeerPushCredit( const withdrawalGroupId = encodeCrock(getRandomBytes(32)); + const wi = await getExchangeWithdrawalInfo( + ws, + exchangeBaseUrl, + Amounts.parseOrThrow(purseStatus.balance), + undefined, + ); + await ws.db .mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming]) .runReadWrite(async (tx) => { @@ -718,6 +730,10 @@ export async function preparePeerPushCredit( contractTermsHash, status: PeerPushPaymentIncomingStatus.Proposed, withdrawalGroupId, + currency: Amounts.currencyOf(purseStatus.balance), + estimatedAmountEffective: Amounts.stringify( + wi.withdrawalAmountEffective, + ), }); await tx.contractTerms.put({ @@ -728,6 +744,8 @@ export async function preparePeerPushCredit( return { amount: purseStatus.balance, + amountEffective: wi.withdrawalAmountEffective, + amountRaw: purseStatus.balance, contractTerms: dec.contractTerms, peerPushPaymentIncomingId, }; diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 1864a0b50..c988e1e84 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -58,6 +58,9 @@ import { WithdrawalGroupStatus, RefreshGroupRecord, RefreshOperationStatus, + PeerPushPaymentIncomingRecord, + PeerPushPaymentIncomingStatus, + PeerPullPaymentInitiationRecord, } from "../db.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { checkDbInvariant } from "../util/invariants.js"; @@ -135,8 +138,7 @@ export async function getTransactionById( const { type, args: rest } = parseId("txn", req.transactionId); if ( type === TransactionType.Withdrawal || - type === TransactionType.PeerPullCredit || - type === TransactionType.PeerPushCredit + type === TransactionType.PeerPullCredit ) { const withdrawalGroupId = rest[0]; return await ws.db @@ -165,24 +167,6 @@ export async function getTransactionById( 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, @@ -356,9 +340,15 @@ export async function getTransactionById( checkDbInvariant(!!ct); return buildTransactionForPushPaymentDebit(debit, ct.contractTermsRaw); }); + } else if (type === TransactionType.PeerPushCredit) { + // FIXME: Implement! + throw Error("getTransaction not yet implemented for PeerPushCredit"); + } else if (type === TransactionType.PeerPushCredit) { + // FIXME: Implement! + throw Error("getTransaction not yet implemented for PeerPullCredit"); } else { const unknownTxType: never = type; - throw Error(`can't delete a '${unknownTxType}' transaction`); + throw Error(`can't retrieve a '${unknownTxType}' transaction`); } } @@ -422,82 +412,144 @@ function buildTransactionForPullPaymentDebit( }; } -function buildTransactionForPullPaymentCredit( - wsr: WithdrawalGroupRecord, - ort?: OperationRetryRecord, +function buildTransactionForPeerPullCredit( + pullCredit: PeerPullPaymentInitiationRecord, + pullCreditOrt: OperationRetryRecord | undefined, + peerContractTerms: PeerContractTerms, + wsr: WithdrawalGroupRecord | undefined, + wsrOrt: OperationRetryRecord | undefined, ): Transaction { - if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPullCredit) { - throw Error(`Unexpected withdrawalType: ${wsr.wgInfo.withdrawalType}`); + if (wsr) { + if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPullCredit) { + throw Error(`Unexpected withdrawalType: ${wsr.wgInfo.withdrawalType}`); + } + /** + * FIXME: this should be handled in the withdrawal process. + * PeerPull withdrawal fails until reserve have funds but it is not + * an error from the user perspective. + */ + const silentWithdrawalErrorForInvoice = + wsrOrt?.lastError && + wsrOrt.lastError.code === + TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE && + Object.values(wsrOrt.lastError.errorsPerCoin ?? {}).every((e) => { + return ( + e.code === TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR && + e.httpStatusCode === 409 + ); + }); + return { + type: TransactionType.PeerPullCredit, + amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), + amountRaw: Amounts.stringify(wsr.instructedAmount), + exchangeBaseUrl: wsr.exchangeBaseUrl, + extendedStatus: wsr.timestampFinish + ? ExtendedStatus.Done + : ExtendedStatus.Pending, + pending: !wsr.timestampFinish, + timestamp: pullCredit.mergeTimestamp, + info: { + expiration: wsr.wgInfo.contractTerms.purse_expiration, + summary: wsr.wgInfo.contractTerms.summary, + }, + talerUri: constructPayPullUri({ + exchangeBaseUrl: wsr.exchangeBaseUrl, + contractPriv: wsr.wgInfo.contractPriv, + }), + transactionId: makeTransactionId( + TransactionType.PeerPullCredit, + pullCredit.pursePub, + ), + frozen: false, + ...(wsrOrt?.lastError + ? { + error: silentWithdrawalErrorForInvoice + ? undefined + : wsrOrt.lastError, + } + : {}), + }; } - /** - * FIXME: this should be handled in the withdrawal process. - * PeerPull withdrawal fails until reserve have funds but it is not - * an error from the user perspective. - */ - const silentWithdrawalErrorForInvoice = - ort?.lastError && - ort.lastError.code === TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE && - Object.values(ort.lastError.errorsPerCoin ?? {}).every((e) => { - return ( - e.code === TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR && - e.httpStatusCode === 409 - ); - }); + return { type: TransactionType.PeerPullCredit, - amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), - amountRaw: Amounts.stringify(wsr.instructedAmount), - exchangeBaseUrl: wsr.exchangeBaseUrl, - extendedStatus: wsr.timestampFinish - ? ExtendedStatus.Done - : ExtendedStatus.Pending, - pending: !wsr.timestampFinish, - timestamp: wsr.timestampStart, + amountEffective: Amounts.stringify(peerContractTerms.amount), + amountRaw: Amounts.stringify(peerContractTerms.amount), + exchangeBaseUrl: pullCredit.exchangeBaseUrl, + extendedStatus: ExtendedStatus.Pending, + pending: true, + timestamp: pullCredit.mergeTimestamp, info: { - expiration: wsr.wgInfo.contractTerms.purse_expiration, - summary: wsr.wgInfo.contractTerms.summary, + expiration: peerContractTerms.purse_expiration, + summary: peerContractTerms.summary, }, talerUri: constructPayPullUri({ - exchangeBaseUrl: wsr.exchangeBaseUrl, - contractPriv: wsr.wgInfo.contractPriv, + exchangeBaseUrl: pullCredit.exchangeBaseUrl, + contractPriv: pullCredit.contractPriv, }), transactionId: makeTransactionId( TransactionType.PeerPullCredit, - wsr.withdrawalGroupId, + pullCredit.pursePub, ), frozen: false, - ...(ort?.lastError - ? { error: silentWithdrawalErrorForInvoice ? undefined : ort.lastError } - : {}), + ...(pullCreditOrt?.lastError ? { error: pullCreditOrt.lastError } : {}), }; } -function buildTransactionForPushPaymentCredit( - wsr: WithdrawalGroupRecord, - ort?: OperationRetryRecord, +function buildTransactionForPeerPushCredit( + pushInc: PeerPushPaymentIncomingRecord, + pushOrt: OperationRetryRecord | undefined, + peerContractTerms: PeerContractTerms, + wsr: WithdrawalGroupRecord | undefined, + wsrOrt: OperationRetryRecord | undefined, ): Transaction { - if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) - throw Error(""); + if (wsr) { + if (wsr.wgInfo.withdrawalType !== WithdrawalRecordType.PeerPushCredit) { + throw Error("invalid withdrawal group type for push payment credit"); + } + + return { + type: TransactionType.PeerPushCredit, + amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), + amountRaw: Amounts.stringify(wsr.instructedAmount), + exchangeBaseUrl: wsr.exchangeBaseUrl, + info: { + expiration: wsr.wgInfo.contractTerms.purse_expiration, + summary: wsr.wgInfo.contractTerms.summary, + }, + extendedStatus: wsr.timestampFinish + ? ExtendedStatus.Done + : ExtendedStatus.Pending, + pending: !wsr.timestampFinish, + timestamp: wsr.timestampStart, + transactionId: makeTransactionId( + TransactionType.PeerPushCredit, + pushInc.peerPushPaymentIncomingId, + ), + frozen: false, + ...(wsrOrt?.lastError ? { error: wsrOrt.lastError } : {}), + }; + } + return { type: TransactionType.PeerPushCredit, - amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), - amountRaw: Amounts.stringify(wsr.instructedAmount), - exchangeBaseUrl: wsr.exchangeBaseUrl, + // FIXME: This is wrong, needs to consider fees! + amountEffective: Amounts.stringify(peerContractTerms.amount), + amountRaw: Amounts.stringify(peerContractTerms.amount), + exchangeBaseUrl: pushInc.exchangeBaseUrl, info: { - expiration: wsr.wgInfo.contractTerms.purse_expiration, - summary: wsr.wgInfo.contractTerms.summary, + expiration: peerContractTerms.purse_expiration, + summary: peerContractTerms.summary, }, - extendedStatus: wsr.timestampFinish - ? ExtendedStatus.Done - : ExtendedStatus.Pending, - pending: !wsr.timestampFinish, - timestamp: wsr.timestampStart, + extendedStatus: ExtendedStatus.Pending, + pending: true, + timestamp: pushInc.timestamp, transactionId: makeTransactionId( TransactionType.PeerPushCredit, - wsr.withdrawalGroupId, + pushInc.peerPushPaymentIncomingId, ), frozen: false, - ...(ort?.lastError ? { error: ort.lastError } : {}), + ...(pushOrt?.lastError ? { error: pushOrt.lastError } : {}), }; } @@ -926,6 +978,8 @@ export async function getTransactions( x.operationRetries, x.peerPullPaymentIncoming, x.peerPushPaymentInitiations, + x.peerPushPaymentIncoming, + x.peerPullPaymentInitiations, x.planchets, x.purchases, x.contractTerms, @@ -970,6 +1024,80 @@ export async function getTransactions( transactions.push(buildTransactionForPullPaymentDebit(pi)); }); + tx.peerPushPaymentIncoming.iter().forEachAsync(async (pi) => { + if (!pi.currency) { + // Legacy transaction + return; + } + if (shouldSkipCurrency(transactionsRequest, pi.currency)) { + return; + } + if (shouldSkipSearch(transactionsRequest, [])) { + return; + } + if (pi.status === PeerPushPaymentIncomingStatus.Proposed) { + // We don't report proposed push credit transactions, user needs + // to scan URI again and confirm to see it. + return; + } + const ct = await tx.contractTerms.get(pi.contractTermsHash); + let wg: WithdrawalGroupRecord | undefined = undefined; + let wgOrt: OperationRetryRecord | undefined = undefined; + if (pi.withdrawalGroupId) { + wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId); + if (wg) { + const withdrawalOpId = RetryTags.forWithdrawal(wg); + wgOrt = await tx.operationRetries.get(withdrawalOpId); + } + } + const pushIncOpId = RetryTags.forPeerPushCredit(pi); + let pushIncOrt = await tx.operationRetries.get(pushIncOpId); + + checkDbInvariant(!!ct); + transactions.push( + buildTransactionForPeerPushCredit( + pi, + pushIncOrt, + ct.contractTermsRaw, + wg, + wgOrt, + ), + ); + }); + + tx.peerPullPaymentInitiations.iter().forEachAsync(async (pi) => { + const currency = Amounts.currencyOf(pi.amount); + if (shouldSkipCurrency(transactionsRequest, currency)) { + return; + } + if (shouldSkipSearch(transactionsRequest, [])) { + return; + } + const ct = await tx.contractTerms.get(pi.contractTermsHash); + let wg: WithdrawalGroupRecord | undefined = undefined; + let wgOrt: OperationRetryRecord | undefined = undefined; + if (pi.withdrawalGroupId) { + wg = await tx.withdrawalGroups.get(pi.withdrawalGroupId); + if (wg) { + const withdrawalOpId = RetryTags.forWithdrawal(wg); + wgOrt = await tx.operationRetries.get(withdrawalOpId); + } + } + const pushIncOpId = RetryTags.forPeerPullPaymentInitiation(pi); + let pushIncOrt = await tx.operationRetries.get(pushIncOpId); + + checkDbInvariant(!!ct); + transactions.push( + buildTransactionForPeerPullCredit( + pi, + pushIncOrt, + ct.contractTermsRaw, + wg, + wgOrt, + ), + ); + }); + tx.refreshGroups.iter().forEachAsync(async (rg) => { if (shouldSkipCurrency(transactionsRequest, rg.currency)) { return; @@ -1009,10 +1137,12 @@ export async function getTransactions( switch (wsr.wgInfo.withdrawalType) { case WithdrawalRecordType.PeerPullCredit: - transactions.push(buildTransactionForPullPaymentCredit(wsr, ort)); + // Will be reported by the corresponding p2p transaction. + // FIXME: If this is an orphan withdrawal, still report it as a withdrawal! return; case WithdrawalRecordType.PeerPushCredit: - transactions.push(buildTransactionForPushPaymentCredit(wsr, ort)); + // Will be reported by the corresponding p2p transaction. + // FIXME: If this is an orphan withdrawal, still report it as a withdrawal! return; case WithdrawalRecordType.BankIntegrated: transactions.push( |