# Bilingual Address Handler Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Centralize address handling so registration writes Japanese address to Salesforce and English address to WHMCS, using the existing bilingual address infrastructure. **Architecture:** Replace `addressFormSchema` (simple English) with `bilingualAddressSchema` in all signup request/session/handoff schemas. Create `AddressWriterService` in BFF that centralizes SF (Japanese) and WHMCS (English via Japan Post API postal code lookup) address writes. Update all signup workflows to use it. Frontend already uses `JapanAddressForm` — just stop converting to WHMCS format before sending. **Tech Stack:** Zod schemas (domain), NestJS services (BFF), React/XState/Zustand (Portal), Japan Post API --- ### Task 1: Update Domain Schemas — Replace addressFormSchema with bilingualAddressSchema in get-started **Files:** - Modify: `packages/domain/get-started/schema.ts` - Modify: `packages/domain/get-started/contract.ts` - Modify: `packages/domain/get-started/index.ts` **Context:** The get-started schemas currently reference `addressFormSchema` (simple: address1, address2, city, state, postcode, country) from `customer/schema.ts`. We need to replace all these references with `bilingualAddressSchema` from `address/schema.ts`, which has both Japanese and English fields. We also remove the duplicate `bilingualEligibilityAddressSchema` that exists in this file. **Step 1: Update imports in `packages/domain/get-started/schema.ts`** Replace the import on line 16-17: ```typescript // OLD (line 16-17): import { addressFormSchema } from "../customer/schema.js"; // NEW: import { bilingualAddressSchema } from "../address/schema.js"; ``` **Step 2: Remove `bilingualEligibilityAddressSchema` (lines 124-140)** Delete the entire `bilingualEligibilityAddressSchema` definition. It's a duplicate of `bilingualAddressSchema` with slightly different field definitions. We'll use `bilingualAddressSchema` directly. **Step 3: Update `guestEligibilityRequestSchema` (line 146-159)** Change line 154 from: ```typescript address: bilingualEligibilityAddressSchema, ``` to: ```typescript address: bilingualAddressSchema, ``` **Step 4: Update `guestHandoffTokenSchema` (line 181-202)** Change line 195 from: ```typescript address: addressFormSchema.partial().optional(), ``` to: ```typescript address: bilingualAddressSchema.partial().optional(), ``` **Step 5: Update `completeAccountRequestSchema` (line 215-238)** Change line 223 from: ```typescript address: addressFormSchema.optional(), ``` to: ```typescript address: bilingualAddressSchema.optional(), ``` **Step 6: Update `signupWithEligibilityRequestSchema` (line 249-272)** Change line 257 from: ```typescript address: addressFormSchema, ``` to: ```typescript address: bilingualAddressSchema, ``` **Step 7: Update `verifyCodeResponseSchema` prefill (line 80-102)** Change line 98 from: ```typescript address: addressFormSchema.partial().optional(), ``` to: ```typescript address: bilingualAddressSchema.partial().optional(), ``` **Step 8: Update `getStartedSessionSchema` (line 320-345)** Change line 330 from: ```typescript address: addressFormSchema.partial().optional(), ``` to: ```typescript address: bilingualAddressSchema.partial().optional(), ``` **Step 9: Update `packages/domain/get-started/contract.ts`** Remove the import of `bilingualEligibilityAddressSchema` on line 19 and the type export on line 90: ```typescript // Remove from imports (line 19): bilingualEligibilityAddressSchema, // Remove from types (line 90): export type BilingualEligibilityAddress = z.infer; ``` **Step 10: Update `packages/domain/get-started/index.ts`** Remove the export of `bilingualEligibilityAddressSchema` on line 52 and `BilingualEligibilityAddress` on line 28: ```typescript // Remove from schema exports (line 52): bilingualEligibilityAddressSchema, // Remove from type exports (line 28): type BilingualEligibilityAddress, ``` **Step 11: Build domain package and verify** Run: `pnpm domain:build` Expected: Build succeeds Run: `pnpm type-check` Expected: May show errors in BFF/Portal files that reference the old schemas — those will be fixed in subsequent tasks. **Step 12: Commit** ```bash git add packages/domain/get-started/ git commit -m "refactor: replace addressFormSchema with bilingualAddressSchema in get-started schemas" ``` --- ### Task 2: Create AddressWriterService in BFF **Files:** - Create: `apps/bff/src/modules/address/address-writer.service.ts` - Modify: `apps/bff/src/modules/address/address.module.ts` **Context:** This service centralizes all address transformation and writing logic. It uses `prepareSalesforceContactAddressFields()` from the domain for SF writes, and calls the Japan Post API to resolve English fields for WHMCS writes. It depends on `JapanPostFacade` (for postal code lookup) and `SalesforceFacade` (for SF contact updates). **Step 1: Create `apps/bff/src/modules/address/address-writer.service.ts`** ```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"; import { extractErrorMessage } from "@bff/core/utils/error.util.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, }); } } ``` **Step 2: Update `apps/bff/src/modules/address/address.module.ts`** ```typescript /** * Address Module * * NestJS module for address lookup and writing functionality. */ import { Module } from "@nestjs/common"; import { AddressController } from "./address.controller.js"; import { AddressWriterService } from "./address-writer.service.js"; import { JapanPostModule } from "@bff/integrations/japanpost/japanpost.module.js"; import { SalesforceModule } from "@bff/integrations/salesforce/salesforce.module.js"; @Module({ imports: [JapanPostModule, SalesforceModule], controllers: [AddressController], providers: [AddressWriterService], exports: [AddressWriterService], }) export class AddressModule {} ``` **Step 3: Verify the SalesforceModule exists and exports SalesforceFacade** Run: `pnpm type-check` from the BFF Expected: May still have downstream errors, but AddressWriterService itself should compile. Check that `SalesforceModule` is importable. If not, check the path — it may be `@bff/integrations/salesforce/salesforce.module.js`. Verify with: ```bash # Check the actual module file path ls apps/bff/src/integrations/salesforce/salesforce.module.ts ``` **Step 4: Commit** ```bash git add apps/bff/src/modules/address/ git commit -m "feat: create AddressWriterService for centralized bilingual address handling" ``` --- ### Task 3: Update NewCustomerSignupWorkflow (Outcome A) to use AddressWriterService **Files:** - Modify: `apps/bff/src/modules/auth/infra/workflows/new-customer-signup-workflow.service.ts` - Modify: `apps/bff/src/modules/auth/auth.module.ts` (or wherever the auth module imports are — ensure AddressModule is imported) **Context:** Currently this workflow (lines 137-189) destructures `address` from the request as `addressFormSchema` shape and manually composes address fields for WHMCS and eligibility case steps. Now `address` will be a `BilingualAddress`. We need to: (1) write Japanese to SF after account creation, (2) resolve English from postal code for WHMCS. **Step 1: Add AddressWriterService dependency** Add to constructor: ```typescript import { AddressWriterService } from "@bff/modules/address/address-writer.service.js"; // In constructor: private readonly addressWriter: AddressWriterService, ``` **Step 2: Update `executeWithSteps()` method (lines 126-240)** The `address` variable (line 137) is now `BilingualAddress`. Update the method: After Step 1 (SF account creation, ~line 148), add SF address write: ```typescript // Step 1.5: Write Japanese address to Salesforce (DEGRADABLE) try { await this.addressWriter.writeToSalesforce(sfResult.sfAccountId, address); } catch (addressError) { this.logger.warn( { error: extractErrorMessage(addressError), email }, "SF address write failed (non-critical, continuing)" ); } ``` Update Step 2 (eligibility case, ~lines 151-169) — the case step likely accepts simple address. Keep passing the basic fields: ```typescript const caseResult = await this.caseStep.execute({ sfAccountId: sfResult.sfAccountId, address: { address1: `${address.townJa}${address.streetAddress}`, city: address.cityJa, state: address.prefectureJa, postcode: address.postcode, country: "Japan", }, }); ``` Update Step 3 (WHMCS client, ~lines 172-189) — resolve English via postal code: ```typescript const whmcsAddress = await this.addressWriter.resolveWhmcsAddressFromBilingual(address); const whmcsResult = await this.whmcsStep.execute({ email, password, firstName, lastName, phone: phone ?? "", address: { address1: whmcsAddress.address1 || "", ...(whmcsAddress.address2 && { address2: whmcsAddress.address2 }), city: whmcsAddress.city, state: whmcsAddress.state, postcode: whmcsAddress.postcode, country: whmcsAddress.country, }, customerNumber: sfResult.customerNumber ?? null, dateOfBirth, gender, }); ``` **Step 3: Ensure AddressModule is imported in the auth module** Find the auth module file and add `AddressModule` to imports. Check: ```bash # Find the auth module grep -l "AuthModule" apps/bff/src/modules/auth/*.ts ``` Add `AddressModule` to the `imports` array. **Step 4: Run type-check** Run: `pnpm type-check` Expected: Errors may remain in other files but this workflow should compile. **Step 5: Commit** ```bash git add apps/bff/src/modules/auth/ git commit -m "refactor: update new-customer-signup workflow to use AddressWriterService" ``` --- ### Task 4: Update GuestEligibilityWorkflow (Outcome B - eligibility check) to use AddressWriterService **Files:** - Modify: `apps/bff/src/modules/auth/infra/workflows/guest-eligibility-workflow.service.ts` **Context:** This workflow already writes Japanese to SF (lines 102-114) via inline code. The `address` is now `BilingualAddress` (from `guestEligibilityRequestSchema` which was updated in Task 1). We replace the inline SF write with `addressWriter.writeToSalesforce()` and store the full bilingual address in the handoff token. **Step 1: Add AddressWriterService dependency** ```typescript import { AddressWriterService } from "@bff/modules/address/address-writer.service.js"; // In constructor: private readonly addressWriter: AddressWriterService, ``` **Step 2: Replace inline SF address write (lines 102-114)** Replace: ```typescript // Save Japanese address to SF Contact (if Japanese address fields provided) if (address.prefectureJa || address.cityJa || address.townJa) { await this.salesforceAccountService.updateContactAddress(sfAccountId, { mailingStreet: `${address.townJa || ""}${address.streetAddress || ""}`.trim(), mailingCity: address.cityJa || address.city, mailingState: address.prefectureJa || address.state, mailingPostalCode: address.postcode, mailingCountry: "Japan", buildingName: address.buildingName ?? null, roomNumber: address.roomNumber ?? null, }); this.logger.debug({ sfAccountId }, "Updated SF Contact with Japanese address"); } ``` With: ```typescript // Write Japanese address to Salesforce try { await this.addressWriter.writeToSalesforce(sfAccountId, address); } catch (addressError) { this.logger.warn( { error: extractErrorMessage(addressError), email: normalizedEmail }, "SF address write failed (non-critical, continuing)" ); } ``` **Step 3: Update eligibility case address (lines 117-128)** Update the case step address to use Japanese fields (since the case is about the Japanese address): ```typescript const { caseId } = await this.eligibilityCaseStep.execute({ sfAccountId, address: { address1: `${address.townJa}${address.streetAddress}`, city: address.cityJa, state: address.prefectureJa, postcode: address.postcode, country: "Japan", }, }); ``` **Step 4: Update handoff token storage (lines 131-143)** The `removeUndefined(address)` call stores the address. Since `address` is now `BilingualAddress`, this will store all bilingual fields. Verify `removeUndefined` doesn't strip needed fields — it only removes `undefined` values, so `null` values for `buildingName`/`roomNumber` will be preserved. This should work as-is. **Step 5: Remove unused imports** Remove the `SalesforceAccountService` import if it's no longer directly used (the `addressWriter` handles SF writes). However, this service is still used for `findByEmail()` and `createAccount()` on lines 74 and 88, so keep it. **Step 6: Commit** ```bash git add apps/bff/src/modules/auth/infra/workflows/guest-eligibility-workflow.service.ts git commit -m "refactor: update guest-eligibility workflow to use AddressWriterService" ``` --- ### Task 5: Update SfCompletionWorkflow (Outcome B - account completion) to use AddressWriterService **Files:** - Modify: `apps/bff/src/modules/auth/infra/workflows/sf-completion-workflow.service.ts` **Context:** This workflow handles both NEW_CUSTOMER (complete-account) and SF_UNMAPPED paths. It resolves address from the request or session (line 122). Now the address in session/request is `BilingualAddress` (partial). The WHMCS step needs English fields resolved via postal code. For SF_UNMAPPED users returning days later, we may only have Japanese fields + postcode from the session. **Step 1: Add AddressWriterService dependency** ```typescript import { AddressWriterService } from "@bff/modules/address/address-writer.service.js"; // In constructor: private readonly addressWriter: AddressWriterService, ``` **Step 2: Update `resolveAddress()` method (lines 217-230)** The method currently requires `address1`, `city`, `state`, `postcode`. For bilingual addresses, we need `postcode` and at least `prefectureJa` or `prefecture`. Update: ```typescript private resolveAddress( requestAddress: CompleteAccountRequest["address"] | undefined, sessionAddress: Record | undefined ): NonNullable { const address = requestAddress ?? sessionAddress; if (!address || !address.postcode) { throw new BadRequestException( "Address information is incomplete. Please ensure postcode is provided." ); } return address as NonNullable; } ``` **Step 3: Update `executeCompletion()` — WHMCS client creation (lines 136-153)** Replace the inline address composition with `addressWriter.resolveWhmcsAddress()`: ```typescript // Resolve English address from postal code for WHMCS const whmcsAddress = await this.addressWriter.resolveWhmcsAddress({ postcode: address.postcode, townJa: address.townJa, streetAddress: address.streetAddress || "", buildingName: address.buildingName, roomNumber: address.roomNumber, residenceType: address.residenceType || "house", }); // Step 2: Create WHMCS client (CRITICAL, has rollback) const whmcsResult = await this.whmcsStep.execute({ firstName: finalFirstName, lastName: finalLastName, email: session.email, password, phone: phone ?? "", address: { address1: whmcsAddress.address1 || "", ...(whmcsAddress.address2 && { address2: whmcsAddress.address2 }), city: whmcsAddress.city, state: whmcsAddress.state, postcode: whmcsAddress.postcode, country: whmcsAddress.country, }, customerNumber: sfResult.customerNumber ?? null, dateOfBirth, gender, }); ``` **Step 4: Add SF address write for NEW_CUSTOMER path** After SF account creation (line 126-133), write Japanese address to SF if this is a new customer: ```typescript // Write Japanese address to Salesforce for new customers if (isNewCustomer && address.prefectureJa) { try { await this.addressWriter.writeToSalesforce(sfResult.sfAccountId, address as BilingualAddress); } catch (addressError) { this.logger.warn( { error: extractErrorMessage(addressError), email: session.email }, "SF address write failed (non-critical, continuing)" ); } } ``` **Step 5: Commit** ```bash git add apps/bff/src/modules/auth/infra/workflows/sf-completion-workflow.service.ts git commit -m "refactor: update sf-completion workflow to use AddressWriterService" ``` --- ### Task 6: Update Portal Frontend — Stop converting to WHMCS format, send bilingual data **Files:** - Modify: `apps/portal/src/features/get-started/components/GetStartedForm/steps/complete-account/useCompleteAccountForm.ts` - Modify: `apps/portal/src/features/get-started/machines/get-started.types.ts` - Modify: `apps/portal/src/features/get-started/machines/get-started.actors.ts` - Modify: `apps/portal/src/features/services/stores/eligibility-check.store.ts` **Context:** The frontend already collects `BilingualAddress` via `JapanAddressForm`. But `useCompleteAccountForm.ts` (lines 63-79) converts it to WHMCS format before storing, and the XState machine types (`GetStartedAddress`) use simple address fields. We need to pass bilingual data through to the API. **Step 1: Update `GetStartedAddress` type in `get-started.types.ts` (lines 24-32)** Replace: ```typescript export interface GetStartedAddress { address1?: string | undefined; address2?: string | undefined; city?: string | undefined; state?: string | undefined; postcode?: string | undefined; country?: string | undefined; countryCode?: string | undefined; } ``` With: ```typescript import type { BilingualAddress } from "@customer-portal/domain/address"; export type GetStartedAddress = Partial; ``` **Step 2: Update `handleAddressChange` in `useCompleteAccountForm.ts` (lines 63-79)** Replace: ```typescript const handleAddressChange = useCallback( (data: JapanAddressFormData, isComplete: boolean) => { setIsAddressComplete(isComplete); const whmcsFields = prepareWhmcsAddressFields(data); updateFormData({ address: { address1: whmcsFields.address1 || "", address2: whmcsFields.address2 || "", city: whmcsFields.city || "", state: whmcsFields.state || "", postcode: whmcsFields.postcode || "", country: "JP", }, }); }, [updateFormData] ); ``` With: ```typescript const handleAddressChange = useCallback( (data: JapanAddressFormData, isComplete: boolean) => { setIsAddressComplete(isComplete); updateFormData({ address: data }); }, [updateFormData] ); ``` Remove the unused import of `prepareWhmcsAddressFields` from line 3. **Step 3: Update `completeAccountActor` in `get-started.actors.ts` (lines 42-71)** The actor currently destructures `formData.address` into simple fields. Update to pass bilingual address: ```typescript export const completeAccountActor = fromPromise( async ({ input }) => { const { sessionToken, formData, accountStatus } = input; const isNewCustomer = accountStatus === "new_customer"; return getStartedApi.completeAccount({ sessionToken, password: formData.password, phone: formData.phone, dateOfBirth: formData.dateOfBirth, gender: formData.gender as "male" | "female" | "other", acceptTerms: formData.acceptTerms, marketingConsent: formData.marketingConsent, ...(isNewCustomer || formData.firstName ? { firstName: formData.firstName } : {}), ...(isNewCustomer || formData.lastName ? { lastName: formData.lastName } : {}), ...(formData.address?.postcode ? { address: formData.address as BilingualAddress } : {}), }); } ); ``` Add the import: ```typescript import type { BilingualAddress } from "@customer-portal/domain/address"; ``` **Step 4: Update eligibility check store — `buildEligibilityAddress` and `completeAccountAction`** In `apps/portal/src/features/services/stores/eligibility-check.store.ts`: The `buildEligibilityAddress` function (lines 172-192) currently converts to WHMCS format. Since the `guestEligibilityRequestSchema` now expects `bilingualAddressSchema`, we pass the `JapanAddressFormData` directly: Replace `buildEligibilityAddress` and its type with: ```typescript // Remove the entire buildEligibilityAddress function and EligibilityCheckAddress interface (lines 157-192) ``` Update `submitOnlyAction` (line 216): ```typescript // OLD: address: buildEligibilityAddress(formData.address), // NEW: address: formData.address, ``` Update `completeAccountAction` (lines 365-379): Replace: ```typescript const whmcsAddress = prepareWhmcsAddressFields(formData.address); const result = await signupWithEligibility({ sessionToken, firstName: formData.firstName.trim(), lastName: formData.lastName.trim(), address: { address1: whmcsAddress.address1 || "", ...(whmcsAddress.address2 && { address2: whmcsAddress.address2 }), city: whmcsAddress.city || "", state: whmcsAddress.state || "", postcode: whmcsAddress.postcode || "", country: "JP", }, ... }); ``` With: ```typescript const result = await signupWithEligibility({ sessionToken, firstName: formData.firstName.trim(), lastName: formData.lastName.trim(), address: formData.address, phone: accountData.phone.trim(), password: accountData.password, dateOfBirth: accountData.dateOfBirth, gender: accountData.gender as "male" | "female" | "other", acceptTerms: accountData.acceptTerms, marketingConsent: accountData.marketingConsent, }); ``` Remove unused imports: `prepareWhmcsAddressFields` from line 20. **Step 5: Run type-check** Run: `pnpm type-check` Expected: Should pass (or show only pre-existing issues). **Step 6: Commit** ```bash git add apps/portal/src/features/get-started/ apps/portal/src/features/services/stores/ git commit -m "refactor: send bilingual address directly from portal instead of converting to WHMCS format" ``` --- ### Task 7: Update BFF Verification Workflow — Prefill bilingual address from Salesforce **Files:** - Modify: `apps/bff/src/modules/auth/infra/workflows/verification-workflow.service.ts` **Context:** When a returning user (SF_UNMAPPED, B2 scenario) verifies their email, the BFF prefills address data from Salesforce. Currently it prefills simple address fields. We need to prefill bilingual fields — at minimum `postcode`, `prefectureJa`, `cityJa`, `townJa`, `streetAddress` from the SF contact's Mailing\* fields. **Step 1: Find the prefill logic** Search for `prefill` in the verification workflow. The `determineAccountStatus()` method builds the prefill object that gets returned to the frontend. **Step 2: Update the SF prefill to include bilingual fields** When building the prefill for SF_UNMAPPED users, map Salesforce fields to bilingual schema: ```typescript prefill: { firstName: sfContact?.FirstName, lastName: sfContact?.LastName, email: session.email, address: { // Japanese fields from SF Contact prefectureJa: sfContact?.MailingState, cityJa: sfContact?.MailingCity, townJa: extractTownFromMailingStreet(sfContact?.MailingStreet), postcode: sfContact?.MailingPostalCode, // streetAddress may be embedded in MailingStreet after town name streetAddress: extractStreetFromMailingStreet(sfContact?.MailingStreet), }, eligibilityStatus: sfAccount?.eligibilityStatus, } ``` Note: `MailingStreet` in SF contains `townJa + streetAddress` (e.g., "東麻布1-5-3"). We need to parse this. The town and street address were concatenated without delimiter in `prepareSalesforceContactAddressFields()`. This parsing may be imperfect — consider storing them separately in the session. For now, store `MailingStreet` as-is and let the BFF resolve at WHMCS write time using only the postal code (which is enough for the Japan Post API lookup). Actually, the simpler approach: just pass the postcode and whatever SF fields we have. The `AddressWriterService.resolveWhmcsAddress()` only needs `postcode` to derive English fields. The frontend won't show the address form for B2 users (address is stored). So the prefill doesn't need to be perfectly parsed — it just needs the postal code for the WHMCS resolution. **Step 3: Commit** ```bash git add apps/bff/src/modules/auth/infra/workflows/verification-workflow.service.ts git commit -m "refactor: prefill bilingual address fields from Salesforce for SF_UNMAPPED users" ``` --- ### Task 8: Ensure AuthModule imports AddressModule **Files:** - Modify: `apps/bff/src/modules/auth/auth.module.ts` (or the module that registers the workflow services) **Context:** The workflow services now depend on `AddressWriterService`, which is provided by `AddressModule`. The auth module needs to import it. **Step 1: Find the auth module** ```bash # Find the file ls apps/bff/src/modules/auth/auth.module.ts ``` **Step 2: Add AddressModule import** ```typescript import { AddressModule } from "@bff/modules/address/address.module.js"; @Module({ imports: [ // ... existing imports AddressModule, ], // ... }) export class AuthModule {} ``` **Step 3: Run type-check and build** Run: `pnpm domain:build && pnpm type-check` Expected: All type errors resolved. **Step 4: Run lint** Run: `pnpm lint` Expected: Passes (may have unused import warnings from removed code — fix those). **Step 5: Commit** ```bash git add apps/bff/src/modules/auth/auth.module.ts git commit -m "chore: import AddressModule in AuthModule for AddressWriterService access" ``` --- ### Task 9: End-to-end verification and cleanup **Files:** Various (cleanup pass) **Step 1: Search for remaining references to old patterns** Search for: - `addressFormSchema` usage in get-started contexts (should be replaced) - `bilingualEligibilityAddressSchema` references (should be removed) - `prepareWhmcsAddressFields` in portal files (should be removed from signup flows — still needed in profile/settings) - `buildEligibilityAddress` (should be removed) ```bash # Check for remaining old references pnpm exec grep -r "addressFormSchema" packages/domain/get-started/ pnpm exec grep -r "bilingualEligibilityAddressSchema" packages/domain/ apps/ pnpm exec grep -r "buildEligibilityAddress" apps/portal/ ``` **Step 2: Full build check** Run: `pnpm domain:build && pnpm type-check && pnpm lint` Expected: All pass. **Step 3: Verify nothing is broken in the domain package exports** Check that `packages/domain/get-started/index.ts` and `packages/domain/address/index.ts` export everything needed. **Step 4: Final commit** ```bash git add -A git commit -m "chore: cleanup remaining references to old address schemas" ```