@fiado/api-invoker 4.71.0 → 4.72.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/index.d.ts +2 -0
- package/bin/index.js +2 -0
- package/bin/retailCards/api/RetailCardsBusinessApi.d.ts +24 -0
- package/bin/retailCards/api/RetailCardsBusinessApi.js +51 -0
- package/bin/retailCards/api/interfaces/IRetailCardsBusinessApi.d.ts +41 -0
- package/bin/retailCards/api/interfaces/IRetailCardsBusinessApi.js +1 -0
- package/bin/retailCards/index.d.ts +2 -0
- package/bin/retailCards/index.js +2 -0
- package/bin/retailCatalog/api/RetailCatalogBusinessApi.d.ts +27 -0
- package/bin/retailCatalog/api/RetailCatalogBusinessApi.js +65 -0
- package/bin/retailCatalog/api/interfaces/IRetailCatalogBusinessApi.d.ts +64 -0
- package/bin/retailCatalog/api/interfaces/IRetailCatalogBusinessApi.js +1 -0
- package/bin/retailCatalog/index.d.ts +2 -0
- package/bin/retailCatalog/index.js +2 -0
- package/docs/TECH_DEBT.md +7 -0
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/retailCards/api/RetailCardsBusinessApi.ts +44 -0
- package/src/retailCards/api/interfaces/IRetailCardsBusinessApi.ts +47 -0
- package/src/retailCards/index.ts +2 -0
- package/src/retailCatalog/api/RetailCatalogBusinessApi.ts +68 -0
- package/src/retailCatalog/api/interfaces/IRetailCatalogBusinessApi.ts +75 -0
- package/src/retailCatalog/index.ts +2 -0
package/bin/index.d.ts
CHANGED
|
@@ -79,6 +79,8 @@ export * from "./cognitoBackofficeConnector/index.js";
|
|
|
79
79
|
export * from "./platformRbac/index.js";
|
|
80
80
|
export * from "./retailOrg/index.js";
|
|
81
81
|
export * from "./retailCustomer/index.js";
|
|
82
|
+
export * from "./retailCatalog/index.js";
|
|
83
|
+
export * from "./retailCards/index.js";
|
|
82
84
|
export * from "./loanConfig/index.js";
|
|
83
85
|
export * from "./loanOfferings/index.js";
|
|
84
86
|
export * from "./loanCollectorAssignment/index.js";
|
package/bin/index.js
CHANGED
|
@@ -79,6 +79,8 @@ export * from "./cognitoBackofficeConnector/index.js";
|
|
|
79
79
|
export * from "./platformRbac/index.js";
|
|
80
80
|
export * from "./retailOrg/index.js";
|
|
81
81
|
export * from "./retailCustomer/index.js";
|
|
82
|
+
export * from "./retailCatalog/index.js";
|
|
83
|
+
export * from "./retailCards/index.js";
|
|
82
84
|
export * from "./loanConfig/index.js";
|
|
83
85
|
export * from "./loanOfferings/index.js";
|
|
84
86
|
export * from "./loanCollectorAssignment/index.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
2
|
+
import { StandardResponse } from "@fiado/gateway-adapter";
|
|
3
|
+
import { AssignCardRequest } from "@fiado/type-kit/bin/retailCards/index.js";
|
|
4
|
+
import { IRetailCardsBusinessApi, MarkDeliveredBody, ReleaseCardBody } from "./interfaces/IRetailCardsBusinessApi.js";
|
|
5
|
+
/**
|
|
6
|
+
* Publisher HTTP del lambda `retail-cards-business` (SureKeep Fase 1, pista Retail) para sus endpoints
|
|
7
|
+
* privados de la tarjeta física PCF. Contrato y semántica completos → `IRetailCardsBusinessApi`
|
|
8
|
+
* (tenant por `?tenantId=` canónico, `StandardResponse` → leer `result.data`).
|
|
9
|
+
*
|
|
10
|
+
* Env var requerida en el consumer: `RETAIL_CARDS_BUSINESS_URL`.
|
|
11
|
+
* El template.yml del consumer la setea con:
|
|
12
|
+
*
|
|
13
|
+
* RETAIL_CARDS_BUSINESS_URL: '{{resolve:ssm:retail-cards-business}}'
|
|
14
|
+
*
|
|
15
|
+
* Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
|
|
16
|
+
*/
|
|
17
|
+
export default class RetailCardsBusinessApi implements IRetailCardsBusinessApi {
|
|
18
|
+
private httpRequest;
|
|
19
|
+
private readonly baseUrl;
|
|
20
|
+
constructor(httpRequest: IHttpRequest);
|
|
21
|
+
assign(cardNumber: string, body: AssignCardRequest, tenantId: string): Promise<StandardResponse<void>>;
|
|
22
|
+
markDelivered(cardNumber: string, body: MarkDeliveredBody, tenantId: string): Promise<StandardResponse<void>>;
|
|
23
|
+
release(cardNumber: string, body: ReleaseCardBody, tenantId: string): Promise<StandardResponse<void>>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { inject, injectable } from "inversify";
|
|
14
|
+
/**
|
|
15
|
+
* Publisher HTTP del lambda `retail-cards-business` (SureKeep Fase 1, pista Retail) para sus endpoints
|
|
16
|
+
* privados de la tarjeta física PCF. Contrato y semántica completos → `IRetailCardsBusinessApi`
|
|
17
|
+
* (tenant por `?tenantId=` canónico, `StandardResponse` → leer `result.data`).
|
|
18
|
+
*
|
|
19
|
+
* Env var requerida en el consumer: `RETAIL_CARDS_BUSINESS_URL`.
|
|
20
|
+
* El template.yml del consumer la setea con:
|
|
21
|
+
*
|
|
22
|
+
* RETAIL_CARDS_BUSINESS_URL: '{{resolve:ssm:retail-cards-business}}'
|
|
23
|
+
*
|
|
24
|
+
* Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
|
|
25
|
+
*/
|
|
26
|
+
let RetailCardsBusinessApi = class RetailCardsBusinessApi {
|
|
27
|
+
httpRequest;
|
|
28
|
+
// El buildspec publica la URL en SSM con "/" final — se normaliza para no armar "//private".
|
|
29
|
+
baseUrl = (process.env.RETAIL_CARDS_BUSINESS_URL || "").replace(/\/+$/, "");
|
|
30
|
+
constructor(httpRequest) {
|
|
31
|
+
this.httpRequest = httpRequest;
|
|
32
|
+
}
|
|
33
|
+
async assign(cardNumber, body, tenantId) {
|
|
34
|
+
const url = `${this.baseUrl}/private/cards/${encodeURIComponent(cardNumber)}/assign?tenantId=${encodeURIComponent(tenantId)}`;
|
|
35
|
+
return await this.httpRequest.post(url, body);
|
|
36
|
+
}
|
|
37
|
+
async markDelivered(cardNumber, body, tenantId) {
|
|
38
|
+
const url = `${this.baseUrl}/private/cards/${encodeURIComponent(cardNumber)}/deliver?tenantId=${encodeURIComponent(tenantId)}`;
|
|
39
|
+
return await this.httpRequest.post(url, body);
|
|
40
|
+
}
|
|
41
|
+
async release(cardNumber, body, tenantId) {
|
|
42
|
+
const url = `${this.baseUrl}/private/cards/${encodeURIComponent(cardNumber)}/release?tenantId=${encodeURIComponent(tenantId)}`;
|
|
43
|
+
return await this.httpRequest.post(url, body);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
RetailCardsBusinessApi = __decorate([
|
|
47
|
+
injectable(),
|
|
48
|
+
__param(0, inject("IHttpRequest")),
|
|
49
|
+
__metadata("design:paramtypes", [Object])
|
|
50
|
+
], RetailCardsBusinessApi);
|
|
51
|
+
export default RetailCardsBusinessApi;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { StandardResponse } from "@fiado/gateway-adapter";
|
|
2
|
+
import { AssignCardRequest } from "@fiado/type-kit/bin/retailCards/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Contrato del publisher HTTP del lambda `retail-cards-business` (SureKeep Fase 1, pista Retail) para
|
|
5
|
+
* sus endpoints privados de la tarjeta física PCF, consumidos por el wizard F2 (paso 09, spike S7).
|
|
6
|
+
*
|
|
7
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
* TENANT — `?tenantId=` CANÓNICO (query), NO header `x-tenant-issuer`.
|
|
9
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
* El silo se resuelve por el `tenantId` canónico que manda el caller (`?tenantId=` →
|
|
11
|
+
* `withContextByTenantId`). Los privados son `Feature.ANONIMUS` (sin RBAC; protección VPC). Sin
|
|
12
|
+
* `tenantId` → `UnknownTenantError` (fail-closed).
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ Retorno `StandardResponse<T>` — el consumer lee `result.data`, NO `result.body`.
|
|
15
|
+
* Errores: el destino lanza `DomainError` tipado → el publisher RECHAZA (no devuelve null).
|
|
16
|
+
*
|
|
17
|
+
* `deliver`/`release` reciben un body ligero (`{ deliveredByUserId? }` / `{ releasedByUserId? }`) que el
|
|
18
|
+
* destino no publica como DTO en `@fiado/type-kit` → se declara INLINE aquí. `assign` sí usa el DTO
|
|
19
|
+
* `AssignCardRequest` del type-kit (`{ assignedToCustomerId, assignedByUserId, creditId? }`).
|
|
20
|
+
*
|
|
21
|
+
* Env var requerida en el consumer: `RETAIL_CARDS_BUSINESS_URL`.
|
|
22
|
+
* El template.yml del consumer la setea con:
|
|
23
|
+
*
|
|
24
|
+
* RETAIL_CARDS_BUSINESS_URL: '{{resolve:ssm:retail-cards-business}}'
|
|
25
|
+
*/
|
|
26
|
+
/** Body de POST `/private/cards/{cardNumber}/deliver`. */
|
|
27
|
+
export interface MarkDeliveredBody {
|
|
28
|
+
deliveredByUserId?: string;
|
|
29
|
+
}
|
|
30
|
+
/** Body de POST `/private/cards/{cardNumber}/release`. */
|
|
31
|
+
export interface ReleaseCardBody {
|
|
32
|
+
releasedByUserId?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface IRetailCardsBusinessApi {
|
|
35
|
+
/** POST `/private/cards/{cardNumber}/assign` — asigna la tarjeta física a un cliente (paso 09). */
|
|
36
|
+
assign(cardNumber: string, body: AssignCardRequest, tenantId: string): Promise<StandardResponse<void>>;
|
|
37
|
+
/** POST `/private/cards/{cardNumber}/deliver` — marca la tarjeta como entregada al cliente (D8). */
|
|
38
|
+
markDelivered(cardNumber: string, body: MarkDeliveredBody, tenantId: string): Promise<StandardResponse<void>>;
|
|
39
|
+
/** POST `/private/cards/{cardNumber}/release` — revierte la asignación si la venta se cancela (DEC-007 destino). */
|
|
40
|
+
release(cardNumber: string, body: ReleaseCardBody, tenantId: string): Promise<StandardResponse<void>>;
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
2
|
+
import { StandardResponse } from "@fiado/gateway-adapter";
|
|
3
|
+
import { IRetailCatalogBusinessApi, ReleaseReservationBody, ReserveInventoryBody, ReserveInventoryResult, SellInventoryBody, StoreCatalogItem } from "./interfaces/IRetailCatalogBusinessApi.js";
|
|
4
|
+
/**
|
|
5
|
+
* Publisher HTTP del lambda `retail-catalog-business` (SureKeep Fase 1, pista Retail) para sus
|
|
6
|
+
* endpoints privados del ciclo de venta del IMEI. Contrato y semántica completos →
|
|
7
|
+
* `IRetailCatalogBusinessApi` (tenant por header `x-tenant-issuer`, `StandardResponse` → leer
|
|
8
|
+
* `result.data`).
|
|
9
|
+
*
|
|
10
|
+
* Env var requerida en el consumer: `RETAIL_CATALOG_BUSINESS_URL`.
|
|
11
|
+
* El template.yml del consumer la setea con:
|
|
12
|
+
*
|
|
13
|
+
* RETAIL_CATALOG_BUSINESS_URL: '{{resolve:ssm:retail-catalog-business}}'
|
|
14
|
+
*
|
|
15
|
+
* Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
|
|
16
|
+
*/
|
|
17
|
+
export default class RetailCatalogBusinessApi implements IRetailCatalogBusinessApi {
|
|
18
|
+
private httpRequest;
|
|
19
|
+
private readonly baseUrl;
|
|
20
|
+
constructor(httpRequest: IHttpRequest);
|
|
21
|
+
private tenantHeader;
|
|
22
|
+
getStoreCatalog(storeId: string, issuer: string): Promise<StandardResponse<StoreCatalogItem[]>>;
|
|
23
|
+
reserveInventory(imei: string, body: ReserveInventoryBody, issuer: string): Promise<StandardResponse<ReserveInventoryResult>>;
|
|
24
|
+
sellInventory(imei: string, body: SellInventoryBody, issuer: string): Promise<StandardResponse<void>>;
|
|
25
|
+
releaseInventory(imei: string, body: ReleaseReservationBody, issuer: string): Promise<StandardResponse<void>>;
|
|
26
|
+
returnInventory(imei: string, issuer: string): Promise<StandardResponse<void>>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { inject, injectable } from "inversify";
|
|
14
|
+
/** Header por el que el destino resuelve el silo (issuer Cognito forwardeado por el caller). */
|
|
15
|
+
const TENANT_ISSUER_HEADER = "x-tenant-issuer";
|
|
16
|
+
/**
|
|
17
|
+
* Publisher HTTP del lambda `retail-catalog-business` (SureKeep Fase 1, pista Retail) para sus
|
|
18
|
+
* endpoints privados del ciclo de venta del IMEI. Contrato y semántica completos →
|
|
19
|
+
* `IRetailCatalogBusinessApi` (tenant por header `x-tenant-issuer`, `StandardResponse` → leer
|
|
20
|
+
* `result.data`).
|
|
21
|
+
*
|
|
22
|
+
* Env var requerida en el consumer: `RETAIL_CATALOG_BUSINESS_URL`.
|
|
23
|
+
* El template.yml del consumer la setea con:
|
|
24
|
+
*
|
|
25
|
+
* RETAIL_CATALOG_BUSINESS_URL: '{{resolve:ssm:retail-catalog-business}}'
|
|
26
|
+
*
|
|
27
|
+
* Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
|
|
28
|
+
*/
|
|
29
|
+
let RetailCatalogBusinessApi = class RetailCatalogBusinessApi {
|
|
30
|
+
httpRequest;
|
|
31
|
+
// El buildspec publica la URL en SSM con "/" final — se normaliza para no armar "//private".
|
|
32
|
+
baseUrl = (process.env.RETAIL_CATALOG_BUSINESS_URL || "").replace(/\/+$/, "");
|
|
33
|
+
constructor(httpRequest) {
|
|
34
|
+
this.httpRequest = httpRequest;
|
|
35
|
+
}
|
|
36
|
+
tenantHeader(issuer) {
|
|
37
|
+
return { [TENANT_ISSUER_HEADER]: issuer };
|
|
38
|
+
}
|
|
39
|
+
async getStoreCatalog(storeId, issuer) {
|
|
40
|
+
const url = `${this.baseUrl}/private/stores/${encodeURIComponent(storeId)}/catalog`;
|
|
41
|
+
return await this.httpRequest.get(url, undefined, this.tenantHeader(issuer));
|
|
42
|
+
}
|
|
43
|
+
async reserveInventory(imei, body, issuer) {
|
|
44
|
+
const url = `${this.baseUrl}/private/inventory/${encodeURIComponent(imei)}/reserve`;
|
|
45
|
+
return await this.httpRequest.put(url, body, this.tenantHeader(issuer));
|
|
46
|
+
}
|
|
47
|
+
async sellInventory(imei, body, issuer) {
|
|
48
|
+
const url = `${this.baseUrl}/private/inventory/${encodeURIComponent(imei)}/sell`;
|
|
49
|
+
return await this.httpRequest.put(url, body, this.tenantHeader(issuer));
|
|
50
|
+
}
|
|
51
|
+
async releaseInventory(imei, body, issuer) {
|
|
52
|
+
const url = `${this.baseUrl}/private/inventory/${encodeURIComponent(imei)}/release`;
|
|
53
|
+
return await this.httpRequest.put(url, body, this.tenantHeader(issuer));
|
|
54
|
+
}
|
|
55
|
+
async returnInventory(imei, issuer) {
|
|
56
|
+
const url = `${this.baseUrl}/private/inventory/${encodeURIComponent(imei)}/return`;
|
|
57
|
+
return await this.httpRequest.put(url, undefined, this.tenantHeader(issuer));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
RetailCatalogBusinessApi = __decorate([
|
|
61
|
+
injectable(),
|
|
62
|
+
__param(0, inject("IHttpRequest")),
|
|
63
|
+
__metadata("design:paramtypes", [Object])
|
|
64
|
+
], RetailCatalogBusinessApi);
|
|
65
|
+
export default RetailCatalogBusinessApi;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { StandardResponse } from "@fiado/gateway-adapter";
|
|
2
|
+
/**
|
|
3
|
+
* Contrato del publisher HTTP del lambda `retail-catalog-business` (SureKeep Fase 1, pista Retail)
|
|
4
|
+
* para sus endpoints privados del ciclo de venta del IMEI, consumidos por el wizard F2.
|
|
5
|
+
*
|
|
6
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
* TENANT — header `x-tenant-issuer` (NO `?tenantId=`).
|
|
8
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
* A diferencia de customer/cards/loan-config (que resuelven el silo por `?tenantId=` canónico), este
|
|
10
|
+
* lambda resuelve el tenant por el ISSUER Cognito forwardeado en el header `x-tenant-issuer`
|
|
11
|
+
* (`PrivateController.issuerFromHeader` → `TenantContextService.withContext(issuer)`). Los privados son
|
|
12
|
+
* `Feature.ANONIMUS` (sin RBAC; la protección es la red VPC), así que el caller DEBE declarar el issuer.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ Retorno `StandardResponse<T>` — el consumer lee `result.data`, NO `result.body`.
|
|
15
|
+
* Errores: el destino lanza `DomainError` tipado → el publisher RECHAZA (no devuelve null); cada
|
|
16
|
+
* consumer mapea a su propio error de dominio.
|
|
17
|
+
*
|
|
18
|
+
* DTOs de catálogo/reserva: el lambda destino NO publica todavía estos shapes en `@fiado/type-kit`
|
|
19
|
+
* (`StoreCatalogItem`, `ReserveInventoryResult`), así que se declaran INLINE aquí (TD: migrar a
|
|
20
|
+
* type-kit cuando el contrato se estabilice). El shape espeja `StoreCatalogItemResponse` /
|
|
21
|
+
* `ReserveInventoryResponse` del `retail-catalog-business`.
|
|
22
|
+
*
|
|
23
|
+
* Env var requerida en el consumer: `RETAIL_CATALOG_BUSINESS_URL`.
|
|
24
|
+
* El template.yml del consumer la setea con:
|
|
25
|
+
*
|
|
26
|
+
* RETAIL_CATALOG_BUSINESS_URL: '{{resolve:ssm:retail-catalog-business}}'
|
|
27
|
+
*/
|
|
28
|
+
/** Ítem del catálogo vendible de un store (espeja `StoreCatalogItemResponse` del destino). */
|
|
29
|
+
export interface StoreCatalogItem {
|
|
30
|
+
sku: string;
|
|
31
|
+
imei: string;
|
|
32
|
+
cashPriceCents: number;
|
|
33
|
+
available: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** Resultado de la reserva (espeja `ReserveInventoryResponse` del destino). */
|
|
36
|
+
export interface ReserveInventoryResult {
|
|
37
|
+
reservationExpiresAt: string;
|
|
38
|
+
}
|
|
39
|
+
/** Body de PUT `/private/inventory/{imei}/reserve`. */
|
|
40
|
+
export interface ReserveInventoryBody {
|
|
41
|
+
sku: string;
|
|
42
|
+
sessionId: string;
|
|
43
|
+
}
|
|
44
|
+
/** Body de PUT `/private/inventory/{imei}/sell` (DEC-020 del destino: `customerId` puebla `soldToCustomerId`). */
|
|
45
|
+
export interface SellInventoryBody {
|
|
46
|
+
sessionId: string;
|
|
47
|
+
customerId: string;
|
|
48
|
+
}
|
|
49
|
+
/** Body de PUT `/private/inventory/{imei}/release` (DEC-018 del destino: verifica titularidad de la sesión). */
|
|
50
|
+
export interface ReleaseReservationBody {
|
|
51
|
+
sessionId: string;
|
|
52
|
+
}
|
|
53
|
+
export interface IRetailCatalogBusinessApi {
|
|
54
|
+
/** GET `/private/stores/{storeId}/catalog` — catálogo vendible (sku, imei, precio de contado, disponibilidad). */
|
|
55
|
+
getStoreCatalog(storeId: string, issuer: string): Promise<StandardResponse<StoreCatalogItem[]>>;
|
|
56
|
+
/** PUT `/private/inventory/{imei}/reserve` — reserva atómica del IMEI para una sesión (AVAILABLE → RESERVED). */
|
|
57
|
+
reserveInventory(imei: string, body: ReserveInventoryBody, issuer: string): Promise<StandardResponse<ReserveInventoryResult>>;
|
|
58
|
+
/** PUT `/private/inventory/{imei}/sell` — vende el IMEI (RESERVED → SOLD). */
|
|
59
|
+
sellInventory(imei: string, body: SellInventoryBody, issuer: string): Promise<StandardResponse<void>>;
|
|
60
|
+
/** PUT `/private/inventory/{imei}/release` — libera la reserva (RESERVED → AVAILABLE, titularidad de la sesión). */
|
|
61
|
+
releaseInventory(imei: string, body: ReleaseReservationBody, issuer: string): Promise<StandardResponse<void>>;
|
|
62
|
+
/** PUT `/private/inventory/{imei}/return` — devuelve el IMEI vendido al stock (SOLD → AVAILABLE, sin body). */
|
|
63
|
+
returnInventory(imei: string, issuer: string): Promise<StandardResponse<void>>;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/docs/TECH_DEBT.md
CHANGED
|
@@ -14,3 +14,10 @@ Registro vivo de deudas conscientes del repo. Cada entrada con ID estable `TD-AI
|
|
|
14
14
|
5. `npx tsc --noEmit` verde.
|
|
15
15
|
6. `npm publish` del api-invoker en su nueva version (Andres ejecuta).
|
|
16
16
|
- **Bloqueante:** SÍ — el primer deploy productivo o CI run del api-invoker (y de cualquier consumer que bumpee a la 4.6.0) rompe si el publish del type-kit NO se materializó antes.
|
|
17
|
+
|
|
18
|
+
## TD-AI-002 — 2026-07-20 — DTOs de catálogo/reserva/deliver tipados INLINE en los clients retail F2
|
|
19
|
+
|
|
20
|
+
- **Contexto:** los clients nuevos `RetailCatalogBusinessApi` (`src/retailCatalog/`) y `RetailCardsBusinessApi` (`src/retailCards/`) declaran INLINE algunos shapes que el lambda destino aún NO publica en `@fiado/type-kit`: `StoreCatalogItem` y `ReserveInventoryResult` (catalog, espejan `StoreCatalogItemResponse`/`ReserveInventoryResponse` del destino), y los bodies ligeros `MarkDeliveredBody`/`ReleaseCardBody` (cards). `assign` sí usa el DTO real `AssignCardRequest` del type-kit.
|
|
21
|
+
- **Asunción / pendiente:** se tiparon inline a propósito para no bloquear el cableado del wizard F2 en un bump de type-kit (regla de la sesión: NO tocar type-kit sin OK). Los shapes espejan fielmente el contrato del destino verificado en código.
|
|
22
|
+
- **Condición para cerrar:** cuando `retail-catalog-business`/`retail-cards-business` publiquen esos DTOs en `@fiado/type-kit` (dominios `retailCatalog`/`retailCards`), reemplazar los inline por el import del type-kit y bumpear.
|
|
23
|
+
- **Bloqueante:** NO — el contrato de red es correcto; es unificación de tipos con downstream.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fiado/api-invoker",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.72.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",
|
package/src/index.ts
CHANGED
|
@@ -79,6 +79,8 @@ export * from "./cognitoBackofficeConnector/index.js";
|
|
|
79
79
|
export * from "./platformRbac/index.js";
|
|
80
80
|
export * from "./retailOrg/index.js";
|
|
81
81
|
export * from "./retailCustomer/index.js";
|
|
82
|
+
export * from "./retailCatalog/index.js";
|
|
83
|
+
export * from "./retailCards/index.js";
|
|
82
84
|
export * from "./loanConfig/index.js";
|
|
83
85
|
export * from "./loanOfferings/index.js";
|
|
84
86
|
export * from "./loanCollectorAssignment/index.js";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { inject, injectable } from "inversify";
|
|
2
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
3
|
+
import { StandardResponse } from "@fiado/gateway-adapter";
|
|
4
|
+
import { AssignCardRequest } from "@fiado/type-kit/bin/retailCards/index.js";
|
|
5
|
+
import {
|
|
6
|
+
IRetailCardsBusinessApi,
|
|
7
|
+
MarkDeliveredBody,
|
|
8
|
+
ReleaseCardBody,
|
|
9
|
+
} from "./interfaces/IRetailCardsBusinessApi.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Publisher HTTP del lambda `retail-cards-business` (SureKeep Fase 1, pista Retail) para sus endpoints
|
|
13
|
+
* privados de la tarjeta física PCF. Contrato y semántica completos → `IRetailCardsBusinessApi`
|
|
14
|
+
* (tenant por `?tenantId=` canónico, `StandardResponse` → leer `result.data`).
|
|
15
|
+
*
|
|
16
|
+
* Env var requerida en el consumer: `RETAIL_CARDS_BUSINESS_URL`.
|
|
17
|
+
* El template.yml del consumer la setea con:
|
|
18
|
+
*
|
|
19
|
+
* RETAIL_CARDS_BUSINESS_URL: '{{resolve:ssm:retail-cards-business}}'
|
|
20
|
+
*
|
|
21
|
+
* Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
|
|
22
|
+
*/
|
|
23
|
+
@injectable()
|
|
24
|
+
export default class RetailCardsBusinessApi implements IRetailCardsBusinessApi {
|
|
25
|
+
// El buildspec publica la URL en SSM con "/" final — se normaliza para no armar "//private".
|
|
26
|
+
private readonly baseUrl = (process.env.RETAIL_CARDS_BUSINESS_URL || "").replace(/\/+$/, "");
|
|
27
|
+
|
|
28
|
+
constructor(@inject("IHttpRequest") private httpRequest: IHttpRequest) {}
|
|
29
|
+
|
|
30
|
+
async assign(cardNumber: string, body: AssignCardRequest, tenantId: string): Promise<StandardResponse<void>> {
|
|
31
|
+
const url = `${this.baseUrl}/private/cards/${encodeURIComponent(cardNumber)}/assign?tenantId=${encodeURIComponent(tenantId)}`;
|
|
32
|
+
return await this.httpRequest.post(url, body);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async markDelivered(cardNumber: string, body: MarkDeliveredBody, tenantId: string): Promise<StandardResponse<void>> {
|
|
36
|
+
const url = `${this.baseUrl}/private/cards/${encodeURIComponent(cardNumber)}/deliver?tenantId=${encodeURIComponent(tenantId)}`;
|
|
37
|
+
return await this.httpRequest.post(url, body);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async release(cardNumber: string, body: ReleaseCardBody, tenantId: string): Promise<StandardResponse<void>> {
|
|
41
|
+
const url = `${this.baseUrl}/private/cards/${encodeURIComponent(cardNumber)}/release?tenantId=${encodeURIComponent(tenantId)}`;
|
|
42
|
+
return await this.httpRequest.post(url, body);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { StandardResponse } from "@fiado/gateway-adapter";
|
|
2
|
+
import { AssignCardRequest } from "@fiado/type-kit/bin/retailCards/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Contrato del publisher HTTP del lambda `retail-cards-business` (SureKeep Fase 1, pista Retail) para
|
|
6
|
+
* sus endpoints privados de la tarjeta física PCF, consumidos por el wizard F2 (paso 09, spike S7).
|
|
7
|
+
*
|
|
8
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
* TENANT — `?tenantId=` CANÓNICO (query), NO header `x-tenant-issuer`.
|
|
10
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
* El silo se resuelve por el `tenantId` canónico que manda el caller (`?tenantId=` →
|
|
12
|
+
* `withContextByTenantId`). Los privados son `Feature.ANONIMUS` (sin RBAC; protección VPC). Sin
|
|
13
|
+
* `tenantId` → `UnknownTenantError` (fail-closed).
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ Retorno `StandardResponse<T>` — el consumer lee `result.data`, NO `result.body`.
|
|
16
|
+
* Errores: el destino lanza `DomainError` tipado → el publisher RECHAZA (no devuelve null).
|
|
17
|
+
*
|
|
18
|
+
* `deliver`/`release` reciben un body ligero (`{ deliveredByUserId? }` / `{ releasedByUserId? }`) que el
|
|
19
|
+
* destino no publica como DTO en `@fiado/type-kit` → se declara INLINE aquí. `assign` sí usa el DTO
|
|
20
|
+
* `AssignCardRequest` del type-kit (`{ assignedToCustomerId, assignedByUserId, creditId? }`).
|
|
21
|
+
*
|
|
22
|
+
* Env var requerida en el consumer: `RETAIL_CARDS_BUSINESS_URL`.
|
|
23
|
+
* El template.yml del consumer la setea con:
|
|
24
|
+
*
|
|
25
|
+
* RETAIL_CARDS_BUSINESS_URL: '{{resolve:ssm:retail-cards-business}}'
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** Body de POST `/private/cards/{cardNumber}/deliver`. */
|
|
29
|
+
export interface MarkDeliveredBody {
|
|
30
|
+
deliveredByUserId?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Body de POST `/private/cards/{cardNumber}/release`. */
|
|
34
|
+
export interface ReleaseCardBody {
|
|
35
|
+
releasedByUserId?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface IRetailCardsBusinessApi {
|
|
39
|
+
/** POST `/private/cards/{cardNumber}/assign` — asigna la tarjeta física a un cliente (paso 09). */
|
|
40
|
+
assign(cardNumber: string, body: AssignCardRequest, tenantId: string): Promise<StandardResponse<void>>;
|
|
41
|
+
|
|
42
|
+
/** POST `/private/cards/{cardNumber}/deliver` — marca la tarjeta como entregada al cliente (D8). */
|
|
43
|
+
markDelivered(cardNumber: string, body: MarkDeliveredBody, tenantId: string): Promise<StandardResponse<void>>;
|
|
44
|
+
|
|
45
|
+
/** POST `/private/cards/{cardNumber}/release` — revierte la asignación si la venta se cancela (DEC-007 destino). */
|
|
46
|
+
release(cardNumber: string, body: ReleaseCardBody, tenantId: string): Promise<StandardResponse<void>>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { inject, injectable } from "inversify";
|
|
2
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
3
|
+
import { StandardResponse } from "@fiado/gateway-adapter";
|
|
4
|
+
import {
|
|
5
|
+
IRetailCatalogBusinessApi,
|
|
6
|
+
ReleaseReservationBody,
|
|
7
|
+
ReserveInventoryBody,
|
|
8
|
+
ReserveInventoryResult,
|
|
9
|
+
SellInventoryBody,
|
|
10
|
+
StoreCatalogItem,
|
|
11
|
+
} from "./interfaces/IRetailCatalogBusinessApi.js";
|
|
12
|
+
|
|
13
|
+
/** Header por el que el destino resuelve el silo (issuer Cognito forwardeado por el caller). */
|
|
14
|
+
const TENANT_ISSUER_HEADER = "x-tenant-issuer";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Publisher HTTP del lambda `retail-catalog-business` (SureKeep Fase 1, pista Retail) para sus
|
|
18
|
+
* endpoints privados del ciclo de venta del IMEI. Contrato y semántica completos →
|
|
19
|
+
* `IRetailCatalogBusinessApi` (tenant por header `x-tenant-issuer`, `StandardResponse` → leer
|
|
20
|
+
* `result.data`).
|
|
21
|
+
*
|
|
22
|
+
* Env var requerida en el consumer: `RETAIL_CATALOG_BUSINESS_URL`.
|
|
23
|
+
* El template.yml del consumer la setea con:
|
|
24
|
+
*
|
|
25
|
+
* RETAIL_CATALOG_BUSINESS_URL: '{{resolve:ssm:retail-catalog-business}}'
|
|
26
|
+
*
|
|
27
|
+
* Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
|
|
28
|
+
*/
|
|
29
|
+
@injectable()
|
|
30
|
+
export default class RetailCatalogBusinessApi implements IRetailCatalogBusinessApi {
|
|
31
|
+
// El buildspec publica la URL en SSM con "/" final — se normaliza para no armar "//private".
|
|
32
|
+
private readonly baseUrl = (process.env.RETAIL_CATALOG_BUSINESS_URL || "").replace(/\/+$/, "");
|
|
33
|
+
|
|
34
|
+
constructor(@inject("IHttpRequest") private httpRequest: IHttpRequest) {}
|
|
35
|
+
|
|
36
|
+
private tenantHeader(issuer: string): Record<string, string> {
|
|
37
|
+
return { [TENANT_ISSUER_HEADER]: issuer };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getStoreCatalog(storeId: string, issuer: string): Promise<StandardResponse<StoreCatalogItem[]>> {
|
|
41
|
+
const url = `${this.baseUrl}/private/stores/${encodeURIComponent(storeId)}/catalog`;
|
|
42
|
+
return await this.httpRequest.get(url, undefined, this.tenantHeader(issuer));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async reserveInventory(
|
|
46
|
+
imei: string,
|
|
47
|
+
body: ReserveInventoryBody,
|
|
48
|
+
issuer: string,
|
|
49
|
+
): Promise<StandardResponse<ReserveInventoryResult>> {
|
|
50
|
+
const url = `${this.baseUrl}/private/inventory/${encodeURIComponent(imei)}/reserve`;
|
|
51
|
+
return await this.httpRequest.put(url, body, this.tenantHeader(issuer));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async sellInventory(imei: string, body: SellInventoryBody, issuer: string): Promise<StandardResponse<void>> {
|
|
55
|
+
const url = `${this.baseUrl}/private/inventory/${encodeURIComponent(imei)}/sell`;
|
|
56
|
+
return await this.httpRequest.put(url, body, this.tenantHeader(issuer));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async releaseInventory(imei: string, body: ReleaseReservationBody, issuer: string): Promise<StandardResponse<void>> {
|
|
60
|
+
const url = `${this.baseUrl}/private/inventory/${encodeURIComponent(imei)}/release`;
|
|
61
|
+
return await this.httpRequest.put(url, body, this.tenantHeader(issuer));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async returnInventory(imei: string, issuer: string): Promise<StandardResponse<void>> {
|
|
65
|
+
const url = `${this.baseUrl}/private/inventory/${encodeURIComponent(imei)}/return`;
|
|
66
|
+
return await this.httpRequest.put(url, undefined, this.tenantHeader(issuer));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { StandardResponse } from "@fiado/gateway-adapter";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Contrato del publisher HTTP del lambda `retail-catalog-business` (SureKeep Fase 1, pista Retail)
|
|
5
|
+
* para sus endpoints privados del ciclo de venta del IMEI, consumidos por el wizard F2.
|
|
6
|
+
*
|
|
7
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
* TENANT — header `x-tenant-issuer` (NO `?tenantId=`).
|
|
9
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
* A diferencia de customer/cards/loan-config (que resuelven el silo por `?tenantId=` canónico), este
|
|
11
|
+
* lambda resuelve el tenant por el ISSUER Cognito forwardeado en el header `x-tenant-issuer`
|
|
12
|
+
* (`PrivateController.issuerFromHeader` → `TenantContextService.withContext(issuer)`). Los privados son
|
|
13
|
+
* `Feature.ANONIMUS` (sin RBAC; la protección es la red VPC), así que el caller DEBE declarar el issuer.
|
|
14
|
+
*
|
|
15
|
+
* ⚠️ Retorno `StandardResponse<T>` — el consumer lee `result.data`, NO `result.body`.
|
|
16
|
+
* Errores: el destino lanza `DomainError` tipado → el publisher RECHAZA (no devuelve null); cada
|
|
17
|
+
* consumer mapea a su propio error de dominio.
|
|
18
|
+
*
|
|
19
|
+
* DTOs de catálogo/reserva: el lambda destino NO publica todavía estos shapes en `@fiado/type-kit`
|
|
20
|
+
* (`StoreCatalogItem`, `ReserveInventoryResult`), así que se declaran INLINE aquí (TD: migrar a
|
|
21
|
+
* type-kit cuando el contrato se estabilice). El shape espeja `StoreCatalogItemResponse` /
|
|
22
|
+
* `ReserveInventoryResponse` del `retail-catalog-business`.
|
|
23
|
+
*
|
|
24
|
+
* Env var requerida en el consumer: `RETAIL_CATALOG_BUSINESS_URL`.
|
|
25
|
+
* El template.yml del consumer la setea con:
|
|
26
|
+
*
|
|
27
|
+
* RETAIL_CATALOG_BUSINESS_URL: '{{resolve:ssm:retail-catalog-business}}'
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Ítem del catálogo vendible de un store (espeja `StoreCatalogItemResponse` del destino). */
|
|
31
|
+
export interface StoreCatalogItem {
|
|
32
|
+
sku: string;
|
|
33
|
+
imei: string;
|
|
34
|
+
cashPriceCents: number;
|
|
35
|
+
available: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Resultado de la reserva (espeja `ReserveInventoryResponse` del destino). */
|
|
39
|
+
export interface ReserveInventoryResult {
|
|
40
|
+
reservationExpiresAt: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Body de PUT `/private/inventory/{imei}/reserve`. */
|
|
44
|
+
export interface ReserveInventoryBody {
|
|
45
|
+
sku: string;
|
|
46
|
+
sessionId: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Body de PUT `/private/inventory/{imei}/sell` (DEC-020 del destino: `customerId` puebla `soldToCustomerId`). */
|
|
50
|
+
export interface SellInventoryBody {
|
|
51
|
+
sessionId: string;
|
|
52
|
+
customerId: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Body de PUT `/private/inventory/{imei}/release` (DEC-018 del destino: verifica titularidad de la sesión). */
|
|
56
|
+
export interface ReleaseReservationBody {
|
|
57
|
+
sessionId: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface IRetailCatalogBusinessApi {
|
|
61
|
+
/** GET `/private/stores/{storeId}/catalog` — catálogo vendible (sku, imei, precio de contado, disponibilidad). */
|
|
62
|
+
getStoreCatalog(storeId: string, issuer: string): Promise<StandardResponse<StoreCatalogItem[]>>;
|
|
63
|
+
|
|
64
|
+
/** PUT `/private/inventory/{imei}/reserve` — reserva atómica del IMEI para una sesión (AVAILABLE → RESERVED). */
|
|
65
|
+
reserveInventory(imei: string, body: ReserveInventoryBody, issuer: string): Promise<StandardResponse<ReserveInventoryResult>>;
|
|
66
|
+
|
|
67
|
+
/** PUT `/private/inventory/{imei}/sell` — vende el IMEI (RESERVED → SOLD). */
|
|
68
|
+
sellInventory(imei: string, body: SellInventoryBody, issuer: string): Promise<StandardResponse<void>>;
|
|
69
|
+
|
|
70
|
+
/** PUT `/private/inventory/{imei}/release` — libera la reserva (RESERVED → AVAILABLE, titularidad de la sesión). */
|
|
71
|
+
releaseInventory(imei: string, body: ReleaseReservationBody, issuer: string): Promise<StandardResponse<void>>;
|
|
72
|
+
|
|
73
|
+
/** PUT `/private/inventory/{imei}/return` — devuelve el IMEI vendido al stock (SOLD → AVAILABLE, sin body). */
|
|
74
|
+
returnInventory(imei: string, issuer: string): Promise<StandardResponse<void>>;
|
|
75
|
+
}
|