taler-typescript-core

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

commit c1b47583705f7d8d7defe53db75c2df70b3f6a0e
parent a6d8bbc01f93819fcaa4868bf4407bacd845da08
Author: Florian Dold <florian@dold.me>
Date:   Mon, 17 Feb 2025 14:12:04 +0100

harness: scenario test for insufficient balance due to undepositable funds

Diffstat:
Mpackages/taler-harness/src/index.ts | 35+++++++++++++++++++++++++++++++++++
Mpackages/taler-util/src/http-client/merchant.ts | 41++++++++++++++++++++++++++++++++++++++++-
Mpackages/taler-util/src/types-taler-common.ts | 10+++++++---
3 files changed, 82 insertions(+), 4 deletions(-)

diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts @@ -188,6 +188,41 @@ testingCli logger.info("Withdrawal operation confirmed"); }); +testingCli + .subcommand("scenarioInsufficientBalance", "scenario-insufficient-balance", { + help: "Test scenario insufficient balance details.", + }) + .action(async (args) => { + const merchantBaseUrl = "https://backend.test.taler.net/instances/sandbox/"; + const merchantApi = new TalerMerchantInstanceHttpClient(merchantBaseUrl); + const tokResp = await merchantApi.createLoginToken("secret-token:sandbox", { + scope: "write", + }); + narrowOpSuccessOrThrow("", tokResp); + + const tok: AccessToken = tokResp.body.token; + + const createResp = await merchantApi.createOrder(tok, { + order: { + amount: "TESTKUDOS:42", + summary: "Hello", + // Intentional, test.taler.net merchant does not support it. + wire_method: "bitcoin", + }, + }); + narrowOpSuccessOrThrow("", createResp); + const orderId = createResp.body.order_id; + const statusResp = await merchantApi.getOrderDetails(tok, orderId); + narrowOpSuccessOrThrow("", statusResp); + if (statusResp.body.order_status !== "unpaid") { + throw Error("unexpected order state"); + } + console.log( + "Insufficient balance order (undepositable due to bitcoin wire method):", + ); + console.log(statusResp.body.taler_pay_uri); + }); + const advancedCli = talerHarnessCli.subcommand("advancedArgs", "advanced", { help: "Subcommands for advanced operations (only use if you know what you're doing!).", }); diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -27,6 +27,8 @@ import { TalerErrorCode, TalerMerchantApi, TalerMerchantConfigResponse, + TokenRequest, + TokenSuccessResponseMerchant, codecForAbortResponse, codecForAccountAddResponse, codecForAccountKycRedirects, @@ -60,6 +62,7 @@ import { codecForTemplateSummaryResponse, codecForTokenFamiliesList, codecForTokenFamilyDetails, + codecForTokenSuccessResponseMerchant, codecForWalletRefundResponse, codecForWalletTemplateDetails, codecForWebhookDetails, @@ -77,8 +80,8 @@ import { } from "@gnu-taler/taler-util/http"; import { opSuccessFromHttp, opUnknownFailure } from "../operation.js"; import { - addPaginationParams, CacheEvictor, + addPaginationParams, makeBearerTokenAuthHeader, nullEvictor, } from "./utils.js"; @@ -90,6 +93,9 @@ export type TalerMerchantInstanceErrorsByMethod< prop extends keyof TalerMerchantInstanceHttpClient, > = FailCasesByMethod<TalerMerchantInstanceHttpClient, prop>; +/** + * FIXME: This should probably not be part of the core merchant HTTP client. + */ export enum TalerMerchantInstanceCacheEviction { CREATE_ORDER, UPDATE_ORDER, @@ -209,6 +215,39 @@ export class TalerMerchantInstanceHttpClient { } // + // Auth + // + + async createLoginToken( + token: string, + body: TokenRequest, + ): Promise< + | OperationOk<TokenSuccessResponseMerchant> + | OperationFail<HttpStatusCode.NotFound> + | OperationFail<HttpStatusCode.Unauthorized> + > { + const url = new URL(`private/token`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + headers: { + Authorization: makeBearerTokenAuthHeader(token as AccessToken), + }, + body, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForTokenSuccessResponseMerchant()); + //FIXME: missing in docs + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + // // Wallet API // diff --git a/packages/taler-util/src/types-taler-common.ts b/packages/taler-util/src/types-taler-common.ts @@ -34,13 +34,11 @@ import { codecForAny, codecForBoolean, codecForConstString, - codecForList, codecForMap, codecForNumber, codecForString, - codecOptional, } from "./codec.js"; -import { ReservePub, codecForEither } from "./index.js"; +import { ReservePub } from "./index.js"; import { TalerProtocolDuration, TalerProtocolTimestamp, @@ -384,6 +382,10 @@ export interface TokenSuccessResponseMerchant { // Opque access token. token: AccessToken; + + scope: string; + + refreshable: boolean; } //FIXME: implement this codec @@ -399,6 +401,8 @@ export const codecForTokenSuccessResponseMerchant = buildCodecForObject<TokenSuccessResponseMerchant>() .property("token", codecForAccessToken()) .property("expiration", codecForTimestamp) + .property("scope", codecForString()) + .property("refreshable", codecForBoolean()) .build("TalerAuthentication.TokenSuccessResponseMerchant"); // FIXME: implement this codec