summaryrefslogtreecommitdiff
path: root/packages/taler-harness/src/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-harness/src/index.ts')
-rw-r--r--packages/taler-harness/src/index.ts393
1 files changed, 340 insertions, 53 deletions
diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts
index 3bec1698a..4b0319a3e 100644
--- a/packages/taler-harness/src/index.ts
+++ b/packages/taler-harness/src/index.ts
@@ -18,6 +18,7 @@
* Imports.
*/
import {
+ AccessToken,
AmountString,
Amounts,
BalancesResponse,
@@ -25,14 +26,23 @@ import {
Duration,
HttpStatusCode,
Logger,
- MerchantInstanceConfig,
- RegisterAccountRequest,
+ PaytoString,
+ TalerAuthenticationHttpClient,
+ TalerBankConversionHttpClient,
+ TalerCoreBankHttpClient,
+ TalerErrorCode,
+ TalerMerchantInstanceHttpClient,
+ TalerMerchantManagementHttpClient,
TransactionsResponse,
decodeCrock,
+ encodeCrock,
generateIban,
j2s,
+ randomBytes,
rsaBlind,
setGlobalLogLevelFromString,
+ setPrintHttpRequestAsCurl,
+ stringifyPayTemplateUri
} from "@gnu-taler/taler-util";
import { clk } from "@gnu-taler/taler-util/clk";
import {
@@ -69,6 +79,7 @@ import {
} from "./harness/helpers.js";
import { getTestInfo, runTests } from "./integrationtests/testrunner.js";
import { lintExchangeDeployment } from "./lint.js";
+import { randomUUID } from "crypto";
const logger = new Logger("taler-harness:index.ts");
@@ -586,6 +597,291 @@ deploymentCli
});
deploymentCli
+ .subcommand("provisionBankMerchant", "provision-bank-and-merchant", {
+ help: "Provision a bank account, merchant instance and link them together.",
+ })
+ .requiredArgument("merchantApiBaseUrl", clk.STRING, {
+ help: "URL location of the merchant backend"
+ })
+ .requiredArgument("corebankApiBaseUrl", clk.STRING, {
+ help: "URL location of the libeufin bank backend"
+ })
+ .requiredOption("merchantToken", ["--merchant-management-token"], clk.STRING, {
+ help: "acces token of the default instance in the merchant backend"
+ })
+ .maybeOption("bankToken", ["--bank-admin-token"], clk.STRING, {
+ help: "libeufin bank admin's password if the account creation is restricted"
+ })
+ .requiredOption("name", ["--legal-name"], clk.STRING, {
+ help: "legal name of the merchant"
+ })
+ .maybeOption("email", ["--email"], clk.STRING, {
+ help: "email contact of the merchant"
+ })
+ .maybeOption("phone", ["--phone"], clk.STRING, {
+ help: "phone contact of the merchant"
+ })
+ .requiredOption("id", ["--id"], clk.STRING, {
+ help: "login id for the bank account and instance id of the merchant backend"
+ })
+ .flag("template", ["--create-template"], {
+ help: "use this flag to create a default template for the merchant with fixed summary"
+ })
+ .requiredOption("password", ["--password"], clk.STRING, {
+ help: "password of the accounts in libeufin bank and merchant backend"
+ })
+ .flag("randomPassword", ["--set-random-password"], {
+ help: "if everything worked ok, change the password of the accounts at the end"
+ })
+ .action(async (args) => {
+ const managementToken = args.provisionBankMerchant.merchantToken as AccessToken;
+ const bankAdminPassword = args.provisionBankMerchant.bankToken as AccessToken;
+ const id = args.provisionBankMerchant.id;
+ const name = args.provisionBankMerchant.name;
+ const email = args.provisionBankMerchant.email;
+ const phone = args.provisionBankMerchant.phone;
+ const password = args.provisionBankMerchant.password;
+
+
+ const httpLib = createPlatformHttpLib({});
+ const merchantManager = new TalerMerchantManagementHttpClient(args.provisionBankMerchant.merchantApiBaseUrl, httpLib);
+ const bank = new TalerCoreBankHttpClient(args.provisionBankMerchant.corebankApiBaseUrl, httpLib);
+ const instanceURL = merchantManager.getSubInstanceAPI(id).href
+ const merchantInstance = new TalerMerchantInstanceHttpClient(instanceURL, httpLib);
+ const conv = new TalerBankConversionHttpClient(bank.getConversionInfoAPI().href, httpLib)
+ const bankAuth = new TalerAuthenticationHttpClient(bank.getAuthenticationAPI(id).href, httpLib)
+
+
+ const bc = await bank.getConfig()
+ if (!bank.isCompatible(bc.body.version)) {
+ logger.error(
+ `bank server version is not compatible: ${bc.body.version}, client version: ${bank.PROTOCOL_VERSION}`,
+ );
+ return;
+ }
+ const mc = await merchantManager.getConfig()
+ if (!merchantManager.isCompatible(mc.body.version)) {
+ logger.error(
+ `merchant server version is not compatible: ${mc.body.version}, client version: ${merchantManager.PROTOCOL_VERSION}`,
+ );
+ return;
+ }
+
+ /**
+ * create bank account
+ */
+ let accountPayto: PaytoString;
+ {
+ const resp = await bank.createAccount(bankAdminPassword, {
+ name: name,
+ password: password,
+ username: id,
+ contact_data: email || phone ? {
+ email: email,
+ phone: phone,
+ } : undefined,
+ })
+
+ if (resp.type === "fail") {
+ logger.error(`unable to provision bank account, HTTP response status ${resp.case}`);
+ process.exit(2);
+ }
+ logger.info(`account ${id} successfully provisioned`);
+ accountPayto = resp.body.internal_payto_uri
+ }
+
+ /**
+ * create merchant account
+ */
+ {
+ const resp = await merchantManager.createInstance(managementToken, {
+ address: {},
+ auth: {
+ method: "token",
+ token: `secret-token:${password}`,
+ },
+ default_pay_delay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ hours: 1 }),
+ ),
+ default_wire_transfer_delay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ hours: 1 }),
+ ),
+ id: id,
+ jurisdiction: {},
+ name: name,
+ use_stefan: true,
+ })
+
+ if (resp.type === "ok") {
+ logger.info(`instance ${id} created successfully`);
+ } else if (resp.case === HttpStatusCode.Conflict) {
+ logger.info(`instance ${id} already exists`);
+ } else {
+ logger.error(
+ `unable to create instance ${id}, HTTP status ${resp.case}`,
+ );
+ process.exit(2);
+ }
+ }
+
+ let wireAccount: string;
+ /**
+ * link bank account and merchant
+ */
+ {
+ const resp = await merchantInstance.addAccount(password as AccessToken, {
+ payto_uri: accountPayto,
+ credit_facade_url: bank.getRevenueAPI(id).href,
+ credit_facade_credentials: {
+ type: "basic",
+ username: id,
+ password: password,
+ }
+ })
+ if (resp.type === "fail") {
+ console.error(`unable to configure bank account for instance ${id}, status ${resp.case}`)
+ console.error(j2s(resp.detail));
+ process.exit(2);
+ }
+ wireAccount = resp.body.h_wire
+ }
+
+ logger.info(`successfully configured bank account for ${id}`);
+
+ let templateURI;
+ /**
+ * create template
+ */
+ if (args.provisionBankMerchant.template) {
+ let currency = bc.body.currency;
+ if (bc.body.allow_conversion) {
+ const cc = await conv.getConfig();
+ if (cc.type === "ok") {
+ currency = cc.body.fiat_currency
+ } else {
+ console.error(
+ `could not get fiat currency status ${cc.case}`,
+ );
+ console.error(j2s(cc.detail));
+ }
+ } else {
+ console.log(`conversion is disabled, using bank currency`)
+ }
+
+ {
+ const resp = await merchantInstance.addTemplate(password as AccessToken, {
+ template_id: "default",
+ template_description: "First template",
+ template_contract: {
+ pay_duration: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ hours: 1 }),
+ ),
+ minimum_age: 0,
+ currency,
+ summary: "Pay me!"
+ }
+ })
+ if (resp.type === "fail") {
+ console.error(`unable to create template for insntaince ${id}, status ${resp.case}`)
+ console.error(j2s(resp.detail));
+ process.exit(2);
+ }
+ }
+
+ logger.info(`template default successfully created`);
+ templateURI = stringifyPayTemplateUri({
+ merchantBaseUrl: instanceURL,
+ templateId: "default",
+ templateParams: {
+ amount: currency
+ }
+ })
+ }
+
+ let finalPassword = password;
+ if (args.provisionBankMerchant.randomPassword) {
+ const prevPassword = password as AccessToken
+ const randomPassword = encodeCrock(randomBytes(16));
+ logger.info("random password: ", randomPassword)
+ let token: AccessToken;
+ {
+ const resp = await bankAuth.createAccessTokenBasic(id, prevPassword, {
+ scope: "readwrite",
+ duration: Duration.toTalerProtocolDuration(Duration.fromSpec({ minutes: 1 })),
+ refreshable: false,
+ })
+ if (resp.type === "fail") {
+ console.error(`unable to login into bank accountfor user ${id}, status ${resp.case}`)
+ console.error(j2s(resp.detail));
+ process.exit(2);
+ }
+ token = resp.body.access_token;
+ }
+
+ {
+ const resp = await bank.updatePassword({ username: id, token }, {
+ old_password: prevPassword,
+ new_password: randomPassword,
+ });
+ if (resp.type === "fail") {
+ console.error(`unable to change bank pasword for user ${id}, status ${resp.case}`)
+ if (resp.case !== HttpStatusCode.Accepted) {
+ console.error(j2s(resp.detail));
+ } else {
+ console.error("2FA required")
+ }
+ process.exit(2);
+ }
+ }
+
+ {
+ const resp = await merchantInstance.updateCurrentInstanceAuthentication(prevPassword, {
+ method: "token",
+ token: `secret-token:${randomPassword}` as AccessToken
+ })
+ if (resp.type === "fail") {
+ console.error(`unable to change merchant password for instance ${id}, status ${resp.case}`)
+ console.error(j2s(resp.detail));
+ process.exit(2);
+ }
+ }
+
+ {
+ const resp = await merchantInstance.updateAccount(randomPassword as AccessToken, wireAccount, {
+ credit_facade_url: bank.getRevenueAPI(id).href,
+ credit_facade_credentials: {
+ type: "basic",
+ username: id,
+ password: randomPassword,
+ }
+ })
+ if (resp.type != "ok") {
+ console.error(
+ `unable to update bank account for instance ${id}, status ${resp.case}`,
+ );
+ console.error(j2s(resp.detail));
+ process.exit(2);
+ }
+ }
+ finalPassword = randomPassword;
+ }
+ logger.info(`successfully configured bank account for ${id}`);
+
+ /**
+ * show result
+ */
+ console.log(JSON.stringify({
+ bankUser: id,
+ bankURL: args.provisionBankMerchant.corebankApiBaseUrl,
+ merchantURL: instanceURL,
+ templateURI,
+ password: finalPassword,
+ }, undefined, 2))
+
+ });
+
+
+deploymentCli
.subcommand("provisionMerchantInstance", "provision-merchant-instance", {
help: "Provision a merchant backend instance.",
})
@@ -595,17 +891,27 @@ deploymentCli
.requiredOption("name", ["--name"], clk.STRING)
.requiredOption("id", ["--id"], clk.STRING)
.requiredOption("payto", ["--payto"], clk.STRING)
+ .maybeOption("bankURL", ["--bankURL"], clk.STRING)
+ .maybeOption("bankUser", ["--bankUser"], clk.STRING)
+ .maybeOption("bankPassword", ["--bankPassword"], clk.STRING)
.action(async (args) => {
- const httpLib = createPlatformHttpLib();
+ const httpLib = createPlatformHttpLib({});
const baseUrl = args.provisionMerchantInstance.merchantApiBaseUrl;
- const managementToken = args.provisionMerchantInstance.managementToken;
- const instanceToken = args.provisionMerchantInstance.instanceToken;
+ const api = new TalerMerchantManagementHttpClient(baseUrl, httpLib)
+ const managementToken = args.provisionMerchantInstance.managementToken as AccessToken;
+ const instanceToken = args.provisionMerchantInstance.instanceToken as AccessToken;
const instanceId = args.provisionMerchantInstance.id;
- const body: MerchantInstanceConfig = {
+ const instancceName = args.provisionMerchantInstance.name;
+ const bankURL = args.provisionMerchantInstance.bankURL;
+ const bankUser = args.provisionMerchantInstance.bankUser;
+ const bankPassword = args.provisionMerchantInstance.bankPassword;
+ const accountPayto = args.provisionMerchantInstance.payto as PaytoString;
+
+ const createResp = await api.createInstance(managementToken, {
address: {},
auth: {
method: "token",
- token: args.provisionMerchantInstance.instanceToken,
+ token: `secret-token:${instanceToken}`,
},
default_pay_delay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ hours: 1 }),
@@ -613,48 +919,35 @@ deploymentCli
default_wire_transfer_delay: { d_us: 1 },
id: instanceId,
jurisdiction: {},
- name: args.provisionMerchantInstance.name,
+ name: instancceName,
use_stefan: true,
- };
- const url = new URL("management/instances", baseUrl);
- const createResp = await httpLib.fetch(url.href, {
- method: "POST",
- body,
- headers: {
- Authorization: `Bearer ${managementToken}`,
- },
- });
- if (createResp.status >= 200 && createResp.status <= 299) {
+ })
+
+ if (createResp.type === "ok") {
logger.info(`instance ${instanceId} created successfully`);
- } else if (createResp.status === HttpStatusCode.Conflict) {
+ } else if (createResp.case === HttpStatusCode.Conflict) {
logger.info(`instance ${instanceId} already exists`);
} else {
logger.error(
- `unable to create instance ${instanceId}, HTTP status ${createResp.status}`,
+ `unable to create instance ${instanceId}, HTTP status ${createResp.case}`,
);
process.exit(2);
}
- const accountsUrl = new URL(
- `instances/${instanceId}/private/accounts`,
- baseUrl,
- );
- const accountBody = {
- payto_uri: args.provisionMerchantInstance.payto,
- };
- const createAccountResp = await httpLib.fetch(accountsUrl.href, {
- method: "POST",
- body: accountBody,
- headers: {
- Authorization: `Bearer ${instanceToken}`,
- },
- });
- if (createAccountResp.status != 200) {
+ const createAccountResp = await api.addAccount(instanceToken, {
+ payto_uri: accountPayto,
+ credit_facade_url: bankURL,
+ credit_facade_credentials: bankUser && bankPassword ? {
+ type: "basic",
+ username: bankUser,
+ password: bankPassword,
+ } : undefined
+ })
+ if (createAccountResp.type != "ok") {
console.error(
- `unable to configure bank account for instance ${instanceId}, status ${createAccountResp.status}`,
+ `unable to configure bank account for instance ${instanceId}, status ${createAccountResp.case}`,
);
- const resp = await createAccountResp.json();
- console.error(j2s(resp));
+ console.error(j2s(createAccountResp.detail));
process.exit(2);
}
logger.info(`successfully configured bank account for ${instanceId}`);
@@ -673,31 +966,25 @@ deploymentCli
.maybeOption("internalPayto", ["--payto"], clk.STRING)
.action(async (args) => {
const httpLib = createPlatformHttpLib();
- const corebankApiBaseUrl = args.provisionBankAccount.corebankApiBaseUrl;
- const url = new URL("accounts", corebankApiBaseUrl);
+ const baseUrl = args.provisionBankAccount.corebankApiBaseUrl;
+ const api = new TalerCoreBankHttpClient(baseUrl, httpLib);
+
const accountLogin = args.provisionBankAccount.login;
- const body: RegisterAccountRequest = {
+ const resp = await api.createAccount(undefined, {
name: args.provisionBankAccount.name,
password: args.provisionBankAccount.password,
username: accountLogin,
is_public: !!args.provisionBankAccount.public,
is_taler_exchange: !!args.provisionBankAccount.exchange,
- payto_uri: args.provisionBankAccount.internalPayto,
- };
- const resp = await httpLib.fetch(url.href, {
- method: "POST",
- body,
- });
- if (resp.status >= 200 && resp.status <= 299) {
+ payto_uri: args.provisionBankAccount.internalPayto as PaytoString,
+ })
+
+ if (resp.type === "ok") {
logger.info(`account ${accountLogin} successfully provisioned`);
return;
}
- if (resp.status === HttpStatusCode.Conflict) {
- logger.info(`account ${accountLogin} already provisioned`);
- return;
- }
logger.error(
- `unable to provision bank account, HTTP response status ${resp.status}`,
+ `unable to provision bank account, HTTP response status ${resp.case}`,
);
process.exit(2);
});