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:
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: