Files
lislgosms/api/src/send-chain/send-completion.service.ts
T

323 lines
11 KiB
TypeScript

import { BillingService } from '../billing/billing.service';
import type { OpenApiService } from '../open-api/open-api.service';
import { PrismaService } from '../prisma/prisma.service';
import type { GatewaySubmitResultDto, GatewaySubmitSegmentResultDto, GatewayReceiptEventDto, GatewayUplinkEventDto, UplinkMatchCandidateInput, GatewayPendingDeliveryQueryDto, GatewayDownstreamSentDto, GatewayDownstreamAcknowledgedDto, GatewayDownstreamFailureType, GatewaySubmitDeadLetterDto, RequeueGatewaySubmitExceptionDto, GatewayDownstreamRecoveryStatusDto, TimeoutUnknownDto } from './send-chain.contracts';
import { downstreamPendingTimeoutHours } from './send-chain.helpers';
import type { SendSubmissionService } from './send-submission.service';
import { SendAccountingService } from './send-accounting.service';
import { SendDownstreamDeliveryService } from './send-downstream-delivery.service';
import { SendDownstreamStateService } from './send-downstream-state.service';
import { SendGatewayResultService } from './send-gateway-result.service';
import { SendReceiptService } from './send-receipt.service';
import { SendRetryService } from './send-retry.service';
import { SendTimeoutService } from './send-timeout.service';
import type { DownstreamDeliveryQueueRequest } from './downstream-receipt-targets';
export type SendCompletionCallbacks = Record<string, never>;
export type SendCompletionFacade = SendCompletionService & SendSubmissionService;
/**
* R10 internal compatibility facade. SendChainService remains the only public NestJS provider.
*/
export class SendCompletionService {
private readonly gatewayResult: SendGatewayResultService;
private readonly receipt: SendReceiptService;
private readonly retry: SendRetryService;
private readonly accounting: SendAccountingService;
private readonly downstreamState: SendDownstreamStateService;
private readonly downstreamDelivery: SendDownstreamDeliveryService;
private readonly timeout: SendTimeoutService;
constructor(
prisma: PrismaService,
billing: BillingService,
openApi: OpenApiService | undefined,
facade: SendCompletionFacade,
callbacks: SendCompletionCallbacks = {},
) {
this.gatewayResult = new SendGatewayResultService(prisma, billing, openApi, facade, callbacks);
this.receipt = new SendReceiptService(prisma, billing, openApi, facade, callbacks);
this.retry = new SendRetryService(prisma, billing, openApi, facade, callbacks);
this.accounting = new SendAccountingService(prisma, billing, openApi, facade, callbacks);
this.downstreamState = new SendDownstreamStateService(prisma, billing, openApi, facade, callbacks);
this.downstreamDelivery = new SendDownstreamDeliveryService(prisma, billing, openApi, facade, callbacks);
this.timeout = new SendTimeoutService(prisma, billing, openApi, facade, callbacks);
}
async handleSubmitSegmentResult(data: GatewaySubmitSegmentResultDto) {
return this.gatewayResult.handleSubmitSegmentResult(data);
}
async resolveSubmitRecordForGatewaySegmentResult(
messageRecordId: string,
data: GatewaySubmitSegmentResultDto,
) {
return this.gatewayResult.resolveSubmitRecordForGatewaySegmentResult(messageRecordId, data);
}
async handleSubmitResult(data: GatewaySubmitResultDto) {
return this.gatewayResult.handleSubmitResult(data);
}
async resolveSubmitRecordForGatewayResult(messageRecordId: string, data: GatewaySubmitResultDto) {
return this.gatewayResult.resolveSubmitRecordForGatewayResult(messageRecordId, data);
}
smsMessageSegmentAuditDelegate() {
return this.gatewayResult.smsMessageSegmentAuditDelegate();
}
async recordSubmitSegments(
message: {
id: string;
tenantId?: string | null;
batchTaskId?: string | null;
channelId?: string | null;
submitId?: string | null;
billingUnits?: number | null;
},
data: GatewaySubmitResultDto,
submittedAt: Date,
) {
return this.gatewayResult.recordSubmitSegments(message, data, submittedAt);
}
async findMessageByGatewayEvent(messageId?: string, gatewayMessageId?: string) {
return this.gatewayResult.findMessageByGatewayEvent(messageId, gatewayMessageId);
}
async requireMessageByGatewayEvent(messageId?: string, gatewayMessageId?: string) {
return this.gatewayResult.requireMessageByGatewayEvent(messageId, gatewayMessageId);
}
async intakeReceipt(data: GatewayReceiptEventDto) {
return this.receipt.intakeReceipt(data);
}
async processPendingUpstreamReceiptInbox(limit = 100) {
return this.receipt.processPendingUpstreamReceiptInbox(limit);
}
async processUpstreamReceiptInboxRecord(id: string) {
return this.receipt.processUpstreamReceiptInboxRecord(id);
}
async runUpstreamReceiptInboxScan() {
return this.receipt.runUpstreamReceiptInboxScan();
}
async handleReceipt(
data: GatewayReceiptEventDto,
incomingIdentity?: { account: string; gatewayHost: string; gatewayPort: number; protocol: string; cmppVersion: string },
) {
return this.receipt.handleReceipt(data, incomingIdentity);
}
async recordReceiptSegment(
message: {
id: string;
tenantId?: string | null;
batchTaskId?: string | null;
channelId?: string | null;
submitId?: string | null;
billingUnits?: number | null;
},
data: GatewayReceiptEventDto,
deliveredAt: Date,
submitRecordId?: string,
) {
return this.receipt.recordReceiptSegment(message, data, deliveredAt, submitRecordId);
}
async aggregateReceiptSegments(
message: {
id: string;
billingUnits?: number | null;
},
data: GatewayReceiptEventDto,
deliveredAt: Date,
submitRecordId?: string,
submitId?: string,
) {
return this.receipt.aggregateReceiptSegments(message, data, deliveredAt, submitRecordId, submitId);
}
async resolveReceiptMessage(
data: GatewayReceiptEventDto,
incomingIdentity?: { account: string; gatewayHost: string; gatewayPort: number; protocol: string; cmppVersion: string },
) {
return this.receipt.resolveReceiptMessage(data, incomingIdentity);
}
async recordGatewaySubmitDeadLetter(data: GatewaySubmitDeadLetterDto) {
return this.retry.recordGatewaySubmitDeadLetter(data);
}
async requeueGatewaySubmitDeadLetter(id: string, data: RequeueGatewaySubmitExceptionDto = {}) {
return this.retry.requeueGatewaySubmitDeadLetter(id, data);
}
async resolveGatewaySubmitDeadLetter(id: string, operatorId?: string) {
return this.retry.resolveGatewaySubmitDeadLetter(id, operatorId);
}
async recoverStaleGatewaySubmitRequeues(now = new Date()) {
return this.retry.recoverStaleGatewaySubmitRequeues(now);
}
async retryMessageIfAllowed(
message: {
id: string;
tenantId: string;
batchTaskId: string;
applicationId?: string | null;
templateId?: string | null;
signatureId?: string | null;
submitId?: string | null;
messageId: string;
phoneNumber: string;
content: string;
billingUnits: number;
queuedAt?: Date;
clientSrcId?: string | null;
applicationExtension?: string | null;
carrier?: string | null;
province?: string | null;
},
reason: string,
sourceSubmitRecordId?: string,
) {
return this.retry.retryMessageIfAllowed(message, reason, sourceSubmitRecordId);
}
async chargeAcceptedMessage(message: {
tenantId: string;
applicationId?: string | null;
batchTaskId: string;
messageId: string;
phoneNumber: string;
content: string;
billingUnits: number;
unitPrice: number | bigint;
amountCents: number | bigint;
}) {
return this.accounting.chargeAcceptedMessage(message);
}
async releaseMessageReservation(
message: { tenantId: string; batchTaskId: string; messageId: string; amountCents: number | bigint; billingUnits: number },
remark: string,
) {
return this.accounting.releaseMessageReservation(message, remark);
}
async refundMessage(
message: { tenantId: string; messageId: string; amountCents: number | bigint; billingUnits: number },
remark: string,
) {
return this.accounting.refundMessage(message, remark);
}
async listPendingDownstreamDeliveries(data: GatewayPendingDeliveryQueryDto) {
return this.downstreamState.listPendingDownstreamDeliveries(data);
}
async markDownstreamDeliveryDelivered(id: string) {
return this.downstreamState.markDownstreamDeliveryDelivered(id);
}
async markDownstreamDeliverySent(data: GatewayDownstreamSentDto) {
return this.downstreamState.markDownstreamDeliverySent(data);
}
async acknowledgeDownstreamDelivery(data: GatewayDownstreamAcknowledgedDto) {
return this.downstreamState.acknowledgeDownstreamDelivery(data);
}
async markDownstreamDeliveryFailed(
id: string,
errorMessage?: string,
failureType: GatewayDownstreamFailureType = 'send_failed',
attempt?: GatewayDownstreamSentDto,
) {
return this.downstreamState.markDownstreamDeliveryFailed(id, errorMessage, failureType, attempt);
}
async recordGatewayDownstreamRecoveryStatus(data: GatewayDownstreamRecoveryStatusDto) {
return this.downstreamState.recordGatewayDownstreamRecoveryStatus(data);
}
async requeueDownstreamDelivery(id: string) {
return this.downstreamState.requeueDownstreamDelivery(id);
}
async recoverStaleDownstreamManualRequeues(now = new Date()) {
return this.downstreamState.recoverStaleDownstreamManualRequeues(now);
}
async batchRequeueDownstreamDeliveries(ids: string[]) {
return this.downstreamState.batchRequeueDownstreamDeliveries(ids);
}
async handleUplink(data: GatewayUplinkEventDto) {
return this.downstreamDelivery.handleUplink(data);
}
async claimUplinkMatchCandidate(uplinkMessageId: string, candidateId: string, operatorId?: string) {
return this.downstreamDelivery.claimUplinkMatchCandidate(uplinkMessageId, candidateId, operatorId);
}
async queueAndTryDownstreamDelivery(data: DownstreamDeliveryQueueRequest) {
return this.downstreamDelivery.queueAndTryDownstreamDelivery(data);
}
async resolveUplinkMatch(
data: GatewayUplinkEventDto,
channel: { id: string; srcId?: string | null },
): Promise<{
tenantId?: string;
applicationId?: string;
messageRecordId?: string;
matchStatus: string;
matchReason: string;
candidates: UplinkMatchCandidateInput[];
}> {
return this.downstreamDelivery.resolveUplinkMatch(data, channel);
}
async recordCmppFailureReceipt(
message: {
id: string;
tenantId?: string | null;
batchTaskId?: string | null;
applicationId?: string | null;
messageId: string;
phoneNumber: string;
cmppSubmitSequenceId?: string | null;
cmppSubmitGroupMessageId?: string | null;
cmppRegisteredDelivery?: boolean | null;
},
errorCode: string,
reason: string,
) {
return this.downstreamDelivery.recordCmppFailureReceipt(message, errorCode, reason);
}
async postGatewayControl(path: string, payload: unknown) {
return this.downstreamDelivery.postGatewayControl(path, payload);
}
async markUnknownTimeout(data: TimeoutUnknownDto) {
return this.timeout.markUnknownTimeout(data);
}
async markExpiredDownstreamDeliveries(olderThanHours = downstreamPendingTimeoutHours()) {
return this.timeout.markExpiredDownstreamDeliveries(olderThanHours);
}
async runReceiptTimeoutScan() {
return this.timeout.runReceiptTimeoutScan();
}
}