taler-typescript-core

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

commit e9668264a6a6a0fb0522de46ecbd89178054db02
parent a5a490e3e4026d692b3db682a2d6cf58c7a7a3f4
Author: Sebastian <sebasjm@gmail.com>
Date:   Mon,  8 Sep 2025 10:05:43 -0300

support for merchant v21, missing pieces

Diffstat:
Mpackages/taler-harness/src/index.ts | 57++++++++++++++++++++++++++++++++-------------------------
Mpackages/taler-harness/src/integrationtests/test-merchant-self-provision-activation.ts | 76++++++++++++++++++++++------------------------------------------------------
Mpackages/taler-util/src/http-client/exchange-client.ts | 2++
Mpackages/taler-util/src/http-client/merchant.ts | 15+++++++--------
4 files changed, 63 insertions(+), 87 deletions(-)

diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts @@ -797,7 +797,7 @@ deploymentCli deploymentCli .subcommand("provisionBankMerchant", "provision-bank-and-merchant", { - help: "Provision a bank account, merchant instance and link them together.", + help: "Provision a bank account, merchant instance and link them together. It will fail if merchant backend requires MFA.", }) .requiredArgument("merchantApiBaseUrl", clk.STRING, { help: "URL location of the merchant backend", @@ -805,14 +805,9 @@ deploymentCli .requiredArgument("corebankApiBaseUrl", clk.STRING, { help: "URL location of the libeufin bank backend", }) - .maybeOption( - "merchantToken", - ["--merchant-management-token"], - clk.STRING, - { - help: "access token of the default instance in the merchant backend", - }, - ) + .maybeOption("merchantToken", ["--merchant-management-token"], clk.STRING, { + help: "access token of the default instance in the merchant backend", + }) .maybeOption( "merchantPassword", ["--merchant-management-password"], @@ -849,9 +844,9 @@ deploymentCli help: "if everything worked ok, change the password of the accounts at the end", }) .action(async (args) => { - const merchantAdminTokenArg = args.provisionBankMerchant.merchantToken ? createRFC8959AccessTokenPlain( - args.provisionBankMerchant.merchantToken, - ) : undefined; + const merchantAdminTokenArg = args.provisionBankMerchant.merchantToken + ? createRFC8959AccessTokenPlain(args.provisionBankMerchant.merchantToken) + : undefined; const merchantAdminPassword = args.provisionBankMerchant.merchantPassword; const bankAdminPassword = args.provisionBankMerchant.bankPassword; @@ -961,9 +956,9 @@ deploymentCli contact_data: email || phone ? { - email: email, - phone: phone, - } + email: email, + phone: phone, + } : undefined, }); @@ -1036,7 +1031,11 @@ deploymentCli console.error( `unable to configure bank account for instance ${id}, status ${resp.case}`, ); - console.error(j2s(resp.detail)); + console.error( + resp.case === HttpStatusCode.Accepted + ? `the merchant requires MFA for this account` + : j2s(resp.detail), + ); process.exit(2); } wireAccount = resp.body.h_wire; @@ -1167,7 +1166,11 @@ deploymentCli console.error( `unable to change merchant password for instance ${id}, status ${resp.case}`, ); - console.error(j2s(resp.detail)); + console.error( + resp.case === HttpStatusCode.Accepted + ? `the merchant requires MFA for this account` + : j2s(resp.detail), + ); process.exit(2); } } @@ -1217,7 +1220,7 @@ deploymentCli deploymentCli .subcommand("provisionMerchantInstance", "provision-merchant-instance", { - help: "Provision a merchant backend instance.", + help: "Provision a merchant backend instance. It will fail if merchant backend require MFA.", }) .requiredArgument("merchantApiBaseUrl", clk.STRING) .requiredOption("managementToken", ["--management-token"], clk.STRING, {}) @@ -1318,17 +1321,21 @@ deploymentCli credit_facade_credentials: bankUser && bankPassword ? { - type: "basic", - username: bankUser, - password: bankPassword, - } + type: "basic", + username: bankUser, + password: bankPassword, + } : undefined, }); if (createAccountResp.type != "ok") { console.error( `unable to configure bank account for instance ${instanceId}, status ${createAccountResp.case}`, ); - console.error(j2s(createAccountResp.detail)); + console.error( + createAccountResp.case === HttpStatusCode.Accepted + ? `the merchant requires MFA for this account` + : j2s(createAccountResp.detail), + ); process.exit(2); } logger.info(`successfully configured bank account for ${instanceId}`); @@ -1826,8 +1833,8 @@ merchantCli const scope = args.token.scope ?? "all"; const duration = args.token.duration ? Duration.toTalerProtocolDuration( - Duration.fromPrettyString(args.token.duration), - ) + Duration.fromPrettyString(args.token.duration), + ) : undefined; const tokResp = await merchantApi.createAccessToken(instance, password, { scope: scope as LoginTokenScope, diff --git a/packages/taler-harness/src/integrationtests/test-merchant-self-provision-activation.ts b/packages/taler-harness/src/integrationtests/test-merchant-self-provision-activation.ts @@ -61,25 +61,35 @@ export async function runMerchantSelfProvisionActivationTest( t.assertDeepEqual(r.instances.length, 2); } - const signupStart = await merchantClient.createInstanceSelfProvision({ + const instanceInfo = { id: "self-instance", name: "My instance", auth: { method: MerchantAuthMethod.TOKEN, password: "123", }, - default_pay_delay: { d_us: "forever" }, - default_wire_transfer_delay: { d_us: "forever" }, + default_pay_delay: { d_us: "forever" as const }, + default_wire_transfer_delay: { d_us: "forever" as const }, jurisdiction: {}, address: {}, email: "some@taler.net", phone_number: "+1111", use_stefan: false, - }); + }; + const signupStart = + await merchantClient.createInstanceSelfProvision(instanceInfo); // creation requires 2fa t.assertDeepEqual(signupStart.type, "fail"); t.assertDeepEqual(signupStart.case, HttpStatusCode.Accepted); + t.assertDeepEqual(signupStart.body.challenges.length, 2); + t.assertDeepEqual(signupStart.body.combi_and, true); + + const firstChallenge = signupStart.body.challenges[0]; + // always first emails since is cheaper + t.assertDeepEqual(firstChallenge.tan_channel, TanChannel.EMAIL); + const secondChallenge = signupStart.body.challenges[1]; + t.assertDeepEqual(secondChallenge.tan_channel, TanChannel.SMS); { // new instance is pending, then is not listed @@ -88,83 +98,42 @@ export async function runMerchantSelfProvisionActivationTest( ); t.assertDeepEqual(r.instances.length, 2); } - const firstChallenge = String(signupStart.body.challenge_id); + { - const ch = succeedOrThrow( - await merchantClient.sendChallenge(firstChallenge), + succeedOrThrow( + await merchantClient.sendChallenge(firstChallenge.challenge_id), ); - // always first emails since is cheaper - t.assertDeepEqual(ch.tan_channel, TanChannel.EMAIL); - // FIXME: some how get the tan code, the merchant backend server should // have a scritp that save the TAN code in a file so we can read it now const tanCode = "1111"; succeedOrThrow( - await merchantClient.confirmChallenge(firstChallenge, { + await merchantClient.confirmChallenge(firstChallenge.challenge_id, { tan: tanCode, }), ); } - const secondSignup = await merchantClient.createInstanceSelfProvision( - { - id: "self-instance", - name: "My instance", - auth: { - method: MerchantAuthMethod.TOKEN, - password: "123", - }, - default_pay_delay: { d_us: "forever" }, - default_wire_transfer_delay: { d_us: "forever" }, - jurisdiction: {}, - address: {}, - email: "some@taler.net", - phone_number: "+1111", - use_stefan: false, - }, - firstChallenge, - ); - // creation requires 2fa - t.assertDeepEqual(secondSignup.type, "fail"); - t.assertDeepEqual(secondSignup.case, HttpStatusCode.Accepted); - - const secondChallenge = String(secondSignup.body.challenge_id); { const ch = succeedOrThrow( - await merchantClient.sendChallenge(secondChallenge), + await merchantClient.sendChallenge(secondChallenge.challenge_id), ); - // next challenge sms - t.assertDeepEqual(ch.tan_channel, TanChannel.SMS); - // FIXME: some how get the tan code, the merchant backend server should // have a scritp that save the TAN code in a file so we can read it now const tanCode = "1111"; succeedOrThrow( - await merchantClient.confirmChallenge(secondChallenge, { + await merchantClient.confirmChallenge(secondChallenge.challenge_id, { tan: tanCode, }), ); } const completeSignup = await merchantClient.createInstanceSelfProvision( + instanceInfo, { - id: "self-instance", - name: "My instance", - auth: { - method: MerchantAuthMethod.TOKEN, - password: "123", - }, - default_pay_delay: { d_us: "forever" }, - default_wire_transfer_delay: { d_us: "forever" }, - jurisdiction: {}, - address: {}, - email: "some@taler.net", - phone_number: "+1111", - use_stefan: false, + challengeIds: [firstChallenge.challenge_id, secondChallenge.challenge_id], }, - secondChallenge, ); t.assertDeepEqual(completeSignup.type, "ok"); @@ -176,7 +145,6 @@ export async function runMerchantSelfProvisionActivationTest( ); t.assertDeepEqual(r.instances.length, 3); } - } runMerchantSelfProvisionActivationTest.suites = ["merchant", "self-provision"]; diff --git a/packages/taler-util/src/http-client/exchange-client.ts b/packages/taler-util/src/http-client/exchange-client.ts @@ -959,6 +959,8 @@ export class TalerExchangeHttpClient { switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForAmlStatisticsResponse()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ statistics: [] }); case HttpStatusCode.Conflict: case HttpStatusCode.NotFound: case HttpStatusCode.Forbidden: diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -21,6 +21,7 @@ import { HttpStatusCode, LibtoolVersion, LoginTokenRequest, + MerchantChallengeSolveRequest, MerchantTanChannel, OperationAlternative, OperationFail, @@ -38,6 +39,7 @@ import { codecForBankAccountDetail, codecForCategoryListResponse, codecForCategoryProductList, + codecForChallengeResponse, codecForClaimResponse, codecForInstancesResponse, codecForInventorySummaryResponse, @@ -70,15 +72,12 @@ import { codecForWalletTemplateDetails, codecForWebhookDetails, codecForWebhookSummaryResponse, - codecForChallengeResponse, opEmptySuccess, opFixedSuccess, opKnownAlternativeHttpFailure, opKnownHttpFailure, opKnownTalerFailure, - opUnknownHttpFailure, - MerchantChallengeSolveRequest, - codecForChallenge, + opUnknownHttpFailure } from "@gnu-taler/taler-util"; import { HttpRequestLibrary, @@ -732,7 +731,7 @@ export class TalerMerchantInstanceHttpClient { return opKnownAlternativeHttpFailure( resp, resp.status, - codecForChallenge(), + codecForChallengeResponse(), ); case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, resp); @@ -853,7 +852,7 @@ export class TalerMerchantInstanceHttpClient { return opKnownAlternativeHttpFailure( resp, resp.status, - codecForChallenge(), + codecForChallengeResponse(), ); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp); @@ -2780,7 +2779,7 @@ export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttp return opKnownAlternativeHttpFailure( resp, resp.status, - codecForChallenge(), + codecForChallengeResponse(), ); case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, resp); @@ -2827,7 +2826,7 @@ export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttp return opKnownAlternativeHttpFailure( resp, resp.status, - codecForChallenge(), + codecForChallengeResponse(), ); case HttpStatusCode.Unauthorized: // FIXME: missing in docs return opKnownHttpFailure(resp.status, resp);