2025-09-25 15:11:28 +09:00
|
|
|
import { Injectable, Inject, BadRequestException } from "@nestjs/common";
|
|
|
|
|
import { Logger } from "nestjs-pino";
|
2025-11-29 16:42:08 +09:00
|
|
|
import { ConfigService } from "@nestjs/config";
|
2025-12-10 16:08:34 +09:00
|
|
|
import { FreebitOrchestratorService } from "@bff/integrations/freebit/services/freebit-orchestrator.service.js";
|
2025-12-29 15:44:01 +09:00
|
|
|
import { WhmcsClientService } from "@bff/integrations/whmcs/services/whmcs-client.service.js";
|
2025-12-10 16:08:34 +09:00
|
|
|
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
|
2026-01-05 16:32:45 +09:00
|
|
|
import { SalesforceOpportunityService } from "@bff/integrations/salesforce/services/salesforce-opportunity.service.js";
|
|
|
|
|
import { SalesforceCaseService } from "@bff/integrations/salesforce/services/salesforce-case.service.js";
|
2025-12-10 16:08:34 +09:00
|
|
|
import { SimValidationService } from "./sim-validation.service.js";
|
2025-12-26 10:30:09 +09:00
|
|
|
import type {
|
|
|
|
|
SimCancelRequest,
|
|
|
|
|
SimCancelFullRequest,
|
|
|
|
|
SimCancellationMonth,
|
|
|
|
|
SimCancellationPreview,
|
|
|
|
|
} from "@customer-portal/domain/sim";
|
2026-01-05 16:32:45 +09:00
|
|
|
import { SALESFORCE_CASE_ORIGIN } from "@customer-portal/domain/support/providers";
|
|
|
|
|
import { SIM_CANCELLATION_NOTICE } from "@customer-portal/domain/opportunity";
|
2025-12-10 16:08:34 +09:00
|
|
|
import { SimScheduleService } from "./sim-schedule.service.js";
|
|
|
|
|
import { SimActionRunnerService } from "./sim-action-runner.service.js";
|
|
|
|
|
import { SimApiNotificationService } from "./sim-api-notification.service.js";
|
2025-12-23 17:53:08 +09:00
|
|
|
import { NotificationService } from "@bff/modules/notifications/notifications.service.js";
|
|
|
|
|
import { NOTIFICATION_SOURCE, NOTIFICATION_TYPE } from "@customer-portal/domain/notifications";
|
2025-11-29 16:42:08 +09:00
|
|
|
|
2025-09-25 15:11:28 +09:00
|
|
|
@Injectable()
|
|
|
|
|
export class SimCancellationService {
|
|
|
|
|
constructor(
|
2025-09-25 16:38:21 +09:00
|
|
|
private readonly freebitService: FreebitOrchestratorService,
|
2025-12-29 15:44:01 +09:00
|
|
|
private readonly whmcsClientService: WhmcsClientService,
|
2025-11-29 16:42:08 +09:00
|
|
|
private readonly mappingsService: MappingsService,
|
2026-01-05 16:32:45 +09:00
|
|
|
private readonly opportunityService: SalesforceOpportunityService,
|
|
|
|
|
private readonly caseService: SalesforceCaseService,
|
2025-09-25 15:11:28 +09:00
|
|
|
private readonly simValidation: SimValidationService,
|
2025-11-18 10:57:36 +09:00
|
|
|
private readonly simSchedule: SimScheduleService,
|
|
|
|
|
private readonly simActionRunner: SimActionRunnerService,
|
2025-11-29 16:42:08 +09:00
|
|
|
private readonly apiNotification: SimApiNotificationService,
|
2025-12-23 17:53:08 +09:00
|
|
|
private readonly notifications: NotificationService,
|
2025-11-29 16:42:08 +09:00
|
|
|
private readonly configService: ConfigService,
|
2025-09-25 15:11:28 +09:00
|
|
|
@Inject(Logger) private readonly logger: Logger
|
|
|
|
|
) {}
|
|
|
|
|
|
2025-11-29 16:42:08 +09:00
|
|
|
private get freebitBaseUrl(): string {
|
|
|
|
|
return this.configService.get<string>("FREEBIT_BASE_URL") || "https://i1.mvno.net/emptool/api";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generate available cancellation months (next 12 months)
|
|
|
|
|
*/
|
2025-12-26 10:30:09 +09:00
|
|
|
private generateCancellationMonths(): SimCancellationMonth[] {
|
|
|
|
|
const months: SimCancellationMonth[] = [];
|
2025-11-29 16:42:08 +09:00
|
|
|
const today = new Date();
|
|
|
|
|
const dayOfMonth = today.getDate();
|
|
|
|
|
|
|
|
|
|
// Start from current month if before 25th, otherwise next month
|
|
|
|
|
const startOffset = dayOfMonth <= 25 ? 0 : 1;
|
|
|
|
|
|
|
|
|
|
for (let i = startOffset; i < startOffset + 12; i++) {
|
|
|
|
|
const date = new Date(today.getFullYear(), today.getMonth() + i, 1);
|
|
|
|
|
const year = date.getFullYear();
|
|
|
|
|
const month = date.getMonth() + 1;
|
|
|
|
|
const monthStr = String(month).padStart(2, "0");
|
|
|
|
|
|
|
|
|
|
// runDate is the 1st of the NEXT month (cancellation takes effect at month end)
|
|
|
|
|
const nextMonth = new Date(year, month, 1);
|
|
|
|
|
const runYear = nextMonth.getFullYear();
|
|
|
|
|
const runMonth = String(nextMonth.getMonth() + 1).padStart(2, "0");
|
|
|
|
|
|
|
|
|
|
months.push({
|
|
|
|
|
value: `${year}-${monthStr}`,
|
|
|
|
|
label: date.toLocaleDateString("en-US", { month: "long", year: "numeric" }),
|
|
|
|
|
runDate: `${runYear}${runMonth}01`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return months;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Calculate minimum contract end date (3 months after start, signup month not included)
|
|
|
|
|
*/
|
|
|
|
|
private calculateMinimumContractEndDate(startDateStr: string): Date | null {
|
|
|
|
|
if (!startDateStr || startDateStr.length < 8) return null;
|
|
|
|
|
|
|
|
|
|
// Parse YYYYMMDD format
|
|
|
|
|
const year = parseInt(startDateStr.substring(0, 4), 10);
|
|
|
|
|
const month = parseInt(startDateStr.substring(4, 6), 10) - 1;
|
|
|
|
|
const day = parseInt(startDateStr.substring(6, 8), 10);
|
|
|
|
|
|
|
|
|
|
if (isNaN(year) || isNaN(month) || isNaN(day)) return null;
|
|
|
|
|
|
|
|
|
|
const startDate = new Date(year, month, day);
|
|
|
|
|
// Minimum term is 3 months after signup month (signup month not included)
|
|
|
|
|
// e.g., signup in January = minimum term ends April 30
|
|
|
|
|
const endDate = new Date(startDate.getFullYear(), startDate.getMonth() + 4, 0);
|
|
|
|
|
|
|
|
|
|
return endDate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get cancellation preview with available months
|
|
|
|
|
*/
|
|
|
|
|
async getCancellationPreview(
|
|
|
|
|
userId: string,
|
|
|
|
|
subscriptionId: number
|
2025-12-26 10:30:09 +09:00
|
|
|
): Promise<SimCancellationPreview> {
|
2025-11-29 16:42:08 +09:00
|
|
|
const validation = await this.simValidation.validateSimSubscription(userId, subscriptionId);
|
|
|
|
|
const simDetails = await this.freebitService.getSimDetails(validation.account);
|
|
|
|
|
|
|
|
|
|
// Get customer info from WHMCS
|
2025-12-26 18:17:37 +09:00
|
|
|
const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(userId);
|
2025-12-29 15:44:01 +09:00
|
|
|
const clientDetails = await this.whmcsClientService.getClientDetails(whmcsClientId);
|
2025-12-10 16:08:34 +09:00
|
|
|
const customerName =
|
|
|
|
|
`${clientDetails.firstname || ""} ${clientDetails.lastname || ""}`.trim() || "Customer";
|
2025-11-29 16:42:08 +09:00
|
|
|
const customerEmail = clientDetails.email || "";
|
|
|
|
|
|
|
|
|
|
// Calculate minimum contract end date
|
|
|
|
|
const startDate = simDetails.startDate;
|
|
|
|
|
const minEndDate = startDate ? this.calculateMinimumContractEndDate(startDate) : null;
|
|
|
|
|
const today = new Date();
|
|
|
|
|
const isWithinMinimumTerm = minEndDate ? today < minEndDate : false;
|
|
|
|
|
|
|
|
|
|
// Format minimum contract end date for display
|
|
|
|
|
let minimumContractEndDate: string | undefined;
|
|
|
|
|
if (minEndDate) {
|
|
|
|
|
const year = minEndDate.getFullYear();
|
|
|
|
|
const month = String(minEndDate.getMonth() + 1).padStart(2, "0");
|
|
|
|
|
minimumContractEndDate = `${year}-${month}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
simNumber: validation.account,
|
|
|
|
|
serialNumber: simDetails.iccid,
|
|
|
|
|
planCode: simDetails.planCode,
|
|
|
|
|
startDate,
|
|
|
|
|
minimumContractEndDate,
|
|
|
|
|
isWithinMinimumTerm,
|
|
|
|
|
availableMonths: this.generateCancellationMonths(),
|
|
|
|
|
customerEmail,
|
|
|
|
|
customerName,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 15:11:28 +09:00
|
|
|
/**
|
2025-11-29 16:42:08 +09:00
|
|
|
* Cancel SIM service (legacy)
|
2025-09-25 15:11:28 +09:00
|
|
|
*/
|
|
|
|
|
async cancelSim(
|
|
|
|
|
userId: string,
|
|
|
|
|
subscriptionId: number,
|
|
|
|
|
request: SimCancelRequest = {}
|
|
|
|
|
): Promise<void> {
|
2025-11-18 10:57:36 +09:00
|
|
|
let account = "";
|
2025-09-25 15:11:28 +09:00
|
|
|
|
2025-11-18 10:57:36 +09:00
|
|
|
await this.simActionRunner.run(
|
|
|
|
|
"Cancel SIM",
|
|
|
|
|
{
|
|
|
|
|
baseContext: {
|
|
|
|
|
userId,
|
|
|
|
|
subscriptionId,
|
|
|
|
|
scheduledAt: request.scheduledAt,
|
|
|
|
|
},
|
|
|
|
|
enrichSuccess: result => ({
|
|
|
|
|
account: result.account,
|
|
|
|
|
runDate: result.runDate,
|
|
|
|
|
}),
|
|
|
|
|
enrichError: () => ({
|
|
|
|
|
account,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
async () => {
|
|
|
|
|
const validation = await this.simValidation.validateSimSubscription(userId, subscriptionId);
|
|
|
|
|
account = validation.account;
|
2025-09-25 15:11:28 +09:00
|
|
|
|
2025-11-18 10:57:36 +09:00
|
|
|
const scheduleResolution = this.simSchedule.resolveScheduledDate(request.scheduledAt);
|
2025-09-25 15:11:28 +09:00
|
|
|
|
2025-11-18 10:57:36 +09:00
|
|
|
await this.freebitService.cancelSim(account, scheduleResolution.date);
|
2025-09-25 15:11:28 +09:00
|
|
|
|
2025-11-18 10:57:36 +09:00
|
|
|
this.logger.log(`Successfully cancelled SIM for subscription ${subscriptionId}`, {
|
|
|
|
|
userId,
|
|
|
|
|
subscriptionId,
|
|
|
|
|
account,
|
|
|
|
|
runDate: scheduleResolution.date,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
account,
|
|
|
|
|
runDate: scheduleResolution.date,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
);
|
2025-09-25 15:11:28 +09:00
|
|
|
}
|
2025-11-29 16:42:08 +09:00
|
|
|
|
|
|
|
|
/**
|
2026-01-05 16:32:45 +09:00
|
|
|
* Cancel SIM service with full flow (PA02-04, Salesforce Case + Opportunity, and email notifications)
|
|
|
|
|
*
|
|
|
|
|
* Flow:
|
|
|
|
|
* 1. Validate SIM subscription
|
|
|
|
|
* 2. Call Freebit PA02-04 API to schedule cancellation
|
|
|
|
|
* 3. Create Salesforce Case with all form details
|
|
|
|
|
* 4. Update Salesforce Opportunity (if linked)
|
|
|
|
|
* 5. Send email notifications
|
2025-11-29 16:42:08 +09:00
|
|
|
*/
|
|
|
|
|
async cancelSimFull(
|
|
|
|
|
userId: string,
|
|
|
|
|
subscriptionId: number,
|
|
|
|
|
request: SimCancelFullRequest
|
|
|
|
|
): Promise<void> {
|
2026-01-05 16:32:45 +09:00
|
|
|
const mapping = await this.mappingsService.findByUserId(userId);
|
|
|
|
|
if (!mapping?.whmcsClientId || !mapping?.sfAccountId) {
|
|
|
|
|
throw new BadRequestException("Account mapping not found");
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 16:42:08 +09:00
|
|
|
const validation = await this.simValidation.validateSimSubscription(userId, subscriptionId);
|
|
|
|
|
const account = validation.account;
|
|
|
|
|
const simDetails = await this.freebitService.getSimDetails(account);
|
|
|
|
|
|
|
|
|
|
// Get customer info from WHMCS
|
2026-01-05 16:32:45 +09:00
|
|
|
const clientDetails = await this.whmcsClientService.getClientDetails(mapping.whmcsClientId);
|
2025-12-10 16:08:34 +09:00
|
|
|
const customerName =
|
|
|
|
|
`${clientDetails.firstname || ""} ${clientDetails.lastname || ""}`.trim() || "Customer";
|
2025-11-29 16:42:08 +09:00
|
|
|
const customerEmail = clientDetails.email || "";
|
|
|
|
|
|
|
|
|
|
// Validate confirmations
|
|
|
|
|
if (!request.confirmRead || !request.confirmCancel) {
|
|
|
|
|
throw new BadRequestException("You must confirm both checkboxes to proceed");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse cancellation month and calculate runDate
|
|
|
|
|
const [year, month] = request.cancellationMonth.split("-").map(Number);
|
|
|
|
|
if (!year || !month) {
|
|
|
|
|
throw new BadRequestException("Invalid cancellation month format");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// runDate is 1st of the NEXT month (cancellation at end of selected month)
|
|
|
|
|
const nextMonth = new Date(year, month, 1);
|
|
|
|
|
const runYear = nextMonth.getFullYear();
|
|
|
|
|
const runMonth = String(nextMonth.getMonth() + 1).padStart(2, "0");
|
|
|
|
|
const runDate = `${runYear}${runMonth}01`;
|
|
|
|
|
|
2026-01-05 16:32:45 +09:00
|
|
|
// Calculate the cancellation date (last day of selected month)
|
|
|
|
|
const lastDayOfMonth = new Date(year, month, 0);
|
|
|
|
|
const cancellationDate = [
|
|
|
|
|
lastDayOfMonth.getFullYear(),
|
|
|
|
|
String(lastDayOfMonth.getMonth() + 1).padStart(2, "0"),
|
|
|
|
|
String(lastDayOfMonth.getDate()).padStart(2, "0"),
|
|
|
|
|
].join("-");
|
|
|
|
|
|
2025-11-29 16:42:08 +09:00
|
|
|
this.logger.log(`Processing SIM cancellation via PA02-04`, {
|
|
|
|
|
userId,
|
|
|
|
|
subscriptionId,
|
|
|
|
|
account,
|
|
|
|
|
cancellationMonth: request.cancellationMonth,
|
|
|
|
|
runDate,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Call PA02-04 cancellation API
|
|
|
|
|
await this.freebitService.cancelAccount(account, runDate);
|
|
|
|
|
|
|
|
|
|
this.logger.log(`Successfully cancelled SIM for subscription ${subscriptionId}`, {
|
|
|
|
|
userId,
|
|
|
|
|
subscriptionId,
|
|
|
|
|
account,
|
|
|
|
|
runDate,
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-05 16:32:45 +09:00
|
|
|
// Find existing Opportunity for this subscription (by WHMCS Service ID)
|
|
|
|
|
let opportunityId: string | null = null;
|
|
|
|
|
try {
|
|
|
|
|
opportunityId = await this.opportunityService.findOpportunityByWhmcsServiceId(subscriptionId);
|
|
|
|
|
} catch {
|
|
|
|
|
this.logger.warn("Could not find Opportunity for SIM subscription", { subscriptionId });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build description with all form data (same pattern as Internet)
|
|
|
|
|
const descriptionLines = [
|
|
|
|
|
`Cancellation Request from Portal`,
|
|
|
|
|
``,
|
|
|
|
|
`Product Type: SIM`,
|
|
|
|
|
`SIM Number: ${account}`,
|
|
|
|
|
`Serial Number: ${simDetails.iccid || "N/A"}`,
|
|
|
|
|
`WHMCS Service ID: ${subscriptionId}`,
|
|
|
|
|
`Cancellation Month: ${request.cancellationMonth}`,
|
|
|
|
|
`Service End Date: ${cancellationDate}`,
|
|
|
|
|
``,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (request.comments) {
|
|
|
|
|
descriptionLines.push(`Customer Comments:`, request.comments, ``);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
descriptionLines.push(`Submitted: ${new Date().toISOString()}`);
|
|
|
|
|
|
|
|
|
|
// Create Salesforce Case for cancellation (same as Internet)
|
|
|
|
|
let caseId: string | undefined;
|
|
|
|
|
try {
|
|
|
|
|
const caseResult = await this.caseService.createCase({
|
|
|
|
|
accountId: mapping.sfAccountId,
|
|
|
|
|
opportunityId: opportunityId || undefined,
|
|
|
|
|
subject: `Cancellation Request - SIM (${request.cancellationMonth})`,
|
|
|
|
|
description: descriptionLines.join("\n"),
|
|
|
|
|
origin: SALESFORCE_CASE_ORIGIN.PORTAL_NOTIFICATION,
|
|
|
|
|
priority: "High",
|
|
|
|
|
});
|
|
|
|
|
caseId = caseResult.id;
|
|
|
|
|
|
|
|
|
|
this.logger.log("SIM cancellation case created", {
|
|
|
|
|
caseId,
|
|
|
|
|
opportunityId,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Log but don't fail - Freebit API was already called successfully
|
|
|
|
|
this.logger.error("Failed to create SIM cancellation Case", {
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
subscriptionId,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update Salesforce Opportunity (if linked via WHMCS_Service_ID__c)
|
|
|
|
|
if (opportunityId) {
|
|
|
|
|
try {
|
|
|
|
|
const cancellationData = {
|
|
|
|
|
scheduledCancellationDate: `${cancellationDate}T23:59:59.000Z`,
|
|
|
|
|
cancellationNotice: SIM_CANCELLATION_NOTICE.RECEIVED,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await this.opportunityService.updateSimCancellationData(opportunityId, cancellationData);
|
|
|
|
|
|
|
|
|
|
this.logger.log("Opportunity updated with SIM cancellation data", {
|
|
|
|
|
opportunityId,
|
|
|
|
|
scheduledDate: cancellationDate,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Log but don't fail - Freebit API was already called successfully
|
|
|
|
|
this.logger.warn("Failed to update Opportunity with SIM cancellation data", {
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
subscriptionId,
|
|
|
|
|
opportunityId,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.logger.debug("No Opportunity linked to SIM subscription", { subscriptionId });
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 17:53:08 +09:00
|
|
|
try {
|
|
|
|
|
await this.notifications.createNotification({
|
|
|
|
|
userId,
|
|
|
|
|
type: NOTIFICATION_TYPE.CANCELLATION_SCHEDULED,
|
|
|
|
|
source: NOTIFICATION_SOURCE.SYSTEM,
|
2026-01-05 16:32:45 +09:00
|
|
|
sourceId: caseId || opportunityId || `sim:${subscriptionId}:${runDate}`,
|
2025-12-23 17:53:08 +09:00
|
|
|
actionUrl: `/account/services/${subscriptionId}`,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
this.logger.warn("Failed to create SIM cancellation notification", {
|
|
|
|
|
userId,
|
|
|
|
|
subscriptionId,
|
|
|
|
|
account,
|
|
|
|
|
runDate,
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 16:42:08 +09:00
|
|
|
// Send admin notification email
|
|
|
|
|
const adminEmailBody = this.apiNotification.buildCancellationAdminEmail({
|
|
|
|
|
customerName,
|
|
|
|
|
simNumber: account,
|
|
|
|
|
serialNumber: simDetails.iccid,
|
|
|
|
|
cancellationMonth: request.cancellationMonth,
|
|
|
|
|
registeredEmail: customerEmail,
|
|
|
|
|
comments: request.comments,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await this.apiNotification.sendApiResultsEmail(
|
|
|
|
|
"SonixNet SIM Online Cancellation",
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
url: `${this.freebitBaseUrl}/master/cnclAcnt/`,
|
|
|
|
|
json: {
|
|
|
|
|
kind: "MVNO",
|
|
|
|
|
account,
|
|
|
|
|
runDate,
|
|
|
|
|
authKey: "[REDACTED]",
|
|
|
|
|
},
|
|
|
|
|
result: {
|
|
|
|
|
resultCode: "100",
|
|
|
|
|
status: { message: "OK", statusCode: "200" },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
adminEmailBody
|
|
|
|
|
);
|
|
|
|
|
|
2026-01-05 16:32:45 +09:00
|
|
|
// Send confirmation email to customer
|
2025-11-29 16:42:08 +09:00
|
|
|
const confirmationSubject = "SonixNet SIM Cancellation Confirmation";
|
|
|
|
|
const confirmationBody = `Dear ${customerName},
|
|
|
|
|
|
|
|
|
|
Your cancellation request for SIM #${account} has been confirmed.
|
|
|
|
|
|
|
|
|
|
The cancellation will take effect at the end of ${request.cancellationMonth}.
|
|
|
|
|
|
|
|
|
|
If you have any questions, please contact us at info@asolutions.co.jp
|
|
|
|
|
|
|
|
|
|
With best regards,
|
|
|
|
|
Assist Solutions Customer Support
|
|
|
|
|
TEL: 0120-660-470 (Mon-Fri / 10AM-6PM)
|
|
|
|
|
Email: info@asolutions.co.jp`;
|
|
|
|
|
|
|
|
|
|
await this.apiNotification.sendCustomerEmail(
|
|
|
|
|
customerEmail,
|
|
|
|
|
confirmationSubject,
|
|
|
|
|
confirmationBody
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-25 15:11:28 +09:00
|
|
|
}
|