/** * Address Writer Service * * Centralizes bilingual address handling for registration workflows. * Writes Japanese address to Salesforce and English address to WHMCS. * * - SF: Uses Japanese fields (prefectureJa, cityJa, townJa) directly from bilingual address * - WHMCS: Resolves English fields by looking up postal code via Japan Post API */ import { Injectable, Inject } from "@nestjs/common"; import { Logger } from "nestjs-pino"; import { type BilingualAddress, type WhmcsAddressFields, prepareSalesforceContactAddressFields, prepareWhmcsAddressFields, } from "@customer-portal/domain/address"; import { JapanPostFacade } from "@bff/integrations/japanpost/index.js"; import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js"; export interface ResolveWhmcsAddressParams { postcode: string; /** Japanese town name for disambiguation when postal code returns multiple results */ townJa?: string | undefined; streetAddress: string; buildingName?: string | null | undefined; roomNumber?: string | null | undefined; residenceType: "house" | "apartment"; } @Injectable() export class AddressWriterService { constructor( private readonly japanPost: JapanPostFacade, private readonly salesforceFacade: SalesforceFacade, @Inject(Logger) private readonly logger: Logger ) {} /** * Write Japanese address to Salesforce Contact. * Uses prepareSalesforceContactAddressFields() for field mapping. */ async writeToSalesforce(sfAccountId: string, address: BilingualAddress): Promise { const sfFields = prepareSalesforceContactAddressFields(address); await this.salesforceFacade.updateContactAddress(sfAccountId, { mailingStreet: sfFields.MailingStreet, mailingCity: sfFields.MailingCity, mailingState: sfFields.MailingState, mailingPostalCode: sfFields.MailingPostalCode, mailingCountry: sfFields.MailingCountry, buildingName: sfFields.BuildingName__c, roomNumber: sfFields.RoomNumber__c, }); this.logger.debug({ sfAccountId }, "Wrote Japanese address to Salesforce"); } /** * Resolve English address from postal code via Japan Post API * and prepare WHMCS address fields. * * Flow: * 1. Call Japan Post API with postal code * 2. Match result using townJa (if multiple matches) * 3. Build BilingualAddress with resolved English fields * 4. Return WhmcsAddressFields via prepareWhmcsAddressFields() */ async resolveWhmcsAddress(params: ResolveWhmcsAddressParams): Promise { const { postcode, townJa, streetAddress, buildingName, roomNumber, residenceType } = params; const lookupResult = await this.japanPost.lookupByZipCode(postcode); if (lookupResult.addresses.length === 0) { this.logger.warn({ postcode }, "Japan Post API returned no results for postal code"); throw new Error(`No address found for postal code ${postcode}`); } // Find the matching address entry let matched = lookupResult.addresses[0]!; if (townJa && lookupResult.addresses.length > 1) { const townMatch = lookupResult.addresses.find(a => a.town === townJa); if (townMatch) { matched = townMatch; } else { this.logger.warn( { postcode, townJa, availableTowns: lookupResult.addresses.map(a => a.town) }, "Could not match townJa in Japan Post results, using first result" ); } } // Build a BilingualAddress from resolved data const bilingualAddress: BilingualAddress = { postcode, prefecture: matched.prefectureRoma, city: matched.cityRoma, town: matched.townRoma, prefectureJa: matched.prefecture, cityJa: matched.city, townJa: matched.town, streetAddress, buildingName: buildingName ?? null, roomNumber: roomNumber ?? null, residenceType, }; return prepareWhmcsAddressFields(bilingualAddress); } /** * Convenience: resolve WHMCS address from a full BilingualAddress. * Re-derives English fields from postal code via Japan Post API. */ async resolveWhmcsAddressFromBilingual(address: BilingualAddress): Promise { return this.resolveWhmcsAddress({ postcode: address.postcode, townJa: address.townJa, streetAddress: address.streetAddress, buildingName: address.buildingName, roomNumber: address.roomNumber, residenceType: address.residenceType, }); } }