@fiado/api-invoker 5.115.0 → 5.117.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/container.config.js +14 -0
- package/bin/datalake-reports-business/api/DatalakeReportsApi.d.ts +13 -0
- package/bin/datalake-reports-business/api/DatalakeReportsApi.js +39 -0
- package/bin/datalake-reports-business/api/interfaces/IDatalakeReportsApi.d.ts +58 -0
- package/bin/datalake-reports-business/api/interfaces/IDatalakeReportsApi.js +1 -0
- package/bin/datalake-reports-business/index.d.ts +2 -0
- package/bin/datalake-reports-business/index.js +2 -0
- package/bin/index.d.ts +3 -0
- package/bin/index.js +3 -0
- package/bin/rewards-business/api/RewardsBusinessApi.d.ts +12 -0
- package/bin/rewards-business/api/RewardsBusinessApi.js +38 -0
- package/bin/rewards-business/api/interfaces/IRewardsBusinessApi.d.ts +33 -0
- package/bin/rewards-business/api/interfaces/IRewardsBusinessApi.js +1 -0
- package/bin/rewards-business/grantIdFor.d.ts +17 -0
- package/bin/rewards-business/grantIdFor.js +30 -0
- package/bin/rewards-business/index.d.ts +5 -0
- package/bin/rewards-business/index.js +5 -0
- package/bin/rewards-business/queue/RewardsGrantPublisher.d.ts +7 -0
- package/bin/rewards-business/queue/RewardsGrantPublisher.js +83 -0
- package/bin/rewards-business/queue/interfaces/IRewardsGrantPublisher.d.ts +32 -0
- package/bin/rewards-business/queue/interfaces/IRewardsGrantPublisher.js +1 -0
- package/bin/rewards-eligibility-business/api/RewardsEligibilityBusinessApi.d.ts +12 -0
- package/bin/rewards-eligibility-business/api/RewardsEligibilityBusinessApi.js +37 -0
- package/bin/rewards-eligibility-business/api/interfaces/IRewardsEligibilityBusinessApi.d.ts +34 -0
- package/bin/rewards-eligibility-business/api/interfaces/IRewardsEligibilityBusinessApi.js +1 -0
- package/bin/rewards-eligibility-business/index.d.ts +4 -0
- package/bin/rewards-eligibility-business/index.js +4 -0
- package/bin/rewards-eligibility-business/queue/RewardsEligibilityPublisher.d.ts +6 -0
- package/bin/rewards-eligibility-business/queue/RewardsEligibilityPublisher.js +34 -0
- package/bin/rewards-eligibility-business/queue/interfaces/IRewardsEligibilityPublisher.d.ts +15 -0
- package/bin/rewards-eligibility-business/queue/interfaces/IRewardsEligibilityPublisher.js +1 -0
- package/package.json +2 -2
- package/src/container.config.ts +17 -0
- package/src/datalake-reports-business/api/DatalakeReportsApi.ts +30 -0
- package/src/datalake-reports-business/api/interfaces/IDatalakeReportsApi.ts +27 -0
- package/src/datalake-reports-business/index.ts +2 -0
- package/src/index.ts +3 -0
- package/src/rewards-business/api/RewardsBusinessApi.ts +27 -0
- package/src/rewards-business/api/interfaces/IRewardsBusinessApi.ts +35 -0
- package/src/rewards-business/grantIdFor.ts +36 -0
- package/src/rewards-business/index.ts +5 -0
- package/src/rewards-business/queue/RewardsGrantPublisher.ts +80 -0
- package/src/rewards-business/queue/interfaces/IRewardsGrantPublisher.ts +35 -0
- package/src/rewards-eligibility-business/api/RewardsEligibilityBusinessApi.ts +26 -0
- package/src/rewards-eligibility-business/api/interfaces/IRewardsEligibilityBusinessApi.ts +36 -0
- package/src/rewards-eligibility-business/index.ts +4 -0
- package/src/rewards-eligibility-business/queue/RewardsEligibilityPublisher.ts +28 -0
- package/src/rewards-eligibility-business/queue/interfaces/IRewardsEligibilityPublisher.ts +16 -0
package/bin/container.config.js
CHANGED
|
@@ -74,6 +74,7 @@ import { EventHistoryApi } from "./event-history-business/index.js";
|
|
|
74
74
|
import ReportProcessorBusinessApi from "./report-processor-business/api/ReportProcessorBusinessApi.js";
|
|
75
75
|
import NotificationWSMessagePublisher from "./notificationWebsockets/queue/NotificationWSMessagePublisher.js";
|
|
76
76
|
import PeopleBusinessApi from "./people-business/api/PeopleBusinessApi.js";
|
|
77
|
+
import DatalakeReportsApi from "./datalake-reports-business/api/DatalakeReportsApi.js";
|
|
77
78
|
import PlacesBusinessApi from "./places-business/api/PlacesBusinessApi.js";
|
|
78
79
|
import STPBusinessApi from "./stp-business/api/STPBusinessApi.js";
|
|
79
80
|
import MulticommServicePaymentApi from "./multicommServicePayment/api/MulticommServicePaymentApi.js";
|
|
@@ -139,6 +140,10 @@ import { KycVerificationsBusinessApi } from "./kyc/index.js";
|
|
|
139
140
|
// Webhook business — publisher de la cola compartida de entrega de eventos a suscriptores externos
|
|
140
141
|
import { WebhookBusinessPublisher } from "./webhook-business/index.js";
|
|
141
142
|
import { CentralPaymentsWebhookApi } from "./central-payments-webhook/index.js";
|
|
143
|
+
// Rewards — publisher de pedidos de entrega + cliente de lecturas privadas (rewards-business) y
|
|
144
|
+
// publisher de GRANT_FAILED + cliente del validador (rewards-eligibility-business)
|
|
145
|
+
import { RewardsGrantPublisher, RewardsBusinessApi } from "./rewards-business/index.js";
|
|
146
|
+
import { RewardsEligibilityPublisher, RewardsEligibilityBusinessApi } from "./rewards-eligibility-business/index.js";
|
|
142
147
|
import CollectionEngineApi from "./collection-engine/api/CollectionEngineApi.js";
|
|
143
148
|
import DomainCallbackApi from "./domain-callback/api/DomainCallbackApi.js";
|
|
144
149
|
export const apiInvokerBindings = new ContainerModule(({ bind }) => {
|
|
@@ -221,6 +226,7 @@ export const apiInvokerBindings = new ContainerModule(({ bind }) => {
|
|
|
221
226
|
bind("IEventHistoryApi").to(EventHistoryApi);
|
|
222
227
|
bind("IReportProcessorBusinessApi").to(ReportProcessorBusinessApi);
|
|
223
228
|
bind("IPeopleBusinessApi").to(PeopleBusinessApi);
|
|
229
|
+
bind("IDatalakeReportsApi").to(DatalakeReportsApi);
|
|
224
230
|
bind("IPlacesBusinessApi").to(PlacesBusinessApi);
|
|
225
231
|
bind("ISTPBusinessApi").to(STPBusinessApi);
|
|
226
232
|
bind("IProductPaymentApi").to(ProductPaymentApi);
|
|
@@ -298,6 +304,14 @@ export const apiInvokerBindings = new ContainerModule(({ bind }) => {
|
|
|
298
304
|
// El productor necesita WEBHOOK_BUSINESS_QUEUE_URL y sqs:SendMessage sobre la cola.
|
|
299
305
|
bind("IWebhookBusinessPublisher").to(WebhookBusinessPublisher);
|
|
300
306
|
bind("ICentralPaymentsWebhookApi").to(CentralPaymentsWebhookApi);
|
|
307
|
+
// Rewards — cola compartida RewardsGrantQueue (pedidos de entrega) y lecturas privadas de rewards-business.
|
|
308
|
+
// El productor necesita REWARDS_GRANT_QUEUE_URL y sqs:SendMessage sobre la cola.
|
|
309
|
+
bind("IRewardsGrantPublisher").to(RewardsGrantPublisher);
|
|
310
|
+
bind("IRewardsBusinessApi").to(RewardsBusinessApi);
|
|
311
|
+
// Rewards eligibility — GRANT_FAILED hacia la cola del validador y cliente de POST /events.
|
|
312
|
+
// El productor necesita REWARDS_ELIGIBILITY_QUEUE_URL y sqs:SendMessage sobre la cola.
|
|
313
|
+
bind("IRewardsEligibilityPublisher").to(RewardsEligibilityPublisher);
|
|
314
|
+
bind("IRewardsEligibilityBusinessApi").to(RewardsEligibilityBusinessApi);
|
|
301
315
|
// Collection engine — motor de cobro central multitenant (collect/deregister)
|
|
302
316
|
bind("ICollectionEngineApi").to(CollectionEngineApi);
|
|
303
317
|
// Domain callback — el motor notifica al dominio dueño del cargo (applyCollectionResult)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
2
|
+
import { DatalakeReportDefinition, DatalakeReportResult, IDatalakeReportsApi } from "./interfaces/IDatalakeReportsApi.js";
|
|
3
|
+
type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
|
|
4
|
+
export default class DatalakeReportsApi implements IDatalakeReportsApi {
|
|
5
|
+
private httpRequest;
|
|
6
|
+
private readonly baseUrl;
|
|
7
|
+
constructor(httpRequest: IHttpRequest);
|
|
8
|
+
listReports(): Promise<FiadoApiResponse<DatalakeReportDefinition[]>>;
|
|
9
|
+
getReport(id: string): Promise<FiadoApiResponse<DatalakeReportDefinition>>;
|
|
10
|
+
runReport(id: string, params: Record<string, unknown>): Promise<FiadoApiResponse<DatalakeReportResult>>;
|
|
11
|
+
getJob(jobId: string, nextToken?: string): Promise<FiadoApiResponse<DatalakeReportResult>>;
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
let DatalakeReportsApi = class DatalakeReportsApi {
|
|
15
|
+
httpRequest;
|
|
16
|
+
baseUrl = process.env.DATALAKE_REPORTS_BUSINESS_LAMBDA_URL || "";
|
|
17
|
+
constructor(httpRequest) {
|
|
18
|
+
this.httpRequest = httpRequest;
|
|
19
|
+
}
|
|
20
|
+
listReports() {
|
|
21
|
+
return this.httpRequest.get(`${this.baseUrl}private/reports`);
|
|
22
|
+
}
|
|
23
|
+
getReport(id) {
|
|
24
|
+
return this.httpRequest.get(`${this.baseUrl}private/reports/${encodeURIComponent(id)}`);
|
|
25
|
+
}
|
|
26
|
+
runReport(id, params) {
|
|
27
|
+
return this.httpRequest.post(`${this.baseUrl}private/reports/${encodeURIComponent(id)}/run`, { params });
|
|
28
|
+
}
|
|
29
|
+
getJob(jobId, nextToken) {
|
|
30
|
+
const qs = nextToken ? `?nextToken=${encodeURIComponent(nextToken)}` : "";
|
|
31
|
+
return this.httpRequest.get(`${this.baseUrl}private/reports/jobs/${encodeURIComponent(jobId)}${qs}`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
DatalakeReportsApi = __decorate([
|
|
35
|
+
injectable(),
|
|
36
|
+
__param(0, inject("IHttpRequest")),
|
|
37
|
+
__metadata("design:paramtypes", [Object])
|
|
38
|
+
], DatalakeReportsApi);
|
|
39
|
+
export default DatalakeReportsApi;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
|
|
2
|
+
/** Contrato local hasta que @fiado/type-kit publique el dominio DatalakeReports (misma forma). */
|
|
3
|
+
export interface DatalakeReportDefinition {
|
|
4
|
+
id: string;
|
|
5
|
+
version: number;
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
source: {
|
|
9
|
+
layer: "gold" | "silver";
|
|
10
|
+
databases: string[];
|
|
11
|
+
};
|
|
12
|
+
pii: boolean;
|
|
13
|
+
audiences: string[];
|
|
14
|
+
permission: string;
|
|
15
|
+
mode: "sync" | "async";
|
|
16
|
+
cacheSeconds: number;
|
|
17
|
+
params: Record<string, {
|
|
18
|
+
type: string;
|
|
19
|
+
required?: boolean;
|
|
20
|
+
default?: unknown;
|
|
21
|
+
values?: string[];
|
|
22
|
+
max?: number;
|
|
23
|
+
min?: number;
|
|
24
|
+
}>;
|
|
25
|
+
principalParams: string[];
|
|
26
|
+
columns: Array<{
|
|
27
|
+
name: string;
|
|
28
|
+
type: string;
|
|
29
|
+
label: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export interface DatalakeReportResult {
|
|
33
|
+
status: "RUNNING" | "DONE" | "FAILED";
|
|
34
|
+
reportId?: string;
|
|
35
|
+
version?: number;
|
|
36
|
+
jobId: string;
|
|
37
|
+
columns?: Array<{
|
|
38
|
+
name: string;
|
|
39
|
+
type: string;
|
|
40
|
+
label: string;
|
|
41
|
+
}>;
|
|
42
|
+
rows?: Array<Record<string, string | number | boolean | null>>;
|
|
43
|
+
rowCount?: number;
|
|
44
|
+
nextToken?: string;
|
|
45
|
+
computedAt?: string;
|
|
46
|
+
fromCache?: boolean;
|
|
47
|
+
bytesScanned?: number;
|
|
48
|
+
reason?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Canal privado de datalake-reports-business (reportes predefinidos sobre Gold y Silver). */
|
|
51
|
+
export interface IDatalakeReportsApi {
|
|
52
|
+
listReports(): Promise<FiadoApiResponse<DatalakeReportDefinition[]>>;
|
|
53
|
+
getReport(id: string): Promise<FiadoApiResponse<DatalakeReportDefinition>>;
|
|
54
|
+
/** 200 con filas, o 200 con status RUNNING y jobId si la corrida cae a asíncrono. */
|
|
55
|
+
runReport(id: string, params: Record<string, unknown>): Promise<FiadoApiResponse<DatalakeReportResult>>;
|
|
56
|
+
getJob(jobId: string, nextToken?: string): Promise<FiadoApiResponse<DatalakeReportResult>>;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/bin/index.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ export * from "./event-history-business/index.js";
|
|
|
65
65
|
export * from "./report-processor-business/index.js";
|
|
66
66
|
export * from "./notificationWebsockets/index.js";
|
|
67
67
|
export * from "./people-business/index.js";
|
|
68
|
+
export * from "./datalake-reports-business/index.js";
|
|
68
69
|
export * from "./places-business/index.js";
|
|
69
70
|
export * from "./stp-business/index.js";
|
|
70
71
|
export * from "./multicommServicePayment/index.js";
|
|
@@ -122,3 +123,5 @@ export * from "./domain-callback/index.js";
|
|
|
122
123
|
export * from "./datacultrConnector/index.js";
|
|
123
124
|
export * from "./webhook-business/index.js";
|
|
124
125
|
export * from "./trustonicConnector/index.js";
|
|
126
|
+
export * from "./rewards-business/index.js";
|
|
127
|
+
export * from "./rewards-eligibility-business/index.js";
|
package/bin/index.js
CHANGED
|
@@ -65,6 +65,7 @@ export * from "./event-history-business/index.js";
|
|
|
65
65
|
export * from "./report-processor-business/index.js";
|
|
66
66
|
export * from "./notificationWebsockets/index.js";
|
|
67
67
|
export * from "./people-business/index.js";
|
|
68
|
+
export * from "./datalake-reports-business/index.js";
|
|
68
69
|
export * from "./places-business/index.js";
|
|
69
70
|
export * from "./stp-business/index.js";
|
|
70
71
|
export * from "./multicommServicePayment/index.js";
|
|
@@ -122,3 +123,5 @@ export * from "./domain-callback/index.js";
|
|
|
122
123
|
export * from "./datacultrConnector/index.js";
|
|
123
124
|
export * from "./webhook-business/index.js";
|
|
124
125
|
export * from "./trustonicConnector/index.js";
|
|
126
|
+
export * from "./rewards-business/index.js";
|
|
127
|
+
export * from "./rewards-eligibility-business/index.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StandardResponse } from "@fiado/gateway-adapter";
|
|
2
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
3
|
+
import type { GrantResponse, PackageContractResponse } from "@fiado/type-kit/bin/rewards/index.js";
|
|
4
|
+
import type { IRewardsBusinessApi } from "./interfaces/IRewardsBusinessApi.js";
|
|
5
|
+
/** Contrato y semántica completos → `IRewardsBusinessApi` (`StandardResponse` → leer `result.data`). */
|
|
6
|
+
export declare class RewardsBusinessApi implements IRewardsBusinessApi {
|
|
7
|
+
private httpRequest;
|
|
8
|
+
private readonly baseUrl;
|
|
9
|
+
constructor(httpRequest: IHttpRequest);
|
|
10
|
+
getPackage(packageId: string): Promise<StandardResponse<PackageContractResponse>>;
|
|
11
|
+
getGrant(grantId: string): Promise<StandardResponse<GrantResponse>>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
/** Contrato y semántica completos → `IRewardsBusinessApi` (`StandardResponse` → leer `result.data`). */
|
|
15
|
+
let RewardsBusinessApi = class RewardsBusinessApi {
|
|
16
|
+
httpRequest;
|
|
17
|
+
// El buildspec publica la URL en SSM con "/" final (`.../v1/`): se normaliza para no armar "//packages".
|
|
18
|
+
baseUrl = (process.env.REWARDS_BUSINESS_LAMBDA_URL || "").replace(/\/+$/, "");
|
|
19
|
+
constructor(httpRequest) {
|
|
20
|
+
this.httpRequest = httpRequest;
|
|
21
|
+
}
|
|
22
|
+
async getPackage(packageId) {
|
|
23
|
+
// PROVISIONAL: sin prefijo `/private` (la API que resuelve el SSM es la PRIVADA de rewards-business).
|
|
24
|
+
// La ruta base exacta (con o sin `/private`) la fija el plan de rewards-business; si cambia, ajustar aquí.
|
|
25
|
+
const url = `${this.baseUrl}/packages/${encodeURIComponent(packageId)}`;
|
|
26
|
+
return await this.httpRequest.get(url);
|
|
27
|
+
}
|
|
28
|
+
async getGrant(grantId) {
|
|
29
|
+
const url = `${this.baseUrl}/grants/${encodeURIComponent(grantId)}`;
|
|
30
|
+
return await this.httpRequest.get(url);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
RewardsBusinessApi = __decorate([
|
|
34
|
+
injectable(),
|
|
35
|
+
__param(0, inject("IHttpRequest")),
|
|
36
|
+
__metadata("design:paramtypes", [Object])
|
|
37
|
+
], RewardsBusinessApi);
|
|
38
|
+
export { RewardsBusinessApi };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { StandardResponse } from "@fiado/gateway-adapter";
|
|
2
|
+
import type { GrantResponse, PackageContractResponse } from "@fiado/type-kit/bin/rewards/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Cliente de la API privada de `rewards-business`. SOLO LECTURA: no hay endpoint privado de
|
|
5
|
+
* escritura (spec 2026-10-05, D11); los pedidos de entrega se publican con `IRewardsGrantPublisher`.
|
|
6
|
+
*
|
|
7
|
+
* Devuelve `StandardResponse<T>` → leer `result.data`. NO uses `FiadoApiResponse`: los dos tipos no
|
|
8
|
+
* son intercambiables en TypeScript (`code`/`date` admiten `null` en uno y no en el otro).
|
|
9
|
+
*
|
|
10
|
+
* Env var requerida en el consumer: `REWARDS_BUSINESS_LAMBDA_URL`. El template.yml del consumer la
|
|
11
|
+
* setea con:
|
|
12
|
+
*
|
|
13
|
+
* REWARDS_BUSINESS_LAMBDA_URL: '{{resolve:ssm:fiado-rewardsbusiness-lambda}}'
|
|
14
|
+
*
|
|
15
|
+
* El nombre del parámetro SSM es el `stack_name` del `samconfig.toml` de rewards-business (el
|
|
16
|
+
* buildspec registra el SSM con ese nombre): debe coincidir con el de arriba.
|
|
17
|
+
*/
|
|
18
|
+
export interface IRewardsBusinessApi {
|
|
19
|
+
/**
|
|
20
|
+
* Contrato de entrada de un paquete: por `itemKey`, las `dependencies` de su recompensa, más
|
|
21
|
+
* nombre, descripción, costo y `totalCost` por moneda. El backoffice del validador lo usa para
|
|
22
|
+
* no dejar activar una evaluación con un paquete inactivo o con inputs que no cubren el contrato.
|
|
23
|
+
*
|
|
24
|
+
* @throws propaga el error del destino (404 `PACKAGE_NOT_FOUND`); nunca devuelve fallback.
|
|
25
|
+
*/
|
|
26
|
+
getPackage(packageId: string): Promise<StandardResponse<PackageContractResponse>>;
|
|
27
|
+
/**
|
|
28
|
+
* Estado de una entrega y de cada ítem. El `grantId` se calcula con `grantIdFor`.
|
|
29
|
+
*
|
|
30
|
+
* @throws propaga el error del destino (404 `GRANT_NOT_FOUND`); nunca devuelve fallback.
|
|
31
|
+
*/
|
|
32
|
+
getGrant(grantId: string): Promise<StandardResponse<GrantResponse>>;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Largo (hex) del `grantId`. 128 bits: colisión despreciable. */
|
|
2
|
+
export declare const REWARDS_GRANT_ID_LENGTH = 32;
|
|
3
|
+
/** Límite de `BenefitPaymentRequest.transactionNumber`. */
|
|
4
|
+
export declare const REWARDS_TRANSACTION_NUMBER_MAX_LENGTH = 64;
|
|
5
|
+
/**
|
|
6
|
+
* Largo máximo del `itemKey` de un paquete para que `RW-{grantId}-{itemKey}` quepa en 64:
|
|
7
|
+
* 64 − len('RW-') − len('-') − 32 = 28. rewards-business debe validarlo al guardar un paquete.
|
|
8
|
+
*/
|
|
9
|
+
export declare const REWARDS_MAX_ITEM_KEY_LENGTH: number;
|
|
10
|
+
/**
|
|
11
|
+
* `grantId` determinístico de una entrega de rewards (spec A 5.3, D11). Lo calcula el llamador al
|
|
12
|
+
* publicar `GRANT_REQUESTED` (para conocerlo sin esperar respuesta) y el worker de rewards al
|
|
13
|
+
* consumirlo: tienen que dar lo mismo, por eso es UNA función compartida.
|
|
14
|
+
*
|
|
15
|
+
* El largo de `service` va como prefijo para que ('a:b','c') y ('a','b:c') no colisionen.
|
|
16
|
+
*/
|
|
17
|
+
export declare function grantIdFor(service: string, idempotencyKey: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/** Largo (hex) del `grantId`. 128 bits: colisión despreciable. */
|
|
3
|
+
export const REWARDS_GRANT_ID_LENGTH = 32;
|
|
4
|
+
/** Límite de `BenefitPaymentRequest.transactionNumber`. */
|
|
5
|
+
export const REWARDS_TRANSACTION_NUMBER_MAX_LENGTH = 64;
|
|
6
|
+
const TRANSACTION_NUMBER_PREFIX = 'RW-';
|
|
7
|
+
/**
|
|
8
|
+
* Largo máximo del `itemKey` de un paquete para que `RW-{grantId}-{itemKey}` quepa en 64:
|
|
9
|
+
* 64 − len('RW-') − len('-') − 32 = 28. rewards-business debe validarlo al guardar un paquete.
|
|
10
|
+
*/
|
|
11
|
+
export const REWARDS_MAX_ITEM_KEY_LENGTH = REWARDS_TRANSACTION_NUMBER_MAX_LENGTH - TRANSACTION_NUMBER_PREFIX.length - 1 - REWARDS_GRANT_ID_LENGTH;
|
|
12
|
+
/**
|
|
13
|
+
* `grantId` determinístico de una entrega de rewards (spec A 5.3, D11). Lo calcula el llamador al
|
|
14
|
+
* publicar `GRANT_REQUESTED` (para conocerlo sin esperar respuesta) y el worker de rewards al
|
|
15
|
+
* consumirlo: tienen que dar lo mismo, por eso es UNA función compartida.
|
|
16
|
+
*
|
|
17
|
+
* El largo de `service` va como prefijo para que ('a:b','c') y ('a','b:c') no colisionen.
|
|
18
|
+
*/
|
|
19
|
+
export function grantIdFor(service, idempotencyKey) {
|
|
20
|
+
if (!service) {
|
|
21
|
+
throw new Error('grantIdFor: service es obligatorio');
|
|
22
|
+
}
|
|
23
|
+
if (!idempotencyKey) {
|
|
24
|
+
throw new Error('grantIdFor: idempotencyKey es obligatoria');
|
|
25
|
+
}
|
|
26
|
+
return createHash('sha256')
|
|
27
|
+
.update(`${service.length}:${service}:${idempotencyKey}`)
|
|
28
|
+
.digest('hex')
|
|
29
|
+
.slice(0, REWARDS_GRANT_ID_LENGTH);
|
|
30
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { GrantRequestedMessage } from "@fiado/type-kit/bin/rewards/index.js";
|
|
2
|
+
import { IRewardsGrantPublisher, RewardsGrantBatchResult } from "./interfaces/IRewardsGrantPublisher.js";
|
|
3
|
+
export declare class RewardsGrantPublisher implements IRewardsGrantPublisher {
|
|
4
|
+
private readonly REWARDS_GRANT_QUEUE_URL;
|
|
5
|
+
publish(message: GrantRequestedMessage): Promise<void>;
|
|
6
|
+
publishBatch(messages: GrantRequestedMessage[]): Promise<RewardsGrantBatchResult>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
import { SendMessageBatchCommand, SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs";
|
|
8
|
+
import { injectable } from "inversify";
|
|
9
|
+
const SQS_BATCH_MAX_ENTRIES = 10;
|
|
10
|
+
// Límite SQS: 262 144 bytes por SendMessageBatch. Margen para no rozarlo.
|
|
11
|
+
const SQS_BATCH_MAX_BYTES = 250_000;
|
|
12
|
+
let RewardsGrantPublisher = class RewardsGrantPublisher {
|
|
13
|
+
REWARDS_GRANT_QUEUE_URL = process.env.REWARDS_GRANT_QUEUE_URL;
|
|
14
|
+
async publish(message) {
|
|
15
|
+
if (!this.REWARDS_GRANT_QUEUE_URL) {
|
|
16
|
+
throw new Error("Error publishing message to RewardsGrantQueue: REWARDS_GRANT_QUEUE_URL no está configurada");
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const client = new SQSClient();
|
|
20
|
+
// Cola ESTÁNDAR (no FIFO): sin MessageGroupId/MessageDeduplicationId ni orden garantizado.
|
|
21
|
+
// La idempotencia la hace el consumidor con el `grantId` derivado de (source.service +
|
|
22
|
+
// idempotencyKey) y un PutItem condicional.
|
|
23
|
+
const sendMessageRequest = {
|
|
24
|
+
QueueUrl: this.REWARDS_GRANT_QUEUE_URL,
|
|
25
|
+
MessageBody: JSON.stringify(message),
|
|
26
|
+
};
|
|
27
|
+
await client.send(new SendMessageCommand(sendMessageRequest));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
31
|
+
throw new Error(`Error publishing message to RewardsGrantQueue: ${errorMessage}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async publishBatch(messages) {
|
|
35
|
+
const failedIdempotencyKeys = [];
|
|
36
|
+
const senderFaultIdempotencyKeys = [];
|
|
37
|
+
if (messages.length === 0) {
|
|
38
|
+
return { failedIdempotencyKeys, senderFaultIdempotencyKeys };
|
|
39
|
+
}
|
|
40
|
+
if (!this.REWARDS_GRANT_QUEUE_URL) {
|
|
41
|
+
throw new Error("Error publishing batch to RewardsGrantQueue: REWARDS_GRANT_QUEUE_URL no está configurada");
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const client = new SQSClient();
|
|
45
|
+
const keyByEntryId = new Map();
|
|
46
|
+
let current = [];
|
|
47
|
+
let currentBytes = 0;
|
|
48
|
+
const flush = async () => {
|
|
49
|
+
if (current.length === 0)
|
|
50
|
+
return;
|
|
51
|
+
const result = await client.send(new SendMessageBatchCommand({ QueueUrl: this.REWARDS_GRANT_QUEUE_URL, Entries: current }));
|
|
52
|
+
for (const failed of result.Failed ?? []) {
|
|
53
|
+
const key = failed.Id ? keyByEntryId.get(failed.Id) : undefined;
|
|
54
|
+
if (key)
|
|
55
|
+
(failed.SenderFault ? senderFaultIdempotencyKeys : failedIdempotencyKeys).push(key);
|
|
56
|
+
}
|
|
57
|
+
current = [];
|
|
58
|
+
currentBytes = 0;
|
|
59
|
+
};
|
|
60
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
61
|
+
const body = JSON.stringify(messages[index]);
|
|
62
|
+
const bytes = Buffer.byteLength(body, "utf8");
|
|
63
|
+
if (current.length >= SQS_BATCH_MAX_ENTRIES || (current.length > 0 && currentBytes + bytes > SQS_BATCH_MAX_BYTES)) {
|
|
64
|
+
await flush();
|
|
65
|
+
}
|
|
66
|
+
const id = `m${index}`;
|
|
67
|
+
keyByEntryId.set(id, messages[index].idempotencyKey);
|
|
68
|
+
current.push({ Id: id, MessageBody: body });
|
|
69
|
+
currentBytes += bytes;
|
|
70
|
+
}
|
|
71
|
+
await flush();
|
|
72
|
+
return { failedIdempotencyKeys, senderFaultIdempotencyKeys };
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
76
|
+
throw new Error(`Error publishing batch to RewardsGrantQueue: ${errorMessage}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
RewardsGrantPublisher = __decorate([
|
|
81
|
+
injectable()
|
|
82
|
+
], RewardsGrantPublisher);
|
|
83
|
+
export { RewardsGrantPublisher };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { GrantRequestedMessage } from "@fiado/type-kit/bin/rewards/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resultado de `publishBatch`: las `idempotencyKey` que SQS NO aceptó.
|
|
4
|
+
* - `failedIdempotencyKeys`: `SenderFault=false` (falla transitoria de SQS) → el llamador PUEDE reintentar.
|
|
5
|
+
* - `senderFaultIdempotencyKeys`: `SenderFault=true` (el mensaje es inválido para SQS) → NO reintentar:
|
|
6
|
+
* volver a mandarlo falla igual.
|
|
7
|
+
*/
|
|
8
|
+
export interface RewardsGrantBatchResult {
|
|
9
|
+
failedIdempotencyKeys: string[];
|
|
10
|
+
senderFaultIdempotencyKeys: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Publisher de pedidos de entrega hacia `RewardsGrantQueue`, que consume `rewards-business`
|
|
14
|
+
* (spec 2026-10-05, D11). La cola es COMPARTIDA entre lambdas: el estándar Fiado publica a una cola
|
|
15
|
+
* compartida con un publisher de `@fiado/api-invoker` (mismo patrón que `ITransactionPublisher`).
|
|
16
|
+
*
|
|
17
|
+
* Que `publish` funcione ES la aceptación del pedido: no hay respuesta síncrona. El `grantId` no
|
|
18
|
+
* viaja en el mensaje; el llamador lo conoce con `grantIdFor(source.service, idempotencyKey)`.
|
|
19
|
+
*
|
|
20
|
+
* Env var requerida en el consumer: `REWARDS_GRANT_QUEUE_URL`
|
|
21
|
+
* (`!ImportValue SqsRewardsGrantQueueUrl`) y el permiso `sqs:SendMessage` sobre la cola.
|
|
22
|
+
*/
|
|
23
|
+
export interface IRewardsGrantPublisher {
|
|
24
|
+
publish(message: GrantRequestedMessage): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Publica varios pedidos con `SendMessageBatch` (tandas de 10 y de ≤ 250 000 bytes). El validador
|
|
27
|
+
* suelta lo masivo por tramos con esto (spec B 7.1 y 7.6). Un fallo parcial NO lanza: se devuelve
|
|
28
|
+
* en `failedIdempotencyKeys` (reintentables) o `senderFaultIdempotencyKeys` (NO reintentar). Si la
|
|
29
|
+
* llamada entera falla, lanza (reintento idempotente por `grantId`).
|
|
30
|
+
*/
|
|
31
|
+
publishBatch(messages: GrantRequestedMessage[]): Promise<RewardsGrantBatchResult>;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StandardResponse } from "@fiado/gateway-adapter";
|
|
2
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
3
|
+
import type { GetRunResponse, PostEventRequest, PostEventResponse } from "@fiado/type-kit/bin/rewardsEligibility/index.js";
|
|
4
|
+
import type { IRewardsEligibilityBusinessApi } from "./interfaces/IRewardsEligibilityBusinessApi.js";
|
|
5
|
+
/** Contrato y semántica completos → `IRewardsEligibilityBusinessApi` (`StandardResponse` → leer `result.data`). */
|
|
6
|
+
export declare class RewardsEligibilityBusinessApi implements IRewardsEligibilityBusinessApi {
|
|
7
|
+
private httpRequest;
|
|
8
|
+
private readonly baseUrl;
|
|
9
|
+
constructor(httpRequest: IHttpRequest);
|
|
10
|
+
postEvent(request: PostEventRequest): Promise<StandardResponse<PostEventResponse>>;
|
|
11
|
+
getRun(runId: string): Promise<StandardResponse<GetRunResponse>>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
/** Contrato y semántica completos → `IRewardsEligibilityBusinessApi` (`StandardResponse` → leer `result.data`). */
|
|
15
|
+
let RewardsEligibilityBusinessApi = class RewardsEligibilityBusinessApi {
|
|
16
|
+
httpRequest;
|
|
17
|
+
// El buildspec publica la URL en SSM con "/" final (`.../v1/`): se normaliza para no armar "//events".
|
|
18
|
+
baseUrl = (process.env.REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL || "").replace(/\/+$/, "");
|
|
19
|
+
constructor(httpRequest) {
|
|
20
|
+
this.httpRequest = httpRequest;
|
|
21
|
+
}
|
|
22
|
+
async postEvent(request) {
|
|
23
|
+
// PROVISIONAL: sin prefijo `/private` (la API que resuelve el SSM es la PRIVADA).
|
|
24
|
+
// La ruta base exacta (con o sin `/private`) la fija el plan del validador; si cambia, ajustar aquí.
|
|
25
|
+
return await this.httpRequest.post(`${this.baseUrl}/events`, request);
|
|
26
|
+
}
|
|
27
|
+
async getRun(runId) {
|
|
28
|
+
const url = `${this.baseUrl}/runs/${encodeURIComponent(runId)}`;
|
|
29
|
+
return await this.httpRequest.get(url);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
RewardsEligibilityBusinessApi = __decorate([
|
|
33
|
+
injectable(),
|
|
34
|
+
__param(0, inject("IHttpRequest")),
|
|
35
|
+
__metadata("design:paramtypes", [Object])
|
|
36
|
+
], RewardsEligibilityBusinessApi);
|
|
37
|
+
export { RewardsEligibilityBusinessApi };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { StandardResponse } from "@fiado/gateway-adapter";
|
|
2
|
+
import type { GetRunResponse, PostEventRequest, PostEventResponse } from "@fiado/type-kit/bin/rewardsEligibility/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Cliente de la API privada de `rewards-eligibility-business` (spec 2026-10-06, §6).
|
|
5
|
+
*
|
|
6
|
+
* Devuelve `StandardResponse<T>` → leer `result.data`. Env var requerida en el consumer:
|
|
7
|
+
* `REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL`, seteada con
|
|
8
|
+
*
|
|
9
|
+
* REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL: '{{resolve:ssm:fiado-rewardseligibilitybusiness-lambda}}'
|
|
10
|
+
*
|
|
11
|
+
* (el nombre del SSM es el `stack_name` del `samconfig.toml` del validador).
|
|
12
|
+
*
|
|
13
|
+
* El emisor NO decide la elegibilidad: informa qué pasó y quién participó (E3). La llamada es NO
|
|
14
|
+
* bloqueante para el flujo del emisor: si falla, el emisor sigue y reintenta con el mismo
|
|
15
|
+
* `idempotencyKey`.
|
|
16
|
+
*
|
|
17
|
+
* RUTA BASE PROVISIONAL: se fija en el plan del validador (`rewards-eligibility-business`); si cambia
|
|
18
|
+
* (con o sin `/private`), se ajusta este cliente y se republica.
|
|
19
|
+
*/
|
|
20
|
+
export interface IRewardsEligibilityBusinessApi {
|
|
21
|
+
/**
|
|
22
|
+
* Dispara las evaluaciones suscritas al `eventKey`. Responde 202 con una corrida por evaluación
|
|
23
|
+
* (`runs` vacío si nadie escucha el evento).
|
|
24
|
+
*
|
|
25
|
+
* @throws propaga el error del destino (400 `INPUT_INVALID`, 404 `EVENT_TYPE_NOT_FOUND`).
|
|
26
|
+
*/
|
|
27
|
+
postEvent(request: PostEventRequest): Promise<StandardResponse<PostEventResponse>>;
|
|
28
|
+
/**
|
|
29
|
+
* Estado de una corrida y una página de sus resultados.
|
|
30
|
+
*
|
|
31
|
+
* @throws propaga el error del destino (404 `RUN_NOT_FOUND`); nunca devuelve fallback.
|
|
32
|
+
*/
|
|
33
|
+
getRun(runId: string): Promise<StandardResponse<GetRunResponse>>;
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { GrantFailedMessage } from "@fiado/type-kit/bin/rewards/index.js";
|
|
2
|
+
import { IRewardsEligibilityPublisher } from "./interfaces/IRewardsEligibilityPublisher.js";
|
|
3
|
+
export declare class RewardsEligibilityPublisher implements IRewardsEligibilityPublisher {
|
|
4
|
+
private readonly REWARDS_ELIGIBILITY_QUEUE_URL;
|
|
5
|
+
publish(message: GrantFailedMessage): Promise<void>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs";
|
|
8
|
+
import { injectable } from "inversify";
|
|
9
|
+
let RewardsEligibilityPublisher = class RewardsEligibilityPublisher {
|
|
10
|
+
REWARDS_ELIGIBILITY_QUEUE_URL = process.env.REWARDS_ELIGIBILITY_QUEUE_URL;
|
|
11
|
+
async publish(message) {
|
|
12
|
+
if (!this.REWARDS_ELIGIBILITY_QUEUE_URL) {
|
|
13
|
+
throw new Error("Error publishing message to RewardsEligibilityQueue: REWARDS_ELIGIBILITY_QUEUE_URL no está configurada");
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const client = new SQSClient();
|
|
17
|
+
// Cola ESTÁNDAR: el consumidor descarta avisos repetidos o atrasados comparando el `n`
|
|
18
|
+
// de la idempotencyKey con el del ledger (spec B 7.7), así que no hace falta dedup en la cola.
|
|
19
|
+
const sendMessageRequest = {
|
|
20
|
+
QueueUrl: this.REWARDS_ELIGIBILITY_QUEUE_URL,
|
|
21
|
+
MessageBody: JSON.stringify(message),
|
|
22
|
+
};
|
|
23
|
+
await client.send(new SendMessageCommand(sendMessageRequest));
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
27
|
+
throw new Error(`Error publishing message to RewardsEligibilityQueue: ${errorMessage}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
RewardsEligibilityPublisher = __decorate([
|
|
32
|
+
injectable()
|
|
33
|
+
], RewardsEligibilityPublisher);
|
|
34
|
+
export { RewardsEligibilityPublisher };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { GrantFailedMessage } from "@fiado/type-kit/bin/rewards/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Publisher del aviso `GRANT_FAILED` hacia la cola del validador (`RewardsEligibilityQueue`), que
|
|
4
|
+
* consume `rewards-eligibility-business` para liberar ledger y cupo (spec A 6.5, spec B 7.7).
|
|
5
|
+
*
|
|
6
|
+
* Lo produce `rewards-business` cuando una entrega termina SIN ningún ítem entregado (FAILED o
|
|
7
|
+
* REJECTED). Una entrega PARTIAL no avisa. El tipo es una unión cerrada a propósito: meter otro
|
|
8
|
+
* mensaje en una cola compartida obliga a tocar también al consumidor.
|
|
9
|
+
*
|
|
10
|
+
* Env var requerida en el consumer: `REWARDS_ELIGIBILITY_QUEUE_URL`
|
|
11
|
+
* (`!ImportValue SqsRewardsEligibilityQueueUrl`) y el permiso `sqs:SendMessage` sobre la cola.
|
|
12
|
+
*/
|
|
13
|
+
export interface IRewardsEligibilityPublisher {
|
|
14
|
+
publish(message: GrantFailedMessage): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fiado/api-invoker",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.117.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.14.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.510.0",
|
|
38
38
|
"dotenv": "^16.4.7"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
package/src/container.config.ts
CHANGED
|
@@ -107,6 +107,8 @@ import { IReportProcessorBusinessApi } from "./report-processor-business/index.j
|
|
|
107
107
|
import { INotificationWSMessagesPublisher } from "./notificationWebsockets/index.js";
|
|
108
108
|
import NotificationWSMessagePublisher from "./notificationWebsockets/queue/NotificationWSMessagePublisher.js";
|
|
109
109
|
import PeopleBusinessApi from "./people-business/api/PeopleBusinessApi.js";
|
|
110
|
+
import DatalakeReportsApi from "./datalake-reports-business/api/DatalakeReportsApi.js";
|
|
111
|
+
import { IDatalakeReportsApi } from "./datalake-reports-business/index.js";
|
|
110
112
|
import { IPeopleBusinessApi } from "./people-business/index.js";
|
|
111
113
|
import PlacesBusinessApi from "./places-business/api/PlacesBusinessApi.js";
|
|
112
114
|
import { IPlacesBusinessApi } from "./places-business/index.js";
|
|
@@ -204,6 +206,10 @@ import { IKycVerificationsBusinessApi, KycVerificationsBusinessApi } from "./kyc
|
|
|
204
206
|
// Webhook business — publisher de la cola compartida de entrega de eventos a suscriptores externos
|
|
205
207
|
import { IWebhookBusinessPublisher, WebhookBusinessPublisher } from "./webhook-business/index.js";
|
|
206
208
|
import { ICentralPaymentsWebhookApi, CentralPaymentsWebhookApi } from "./central-payments-webhook/index.js";
|
|
209
|
+
// Rewards — publisher de pedidos de entrega + cliente de lecturas privadas (rewards-business) y
|
|
210
|
+
// publisher de GRANT_FAILED + cliente del validador (rewards-eligibility-business)
|
|
211
|
+
import { IRewardsGrantPublisher, IRewardsBusinessApi, RewardsGrantPublisher, RewardsBusinessApi } from "./rewards-business/index.js";
|
|
212
|
+
import { IRewardsEligibilityPublisher, IRewardsEligibilityBusinessApi, RewardsEligibilityPublisher, RewardsEligibilityBusinessApi } from "./rewards-eligibility-business/index.js";
|
|
207
213
|
// Collection engine — cliente del motor de cobro central (collect/deregister)
|
|
208
214
|
import { ICollectionEngineApi } from "./collection-engine/index.js";
|
|
209
215
|
import CollectionEngineApi from "./collection-engine/api/CollectionEngineApi.js";
|
|
@@ -293,6 +299,7 @@ export const apiInvokerBindings = new ContainerModule(({ bind }) => {
|
|
|
293
299
|
bind<IEventHistoryApi>("IEventHistoryApi").to(EventHistoryApi);
|
|
294
300
|
bind<IReportProcessorBusinessApi>("IReportProcessorBusinessApi").to(ReportProcessorBusinessApi);
|
|
295
301
|
bind<IPeopleBusinessApi>("IPeopleBusinessApi").to(PeopleBusinessApi);
|
|
302
|
+
bind<IDatalakeReportsApi>("IDatalakeReportsApi").to(DatalakeReportsApi);
|
|
296
303
|
bind<IPlacesBusinessApi>("IPlacesBusinessApi").to(PlacesBusinessApi);
|
|
297
304
|
bind<ISTPBusinessApi>("ISTPBusinessApi").to(STPBusinessApi);
|
|
298
305
|
bind<IProductPaymentApi>("IProductPaymentApi").to(ProductPaymentApi);
|
|
@@ -395,6 +402,16 @@ export const apiInvokerBindings = new ContainerModule(({ bind }) => {
|
|
|
395
402
|
bind<IWebhookBusinessPublisher>("IWebhookBusinessPublisher").to(WebhookBusinessPublisher);
|
|
396
403
|
bind<ICentralPaymentsWebhookApi>("ICentralPaymentsWebhookApi").to(CentralPaymentsWebhookApi);
|
|
397
404
|
|
|
405
|
+
// Rewards — cola compartida RewardsGrantQueue (pedidos de entrega) y lecturas privadas de rewards-business.
|
|
406
|
+
// El productor necesita REWARDS_GRANT_QUEUE_URL y sqs:SendMessage sobre la cola.
|
|
407
|
+
bind<IRewardsGrantPublisher>("IRewardsGrantPublisher").to(RewardsGrantPublisher);
|
|
408
|
+
bind<IRewardsBusinessApi>("IRewardsBusinessApi").to(RewardsBusinessApi);
|
|
409
|
+
|
|
410
|
+
// Rewards eligibility — GRANT_FAILED hacia la cola del validador y cliente de POST /events.
|
|
411
|
+
// El productor necesita REWARDS_ELIGIBILITY_QUEUE_URL y sqs:SendMessage sobre la cola.
|
|
412
|
+
bind<IRewardsEligibilityPublisher>("IRewardsEligibilityPublisher").to(RewardsEligibilityPublisher);
|
|
413
|
+
bind<IRewardsEligibilityBusinessApi>("IRewardsEligibilityBusinessApi").to(RewardsEligibilityBusinessApi);
|
|
414
|
+
|
|
398
415
|
// Collection engine — motor de cobro central multitenant (collect/deregister)
|
|
399
416
|
bind<ICollectionEngineApi>("ICollectionEngineApi").to(CollectionEngineApi);
|
|
400
417
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { inject, injectable } from "inversify";
|
|
2
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
3
|
+
import { DatalakeReportDefinition, DatalakeReportResult, IDatalakeReportsApi } from "./interfaces/IDatalakeReportsApi.js";
|
|
4
|
+
type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
|
|
5
|
+
|
|
6
|
+
@injectable()
|
|
7
|
+
export default class DatalakeReportsApi implements IDatalakeReportsApi {
|
|
8
|
+
|
|
9
|
+
private readonly baseUrl = process.env.DATALAKE_REPORTS_BUSINESS_LAMBDA_URL || "";
|
|
10
|
+
|
|
11
|
+
constructor(@inject("IHttpRequest") private httpRequest: IHttpRequest) {
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
listReports(): Promise<FiadoApiResponse<DatalakeReportDefinition[]>> {
|
|
15
|
+
return this.httpRequest.get(`${this.baseUrl}private/reports`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getReport(id: string): Promise<FiadoApiResponse<DatalakeReportDefinition>> {
|
|
19
|
+
return this.httpRequest.get(`${this.baseUrl}private/reports/${encodeURIComponent(id)}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
runReport(id: string, params: Record<string, unknown>): Promise<FiadoApiResponse<DatalakeReportResult>> {
|
|
23
|
+
return this.httpRequest.post(`${this.baseUrl}private/reports/${encodeURIComponent(id)}/run`, { params });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getJob(jobId: string, nextToken?: string): Promise<FiadoApiResponse<DatalakeReportResult>> {
|
|
27
|
+
const qs = nextToken ? `?nextToken=${encodeURIComponent(nextToken)}` : "";
|
|
28
|
+
return this.httpRequest.get(`${this.baseUrl}private/reports/jobs/${encodeURIComponent(jobId)}${qs}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
|
|
2
|
+
|
|
3
|
+
/** Contrato local hasta que @fiado/type-kit publique el dominio DatalakeReports (misma forma). */
|
|
4
|
+
export interface DatalakeReportDefinition {
|
|
5
|
+
id: string; version: number; title: string; description: string;
|
|
6
|
+
source: { layer: "gold" | "silver"; databases: string[] };
|
|
7
|
+
pii: boolean; audiences: string[]; permission: string; mode: "sync" | "async"; cacheSeconds: number;
|
|
8
|
+
params: Record<string, { type: string; required?: boolean; default?: unknown; values?: string[]; max?: number; min?: number }>;
|
|
9
|
+
principalParams: string[];
|
|
10
|
+
columns: Array<{ name: string; type: string; label: string }>;
|
|
11
|
+
}
|
|
12
|
+
export interface DatalakeReportResult {
|
|
13
|
+
status: "RUNNING" | "DONE" | "FAILED";
|
|
14
|
+
reportId?: string; version?: number; jobId: string;
|
|
15
|
+
columns?: Array<{ name: string; type: string; label: string }>;
|
|
16
|
+
rows?: Array<Record<string, string | number | boolean | null>>;
|
|
17
|
+
rowCount?: number; nextToken?: string; computedAt?: string; fromCache?: boolean; bytesScanned?: number; reason?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Canal privado de datalake-reports-business (reportes predefinidos sobre Gold y Silver). */
|
|
21
|
+
export interface IDatalakeReportsApi {
|
|
22
|
+
listReports(): Promise<FiadoApiResponse<DatalakeReportDefinition[]>>;
|
|
23
|
+
getReport(id: string): Promise<FiadoApiResponse<DatalakeReportDefinition>>;
|
|
24
|
+
/** 200 con filas, o 200 con status RUNNING y jobId si la corrida cae a asíncrono. */
|
|
25
|
+
runReport(id: string, params: Record<string, unknown>): Promise<FiadoApiResponse<DatalakeReportResult>>;
|
|
26
|
+
getJob(jobId: string, nextToken?: string): Promise<FiadoApiResponse<DatalakeReportResult>>;
|
|
27
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -65,6 +65,7 @@ export * from "./event-history-business/index.js";
|
|
|
65
65
|
export * from "./report-processor-business/index.js";
|
|
66
66
|
export * from "./notificationWebsockets/index.js";
|
|
67
67
|
export * from "./people-business/index.js";
|
|
68
|
+
export * from "./datalake-reports-business/index.js";
|
|
68
69
|
export * from "./places-business/index.js";
|
|
69
70
|
export * from "./stp-business/index.js";
|
|
70
71
|
export * from "./multicommServicePayment/index.js";
|
|
@@ -122,3 +123,5 @@ export * from "./domain-callback/index.js";
|
|
|
122
123
|
export * from "./datacultrConnector/index.js";
|
|
123
124
|
export * from "./webhook-business/index.js";
|
|
124
125
|
export * from "./trustonicConnector/index.js";
|
|
126
|
+
export * from "./rewards-business/index.js";
|
|
127
|
+
export * from "./rewards-eligibility-business/index.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { inject, injectable } from "inversify";
|
|
2
|
+
import type { StandardResponse } from "@fiado/gateway-adapter";
|
|
3
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
4
|
+
import type { GrantResponse, PackageContractResponse } from "@fiado/type-kit/bin/rewards/index.js";
|
|
5
|
+
import type { IRewardsBusinessApi } from "./interfaces/IRewardsBusinessApi.js";
|
|
6
|
+
|
|
7
|
+
/** Contrato y semántica completos → `IRewardsBusinessApi` (`StandardResponse` → leer `result.data`). */
|
|
8
|
+
@injectable()
|
|
9
|
+
export class RewardsBusinessApi implements IRewardsBusinessApi {
|
|
10
|
+
|
|
11
|
+
// El buildspec publica la URL en SSM con "/" final (`.../v1/`): se normaliza para no armar "//packages".
|
|
12
|
+
private readonly baseUrl = (process.env.REWARDS_BUSINESS_LAMBDA_URL || "").replace(/\/+$/, "");
|
|
13
|
+
|
|
14
|
+
constructor(@inject("IHttpRequest") private httpRequest: IHttpRequest) { }
|
|
15
|
+
|
|
16
|
+
async getPackage(packageId: string): Promise<StandardResponse<PackageContractResponse>> {
|
|
17
|
+
// PROVISIONAL: sin prefijo `/private` (la API que resuelve el SSM es la PRIVADA de rewards-business).
|
|
18
|
+
// La ruta base exacta (con o sin `/private`) la fija el plan de rewards-business; si cambia, ajustar aquí.
|
|
19
|
+
const url = `${this.baseUrl}/packages/${encodeURIComponent(packageId)}`;
|
|
20
|
+
return await this.httpRequest.get<StandardResponse<PackageContractResponse>>(url);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async getGrant(grantId: string): Promise<StandardResponse<GrantResponse>> {
|
|
24
|
+
const url = `${this.baseUrl}/grants/${encodeURIComponent(grantId)}`;
|
|
25
|
+
return await this.httpRequest.get<StandardResponse<GrantResponse>>(url);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { StandardResponse } from "@fiado/gateway-adapter";
|
|
2
|
+
import type { GrantResponse, PackageContractResponse } from "@fiado/type-kit/bin/rewards/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cliente de la API privada de `rewards-business`. SOLO LECTURA: no hay endpoint privado de
|
|
6
|
+
* escritura (spec 2026-10-05, D11); los pedidos de entrega se publican con `IRewardsGrantPublisher`.
|
|
7
|
+
*
|
|
8
|
+
* Devuelve `StandardResponse<T>` → leer `result.data`. NO uses `FiadoApiResponse`: los dos tipos no
|
|
9
|
+
* son intercambiables en TypeScript (`code`/`date` admiten `null` en uno y no en el otro).
|
|
10
|
+
*
|
|
11
|
+
* Env var requerida en el consumer: `REWARDS_BUSINESS_LAMBDA_URL`. El template.yml del consumer la
|
|
12
|
+
* setea con:
|
|
13
|
+
*
|
|
14
|
+
* REWARDS_BUSINESS_LAMBDA_URL: '{{resolve:ssm:fiado-rewardsbusiness-lambda}}'
|
|
15
|
+
*
|
|
16
|
+
* El nombre del parámetro SSM es el `stack_name` del `samconfig.toml` de rewards-business (el
|
|
17
|
+
* buildspec registra el SSM con ese nombre): debe coincidir con el de arriba.
|
|
18
|
+
*/
|
|
19
|
+
export interface IRewardsBusinessApi {
|
|
20
|
+
/**
|
|
21
|
+
* Contrato de entrada de un paquete: por `itemKey`, las `dependencies` de su recompensa, más
|
|
22
|
+
* nombre, descripción, costo y `totalCost` por moneda. El backoffice del validador lo usa para
|
|
23
|
+
* no dejar activar una evaluación con un paquete inactivo o con inputs que no cubren el contrato.
|
|
24
|
+
*
|
|
25
|
+
* @throws propaga el error del destino (404 `PACKAGE_NOT_FOUND`); nunca devuelve fallback.
|
|
26
|
+
*/
|
|
27
|
+
getPackage(packageId: string): Promise<StandardResponse<PackageContractResponse>>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Estado de una entrega y de cada ítem. El `grantId` se calcula con `grantIdFor`.
|
|
31
|
+
*
|
|
32
|
+
* @throws propaga el error del destino (404 `GRANT_NOT_FOUND`); nunca devuelve fallback.
|
|
33
|
+
*/
|
|
34
|
+
getGrant(grantId: string): Promise<StandardResponse<GrantResponse>>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
/** Largo (hex) del `grantId`. 128 bits: colisión despreciable. */
|
|
4
|
+
export const REWARDS_GRANT_ID_LENGTH = 32;
|
|
5
|
+
|
|
6
|
+
/** Límite de `BenefitPaymentRequest.transactionNumber`. */
|
|
7
|
+
export const REWARDS_TRANSACTION_NUMBER_MAX_LENGTH = 64;
|
|
8
|
+
|
|
9
|
+
const TRANSACTION_NUMBER_PREFIX = 'RW-';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Largo máximo del `itemKey` de un paquete para que `RW-{grantId}-{itemKey}` quepa en 64:
|
|
13
|
+
* 64 − len('RW-') − len('-') − 32 = 28. rewards-business debe validarlo al guardar un paquete.
|
|
14
|
+
*/
|
|
15
|
+
export const REWARDS_MAX_ITEM_KEY_LENGTH =
|
|
16
|
+
REWARDS_TRANSACTION_NUMBER_MAX_LENGTH - TRANSACTION_NUMBER_PREFIX.length - 1 - REWARDS_GRANT_ID_LENGTH;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `grantId` determinístico de una entrega de rewards (spec A 5.3, D11). Lo calcula el llamador al
|
|
20
|
+
* publicar `GRANT_REQUESTED` (para conocerlo sin esperar respuesta) y el worker de rewards al
|
|
21
|
+
* consumirlo: tienen que dar lo mismo, por eso es UNA función compartida.
|
|
22
|
+
*
|
|
23
|
+
* El largo de `service` va como prefijo para que ('a:b','c') y ('a','b:c') no colisionen.
|
|
24
|
+
*/
|
|
25
|
+
export function grantIdFor(service: string, idempotencyKey: string): string {
|
|
26
|
+
if (!service) {
|
|
27
|
+
throw new Error('grantIdFor: service es obligatorio');
|
|
28
|
+
}
|
|
29
|
+
if (!idempotencyKey) {
|
|
30
|
+
throw new Error('grantIdFor: idempotencyKey es obligatoria');
|
|
31
|
+
}
|
|
32
|
+
return createHash('sha256')
|
|
33
|
+
.update(`${service.length}:${service}:${idempotencyKey}`)
|
|
34
|
+
.digest('hex')
|
|
35
|
+
.slice(0, REWARDS_GRANT_ID_LENGTH);
|
|
36
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { SendMessageBatchCommand, SendMessageBatchRequestEntry, SendMessageCommand, SendMessageRequest, SQSClient } from "@aws-sdk/client-sqs";
|
|
2
|
+
import { injectable } from "inversify";
|
|
3
|
+
import type { GrantRequestedMessage } from "@fiado/type-kit/bin/rewards/index.js";
|
|
4
|
+
import { IRewardsGrantPublisher, RewardsGrantBatchResult } from "./interfaces/IRewardsGrantPublisher.js";
|
|
5
|
+
|
|
6
|
+
const SQS_BATCH_MAX_ENTRIES = 10;
|
|
7
|
+
// Límite SQS: 262 144 bytes por SendMessageBatch. Margen para no rozarlo.
|
|
8
|
+
const SQS_BATCH_MAX_BYTES = 250_000;
|
|
9
|
+
|
|
10
|
+
@injectable()
|
|
11
|
+
export class RewardsGrantPublisher implements IRewardsGrantPublisher {
|
|
12
|
+
private readonly REWARDS_GRANT_QUEUE_URL = process.env.REWARDS_GRANT_QUEUE_URL;
|
|
13
|
+
|
|
14
|
+
async publish(message: GrantRequestedMessage): Promise<void> {
|
|
15
|
+
if (!this.REWARDS_GRANT_QUEUE_URL) {
|
|
16
|
+
throw new Error("Error publishing message to RewardsGrantQueue: REWARDS_GRANT_QUEUE_URL no está configurada");
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const client: SQSClient = new SQSClient();
|
|
20
|
+
// Cola ESTÁNDAR (no FIFO): sin MessageGroupId/MessageDeduplicationId ni orden garantizado.
|
|
21
|
+
// La idempotencia la hace el consumidor con el `grantId` derivado de (source.service +
|
|
22
|
+
// idempotencyKey) y un PutItem condicional.
|
|
23
|
+
const sendMessageRequest: SendMessageRequest = {
|
|
24
|
+
QueueUrl: this.REWARDS_GRANT_QUEUE_URL,
|
|
25
|
+
MessageBody: JSON.stringify(message),
|
|
26
|
+
};
|
|
27
|
+
await client.send(new SendMessageCommand(sendMessageRequest));
|
|
28
|
+
} catch (error) {
|
|
29
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
30
|
+
throw new Error(`Error publishing message to RewardsGrantQueue: ${errorMessage}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async publishBatch(messages: GrantRequestedMessage[]): Promise<RewardsGrantBatchResult> {
|
|
35
|
+
const failedIdempotencyKeys: string[] = [];
|
|
36
|
+
const senderFaultIdempotencyKeys: string[] = [];
|
|
37
|
+
if (messages.length === 0) {
|
|
38
|
+
return { failedIdempotencyKeys, senderFaultIdempotencyKeys };
|
|
39
|
+
}
|
|
40
|
+
if (!this.REWARDS_GRANT_QUEUE_URL) {
|
|
41
|
+
throw new Error("Error publishing batch to RewardsGrantQueue: REWARDS_GRANT_QUEUE_URL no está configurada");
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const client: SQSClient = new SQSClient();
|
|
45
|
+
const keyByEntryId = new Map<string, string>();
|
|
46
|
+
let current: SendMessageBatchRequestEntry[] = [];
|
|
47
|
+
let currentBytes = 0;
|
|
48
|
+
|
|
49
|
+
const flush = async (): Promise<void> => {
|
|
50
|
+
if (current.length === 0) return;
|
|
51
|
+
const result = await client.send(
|
|
52
|
+
new SendMessageBatchCommand({ QueueUrl: this.REWARDS_GRANT_QUEUE_URL, Entries: current }),
|
|
53
|
+
);
|
|
54
|
+
for (const failed of result.Failed ?? []) {
|
|
55
|
+
const key = failed.Id ? keyByEntryId.get(failed.Id) : undefined;
|
|
56
|
+
if (key) (failed.SenderFault ? senderFaultIdempotencyKeys : failedIdempotencyKeys).push(key);
|
|
57
|
+
}
|
|
58
|
+
current = [];
|
|
59
|
+
currentBytes = 0;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
63
|
+
const body = JSON.stringify(messages[index]);
|
|
64
|
+
const bytes = Buffer.byteLength(body, "utf8");
|
|
65
|
+
if (current.length >= SQS_BATCH_MAX_ENTRIES || (current.length > 0 && currentBytes + bytes > SQS_BATCH_MAX_BYTES)) {
|
|
66
|
+
await flush();
|
|
67
|
+
}
|
|
68
|
+
const id = `m${index}`;
|
|
69
|
+
keyByEntryId.set(id, messages[index].idempotencyKey);
|
|
70
|
+
current.push({ Id: id, MessageBody: body });
|
|
71
|
+
currentBytes += bytes;
|
|
72
|
+
}
|
|
73
|
+
await flush();
|
|
74
|
+
return { failedIdempotencyKeys, senderFaultIdempotencyKeys };
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
77
|
+
throw new Error(`Error publishing batch to RewardsGrantQueue: ${errorMessage}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { GrantRequestedMessage } from "@fiado/type-kit/bin/rewards/index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resultado de `publishBatch`: las `idempotencyKey` que SQS NO aceptó.
|
|
5
|
+
* - `failedIdempotencyKeys`: `SenderFault=false` (falla transitoria de SQS) → el llamador PUEDE reintentar.
|
|
6
|
+
* - `senderFaultIdempotencyKeys`: `SenderFault=true` (el mensaje es inválido para SQS) → NO reintentar:
|
|
7
|
+
* volver a mandarlo falla igual.
|
|
8
|
+
*/
|
|
9
|
+
export interface RewardsGrantBatchResult {
|
|
10
|
+
failedIdempotencyKeys: string[];
|
|
11
|
+
senderFaultIdempotencyKeys: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Publisher de pedidos de entrega hacia `RewardsGrantQueue`, que consume `rewards-business`
|
|
16
|
+
* (spec 2026-10-05, D11). La cola es COMPARTIDA entre lambdas: el estándar Fiado publica a una cola
|
|
17
|
+
* compartida con un publisher de `@fiado/api-invoker` (mismo patrón que `ITransactionPublisher`).
|
|
18
|
+
*
|
|
19
|
+
* Que `publish` funcione ES la aceptación del pedido: no hay respuesta síncrona. El `grantId` no
|
|
20
|
+
* viaja en el mensaje; el llamador lo conoce con `grantIdFor(source.service, idempotencyKey)`.
|
|
21
|
+
*
|
|
22
|
+
* Env var requerida en el consumer: `REWARDS_GRANT_QUEUE_URL`
|
|
23
|
+
* (`!ImportValue SqsRewardsGrantQueueUrl`) y el permiso `sqs:SendMessage` sobre la cola.
|
|
24
|
+
*/
|
|
25
|
+
export interface IRewardsGrantPublisher {
|
|
26
|
+
publish(message: GrantRequestedMessage): Promise<void>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Publica varios pedidos con `SendMessageBatch` (tandas de 10 y de ≤ 250 000 bytes). El validador
|
|
30
|
+
* suelta lo masivo por tramos con esto (spec B 7.1 y 7.6). Un fallo parcial NO lanza: se devuelve
|
|
31
|
+
* en `failedIdempotencyKeys` (reintentables) o `senderFaultIdempotencyKeys` (NO reintentar). Si la
|
|
32
|
+
* llamada entera falla, lanza (reintento idempotente por `grantId`).
|
|
33
|
+
*/
|
|
34
|
+
publishBatch(messages: GrantRequestedMessage[]): Promise<RewardsGrantBatchResult>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { inject, injectable } from "inversify";
|
|
2
|
+
import type { StandardResponse } from "@fiado/gateway-adapter";
|
|
3
|
+
import type { IHttpRequest } from "@fiado/http-client";
|
|
4
|
+
import type { GetRunResponse, PostEventRequest, PostEventResponse } from "@fiado/type-kit/bin/rewardsEligibility/index.js";
|
|
5
|
+
import type { IRewardsEligibilityBusinessApi } from "./interfaces/IRewardsEligibilityBusinessApi.js";
|
|
6
|
+
|
|
7
|
+
/** Contrato y semántica completos → `IRewardsEligibilityBusinessApi` (`StandardResponse` → leer `result.data`). */
|
|
8
|
+
@injectable()
|
|
9
|
+
export class RewardsEligibilityBusinessApi implements IRewardsEligibilityBusinessApi {
|
|
10
|
+
|
|
11
|
+
// El buildspec publica la URL en SSM con "/" final (`.../v1/`): se normaliza para no armar "//events".
|
|
12
|
+
private readonly baseUrl = (process.env.REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL || "").replace(/\/+$/, "");
|
|
13
|
+
|
|
14
|
+
constructor(@inject("IHttpRequest") private httpRequest: IHttpRequest) { }
|
|
15
|
+
|
|
16
|
+
async postEvent(request: PostEventRequest): Promise<StandardResponse<PostEventResponse>> {
|
|
17
|
+
// PROVISIONAL: sin prefijo `/private` (la API que resuelve el SSM es la PRIVADA).
|
|
18
|
+
// La ruta base exacta (con o sin `/private`) la fija el plan del validador; si cambia, ajustar aquí.
|
|
19
|
+
return await this.httpRequest.post<StandardResponse<PostEventResponse>>(`${this.baseUrl}/events`, request);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async getRun(runId: string): Promise<StandardResponse<GetRunResponse>> {
|
|
23
|
+
const url = `${this.baseUrl}/runs/${encodeURIComponent(runId)}`;
|
|
24
|
+
return await this.httpRequest.get<StandardResponse<GetRunResponse>>(url);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { StandardResponse } from "@fiado/gateway-adapter";
|
|
2
|
+
import type { GetRunResponse, PostEventRequest, PostEventResponse } from "@fiado/type-kit/bin/rewardsEligibility/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cliente de la API privada de `rewards-eligibility-business` (spec 2026-10-06, §6).
|
|
6
|
+
*
|
|
7
|
+
* Devuelve `StandardResponse<T>` → leer `result.data`. Env var requerida en el consumer:
|
|
8
|
+
* `REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL`, seteada con
|
|
9
|
+
*
|
|
10
|
+
* REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL: '{{resolve:ssm:fiado-rewardseligibilitybusiness-lambda}}'
|
|
11
|
+
*
|
|
12
|
+
* (el nombre del SSM es el `stack_name` del `samconfig.toml` del validador).
|
|
13
|
+
*
|
|
14
|
+
* El emisor NO decide la elegibilidad: informa qué pasó y quién participó (E3). La llamada es NO
|
|
15
|
+
* bloqueante para el flujo del emisor: si falla, el emisor sigue y reintenta con el mismo
|
|
16
|
+
* `idempotencyKey`.
|
|
17
|
+
*
|
|
18
|
+
* RUTA BASE PROVISIONAL: se fija en el plan del validador (`rewards-eligibility-business`); si cambia
|
|
19
|
+
* (con o sin `/private`), se ajusta este cliente y se republica.
|
|
20
|
+
*/
|
|
21
|
+
export interface IRewardsEligibilityBusinessApi {
|
|
22
|
+
/**
|
|
23
|
+
* Dispara las evaluaciones suscritas al `eventKey`. Responde 202 con una corrida por evaluación
|
|
24
|
+
* (`runs` vacío si nadie escucha el evento).
|
|
25
|
+
*
|
|
26
|
+
* @throws propaga el error del destino (400 `INPUT_INVALID`, 404 `EVENT_TYPE_NOT_FOUND`).
|
|
27
|
+
*/
|
|
28
|
+
postEvent(request: PostEventRequest): Promise<StandardResponse<PostEventResponse>>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Estado de una corrida y una página de sus resultados.
|
|
32
|
+
*
|
|
33
|
+
* @throws propaga el error del destino (404 `RUN_NOT_FOUND`); nunca devuelve fallback.
|
|
34
|
+
*/
|
|
35
|
+
getRun(runId: string): Promise<StandardResponse<GetRunResponse>>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SendMessageCommand, SendMessageRequest, SQSClient } from "@aws-sdk/client-sqs";
|
|
2
|
+
import { injectable } from "inversify";
|
|
3
|
+
import type { GrantFailedMessage } from "@fiado/type-kit/bin/rewards/index.js";
|
|
4
|
+
import { IRewardsEligibilityPublisher } from "./interfaces/IRewardsEligibilityPublisher.js";
|
|
5
|
+
|
|
6
|
+
@injectable()
|
|
7
|
+
export class RewardsEligibilityPublisher implements IRewardsEligibilityPublisher {
|
|
8
|
+
private readonly REWARDS_ELIGIBILITY_QUEUE_URL = process.env.REWARDS_ELIGIBILITY_QUEUE_URL;
|
|
9
|
+
|
|
10
|
+
async publish(message: GrantFailedMessage): Promise<void> {
|
|
11
|
+
if (!this.REWARDS_ELIGIBILITY_QUEUE_URL) {
|
|
12
|
+
throw new Error("Error publishing message to RewardsEligibilityQueue: REWARDS_ELIGIBILITY_QUEUE_URL no está configurada");
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const client: SQSClient = new SQSClient();
|
|
16
|
+
// Cola ESTÁNDAR: el consumidor descarta avisos repetidos o atrasados comparando el `n`
|
|
17
|
+
// de la idempotencyKey con el del ledger (spec B 7.7), así que no hace falta dedup en la cola.
|
|
18
|
+
const sendMessageRequest: SendMessageRequest = {
|
|
19
|
+
QueueUrl: this.REWARDS_ELIGIBILITY_QUEUE_URL,
|
|
20
|
+
MessageBody: JSON.stringify(message),
|
|
21
|
+
};
|
|
22
|
+
await client.send(new SendMessageCommand(sendMessageRequest));
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
25
|
+
throw new Error(`Error publishing message to RewardsEligibilityQueue: ${errorMessage}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GrantFailedMessage } from "@fiado/type-kit/bin/rewards/index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Publisher del aviso `GRANT_FAILED` hacia la cola del validador (`RewardsEligibilityQueue`), que
|
|
5
|
+
* consume `rewards-eligibility-business` para liberar ledger y cupo (spec A 6.5, spec B 7.7).
|
|
6
|
+
*
|
|
7
|
+
* Lo produce `rewards-business` cuando una entrega termina SIN ningún ítem entregado (FAILED o
|
|
8
|
+
* REJECTED). Una entrega PARTIAL no avisa. El tipo es una unión cerrada a propósito: meter otro
|
|
9
|
+
* mensaje en una cola compartida obliga a tocar también al consumidor.
|
|
10
|
+
*
|
|
11
|
+
* Env var requerida en el consumer: `REWARDS_ELIGIBILITY_QUEUE_URL`
|
|
12
|
+
* (`!ImportValue SqsRewardsEligibilityQueueUrl`) y el permiso `sqs:SendMessage` sobre la cola.
|
|
13
|
+
*/
|
|
14
|
+
export interface IRewardsEligibilityPublisher {
|
|
15
|
+
publish(message: GrantFailedMessage): Promise<void>;
|
|
16
|
+
}
|