taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 76278d5bce0e73a54c269a093649aaf87e416ec9
parent db7b7beea74e50463a0e5ff67db3b304047f2649
Author: Florian Dold <dold@taler.net>
Date:   Thu, 23 Jul 2026 03:02:58 +0200

wallet: use typed exchange client for kyc-check polling

Diffstat:
Mpackages/taler-util/src/http-client/exchange-client.ts | 14++++++++++++++
Mpackages/taler-wallet-core/src/kyc.ts | 116++++++++++++++++++++++++++++++++++++++-----------------------------------------
2 files changed, 70 insertions(+), 60 deletions(-)

diff --git a/packages/taler-util/src/http-client/exchange-client.ts b/packages/taler-util/src/http-client/exchange-client.ts @@ -667,6 +667,14 @@ export class TalerExchangeHttpClient { accountSig: EddsaSignatureString; longpoll?: boolean; awaitAuth?: boolean; + /** + * Long-poll target: return once the account reaches this KYC state. + */ + lpt?: number; + /** + * Long-poll until the account's rule generation is at least this value. + */ + minRule?: number; }): Promise< | OperationOk<AccountKycStatus> | OperationAlternative<HttpStatusCode.Accepted, AccountKycStatus> @@ -682,6 +690,12 @@ export class TalerExchangeHttpClient { if (awaitAuth !== undefined) { url.searchParams.set("await_auth", awaitAuth ? "YES" : "NO"); } + if (args.lpt !== undefined) { + url.searchParams.set("lpt", String(args.lpt)); + } + if (args.minRule !== undefined) { + url.searchParams.set("min_rule", String(args.minRule)); + } const resp = await this.fetch( url, diff --git a/packages/taler-wallet-core/src/kyc.ts b/packages/taler-wallet-core/src/kyc.ts @@ -21,21 +21,12 @@ import { AmountLike, Amounts, AmountString, - codecForAccountKycStatus, Duration, HttpStatusCode, Logger, TalerPreciseTimestamp, } from "@gnu-taler/taler-util"; -import { - HttpResponse, - readSuccessResponseJsonOrThrow, -} from "@gnu-taler/taler-util/http"; -import { - cancelableFetch, - cancelableLongPoll, - TaskRunResult, -} from "./common.js"; +import { TaskRunResult } from "./common.js"; import { DbPreciseTimestamp, timestampAbsoluteFromDb, @@ -43,6 +34,7 @@ import { } from "./db-common.js"; import { ReadyExchangeSummary } from "./exchanges.js"; import { WalletExecutionContext } from "./index.js"; +import { walletExchangeClient } from "./wallet.js"; /** * @fileoverview Helpers for KYC. @@ -384,14 +376,9 @@ export async function runKycCheckAlgo( accountPub: st.accountPub, }); - const headers = { - ["Account-Owner-Signature"]: sigResp.sig, - ["Account-Owner-Pub"]: st.accountPub, - }; - - const url = new URL(`kyc-check/${st.paytoHash}`, st.exchangeBaseUrl); - let doLongpoll: boolean; + let lpt: number | undefined; + let minRule: number | undefined; if (st.lastCheckStatus == null || !st.haveAccessToken) { doLongpoll = false; @@ -401,44 +388,62 @@ export async function runKycCheckAlgo( (st.lastCheckStatus === HttpStatusCode.NotFound && st.lastBadKycAuth) ) { doLongpoll = true; - url.searchParams.set("lpt", "1"); + lpt = 1; } else if (st.lastAmlReview) { doLongpoll = true; - url.searchParams.set("lpt", "1"); + lpt = 1; if (st.lastRuleGen != null) { - url.searchParams.set("min_rule", `${st.lastRuleGen}`); + minRule = st.lastRuleGen; } } else { doLongpoll = true; if (st.lastRuleGen != null) { - url.searchParams.set("min_rule", `${st.lastRuleGen}`); + minRule = st.lastRuleGen; } } - logger.info(`kyc url ${url.href}, longpoll=${doLongpoll}`); - - let kycStatusRes: HttpResponse; + logger.info( + `kyc check for ${st.paytoHash}, longpoll=${doLongpoll}, lpt=${lpt}, minRule=${minRule}`, + ); - if (doLongpoll) { - kycStatusRes = await cancelableLongPoll(wex, url, { - headers, - }); - } else { - kycStatusRes = await cancelableFetch(wex, url, { - headers, - }); - } + const exchangeClient = walletExchangeClient(st.exchangeBaseUrl, wex); + const kycRes = await exchangeClient.checkKycStatus({ + paytoHash: st.paytoHash, + accountPub: st.accountPub, + accountSig: sigResp.sig, + longpoll: doLongpoll, + lpt, + minRule, + }); + const status = kycRes.response.status; logger.trace( - `request to ${kycStatusRes.requestUrl} returned status ${kycStatusRes.status}`, + `request to ${kycRes.response.requestUrl} returned status ${status}`, ); - const respJson = await kycStatusRes.json(); + // Generic fields, read across all response kinds for the back-off comparison + // below. Success bodies carry rule_gen/aml_review; failures carry an error + // code. + let code: number | undefined; + let ruleGen: number | undefined; + let amlReview: boolean | undefined; + switch (kycRes.case) { + case "ok": + case HttpStatusCode.Accepted: + ruleGen = kycRes.body.rule_gen; + amlReview = kycRes.body.aml_review; + break; + case HttpStatusCode.NoContent: + break; + default: + code = kycRes.detail?.code; + break; + } const sameResp = - kycStatusRes.status === st.lastCheckStatus && - respJson.code === st.lastCheckCode && - respJson.rule_gen === st.lastRuleGen; + status === st.lastCheckStatus && + code === st.lastCheckCode && + ruleGen === st.lastRuleGen; if (sameResp) { logger.trace(`kyc-check response didn't change, retrying with back-off`); @@ -448,9 +453,9 @@ export async function runKycCheckAlgo( } const updatedStatus: GenericKycStatusResp["updatedStatus"] = { - lastAmlReview: respJson.aml_review, - lastCheckCode: respJson.code, - lastCheckStatus: kycStatusRes.status, + lastAmlReview: amlReview, + lastCheckCode: code, + lastCheckStatus: status, lastDeny: st.lastDeny, lastRuleGen: st.lastRuleGen, lastBadKycAuth: st.lastBadKycAuth ?? false, @@ -461,35 +466,26 @@ export async function runKycCheckAlgo( taskResult: TaskRunResult.progress(), updatedStatus, requiresAuth: - kycStatusRes.status === HttpStatusCode.Conflict || - kycStatusRes.status === HttpStatusCode.Forbidden || - (kycStatusRes.status === HttpStatusCode.NotFound && st.lastBadKycAuth), + status === HttpStatusCode.Conflict || + status === HttpStatusCode.Forbidden || + (status === HttpStatusCode.NotFound && st.lastBadKycAuth), }; let exposedLimits: AccountLimit[] | undefined = undefined; - switch (kycStatusRes.status) { + switch (kycRes.case) { case HttpStatusCode.NoContent: updatedStatus.lastDeny = undefined; break; - case HttpStatusCode.Ok: { - const resp = await readSuccessResponseJsonOrThrow( - kycStatusRes, - codecForAccountKycStatus(), - ); - updatedStatus.lastRuleGen = resp.rule_gen; + case "ok": + updatedStatus.lastRuleGen = kycRes.body.rule_gen; updatedStatus.lastDeny = undefined; - updatedStatus.accessToken = resp.access_token; + updatedStatus.accessToken = kycRes.body.access_token; break; - } case HttpStatusCode.Accepted: - const resp = await readSuccessResponseJsonOrThrow( - kycStatusRes, - codecForAccountKycStatus(), - ); - updatedStatus.accessToken = resp.access_token; - updatedStatus.lastRuleGen = resp.rule_gen; - exposedLimits = resp.limits; + updatedStatus.accessToken = kycRes.body.access_token; + updatedStatus.lastRuleGen = kycRes.body.rule_gen; + exposedLimits = kycRes.body.limits; rst.taskResult = TaskRunResult.longpollReturnedPending(); break; case HttpStatusCode.NotFound: