aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-06-05 13:33:14 +0200
committerFlorian Dold <florian@dold.me>2023-06-05 13:33:14 +0200
commite671880b9e11b020c3fc797fb45e6e4b8ee3ee62 (patch)
tree454aff17188f80be64ea5825c41f7857ce785473 /packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
parentfda5a0ed87a6473a6b34bd1ac07d5f1d45dfbc19 (diff)
downloadwallet-core-e671880b9e11b020c3fc797fb45e6e4b8ee3ee62.tar.xz
wallet-core: use long-polling for P2P kyc
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.ts209
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
+}