# 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"
```
