taler-typescript-core

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

commit 026b03cad5ed2443d96dc9400b003a1550485c76
parent 4bb39d26c18e9d4e63b57a873225e7c2acc45b81
Author: Sebastian <sebasjm@gmail.com>
Date:   Mon, 25 Aug 2025 08:41:46 -0300

fix #10286

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-merchant-self-provision-activation.ts | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mpackages/taler-util/src/http-client/merchant.ts | 10++++++++++
Mpackages/taler-util/src/types-taler-merchant.ts | 55++++++++++++++++++++++++++++++++++++++++++++-----------
Mpackages/taler-wallet-webextension/src/wallet/History.tsx | 7+++----
4 files changed, 173 insertions(+), 24 deletions(-)

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 @@ -21,17 +21,18 @@ import { HttpStatusCode, MerchantAuthMethod, succeedOrThrow, - TalerMerchantManagementHttpClient + TalerMerchantManagementHttpClient, } from "@gnu-taler/taler-util"; import { createSimpleTestkudosEnvironmentV3 } from "harness/environments.js"; -import { - GlobalTestState -} from "../harness/harness.js"; +import { GlobalTestState } from "../harness/harness.js"; +import { TanChannel } from "../../../taler-util/src/types-taler-corebank.js"; /** * Do basic checks on instance management and authentication. */ -export async function runMerchantSelfProvisionActivationTest(t: GlobalTestState) { +export async function runMerchantSelfProvisionActivationTest( + t: GlobalTestState, +) { // Set up test environment const { @@ -41,18 +42,26 @@ export async function runMerchantSelfProvisionActivationTest(t: GlobalTestState) merchant, bank, merchantAdminAccessToken, - } = await createSimpleTestkudosEnvironmentV3(t); + } = await createSimpleTestkudosEnvironmentV3(t, undefined, { + additionalMerchantConfig(m) { + m.modifyConfig(async (cfg) => { + cfg.setString("merchant", "ENABLE_SELF_PROVISIONING", "yes"); + }); + }, + }); const merchantClient = new TalerMerchantManagementHttpClient( merchant.makeInstanceBaseUrl(), ); { - const r = succeedOrThrow(await merchantClient.listInstances(merchantAdminAccessToken)); + const r = succeedOrThrow( + await merchantClient.listInstances(merchantAdminAccessToken), + ); t.assertDeepEqual(r.instances.length, 2); } - const creation = await merchantClient.createInstanceSelfProvision({ + const signupStart = await merchantClient.createInstanceSelfProvision({ id: "self-instance", name: "My instance", auth: { @@ -63,13 +72,111 @@ export async function runMerchantSelfProvisionActivationTest(t: GlobalTestState) default_wire_transfer_delay: { d_us: "forever" }, jurisdiction: {}, address: {}, + email: "some@taler.net", + phone_number: "+1111", use_stefan: false, }); - t.assertTrue(creation.type === "fail" && creation.case === HttpStatusCode.Accepted); + // creation requires 2fa + t.assertDeepEqual(signupStart.type, "fail"); + t.assertDeepEqual(signupStart.case, HttpStatusCode.Accepted); + + { + // new instance is pending, then is not listed + const r = succeedOrThrow( + await merchantClient.listInstances(merchantAdminAccessToken), + ); + t.assertDeepEqual(r.instances.length, 2); + } + const firstChallenge = String(signupStart.body.challenge_id); + { + const ch = succeedOrThrow( + await merchantClient.sendChallenge(firstChallenge), + ); + + // 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, { + 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), + ); + + // 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, { + tan: tanCode, + }), + ); + } + + const completeSignup = 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, + }, + secondChallenge, + ); + + t.assertDeepEqual(completeSignup.type, "ok"); + + { + // new instance is completed, now it should be visible + const r = succeedOrThrow( + await merchantClient.listInstances(merchantAdminAccessToken), + ); + t.assertDeepEqual(r.instances.length, 3); + } + } runMerchantSelfProvisionActivationTest.suites = ["merchant", "self-provision"]; diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -2549,6 +2549,12 @@ export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttp // 2FA Authentication // + /** + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-auth-channels-$CHANNEL-validate + * + * @param channel + * @returns + */ async requestAccountChannelValidation(channel: MerchantTanChannel) { const url = new URL(`auth-channels/${channel}/validate`, this.baseUrl); @@ -2660,10 +2666,14 @@ export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttp */ async createInstanceSelfProvision( body: TalerMerchantApi.InstanceConfigurationMessage, + cid?: string ) { const url = new URL(`instances`, this.baseUrl); const headers: Record<string, string> = {}; + if (cid) { + headers["X-Challenge-Id"] = cid + } const resp = await this.httpLib.fetch(url.href, { method: "POST", body, diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts @@ -1039,7 +1039,10 @@ export const codecForMerchantContractChoice = buildCodecForObject<MerchantContractChoice>() .property("amount", codecForAmountString()) .property("description", codecOptional(codecForString())) - .property("description_i18n", codecOptional(codecForInternationalizedString())) + .property( + "description_i18n", + codecOptional(codecForInternationalizedString()), + ) .property("inputs", codecForList(codecForMerchantContractInput())) .property("outputs", codecForList(codecForMerchantContractOutput())) .property("max_fee", codecForAmountString()) @@ -1219,7 +1222,7 @@ export interface TalerMerchantConfigResponse { export enum MerchantTanChannel { SMS = "sms", - EMAIL = "email" + EMAIL = "email", } export interface ExchangeConfigInfo { @@ -1592,6 +1595,10 @@ export interface InstanceConfigurationMessage { // Merchant email for customer contact. email?: string; + // Merchant phone number for password reset (2-FA) + // @since **v21**. + phone_number?: string; + // Merchant public website. website?: string; @@ -1712,6 +1719,10 @@ export interface InstanceReconfigurationMessage { // Merchant email for customer contact. email?: string; + // Merchant phone number for password reset (2-FA) + // @since **v21**. + phone_number?: string; + // Merchant public website. website?: string; @@ -1778,6 +1789,18 @@ export interface QueryInstancesResponse { // Merchant email for customer contact. email?: string; + // True if the email address was validated. + // @since **v21**. + email_validated?: boolean; + + // Merchant phone number for password reset (2-FA) + // @since **v21**. + phone_number?: string; + + // True if the email address was validated. + // @since **v21**. + phone_validated?: boolean; + // Merchant public website. website?: string; @@ -3564,15 +3587,22 @@ export const codecForTalerMerchantConfigResponse = .property("currencies", codecForMap(codecForCurrencySpecificiation())) .property("exchanges", codecForList(codecForExchangeConfigInfo())) .property("implementation", codecOptional(codecForString())) - .property("have_self_provisioning", codecOptionalDefault(codecForBoolean(), false)) - .property("mandatory_tan_channels", codecOptionalDefault( - codecForList( - codecForEither( - codecForConstString(MerchantTanChannel.SMS), - codecForConstString(MerchantTanChannel.EMAIL), + .property( + "have_self_provisioning", + codecOptionalDefault(codecForBoolean(), false), + ) + .property( + "mandatory_tan_channels", + codecOptionalDefault( + codecForList( + codecForEither( + codecForConstString(MerchantTanChannel.SMS), + codecForConstString(MerchantTanChannel.EMAIL), + ), ), - ), [] - ),) + [], + ), + ) .build("TalerMerchantApi.VersionResponse"); export const codecForClaimResponse = (): Codec<ClaimResponse> => @@ -4022,7 +4052,10 @@ export const codecForOrderChoice = (): Codec<OrderChoice> => buildCodecForObject<OrderChoice>() .property("amount", codecForAmountString()) .property("description", codecOptional(codecForString())) - .property("description_i18n", codecOptional(codecForInternationalizedString())) + .property( + "description_i18n", + codecOptional(codecForInternationalizedString()), + ) .property("max_fee", codecOptional(codecForAmountString())) .property("inputs", codecOptional(codecForList(codecForOrderInput()))) .property("outputs", codecOptional(codecForList(codecForOrderOutput()))) diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -24,7 +24,7 @@ import { URL, WalletBalance, parseScopeInfoShort, - stringifyScopeInfoShort + stringifyScopeInfoShort, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useTranslationContext } from "@gnu-taler/web-util/browser"; @@ -73,11 +73,10 @@ export function HistoryPage({ const state = useAsyncAsHook(async () => { const b = await api.wallet.call(WalletApiOperation.GetBalances, {}); const balances = b.balances; - const tx = await api.wallet.call(WalletApiOperation.GetTransactions, { + const tx = await api.wallet.call(WalletApiOperation.GetTransactionsV2, { scopeInfo: showSearch ? undefined : selectedScope, - sort: "descending", includeRefreshes: settings.showRefeshTransactions, - search, + limit: -100 }); return { balances, transactions: tx.transactions }; }, [selectedScope, search]);