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