import { parseProtocolSequence } from '../common/protocol-uint32'; import type { PrismaService } from '../prisma/prisma.service'; export type FinalReceiptMessage = { id: string; tenantId?: string | null; applicationId?: string | null; messageId: string; phoneNumber: string; cmppSubmitSequenceId?: string | null; cmppSubmitGroupMessageId?: string | null; cmppRegisteredDelivery?: boolean | null; }; export type DownstreamDeliveryQueueRequest = { tenantId: string; applicationId?: string | null; messageRecordId?: string | null; messageId?: string | null; deliveryType: 'receipt' | 'uplink'; payload: Record; receiptDedupeKey?: string; queueHttpWebhook?: boolean; queueCmppDelivery?: boolean; allowBusinessRejectionCmppDelivery?: boolean; propagateHttpQueueError?: boolean; }; type ClientReceiptTarget = { segmentIndex: number; segmentTotal: number; submitSequenceId: number; submitGroupMessageId: string; registeredDelivery: boolean; }; async function resolveClientReceiptTargets( prisma: PrismaService, message: FinalReceiptMessage, ): Promise { if (message.cmppSubmitGroupMessageId) { const group = await prisma.cmppInboundLongMessage.findFirst({ where: { messageId: message.cmppSubmitGroupMessageId }, include: { segments: { orderBy: { segmentIndex: 'asc' } } }, }); if (group?.segments.length) { return group.segments.flatMap((segment) => { const submitSequenceId = parseProtocolSequence(segment.sequenceId); if (submitSequenceId === undefined) return []; return [ { segmentIndex: segment.segmentIndex, segmentTotal: group.segmentTotal, submitSequenceId, submitGroupMessageId: group.messageId, registeredDelivery: segment.registeredDelivery, }, ]; }); } } const submitSequenceId = parseProtocolSequence(message.cmppSubmitSequenceId); if (submitSequenceId === undefined) return []; return [ { segmentIndex: 1, segmentTotal: 1, submitSequenceId, submitGroupMessageId: message.cmppSubmitGroupMessageId ?? message.messageId, // Null means a historical CMPP record created before this field existed. registeredDelivery: message.cmppRegisteredDelivery !== false, }, ]; } /** * Queue one business-level HTTP callback and one CMPP status report for every * original client fragment that requested Registered_Delivery. Internal retry, * refund and billing remain message-level; only protocol delivery is expanded. */ export async function queueFinalReceiptDeliveries( prisma: PrismaService, queue: (request: DownstreamDeliveryQueueRequest) => Promise, data: { message: FinalReceiptMessage; payload: Record; segmentPayloads?: Record>; allowBusinessRejectionCmppDelivery?: boolean; propagateHttpQueueError?: boolean; }, ) { const { message } = data; if (!message.tenantId || !message.applicationId) { return { queued: false, cmppTargetCount: 0 }; } // HTTP submissions have one client message identity, so their webhook stays // message-level even when the carrier internally split the SMS into segments. await queue({ tenantId: message.tenantId, applicationId: message.applicationId, messageRecordId: message.id, messageId: message.messageId, deliveryType: 'receipt', payload: data.payload, queueHttpWebhook: true, queueCmppDelivery: false, propagateHttpQueueError: data.propagateHttpQueueError, }); const targets = (await resolveClientReceiptTargets(prisma, message)).filter((target) => target.registeredDelivery); for (const target of targets) { const isSingleFragment = target.segmentTotal === 1; await queue({ tenantId: message.tenantId, applicationId: message.applicationId, messageRecordId: message.id, messageId: message.messageId, deliveryType: 'receipt', payload: { ...data.payload, ...data.segmentPayloads?.[target.segmentIndex], submitSequenceId: target.submitSequenceId, submitGroupMessageId: target.submitGroupMessageId, clientSegmentIndex: target.segmentIndex, clientSegmentTotal: target.segmentTotal, }, receiptDedupeKey: isSingleFragment ? `receipt:${message.id}` : `receipt:${message.id}:segment:${target.segmentIndex}`, queueHttpWebhook: false, queueCmppDelivery: true, allowBusinessRejectionCmppDelivery: data.allowBusinessRejectionCmppDelivery, }); } return { queued: true, cmppTargetCount: targets.length }; }