aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-util/src/http-client/merchant.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-util/src/http-client/merchant.ts')
-rw-r--r--packages/taler-util/src/http-client/merchant.ts305
1 files changed, 286 insertions, 19 deletions
diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts
index 10afdc8eb..e6af6dfe8 100644
--- a/packages/taler-util/src/http-client/merchant.ts
+++ b/packages/taler-util/src/http-client/merchant.ts
@@ -19,19 +19,24 @@ import {
FailCasesByMethod,
HttpStatusCode,
LibtoolVersion,
+ OperationFail,
+ OperationOk,
PaginationParams,
ResultByMethod,
+ TalerError,
+ TalerErrorCode,
TalerMerchantApi,
+ TalerMerchantConfigResponse,
codecForAbortResponse,
codecForAccountAddResponse,
codecForAccountKycRedirects,
codecForAccountsSummaryResponse,
codecForBankAccountDetail,
- codecForBankAccountEntry,
+ codecForCategoryListResponse,
+ codecForCategoryProductList,
codecForClaimResponse,
codecForInstancesResponse,
codecForInventorySummaryResponse,
- codecForMerchantConfig,
codecForMerchantOrderPrivateStatusResponse,
codecForMerchantPosProductDetail,
codecForMerchantRefundResponse,
@@ -40,6 +45,7 @@ import {
codecForOtpDeviceSummaryResponse,
codecForOutOfStockResponse,
codecForPaidRefundStatusResponse,
+ codecForPaymentDeniedLegallyResponse,
codecForPaymentResponse,
codecForPostOrderResponse,
codecForProductDetail,
@@ -47,6 +53,8 @@ import {
codecForStatusGoto,
codecForStatusPaid,
codecForStatusStatusUnpaid,
+ codecForTalerCommonConfigResponse,
+ codecForTalerMerchantConfigResponse,
codecForTansferList,
codecForTemplateDetails,
codecForTemplateSummaryResponse,
@@ -64,12 +72,13 @@ import {
HttpRequestLibrary,
HttpResponse,
createPlatformHttpLib,
+ readSuccessResponseJsonOrThrow,
readTalerErrorResponse,
} from "@gnu-taler/taler-util/http";
import { opSuccessFromHttp, opUnknownFailure } from "../operation.js";
import {
+ addPaginationParams,
CacheEvictor,
- addMerchantPaginationParams,
makeBearerTokenAuthHeader,
nullEvictor,
} from "./utils.js";
@@ -93,6 +102,9 @@ export enum TalerMerchantInstanceCacheEviction {
CREATE_PRODUCT,
UPDATE_PRODUCT,
DELETE_PRODUCT,
+ CREATE_CATEGORY,
+ UPDATE_CATEGORY,
+ DELETE_CATEGORY,
CREATE_TRANSFER,
DELETE_TRANSFER,
CREATE_DEVICE,
@@ -109,11 +121,13 @@ export enum TalerMerchantInstanceCacheEviction {
DELETE_TOKENFAMILY,
LAST,
}
+
export enum TalerMerchantManagementCacheEviction {
CREATE_INSTANCE = TalerMerchantInstanceCacheEviction.LAST + 1,
UPDATE_INSTANCE,
DELETE_INSTANCE,
}
+
/**
* Protocol version spoken with the core bank.
*
@@ -124,7 +138,7 @@ export enum TalerMerchantManagementCacheEviction {
* Uses libtool's current:revision:age versioning.
*/
export class TalerMerchantInstanceHttpClient {
- public readonly PROTOCOL_VERSION = "15:0:0";
+ public readonly PROTOCOL_VERSION = "17:0:1";
readonly httpLib: HttpRequestLibrary;
readonly cacheEvictor: CacheEvictor<TalerMerchantInstanceCacheEviction>;
@@ -145,17 +159,48 @@ export class TalerMerchantInstanceHttpClient {
/**
* https://docs.taler.net/core/api-merchant.html#get--config
- *
*/
- async getConfig() {
+ async getConfig(): Promise<
+ | OperationFail<HttpStatusCode.NotFound>
+ | OperationOk<TalerMerchantConfigResponse>
+ > {
const url = new URL(`config`, this.baseUrl);
-
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
switch (resp.status) {
- case HttpStatusCode.Ok:
- return opSuccessFromHttp(resp, codecForMerchantConfig());
+ case HttpStatusCode.Ok: {
+ const minBody = await readSuccessResponseJsonOrThrow(
+ resp,
+ codecForTalerCommonConfigResponse(),
+ );
+ const expectedName = "taler-merchant";
+ if (minBody.name !== expectedName) {
+ throw TalerError.fromUncheckedDetail({
+ code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR,
+ requestUrl: resp.requestUrl,
+ httpStatusCode: resp.status,
+ detail: `Unexpected server component name (got ${minBody.name}, expected ${expectedName}})`,
+ });
+ }
+ if (!this.isCompatible(minBody.version)) {
+ throw TalerError.fromUncheckedDetail({
+ code: TalerErrorCode.GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION,
+ requestUrl: resp.requestUrl,
+ httpStatusCode: resp.status,
+ detail: `Unsupported protocol version, client supports ${this.PROTOCOL_VERSION}, server supports ${minBody.version}`,
+ });
+ }
+ // Now that we've checked the basic body, re-parse the full response.
+ const body = await readSuccessResponseJsonOrThrow(
+ resp,
+ codecForTalerMerchantConfigResponse(),
+ );
+ return {
+ type: "ok",
+ body,
+ };
+ }
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
@@ -232,6 +277,12 @@ export class TalerMerchantInstanceHttpClient {
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.GatewayTimeout:
return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.UnavailableForLegalReasons:
+ return opKnownAlternativeFailure(
+ resp,
+ resp.status,
+ codecForPaymentDeniedLegallyResponse(),
+ );
default:
return opUnknownFailure(resp, await readTalerErrorResponse(resp));
}
@@ -387,6 +438,12 @@ export class TalerMerchantInstanceHttpClient {
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.UnavailableForLegalReasons:
+ return opKnownAlternativeFailure(
+ resp,
+ resp.status,
+ codecForPaymentDeniedLegallyResponse(),
+ );
default:
return opUnknownFailure(resp, await readTalerErrorResponse(resp));
}
@@ -467,7 +524,7 @@ export class TalerMerchantInstanceHttpClient {
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private
*
*/
- async getCurrentInstanceDetails(token: AccessToken) {
+ async getCurrentInstanceDetails(token: AccessToken | undefined) {
const url = new URL(`private`, this.baseUrl);
const headers: Record<string, string> = {};
@@ -560,6 +617,8 @@ export class TalerMerchantInstanceHttpClient {
});
switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(resp, codecForAccountKycRedirects());
case HttpStatusCode.Accepted:
return opSuccessFromHttp(resp, codecForAccountKycRedirects());
case HttpStatusCode.NoContent:
@@ -662,10 +721,13 @@ export class TalerMerchantInstanceHttpClient {
/**
* https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-accounts
*/
- async listBankAccounts(token: AccessToken, params?: PaginationParams) {
+ async listBankAccounts(
+ token: AccessToken | undefined,
+ params?: PaginationParams,
+ ) {
const url = new URL(`private/accounts`, this.baseUrl);
- // addMerchantPaginationParams(url, params);
+ // addPaginationParams(url, params);
const headers: Record<string, string> = {};
if (token) {
@@ -754,6 +816,173 @@ export class TalerMerchantInstanceHttpClient {
//
/**
+ * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-categories
+ */
+ async listCategories(
+ token: AccessToken | undefined,
+ params?: PaginationParams,
+ ) {
+ const url = new URL(`private/categories`, this.baseUrl);
+
+ // addPaginationParams(url, params);
+
+ const headers: Record<string, string> = {};
+ if (token) {
+ headers.Authorization = makeBearerTokenAuthHeader(token);
+ }
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers,
+ });
+
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(resp, codecForCategoryListResponse());
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.Unauthorized: // FIXME: missing in docs
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ /**
+ * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-categories-$CATEGORY_ID
+ */
+ async getCategoryDetails(token: AccessToken | undefined, cId: string) {
+ const url = new URL(`private/categories/${cId}`, this.baseUrl);
+
+ const headers: Record<string, string> = {};
+ if (token) {
+ headers.Authorization = makeBearerTokenAuthHeader(token);
+ }
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers,
+ });
+
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(resp, codecForCategoryProductList());
+ case HttpStatusCode.Unauthorized: // FIXME: missing in docs
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ /**
+ * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-categories
+ */
+ async addCategory(
+ token: AccessToken | undefined,
+ body: TalerMerchantApi.CategoryCreateRequest,
+ ) {
+ const url = new URL(`private/categories`, this.baseUrl);
+
+ const headers: Record<string, string> = {};
+ if (token) {
+ headers.Authorization = makeBearerTokenAuthHeader(token);
+ }
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body,
+ headers,
+ });
+
+ switch (resp.status) {
+ case HttpStatusCode.Ok: {
+ this.cacheEvictor.notifySuccess(
+ TalerMerchantInstanceCacheEviction.CREATE_CATEGORY,
+ );
+ return opEmptySuccess(resp);
+ }
+ case HttpStatusCode.Unauthorized: // FIXME: missing in docs
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotFound: // FIXME: missing in docs
+ return opKnownHttpFailure(resp.status, resp);
+ // case HttpStatusCode.Conflict:
+ // return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ /**
+ * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-categories
+ */
+ async updateCategory(
+ token: AccessToken | undefined,
+ cid: string,
+ body: TalerMerchantApi.CategoryCreateRequest,
+ ) {
+ const url = new URL(`private/categories/${cid}`, this.baseUrl);
+
+ const headers: Record<string, string> = {};
+ if (token) {
+ headers.Authorization = makeBearerTokenAuthHeader(token);
+ }
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "PATCH",
+ body,
+ headers,
+ });
+
+ switch (resp.status) {
+ case HttpStatusCode.NoContent: {
+ this.cacheEvictor.notifySuccess(
+ TalerMerchantInstanceCacheEviction.UPDATE_CATEGORY,
+ );
+ return opEmptySuccess(resp);
+ }
+ case HttpStatusCode.Unauthorized: // FIXME: missing in docs
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotFound: // FIXME: missing in docs
+ return opKnownHttpFailure(resp.status, resp);
+ // case HttpStatusCode.Conflict:
+ // return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ /**
+ * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-categories-$CATEGORY_ID
+ */
+ async deleteCategory(token: AccessToken | undefined, cId: string) {
+ const url = new URL(`private/categories/${cId}`, this.baseUrl);
+
+ const headers: Record<string, string> = {};
+ if (token) {
+ headers.Authorization = makeBearerTokenAuthHeader(token);
+ }
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "DELETE",
+ headers,
+ });
+
+ switch (resp.status) {
+ case HttpStatusCode.NoContent: {
+ this.cacheEvictor.notifySuccess(
+ TalerMerchantInstanceCacheEviction.DELETE_CATEGORY,
+ );
+ return opEmptySuccess(resp);
+ }
+ case HttpStatusCode.Unauthorized: // FIXME: missing in docs
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ // case HttpStatusCode.Conflict:
+ // return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+ }
+ }
+
+ /**
* https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-products
*/
async addProduct(
@@ -837,7 +1066,7 @@ export class TalerMerchantInstanceHttpClient {
) {
const url = new URL(`private/products`, this.baseUrl);
- addMerchantPaginationParams(url, params);
+ addPaginationParams(url, params);
const headers: Record<string, string> = {};
if (token) {
@@ -913,7 +1142,7 @@ export class TalerMerchantInstanceHttpClient {
}
/**
- * https://docs.taler.net/core/api-merchant.html#reserving-inventory
+ * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-products-$PRODUCT_ID-lock
*/
async lockProduct(
token: AccessToken | undefined,
@@ -951,7 +1180,7 @@ export class TalerMerchantInstanceHttpClient {
}
/**
- * https://docs.taler.net/core/api-merchant.html#removing-products-from-inventory
+ * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-products-$PRODUCT_ID
*/
async deleteProduct(token: AccessToken | undefined, productId: string) {
const url = new URL(`private/products/${productId}`, this.baseUrl);
@@ -1020,6 +1249,12 @@ export class TalerMerchantInstanceHttpClient {
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Unauthorized: // FIXME: missing in docs
return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.UnavailableForLegalReasons:
+ return opKnownAlternativeFailure(
+ resp,
+ resp.status,
+ codecForPaymentDeniedLegallyResponse(),
+ );
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Gone:
@@ -1063,7 +1298,7 @@ export class TalerMerchantInstanceHttpClient {
if (params.wired !== undefined) {
url.searchParams.set("wired", params.wired ? "YES" : "NO");
}
- addMerchantPaginationParams(url, params);
+ addPaginationParams(url, params);
const headers: Record<string, string> = {};
if (token) {
@@ -1257,6 +1492,12 @@ export class TalerMerchantInstanceHttpClient {
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict:
return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.UnavailableForLegalReasons:
+ return opKnownAlternativeFailure(
+ resp,
+ resp.status,
+ codecForPaymentDeniedLegallyResponse(),
+ );
default:
return opUnknownFailure(resp, await readTalerErrorResponse(resp));
}
@@ -1324,7 +1565,7 @@ export class TalerMerchantInstanceHttpClient {
if (params.verified !== undefined) {
url.searchParams.set("verified", params.verified ? "YES" : "NO");
}
- addMerchantPaginationParams(url, params);
+ addPaginationParams(url, params);
const headers: Record<string, string> = {};
if (token) {
@@ -1465,7 +1706,7 @@ export class TalerMerchantInstanceHttpClient {
) {
const url = new URL(`private/otp-devices`, this.baseUrl);
- addMerchantPaginationParams(url, params);
+ addPaginationParams(url, params);
const headers: Record<string, string> = {};
if (token) {
@@ -1638,7 +1879,7 @@ export class TalerMerchantInstanceHttpClient {
) {
const url = new URL(`private/templates`, this.baseUrl);
- addMerchantPaginationParams(url, params);
+ addPaginationParams(url, params);
const headers: Record<string, string> = {};
if (token) {
@@ -2370,3 +2611,29 @@ export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttp
}
}
}
+
+// 2024-09-23T01:23:14.421Z http.ts INFO malformed error response (status 200): {
+// "kyc_data": [
+// {
+// "payto_uri": "payto://iban/DE1327812254798?receiver-name=the%20name%20of%20merchant",
+// "exchange_url": "http://exchange.taler.test:1180/",
+// "no_keys": false,
+// "auth_conflict": false,
+// "exchange_http_status": 204,
+// "limits": [],
+// "payto_kycauths": [
+// "payto://iban/DE9714548806481?receiver-name=Exchanger+Normal&subject=54DR9R0CEWA1A7FK3QWABJ1PRBCD2X6S418Y5DE0P9Q1ASKTX770"
+// ]
+// },
+// {
+// "payto_uri": "payto://iban/DE1327812254798?receiver-name=the%20name%20of%20merchant",
+// "exchange_url": "https://exchange.demo.taler.net/",
+// "no_keys": false,
+// "auth_conflict": false,
+// "exchange_http_status": 400,
+// "exchange_code": 26,
+// "access_token": "0000000000000000000000000000000000000000000000000000",
+// "limits": []
+// }
+// ]
+// }