diff options
Diffstat (limited to 'packages/taler-util/src/MerchantApiClient.ts')
-rw-r--r-- | packages/taler-util/src/MerchantApiClient.ts | 203 |
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)); } } |