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 | 209 |
1 files changed, 174 insertions, 35 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 69e0f3c27..91b0b6022 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 @@ -44,10 +44,16 @@ import { TransactionMajorState, TransactionMinorState, TransactionState, + TalerError, + TalerErrorCode, + WalletKycUuid, + makeErrorDetail, } from "@gnu-taler/taler-util"; import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { InternalWalletState, + KycPendingInfo, + KycUserType, PeerPullDebitRecordStatus, PeerPushPaymentIncomingRecord, PeerPushPaymentIncomingStatus, @@ -62,9 +68,12 @@ import { queryCoinInfosForSelection, talerPaytoFromExchangeReserve, } from "./pay-peer-common.js"; -import { constructTransactionIdentifier, notifyTransition, stopLongpolling } from "./transactions.js"; import { - checkWithdrawalKycStatus, + constructTransactionIdentifier, + notifyTransition, + stopLongpolling, +} from "./transactions.js"; +import { getExchangeWithdrawalInfo, internalCreateWithdrawalGroup, } from "./withdraw.js"; @@ -75,6 +84,7 @@ import { 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"); @@ -215,6 +225,156 @@ export async function preparePeerPushCredit( }; } +async function longpollKycStatus( + ws: InternalWalletState, + peerPushPaymentIncomingId: string, + exchangeUrl: string, + kycInfo: KycPendingInfo, + userType: KycUserType, +): Promise<OperationAttemptResult> { + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushCredit, + peerPushPaymentIncomingId, + }); + const retryTag = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushCredit, + peerPushPaymentIncomingId, + }); + + runLongpollAsync(ws, retryTag, async (ct) => { + const url = new URL( + `kyc-check/${kycInfo.requirementRow}/${kycInfo.paytoHash}/${userType}`, + exchangeUrl, + ); + url.searchParams.set("timeout_ms", "10000"); + logger.info(`kyc url ${url.href}`); + const kycStatusRes = await ws.http.fetch(url.href, { + method: "GET", + cancellationToken: ct, + }); + if ( + kycStatusRes.status === HttpStatusCode.Ok || + //FIXME: NoContent is not expected https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-merge + // remove after the exchange is fixed or clarified + kycStatusRes.status === HttpStatusCode.NoContent + ) { + const transitionInfo = await ws.db + .mktx((x) => [x.peerPushPaymentIncoming]) + .runReadWrite(async (tx) => { + const peerInc = await tx.peerPushPaymentIncoming.get( + peerPushPaymentIncomingId, + ); + if (!peerInc) { + return; + } + if ( + peerInc.status !== + PeerPushPaymentIncomingStatus.PendingMergeKycRequired + ) { + return; + } + const oldTxState = computePeerPushCreditTransactionState(peerInc); + peerInc.status = PeerPushPaymentIncomingStatus.PendingMerge; + const newTxState = computePeerPushCreditTransactionState(peerInc); + await tx.peerPushPaymentIncoming.put(peerInc); + return { oldTxState, newTxState }; + }); + notifyTransition(ws, transactionId, transitionInfo); + return { ready: true }; + } else if (kycStatusRes.status === HttpStatusCode.Accepted) { + // FIXME: Do we have to update the URL here? + return { ready: false }; + } else { + throw Error( + `unexpected response from kyc-check (${kycStatusRes.status})`, + ); + } + }); + return { + type: OperationAttemptResultType.Longpoll, + }; +} + +async function processPeerPushCreditKycRequired( + ws: InternalWalletState, + peerInc: PeerPushPaymentIncomingRecord, + kycPending: WalletKycUuid, +): Promise<OperationAttemptResult> { + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushCredit, + peerPushPaymentIncomingId: peerInc.peerPushPaymentIncomingId, + }); + const { peerPushPaymentIncomingId } = peerInc; + + const userType = "individual"; + const url = new URL( + `kyc-check/${kycPending.requirement_row}/${kycPending.h_payto}/${userType}`, + peerInc.exchangeBaseUrl, + ); + + logger.info(`kyc url ${url.href}`); + const kycStatusRes = await ws.http.fetch(url.href, { + method: "GET", + }); + + if ( + kycStatusRes.status === HttpStatusCode.Ok || + //FIXME: NoContent is not expected https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-merge + // remove after the exchange is fixed or clarified + kycStatusRes.status === HttpStatusCode.NoContent + ) { + logger.warn("kyc requested, but already fulfilled"); + return { + type: OperationAttemptResultType.Finished, + result: undefined, + }; + } else if (kycStatusRes.status === HttpStatusCode.Accepted) { + const kycStatus = await kycStatusRes.json(); + logger.info(`kyc status: ${j2s(kycStatus)}`); + const { transitionInfo, result } = await ws.db + .mktx((x) => [x.peerPushPaymentIncoming]) + .runReadWrite(async (tx) => { + const peerInc = await tx.peerPushPaymentIncoming.get( + peerPushPaymentIncomingId, + ); + if (!peerInc) { + return { + transitionInfo: undefined, + result: OperationAttemptResult.finishedEmpty(), + }; + } + const oldTxState = computePeerPushCreditTransactionState(peerInc); + peerInc.kycInfo = { + paytoHash: kycPending.h_payto, + requirementRow: kycPending.requirement_row, + }; + peerInc.kycUrl = kycStatus.kyc_url; + peerInc.status = PeerPushPaymentIncomingStatus.PendingMergeKycRequired; + const newTxState = computePeerPushCreditTransactionState(peerInc); + await tx.peerPushPaymentIncoming.put(peerInc); + // We'll remove this eventually! New clients should rely on the + // kycUrl field of the transaction, not the error code. + const res: OperationAttemptResult = { + type: OperationAttemptResultType.Error, + errorDetail: makeErrorDetail( + TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED, + { + kycUrl: kycStatus.kyc_url, + }, + ), + }; + return { + transitionInfo: { oldTxState, newTxState }, + result: res, + }; + }); + notifyTransition(ws, transactionId, transitionInfo); + return result; + } else { + throw Error(`unexpected response from kyc-check (${kycStatusRes.status})`); + } +} + export async function processPeerPushCredit( ws: InternalWalletState, peerPushPaymentIncomingId: string, @@ -246,17 +406,15 @@ export async function processPeerPushCredit( const amount = Amounts.parseOrThrow(contractTerms.amount); if ( - peerInc.status === PeerPushPaymentIncomingStatus.PendingMergeKycRequired && - peerInc.kycInfo + peerInc.status === PeerPushPaymentIncomingStatus.PendingMergeKycRequired ) { - const txId = constructTransactionIdentifier({ - tag: TransactionType.PeerPushCredit, - peerPushPaymentIncomingId: peerInc.peerPushPaymentIncomingId, - }); - await checkWithdrawalKycStatus( + if (!peerInc.kycInfo) { + throw Error("invalid state, kycInfo required"); + } + return await longpollKycStatus( ws, + peerPushPaymentIncomingId, peerInc.exchangeBaseUrl, - txId, peerInc.kycInfo, "individual", ); @@ -298,33 +456,16 @@ export async function processPeerPushCredit( reserve_sig: sigRes.accountSig, }; - const mergeHttpResp = await ws.http.postJson(mergePurseUrl.href, mergeReq); + const mergeHttpResp = await ws.http.fetch(mergePurseUrl.href, { + method: "POST", + body: mergeReq, + }); if (mergeHttpResp.status === HttpStatusCode.UnavailableForLegalReasons) { const respJson = await mergeHttpResp.json(); const kycPending = codecForWalletKycUuid().decode(respJson); logger.info(`kyc uuid response: ${j2s(kycPending)}`); - - await ws.db - .mktx((x) => [x.peerPushPaymentIncoming]) - .runReadWrite(async (tx) => { - const peerInc = await tx.peerPushPaymentIncoming.get( - peerPushPaymentIncomingId, - ); - if (!peerInc) { - return; - } - peerInc.kycInfo = { - paytoHash: kycPending.h_payto, - requirementRow: kycPending.requirement_row, - }; - peerInc.status = PeerPushPaymentIncomingStatus.PendingMergeKycRequired; - await tx.peerPushPaymentIncoming.put(peerInc); - }); - return { - type: OperationAttemptResultType.Pending, - result: undefined, - }; + processPeerPushCreditKycRequired(ws, peerInc, kycPending); } logger.trace(`merge request: ${j2s(mergeReq)}`); @@ -412,7 +553,6 @@ export async function confirmPeerPushCredit( }; } - export async function processPeerPullDebit( ws: InternalWalletState, peerPullPaymentIncomingId: string, @@ -483,7 +623,6 @@ export async function processPeerPullDebit( }; } - export async function suspendPeerPushCreditTransaction( ws: InternalWalletState, peerPushPaymentIncomingId: string, @@ -767,4 +906,4 @@ export function computePeerPushCreditTransactionActions( default: assertUnreachable(pushCreditRecord.status); } -}
\ No newline at end of file +} |