summaryrefslogtreecommitdiff
path: root/packages/taler-util/src/MerchantApiClient.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-util/src/MerchantApiClient.ts')
-rw-r--r--packages/taler-util/src/MerchantApiClient.ts203
1 files changed, 132 insertions, 71 deletions
diff --git a/packages/taler-util/src/MerchantApiClient.ts b/packages/taler-util/src/MerchantApiClient.ts
index 2e10e394a..c27f1d582 100644
--- a/packages/taler-util/src/MerchantApiClient.ts
+++ b/packages/taler-util/src/MerchantApiClient.ts
@@ -16,31 +16,50 @@
import { codecForAny } from "./codec.js";
import {
+ TalerMerchantApi,
+ codecForMerchantConfig,
+ codecForMerchantOrderPrivateStatusResponse,
+} from "./http-client/types.js";
+import { HttpStatusCode } from "./http-status-codes.js";
+import {
createPlatformHttpLib,
expectSuccessResponseOrThrow,
readSuccessResponseJsonOrThrow,
+ readTalerErrorResponse,
} from "./http.js";
import { FacadeCredentials } from "./libeufin-api-types.js";
+import { LibtoolVersion } from "./libtool-version.js";
import { Logger } from "./logging.js";
import {
- MerchantReserveCreateConfirmation,
- codecForMerchantReserveCreateConfirmation,
- TippingReserveStatus,
MerchantInstancesResponse,
MerchantPostOrderRequest,
MerchantPostOrderResponse,
- codecForMerchantPostOrderResponse,
- MerchantOrderPrivateStatusResponse,
- codecForMerchantOrderPrivateStatusResponse,
- RewardCreateRequest,
- RewardCreateConfirmation,
MerchantTemplateAddDetails,
+ codecForMerchantPostOrderResponse,
} from "./merchant-api-types.js";
+import {
+ FailCasesByMethod,
+ OperationFail,
+ OperationOk,
+ ResultByMethod,
+ opEmptySuccess,
+ opKnownHttpFailure,
+ opSuccessFromHttp,
+ opUnknownFailure,
+} from "./operation.js";
import { AmountString } from "./taler-types.js";
import { TalerProtocolDuration } from "./time.js";
const logger = new Logger("MerchantApiClient.ts");
+// FIXME: Explain!
+export type TalerMerchantResultByMethod<prop extends keyof MerchantApiClient> =
+ ResultByMethod<MerchantApiClient, prop>;
+
+// FIXME: Explain!
+export type TalerMerchantErrorsByMethod<prop extends keyof MerchantApiClient> =
+ FailCasesByMethod<MerchantApiClient, prop>;
+
export interface MerchantAuthConfiguration {
method: "external" | "token";
token?: string;
@@ -106,6 +125,23 @@ export interface PrivateOrderStatusQuery {
sessionId?: string;
}
+export interface OtpDeviceAddDetails {
+ // Device ID to use.
+ otp_device_id: string;
+
+ // Human-readable description for the device.
+ otp_device_description: string;
+
+ // A base64-encoded key
+ otp_key: string;
+
+ // Algorithm for computing the POS confirmation.
+ otp_algorithm: number;
+
+ // Counter for counter-based OTP devices.
+ otp_ctr?: number;
+}
+
/**
* Client for the GNU Taler merchant backend.
*/
@@ -118,10 +154,15 @@ export class MerchantApiClient {
readonly auth: MerchantAuthConfiguration;
- constructor(baseUrl: string, auth?: MerchantAuthConfiguration) {
+ public readonly PROTOCOL_VERSION = "6:0:2";
+
+ constructor(
+ baseUrl: string,
+ options: { auth?: MerchantAuthConfiguration } = {},
+ ) {
this.baseUrl = baseUrl;
- this.auth = auth ?? {
+ this.auth = options?.auth ?? {
method: "external",
};
}
@@ -138,35 +179,6 @@ export class MerchantApiClient {
await expectSuccessResponseOrThrow(res);
}
- async deleteTippingReserve(req: DeleteTippingReserveArgs): Promise<void> {
- const url = new URL(`private/reserves/${req.reservePub}`, this.baseUrl);
- if (req.purge) {
- url.searchParams.set("purge", "YES");
- }
- const resp = await this.httpClient.fetch(url.href, {
- method: "DELETE",
- headers: this.makeAuthHeader(),
- });
- logger.info(`delete status: ${resp.status}`);
- return;
- }
-
- async createTippingReserve(
- req: CreateMerchantTippingReserveRequest,
- ): Promise<MerchantReserveCreateConfirmation> {
- const url = new URL("private/reserves", this.baseUrl);
- const resp = await this.httpClient.fetch(url.href, {
- method: "POST",
- body: req,
- headers: this.makeAuthHeader(),
- });
- const respData = readSuccessResponseJsonOrThrow(
- resp,
- codecForMerchantReserveCreateConfirmation(),
- );
- return respData;
- }
-
async getPrivateInstanceInfo(): Promise<any> {
const url = new URL("private", this.baseUrl);
const resp = await this.httpClient.fetch(url.href, {
@@ -176,16 +188,6 @@ export class MerchantApiClient {
return await resp.json();
}
- async getPrivateTipReserves(): Promise<TippingReserveStatus> {
- const url = new URL("private/reserves", this.baseUrl);
- const resp = await this.httpClient.fetch(url.href, {
- method: "GET",
- headers: this.makeAuthHeader(),
- });
- // FIXME: Validate!
- return await resp.json();
- }
-
async deleteInstance(instanceId: string) {
const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
const resp = await this.httpClient.fetch(url.href, {
@@ -239,9 +241,24 @@ export class MerchantApiClient {
);
}
+ async deleteOrder(req: { orderId: string; force?: boolean }): Promise<void> {
+ let url = new URL(`private/orders/${req.orderId}`, this.baseUrl);
+ if (req.force) {
+ url.searchParams.set("force", "yes");
+ }
+ const resp = await this.httpClient.fetch(url.href, {
+ method: "DELETE",
+ body: req,
+ headers: this.makeAuthHeader(),
+ });
+ if (resp.status !== 204) {
+ throw Error(`failed to delete order (status ${resp.status})`);
+ }
+ }
+
async queryPrivateOrderStatus(
query: PrivateOrderStatusQuery,
- ): Promise<MerchantOrderPrivateStatusResponse> {
+ ): Promise<TalerMerchantApi.MerchantOrderStatusResponse> {
const reqUrl = new URL(`private/orders/${query.orderId}`, this.baseUrl);
if (query.sessionId) {
reqUrl.searchParams.set("session_id", query.sessionId);
@@ -255,25 +272,6 @@ export class MerchantApiClient {
);
}
- async giveTip(req: RewardCreateRequest): Promise<RewardCreateConfirmation> {
- const reqUrl = new URL(`private/rewards`, this.baseUrl);
- const resp = await this.httpClient.fetch(reqUrl.href, {
- method: "POST",
- body: req,
- });
- // FIXME: validate
- return resp.json();
- }
-
- async queryTippingReserves(): Promise<TippingReserveStatus> {
- const reqUrl = new URL(`private/reserves`, this.baseUrl);
- const resp = await this.httpClient.fetch(reqUrl.href, {
- headers: this.makeAuthHeader(),
- });
- // FIXME: validate
- return resp.json();
- }
-
async giveRefund(r: {
instance: string;
orderId: string;
@@ -301,8 +299,71 @@ export class MerchantApiClient {
body: req,
headers: this.makeAuthHeader(),
});
- if (resp.status !== 204) {
- throw Error("failed to create template");
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ case HttpStatusCode.NoContent:
+ return opEmptySuccess(resp);
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ async getTemplate(templateId: string) {
+ let url = new URL(`private/templates/${templateId}`, this.baseUrl);
+ const resp = await this.httpClient.fetch(url.href, {
+ method: "GET",
+ headers: this.makeAuthHeader(),
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(resp, codecForAny());
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ isCompatible(version: string): boolean {
+ const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version);
+ return compare?.compatible ?? false;
+ }
+ /**
+ * https://docs.taler.net/core/api-merchant.html#get--config
+ *
+ */
+ async getConfig(): Promise<OperationOk<TalerMerchantApi.VersionResponse>> {
+ const url = new URL(`config`, this.baseUrl);
+ const resp = await this.httpClient.fetch(url.href, {
+ method: "GET",
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(resp, codecForMerchantConfig());
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ async createOtpDevice(
+ req: OtpDeviceAddDetails,
+ ): Promise<OperationOk<void> | OperationFail<HttpStatusCode.NotFound>> {
+ let url = new URL("private/otp-devices", this.baseUrl);
+ const resp = await this.httpClient.fetch(url.href, {
+ method: "POST",
+ body: req,
+ headers: this.makeAuthHeader(),
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ case HttpStatusCode.NoContent:
+ return opEmptySuccess(resp);
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
}
}