352 lines
11 KiB
TypeScript
352 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;
|
|
messageId?: 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();
|
|
}
|
|
}
|