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