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'; export type SendCompletionCallbacks = Record; 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 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: { tenantId: string; applicationId?: string | null; messageRecordId?: string | null; messageId?: string | null; deliveryType: 'receipt' | 'uplink'; payload: Record; }) { 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; }, 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(); } }