130 lines
4.4 KiB
TypeScript
130 lines
4.4 KiB
TypeScript
|
|
/**
|
||
|
|
* 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<void> {
|
||
|
|
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<WhmcsAddressFields> {
|
||
|
|
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<WhmcsAddressFields> {
|
||
|
|
return this.resolveWhmcsAddress({
|
||
|
|
postcode: address.postcode,
|
||
|
|
townJa: address.townJa,
|
||
|
|
streetAddress: address.streetAddress,
|
||
|
|
buildingName: address.buildingName,
|
||
|
|
roomNumber: address.roomNumber,
|
||
|
|
residenceType: address.residenceType,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|