commit 2efb0d71f3507bade1fbcb609251d6ebf18d9c81 parent 810b6d4a34b7f758c3181d6525aff77591262f0c Author: Sebastian <sebasjm@taler-systems.com> Date: Fri, 30 Jan 2026 10:51:31 -0300 fix #10627 The /kyc now returns etag even in noContent. This leads to breaking API where we were testing for empty body to infer that no kyc was required Diffstat:
12 files changed, 94 insertions(+), 107 deletions(-)
diff --git a/packages/merchant-backoffice-ui/src/Routing.tsx b/packages/merchant-backoffice-ui/src/Routing.tsx @@ -936,7 +936,7 @@ function KycBanner(): VNode { kycStatus !== undefined && !(kycStatus instanceof TalerError) && kycStatus.type === "ok" && - !!kycStatus.body && + kycStatus.body.kycRequired && kycStatus.body.kyc_data.findIndex( (d) => d.payto_kycauths !== undefined || d.access_token !== undefined, ) !== -1; diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx @@ -49,7 +49,7 @@ export function Sidebar({ mobile }: Props): VNode { kycStatus !== undefined && !(kycStatus instanceof TalerError) && kycStatus.type === "ok" && - !!kycStatus.body + kycStatus.body.kycRequired === true ? kycStatus.body.kyc_data : []; diff --git a/packages/merchant-backoffice-ui/src/hooks/instance.ts b/packages/merchant-backoffice-ui/src/hooks/instance.ts @@ -102,15 +102,14 @@ export function useInstanceKYCDetailsLongPolling() { data, (result) => { if (!result || result.type === "fail") return undefined; - if (!result.body) return undefined; if (!result.body.etag) return undefined; - return result.body + return result.body.etag }, - async (latestStatus) => { + async (latestEtag) => { const r = await lib.instance.getCurrentInstanceKycStatus(token!, { longpoll: { type: "state-change", - etag: latestStatus.etag!, + etag: latestEtag, timeout: LONG_POLL_DELAY } }); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/index.tsx @@ -80,7 +80,7 @@ export default function ListKYC(_p: Props): VNode { } const status = result.body; - if (!status) { + if (!status.kycRequired) { return <div>no kyc required</div>; } return ( diff --git a/packages/taler-harness/src/harness/tops.ts b/packages/taler-harness/src/harness/tops.ts @@ -859,7 +859,7 @@ export async function doTopsKycAuth( t.assertDeepEqual(kycStatus.case, "ok"); - t.assertTrue(kycStatus.body != null); + t.assertTrue(kycStatus.body.kycRequired === true); if ( kycStatus.body.kyc_data[0].status === @@ -910,7 +910,7 @@ export async function doTopsKycAuth( ); logger.info(`kyc status after transfer: ${j2s(kycStatus)}`); t.assertDeepEqual(kycStatus.case, "ok"); - t.assertTrue(kycStatus.body != null); + t.assertTrue(kycStatus.body.kycRequired === true); t.assertDeepEqual(kycStatus.body.kyc_data[0].status, "kyc-required"); t.assertTrue(typeof kycStatus.body.kyc_data[0].access_token === "string"); accessToken = kycStatus.body.kyc_data[0].access_token as AccessToken; diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit-rewrite.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit-rewrite.ts @@ -21,6 +21,7 @@ import { Configuration, encodeCrock, hashNormalizedPaytoUri, + isOperationOk, j2s, Logger, MerchantAccountKycRedirectsResponse, @@ -121,9 +122,14 @@ export async function runKycMerchantDepositRewriteTest(t: GlobalTestState) { let merchantBankAccount: PaytoString | undefined; { const kycStatus = await retryUntilNonNull(async () => { - const res = succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( - await merchantApi.getCurrentInstanceKycStatus(merchantAdminAccessToken), - ); + const d = await merchantApi.getCurrentInstanceKycStatus(merchantAdminAccessToken); + t.assertTrue(d.type === "ok") + t.assertTrue(d.body.kycRequired === true) + if (d.body.kyc_data[0].payto_kycauths) { + return d.body; + } + const res = d.body + if ( res && res.kyc_data[0].status !== MerchantAccountKycStatus.EXCHANGE_UNREACHABLE @@ -199,11 +205,11 @@ export async function runKycMerchantDepositRewriteTest(t: GlobalTestState) { { const kycStatus = await retryUntilNonNull(async () => { await merchant.runKyccheckOnce(); - const x = succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( - await merchantApi.getCurrentInstanceKycStatus(merchantAdminAccessToken), - ); - if (!!x && !x.kyc_data[0].payto_kycauths) { - return x; + const d = await merchantApi.getCurrentInstanceKycStatus(merchantAdminAccessToken); + t.assertTrue(d.type === "ok") + t.assertTrue(d.body.kycRequired === true) + if (d.body.kyc_data[0].payto_kycauths) { + return d.body; } return undefined; }); @@ -249,11 +255,16 @@ export async function runKycMerchantDepositRewriteTest(t: GlobalTestState) { // Required, since the merchant only checks infrequently. await merchant.runKyccheckOnce(); // Now we can check the status - const x = succeedOrThrow<MerchantAccountKycRedirectsResponse | void>( - await merchantApi.getCurrentInstanceKycStatus(merchantAdminAccessToken), - ); - if ((x?.kyc_data[0].limits?.length ?? 0) === 0) { - return x; + const d = await merchantApi.getCurrentInstanceKycStatus(merchantAdminAccessToken); + t.assertTrue(d.type === "ok") + t.assertTrue(d.body.kycRequired === true) + if (d.body.kyc_data[0].payto_kycauths) { + return d.body; + } + const res = d.body + + if ((res?.kyc_data[0].limits?.length ?? 0) === 0) { + return res; } return undefined; }); diff --git a/packages/taler-harness/src/integrationtests/test-merchant-bank-bad-wire-target.ts b/packages/taler-harness/src/integrationtests/test-merchant-bank-bad-wire-target.ts @@ -72,10 +72,10 @@ export async function runMerchantBankBadWireTargetTest(t: GlobalTestState) { merchant.makeInstanceBaseUrl("minst2"), ); - const kycStatus = - succeedOrThrow<TalerMerchantApi.MerchantAccountKycRedirectsResponse | void>( - await merchantClient.getCurrentInstanceKycStatus(accessToken), - ); + const d = await merchantClient.getCurrentInstanceKycStatus(accessToken); + t.assertTrue(d.type === "ok") + t.assertTrue(d.body.kycRequired === true) + const kycStatus = d.body t.assertTrue(!!kycStatus); diff --git a/packages/taler-harness/src/integrationtests/test-tops-aml-basic.ts b/packages/taler-harness/src/integrationtests/test-tops-aml-basic.ts @@ -76,17 +76,11 @@ export async function runTopsAmlBasicTest(t: GlobalTestState) { ); // Do KYC auth transfer { - const kycStatus = await merchantClient.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - {}, - ); - + const kycStatus = await merchantClient.getCurrentInstanceKycStatus(merchantAdminAccessToken); + t.assertTrue(kycStatus.type === "ok") + t.assertTrue(kycStatus.body.kycRequired === true) console.log(`kyc status: ${j2s(kycStatus)}`); - t.assertDeepEqual(kycStatus.case, "ok"); - - t.assertTrue(kycStatus.body != null); - t.assertDeepEqual(kycStatus.body.kyc_data[0].status, "kyc-wire-required"); const depositPaytoUri = kycStatus.body.kyc_data[0].payto_uri; @@ -114,16 +108,14 @@ export async function runTopsAmlBasicTest(t: GlobalTestState) { // Wait for auth transfer to be registered by the exchange { - const kycStatus = await merchantClient.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - { - reason: KycStatusLongPollingReason.AUTH_TRANSFER, - timeout: 30000, - }, - ); + const kycStatus = await merchantClient.getCurrentInstanceKycStatus(merchantAdminAccessToken, { + reason: KycStatusLongPollingReason.AUTH_TRANSFER, // FIXME: deprecated + timeout: 30000, + }); + t.assertTrue(kycStatus.type === "ok") + t.assertTrue(kycStatus.body.kycRequired === true) + logger.info(`kyc status after transfer: ${j2s(kycStatus)}`); - t.assertDeepEqual(kycStatus.case, "ok"); - t.assertTrue(kycStatus.body != null); t.assertDeepEqual(kycStatus.body.kyc_data[0].status, "kyc-required"); t.assertTrue(typeof kycStatus.body.kyc_data[0].access_token === "string"); accessToken = kycStatus.body.kyc_data[0].access_token as AccessToken; @@ -172,13 +164,10 @@ export async function runTopsAmlBasicTest(t: GlobalTestState) { await merchant.runKyccheckOnce(); { - const kycStatus = await merchantClient.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - { - reason: KycStatusLongPollingReason.AUTH_TRANSFER, - timeout: 30000, - }, - ); + const kycStatus = await merchantClient.getCurrentInstanceKycStatus(merchantAdminAccessToken, { + reason: KycStatusLongPollingReason.AUTH_TRANSFER, // FIXME: deprecated + timeout: 30000, + }); logger.info(`kyc status after accept-tos: ${j2s(kycStatus)}`); } diff --git a/packages/taler-harness/src/integrationtests/test-tops-aml-custom-addr-postal.ts b/packages/taler-harness/src/integrationtests/test-tops-aml-custom-addr-postal.ts @@ -64,17 +64,15 @@ export async function runTopsAmlCustomAddrPostalTest(t: GlobalTestState) { ); // Do KYC auth transfer { - const kycStatus = await merchantClient.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - {}, - ); + const kycStatus = await merchantClient.getCurrentInstanceKycStatus(merchantAdminAccessToken, { + reason: KycStatusLongPollingReason.AUTH_TRANSFER, // FIXME: deprecated + timeout: 30000, + }); + t.assertTrue(kycStatus.type === "ok") + t.assertTrue(kycStatus.body.kycRequired === true) console.log(`kyc status: ${j2s(kycStatus)}`); - t.assertDeepEqual(kycStatus.case, "ok"); - - t.assertTrue(kycStatus.body != null); - t.assertDeepEqual(kycStatus.body.kyc_data[0].status, "kyc-wire-required"); const depositPaytoUri = kycStatus.body.kyc_data[0].payto_uri; @@ -102,16 +100,14 @@ export async function runTopsAmlCustomAddrPostalTest(t: GlobalTestState) { // Wait for auth transfer to be registered by the exchange { - const kycStatus = await merchantClient.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - { - reason: KycStatusLongPollingReason.AUTH_TRANSFER, - timeout: 30000, - }, - ); + const kycStatus = await merchantClient.getCurrentInstanceKycStatus(merchantAdminAccessToken, { + reason: KycStatusLongPollingReason.AUTH_TRANSFER, // FIXME: deprecated + timeout: 30000, + }); + t.assertTrue(kycStatus.type === "ok") + t.assertTrue(kycStatus.body.kycRequired === true) + logger.info(`kyc status after transfer: ${j2s(kycStatus)}`); - t.assertDeepEqual(kycStatus.case, "ok"); - t.assertTrue(kycStatus.body != null); t.assertDeepEqual(kycStatus.body.kyc_data[0].status, "kyc-required"); t.assertTrue(typeof kycStatus.body.kyc_data[0].access_token === "string"); accessToken = kycStatus.body.kyc_data[0].access_token as AccessToken; @@ -164,13 +160,11 @@ export async function runTopsAmlCustomAddrPostalTest(t: GlobalTestState) { await merchant.runKyccheckOnce(); { - const kycStatus = await merchantClient.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - { - reason: KycStatusLongPollingReason.AUTH_TRANSFER, - timeout: 30000, - }, - ); + const kycStatus = await merchantClient.getCurrentInstanceKycStatus(merchantAdminAccessToken, { + reason: KycStatusLongPollingReason.AUTH_TRANSFER, // FIXME: deprecated + timeout: 30000, + }); + logger.info(`kyc status after accept-tos: ${j2s(kycStatus)}`); } diff --git a/packages/taler-harness/src/integrationtests/test-tops-aml-custom-addr-sms.ts b/packages/taler-harness/src/integrationtests/test-tops-aml-custom-addr-sms.ts @@ -58,17 +58,15 @@ export async function runTopsAmlCustomAddrSmsTest(t: GlobalTestState) { ); // Do KYC auth transfer { - const kycStatus = await merchantClient.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - {}, - ); + const kycStatus = await merchantClient.getCurrentInstanceKycStatus(merchantAdminAccessToken, { + reason: KycStatusLongPollingReason.AUTH_TRANSFER, // FIXME: deprecated + timeout: 30000, + }); + t.assertTrue(kycStatus.type === "ok") + t.assertTrue(kycStatus.body.kycRequired === true) console.log(`kyc status: ${j2s(kycStatus)}`); - t.assertDeepEqual(kycStatus.case, "ok"); - - t.assertTrue(kycStatus.body != null); - t.assertDeepEqual(kycStatus.body.kyc_data[0].status, "kyc-wire-required"); const depositPaytoUri = kycStatus.body.kyc_data[0].payto_uri; @@ -96,16 +94,15 @@ export async function runTopsAmlCustomAddrSmsTest(t: GlobalTestState) { // Wait for auth transfer to be registered by the exchange { - const kycStatus = await merchantClient.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - { - reason: KycStatusLongPollingReason.AUTH_TRANSFER, - timeout: 30000, - }, - ); + const kycStatus = await merchantClient.getCurrentInstanceKycStatus(merchantAdminAccessToken, { + reason: KycStatusLongPollingReason.AUTH_TRANSFER, // FIXME: deprecated + timeout: 30000, + }); + t.assertTrue(kycStatus.type === "ok") + t.assertTrue(kycStatus.body.kycRequired === true) + logger.info(`kyc status after transfer: ${j2s(kycStatus)}`); - t.assertDeepEqual(kycStatus.case, "ok"); - t.assertTrue(kycStatus.body != null); + t.assertDeepEqual(kycStatus.body.kyc_data[0].status, "kyc-required"); t.assertTrue(typeof kycStatus.body.kyc_data[0].access_token === "string"); accessToken = kycStatus.body.kyc_data[0].access_token as AccessToken; @@ -158,13 +155,10 @@ export async function runTopsAmlCustomAddrSmsTest(t: GlobalTestState) { await merchant.runKyccheckOnce(); { - const kycStatus = await merchantClient.getCurrentInstanceKycStatus( - merchantAdminAccessToken, - { - reason: KycStatusLongPollingReason.AUTH_TRANSFER, - timeout: 30000, - }, - ); + const kycStatus = await merchantClient.getCurrentInstanceKycStatus(merchantAdminAccessToken, { + reason: KycStatusLongPollingReason.AUTH_TRANSFER, // FIXME: deprecated + timeout: 30000, + }); logger.info(`kyc status after accept-tos: ${j2s(kycStatus)}`); } diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -881,12 +881,12 @@ export class TalerMerchantInstanceHttpClient { switch (resp.status) { case HttpStatusCode.Ok: { const f = await opSuccessFromHttp(resp, codecForAccountKycRedirects()); - return opFixedSuccess({ etag, ...f.body }) + return opFixedSuccess({ etag, kycRequired: true as const, ...f.body }) } case HttpStatusCode.NoContent: - return opEmptySuccess(); + return opFixedSuccess({ etag, kycRequired: false as const }) case HttpStatusCode.NotModified: - return opEmptySuccess(); + return opFixedSuccess({ etag, kycRequired: undefined }) case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: // FIXME: missing in docs diff --git a/packages/web-util/src/hooks/useAsync.ts b/packages/web-util/src/hooks/useAsync.ts @@ -88,16 +88,16 @@ export function useLongPolling<Res, Rt>( const minTime = opts?.minTime ?? 1000; const [retry, setRetry] = useState<{ - count: number; + // count: number; fn: (() => Promise<Res>) | undefined; startMs: number | undefined; }>({ - count: 0, + // count: 0, fn: undefined, startMs: undefined, }); - const result = useAsync(retry.fn, [retry.count, ...deps]); + const result = useAsync(retry.fn, [retry.startMs, ...deps]); const body = result ?? initial; @@ -105,13 +105,13 @@ export function useLongPolling<Res, Rt>( if (body === undefined) return; const _body = body; - const rt = shouldRetryFn(_body, retry.count); + const rt = shouldRetryFn(_body, retry.startMs ?? 0); if (rt === undefined) return; function doRetry(rt: Rt) { // call again setRetry((lt) => ({ - count: lt.count + 1, + // count: lt.count + 1, fn: () => retryFn(rt), startMs: new Date().getTime(), }));