diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts | 267 |
1 files changed, 185 insertions, 82 deletions
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts index 1a79c7b87..9b563b37e 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts @@ -15,76 +15,74 @@ */ import { - PreparePeerPushCredit, - PreparePeerPushCreditResponse, - parsePayPushUri, - codecForPeerContractTerms, - TransactionType, - encodeCrock, - eddsaGetPublic, - decodeCrock, - codecForExchangeGetContractResponse, - getRandomBytes, - ContractTermsUtil, - Amounts, - TalerPreciseTimestamp, AcceptPeerPushPaymentResponse, + Amounts, ConfirmPeerPushCreditRequest, + ContractTermsUtil, ExchangePurseMergeRequest, HttpStatusCode, + Logger, PeerContractTerms, + PreparePeerPushCredit, + PreparePeerPushCreditResponse, + TalerErrorCode, + TalerPreciseTimestamp, TalerProtocolTimestamp, - WalletAccountMergeFlags, - codecForAny, - codecForWalletKycUuid, - j2s, - Logger, - ExchangePurseDeposits, TransactionAction, TransactionMajorState, TransactionMinorState, TransactionState, - TalerError, - TalerErrorCode, + TransactionType, + WalletAccountMergeFlags, WalletKycUuid, + codecForAny, + codecForExchangeGetContractResponse, + codecForPeerContractTerms, + codecForWalletKycUuid, + decodeCrock, + eddsaGetPublic, + encodeCrock, + getRandomBytes, + j2s, makeErrorDetail, + parsePayPushUri, } from "@gnu-taler/taler-util"; import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { InternalWalletState, KycPendingInfo, KycUserType, - PeerPullDebitRecordStatus, PeerPushPaymentIncomingRecord, PeerPushPaymentIncomingStatus, PendingTaskType, WithdrawalGroupStatus, WithdrawalRecordType, } from "../index.js"; +import { assertUnreachable } from "../util/assertUnreachable.js"; +import { checkDbInvariant } from "../util/invariants.js"; +import { + OperationAttemptResult, + OperationAttemptResultType, + constructTaskIdentifier, +} from "../util/retries.js"; +import { runLongpollAsync } from "./common.js"; import { updateExchangeFromUrl } from "./exchanges.js"; import { codecForExchangePurseStatus, getMergeReserveInfo, - queryCoinInfosForSelection, talerPaytoFromExchangeReserve, } from "./pay-peer-common.js"; import { + TransitionInfo, constructTransactionIdentifier, notifyTransition, stopLongpolling, } from "./transactions.js"; import { getExchangeWithdrawalInfo, - internalCreateWithdrawalGroup, + internalPerformCreateWithdrawalGroup, + internalPrepareCreateWithdrawalGroup, } from "./withdraw.js"; -import { checkDbInvariant } from "../util/invariants.js"; -import { - OperationAttemptResult, - OperationAttemptResultType, - constructTaskIdentifier, -} from "../util/retries.js"; -import { assertUnreachable } from "../util/assertUnreachable.js"; -import { runLongpollAsync } from "./common.js"; const logger = new Logger("pay-peer-push-credit.ts"); @@ -148,7 +146,7 @@ export async function preparePeerPushCredit( const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl); - const contractHttpResp = await ws.http.get(getContractUrl.href); + const contractHttpResp = await ws.http.fetch(getContractUrl.href); const contractResp = await readSuccessResponseJsonOrThrow( contractHttpResp, @@ -375,51 +373,19 @@ async function processPeerPushCreditKycRequired( } } -export async function processPeerPushCredit( +async function handlePendingMerge( ws: InternalWalletState, - peerPushPaymentIncomingId: string, + peerInc: PeerPushPaymentIncomingRecord, + contractTerms: PeerContractTerms, ): Promise<OperationAttemptResult> { - let peerInc: PeerPushPaymentIncomingRecord | undefined; - let contractTerms: PeerContractTerms | undefined; - await ws.db - .mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming]) - .runReadWrite(async (tx) => { - peerInc = await tx.peerPushPaymentIncoming.get(peerPushPaymentIncomingId); - if (!peerInc) { - return; - } - const ctRec = await tx.contractTerms.get(peerInc.contractTermsHash); - if (ctRec) { - contractTerms = ctRec.contractTermsRaw; - } - await tx.peerPushPaymentIncoming.put(peerInc); - }); - - if (!peerInc) { - throw Error( - `can't accept unknown incoming p2p push payment (${peerPushPaymentIncomingId})`, - ); - } - - checkDbInvariant(!!contractTerms); + const { peerPushPaymentIncomingId } = peerInc; + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushCredit, + peerPushPaymentIncomingId, + }); const amount = Amounts.parseOrThrow(contractTerms.amount); - if ( - peerInc.status === PeerPushPaymentIncomingStatus.PendingMergeKycRequired - ) { - if (!peerInc.kycInfo) { - throw Error("invalid state, kycInfo required"); - } - return await longpollKycStatus( - ws, - peerPushPaymentIncomingId, - peerInc.exchangeBaseUrl, - peerInc.kycInfo, - "individual", - ); - } - const mergeReserveInfo = await getMergeReserveInfo(ws, { exchangeBaseUrl: peerInc.exchangeBaseUrl, }); @@ -475,7 +441,7 @@ export async function processPeerPushCredit( ); logger.trace(`merge response: ${j2s(res)}`); - await internalCreateWithdrawalGroup(ws, { + const withdrawalGroupPrep = await internalPrepareCreateWithdrawalGroup(ws, { amount, wgInfo: { withdrawalType: WithdrawalRecordType.PeerPushCredit, @@ -490,23 +456,51 @@ export async function processPeerPushCredit( }, }); - await ws.db - .mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming]) + const txRes = await ws.db + .mktx((x) => [ + x.contractTerms, + x.peerPushPaymentIncoming, + x.withdrawalGroups, + x.reserves, + x.exchanges, + x.exchangeDetails, + x.exchangeTrust, + ]) .runReadWrite(async (tx) => { const peerInc = await tx.peerPushPaymentIncoming.get( peerPushPaymentIncomingId, ); if (!peerInc) { - return; + return undefined; } - if ( - peerInc.status === PeerPushPaymentIncomingStatus.PendingMerge || - peerInc.status === PeerPushPaymentIncomingStatus.PendingMergeKycRequired - ) { - peerInc.status = PeerPushPaymentIncomingStatus.Done; + let withdrawalTransition: TransitionInfo | undefined; + const oldTxState = computePeerPushCreditTransactionState(peerInc); + switch (peerInc.status) { + case PeerPushPaymentIncomingStatus.PendingMerge: + case PeerPushPaymentIncomingStatus.PendingMergeKycRequired: { + peerInc.status = PeerPushPaymentIncomingStatus.PendingWithdrawing; + const wgRes = await internalPerformCreateWithdrawalGroup( + ws, + tx, + withdrawalGroupPrep, + ); + peerInc.withdrawalGroupId = wgRes.withdrawalGroup.withdrawalGroupId; + break; + } } await tx.peerPushPaymentIncoming.put(peerInc); + const newTxState = computePeerPushCreditTransactionState(peerInc); + return { + peerPushCreditTransition: { oldTxState, newTxState }, + withdrawalTransition, + }; }); + notifyTransition( + ws, + withdrawalGroupPrep.transactionId, + txRes?.withdrawalTransition, + ); + notifyTransition(ws, transactionId, txRes?.peerPushCreditTransition); return { type: OperationAttemptResultType.Finished, @@ -514,6 +508,115 @@ export async function processPeerPushCredit( }; } +async function handlePendingWithdrawing( + ws: InternalWalletState, + peerInc: PeerPushPaymentIncomingRecord, +): Promise<OperationAttemptResult> { + if (!peerInc.withdrawalGroupId) { + throw Error("invalid db state (withdrawing, but no withdrawal group ID"); + } + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushCredit, + peerPushPaymentIncomingId: peerInc.peerPushPaymentIncomingId, + }); + const wgId = peerInc.withdrawalGroupId; + let finished: boolean = false; + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentIncoming, x.withdrawalGroups]) + .runReadWrite(async (tx) => { + const ppi = await tx.peerPushPaymentIncoming.get( + peerInc.peerPushPaymentIncomingId, + ); + if (!ppi) { + finished = true; + return; + } + if (ppi.status !== PeerPushPaymentIncomingStatus.PendingWithdrawing) { + finished = true; + return; + } + const oldTxState = computePeerPushCreditTransactionState(ppi); + const wg = await tx.withdrawalGroups.get(wgId); + if (!wg) { + // FIXME: Fail the operation instead? + return undefined; + } + switch (wg.status) { + case WithdrawalGroupStatus.Finished: + finished = true; + ppi.status = PeerPushPaymentIncomingStatus.Done; + break; + // FIXME: Also handle other final states! + } + await tx.peerPushPaymentIncoming.put(ppi); + const newTxState = computePeerPushCreditTransactionState(ppi); + return { + oldTxState, + newTxState, + }; + }); + notifyTransition(ws, transactionId, transitionInfo); + if (finished) { + return OperationAttemptResult.finishedEmpty(); + } else { + // FIXME: Return indicator that we depend on the other operation! + return OperationAttemptResult.pendingEmpty(); + } +} + +export async function processPeerPushCredit( + ws: InternalWalletState, + peerPushPaymentIncomingId: string, +): Promise<OperationAttemptResult> { + let peerInc: PeerPushPaymentIncomingRecord | undefined; + let contractTerms: PeerContractTerms | undefined; + await ws.db + .mktx((x) => [x.contractTerms, x.peerPushPaymentIncoming]) + .runReadWrite(async (tx) => { + peerInc = await tx.peerPushPaymentIncoming.get(peerPushPaymentIncomingId); + if (!peerInc) { + return; + } + const ctRec = await tx.contractTerms.get(peerInc.contractTermsHash); + if (ctRec) { + contractTerms = ctRec.contractTermsRaw; + } + await tx.peerPushPaymentIncoming.put(peerInc); + }); + + checkDbInvariant(!!contractTerms); + + if (!peerInc) { + throw Error( + `can't accept unknown incoming p2p push payment (${peerPushPaymentIncomingId})`, + ); + } + + switch (peerInc.status) { + case PeerPushPaymentIncomingStatus.PendingMergeKycRequired: { + if (!peerInc.kycInfo) { + throw Error("invalid state, kycInfo required"); + } + return await longpollKycStatus( + ws, + peerPushPaymentIncomingId, + peerInc.exchangeBaseUrl, + peerInc.kycInfo, + "individual", + ); + } + + case PeerPushPaymentIncomingStatus.PendingMerge: + return handlePendingMerge(ws, peerInc, contractTerms); + + case PeerPushPaymentIncomingStatus.PendingWithdrawing: + return handlePendingWithdrawing(ws, peerInc); + + default: + return OperationAttemptResult.finishedEmpty(); + } +} + export async function confirmPeerPushCredit( ws: InternalWalletState, req: ConfirmPeerPushCreditRequest, |