diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts | 200 |
1 files changed, 167 insertions, 33 deletions
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts index b9c9728a1..333202a69 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts @@ -26,6 +26,7 @@ import { InitiatePeerPullCreditRequest, InitiatePeerPullCreditResponse, Logger, + TalerErrorCode, TalerPreciseTimestamp, TransactionAction, TransactionMajorState, @@ -33,12 +34,14 @@ import { TransactionState, TransactionType, WalletAccountMergeFlags, + WalletKycUuid, codecForAny, codecForWalletKycUuid, constructPayPullUri, encodeCrock, getRandomBytes, j2s, + makeErrorDetail, } from "@gnu-taler/taler-util"; import { readSuccessResponseJsonOrErrorCode, @@ -46,6 +49,8 @@ import { throwUnexpectedRequestError, } from "@gnu-taler/taler-util/http"; import { + KycPendingInfo, + KycUserType, PeerPullPaymentInitiationRecord, PeerPullPaymentInitiationStatus, WithdrawalGroupStatus, @@ -167,6 +172,75 @@ export async function queryPurseForPeerPullCredit( }; } +async function longpollKycStatus( + ws: InternalWalletState, + pursePub: string, + exchangeUrl: string, + kycInfo: KycPendingInfo, + userType: KycUserType, +): Promise<OperationAttemptResult> { + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPullCredit, + pursePub, + }); + const retryTag = constructTaskIdentifier({ + tag: PendingTaskType.PeerPullCredit, + pursePub, + }); + + 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.peerPullPaymentInitiations]) + .runReadWrite(async (tx) => { + const peerIni = await tx.peerPullPaymentInitiations.get( + pursePub, + ); + if (!peerIni) { + return; + } + if ( + peerIni.status !== PeerPullPaymentInitiationStatus.PendingMergeKycRequired + ) { + return; + } + const oldTxState = computePeerPullCreditTransactionState(peerIni); + peerIni.status = PeerPullPaymentInitiationStatus.PendingCreatePurse; + const newTxState = computePeerPullCreditTransactionState(peerIni); + await tx.peerPullPaymentInitiations.put(peerIni); + 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, + }; +} + export async function processPeerPullCredit( ws: InternalWalletState, pursePub: string, @@ -233,20 +307,16 @@ export async function processPeerPullCredit( type: OperationAttemptResultType.Longpoll, }; case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: { - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.PeerPullCredit, - pursePub: pullIni.pursePub, - }); - if (pullIni.kycInfo) { - await checkWithdrawalKycStatus( - ws, - pullIni.exchangeBaseUrl, - transactionId, - pullIni.kycInfo, - "individual", - ); + if (!pullIni.kycInfo) { + throw Error("invalid state, kycInfo required"); } - break; + return await longpollKycStatus( + ws, + pursePub, + pullIni.exchangeBaseUrl, + pullIni.kycInfo, + "individual", + ); } case PeerPullPaymentInitiationStatus.PendingCreatePurse: break; @@ -325,26 +395,7 @@ export async function processPeerPullCredit( const respJson = await httpResp.json(); const kycPending = codecForWalletKycUuid().decode(respJson); logger.info(`kyc uuid response: ${j2s(kycPending)}`); - - await ws.db - .mktx((x) => [x.peerPullPaymentInitiations]) - .runReadWrite(async (tx) => { - const peerIni = await tx.peerPullPaymentInitiations.get(pursePub); - if (!peerIni) { - return; - } - peerIni.kycInfo = { - paytoHash: kycPending.h_payto, - requirementRow: kycPending.requirement_row, - }; - peerIni.status = - PeerPullPaymentInitiationStatus.PendingMergeKycRequired; - await tx.peerPullPaymentInitiations.put(peerIni); - }); - return { - type: OperationAttemptResultType.Pending, - result: undefined, - }; + return processPeerPullCreditKycRequired(ws, pullIni, kycPending); } const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny()); @@ -368,6 +419,89 @@ export async function processPeerPullCredit( }; } +async function processPeerPullCreditKycRequired( + ws: InternalWalletState, + peerIni: PeerPullPaymentInitiationRecord, + kycPending: WalletKycUuid, +): Promise<OperationAttemptResult> { + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPullCredit, + pursePub: peerIni.pursePub, + }); + const { pursePub } = peerIni; + + const userType = "individual"; + const url = new URL( + `kyc-check/${kycPending.requirement_row}/${kycPending.h_payto}/${userType}`, + peerIni.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.peerPullPaymentInitiations]) + .runReadWrite(async (tx) => { + const peerInc = await tx.peerPullPaymentInitiations.get( + pursePub, + ); + if (!peerInc) { + return { + transitionInfo: undefined, + result: OperationAttemptResult.finishedEmpty(), + }; + } + const oldTxState = computePeerPullCreditTransactionState(peerInc); + peerInc.kycInfo = { + paytoHash: kycPending.h_payto, + requirementRow: kycPending.requirement_row, + }; + peerInc.kycUrl = kycStatus.kyc_url; + peerInc.status = PeerPullPaymentInitiationStatus.PendingMergeKycRequired; + const newTxState = computePeerPullCreditTransactionState(peerInc); + await tx.peerPullPaymentInitiations.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 { + type: OperationAttemptResultType.Pending, + result: undefined, + }; + } else { + throw Error(`unexpected response from kyc-check (${kycStatusRes.status})`); + } +} + /** * Check fees and available exchanges for a peer push payment initiation. */ |