Assist_Design/docs/plans/2026-03-03-bilingual-address-handler-plan.md
barsa 6299fbabdc refactor: enhance address handling in BFF workflows
- Integrated AddressWriterService into GuestEligibilityWorkflowService and NewCustomerSignupWorkflowService for improved address writing to Salesforce.
- Updated AddressModule to include SalesforceModule and export AddressWriterService.
- Refactored address handling in various workflows to utilize the new address structure, ensuring consistency and reliability in address processing.
- Removed deprecated address building logic from eligibility check store, streamlining address management across components.
2026-03-03 16:33:40 +09:00

952 lines
30 KiB
Markdown

# 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<typeof bilingualEligibilityAddressSchema>;
```
**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<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,
});
}
}
```
**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<string, string | undefined> | undefined
): NonNullable<CompleteAccountRequest["address"]> {
const address = requestAddress ?? sessionAddress;
if (!address || !address.postcode) {
throw new BadRequestException(
"Address information is incomplete. Please ensure postcode is provided."
);
}
return address as NonNullable<CompleteAccountRequest["address"]>;
}
```
**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<BilingualAddress>;
```
**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<CompleteAccountOutput, CompleteAccountInput>(
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"
```