@fiado/api-invoker 5.43.0 → 5.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/collection-engine/api/CollectionEngineApi.d.ts +2 -3
- package/bin/collection-engine/api/CollectionEngineApi.js +7 -7
- package/bin/collection-engine/api/interfaces/ICollectionEngineApi.d.ts +25 -15
- package/bin/container.config.js +1 -1
- package/bin/document-generator/DocumentGeneratorApi.d.ts +1 -1
- package/bin/document-generator/DocumentGeneratorApi.js +3 -3
- package/bin/legalDocument/api/LegalDocumentApi.d.ts +2 -1
- package/bin/legalDocument/api/LegalDocumentApi.js +7 -0
- package/bin/legalDocument/api/interfaces/ILegalDocumentApi.d.ts +5 -0
- package/bin/retailCatalog/api/RetailCatalogBusinessApi.d.ts +2 -1
- package/bin/retailCatalog/api/RetailCatalogBusinessApi.js +11 -0
- package/bin/retailCatalog/api/interfaces/IRetailCatalogBusinessApi.d.ts +56 -5
- package/bin/retailCatalog/api/interfaces/IRetailCatalogBusinessApi.js +2 -1
- package/package.json +2 -2
- package/src/collection-engine/api/CollectionEngineApi.ts +8 -9
- package/src/collection-engine/api/interfaces/ICollectionEngineApi.ts +25 -16
- package/src/container.config.ts +2 -2
- package/src/document-generator/DocumentGeneratorApi.ts +4 -4
- package/src/legalDocument/api/LegalDocumentApi.ts +12 -1
- package/src/legalDocument/api/interfaces/ILegalDocumentApi.ts +6 -0
- package/src/retailCatalog/api/RetailCatalogBusinessApi.ts +16 -0
- package/src/retailCatalog/api/interfaces/IRetailCatalogBusinessApi.ts +61 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IHttpRequest } from "@fiado/http-client";
|
|
2
|
-
import { CollectRequest, CollectResponse,
|
|
2
|
+
import { CollectRequest, CollectResponse, DeregisterChargeResponse } from "@fiado/type-kit/bin/collection/index.js";
|
|
3
3
|
import { ICollectionEngineApi } from "./interfaces/ICollectionEngineApi.js";
|
|
4
4
|
type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
|
|
5
5
|
/**
|
|
@@ -11,7 +11,6 @@ export default class CollectionEngineApi implements ICollectionEngineApi {
|
|
|
11
11
|
private readonly baseUrl;
|
|
12
12
|
constructor(httpRequest: IHttpRequest);
|
|
13
13
|
collect(request: CollectRequest): Promise<FiadoApiResponse<CollectResponse>>;
|
|
14
|
-
|
|
15
|
-
deregister(ownerRef: string, productId: string, chargeId: string): Promise<FiadoApiResponse<boolean>>;
|
|
14
|
+
deregister(domain: string, chargeId: string): Promise<FiadoApiResponse<DeregisterChargeResponse>>;
|
|
16
15
|
}
|
|
17
16
|
export {};
|
|
@@ -25,13 +25,13 @@ let CollectionEngineApi = class CollectionEngineApi {
|
|
|
25
25
|
const url = `${this.baseUrl}collection/collect`;
|
|
26
26
|
return await this.httpRequest.post(url, request);
|
|
27
27
|
}
|
|
28
|
-
async
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return await this.httpRequest.post(url, {
|
|
28
|
+
async deregister(domain, chargeId) {
|
|
29
|
+
// `chargeId` lleva separadores `#` en todos los dominios (`SALE#42#INSTALLMENT#3`): sin
|
|
30
|
+
// escapar, el navegador o el cliente HTTP lo tratan como fragmento y el motor recibe la
|
|
31
|
+
// parte anterior al primer `#`, o sea otro cargo.
|
|
32
|
+
const url = `${this.baseUrl}collection/domains/${encodeURIComponent(domain)}`
|
|
33
|
+
+ `/charges/${encodeURIComponent(chargeId)}/deregister`;
|
|
34
|
+
return await this.httpRequest.post(url, {});
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
37
|
CollectionEngineApi = __decorate([
|
|
@@ -1,30 +1,40 @@
|
|
|
1
|
-
import { CollectRequest, CollectResponse,
|
|
1
|
+
import { CollectRequest, CollectResponse, DeregisterChargeResponse } from "@fiado/type-kit/bin/collection/index.js";
|
|
2
2
|
type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
|
|
3
3
|
/**
|
|
4
4
|
* Cliente para invocar los endpoints privados del lambda `collection-engine-business`.
|
|
5
5
|
*
|
|
6
6
|
* Los endpoints viven bajo `COLLECTION_ENGINE_LAMBDA_URL` (API Gateway privado / VPC).
|
|
7
|
-
* El motor es agnóstico de proveedores
|
|
8
|
-
*
|
|
7
|
+
* El motor es agnóstico de proveedores y cuentas: el dominio dice QUÉ se debe y el motor
|
|
8
|
+
* decide de dónde cobrarlo según la config del producto.
|
|
9
|
+
*
|
|
10
|
+
* El alta explícita de un cargo cobrable (`registerCollectible`) todavía no existe en el
|
|
11
|
+
* motor: hoy `collect` crea el intent si no lo había, derivando su identidad de
|
|
12
|
+
* `domain#chargeId`. El método se reincorporará cuando el motor exponga la ruta.
|
|
9
13
|
*/
|
|
10
14
|
export interface ICollectionEngineApi {
|
|
11
15
|
/**
|
|
12
|
-
* Cobro on-demand síncrono. El dominio
|
|
13
|
-
*
|
|
16
|
+
* Cobro on-demand síncrono. El dominio manda el cargo ya resuelto y el motor recorre la
|
|
17
|
+
* cascada de fuentes configurada para el producto. Idempotente por `domain#chargeId`:
|
|
18
|
+
* repetir la llamada sobre un cargo ya cobrado devuelve el resultado anterior sin volver
|
|
19
|
+
* a mover dinero.
|
|
20
|
+
*
|
|
14
21
|
* Backend: POST /collection/collect (privado VPC).
|
|
15
22
|
*/
|
|
16
23
|
collect(request: CollectRequest): Promise<FiadoApiResponse<CollectResponse>>;
|
|
17
24
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* Baja de un cargo: detiene los reintentos de un cobro que el dominio castigó, condonó o
|
|
26
|
+
* reestructuró. Sin esto, un cargo sin fondos se reintenta indefinidamente.
|
|
27
|
+
*
|
|
28
|
+
* Identifica el cobro por `domain` y `chargeId`, que es la llave con la que el motor lo
|
|
29
|
+
* registró. Es idempotente: dar de baja un cargo ya terminal devuelve `ALREADY_TERMINAL`
|
|
30
|
+
* sin alterarlo.
|
|
31
|
+
*
|
|
32
|
+
* Un cargo desconocido responde 404, y el cliente HTTP lo propaga como excepción — no llega
|
|
33
|
+
* como envelope con `code`. El dominio debe atraparlo si un cargo ya inexistente es un caso
|
|
34
|
+
* esperado de su flujo.
|
|
35
|
+
*
|
|
36
|
+
* Backend: POST /collection/domains/{domain}/charges/{chargeId}/deregister (privado VPC).
|
|
27
37
|
*/
|
|
28
|
-
deregister(
|
|
38
|
+
deregister(domain: string, chargeId: string): Promise<FiadoApiResponse<DeregisterChargeResponse>>;
|
|
29
39
|
}
|
|
30
40
|
export {};
|
package/bin/container.config.js
CHANGED
|
@@ -280,7 +280,7 @@ export const apiInvokerBindings = new ContainerModule(({ bind }) => {
|
|
|
280
280
|
// El productor necesita WEBHOOK_BUSINESS_QUEUE_URL y sqs:SendMessage sobre la cola.
|
|
281
281
|
bind("IWebhookBusinessPublisher").to(WebhookBusinessPublisher);
|
|
282
282
|
bind("ICentralPaymentsWebhookApi").to(CentralPaymentsWebhookApi);
|
|
283
|
-
// Collection engine — motor de cobro central multitenant (collect/
|
|
283
|
+
// Collection engine — motor de cobro central multitenant (collect/deregister)
|
|
284
284
|
bind("ICollectionEngineApi").to(CollectionEngineApi);
|
|
285
285
|
// Domain callback — el motor notifica al dominio dueño del cargo (applyCollectionResult)
|
|
286
286
|
bind("IDomainCallbackApi").to(DomainCallbackApi);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IHttpRequest } from "@fiado/http-client";
|
|
1
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
2
2
|
import { IDocumentGeneratorApi } from "./interfaces/IDocumentGeneratorApi.js";
|
|
3
3
|
import { DocumentGenerateRequest } from "@fiado/type-kit/bin/credit/dtos/internal/DocumentGenerateRequest.js";
|
|
4
4
|
import { DocumentGenerateResponse } from "@fiado/type-kit/bin/credit/dtos/internal/DocumentGenerateResponse.js";
|
|
@@ -37,17 +37,17 @@ let DocumentGeneratorApi = class DocumentGeneratorApi {
|
|
|
37
37
|
async getDocument(documentId) {
|
|
38
38
|
const url = `${this.baseUrl}document/${documentId}`;
|
|
39
39
|
const operationName = "documentGetDocument";
|
|
40
|
-
return await this.httpRequest.
|
|
40
|
+
return await this.httpRequest.get(url, undefined, { 'operationName': operationName });
|
|
41
41
|
}
|
|
42
42
|
async getDocumentUrl(documentId) {
|
|
43
43
|
const url = `${this.baseUrl}document/${documentId}/url`;
|
|
44
44
|
const operationName = "documentGetDocumentUrl";
|
|
45
|
-
return await this.httpRequest.
|
|
45
|
+
return await this.httpRequest.get(url, undefined, { 'operationName': operationName });
|
|
46
46
|
}
|
|
47
47
|
async getDocumentByReference(type, referenceId) {
|
|
48
48
|
const url = `${this.baseUrl}document/by-reference/${type}/${referenceId}`;
|
|
49
49
|
const operationName = "documentGetDocumentByReference";
|
|
50
|
-
return await this.httpRequest.
|
|
50
|
+
return await this.httpRequest.get(url, undefined, { 'operationName': operationName });
|
|
51
51
|
}
|
|
52
52
|
};
|
|
53
53
|
DocumentGeneratorApi = __decorate([
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ILegalDocumentApi } from "./interfaces/ILegalDocumentApi.js";
|
|
2
|
-
import { IHttpRequest } from "@fiado/http-client";
|
|
2
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
3
3
|
export declare class LegalDocumentApi implements ILegalDocumentApi {
|
|
4
4
|
private httpRequest;
|
|
5
5
|
private readonly baseUrl;
|
|
6
6
|
constructor(httpRequest: IHttpRequest);
|
|
7
7
|
getPendingSign(directoryId: string): Promise<any>;
|
|
8
8
|
getDirectoryAcceptanceByDirectoryId(directoryId: string): Promise<any>;
|
|
9
|
+
getDocumentByType(type: string): Promise<any>;
|
|
9
10
|
}
|
|
@@ -31,6 +31,13 @@ let LegalDocumentApi = class LegalDocumentApi {
|
|
|
31
31
|
const url = `${this.baseUrl}directories/${directoryId}`;
|
|
32
32
|
return await this.httpRequest.get(url);
|
|
33
33
|
}
|
|
34
|
+
async getDocumentByType(type) {
|
|
35
|
+
if (!type) {
|
|
36
|
+
throw new Error("Document type is required.");
|
|
37
|
+
}
|
|
38
|
+
const url = `${this.baseUrl}documents/types/${encodeURIComponent(type)}`;
|
|
39
|
+
return await this.httpRequest.get(url);
|
|
40
|
+
}
|
|
34
41
|
};
|
|
35
42
|
LegalDocumentApi = __decorate([
|
|
36
43
|
injectable(),
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export interface ILegalDocumentApi {
|
|
2
2
|
getPendingSign(directoryId: string): Promise<any>;
|
|
3
3
|
getDirectoryAcceptanceByDirectoryId(directoryId: string): Promise<any>;
|
|
4
|
+
/**
|
|
5
|
+
* Documento legal vigente de un tipo (PRIVACY_PAY, etc.).
|
|
6
|
+
* Devuelve el documento con su URL firmada y el vencimiento de esa URL.
|
|
7
|
+
*/
|
|
8
|
+
getDocumentByType(type: string): Promise<any>;
|
|
4
9
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IHttpRequest } from "@fiado/http-client";
|
|
2
2
|
import { StandardResponse } from "@fiado/gateway-adapter";
|
|
3
|
-
import { IRetailCatalogBusinessApi, ReleaseReservationBody, ReserveInventoryBody, ReserveInventoryResult, SellInventoryBody, StoreCatalogItem } from "./interfaces/IRetailCatalogBusinessApi.js";
|
|
3
|
+
import { ImeiProductLookupResponse, IRetailCatalogBusinessApi, ReleaseReservationBody, ReserveInventoryBody, ReserveInventoryResult, SellInventoryBody, StoreCatalogItem } from "./interfaces/IRetailCatalogBusinessApi.js";
|
|
4
4
|
/**
|
|
5
5
|
* Publisher HTTP del lambda `retail-catalog-business` (SureKeep Fase 1, pista Retail) para sus
|
|
6
6
|
* endpoints privados del ciclo de venta del IMEI. Contrato y semántica completos →
|
|
@@ -20,6 +20,7 @@ export default class RetailCatalogBusinessApi implements IRetailCatalogBusinessA
|
|
|
20
20
|
constructor(httpRequest: IHttpRequest);
|
|
21
21
|
private tenantHeader;
|
|
22
22
|
getStoreCatalog(storeId: string, issuer: string): Promise<StandardResponse<StoreCatalogItem[]>>;
|
|
23
|
+
getProductsByImei(imeis: string[], tenantId: string): Promise<StandardResponse<ImeiProductLookupResponse>>;
|
|
23
24
|
reserveInventory(imei: string, body: ReserveInventoryBody, issuer: string): Promise<StandardResponse<ReserveInventoryResult>>;
|
|
24
25
|
sellInventory(imei: string, body: SellInventoryBody, issuer: string): Promise<StandardResponse<void>>;
|
|
25
26
|
releaseInventory(imei: string, body: ReleaseReservationBody, issuer: string): Promise<StandardResponse<void>>;
|
|
@@ -11,6 +11,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
11
11
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
12
|
};
|
|
13
13
|
import { inject, injectable } from "inversify";
|
|
14
|
+
import { IMEI_PRODUCT_BATCH_MAX, } from "./interfaces/IRetailCatalogBusinessApi.js";
|
|
14
15
|
/** Header por el que el destino resuelve el silo (issuer Cognito forwardeado por el caller). */
|
|
15
16
|
const TENANT_ISSUER_HEADER = "x-tenant-issuer";
|
|
16
17
|
/**
|
|
@@ -40,6 +41,16 @@ let RetailCatalogBusinessApi = class RetailCatalogBusinessApi {
|
|
|
40
41
|
const url = `${this.baseUrl}/private/stores/${encodeURIComponent(storeId)}/catalog`;
|
|
41
42
|
return await this.httpRequest.get(url, undefined, this.tenantHeader(issuer));
|
|
42
43
|
}
|
|
44
|
+
async getProductsByImei(imeis, tenantId) {
|
|
45
|
+
// Se corta acá: mandar un lote que el destino va a rechazar solo gasta un round-trip.
|
|
46
|
+
if (imeis.length > IMEI_PRODUCT_BATCH_MAX) {
|
|
47
|
+
throw new Error(`El lote admite hasta ${IMEI_PRODUCT_BATCH_MAX} IMEIs y llegaron ${imeis.length}`);
|
|
48
|
+
}
|
|
49
|
+
// `URLSearchParams` codifica la coma del separador; el destino hace `split(',')` sobre el
|
|
50
|
+
// valor ya decodificado por API Gateway.
|
|
51
|
+
const query = new URLSearchParams({ imeis: imeis.join(","), tenantId });
|
|
52
|
+
return await this.httpRequest.get(`${this.baseUrl}/private/products/by-imei?${query.toString()}`);
|
|
53
|
+
}
|
|
43
54
|
async reserveInventory(imei, body, issuer) {
|
|
44
55
|
const url = `${this.baseUrl}/private/inventory/${encodeURIComponent(imei)}/reserve`;
|
|
45
56
|
return await this.httpRequest.put(url, body, this.tenantHeader(issuer));
|
|
@@ -5,12 +5,17 @@ import type { MdmLockModeEnum, MdmProviderEnum } from "@fiado/type-kit/bin/retai
|
|
|
5
5
|
* para sus endpoints privados del ciclo de venta del IMEI, consumidos por el wizard F2.
|
|
6
6
|
*
|
|
7
7
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
-
* TENANT —
|
|
8
|
+
* TENANT — DOS convenciones conviven acá. Mirá la firma de cada método.
|
|
9
9
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
10
|
+
* Los cinco métodos del ciclo de venta del IMEI toman `issuer` y lo mandan en el header
|
|
11
|
+
* `x-tenant-issuer` (`PrivateController.issuerFromHeader` → `TenantContextService.withContext`).
|
|
12
|
+
* `getProductsByImei` toma `tenantId` y lo manda por `?tenantId=`
|
|
13
|
+
* (→ `TenantContextService.withContextByTenantId`), porque su consumidor es un backoffice que no
|
|
14
|
+
* tiene el issuer Cognito. Los privados son `Feature.ANONIMUS` (sin RBAC; la protección es la red
|
|
15
|
+
* VPC), así que el caller DEBE declarar uno de los dos — no hay default.
|
|
16
|
+
*
|
|
17
|
+
* TD: converger los cinco del ciclo de venta a `?tenantId=`, el camino canónico para
|
|
18
|
+
* service-to-service. No se migran acá para no romper al wizard, que ya consume el header.
|
|
14
19
|
*
|
|
15
20
|
* ⚠️ Retorno `StandardResponse<T>` — el consumer lee `result.data`, NO `result.body`.
|
|
16
21
|
* Errores: el destino lanza `DomainError` tipado → el publisher RECHAZA (no devuelve null); cada
|
|
@@ -53,6 +58,28 @@ export interface StoreCatalogItem {
|
|
|
53
58
|
/** Modo de bloqueo pactado; `null` si el producto no lleva MDM. */
|
|
54
59
|
mdmLockMode: MdmLockModeEnum | null;
|
|
55
60
|
}
|
|
61
|
+
/** Techo de IMEIs por request del lote IMEI->producto. Lo declara y lo honra el destino. */
|
|
62
|
+
export declare const IMEI_PRODUCT_BATCH_MAX = 100;
|
|
63
|
+
/** Por que se pudo (o no) resolver el producto de un IMEI del lote. */
|
|
64
|
+
export type ImeiProductLookupStatus = "OK" | "NOT_FOUND" | "UNAVAILABLE" | "INVALID_ID";
|
|
65
|
+
/** Una entrada del lote IMEI->producto (espeja `ImeiProductLookupEntry` del destino). */
|
|
66
|
+
export interface ImeiProductLookupEntry {
|
|
67
|
+
imei: string;
|
|
68
|
+
/**
|
|
69
|
+
* `OK` trae sku/brand/model · `NOT_FOUND` el catalogo no llega al producto (dato POSITIVO) ·
|
|
70
|
+
* `UNAVAILABLE` no se pudo leer ese IMEI, reintentalo · `INVALID_ID` no tiene forma de IMEI.
|
|
71
|
+
*/
|
|
72
|
+
status: ImeiProductLookupStatus;
|
|
73
|
+
sku?: string;
|
|
74
|
+
/** Marca comercial (p. ej. "Apple"). Solo viene con `status: OK`. */
|
|
75
|
+
brand?: string;
|
|
76
|
+
/** Modelo (p. ej. "iPhone 15 Pro"). Solo viene con `status: OK`. */
|
|
77
|
+
model?: string;
|
|
78
|
+
}
|
|
79
|
+
/** Respuesta del lote: UNA entrada por IMEI pedido, en el MISMO orden. */
|
|
80
|
+
export interface ImeiProductLookupResponse {
|
|
81
|
+
items: ImeiProductLookupEntry[];
|
|
82
|
+
}
|
|
56
83
|
/** Resultado de la reserva (espeja `ReserveInventoryResponse` del destino). */
|
|
57
84
|
export interface ReserveInventoryResult {
|
|
58
85
|
reservationExpiresAt: string;
|
|
@@ -74,6 +101,30 @@ export interface ReleaseReservationBody {
|
|
|
74
101
|
export interface IRetailCatalogBusinessApi {
|
|
75
102
|
/** GET `/private/stores/{storeId}/catalog` — catálogo vendible (sku, imei, precio de contado, disponibilidad). */
|
|
76
103
|
getStoreCatalog(storeId: string, issuer: string): Promise<StandardResponse<StoreCatalogItem[]>>;
|
|
104
|
+
/**
|
|
105
|
+
* GET `/private/products/by-imei?imeis=&tenantId=` — resuelve un lote de IMEIs a su producto
|
|
106
|
+
* (sku + marca + modelo). Para los lambdas que del equipo solo guardan el IMEI.
|
|
107
|
+
*
|
|
108
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
109
|
+
* ⚠️ El tenant va por `?tenantId=`, NO por el header `x-tenant-issuer` del resto de este publisher.
|
|
110
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
111
|
+
* El consumidor es un backoffice que solo tiene el `tenantId` de su contexto; el issuer Cognito
|
|
112
|
+
* no existe de su lado. El destino entra por `TenantContextService.withContextByTenantId`, con las
|
|
113
|
+
* mismas garantías (mismo diccionario, AssumeRole del silo, fail-closed sin tenant).
|
|
114
|
+
*
|
|
115
|
+
* ── El contrato del lote ────────────────────────────────────────────────────────────────────
|
|
116
|
+
* Devuelve UNA entrada por cada IMEI pedido, en el MISMO orden — el caller nunca reconcilia. Los
|
|
117
|
+
* repetidos se consultan una sola vez pero salen repetidos. Techo de `IMEI_PRODUCT_BATCH_MAX`
|
|
118
|
+
* IMEIs por request; el caller parte los lotes mayores.
|
|
119
|
+
*
|
|
120
|
+
* Ningún IMEI tumba el lote: el mal formado sale `INVALID_ID`, el que el catálogo no alcanza a
|
|
121
|
+
* resolver sale `NOT_FOUND` y el que DynamoDB no llegó a leer sale `UNAVAILABLE` (reintentable).
|
|
122
|
+
* Solo un fallo total del destino rechaza la promesa.
|
|
123
|
+
*
|
|
124
|
+
* @throws Error si el lote supera el techo (se corta acá, sin salir a la red) ·
|
|
125
|
+
* `400 IMEI_BATCH_REQUIRED` si `imeis` viene vacío · `400 UNKNOWN_TENANT` sin `tenantId`.
|
|
126
|
+
*/
|
|
127
|
+
getProductsByImei(imeis: string[], tenantId: string): Promise<StandardResponse<ImeiProductLookupResponse>>;
|
|
77
128
|
/** PUT `/private/inventory/{imei}/reserve` — reserva atómica del IMEI para una sesión (AVAILABLE → RESERVED). */
|
|
78
129
|
reserveInventory(imei: string, body: ReserveInventoryBody, issuer: string): Promise<StandardResponse<ReserveInventoryResult>>;
|
|
79
130
|
/** PUT `/private/inventory/{imei}/sell` — vende el IMEI (RESERVED → SOLD). */
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
/** Techo de IMEIs por request del lote IMEI->producto. Lo declara y lo honra el destino. */
|
|
2
|
+
export const IMEI_PRODUCT_BATCH_MAX = 100;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fiado/api-invoker",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.45.0",
|
|
4
4
|
"description": "Sirve como un puente entre diferentes funciones lambda, facilitando la comunicación entre ellas a través de invocaciones http",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@fiado/gateway-adapter": "^3.9.0",
|
|
35
35
|
"@fiado/http-client": "^2.0.1",
|
|
36
36
|
"@fiado/logger": "^1.1.3",
|
|
37
|
-
"@fiado/type-kit": "^3.
|
|
37
|
+
"@fiado/type-kit": "^3.367.0",
|
|
38
38
|
"dotenv": "^16.4.7"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
@@ -3,7 +3,7 @@ import type { IHttpRequest } from "@fiado/http-client";
|
|
|
3
3
|
import {
|
|
4
4
|
CollectRequest,
|
|
5
5
|
CollectResponse,
|
|
6
|
-
|
|
6
|
+
DeregisterChargeResponse,
|
|
7
7
|
} from "@fiado/type-kit/bin/collection/index.js";
|
|
8
8
|
import { ICollectionEngineApi } from "./interfaces/ICollectionEngineApi.js";
|
|
9
9
|
|
|
@@ -26,13 +26,12 @@ export default class CollectionEngineApi implements ICollectionEngineApi {
|
|
|
26
26
|
return await this.httpRequest.post(url, request);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
async
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return await this.httpRequest.post(url, { ownerRef, productId, chargeId });
|
|
29
|
+
async deregister(domain: string, chargeId: string): Promise<FiadoApiResponse<DeregisterChargeResponse>> {
|
|
30
|
+
// `chargeId` lleva separadores `#` en todos los dominios (`SALE#42#INSTALLMENT#3`): sin
|
|
31
|
+
// escapar, el navegador o el cliente HTTP lo tratan como fragmento y el motor recibe la
|
|
32
|
+
// parte anterior al primer `#`, o sea otro cargo.
|
|
33
|
+
const url = `${this.baseUrl}collection/domains/${encodeURIComponent(domain)}`
|
|
34
|
+
+ `/charges/${encodeURIComponent(chargeId)}/deregister`;
|
|
35
|
+
return await this.httpRequest.post(url, {});
|
|
37
36
|
}
|
|
38
37
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CollectRequest,
|
|
3
3
|
CollectResponse,
|
|
4
|
-
|
|
4
|
+
DeregisterChargeResponse,
|
|
5
5
|
} from "@fiado/type-kit/bin/collection/index.js";
|
|
6
6
|
|
|
7
7
|
type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
|
|
@@ -10,29 +10,38 @@ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApi
|
|
|
10
10
|
* Cliente para invocar los endpoints privados del lambda `collection-engine-business`.
|
|
11
11
|
*
|
|
12
12
|
* Los endpoints viven bajo `COLLECTION_ENGINE_LAMBDA_URL` (API Gateway privado / VPC).
|
|
13
|
-
* El motor es agnóstico de proveedores
|
|
14
|
-
*
|
|
13
|
+
* El motor es agnóstico de proveedores y cuentas: el dominio dice QUÉ se debe y el motor
|
|
14
|
+
* decide de dónde cobrarlo según la config del producto.
|
|
15
|
+
*
|
|
16
|
+
* El alta explícita de un cargo cobrable (`registerCollectible`) todavía no existe en el
|
|
17
|
+
* motor: hoy `collect` crea el intent si no lo había, derivando su identidad de
|
|
18
|
+
* `domain#chargeId`. El método se reincorporará cuando el motor exponga la ruta.
|
|
15
19
|
*/
|
|
16
20
|
export interface ICollectionEngineApi {
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
|
-
* Cobro on-demand síncrono. El dominio
|
|
20
|
-
*
|
|
23
|
+
* Cobro on-demand síncrono. El dominio manda el cargo ya resuelto y el motor recorre la
|
|
24
|
+
* cascada de fuentes configurada para el producto. Idempotente por `domain#chargeId`:
|
|
25
|
+
* repetir la llamada sobre un cargo ya cobrado devuelve el resultado anterior sin volver
|
|
26
|
+
* a mover dinero.
|
|
27
|
+
*
|
|
21
28
|
* Backend: POST /collection/collect (privado VPC).
|
|
22
29
|
*/
|
|
23
30
|
collect(request: CollectRequest): Promise<FiadoApiResponse<CollectResponse>>;
|
|
24
31
|
|
|
25
32
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* Baja de un cargo: detiene los reintentos de un cobro que el dominio castigó, condonó o
|
|
34
|
+
* reestructuró. Sin esto, un cargo sin fondos se reintenta indefinidamente.
|
|
35
|
+
*
|
|
36
|
+
* Identifica el cobro por `domain` y `chargeId`, que es la llave con la que el motor lo
|
|
37
|
+
* registró. Es idempotente: dar de baja un cargo ya terminal devuelve `ALREADY_TERMINAL`
|
|
38
|
+
* sin alterarlo.
|
|
39
|
+
*
|
|
40
|
+
* Un cargo desconocido responde 404, y el cliente HTTP lo propaga como excepción — no llega
|
|
41
|
+
* como envelope con `code`. El dominio debe atraparlo si un cargo ya inexistente es un caso
|
|
42
|
+
* esperado de su flujo.
|
|
43
|
+
*
|
|
44
|
+
* Backend: POST /collection/domains/{domain}/charges/{chargeId}/deregister (privado VPC).
|
|
36
45
|
*/
|
|
37
|
-
deregister(
|
|
46
|
+
deregister(domain: string, chargeId: string): Promise<FiadoApiResponse<DeregisterChargeResponse>>;
|
|
38
47
|
}
|
package/src/container.config.ts
CHANGED
|
@@ -195,7 +195,7 @@ import { IKycVerificationsBusinessApi, KycVerificationsBusinessApi } from "./kyc
|
|
|
195
195
|
// Webhook business — publisher de la cola compartida de entrega de eventos a suscriptores externos
|
|
196
196
|
import { IWebhookBusinessPublisher, WebhookBusinessPublisher } from "./webhook-business/index.js";
|
|
197
197
|
import { ICentralPaymentsWebhookApi, CentralPaymentsWebhookApi } from "./central-payments-webhook/index.js";
|
|
198
|
-
// Collection engine — cliente del motor de cobro central (collect/
|
|
198
|
+
// Collection engine — cliente del motor de cobro central (collect/deregister)
|
|
199
199
|
import { ICollectionEngineApi } from "./collection-engine/index.js";
|
|
200
200
|
import CollectionEngineApi from "./collection-engine/api/CollectionEngineApi.js";
|
|
201
201
|
// Domain callback — cliente genérico que usa el motor para notificar a cualquier dominio
|
|
@@ -374,7 +374,7 @@ export const apiInvokerBindings = new ContainerModule(({ bind }) => {
|
|
|
374
374
|
bind<IWebhookBusinessPublisher>("IWebhookBusinessPublisher").to(WebhookBusinessPublisher);
|
|
375
375
|
bind<ICentralPaymentsWebhookApi>("ICentralPaymentsWebhookApi").to(CentralPaymentsWebhookApi);
|
|
376
376
|
|
|
377
|
-
// Collection engine — motor de cobro central multitenant (collect/
|
|
377
|
+
// Collection engine — motor de cobro central multitenant (collect/deregister)
|
|
378
378
|
bind<ICollectionEngineApi>("ICollectionEngineApi").to(CollectionEngineApi);
|
|
379
379
|
|
|
380
380
|
// Domain callback — el motor notifica al dominio dueño del cargo (applyCollectionResult)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import dotenv from 'dotenv';
|
|
2
2
|
import { inject, injectable } from "inversify";
|
|
3
|
-
import { IHttpRequest } from "@fiado/http-client";
|
|
3
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
4
4
|
import { IDocumentGeneratorApi } from "./interfaces/IDocumentGeneratorApi.js";
|
|
5
5
|
import { DocumentGenerateRequest } from "@fiado/type-kit/bin/credit/dtos/internal/DocumentGenerateRequest.js";
|
|
6
6
|
import { DocumentGenerateResponse } from "@fiado/type-kit/bin/credit/dtos/internal/DocumentGenerateResponse.js";
|
|
@@ -34,18 +34,18 @@ export class DocumentGeneratorApi implements IDocumentGeneratorApi {
|
|
|
34
34
|
public async getDocument(documentId: string): Promise<DocumentGenerateResponse> {
|
|
35
35
|
const url = `${this.baseUrl}document/${documentId}`;
|
|
36
36
|
const operationName = "documentGetDocument";
|
|
37
|
-
return await this.httpRequest.
|
|
37
|
+
return await this.httpRequest.get(url, undefined, { 'operationName': operationName });
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
public async getDocumentUrl(documentId: string): Promise<{ url: string; expiresAt: string }> {
|
|
41
41
|
const url = `${this.baseUrl}document/${documentId}/url`;
|
|
42
42
|
const operationName = "documentGetDocumentUrl";
|
|
43
|
-
return await this.httpRequest.
|
|
43
|
+
return await this.httpRequest.get(url, undefined, { 'operationName': operationName });
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
public async getDocumentByReference(type: string, referenceId: string): Promise<DocumentGenerateResponse[]> {
|
|
47
47
|
const url = `${this.baseUrl}document/by-reference/${type}/${referenceId}`;
|
|
48
48
|
const operationName = "documentGetDocumentByReference";
|
|
49
|
-
return await this.httpRequest.
|
|
49
|
+
return await this.httpRequest.get(url, undefined, { 'operationName': operationName });
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { inject, injectable } from "inversify";
|
|
2
2
|
import { ILegalDocumentApi } from "./interfaces/ILegalDocumentApi.js";
|
|
3
|
-
import { IHttpRequest } from "@fiado/http-client";
|
|
3
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
@@ -34,5 +34,16 @@ export class LegalDocumentApi implements ILegalDocumentApi {
|
|
|
34
34
|
|
|
35
35
|
return await this.httpRequest.get<any>(url);
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
async getDocumentByType(type: string): Promise<any> {
|
|
39
|
+
|
|
40
|
+
if (!type) {
|
|
41
|
+
throw new Error("Document type is required.")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const url = `${this.baseUrl}documents/types/${encodeURIComponent(type)}`;
|
|
45
|
+
|
|
46
|
+
return await this.httpRequest.get<any>(url);
|
|
47
|
+
}
|
|
37
48
|
}
|
|
38
49
|
|
|
@@ -5,4 +5,10 @@ export interface ILegalDocumentApi {
|
|
|
5
5
|
getPendingSign(directoryId: string): Promise<any>;
|
|
6
6
|
getDirectoryAcceptanceByDirectoryId(directoryId: string): Promise<any>;
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Documento legal vigente de un tipo (PRIVACY_PAY, etc.).
|
|
10
|
+
* Devuelve el documento con su URL firmada y el vencimiento de esa URL.
|
|
11
|
+
*/
|
|
12
|
+
getDocumentByType(type: string): Promise<any>;
|
|
13
|
+
|
|
8
14
|
}
|
|
@@ -2,6 +2,8 @@ import { inject, injectable } from "inversify";
|
|
|
2
2
|
import type { IHttpRequest } from "@fiado/http-client";
|
|
3
3
|
import { StandardResponse } from "@fiado/gateway-adapter";
|
|
4
4
|
import {
|
|
5
|
+
IMEI_PRODUCT_BATCH_MAX,
|
|
6
|
+
ImeiProductLookupResponse,
|
|
5
7
|
IRetailCatalogBusinessApi,
|
|
6
8
|
ReleaseReservationBody,
|
|
7
9
|
ReserveInventoryBody,
|
|
@@ -42,6 +44,20 @@ export default class RetailCatalogBusinessApi implements IRetailCatalogBusinessA
|
|
|
42
44
|
return await this.httpRequest.get(url, undefined, this.tenantHeader(issuer));
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
async getProductsByImei(
|
|
48
|
+
imeis: string[],
|
|
49
|
+
tenantId: string,
|
|
50
|
+
): Promise<StandardResponse<ImeiProductLookupResponse>> {
|
|
51
|
+
// Se corta acá: mandar un lote que el destino va a rechazar solo gasta un round-trip.
|
|
52
|
+
if (imeis.length > IMEI_PRODUCT_BATCH_MAX) {
|
|
53
|
+
throw new Error(`El lote admite hasta ${IMEI_PRODUCT_BATCH_MAX} IMEIs y llegaron ${imeis.length}`);
|
|
54
|
+
}
|
|
55
|
+
// `URLSearchParams` codifica la coma del separador; el destino hace `split(',')` sobre el
|
|
56
|
+
// valor ya decodificado por API Gateway.
|
|
57
|
+
const query = new URLSearchParams({ imeis: imeis.join(","), tenantId });
|
|
58
|
+
return await this.httpRequest.get(`${this.baseUrl}/private/products/by-imei?${query.toString()}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
async reserveInventory(
|
|
46
62
|
imei: string,
|
|
47
63
|
body: ReserveInventoryBody,
|
|
@@ -6,12 +6,17 @@ import type { MdmLockModeEnum, MdmProviderEnum } from "@fiado/type-kit/bin/retai
|
|
|
6
6
|
* para sus endpoints privados del ciclo de venta del IMEI, consumidos por el wizard F2.
|
|
7
7
|
*
|
|
8
8
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
-
* TENANT —
|
|
9
|
+
* TENANT — DOS convenciones conviven acá. Mirá la firma de cada método.
|
|
10
10
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* `
|
|
11
|
+
* Los cinco métodos del ciclo de venta del IMEI toman `issuer` y lo mandan en el header
|
|
12
|
+
* `x-tenant-issuer` (`PrivateController.issuerFromHeader` → `TenantContextService.withContext`).
|
|
13
|
+
* `getProductsByImei` toma `tenantId` y lo manda por `?tenantId=`
|
|
14
|
+
* (→ `TenantContextService.withContextByTenantId`), porque su consumidor es un backoffice que no
|
|
15
|
+
* tiene el issuer Cognito. Los privados son `Feature.ANONIMUS` (sin RBAC; la protección es la red
|
|
16
|
+
* VPC), así que el caller DEBE declarar uno de los dos — no hay default.
|
|
17
|
+
*
|
|
18
|
+
* TD: converger los cinco del ciclo de venta a `?tenantId=`, el camino canónico para
|
|
19
|
+
* service-to-service. No se migran acá para no romper al wizard, que ya consume el header.
|
|
15
20
|
*
|
|
16
21
|
* ⚠️ Retorno `StandardResponse<T>` — el consumer lee `result.data`, NO `result.body`.
|
|
17
22
|
* Errores: el destino lanza `DomainError` tipado → el publisher RECHAZA (no devuelve null); cada
|
|
@@ -62,6 +67,32 @@ export interface StoreCatalogItem {
|
|
|
62
67
|
mdmLockMode: MdmLockModeEnum | null;
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
/** Techo de IMEIs por request del lote IMEI->producto. Lo declara y lo honra el destino. */
|
|
71
|
+
export const IMEI_PRODUCT_BATCH_MAX = 100;
|
|
72
|
+
|
|
73
|
+
/** Por que se pudo (o no) resolver el producto de un IMEI del lote. */
|
|
74
|
+
export type ImeiProductLookupStatus = "OK" | "NOT_FOUND" | "UNAVAILABLE" | "INVALID_ID";
|
|
75
|
+
|
|
76
|
+
/** Una entrada del lote IMEI->producto (espeja `ImeiProductLookupEntry` del destino). */
|
|
77
|
+
export interface ImeiProductLookupEntry {
|
|
78
|
+
imei: string;
|
|
79
|
+
/**
|
|
80
|
+
* `OK` trae sku/brand/model · `NOT_FOUND` el catalogo no llega al producto (dato POSITIVO) ·
|
|
81
|
+
* `UNAVAILABLE` no se pudo leer ese IMEI, reintentalo · `INVALID_ID` no tiene forma de IMEI.
|
|
82
|
+
*/
|
|
83
|
+
status: ImeiProductLookupStatus;
|
|
84
|
+
sku?: string;
|
|
85
|
+
/** Marca comercial (p. ej. "Apple"). Solo viene con `status: OK`. */
|
|
86
|
+
brand?: string;
|
|
87
|
+
/** Modelo (p. ej. "iPhone 15 Pro"). Solo viene con `status: OK`. */
|
|
88
|
+
model?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Respuesta del lote: UNA entrada por IMEI pedido, en el MISMO orden. */
|
|
92
|
+
export interface ImeiProductLookupResponse {
|
|
93
|
+
items: ImeiProductLookupEntry[];
|
|
94
|
+
}
|
|
95
|
+
|
|
65
96
|
/** Resultado de la reserva (espeja `ReserveInventoryResponse` del destino). */
|
|
66
97
|
export interface ReserveInventoryResult {
|
|
67
98
|
reservationExpiresAt: string;
|
|
@@ -88,6 +119,31 @@ export interface IRetailCatalogBusinessApi {
|
|
|
88
119
|
/** GET `/private/stores/{storeId}/catalog` — catálogo vendible (sku, imei, precio de contado, disponibilidad). */
|
|
89
120
|
getStoreCatalog(storeId: string, issuer: string): Promise<StandardResponse<StoreCatalogItem[]>>;
|
|
90
121
|
|
|
122
|
+
/**
|
|
123
|
+
* GET `/private/products/by-imei?imeis=&tenantId=` — resuelve un lote de IMEIs a su producto
|
|
124
|
+
* (sku + marca + modelo). Para los lambdas que del equipo solo guardan el IMEI.
|
|
125
|
+
*
|
|
126
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
127
|
+
* ⚠️ El tenant va por `?tenantId=`, NO por el header `x-tenant-issuer` del resto de este publisher.
|
|
128
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
129
|
+
* El consumidor es un backoffice que solo tiene el `tenantId` de su contexto; el issuer Cognito
|
|
130
|
+
* no existe de su lado. El destino entra por `TenantContextService.withContextByTenantId`, con las
|
|
131
|
+
* mismas garantías (mismo diccionario, AssumeRole del silo, fail-closed sin tenant).
|
|
132
|
+
*
|
|
133
|
+
* ── El contrato del lote ────────────────────────────────────────────────────────────────────
|
|
134
|
+
* Devuelve UNA entrada por cada IMEI pedido, en el MISMO orden — el caller nunca reconcilia. Los
|
|
135
|
+
* repetidos se consultan una sola vez pero salen repetidos. Techo de `IMEI_PRODUCT_BATCH_MAX`
|
|
136
|
+
* IMEIs por request; el caller parte los lotes mayores.
|
|
137
|
+
*
|
|
138
|
+
* Ningún IMEI tumba el lote: el mal formado sale `INVALID_ID`, el que el catálogo no alcanza a
|
|
139
|
+
* resolver sale `NOT_FOUND` y el que DynamoDB no llegó a leer sale `UNAVAILABLE` (reintentable).
|
|
140
|
+
* Solo un fallo total del destino rechaza la promesa.
|
|
141
|
+
*
|
|
142
|
+
* @throws Error si el lote supera el techo (se corta acá, sin salir a la red) ·
|
|
143
|
+
* `400 IMEI_BATCH_REQUIRED` si `imeis` viene vacío · `400 UNKNOWN_TENANT` sin `tenantId`.
|
|
144
|
+
*/
|
|
145
|
+
getProductsByImei(imeis: string[], tenantId: string): Promise<StandardResponse<ImeiProductLookupResponse>>;
|
|
146
|
+
|
|
91
147
|
/** PUT `/private/inventory/{imei}/reserve` — reserva atómica del IMEI para una sesión (AVAILABLE → RESERVED). */
|
|
92
148
|
reserveInventory(imei: string, body: ReserveInventoryBody, issuer: string): Promise<StandardResponse<ReserveInventoryResult>>;
|
|
93
149
|
|