From d7efc99fdc07fc4b0b8f5c45255eb60d0ce3c4d4 Mon Sep 17 00:00:00 2001 From: ramirez Date: Tue, 24 Feb 2026 12:11:14 +0900 Subject: [PATCH 1/2] Add Japanese translations of UAT and how-it-works documentation Translated all UAT test guides and how-it-works documentation to Japanese. Files are organized under docs/ja/uat/ and docs/ja/how-it-works/. --- docs/ja/how-it-works/accounts-and-identity.md | 40 ++++ docs/ja/how-it-works/addon-installation.md | 81 +++++++ docs/ja/how-it-works/billing-and-payments.md | 35 +++ docs/ja/how-it-works/complete-guide.md | 199 +++++++++++++++++ .../dashboard-and-notifications.md | 48 +++++ .../eligibility-and-verification.md | 65 ++++++ docs/ja/how-it-works/order-fulfillment.md | 84 ++++++++ docs/ja/how-it-works/ordering-provisioning.md | 83 +++++++ .../how-it-works/orders-and-provisioning.md | 41 ++++ docs/ja/how-it-works/services-and-checkout.md | 45 ++++ docs/ja/how-it-works/subscriptions.md | 28 +++ docs/ja/how-it-works/support-cases.md | 70 ++++++ docs/ja/how-it-works/system-overview.md | 56 +++++ docs/ja/uat/address-management.md | 97 +++++++++ docs/ja/uat/appendix-cross-reference.md | 132 ++++++++++++ docs/ja/uat/billing-and-invoices.md | 80 +++++++ docs/ja/uat/browsing-services.md | 88 ++++++++ docs/ja/uat/dashboard-and-profile.md | 203 ++++++++++++++++++ docs/ja/uat/identity-verification.md | 85 ++++++++ docs/ja/uat/login-and-authentication.md | 114 ++++++++++ .../ja/uat/managing-internet-subscriptions.md | 96 +++++++++ docs/ja/uat/managing-sim-subscriptions.md | 127 +++++++++++ docs/ja/uat/managing-vpn-subscriptions.md | 69 ++++++ docs/ja/uat/notifications.md | 81 +++++++ docs/ja/uat/ordering-a-service.md | 146 +++++++++++++ docs/ja/uat/signup-and-account-creation.md | 162 ++++++++++++++ docs/ja/uat/support-cases.md | 74 +++++++ 27 files changed, 2429 insertions(+) create mode 100644 docs/ja/how-it-works/accounts-and-identity.md create mode 100644 docs/ja/how-it-works/addon-installation.md create mode 100644 docs/ja/how-it-works/billing-and-payments.md create mode 100644 docs/ja/how-it-works/complete-guide.md create mode 100644 docs/ja/how-it-works/dashboard-and-notifications.md create mode 100644 docs/ja/how-it-works/eligibility-and-verification.md create mode 100644 docs/ja/how-it-works/order-fulfillment.md create mode 100644 docs/ja/how-it-works/ordering-provisioning.md create mode 100644 docs/ja/how-it-works/orders-and-provisioning.md create mode 100644 docs/ja/how-it-works/services-and-checkout.md create mode 100644 docs/ja/how-it-works/subscriptions.md create mode 100644 docs/ja/how-it-works/support-cases.md create mode 100644 docs/ja/how-it-works/system-overview.md create mode 100644 docs/ja/uat/address-management.md create mode 100644 docs/ja/uat/appendix-cross-reference.md create mode 100644 docs/ja/uat/billing-and-invoices.md create mode 100644 docs/ja/uat/browsing-services.md create mode 100644 docs/ja/uat/dashboard-and-profile.md create mode 100644 docs/ja/uat/identity-verification.md create mode 100644 docs/ja/uat/login-and-authentication.md create mode 100644 docs/ja/uat/managing-internet-subscriptions.md create mode 100644 docs/ja/uat/managing-sim-subscriptions.md create mode 100644 docs/ja/uat/managing-vpn-subscriptions.md create mode 100644 docs/ja/uat/notifications.md create mode 100644 docs/ja/uat/ordering-a-service.md create mode 100644 docs/ja/uat/signup-and-account-creation.md create mode 100644 docs/ja/uat/support-cases.md diff --git a/docs/ja/how-it-works/accounts-and-identity.md b/docs/ja/how-it-works/accounts-and-identity.md new file mode 100644 index 00000000..3572bc8b --- /dev/null +++ b/docs/ja/how-it-works/accounts-and-identity.md @@ -0,0 +1,40 @@ +--- +sidebar_position: 2 +title: "アカウントとID" +--- + +# アカウントとID + +サインアップ、WHMCSリンク、プロフィール更新に関する説明です。 + +## 新規サインアップフロー + +- 必要な入力:メール、パスワード、名前、電話番号、完全な請求先住所、Customer Number(Salesforceから)。 +- Salesforce確認:Customer Numberを検索。既にポータル/WHMCSリンクがある場合、ログインを促します。 +- WHMCSアカウント作成:提供された連絡先 + 住所情報でWHMCSクライアントを作成。必須フィールドは名前、メール、電話番号、住所。オプションのカスタムフィールド: + - Customer Number(カスタムフィールドID 198) + - 生年月日 + - 性別 + - 国籍 +- ポータルレコード:ポータルユーザー(Argon2でハッシュ化されたパスワード)と`id_mapping`行を保存。 +- Salesforceフラグ:作成後にSalesforce Accountにポータルステータスを設定(status = Active、source = New Signup)。 + +### エラー時 + +- Customer Numberが見つからないまたは既にマッピング済み:ログインを促すメッセージを表示。 +- WHMCSに既にこのメールが存在:リンクを促すメッセージを表示。 +- WHMCS作成失敗:「請求アカウントの作成に失敗しました」メッセージを表示。ポータルにはコミットされません。 + +## 既存WHMCSユーザーのリンク + +- 既にこのメールでWHMCSアカウントを持つユーザー向け: + - WHMCSログインを検証。 + - WHMCSカスタムフィールド198からCustomer Numberを読み取り、対応するSalesforce Accountを検索。 + - ポータルユーザー(パスワードなし)と`id_mapping`を作成。 + - Salesforceポータルフラグを設定(status = Active、source = Migrated)。新しいポータルパスワードの設定を促します。 + +## プロフィール & 住所更新 + +- ポータルでの住所・プロフィール編集はWHMCSに直接書き込まれ、ユーザーのWHMCSキャッシュがクリアされ変更が即座に反映。 +- 住所はWHMCSで権威的に扱われます。Salesforceは注文作成時のみ住所スナップショットを受信。 +- パスワード変更はポータルのみ。WHMCS認証情報はポータルに保存されません。 diff --git a/docs/ja/how-it-works/addon-installation.md b/docs/ja/how-it-works/addon-installation.md new file mode 100644 index 00000000..1cc32940 --- /dev/null +++ b/docs/ja/how-it-works/addon-installation.md @@ -0,0 +1,81 @@ +--- +sidebar_position: 12 +title: "アドオンと設置ロジック" +--- + +# アドオンと設置ロジック — ビジネスルール + +## Salesforceの商品分類 + +### Item_Class__c の値 + +- **Service**:メインの顧客選択可能商品(インターネットプラン、SIMプラン、VPN) +- **Installation**:サービスの設置オプション(一括または月額) +- **Add-on**:オプションの追加サービス(スタンドアロンまたはバンドル) +- **Activation**:必須の一括アクティベーション料金 + +## アドオンロジック + +### スタンドアロンアドオン + +アドオンはバンドルなしで独立して存在できます: + +```typescript +// 例:SIM用Voice Mailアドオン +{ + sku: "SIM-ADDON-VOICE-MAIL", + itemClass: "Add-on", + billingCycle: "Monthly", + isBundledAddon: false, + bundledAddonId: null +} +``` + +### バンドルアドオン + +アドオンは設置/セットアップとバンドルできます: + +```typescript +// 月額サービスアドオン +{ + sku: "INTERNET-ADDON-HIKARI-DENWA", + itemClass: "Add-on", + billingCycle: "Monthly", + isBundledAddon: true, + bundledAddonId: "a0X4x000000INSTALL123" +} + +// アドオンの設置 +{ + sku: "INTERNET-ADDON-HIKARI-DENWA-INSTALL", + itemClass: "Add-on", // アドオンの設置も"Add-on"として分類 + billingCycle: "Onetime", + isBundledAddon: true, + bundledAddonId: "a0X4x000000SERVICE456" +} +``` + +## 設置ロジック + +### サービス設置 + +メインサービスの設置は「Installation」として分類されます。 + +### アドオン設置 + +アドオンの設置は「Add-on」として分類されます(「Installation」ではありません)。 + +## ビジネスルール + +### バンドルルール + +- アドオンのみバンドル可能(Item_Class__c = "Add-on") +- サービス設置は別(Item_Class__c = "Installation") +- バンドルアドオンは一致する`bundledAddonId`参照が必要 +- バンドルペア:同じバンドル関係を持つ1つのMonthly + 1つのOnetime + +### SKUパターン + +- サービス設置:`*-INSTALL-*`(Item_Class__c = "Installation") +- アドオン設置:`*-ADDON-*-INSTALL`(Item_Class__c = "Add-on") +- 月額アドオン:`*-ADDON-*`(INSTALLサフィックスなし、Item_Class__c = "Add-on") diff --git a/docs/ja/how-it-works/billing-and-payments.md b/docs/ja/how-it-works/billing-and-payments.md new file mode 100644 index 00000000..147521b7 --- /dev/null +++ b/docs/ja/how-it-works/billing-and-payments.md @@ -0,0 +1,35 @@ +--- +sidebar_position: 7 +title: "請求と支払い" +--- + +# 請求、請求書、支払い + +請求データの表示、更新、正確性の維持方法を説明します。 + +## データソース + +- WHMCSがクライアント、請求書、支払い方法、ゲートウェイ、サブスクリプションの請求管理システムです。 +- ポータルは支払い詳細を保存しません。WHMCSに既に保存されている支払い方法のみ読み取ります。 + +## 請求書 + +- 取得:マッピングされたクライアントID用の請求書一覧と詳細をWHMCSから取得。 +- キャッシュ:一覧は90秒、個別請求書は5分キャッシュ。WHMCSのWebhookと書き込み操作でキャッシュをクリア。 +- 請求書の支払い:ポータルが特定の請求書用のWHMCS SSOリンクを生成し、顧客が認証情報を再入力せずにWHMCSで直接支払い可能。 + +## 支払い方法 & ゲートウェイ + +- 支払い方法はWHMCSに保存。注文受付前に少なくとも1つの方法が存在することを確認。 +- キャッシュ:支払い方法はユーザーごとに15分、決済ゲートウェイ一覧は1時間キャッシュ。 + +## 請求における住所 & プロフィール + +- ポータルに表示される請求先住所と連絡先フィールドはWHMCSから取得され権威的に扱われます。 +- プロフィール/住所を編集すると、ポータルがWHMCSに変更を書き込みキャッシュをクリアし、請求書に即座に新しい住所が反映。 + +## エラー時の動作 + +- WHMCS利用不可:「請求システムが利用できません、やり直してください」を表示。 +- 支払い方法不足:チェックアウト/フルフィルメントを停止し支払い方法の追加を促す。 +- 請求書が見つからない:他のユーザーデータを漏洩せずに「請求書が見つかりません」を返却。 diff --git a/docs/ja/how-it-works/complete-guide.md b/docs/ja/how-it-works/complete-guide.md new file mode 100644 index 00000000..a8dec4ed --- /dev/null +++ b/docs/ja/how-it-works/complete-guide.md @@ -0,0 +1,199 @@ +--- +sidebar_position: 13 +title: "完全技術ガイド" +--- + +# カスタマーポータル — 技術運用ガイド + +カスタマーポータルがWHMCS、Salesforce、その他のシステムとどのように連携するかを説明する包括的なガイドです。 + +--- + +## システムアーキテクチャ + +ポータルは2つのメインコンポーネントで構成されています: + +- **フロントエンド**:顧客UIを提供するNext.jsアプリケーション +- **BFF(Backend-for-Frontend)**:外部システムへの呼び出しを調整するNestJS API + +### 接続システム + +| システム | 役割 | 連携方法 | +| --- | --- | --- | +| **WHMCS** | 請求の管理システム | REST API | +| **Salesforce** | CRMと注文管理 | REST API + Change Data Capture (CDC) | +| **Freebit** | SIM/MVNOプロビジョニング | REST API | +| **SFTP (fs.mvno.net)** | 通話/SMS明細レコード | SFTPファイルダウンロード | +| **PostgreSQL** | ポータルユーザーアカウントとIDマッピング | 直接接続 | +| **Redis** | キャッシュとリアルタイムイベント用pub/sub | 直接接続 | + +### IDマッピング + +ポータルはPostgreSQLの`id_mappings`テーブルで以下のマッピングを管理: + +- `user_id`(ポータルUUID)↔ `whmcs_client_id`(整数)↔ `sf_account_id`(Salesforce 18文字ID) + +--- + +## データ所有権とフロー + +| データ | 管理システム | ポータルの動作 | +| --- | --- | --- | +| ユーザー認証情報 | Portal (PostgreSQL) | Argon2でハッシュ化、WHMCS/SF認証情報の保存なし | +| クライアントプロフィール & 住所 | WHMCS | ポータルがWHMCSに読み書き、更新時にキャッシュクリア | +| 商品カタログ & 価格 | Salesforce (Pricebook) | ポータル価格表から読み取り | +| 注文 & 注文ステータス | Salesforce (Order) | ポータルが注文作成、Salesforce CDCがフルフィルメントをトリガー | +| 請求書 & 支払い方法 | WHMCS | ポータルは読み取りのみ、WHMCS SSO経由で支払い | +| サブスクリプション/サービス | WHMCS (tblhosting) | ポータルは読み取りのみ | +| サポートケース | Salesforce (Case) | ポータルがOrigin = "Portal Website"でケース作成/読み取り | +| SIM詳細 & 使用量 | Freebit | ポータルがFreebit API経由で読み書き | + +--- + +## アカウント作成とリンク + +### 新規顧客サインアップ + +**検証ステップ:** + +1. ポータル`users`テーブルにメールが存在するか確認 +2. Customer Number(`SF_Account_No__c`)でSalesforce Accountを検索 +3. `WH_Account__c`フィールドが既に設定されているか確認 +4. WHMCS `GetClientsDetails`でメールが存在するか確認 + +**作成ステップ(検証通過時):** + +1. WHMCS `AddClient` APIアクションでWHMCSクライアントを作成 +2. PostgreSQLにポータルユーザーレコードを作成(Argon2ハッシュ) +3. 同一トランザクションでIDマッピングを作成 +4. Salesforce Accountをポータルフィールドで更新 + +--- + +## プロフィールと住所管理 + +- すべてのプロフィール/住所データはWHMCS `GetClientsDetails` API経由で読み取り +- プロフィール更新はWHMCS `UpdateClient` API経由で書き込み +- キャッシュは更新成功後に即座に無効化 +- Salesforceは注文作成時のみ住所スナップショットを受信 + +--- + +## パスワード管理 + +- ポータルパスワードはPostgreSQLにArgon2ハッシュで保存 +- WHMCS認証情報とは完全に別管理 +- パスワードリセットは時間制限付きトークンを使用 +- リセット後、既存セッションはすべて無効化 +- レート制限:IPあたり15分で5回まで + +--- + +## 商品カタログと利用資格 + +- 商品は`PORTAL_PRICEBOOK_ID`で設定されたSalesforce Pricebookから取得 +- ポータル表示対象の商品のみ表示 +- カテゴリ:Internet、SIM/Mobile、VPN +- カタログはTTLなしでRedisにキャッシュ、Salesforce CDC `PricebookEntry`変更時に無効化 + +### SIMファミリープラン + +- WHMCSにアクティブなSIMサブスクリプションが存在する場合、ファミリー/割引SIMプランを表示 +- アクティブなSIMがない場合、ファミリープランは非表示 + +--- + +## 注文作成 + +### 事前チェックアウト検証 + +1. 有効なIDマッピングの存在確認 +2. WHMCSに少なくとも1つの支払い方法の存在確認 +3. インターネット注文:WHMCSにアクティブなインターネットサービスがないこと + +--- + +## 注文フルフィルメントとプロビジョニング + +### トリガー + +- Salesforce CDCが注文ステータス変更を検出 +- べき等キーによる重複処理防止 + +### ステップ + +1. Salesforce `Activation_Status__c` = "Activating" に更新 +2. OrderItemsをWHMCS商品にマッピング +3. WHMCS `AddOrder` APIアクション呼び出し +4. SIM注文:Freebit APIアクティベーション +5. SalesforceをWHMCS Order ID等で更新 +6. リアルタイムイベントを公開 + +--- + +## 請求と支払い + +- 請求書はWHMCS `GetInvoices`/`GetInvoice` API経由で取得 +- 一覧は90秒、個別は5分キャッシュ +- 支払い方法はWHMCS `GetPayMethods` API経由で取得、15分キャッシュ +- WHMCS SSO経由で請求書支払い用リンクを生成 + +--- + +## SIM管理 + +SIM商品として識別されたサブスクリプションに対し、Freebit API経由で追加管理が可能。 + +### 利用可能な操作 + +| 操作 | Freebit API | タイミング | +| --- | --- | --- | +| データチャージ | `addSpec` / `eachQuota` | 即時またはスケジュール | +| プラン変更 | `changePlan` | 翌月1日 | +| 音声機能更新 | `talkoption/changeOrder` | 即時 | +| ネットワークタイプ更新 | `contractline/change` | 即時 | +| SIMプラン解約 | `releasePlan` | スケジュール | +| eSIM再発行 | `reissueEsim` | スケジュール | + +### 操作タイミング制約 + +重要ルール:以下の操作間には**30分の最小間隔**が必要: +- 音声機能変更 +- ネットワークタイプ変更 +- プラン変更 + +--- + +## サポートケース + +| フィールド | API名 | 値 | +| --- | --- | --- | +| AccountId | `AccountId` | マッピングから | +| Origin | `Origin` | "Portal Website" | +| Subject | `Subject` | 顧客入力(必須) | +| Description | `Description` | 顧客入力(必須) | +| Status | `Status` | "New" | + +ケースは顧客のリンク済みAccountに厳密にフィルタリングされます。 + +--- + +## ダッシュボードとサマリーデータ + +| メトリック | ソース | クエリ | +| --- | --- | --- | +| 最近の注文 | Salesforce | アカウントの過去30日の注文 | +| 未払い請求書 | WHMCS | Unpaid/Overdue請求書 | +| 有効サービス | WHMCS | Active状態のサブスクリプション | +| 未解決ケース | Salesforce | Status ≠ Closedのケース | + +--- + +## リアルタイムイベント + +### SSEエンドポイント + +- 単一エンドポイント:`GET /api/events` +- Server-Sent Events (SSE) 接続 +- 認証が必要 +- マルチインスタンス配信用にRedis pub/subをバックエンドに使用 diff --git a/docs/ja/how-it-works/dashboard-and-notifications.md b/docs/ja/how-it-works/dashboard-and-notifications.md new file mode 100644 index 00000000..8f36c2ef --- /dev/null +++ b/docs/ja/how-it-works/dashboard-and-notifications.md @@ -0,0 +1,48 @@ +--- +sidebar_position: 11 +title: "ダッシュボードと通知" +--- + +# ダッシュボードと通知 + +**顧客ダッシュボード**の一貫性の維持方法と**アプリ内通知**の生成方法を説明します。 + +## ダッシュボード「単一リードモデル」(`/api/me/status`) + +ビジネスロジックをフロントエンドの外に保つため、ポータルは単一のBFFエンドポイントを使用します: + +- **エンドポイント**:`GET /api/me/status` +- **目的**:顧客の現在の状態の一貫したスナップショットを返却(サマリー + タスク + ゲーティングシグナル) + +レスポンスに含まれるもの: + +- **`summary`**:統計、次回請求書、アクティビティ +- **`internetEligibility`**:インターネット利用資格ステータス/詳細 +- **`residenceCardVerification`**:在留カード確認ステータス/詳細 +- **`paymentMethods.totalCount`**:保存された支払い方法の数 +- **`tasks[]`**:優先順位付きダッシュボードタスクのリスト + +## アプリ内通知 + +アプリ内通知はPostgresに保存され、Notifications API経由で取得されます。 + +### 全通知タイプ + +| タイプ | タイトル | 作成者 | トリガー | +| --- | --- | --- | --- | +| `ELIGIBILITY_ELIGIBLE` | 「インターネットサービスが利用可能」 | Platform Event | 利用資格 → Eligible | +| `ELIGIBILITY_INELIGIBLE` | 「インターネットサービスは利用不可」 | Platform Event | 利用資格 → Ineligible | +| `VERIFICATION_VERIFIED` | 「本人確認完了」 | Platform Event | 確認 → Verified | +| `VERIFICATION_REJECTED` | 「本人確認に対応が必要」 | Platform Event | 確認 → Rejected | +| `ORDER_APPROVED` | 「注文承認済み」 | フルフィルメントフロー | Salesforceで注文承認 | +| `ORDER_ACTIVATED` | 「サービス有効化」 | フルフィルメントフロー | WHMCSプロビジョニング完了 | +| `ORDER_FAILED` | 「注文に対応が必要」 | フルフィルメントフロー | フルフィルメントエラー | +| `CANCELLATION_SCHEDULED` | 「解約予定」 | 解約フロー | 顧客が解約リクエスト | +| `INVOICE_DUE` | 「請求書期限」 | ダッシュボードチェック | 7日以内に支払期限 | + +### 重複排除 + +| タイプカテゴリ | 重複排除ウィンドウ | ロジック | +| --- | --- | --- | +| 大部分のタイプ | 1時間 | 同じ `type` + `sourceId` | +| 請求書期限等 | 24時間 | 同じ `type` + `sourceId` | diff --git a/docs/ja/how-it-works/eligibility-and-verification.md b/docs/ja/how-it-works/eligibility-and-verification.md new file mode 100644 index 00000000..d72a4d49 --- /dev/null +++ b/docs/ja/how-it-works/eligibility-and-verification.md @@ -0,0 +1,65 @@ +--- +sidebar_position: 9 +title: "利用資格と本人確認" +--- + +# 利用資格と本人確認 + +カスタマーポータルにおける利用資格と本人確認の仕組みを説明します: + +- **インターネット利用資格**(NTTサービス提供可否確認) +- **本人確認**(在留カード/身分証明書) + +## 概要 + +| 概念 | ソース | 説明 | +| --- | --- | --- | +| 商品 + 価格 | Salesforce価格表 | 単一カタログソース | +| 支払い方法 | WHMCS | Stripe経由のカード保存 | +| 注文 + フルフィルメント | Salesforce Order(+ 下流WHMCS) | 運用ワークフロー | +| インターネット利用資格 | Salesforce Account(+ Case) | 将来のインターネット注文に再利用 | +| ID確認ステータス | Salesforce Account(+ Files) | 将来の注文に再利用 | + +## インターネット利用資格(NTT住所確認) + +### 仕組み + +1. 顧客が `/account/services/internet` に移動 +2. **Check Availability**をクリック(サービス住所の登録が必要) +3. ポータルが `POST /api/services/internet/eligibility-request` を呼び出し確認画面を表示 +4. ポータルがSalesforce Opportunity(Stage = `Introduction`)を検索/作成し、エージェントレビュー用のSalesforce Caseを作成 +5. エージェントがNTTサービス提供可否を確認(手動プロセス) +6. エージェントがAccountの利用資格フィールドを更新 +7. Salesforce Flowが顧客にメール通知を送信 +8. 顧客が戻り利用可能なプランを確認 + +### キャッシュとリアルタイム更新 + +#### Redisキャッシュ(BFF) + +| キャッシュキー | 内容 | 無効化 | +| --- | --- | --- | +| `services:eligibility:{accountId}` | 利用資格ステータスと値 | Platform Event | +| `services:verification:{accountId}` | 確認ステータスと日付 | Platform Event | + +CDC駆動の無効化 + 安全TTL(デフォルト12時間)を使用。Salesforce Platform Event受信時、BFFは: + +1. 両方のキャッシュを無効化 +2. SSE `account.updated` を接続中のポータルに送信 +3. ポータルが最新データを再取得 + +#### レート制限 + +- パブリックカタログエンドポイント:IP + User-Agentごとにレート制限 +- `POST /api/services/internet/eligibility-request`:認証済み、レート制限、べき等 + +### サブスクリプションタイプ検出 + +ポータルは商品名マッチングでインターネットサブスクリプションを識別します: + +```typescript +// 以下のパターンにマッチ(大文字小文字不問): +// - "internet" +// - "sonixnet" +// - "ntt" + "fiber" +``` diff --git a/docs/ja/how-it-works/order-fulfillment.md b/docs/ja/how-it-works/order-fulfillment.md new file mode 100644 index 00000000..132441eb --- /dev/null +++ b/docs/ja/how-it-works/order-fulfillment.md @@ -0,0 +1,84 @@ +--- +sidebar_position: 6 +title: "注文フルフィルメント" +--- + +# 注文フルフィルメント + +Salesforceの注文がWHMCSのサービスとして実現される技術的プロセスを説明します。 + +## フルフィルメントトリガー + +Salesforce Change Data Capture (CDC)が注文の`Activation_Status__c`フィールドの変更を検出すると、BFFのプロビジョニングキュー(BullMQ)にジョブがエンキューされます。 + +### トリガー条件 + +- 注文ステータスが「Approved」または「Reactivate」に変更 +- べき等キーにより同一注文の重複処理を防止 +- ジョブはバックオフ付きリトライ戦略を使用 + +## プロビジョニングワークフロー + +### ステップ1:ステータス更新 + +Salesforce注文の`Activation_Status__c`を「Activating」に設定し、処理中であることを示します。 + +### ステップ2:WHMCS注文作成 + +- Salesforce OrderItemsをWHMCS商品IDにマッピング(Product2の`WH_Product_ID__c`フィールドを使用) +- WHMCS `AddOrder` APIアクションを呼び出し +- 作成されるもの:WHMCSオーダー、請求書、サブスクリプション/サービスレコード +- 支払い方法:`stripe` +- プロモコード:設定されている場合に適用 + +### ステップ3:SIMアクティベーション(SIM注文のみ) + +- eSIM注文:Freebit APIでeSIMプロファイルをアクティベート +- Physical SIM注文:エージェントが`Assign_Physical_SIM__c`にSIMを割り当て後にアクティベート + +### ステップ4:Salesforce更新 + +- `WHMCS_Order_ID__c`に作成されたWHMCS Order IDを記録 +- `Activation_Status__c`を「Active」(成功時)またはエラーステータスに更新 +- 各OrderItemに`WHMCS_Service_ID__c`を記録 +- 関連Opportunityに`WHMCS_Service_ID__c`を記録しステージを「Active」に更新 + +### ステップ5:リアルタイム通知 + +- SSEイベントを公開して接続中のクライアントに注文ステータスの変更を通知 +- アプリ内通知を作成(「Order approved」「Service activated」など) + +## エラー処理とリカバリ + +### 支払い方法不足 + +`Activation_Error_Code__c` = `PAYMENT_METHOD_MISSING`が設定されます。顧客がWHMCSで支払い方法を追加し、サポートチームが再トリガーするまでプロビジョニングは一時停止されます。 + +### WHMCS APIエラー + +- WHMCS注文作成失敗時、Salesforceステータスをロールバック +- エラーコードとメッセージをSalesforceに記録 +- 部分的なWHMCS注文は保持されません + +### Freebit/SIMエラー + +- エラーをログに記録しactivation statusに表示 +- 注文はSalesforceにリトライ用に保持 + +### 一時的エラー + +- BullMQキューでエクスポネンシャルバックオフ付きリトライ +- 最大リトライ回数後にフルフィルメント失敗としてマーク + +## 双方向リンク + +フルフィルメント後、システムは双方向にリンクされます: + +| 方向 | フィールド | 場所 | 値 | +| --- | --- | --- | --- | +| SF Order → WHMCS | `WHMCS_Order_ID__c` | Salesforce Order | WHMCS Order ID | +| SF OrderItem → WHMCS | `WHMCS_Service_ID__c` | Salesforce OrderItem | WHMCSサービスID | +| SF Opportunity → WHMCS | `WHMCS_Service_ID__c` | Salesforce Opportunity | メインWHMCSサービスID | +| WHMCS Service → SF | OpportunityIdカスタムフィールド | WHMCS Product/Service | Salesforce Opportunity ID | + +この双方向リンクにより解約が機能します。ポータルはWHMCSサービスIDでOpportunityを検索しステージを更新します。 diff --git a/docs/ja/how-it-works/ordering-provisioning.md b/docs/ja/how-it-works/ordering-provisioning.md new file mode 100644 index 00000000..1c640a63 --- /dev/null +++ b/docs/ja/how-it-works/ordering-provisioning.md @@ -0,0 +1,83 @@ +--- +sidebar_position: 5 +title: "注文とプロビジョニング(詳細)" +--- + +# 注文とプロビジョニング(詳細ガイド) + +注文作成からフルフィルメントまでの詳細な技術フローを説明します。 + +## 注文作成フロー + +### チェックアウトセッション + +- ユーザーがプランを設定すると、チェックアウトセッションがサーバーサイドで構築されます +- セッションには選択されたプラン、アドオン、設置オプション、すべての設定詳細が含まれます +- セッションには有効期限があり、長時間経過すると設定プロセスのやり直しが必要 + +### 事前検証 + +1. ユーザーにWHMCSクライアントマッピングが存在すること +2. WHMCSに少なくとも1つの支払い方法が存在すること +3. インターネット注文の場合、WHMCSにアクティブなインターネットサービスがないこと +4. 在留カードが提出済み(SubmittedまたはVerified)であること + +### Salesforce Order構造 + +**Orderオブジェクトフィールド:** + +| フィールド | API名 | 値 | +| --- | --- | --- | +| AccountId | `AccountId` | IDマッピングから | +| Status | `Status` | "Draft" | +| Type | `Type__c` | Internet / SIM / VPN | +| EffectiveDate | `EffectiveDate` | 本日の日付 | +| Pricebook2Id | `Pricebook2Id` | ポータル価格表ID | + +**OrderItemレコード:** +- Salesforce Composite API経由で作成 +- 各SKUに対してPricebookEntryIdからカタログ検索 +- UnitPriceとQuantityを価格表から設定 + +### 住所スナップショット + +注文には注文時点の住所が保存されます。後でプロフィールの住所が変更されても、注文の住所は変わりません。 + +## フルフィルメントプロセス + +### トリガー + +- Salesforce CDCが注文ステータスの変更を検出(例:「Approved」「Reactivate」) +- イベントがBFFのプロビジョニングキュー(BullMQ)に送信 +- べき等キーによる重複処理防止 + +### プロビジョニングステップ + +1. Salesforce `Activation_Status__c` = "Activating" に更新 +2. Salesforce OrderItemsをWHMCS商品にマッピング +3. WHMCS `AddOrder` APIアクションを呼び出し:WHMCS注文、請求書、サブスクリプションレコードを作成 +4. SIM注文の場合:FreebitアクティベーションAPIを呼び出し +5. SalesforceをWHMCS Order ID、Activation Status、エラー情報で更新 +6. 注文キャッシュを無効化 +7. UI ライブ更新用のリアルタイムイベントを公開 + +### 分散トランザクション + +フルフィルメントはロールバック付きの分散トランザクションパターンを使用: +- Salesforceステータス更新後にWHMCS作成が失敗 → Salesforceステータスをロールバック +- WHMCSに部分的な注文は残りません + +### エラー処理 + +| シナリオ | 動作 | +| --- | --- | +| 支払い方法不足 | Salesforceに`PAYMENT_METHOD_MISSING`で一時停止 | +| WHMCS APIエラー | 失敗としてマーク、Salesforceステータスをロールバック | +| Freebitエラー | 失敗としてマーク、エラーをSalesforceに記録 | +| 一時的エラー | BullMQキューでバックオフ付きリトライ | + +## リアルタイム注文追跡 + +- 顧客は注文イベントストリーム(SSE)に接続可能 +- Salesforceでステータスが変更されると、UIが手動更新なしで自動更新 +- 注文詳細ページはServer-Sent Events経由でライブ接続を維持 diff --git a/docs/ja/how-it-works/orders-and-provisioning.md b/docs/ja/how-it-works/orders-and-provisioning.md new file mode 100644 index 00000000..9370ac73 --- /dev/null +++ b/docs/ja/how-it-works/orders-and-provisioning.md @@ -0,0 +1,41 @@ +--- +sidebar_position: 4 +title: "注文とプロビジョニング" +--- + +# 注文とプロビジョニング + +顧客の注文がどのように作成され、どこに保存され、フルフィルメントがどのようにWHMCSに到達するかを説明します。 + +## 注文作成(顧客チェックアウト) + +- 注文はポータル価格表を使用してSalesforceに作成されます。レコードに含まれるもの: + - AccountId(ユーザーのマッピングから) + - 注文タイプ(Internet、SIM、VPN) + - アクティベーション設定 + - 顧客プロフィール/チェックアウトフォームからの住所スナップショット + - ステータスは「Pending Review」で開始 +- 作成前にWHMCS支払い方法の存在と(インターネットの場合)WHMCSにアクティブなインターネットサービスがないことを確認。 + +## WHMCSへのフルフィルメント(Salesforceトリガー) + +- Salesforce Change Data Capture (CDC)が注文ステータスの変更を監視。トリガー時にプロビジョニングジョブをキューに入れます。 +- フルフィルメントステップ: + 1. Salesforce注文のactivation statusを「Activating」に設定 + 2. Salesforce注文アイテムをWHMCS商品にマッピングし、WHMCS AddOrderを呼び出して請求注文を作成 + 3. SIM注文の場合、FreebitでSIMをアクティベート + 4. SalesforceをWHMCS Order ID、activation status/結果、エラーコード/メッセージで更新 + 5. ライブイベントを公開してUIが注文ステータスをリアルタイム更新(SSE) + +## データの所在 + +- Salesforce:マスター注文レコード、ステータス、住所スナップショット、商品明細 +- WHMCS:フルフィルメント時に作成される請求コピー(請求書/サブスクリプション用) +- Freebit:SIMプロビジョニングタスクのみ + +## エラー時の動作 + +- 支払い方法不足:フルフィルメントを一時停止。`Activation_Error_Code__c` = `PAYMENT_METHOD_MISSING` +- 検証/マッピング問題:停止してエラーを返却。Salesforceにエラーコード/メッセージを記録 +- WHMCSエラー:失敗としてマークし、エラーをSalesforceに書き戻し +- Freebit/SIMエラー:ログに記録しactivation statusに表示。リトライ用にSalesforceに注文を保持 diff --git a/docs/ja/how-it-works/services-and-checkout.md b/docs/ja/how-it-works/services-and-checkout.md new file mode 100644 index 00000000..bc28cd95 --- /dev/null +++ b/docs/ja/how-it-works/services-and-checkout.md @@ -0,0 +1,45 @@ +--- +sidebar_position: 3 +title: "サービスとチェックアウト" +--- + +# サービスとチェックアウト + +商品データの取得元、検証内容、鮮度の維持方法を説明します。 + +## 商品ソース + +- 商品と価格はWHMCSではなく、Salesforceのポータル価格表(`PORTAL_PRICEBOOK_ID`)から取得。 +- カテゴリ:Internet、VPN、SIM/モバイル。各SKUはSalesforce Product2 + PricebookEntryレコード。 +- ポータルカテゴリ向けの商品のみ取得。名前、SKU、価格のソースはSalesforce。 + +### SIMファミリープラン + +- ユーザーがWHMCSにアクティブなSIMサービスを持つ場合、SIMファミリー/割引プランも表示。 +- SIMがない場合、通常プランのみ表示。 + +## 利用資格 & 検証 + +- インターネット注文はSalesforceに保存されたアカウント固有の利用資格を確認。 +- チェックアウト時の確認事項: + - WHMCSクライアントマッピングと少なくとも1つの支払い方法の存在 + - 選択されたSKUがSalesforce価格表に存在 + - インターネット注文の場合、WHMCSにアクティブなインターネットサービスがないこと(重複ブロック) + +## チェックアウトで取得されるデータ + +- 住所スナップショット:顧客の住所をSalesforce Order請求フィールドにコピー。 +- アクティベーション設定:Salesforce Orderに保存。 +- カードデータはポータルに保存しません。WHMCSに支払い方法が存在することのみ検証。 + +## 商品カタログのキャッシュ + +- 商品カタログデータはSalesforce Change Data Capture (CDC)イベントを使用。時間ベースの有効期限なし。 +- Volatileなカタログデータは60秒TTL。 +- アカウントごとの利用資格はTTLなしでキャッシュ、Salesforce変更時にクリア。 + +## エラー時の動作 + +- 支払い方法不足:「支払い方法を追加してください」メッセージでチェックアウトをブロック。 +- 利用資格なしまたはインターネット重複:注文を停止し理由を説明。 +- Salesforce価格表の問題:「サービスが利用できません、後でやり直してください」を返却。 diff --git a/docs/ja/how-it-works/subscriptions.md b/docs/ja/how-it-works/subscriptions.md new file mode 100644 index 00000000..bc7dff83 --- /dev/null +++ b/docs/ja/how-it-works/subscriptions.md @@ -0,0 +1,28 @@ +--- +sidebar_position: 8 +title: "サブスクリプション" +--- + +# サブスクリプションとサービス + +アクティブなサービスの表示と更新方法を説明します。 + +## データソース + +- サブスクリプション(サービス/商品)はマッピングされたクライアントID経由でWHMCSから読み取ります。 +- ステータス(Active、Pending、Suspended、Cancelled、Completed)はWHMCSから直接取得。 + +## 鮮度 + +- サブスクリプション一覧は5分、個別サブスクリプションは10分キャッシュ。 +- WHMCSが変更を通知(Webhook)するか、関連サービスに影響する可能性のあるプロフィール/住所データを更新した際にキャッシュをクリア。 + +## 表示内容 + +- WHMCSに保存されているサービス名、ステータス、開始日、金額、通貨。 +- サブスクリプションはダッシュボード統計にも使用(例:アクティブ数)。統計はキャッシュ無効化後に更新。 + +## エラー時の動作 + +- WHMCSマッピング不足:空のリストではなく明確なエラーを返却。 +- WHMCS利用不可:「後でやり直してください」を表示。失敗をキャッシュしません。 diff --git a/docs/ja/how-it-works/support-cases.md b/docs/ja/how-it-works/support-cases.md new file mode 100644 index 00000000..c9e6de85 --- /dev/null +++ b/docs/ja/how-it-works/support-cases.md @@ -0,0 +1,70 @@ +--- +sidebar_position: 10 +title: "サポートケース" +--- + +# サポートケース + +ポータルが顧客のサポートケースを表示・作成する方法を説明します。 + +## データソース & スコープ + +- ケースはSalesforceで直接読み書きされます。Originは「Portal Support」に設定。 +- ポータルは顧客のマッピングされたSalesforce Accountのケースのみ表示し、顧客間のデータ漏洩を防ぎます。 + +## ケースの作成 + +- 必須入力:件名と説明。オプション:カテゴリ/タイプと優先度。 +- ポータルがSalesforceにStatus = Newでケースを作成。 + +## ケースの表示 + +- ケースはRedisキャッシュ付きでSalesforceから取得。 +- オープン vs クローズの件数をサマリー表示し、高優先度ケースをハイライト。 + +## Platform Eventsによるリアルタイム更新 + +サポートケースはPlatform Eventsを使用してリアルタイムキャッシュ無効化を行います: + +**Platform Event:** `Case_Status_Update__e` + +**BFFの動作:** +1. `support:cases:{accountId}` キャッシュを無効化 +2. `support:messages:{caseId}` キャッシュを無効化 +3. SSE `support.case.changed` を接続中のポータルに送信 +4. ポータルがケースデータを自動的に再取得 + +## キャッシュ戦略 + +| キャッシュキー | TTL | 無効化タイミング | +| --- | --- | --- | +| `support:cases:{accountId}` | 2分 | ケース作成時 | +| `support:messages:{caseId}` | 1分 | コメント追加時 | + +## 顧客向けステータスマッピング + +| Salesforceステータス | ポータル表示 | 意味 | +| --- | --- | --- | +| 新規 (New) | New | 新しいケース、未レビュー | +| 対応中 (In Progress) | In Progress | サポートが対応中 | +| Awaiting Approval | In Progress | 内部ワークフロー(非表示) | +| 完了済み (Replied) | Awaiting Customer | サポートが返信済み、顧客の回答待ち | +| Closed | Closed | ケースクローズ | + +## ケース会話(メッセージ) + +ケース詳細ビューには統合された会話タイムラインが表示されます: + +1. **EmailMessages** — ケースに添付されたメール交換 +2. **CaseComments** — 顧客またはエージェントが追加したコメント + +### 機能 + +- **日付グループ化**:メッセージは日付でグループ化(Today、Yesterday、日付表示など) +- **添付ファイルインジケーター**:添付ファイル付きメッセージにクリップアイコン表示 +- **メール本文のクリーニング**:引用返信チェーンを自動的にストリップし最新の返信のみ表示 + +## エラー時の動作 + +- Salesforce利用不可:「サポートシステムが利用できません、後でやり直してください」を表示。 +- ケースが見つからないまたは別のアカウントに属する:情報漏洩を防ぐため「ケースが見つかりません」で応答。 diff --git a/docs/ja/how-it-works/system-overview.md b/docs/ja/how-it-works/system-overview.md new file mode 100644 index 00000000..cb0d3c92 --- /dev/null +++ b/docs/ja/how-it-works/system-overview.md @@ -0,0 +1,56 @@ +--- +sidebar_position: 1 +title: "システム概要" +--- + +# ポータルの仕組み(概要) + +目的:ポータルの機能、各システムが管理するデータ、データの鮮度管理方法を説明します。 + +## コアコンポーネントと責務 + +- Portal UI (Next.js) + BFF API (NestJS):すべてのユーザートラフィックを処理し、外部システムを呼び出します。 +- Postgres:ポータルユーザーとクロスシステムマッピング `user_id ↔ whmcs_client_id ↔ sf_account_id` を保存。 +- Redisキャッシュ:**グローバル**キャッシュ(商品カタログなど)と**アカウントスコープ**キャッシュ(利用資格など)の組み合わせで負荷を軽減し、顧客データの混在を防ぎます。 +- WHMCS:請求の管理システム(クライアント、住所、請求書、支払い方法、サブスクリプション)。 +- Salesforce:CRM(アカウント/コンタクト)、商品カタログ/価格表、注文、サポートケースの管理システム。 +- Freebit:SIMプロビジョニングのみ、モバイル/SIM注文のフルフィルメント時に使用。 + +## 高レベルデータフロー + +- **サインアップ**:ポータルがSalesforceでCustomer Numberを確認 → WHMCSクライアント(請求アカウント)を作成 → ポータルユーザー + マッピングを保存 → Salesforceをポータルステータス + WHMCS IDで更新。 +- **ログイン/リンク**:既存WHMCSユーザーがWHMCS認証情報を検証。ポータルユーザーを作成し、IDをマッピングし、Salesforceアカウントをポータルアクティブとしてマーク。 +- **サービス & チェックアウト**:商品/価格はSalesforceのポータル価格表から取得。利用資格はアカウントごとに確認。チェックアウト前にWHMCSの支払い方法が必要。 +- **注文**:Salesforceに住所スナップショット付きで作成。Salesforce変更イベントがフルフィルメントをトリガーし、対応するWHMCS注文を作成しSalesforceステータスを更新。 +- **請求**:請求書、支払い方法、サブスクリプションはWHMCSから読み取り。WHMCS内での請求書支払い用に安全なSSOリンクを生成。 +- **サポート**:ケースはOrigin = "Portal Website"でSalesforceに直接作成/読み取り。 + +## データ所有権チートシート + +- ID & セッション:Portal DB(ハッシュ化パスワード、WHMCS/SF認証情報の保存なし) +- 請求プロフィール & 住所:WHMCS(権威的ソース)。ポータルが変更をWHMCSに書き戻し。 +- 注文 & 注文ステータス:Salesforce(真のソース)。フルフィルメント時にWHMCSが請求/プロビジョニングコピーを受信。 +- サポートケース:Salesforce(ポータルはアカウントのケースのみフィルター)。 + +## キャッシュ & 鮮度(Redis) + +- サービスカタログ:イベント駆動(Salesforce CDC)+ 12時間の安全TTL。「volatile」データは60秒TTL。 +- 注文:イベント駆動(Salesforce CDC)、TTLなし。 +- 請求書:一覧90秒キャッシュ、詳細5分キャッシュ。 +- サブスクリプション/サービス:一覧5分、個別10分キャッシュ。 +- 支払い方法:15分キャッシュ。決済ゲートウェイ一覧:1時間キャッシュ。 +- WHMCSクライアントプロフィール:30分キャッシュ。 +- サポートケース:Salesforceからライブ読み取り(キャッシュなし)。 + +## エラー時の動作 + +- 安全に失敗し明確なメッセージを表示:Customer Number不明、重複アカウント、支払い方法不足など。 +- WHMCS/Salesforceが一時的に利用不可の場合、部分的なデータではなく「後でやり直してください」メッセージを表示。 +- フルフィルメントがエラーコード/メッセージをSalesforceに書き戻し、チームがプロビジョニング停止の理由を確認可能。 + +## パブリック vs アカウントAPIの境界 + +BFFは2種類のサービスカタログエンドポイントを公開: + +- **パブリックカタログ(パーソナライズなし)**:`GET /api/public/services/*` — Cookie/トークンを無視。パブリックキャッシュ可能。 +- **アカウントカタログ(認証 + パーソナライズ)**:`GET /api/account/services/*` — 認証が必要。アカウント固有のカタログバリアント返却可能。 diff --git a/docs/ja/uat/address-management.md b/docs/ja/uat/address-management.md new file mode 100644 index 00000000..7a16c352 --- /dev/null +++ b/docs/ja/uat/address-management.md @@ -0,0 +1,97 @@ +--- +sidebar_position: 13 +title: "住所管理" +--- + +# 住所管理 + +## 概要 + +このガイドでは、ポータルで住所を表示・編集する方法を説明します。住所は日本固有のフォーマットを使用し、日本語(Salesforce用)とローマ字英語(WHMCS用)の両方で保存されます。ポータルは日本郵便APIと連携して郵便番号から住所を検索し、正確性を確保します。 + +## ポータルフロー + +### 現在の住所の確認 + +1. **Account > Settings**(`/account/settings`)に移動します。 +2. **Address Information**カードに現在の住所が表示されます。 +3. 右上に**Edit**ボタンがあります。 +4. 住所が未登録の場合、「No address on file」と**Add Address**ボタンが表示されます。 + +### 住所の編集 + +1. **Edit**をクリックするとステップバイステップの住所フォームに切り替わります。 +2. フォームは**段階的開示**を使用 — 各ステップは前のステップが完了した後に表示されます。 + +#### ステップ1:郵便番号の入力 + +1. 7桁の日本の郵便番号を入力(例:「100-0001」または「1000001」)。 +2. 有効な7桁のコードが入力されると、ポータルが日本郵便APIを使用して住所を自動検索。 +3. 郵便番号が見つかると**「Verified」バッジ**が表示され、検索結果が表示されます: + - **都道府県**(ローマ字と日本語の両方、例:「Tokyo / 東京都」) + - **市区町村**(両方の言語、例:「Minato-ku / 港区」) + - **町名**(両方の言語、例:「Higashiazabu / 東麻布」) +4. これらのフィールドは読み取り専用で手動編集はできません。 + +#### ステップ2:番地 + +1. **Street Address**フィールドが表示されます。 +2. 丁目-番地-号のハイフン区切り形式で入力(例:「1-5-3」)。 +3. ヘルパーテキスト:「Enter chome-banchi-go (e.g., 1-5-3)」 + +#### ステップ3:住居タイプ + +1. **Residence Type**セレクターが表示されます。 +2. 2つのオプション: + - **House**(一戸建てアイコン) + - **Apartment**(建物アイコン) + +#### ステップ4:建物の詳細 + +1. **Building Name**(必須):建物名または住居名を入力。 +2. **Room Number**(アパートの場合のみ必須):部屋番号を入力。 + +#### 完了 + +1. すべてのフィールド入力後、緑の**「Address Complete」**メッセージが表示。 +2. **Save Address**をクリックして変更を送信。 + +## WHMCSで起こること + +- WHMCSは顧客の請求先住所の**管理システム**です。 +- 住所保存時、ポータルがWHMCSに書き込みキャッシュをクリアします。 +- WHMCSフィールドへのマッピング: + +| WHMCSフィールド | 内容 | 例 | +| --- | --- | --- | +| address1 | 建物名 + 部屋番号 | "Sunshine Mansion 201" | +| address2 | 町名 + 番地(ローマ字) | "Higashiazabu 1-5-3" | +| city | 市区町村(ローマ字) | "Minato-ku" | +| state | 都道府県(ローマ字) | "Tokyo" | +| postcode | 郵便番号 | "1060044" | +| country | 国コード | "JP" | + +## Salesforceで起こること + +- SalesforceのContactレコードに**日本語版**の住所が更新されます: + +| Salesforceフィールド | 内容 | 例 | +| --- | --- | --- | +| MailingStreet | 町名 + 番地(日本語) | "東麻布1-5-3" | +| MailingCity | 市区町村(日本語) | "港区" | +| MailingState | 都道府県(日本語) | "東京都" | +| MailingPostalCode | 郵便番号 | "1060044" | +| MailingCountry | 国 | "Japan" | +| BuildingName__c | 建物名 | "Sunshine Mansion" | +| RoomNumber__c | 部屋番号 | "201" | + +## 確認すべき重要事項 + +- 有効な郵便番号で都道府県、市区町村、町名が日本語と英語の両方で正しく自動入力されること +- 無効な郵便番号でエラーメッセージが表示されること +- 段階的開示(各ステップが前のステップ完了後にのみ表示)が機能すること +- 「Apartment」選択時に部屋番号フィールドが表示、「House」選択時に非表示であること +- 保存後、WHMCSクライアントプロフィールにローマ字英語で正しく反映されること +- 保存後、Salesforce Contactレコードに日本語で正しく反映されること +- 保存後、ポータルの住所カードが即座に更新されること +- 「Cancel」クリックで変更が破棄され以前の値に戻ること diff --git a/docs/ja/uat/appendix-cross-reference.md b/docs/ja/uat/appendix-cross-reference.md new file mode 100644 index 00000000..3c15576b --- /dev/null +++ b/docs/ja/uat/appendix-cross-reference.md @@ -0,0 +1,132 @@ +--- +sidebar_position: 14 +title: "付録:クロスリファレンス" +--- + +# クロスリファレンス付録 + +カスタマーポータルの機能をWHMCSとSalesforceの対応するレコードにマッピングするクイックリファレンステーブルです。確認が必要な箇所を素早く調べる際に使用してください。 + +## システム間の連携方法 + +### 顧客リンク(サインアップ時に確立) + +``` +Portal User ID ←→ WHMCS Client ID ←→ Salesforce Account ID + (ポータルデータベースがこの三方向マッピングを保存) +``` + +| システム | フィールド | 保存内容 | +| --- | --- | --- | +| WHMCSクライアント | カスタムフィールド #198 ("Customer Number") | SalesforceアカウントNumber(`SF_Account_No__c`) | +| Salesforceアカウント | `WH_Account__c` | WHMCSクライアントID("#1234 - Customer Name"形式) | +| Salesforceアカウント | `SF_Account_No__c` | Customer Number(WHMCSフィールド#198と同値) | + +### サブスクリプション/Opportunityリンク(プロビジョニング時に確立) + +``` +WHMCS Service ←→ Salesforce Opportunity +``` + +| システム | フィールド | 保存内容 | +| --- | --- | --- | +| WHMCSサービス | カスタムフィールド "OpportunityId" | Salesforce Opportunity ID | +| Salesforce Opportunity | `WHMCS_Service_ID__c` | WHMCSサービス/商品ID | + +### Orderリンク(プロビジョニング時に確立) + +| システム | フィールド | 保存内容 | +| --- | --- | --- | +| Salesforce Order | `WHMCS_Order_ID__c` | WHMCS Order ID | +| Salesforce OrderItem | `WHMCS_Service_ID__c` | 各明細のWHMCSサービスID | + +--- + +## Salesforce Accountフィールドリファレンス + +### ポータル固有フィールド + +| フィールドラベル | API名 | 設定者 | タイミング | 値 | +| --- | --- | --- | --- | --- | +| Customer Number | `SF_Account_No__c` | Salesforce | アカウント作成 | 一意の識別子 | +| Portal Status | `Portal_Status__c` | ポータル | サインアップ | "Active" | +| Portal Registration Source | `Portal_Registration_Source__c` | ポータル | サインアップ | "New Signup"または"Migrated" | +| Portal Last Sign-In | `Portal_Last_SignIn__c` | ポータル | 各ログイン | タイムスタンプ | +| WHMCS Account | `WH_Account__c` | ポータル | サインアップ | "#1234 - Customer Name"形式 | + +### インターネット利用資格フィールド + +| フィールドラベル | API名 | 設定者 | タイミング | +| --- | --- | --- | --- | +| Internet Eligibility | `Internet_Eligibility__c` | SFエージェント | 手動NTT確認後 | +| Eligibility Status | `Internet_Eligibility_Status__c` | SFエージェント | 確認後 | +| Request Date | `Internet_Eligibility_Request_Date_Time__c` | ポータル | 顧客リクエスト時 | +| Checked Date | `Internet_Eligibility_Checked_Date_Time__c` | SFエージェント | エージェント確認時 | + +### ID確認フィールド + +| フィールドラベル | API名 | 設定者 | タイミング | +| --- | --- | --- | --- | +| Verification Status | `Id_Verification_Status__c` | ポータル/SFエージェント | アップロード/確認 | +| Submitted Date | `Id_Verification_Submitted_Date_Time__c` | ポータル | 書類アップロード時 | +| Verified Date | `Id_Verification_Verified_Date_Time__c` | SFエージェント | 承認後 | +| Rejection Message | `Id_Verification_Rejection_Message__c` | SFエージェント | 却下時 | + +--- + +## Salesforce Opportunityステージリファレンス + +| ステージ | 意味 | +| --- | --- | +| Introduction | 初期関心(利用資格確認リクエストなど) | +| Ready | 利用資格ありで注文準備完了 | +| Post Processing | 注文済み、確認中 | +| Active | サービス稼働中 | +| △Cancelling | 解約リクエスト済み、請求期間終了まで稼働 | +| 〇Cancelled | サービス解約済み | +| Void | 未対応または利用資格なし | + +--- + +## ポータルアクション → WHMCSレコード + +| ポータルアクション | WHMCSオブジェクト | 確認フィールド | +| --- | --- | --- | +| サインアップ(新規顧客) | Client | 名前、メール、電話、住所、カスタムフィールド#198 | +| サインアップ(WHMCS移行) | Client(既存) | 重複なし確認、フィールド#198にSFアカウントNumber | +| プロフィール編集 | Client | メール、電話番号の更新 | +| 住所編集 | Client | Address 1、2、City、State、Postcode、Country | +| 注文プロビジョニング | Product/Service | 商品名、ステータス(Active)、OpportunityId | +| SIMプラン変更 | Product/Service | 商品名とAmount更新(翌月1日有効) | +| SIMデータチャージ | Invoice | チャージ金額のInvoice(500 JPY/GB) | +| 音声機能トグル | アドオンサービス | Voice Mail(pid 119)、Call Waiting(pid 123) | +| 請求書表示 | Invoices | Invoice Number、Status、Amount、Due Date | +| サブスクリプション解約 | Product/Service | 請求期間終了時にCancelledに変更 | + +## ポータルアクション → Salesforceレコード + +| ポータルアクション | Salesforceオブジェクト | 確認フィールド | +| --- | --- | --- | +| サインアップ | Account | `Portal_Status__c` = Active、`WH_Account__c` | +| 注文 | Order | `Status`、`Type`、`OpportunityId` | +| 注文 | Opportunity | `StageName` = "Post Processing" | +| サポートケース作成 | Case | Origin = "Portal Support" | +| 在留カードアップロード | Account | `Id_Verification_Status__c` = "Submitted" | +| SIM解約 | Opportunity | `StageName` = "△Cancelling" | +| インターネット解約 | Opportunity | `StageName` = "△Cancelling"、`LineReturn__c` | + +## ポータル機能 → データソース + +| ポータル機能 | 主要データソース | 二次ソース | +| --- | --- | --- | +| ダッシュボード - 有効サービス数 | WHMCS | — | +| ダッシュボード - 未解決ケース数 | Salesforce | — | +| サービスカタログ | Salesforce(Product2) | — | +| 利用資格ステータス | Salesforce(Accountフィールド) | — | +| サブスクリプション一覧 | WHMCS | — | +| SIM管理 | Freebit | WHMCS(請求) | +| 請求書 | WHMCS | — | +| サポートケース | Salesforce | — | +| 本人確認 | Salesforce | — | +| 通知 | ポータルDB | Salesforce Platform Events | +| 住所 | WHMCS | 日本郵便API | diff --git a/docs/ja/uat/billing-and-invoices.md b/docs/ja/uat/billing-and-invoices.md new file mode 100644 index 00000000..c0ae47de --- /dev/null +++ b/docs/ja/uat/billing-and-invoices.md @@ -0,0 +1,80 @@ +--- +sidebar_position: 9 +title: "請求と請求書" +--- + +# 請求と請求書 + +## 概要 + +このガイドでは、ポータルで請求情報を表示・管理する方法を説明します。すべての請求データ(請求書、支払い方法、請求サマリー)はWHMCSから取得されます。ポータルはデータを読み取りますがローカルには保存しません。請求書の閲覧、フィルタリング・検索、詳細の表示、PDFダウンロード、未払い請求書の支払いが可能です。 + +## ポータルフロー + +### 請求書一覧の表示 + +1. **Account > Billing > Invoices**(`/account/billing/invoices`)に移動します。 +2. 上部に**サマリー統計バー**が表示されます:合計、支払い済み(緑)、未払い(琥珀色)、延滞(琥珀色)。 +3. **検索・フィルターバー**:請求書番号や説明での検索、ステータスフィルター(All、Paid、Unpaid、Cancelled、Overdue、Collections)。 +4. 各請求書行に請求書番号、説明、ステータスバッジ、合計金額、支払期日、発行日が表示されます。 + +### 請求書ステータスバッジ + +| ステータス | 色 | 意味 | +| --- | --- | --- | +| Paid | 緑 | 全額支払い済み | +| Unpaid | 琥珀色 | 支払い期限前の未払い | +| Overdue | 赤 | 支払い期限超過 | +| Draft | グレー | 下書き、未発行 | +| Cancelled | グレー | キャンセル済み | +| Refunded | 青 | 返金済み | +| Collections | 赤 | 督促に送付済み | + +### 請求書詳細の表示 + +1. 一覧の請求書行をクリックして詳細ページを開きます。 +2. 詳細ページの表示内容: + - **サマリーバー**:合計金額、通貨コード、ステータスバッジ、支払期日、請求書番号、発行日 + - **Download PDF**ボタン — 安全なWHMCSリンクを生成しPDFをダウンロード + - **Pay Now**ボタン — UnpaidまたはOverdueの請求書のみ表示。新しいタブでWHMCS決済ページを開く + - **支払い済み**の場合、緑の「Payment received」バナーが支払日と共に表示 +3. **明細項目**セクションに各項目の説明、タイプ、金額が表示されます。 +4. **合計セクション**に小計、税金、合計が表示されます。 + +### 請求書PDFのダウンロード + +1. 詳細ページから**Download PDF**をクリック。 +2. ポータルがWHMCS請求書ページへの安全なSSO(シングルサインオン)リンクを生成します。 +3. ブラウザがPDFのダウンロードを開始します。 + +### 請求書の支払い + +1. 未払いまたは延滞の請求書詳細ページから**Pay Now**をクリック。 +2. 新しいブラウザタブでWHMCS決済ページが開きます。 +3. 保存された支払い方法を使用して支払いを完了できます。 + +### 支払い方法の管理 + +1. 支払い方法はWHMCS(通常Stripe経由)に保存されています。 +2. **Manage Payment Methods**ボタンでSSO経由でWHMCSの支払い方法ページを開けます。 + +## WHMCSで起こること + +- **請求書**:すべての請求書はWHMCSのクライアントアカウントに保存されます。 +- **請求書ステータス**:WHMCSがステータスライフサイクルを管理(Draft、Unpaid、Paid、Overdue、Cancelled、Refunded、Collections)。 +- **取引**:支払い時にWHMCSにTransactionレコードが作成されます。 +- **SSOリンク**:ポータルが一時的なSSOリンクを生成し、別途WHMCSログインなしで請求書の表示/ダウンロード/支払いが可能。 + +## Salesforceで起こること + +- Salesforceは請求データの管理システムでは**ありません**。請求書と支払いは完全にWHMCSに存在します。 + +## 確認すべき重要事項 + +- 請求書一覧が正しく読み込まれ、サマリー統計がWHMCS管理画面と一致すること +- ステータスフィルターと請求書番号検索が正しく機能すること +- 請求書詳細(合計金額、明細項目、税金)がWHMCS管理画面と一致すること +- PDFダウンロードが正常に機能すること +- Pay Nowが新しいタブでWHMCS決済ページを開くこと +- ステータスバッジの色分けが正確であること(Paid=緑、Unpaid=琥珀色、Overdue=赤) +- ページネーションが10件超の請求書で正常に機能すること diff --git a/docs/ja/uat/browsing-services.md b/docs/ja/uat/browsing-services.md new file mode 100644 index 00000000..c931a7e9 --- /dev/null +++ b/docs/ja/uat/browsing-services.md @@ -0,0 +1,88 @@ +--- +sidebar_position: 4 +title: "サービスの閲覧" +--- + +# サービスの閲覧 + +## 概要 + +このガイドでは、カスタマーポータルでサービスカタログを閲覧する方法を説明します。ポータルでは、インターネット(光ファイバー)、SIM/eSIM(モバイルデータ・音声)、VPNルーター(ストリーミングアクセス)の3つの主要な注文可能サービスタイプに加え、ビジネスとオンサイトサポートの情報ページを提供しています。サービスはSalesforceの商品カタログから取得され、ログインユーザーとゲストで表示が異なります。 + +## ポータルフロー + +### サービスランディングページ + +1. **ゲストとして**、`/services` にアクセスします。「Our Services」というヒーローセクションと、バリュープロポジションバッジ、サービスカードのグリッドが表示されます。 +2. **ログインユーザーとして**、`/account/services` にアクセスします。同じサービスカードが「Services」タイトルのアカウントページレイアウトで表示されます。 +3. ページには2行に5つのサービスカードが表示されます: + - **上段(大きなカード):** Internet(光ファイバー)とSIM & eSIM(モバイルデータ)。SIMカードには「1st month free」バッジ。 + - **下段(小さなカード):** VPN Router、Business、Onsite Support。 +4. 各カードにはサービス名、サブタイトル、説明、主要機能ピル、「View Plans」リンクが表示されます。 + +### インターネットプランの閲覧 + +#### 公開ビュー(未ログイン) + +1. `/services/internet` にアクセスします。速度オファリング(戸建て向けHikari Cross、マンション向けHikari)、プラン比較、「How It Works」セクションのあるマーケティングスタイルのページが表示されます。 +2. プランはオファリングタイプ別にグループ化されています。 +3. 各オファリングには速度ティア(Standard、Pro、Platinum)と月額料金、セットアップ料金が表示されます。 + +#### アカウントビュー(ログイン済み) + +1. `/account/services/internet` にアクセスします。表示はインターネット利用資格ステータスに依存します: + + **未リクエスト:** 「Check Availability」ボタン付きの公開マーケティングコンテンツが表示されます。 + + **審査中:** 「Checking Availability」ステータスバッジ付きの「Your Internet Options」見出しが表示されます。1〜2営業日以内にチームが住所を確認する旨のメッセージが表示されます。 + + **利用可能:** 利用可能な速度(例:「Cross 10G」)のステータスバッジが表示されます。住所で利用可能なオファリングカードとティア詳細が表示されます。 + + **利用不可:** 住所でインターネットサービスが利用できない旨のメッセージが表示されます。 + +2. 既にアクティブなインターネットサブスクリプションがある場合、「Active subscription found」の警告バナーが表示されます。 + +### SIM & eSIMプランの閲覧 + +1. `/services/sim`(公開)または `/account/services/sim`(ログイン済み)にアクセスします。 +2. プランは上部の3つのタブで構成されています: + - **Data + Voice**(デフォルト):モバイルデータ、SMS、音声通話を含むプラン。NTTドコモネットワーク。 + - **Data Only**:データとSMSのみ。 + - **Voice Only**:音声サービスのみ。 +3. 各プランカードにはプラン名、データ容量、月額料金、音声通話の有無、ファミリー割引の有無が表示されます。 + +### VPNルータープランの閲覧 + +1. `/services/vpn`(公開)または `/account/services/vpn`(ログイン済み)にアクセスします。 +2. US(サンフランシスコ)とUK(ロンドン)の2つのリージョンオプションが表示されます。 +3. 各プランカードにはリージョン、月額料金、主要機能が表示されます。 + +### インターネット利用資格確認(公開フロー) + +1. ゲストユーザーがインターネットの利用可能状況を確認する場合、複数ステップのプロセスがあります: + - **ステップ1 - 詳細入力**:メールアドレスとオプションの住所を提供 + - **ステップ2 - メール認証**:ワンタイムコードによる認証 + - **ステップ3 - アカウント作成**(必要な場合) + - **ステップ4 - 完了**:リクエストが送信され確認メッセージが表示 + +## Salesforceで起こること + +- **商品カタログ**:すべてのサービスプラン、アドオン、設置オプション、アクティベーション料金はSalesforceの**Product2**レコードとして保存されます。主要フィールド:`SKU__c`、`Portal_Category__c`、`Portal_Catalog__c`、`Portal_Accessible__c`など。 +- **価格表エントリ**:各Product2に関連する**PricebookEntry**レコードが単価を定義します。 +- **インターネット利用資格**:利用資格ステータスはSalesforceの**Account**レコードのフィールドに保存されます(`Internet_Eligibility__c`、`Internet_Eligibility_Status__c`など)。 +- **利用資格リクエスト**:利用資格確認の送信時には、常に新しい**Case**がSalesforceに作成されます。サポートエージェントがNTTの光ファイバー利用可能性を手動で確認します。 + +## WHMCSで起こること + +- WHMCSはサービス閲覧フローに直接関与しません。カタログはSalesforceから完全に提供されます。 +- 各Salesforce Product2レコードには、対応するWHMCS商品IDにマッピングする`WH_Product_ID__c`フィールドがあります。 + +## 確認すべき重要事項 + +1. **カタログの完全性**:すべてのInternet、SIM、VPNプランが表示されることを確認。 +2. **価格の正確性**:表示価格がSalesforceのPricebookEntry単価と一致することを確認。 +3. **プランのグループ化**:Internet(Hikari vs Hikari Cross)、SIM(Data+Voice、Data Only、Voice Only)が正しくグループ化されていることを確認。 +4. **表示順序**:`Display_Order__c` に基づく正しい並び順を確認。 +5. **インターネット利用資格フロー**:各利用資格ステータスをテスト。 +6. **アクティブなサブスクリプション警告**:既にアクティブなインターネットサブスクリプションがある場合の警告バナーを確認。 +7. **レスポンシブデザイン**:モバイルとタブレットサイズでのレイアウト適応を確認。 diff --git a/docs/ja/uat/dashboard-and-profile.md b/docs/ja/uat/dashboard-and-profile.md new file mode 100644 index 00000000..a9f7a65a --- /dev/null +++ b/docs/ja/uat/dashboard-and-profile.md @@ -0,0 +1,203 @@ +--- +sidebar_position: 3 +title: "ダッシュボードとプロフィール" +--- + +# ダッシュボードとプロフィール + +## 概要 + +このガイドでは、ログイン後にユーザーが表示するメインダッシュボード、個人情報や住所を表示・編集できるプロフィールページ、本人確認セクションについて説明します。ダッシュボードはアカウントの概要を一目で確認でき、プロフィール/設定ページでは詳細を管理できます。 + +## ポータルフロー + +### ダッシュボード + +ログイン後、`/account` のダッシュボードに移動します。ページタイトルは**「Dashboard」**で、「Overview of your account.」(アカウントの概要)という説明が表示されます。 + +#### 挨拶セクション + +ページ上部にパーソナライズされた挨拶が表示されます: + +- 小さなテキストで**「Welcome back」**(おかえりなさい) +- **名前**が大きな太字で表示(名前がない場合はメールのユーザー名) +- その下にステータスバッジが表示され、注意が必要なタスクの数を示します: + - 緊急タスクがある場合、バッジは赤で感嘆符アイコン付き(例:「1 task needs attention」) + - 緊急でないタスクの場合、バッジは黄色 + - すべて最新の場合、「Everything is up to date」というメッセージ + +#### タスクセクション + +挨拶の下に最大4つのタスクカードが表示されます。これらは注意が必要なアクションアイテムです。各タスクカードには: + +- 優先度を示す色付きの左ボーダー(赤:重要、黄色:警告、青:情報、またはニュートラル) +- タスクタイプを表すアイコン +- 何をすべきかを説明するタイトルと説明 +- タスクを解決するためのアクションボタンまたはリンク + +以下のタスクタイプが表示される可能性があります(条件が満たされた場合のみ): + +- **期限超過または近日中の請求書** — 支払い期限が近いまたは期限超過の請求書についてのリマインダー。請求書ページへリンク。 +- **支払い方法の追加** — 登録されている支払い方法がない場合のプロンプト。支払いページへリンク。 +- **進行中の注文** — 注文が処理中であることを表示。注文ページへリンク。 +- **インターネット利用資格審査中** — 住所の利用資格確認がまだ進行中であることを表示。 +- **本人確認が必要** — 在留カードのアップロードまたは再アップロードを促す。確認ページへリンク。 +- **オンボーディング** — 新しいアカウントの次のステップを提案。 + +#### アカウント概要(クイック統計) + +左下エリアに**「Account Overview」**セクションがあり、3つのクリック可能な統計カードが表示されます: + +- **Active Services**(有効なサービス)— 有効なサブスクリプションの数。クリックすると `/account/services` に移動。 +- **Open Support Cases**(未解決のサポートケース)— 未解決のサポートチケットの数。クリックすると `/account/support` に移動。未解決のケースがある場合、カードが黄色でハイライト。 +- **Recent Orders**(最近の注文)— 最近の注文数。クリックすると `/account/orders` に移動。 + +#### 最近のアクティビティ + +右下エリアに**「Recent Activity」**タイムラインがあり、最近のアカウントイベント(最大5件)が表示されます。各アクティビティアイテムには: + +- アクティビティタイプを示す色付きアイコン +- 何が起きたかを説明するタイトル +- 相対的なタイムスタンプ(例:「2 hours ago」または「Yesterday」) + +アクティビティタイプ: + +- **請求書作成**(青アイコン)— 新しい請求書が生成された +- **請求書支払い済み**(緑アイコン)— 請求書が支払われた +- **サービス有効化**(紫/プライマリアイコン)— サービスが有効化された +- **ケース作成**(黄色アイコン)— サポートケースが開かれた +- **ケースクローズ**(緑アイコン)— サポートケースが解決された + +最近のアクティビティがない場合、プレースホルダーメッセージが表示されます:「No recent activity. Your account activity will appear here as you use our services.」 + +### プロフィール/設定ページ + +`/account/settings` に移動します。ページタイトルは**「Profile」**で、「Manage your account information.」(アカウント情報を管理)という説明が表示されます。 + +プロフィールページには3つのメインセクションがあります: + +#### 個人情報カード + +個人情報と右上隅の鉛筆**Edit**ボタンが表示されます。 + +**読み取り専用フィールド**(ポータルから変更不可): + +- **First Name** — 名前。注記:「Name cannot be changed from the portal.」 +- **Last Name** — 姓。同じ注記。 +- **Customer Number** — SalesforceのCustomer Number。注記:「Customer number is read-only.」 +- **Date of Birth** — 生年月日。注記:「Date of birth is stored in billing profile.」 +- **Gender** — 性別。注記:「Gender is stored in billing profile.」 + +**編集可能フィールド**(Editクリックで変更可能): + +- **Email Address** — メールアドレス。編集モードではテキスト入力に変更。 +- **Phone Number** — 電話番号。編集モードではテキスト入力に変更。 + +**Edit**をクリックすると編集可能フィールドが入力フィールドに切り替わります。2つのボタンが表示されます: + +- **Cancel** — 変更を破棄し表示モードに戻る +- **Save Changes** — 更新を保存。保存中は「Saving...」と表示 + +#### 住所情報カード + +郵送先住所と**Edit**ボタンが表示されます。 + +**表示モード**では、住所がフォーマットされたブロックで表示されます: + +- 番地(Address 1とAddress 2) +- 市区町村、都道府県、郵便番号 +- 国 + +住所が登録されていない場合、「No address on file」プレースホルダーと**Add Address**ボタンが表示されます。 + +**編集モード**では、住所フォームが表示され以下を更新できます: + +- Address 1(番地) +- Address 2(アパート、スイートなど) +- City(市区町村) +- State/Prefecture(都道府県) +- Postcode(郵便番号) +- Country(国) +- Phone NumberとPhone Country Code + +#### 本人確認カード + +在留カード確認のステータスとステータスピルが右上隅に表示されます。 + +ステータスピルの種類: + +- **Verified**(緑)— 本人確認が完了。確認日と共に表示。 +- **Under Review**(青)— 書類が提出され審査中。提出日と共に表示。 +- **Action Needed**(黄色)— 提出が却下された。却下理由とともに新しい書類のアップロードが求められます。 +- **Required for SIM**(黄色)— まだ書類が提出されていない。SIMサービスの有効化に在留カードのアップロードが必要であることを説明。 + +アップロードが可能な場合: + +- 画像(JPG、PNG)またはPDF(5MBまで)を受け付けるファイル入力 +- ファイル選択後、ファイル名と**Change**ボタンが表示 +- **Submit Document**ボタン。アップロード中は「Uploading...」と表示 +- 受付形式の注記:「Accepted formats: JPG, PNG, or PDF (max 5MB).」 + +## WHMCSで起こること + +### ダッシュボード + +- ダッシュボードデータは複数のソースから読み取られます。有効なサービス数、未解決ケース数、最近の注文はWHMCSとSalesforceからデータを取得します。 +- ダッシュボード表示時にWHMCSのレコードは作成または変更されません。 + +### プロフィールと住所の変更 + +- 個人情報カードから**メール**や**電話番号**を編集すると、変更はWHMCSクライアントレコードに書き込まれます。 +- **住所**を編集すると、変更はWHMCSクライアントレコードに直接書き込まれます。 +- 変更はポータルが保存後にユーザーのWHMCSキャッシュをクリアするため、すぐにWHMCSに反映されます。 +- **名前、生年月日、性別**はポータルから変更できません。変更するには管理者がWHMCSで直接更新する必要があります。 + +### 本人確認 + +- 在留カードの確認ステータスはWHMCSではなくSalesforceに保存されます。 + +## Salesforceで起こること + +### ダッシュボード + +- ダッシュボードは注文情報や利用資格ステータスなど一部のデータをSalesforceから読み取ります。ダッシュボード表示時にSalesforceのレコードは作成または変更されません。 + +### プロフィール変更 + +- ポータルを通じたメールと電話の変更はWHMCSのみに書き込まれます。Salesforceの住所データは注文作成時にスナップショットとして更新されます。 + +### 本人確認 + +- 在留カードをアップロードすると、ファイルと確認ステータスはSalesforceを通じて管理されます。 +- Salesforceで確認ステータスを確認するには: + - 顧客の**Account**レコードを見つけます + - **Id Verification Status**フィールドを確認します + - ステータス値:Not Submitted、Submitted(審査中)、Verified、Rejected + +## 確認すべき重要事項 + +### ダッシュボード + +- ログイン後、挨拶に正しい名前が表示されることを確認。 +- 未払い請求書がある場合、タスクカードが表示されることを確認。 +- 支払い方法がない場合、追加を促すタスクカードが表示されることを確認。 +- 「Active Services」統計カードをクリックして `/account/services` に移動することを確認。 +- 新規アカウントでは「Everything is up to date」と「No recent activity」が表示されることを確認。 + +### プロフィール/個人情報 + +- `/account/settings` に移動して情報が正しく表示されることを確認。 +- **Edit**をクリックしてメールまたは電話番号を変更、**Save Changes**をクリックして保存されることを確認。 +- 保存後、WHMCSクライアントレコードでメールまたは電話番号が更新されたことを確認。 + +### 住所 + +- 住所情報カードを確認。 +- **Edit**をクリックして住所を変更、**Save Address**をクリックして保存されることを確認。 +- 保存後、WHMCSクライアントレコードで住所フィールドが更新されたことを確認。 + +### 本人確認 + +- 在留カードが未提出の場合、「Required for SIM」ステータスとアップロードフォームが表示されることを確認。 +- テスト書類をアップロードして正常に送信されることを確認。ステータスが「Under Review」に変わるはずです。 +- 5MB超のファイルまたは未対応形式をアップロードしてエラーが表示されることを確認。 diff --git a/docs/ja/uat/identity-verification.md b/docs/ja/uat/identity-verification.md new file mode 100644 index 00000000..84c6c35d --- /dev/null +++ b/docs/ja/uat/identity-verification.md @@ -0,0 +1,85 @@ +--- +sidebar_position: 11 +title: "本人確認" +--- + +# 本人確認 + +## 概要 + +このガイドでは、特定の注文(SIMのアクティベーションなど)を行う前に完了する必要がある在留カード(ID)の確認プロセスを説明します。在留カードの写真またはスキャンをアップロードし、サポートエージェントが審査します。確認ステータスはSalesforceのAccountレコードで追跡されます。 + +## ポータルフロー + +### ID確認の場所 + +ID確認は2つの場所で利用可能です: + +1. **プロフィール/設定ページ**(`/account/settings`):設定ページに統合 +2. **スタンドアロン確認ページ**(`/account/settings/verification`):チェックアウトフローからリダイレクトされる際に使用 + +### ステータス:未提出 + +1. 在留カードが未提出の場合: + - **「Not Submitted」**ステータスピル(琥珀色) + - アップロード手順の説明 + - JPG、PNG、またはPDF、5MB以下の在留カードの写真またはスキャンをアップロード +2. ファイルピッカーでデバイスからファイルを選択。 +3. ファイル選択後、プレビューが表示され**Change**ボタンで別のファイルを選択可能。 +4. **Submit Document**をクリックしてアップロード。 + +### ステータス:審査中(Pending) + +1. アップロード成功後、ステータスが**「Under Review」**(青)に変更。 +2. 提出タイムスタンプが表示されます。 +3. 審査中は新しい書類をアップロードできません。 + +### ステータス:確認済み + +1. エージェントが承認すると、ステータスが**「Verified」**(緑)に変更。 +2. 「Your residence card is on file and approved. No further action is required.」という成功バナーが表示。 +3. アップロードフォームは非表示。 + +### ステータス:却下 + +1. エージェントが却下すると、ステータスが**「Action Needed」**(琥珀色)に変更。 +2. エージェントの却下理由(レビューアーノート)が表示。 +3. ファイルピッカーが再表示され、修正した書類をアップロード可能。 +4. 再アップロード後、ステータスが「Under Review」に戻ります。 + +### 受付ファイルタイプと制限 + +| プロパティ | 値 | +| --- | --- | +| ファイルタイプ | JPG、JPEG、PNG、PDF、その他画像 | +| 最大サイズ | 5 MB | +| ヒント | 高解像度の写真ほど審査が早くなります | + +## Salesforceで起こること + +- **ファイル保存**:アップロードされた書類はSalesforceの**ContentVersion**レコードとして保存されます。 +- **アップロード時に更新されるAccountフィールド**: + - `Id_Verification_Status__c` = **「Submitted」** + - `Id_Verification_Submitted_Date_Time__c` = アップロードタイムスタンプ +- **エージェント審査**: + - **承認**:`Id_Verification_Status__c` = **「Verified」** + - **却下**:`Id_Verification_Status__c` = **「Rejected」**、`Id_Verification_Rejection_Message__c` に理由を記入 +- **Platform Event**:確認ステータスが変更されるとPlatform Event(`Account_Update__e`)が発火し、ポータルがリアルタイムで更新。 +- **通知**:VerifiedまたはRejectedに変更されるとアプリ内通知が作成されます。 + +### Salesforceのステータス値 + +| Salesforce値 | ポータル表示 | 注文送信可能? | +| --- | --- | --- | +| Not Submitted | Not Submitted | いいえ | +| Submitted | Under Review | はい | +| Verified | Verified | はい | +| Rejected | Action Needed | いいえ | + +## 確認すべき重要事項 + +- JPG、PNG、またはPDF(5MB以下)をアップロードし、SalesforceにContentVersionレコードが作成されることを確認 +- Salesforceでの却下が、ポータルで「Action Needed」と却下理由として表示されることを確認 +- Salesforceでの承認がポータルで「Verified」として表示されることを確認 +- ステータス変更がPlatform Events経由でリアルタイムで反映されること +- 5MB超のファイルや未対応形式でエラーが表示されることを確認 diff --git a/docs/ja/uat/login-and-authentication.md b/docs/ja/uat/login-and-authentication.md new file mode 100644 index 00000000..442d17cb --- /dev/null +++ b/docs/ja/uat/login-and-authentication.md @@ -0,0 +1,114 @@ +--- +sidebar_position: 2 +title: "ログインと認証" +--- + +# ログインと認証 + +## 概要 + +このガイドでは、カスタマーポータルへのサインイン方法、2段階ログインフロー(パスワード入力後のワンタイムコード)、パスワードを忘れた場合の対処法、セッションタイムアウトの処理について説明します。ポータルはセキュリティのためメールアドレス+パスワード認証と必須のOTP(ワンタイムパスワード)認証ステップを使用します。 + +## ポータルフロー + +### サインイン + +1. `/auth/login` のログインページに移動します。ページタイトルは**「Welcome back」**(おかえりなさい)で、「Sign in to your Assist Solutions account.」(Assist Solutionsアカウントにサインイン)というサブタイトルが表示されます。 +2. 以下のフィールドを持つフォームが表示されます: + - **Email Address**(メールアドレス)(必須) + - **Password**(パスワード)(必須) + - **Remember me**(ログイン状態を保持)チェックボックス +3. フォームの下にリンクがあります: + - **Forgot your password?** — パスワードリセットフローへ + - **Don't have an account? Get started** — 登録フローへ + - **Existing customer? Transfer your account** — 登録/移行フローへ +4. メールアドレスとパスワードを入力し、**Sign in**(サインイン)をクリックします。 + +### OTP認証(第2ステップ) + +有効な認証情報を入力した後、ポータルはすぐにログインしません。代わりに: + +1. ページが**「Check your email」**(メールを確認してください)とメールアイコンの表示に変わります。 +2. 「We sent a verification code to [マスクされたメール]」(認証コードを送信しました)というメッセージが表示されます(例:`t***@example.com`)。 +3. 6桁のOTP入力フィールドが表示されます。 +4. コードの有効期限までのカウントダウンタイマーがMM:SS形式で表示されます。 +5. 6桁のコードを入力します。6桁すべて入力すると自動送信されるか、手動で**Verify and Sign In**(認証してサインイン)をクリックできます。 +6. 入力の下に**「Remember this device for 7 days」**(このデバイスを7日間記憶する)チェックボックスがあります。チェックすると、7日間このデバイスでOTPの入力が不要になります。 +7. コードが期限切れになると「Code expired. Please go back and try again.」(コードの期限が切れました。戻ってやり直してください)というメッセージが表示されます。 +8. **Back to login**(ログインに戻る)リンクで認証情報入力ステップに戻れます。 +9. 下部にヘルプメッセージ「Didn't receive the code? Check your spam folder or go back to try again.」(コードが届きませんか?迷惑メールフォルダを確認するか、戻ってやり直してください)が表示されます。 + +### ログイン成功後 + +- OTPが認証されると、**「Redirecting to dashboard...」**(ダッシュボードにリダイレクト中...)というローディングオーバーレイが短時間表示されます。 +- `/account` のアカウントダッシュボードにリダイレクトされます。 +- ログインにリダイレクトされる前に特定のページにアクセスしようとしていた場合、ダッシュボードではなくそのページに戻ります。 + +### ログインエラーメッセージ + +- 間違った認証情報を入力すると、フォームにエラーメッセージが表示されます。 +- アカウントがロックされているか、サーバーの問題がある場合、適切なエラーメッセージが表示されます。 +- ログアウト後にログインページに到達した場合、上部にバナーが表示されます: + - **セッション期限切れ**(黄色警告):「For your security, your session expired. Please sign in again to continue.」 + - **安全のためサインアウト**(赤色エラー):「We detected a security change and signed you out. Please sign in again to verify your session.」 + - **サインアウト**(青色情報):「You have been signed out. Sign in again whenever you're ready.」 + +### パスワードを忘れた場合のフロー + +1. ログインページから**Forgot your password?**をクリックします。 +2. **「Forgot password」**(パスワードをお忘れですか)ページに移動します。 +3. メールアドレスを入力し、**Send reset link**(リセットリンクを送信)をクリックします。 +4. システムがそのアドレスにパスワードリセットメールを送信します。 +5. 下部に**Back to login**(ログインに戻る)リンクがあります。 + +### パスワードリセットフロー + +1. メールからリセットリンクを開きます(`/auth/reset-password?token=...` に移動)。 +2. ページタイトルは**「Reset your password」**(パスワードをリセット)です。 +3. 以下のフィールドが表示されます: + - **New password**(新しいパスワード)(必須) + - **Confirm password**(パスワード確認)(必須) +4. 新しいパスワードを入力・確認し、**Update password**(パスワードを更新)をクリックします。 +5. リセットリンクが欠落または期限切れの場合、メッセージが表示されます。**Request new reset link**(新しいリセットリンクをリクエスト)ボタンで新しいリンクを取得できます。 +6. 下部に**Back to login**(ログインに戻る)リンクがあります。 + +### セッション動作 + +ポータルはログインセッションを自動的に管理します: + +- **自動更新**:システムは4分ごとにセッションを確認し、期限が近い場合はバックグラウンドで更新します。 +- **タブフォーカス更新**:ポータルタブに戻ったとき、必要に応じてセッションを確認・更新します。 +- **セッションタイムアウト警告**:セッション期限の約5分前にポップアップダイアログが表示されます。タイトルは**「Session Expiring Soon」**(セッションがまもなく期限切れ)で、残り時間と2つのボタンが表示されます: + - **Extend Session** — セッションを延長 + - **Logout Now** — 即座にログアウト +- **セッション期限切れ**:警告に応答せずセッションが期限切れになると、自動的にログアウトされ「Session Expired」バナーのあるログインページにリダイレクトされます。 + +### ログアウト + +- アカウントメニューからいつでもログアウトできます。 +- ログアウト後、「Signed Out」情報バナーのあるログインページにリダイレクトされます。 + +## WHMCSで起こること + +ログインと認証はカスタマーポータルで完全に処理されます。WHMCSはログインプロセスに関与しません。 + +- ユーザーがログイン、ログアウト、またはパスワードリセットした際にWHMCSでレコードが作成または更新されることはありません。 +- ポータルのパスワードはWHMCSのパスワードとは別に保存されます。ポータルのパスワードを変更しても、ユーザーのWHMCSパスワードには影響しません。 + +## Salesforceで起こること + +ログインと認証はカスタマーポータルで完全に処理されます。Salesforceはログインプロセスに関与しません。 + +- ユーザーがログイン、ログアウト、またはパスワードリセットした際にSalesforceでレコードが作成または更新されることはありません。 + +## 確認すべき重要事項 + +- **正常系ログイン**:有効なメールとパスワードを入力、OTPを受信・入力し、ダッシュボードに到達することを確認。 +- **間違ったパスワード**:間違ったパスワードを入力し、エラーメッセージが表示されることを確認。OTPステップに到達しないはずです。 +- **間違ったOTP**:正しいパスワード入力後、間違ったOTPコードを入力。エラーメッセージが表示されることを確認。 +- **期限切れOTP**:OTPカウントダウンがゼロになるまで待つ。「Code expired」メッセージが表示されることを確認。 +- **デバイス記憶**:OTP時に「Remember this device for 7 days」をチェック。ログアウトして再ログインし、同じブラウザでOTPが要求されないことを確認。 +- **パスワード忘れフロー**:「Forgot your password?」をクリック、メールを入力、リセットメールが送信されることを確認。リセットリンクを開き、新しいパスワードを設定し、新しいパスワードでログインできることを確認。 +- **期限切れリセットリンク**:古いまたは無効なリセットリンクを使用。エラーメッセージが表示されることを確認。 +- **セッションタイムアウト**:セッションタイムアウト警告が表示されるまでアクティビティなしでログイン状態を維持。「Extend Session」をクリックしてセッションが継続されることを確認。 +- **ログイン後リダイレクト**:ログアウト状態で保護されたページに直接移動。ログイン後にダッシュボードではなくそのページに戻ることを確認。 diff --git a/docs/ja/uat/managing-internet-subscriptions.md b/docs/ja/uat/managing-internet-subscriptions.md new file mode 100644 index 00000000..64b95721 --- /dev/null +++ b/docs/ja/uat/managing-internet-subscriptions.md @@ -0,0 +1,96 @@ +--- +sidebar_position: 7 +title: "インターネットサブスクリプションの管理" +--- + +# インターネットサブスクリプションの管理 + +## 概要 + +このガイドでは、ポータルでインターネットサブスクリプションを表示・管理する方法を説明します。インターネットサービスは「SonixNet via NTT Optical Fiber」ブランドで販売されるNTT光ファイバー接続です。ポータルではサブスクリプションの詳細と請求履歴を表示し、解約フローを提供します。SIMサブスクリプションとは異なり、インターネットサブスクリプションにはプラン変更や設定のセルフサービス管理ダッシュボードがありません。これらの変更はカスタマーサポートを通じて処理されます。 + +## ポータルフロー + +### インターネットサブスクリプションの確認 + +1. サイドバーから**Subscriptions**に移動します。 +2. インターネットサブスクリプションをクリックして詳細ページを開きます。 +3. 4つの統計カード(サービスステータス、請求金額、次回請求日、登録日)が表示されます。 +4. その下に**Billing History**(請求履歴)セクションが表示されます。 +5. サービスがActiveの場合、右上に**Cancel Service**ボタンが表示されます。 + +インターネットサブスクリプションにはSIM Managementタブは**ありません**。 + +### インターネットプランの理解 + +**オファリングタイプ**(物理接続の種類): +- **Home 1G** — 戸建て、最大1Gbps +- **Apartment 1G** — マンション、1Gbps光ファイバー +- **Apartment 100M** — マンション、共有100Mbps +- **Flets X (Home 10G)** — 戸建て、最大10Gbps(プレミアムティア) + +**プランティア**(サービスレベルと価格): +- **Silver** — ベーシックティア +- **Gold** — ミッドティア +- **Platinum** — プレミアムティア + +### 設置オプション + +- **Single Installation**(一括払い)— WHMCS商品ID 242 +- **12-Month Installation**(月額分割)— WHMCS商品ID 243 +- **24-Month Installation**(月額分割)— WHMCS商品ID 244 +- **Weekend Installation Fee**(アドオン、一括)— WHMCS商品ID 245 + +### アドオンサービス + +- **ひかり電話(ホームフォン)** — VoIPホームフォンサービス、月額請求 +- **ひかり電話設置料金** — 一括セットアップ料金 + +### インターネットサブスクリプションの解約 + +1. 詳細ページから**Cancel Service**ボタンをクリック(Activeステータスの場合のみ表示)。 +2. 解約が既に進行中の場合、**Cancellation In Progress**ステータスビューが表示され、予定終了日と**Equipment Return Status**(機器返却ステータス)セクションが表示されます。 +3. 解約フロー(3ステップ): + - **ステップ1**:サービス情報の確認と解約条件 + - **ステップ2**:解約月の選択と確認チェックボックス + - **ステップ3**:確認と送信 + +## WHMCSで起こること + +| アクション | WHMCSへの影響 | +| --- | --- | +| **サブスクリプション表示** | WHMCSからデータを読み取り。5〜10分キャッシュ。 | +| **インターネット解約** | 請求期間終了まで継続。関連サービス(設置分割、ひかり電話等)も解約。 | + +**WHMCS商品IDマッピング:** + +| プラン | Home 1G | Apt 1G | Apt 100M | Flets X (10G) | +| --- | --- | --- | --- | --- | +| Silver | 181 | 184 | 187 | 239 | +| Gold | 182 | 185 | 188 | 214 | +| Platinum | 183 | 186 | 189 | 213 | + +## Salesforceで起こること + +| アクション | Salesforceへの影響 | +| --- | --- | +| **サブスクリプション表示** | 基本表示にSalesforce読み取りなし。 | +| **インターネット解約** | Salesforce Opportunityのステージが「Active」から「△Cancelling」に変更。サポートチームに通知する内部Caseが作成。 | + +**解約時に更新されるSalesforce Opportunityフィールド:** + +| フィールド | API名 | 値 | +| --- | --- | --- | +| Stage | `StageName` | 「△Cancelling」→「〇Cancelled」 | +| Scheduled Cancellation | `ScheduledCancellationDateAndTime__c` | 選択した解約月 | +| Cancellation Notice | `CancellationNotice__c` | ステータス | +| Line Return (Equipment) | `LineReturn__c` | 機器返却ステータス | + +## 確認すべき重要事項 + +- インターネットサブスクリプションにSIM Managementタブが表示されないこと +- 4つの統計カードがWHMCSと一致する正しい値を表示すること +- Cancel ServiceボタンがActiveステータスの場合のみ表示されること +- 解約フローが正しいサービス詳細を表示すること +- 解約進行中の場合、Equipment Return Statusセクションが表示されること +- 解約送信後、Salesforce Opportunityが「Cancelling」に移行すること diff --git a/docs/ja/uat/managing-sim-subscriptions.md b/docs/ja/uat/managing-sim-subscriptions.md new file mode 100644 index 00000000..7778a295 --- /dev/null +++ b/docs/ja/uat/managing-sim-subscriptions.md @@ -0,0 +1,127 @@ +--- +sidebar_position: 6 +title: "SIMサブスクリプションの管理" +--- + +# SIMサブスクリプションの管理 + +## 概要 + +このガイドでは、アクティブなSIMサブスクリプションに関してポータルで行えるすべての操作を説明します。SIMの詳細とデータ使用量の確認、プラン変更、データのチャージ、SIMカードの再発行、音声機能の管理、通話・SMS履歴の確認、サービスの解約が含まれます。SIM操作はFreebit MVNOプラットフォームを経由し、WHMCSで請求が追跡されます。 + +## ポータルフロー + +### SIMサブスクリプションの確認 + +1. サイドバーから**Subscriptions**(サブスクリプション)に移動します。 +2. サブスクリプション一覧にすべてのサービスが表示されます。 +3. SIMサブスクリプションをクリックして詳細ページを開きます。 +4. 詳細ページには**Overview & Billing**と**SIM Management**の2つのタブがあります。 +5. **Overview & Billing**タブには4つの統計カード(サービスステータス、請求金額、次回請求日、登録日)と請求履歴テーブルが表示されます。 + +### SIM管理ダッシュボード + +**SIM Management**タブがメインハブです。以下が表示されます: + +**ヘッダーエリア:** +- 電話番号(MSISDN)が見出しとして表示 +- 右上に**Top Up Data**(データチャージ)ボタン + +**左列:** +- 2x2グリッドの**アクションタイル**:Call History、Change Plan、Reissue SIM、Cancel SIM(赤色) +- **Voice/Networkステータス**セクション:Voice Mail、Network Type(4G/5G)、Call Waiting、International Roamingのトグルスイッチ。データ専用プランの場合、音声機能は非表示。 + +**右列:** +- **円形データ使用量チャート**:残りのデータ量と使用量を表示 +- **請求サマリー**:月額コスト、次回請求日、登録日 +- **重要事項ボックス**:変更に約30分かかること、デバイスの再起動が必要な場合があること、音声/ネットワーク/プラン変更は30分以上の間隔が必要なこと + +### データのチャージ + +1. SIM管理ダッシュボードから**Top Up Data**ボタンをクリック。 +2. 専用の**Top Up Data**ページに移動。 +3. データ量をGB単位で入力(最小1GB、最大50GB)。 +4. リアルタイムのコスト計算が表示:1GB = 500 JPY。 +5. **Submit Top Up**をクリック。 +6. システムがWHMCS請求書を作成し、保存された支払い方法で決済後、Freebit経由でデータを追加。 +7. 成功時、緑のバナーでチャージ完了が確認されます。 + +### SIMプランの変更 + +1. SIM管理ダッシュボードから**Change Plan**をクリック。 +2. **Current Plan**(現在のプラン)が上部に表示され、下に**利用可能なプラン**がラジオボタンカードで表示されます。 +3. プランを選択して**Confirm Plan Change**をクリック。 +4. 重要なルール: + - プラン変更は翌月1日に有効になります + - 当月25日までにリクエストが必要です + - 新プラン有効化時にデータ残高がリセットされます + +### SIMの再発行(物理SIMまたはeSIM) + +1. SIM管理ダッシュボードから**Reissue SIM**をクリック。 +2. 現在のSIM情報が表示されます。 +3. 交換用SIMタイプを選択: + - **Physical SIM** — 新しいSIMカードが郵送(通常3〜5営業日) + - **eSIM** — 新しいeSIMプロファイルをダウンロード。新しいデバイスの32桁EIDの入力が必要。 +4. **Submit Reissue Request**をクリック。 + +### 音声機能とネットワークタイプの管理 + +1. **Voice/Networkステータス**セクションにトグルスイッチがあります。 +2. 利用可能なトグル: + - **Voice Mail** — オン/オフ(有効時300 JPY/月) + - **Call Waiting** — オン/オフ(有効時300 JPY/月) + - **International Roaming** — オン/オフ + - **Network Type** — 4Gと5Gの切り替え +3. トグルを切り替えると変更が即座に送信されます。 +4. 変更は約30分で有効になります。音声、ネットワーク、プラン変更は30分以上の間隔が必要です。 + +### 通話・SMS履歴の確認 + +1. SIM管理ダッシュボードから**Call History**タイルをクリック。 +2. 3つのタブ: + - **Domestic Calls** — 日付、時刻、発信先、通話時間、通話料金 + - **International Calls** — 日付、開始/終了時刻、国、発信先、通話料金 + - **SMS History** — 日付、時刻、送信先、タイプ +3. テーブルは50件/ページでページネーションされます。 +4. 履歴は通話・メッセージから約2ヶ月後に更新されます。 + +### SIMサブスクリプションの解約 + +1. SIM管理ダッシュボードから**Cancel SIM**タイル(赤色)をクリック。 +2. 3ステップの解約フロー: + - **ステップ1**:サービス情報の確認と解約条件の表示 + - **ステップ2**:解約月の選択と確認チェックボックス + - **ステップ3**:確認と送信 +3. 成功時:「Cancellation request submitted.」のメッセージが表示されます。 + +## WHMCSで起こること + +| アクション | WHMCSへの影響 | +| --- | --- | +| **サブスクリプション表示** | WHMCS経由でデータを読み取り。5〜10分キャッシュ。 | +| **データチャージ** | チャージ金額の新しいInvoiceを作成し、即座に決済。 | +| **プラン変更** | Freebitでスケジュールされ、翌月1日にWHMCSレコードが更新。 | +| **SIM再発行** | 直接のWHMCS変更なし。同じサービスIDで継続。 | +| **音声機能** | Freebit経由で変更を適用。WHMCSアドオンサービスを更新。 | +| **通話履歴** | 読み取り専用。Freebit SFTPファイルから取得。 | +| **SIM解約** | 請求期間終了まで継続。WHMCSサービスステータスがCancelledに変更。 | + +## Salesforceで起こること + +| アクション | Salesforceへの影響 | +| --- | --- | +| **サブスクリプション表示** | 基本表示にSalesforce読み取りなし。 | +| **データチャージ** | Salesforceレコードは作成されません。 | +| **SIM解約** | Salesforce Opportunityのステージが「Active」から「△Cancelling」に変更。 | + +## 確認すべき重要事項 + +- SIMサブスクリプションに「Overview & Billing」と「SIM Management」の両タブが表示されること +- データ使用量チャートが正しい値を表示すること +- データチャージのコスト計算がリアルタイムで更新されること +- プラン変更の25日締め切りと翌月1日有効のルールが表示されること +- eSIM再発行に32桁のEIDが必要であること +- 音声機能トグルの30分間隔ルールが適用されること +- 通話・SMS履歴の3つのタブが正しく読み込まれること +- 解約フローの確認チェックボックスが必須であること diff --git a/docs/ja/uat/managing-vpn-subscriptions.md b/docs/ja/uat/managing-vpn-subscriptions.md new file mode 100644 index 00000000..418ee305 --- /dev/null +++ b/docs/ja/uat/managing-vpn-subscriptions.md @@ -0,0 +1,69 @@ +--- +sidebar_position: 8 +title: "VPNサブスクリプションの管理" +--- + +# VPNサブスクリプションの管理 + +## 概要 + +このガイドでは、ポータルでVPNサブスクリプションを表示・管理する方法を説明します。VPNはルーターベースのサービスで、米国(サンフランシスコ)または英国(ロンドン)への事前設定されたVPN接続を提供し、主にリージョン固有のストリーミングコンテンツへのアクセスに使用されます。長期契約のない月額レンタルです。 + +## ポータルフロー + +### VPNサブスクリプションの確認 + +1. サイドバーから**Subscriptions**に移動します。 +2. VPNサブスクリプション(例:「SonixNet USA Remote Access VPN Service (Monthly)」)をクリック。 +3. 4つの統計カード(サービスステータス、請求金額、次回請求日、登録日)が表示されます。 +4. その下に**Billing History**セクションが表示されます。 + +VPNサブスクリプションにはSIM Managementタブや特別な管理ダッシュボードは**ありません**。 + +### VPNプランの理解 + +2つのリージョンオプション: + +- **VPN USA(サンフランシスコ)** — 米国サーバー経由。WHMCS商品ID 33、月額請求。 +- **VPN UK(ロンドン)** — 英国サーバー経由。WHMCS商品ID 54、月額請求。 + +各VPN注文には一括の**VPNアクティベーション料金**(WHMCS商品ID 37、約3,000 JPY)が含まれます。 + +サービスの特徴: +- **ルーターレンタル** — 事前設定されたVPNルーターが郵送されます +- 技術的なセットアップは不要(接続してつなぐだけ) +- VPNルーターのWiFiに接続されたすべてのデバイスがVPN経由でルーティング +- コンテンツサブスクリプション(Netflix、Hulu、BBC iPlayerなど)は含まれません +- 長期契約なし — いつでも解約可能 +- 価格は10%消費税別 + +### VPN請求 + +典型的なVPNサブスクリプションは2つのWHMCSサービスで構成: +1. **月額VPNサービス** — 毎月の定期請求 +2. **VPNアクティベーション料金** — 一括請求 + +### VPNサブスクリプションの解約 + +VPN解約は現在、SIMやインターネットと同様のセルフサービスポータルフローでは処理されていません。解約するには、サポートケースシステムを通じてカスタマーサポートに連絡してください。 + +## WHMCSで起こること + +| アクション | WHMCSへの影響 | +| --- | --- | +| **サブスクリプション表示** | WHMCSからデータを読み取り。5〜10分キャッシュ。 | + +## Salesforceで起こること + +| アクション | Salesforceへの影響 | +| --- | --- | +| **サブスクリプション表示** | 基本表示にSalesforce読み取りなし。 | +| **VPN注文** | Salesforce OrderがVPN Product2レコードにリンクして作成。 | + +## 確認すべき重要事項 + +- VPNサブスクリプションにSIM Managementタブが表示されないこと +- 4つの統計カードがWHMCSと一致する正しい値を表示すること +- 請求履歴に定期月額請求とアクティベーション料金の両方が表示されること +- 商品名にVPNリージョン(USAまたはUK)が明確に表示されること +- VPN、SIM、インターネットのサブスクリプションを同時に持てること diff --git a/docs/ja/uat/notifications.md b/docs/ja/uat/notifications.md new file mode 100644 index 00000000..dda2d912 --- /dev/null +++ b/docs/ja/uat/notifications.md @@ -0,0 +1,81 @@ +--- +sidebar_position: 12 +title: "通知" +--- + +# 通知 + +## 概要 + +このガイドでは、アカウント、注文、請求、サービスに関する重要なイベントを通知するアプリ内通知システムについて説明します。通知はポータルのデータベースに保存され、Salesforce(Platform Events経由)、WHMCS(フルフィルメントプロセス経由)、ポータル自体からのイベントによってトリガーされます。 + +## ポータルフロー + +### 通知ベル + +1. **ベルアイコン**はログイン中のすべてのページのナビゲーションバーに表示されます。 +2. **未読通知**がある場合、ベルにカウントバッジが表示されます(9件超は「9+」)。 +3. 未読数は60秒ごとに自動更新されます。 + +### 通知ドロップダウン + +1. ベルをクリックすると最新10件の通知が表示されます。 +2. 各通知に表示される情報: + - タイプを示す**アイコン**(緑:肯定的、琥珀色:警告、青:一般) + - 通知**タイトル**(未読は太字) + - 短い**メッセージ** + - **相対的なタイムスタンプ** + - **未読インジケーター**(小さな青い点) +3. 上部に**「Mark all read」**ボタン(未読通知がある場合)。 +4. 各通知はホバー時のXボタンで**個別に非表示**にできます。 + +### 通知タイプ + +| タイプ | タイトル | トリガー | リンク先 | +| --- | --- | --- | --- | +| Internet Eligible | 「インターネットサービスが利用可能」 | 利用資格確認完了 | インターネットサービスページ | +| Internet Ineligible | 「インターネットサービスは利用不可」 | 利用資格確認完了 | サポートページ | +| ID Verified | 「本人確認完了」 | エージェントが在留カードを承認 | チェックアウト継続 | +| ID Rejected | 「本人確認に対応が必要」 | エージェントが在留カードを却下 | 確認再提出ページ | +| Order Approved | 「注文承認済み」 | Salesforceで注文承認 | 注文ページ | +| Service Activated | 「サービス有効化」 | WHMCSプロビジョニング完了 | サービスページ | +| Order Failed | 「注文に対応が必要」 | フルフィルメントエラー | サポートページ | +| Cancellation Scheduled | 「解約予定」 | 顧客が解約をリクエスト | サービスページ | +| Invoice Due | 「請求書期限」 | 7日以内に支払期限 | 請求書ページ | + +### 通知のトリガー方法 + +**Salesforceから(Platform Events経由):** +- Salesforceエージェントが`Internet_Eligibility_Status__c`や`Id_Verification_Status__c`を更新した際、Platform Eventが発火し通知を作成。 +- 通知は**最終状態**(Eligible/Ineligible、Verified/Rejected)でのみ作成されます。 + +**フルフィルメントプロセスから:** +- 注文の承認、有効化、またはフルフィルメントワークフロー中のエラー時に通知を作成。 + +**ダッシュボードチェックから:** +- ダッシュボード読み込み時に7日以内に期限の請求書があるかチェックし、通知を作成。 + +### 重複排除 + +- 大部分の通知タイプは1時間の重複排除ウィンドウを使用。 +- 請求書期限等は24時間の重複排除ウィンドウを使用。 + +## WHMCSで起こること + +- WHMCSはポータル通知を直接保存しません。 +- ただし、WHMCSイベントが間接的に通知をトリガーする場合があります(サービス有効化時、請求書期限接近時など)。 + +## Salesforceで起こること + +- Salesforceイベントが**Platform Events**経由で通知をトリガーします。 +- Salesforceはポータル通知レコードを**保存しません**。通知はポータルのデータベースに存在します。 + +## 確認すべき重要事項 + +- ベルアイコンが正しい未読数を表示すること +- ドロップダウンに正しいタイトル、メッセージ、アイコン、タイムスタンプが表示されること +- 通知クリックで未読インジケーターが消えること +- 「Mark all read」ですべての未読インジケーターが消えること +- アクションリンク付き通知が正しいページに移動すること +- Salesforceでの利用資格・確認ステータス変更時に通知が表示されること +- 同じイベントを1時間以内に2回トリガーしても通知が1つだけ作成されること diff --git a/docs/ja/uat/ordering-a-service.md b/docs/ja/uat/ordering-a-service.md new file mode 100644 index 00000000..835f0402 --- /dev/null +++ b/docs/ja/uat/ordering-a-service.md @@ -0,0 +1,146 @@ +--- +sidebar_position: 5 +title: "サービスの注文" +--- + +# サービスの注文 + +## 概要 + +このガイドでは、サービスの設定からチェックアウト、注文送信、フルフィルメント、リアルタイムのステータス追跡まで、注文ライフサイクル全体を説明します。これはポータルで最も重要なフローの一つです。注文はポータルで開始され、Salesforceにレコード(Order、Opportunity)を作成し、最終的にWHMCSでサービスをプロビジョニングします。すべての注文はSalesforceのサポートエージェントによる手動承認が必要です。自動プロビジョニングはありません。 + +## ポータルフロー + +### サービスの設定 + +チェックアウトに進む前に、ユーザーは選択したサービスを設定する必要があります。各サービスタイプには独自のマルチステップ設定ウィザードがあります。 + +#### インターネットの設定 + +1. インターネットプランページからプランティア(Standard、Pro、またはPlatinum)をクリックして `/account/services/internet/configure` で設定を開始します。 +2. **ステップ1 — サービス設定**:選択したプランの詳細を確認。接続アクセスモード(自前ルーターでIPoE、ホームゲートウェイでIPoE、またはPPPoE)を選択。 +3. **ステップ2 — 設置**:設置オプションを選択。契約期間(一括、12ヶ月、24ヶ月)により設置料金の支払い方法が変わります。 +4. **ステップ3 — アドオン**:オプションでインターネットサービスのアドオンを選択。一部のアドオンはバンドル(特定プランに自動的に含まれる)されています。 +5. **ステップ4 — 注文確認**:プラン、設置オプション、アドオン、月額合計、一括合計のサマリーを確認。価格は10%消費税別。**Proceed to Checkout** をクリック。 + +#### SIM / eSIMの設定 + +1. SIMプランページでプランカードの **Select Plan** をクリック。 +2. **ステップ1 — SIMタイプ選択**:**eSIM** または **Physical SIM** を選択。eSIMの場合、デバイスのEID(32桁の識別子)を入力する必要があります。 +3. **ステップ2 — アクティベーション**:**Immediate**(即時)または **Scheduled**(予定日指定)を選択。 +4. **ステップ3 — アドオン**:オプションでアドオンを選択。 +5. **ステップ4 — 番号ポーティング(MNP)**:既存の電話番号を移行するかを選択。移行する場合、MNP予約番号、有効期限、キャリアアカウント番号、個人情報を入力。 +6. **ステップ5 — 注文確認**:すべてのサマリーを確認。**Proceed to Checkout** をクリック。 + +#### VPNルーターの設定 + +1. VPNプランページで希望リージョン(USまたはUK)のプランカードをクリック。 +2. プラン選択、アドオン、レビュー、チェックアウトへ進みます。 + +### チェックアウト + +1. 設定ウィザードから「Proceed to Checkout」をクリック後、ポータルが**チェックアウトセッション**を構築します。 +2. 未ログインの場合、ログインページにリダイレクトされます。カートデータは保持されます。 +3. チェックアウトページには **「Checkout」** というタイトルで「Verify your address, review totals, and submit your order.」という説明が表示されます。 + +#### チェックアウト要件 + +送信前に3つの要件をすべて満たす必要があります: + +**1. サービス住所の確認** +- 登録済み住所が表示されます。正しいことを確認ボタンで確認する必要があります。 + +**2. 支払い方法** +- WHMCSに支払い方法が登録されているか確認します。ない場合、WHMCS決済ポータルへのリンクが表示されます。 + +**3. 本人確認(在留カード)** +- 日本の電気通信規制により本人確認が必要です。在留カード画像のアップロードまたはステータスが表示されます。 + +#### 注文サマリーと送信 + +1. **Review & Submit** セクションに推定月額と一括合計が表示されます。 +2. **Submit order** ボタンは3つの要件がすべて満たされるまで無効です。 +3. **Submit order** をクリック。成功すると注文詳細ページにリダイレクトされます。 + +### 注文確認と追跡 + +1. 送信成功後、注文詳細ページに移動します。 +2. 緑の **「Order submitted」** バナーが表示されます。 + +#### 注文詳細ページ + +- **統計カード**:注文ステータス、月額コスト、一括コスト、注文日の4つのカード。 +- **注文進捗タイムライン**:サービスタイプ別のステップ進行バー: + - **Internet**:Submitted -> Under Review -> Scheduled -> Active + - **SIM**:Submitted -> Processing -> Activating -> Active + - **VPN**:Submitted -> Processing -> Active +- **注文詳細カード**:サービスラベル、注文日、価格、注文アイテムリスト。 +- **次のステップ**:現在のステータスに基づく次のアクションの説明。 + +#### リアルタイム注文更新 + +- 注文詳細ページはServer-Sent Eventsを使用してサーバーとの**ライブ接続**を確立します。Salesforceで注文ステータスが変更されると、ページが自動更新されます。 + +#### 注文ステータスの意味 + +| ポータルステータス | 意味 | +| --- | --- | +| **Under Review** | 注文が送信され、チームが確認中 | +| **Installation Scheduled** | インターネット注文:設置日が設定済み | +| **Setting Up Service** | バックエンドがサービスをプロビジョニング中 | +| **Service Active** | サービスが完全にプロビジョニングされアクティブ | +| **Processing** | 一般的な処理状態 | + +### 注文一覧ページ + +1. `/account/orders` ですべての注文を確認できます。 +2. 上部にサマリー統計、検索、ステータス・タイプフィルターが表示されます。 + +## Salesforceで起こること + +### 注文作成 + +ユーザーが「Submit order」をクリックすると、BFFがSalesforceで以下のステップを実行します: + +1. **注文の検証**:アカウントマッピングの確認、SKUの存在確認、ビジネスルールの検証。 +2. **Opportunityとの紐付け**:既存のオープンOpportunityを検索または新規作成。ステージを **「Post Processing」** に設定。 +3. **Orderの作成**:新しい**Order**レコードを作成。`Status` = "Draft"、`Type` = 注文タイプ。 +4. **Order Itemsの作成**:各SKUに対して**OrderItem**レコードを作成。 +5. **サポートチームへの通知**:内部Caseを作成して新規注文を通知。 + +## WHMCSで起こること + +### 注文フルフィルメント(プロビジョニング) + +Salesforce Orderの作成後、すべての注文はプロビジョニング前にサポートチームによる手動レビューと承認が必要です。 + +1. **インターネット注文**:サポートチームが確認後、NTTとの調整を行い、`Activation_Status__c` を更新してプロビジョニングをトリガー。 +2. **SIM注文(eSIM)**:レビューと在留カード確認後、WHMCSにProduct/Serviceを作成し、モバイルネットワークでSIMをアクティベート。 +3. **SIM注文(Physical SIM)**:物理SIMカードの割り当て後にプロビジョニング。 +4. **VPN注文**:レビュー後、WHMCSにProduct/Serviceを作成し、ルーターを発送。 + +### 作成されるWHMCSレコード + +- **Product/Service**:ユーザーのWHMCSクライアントに新しいサービスレコードを作成。 +- **Order**:サービスとアドオンをグループ化するWHMCS Orderレコード。 +- **Invoice**:WHMCSが請求サイクルと価格に基づいて請求書を自動生成。 + +## 確認すべき重要事項 + +### 注文の配置 + +1. **エンドツーエンドフロー**:各サービスタイプ(Internet、SIM eSIM、SIM Physical、VPN)でフルフローを実行。 +2. **チェックアウト要件**:3つの要件がすべて満たされるまでSubmitボタンが無効であることを確認。 +3. **住所確認**:完全な住所と不完全な住所でテスト。 +4. **支払い方法確認**:支払い方法ありとなしでテスト。 + +### Salesforce確認 + +5. **Orderレコード**:送信後、Salesforceで注文を確認。 +6. **Order Items**:チェックアウトカートの各SKUがOrderItemとして表示されることを確認。 +7. **Opportunity**:Orderに紐付けられ、ステージが「Post Processing」であることを確認。 + +### 注文追跡 + +8. **リアルタイム更新**:注文詳細ページを開いた状態でSalesforceでステータスを変更し、ページが自動更新されることを確認。 +9. **ステータスラベル**:各ステータスが正しく表示されることを確認。 diff --git a/docs/ja/uat/signup-and-account-creation.md b/docs/ja/uat/signup-and-account-creation.md new file mode 100644 index 00000000..069d68fb --- /dev/null +++ b/docs/ja/uat/signup-and-account-creation.md @@ -0,0 +1,162 @@ +--- +sidebar_position: 1 +title: "アカウント登録と作成" +--- + +# アカウント登録と作成 + +## 概要 + +このガイドでは、カスタマーポータルで新しいユーザーがアカウントを作成する方法を説明します。プロセスはメール認証から始まり、ユーザーが完全に新規か、Salesforceに既に存在するか、WHMCSの請求アカウントを既に持っているかによって分岐します。最終的に、ユーザーはポータルログイン、リンクされたWHMCSクライアントレコード、更新されたSalesforceアカウントを持つことになります。 + +## ポータルフロー + +### ステップ1:メールアドレスの入力 + +1. `/auth/get-started` の「はじめに」ページに移動します。ログインページから「Get started」または「Transfer your account」をクリックしてアクセスできます。 +2. **「Get Started」**(はじめましょう)というタイトルと「Enter your email to begin.」(メールアドレスを入力してください)というサブタイトルのシンプルなフォームが表示されます。 +3. メールアドレスフィールドにメールアドレスを入力し、**Send Verification Code**(認証コードを送信)をクリックします。 +4. システムがそのメールアドレスに6桁のワンタイムコードを送信します。 + +### ステップ2:メールの確認 + +1. ページが**「Verify Your Email」**(メールを確認)に変わり、「Enter the code we sent to your email.」(メールに送信したコードを入力してください)というサブタイトルが表示されます。 +2. 正しいアドレスが使用されたことを確認できるよう、メールアドレスが画面に表示されます。 +3. 6桁のコードを入力します。6桁すべて入力すると自動送信されるか、手動で**Verify Code**(コードを確認)をクリックできます。 +4. コードは10分後に期限切れになります。期限が切れた場合は、**Resend code**(コードを再送信)リンクで新しいコードを取得してください。届かない場合は迷惑メールフォルダを確認してください。 +5. 間違ったメールアドレスを入力した場合は、**Change email**(メールを変更)をクリックして前のステップに戻ります。 +6. 試行回数には制限があります。残りが少なくなると「2 attempts remaining」のような警告が表示されます。 + +### ステップ3:アカウントステータス(認証後の処理) + +コードが認証されると、システムがメールアドレスが既知かどうかを確認します。以下の4つの結果のいずれかが表示されます: + +#### 結果A:新規顧客(既存レコードなし) + +- ページに緑のチェックマーク付きの**「Email Verified!」**(メール認証完了!)が表示されます。 +- 「Let's set up your account so you can access all our services.」(すべてのサービスにアクセスできるようアカウントを設定しましょう)というメッセージが表示されます。 +- **Continue**(続行)をクリックして、アカウント作成フォームに進みます。 + +#### 結果B:Salesforceのみの顧客(以前のお問い合わせで既知) + +- ページに**「Welcome back, [名前]!」**(おかえりなさい、[名前]さん!)と表示され、以前のお問い合わせからの情報が見つかったことが説明されます。 +- サマリーカードに既にファイルにあるデータ(以前のお問い合わせからの名前、メール、住所)と、まだ提供が必要なデータ(電話番号、生年月日、パスワード)が表示されます。 +- 資格ステータスが既に判定されている場合は、それも表示されます。 +- **Continue**(続行)をクリックしてアカウント作成フォームに進みます。一部のフィールドは事前入力されています。 + +#### 結果C:既存のWHMCS請求アカウント(ポータル未リンク) + +- ページに**「Welcome Back!」**(おかえりなさい!)と表示され、既存の請求アカウントが見つかったことが説明されます。 +- サマリーカードにWHMCSからの既存データ(メール認証済み、名前、電話番号、住所)と提供が必要なもの(新しいポータルパスワード)が表示されます。 +- **Continue**(続行)をクリックして移行フォームに進みます。 + +#### 結果D:ポータルアカウントが既に存在 + +- ページに**「Account Found」**(アカウントが見つかりました)と表示され、既にポータルアカウントがあることが説明されます。 +- **Go to Login**(ログインへ)ボタンが表示され、メールアドレスが事前入力されたログインページに移動します。 + +### ステップ4a:アカウント作成フォーム(新規顧客またはSalesforceのみ) + +このフォームでは、アカウント作成に必要な残りの情報を収集します。 + +**完全新規の顧客**は以下を入力します: + +- 姓名 +- 住所(郵便番号、都道府県、市区町村、町名番地のフィールドを持つ日本向け住所フォーム) +- 電話番号 +- 生年月日(日付ピッカー) +- 性別(ラジオボタン:男性、女性、その他) +- パスワードとパスワード確認 +- 利用規約とプライバシーポリシーへの同意チェックボックス(必須) +- マーケティング同意チェックボックス(任意) + +**Salesforceのみの顧客**は、Salesforceアカウントレコードから名前と住所が事前入力されます。事前入力された情報のサマリーが上部に表示され、残りのフィールド(電話番号、生年月日、性別、パスワード、利用規約)を入力します。 + +パスワード要件は入力時にリアルタイムで検証表示されます: + +- 8文字以上 +- 大文字を含む +- 小文字を含む +- 数字を含む + +すべて入力したら**Create Account**(アカウント作成)をクリックします。 + +### ステップ4b:アカウント移行フォーム(既存WHMCSユーザー) + +このフォームは請求情報がWHMCSに既に登録されているため、よりシンプルです。 + +- 名前、メール、電話番号、住所が無効化された(グレーアウトした)フィールドに表示され、登録情報を確認できますが、ここでは変更できません。 +- 生年月日と性別は移行時には収集されません — WHMCSに既にこの情報があります。 +- 以下を入力します: + - パスワードとパスワード確認(上記と同じ要件) + - 利用規約とプライバシーポリシーへの同意(必須) + - マーケティング同意(任意) + +準備ができたら**Set Up Account**(アカウント設定)をクリックします。 + +### ステップ5:完了 + +- 緑のチェックマーク付きの**「Account Created!」**(アカウント作成完了!)ページが表示されます。 +- 「Your account has been set up successfully. You can now access all our services.」(アカウントが正常に設定されました。すべてのサービスにアクセスできます)というメッセージが表示されます。 +- 2つのオプションが提示されます: + - **Go to Dashboard** — 新しいアカウントダッシュボードに移動 + - **Check Internet Availability** — インターネットサービスページに移動して住所での利用可能状況を確認 +- 自動的にログインされ、ポータルの使用を開始できます。 + +## WHMCSで起こること + +登録に成功すると、WHMCSで以下が確認できます: + +- **新しいクライアントレコードが作成**(新規およびSalesforceのみの顧客向け)、または**既存のクライアントレコードがリンク**(WHMCS移行向け)。 +- **Clients > Client List** に移動し、登録時に使用したメールアドレスで検索します。 +- クライアントレコードを開き、以下を確認します: + - **First Name** と **Last Name** が登録時に入力したものと一致 + - **Email Address** が認証済みメールと一致 + - **Phone Number** が入力したものと一致 + - **住所フィールド**(Address 1、City、State、Postcode、Country)が入力したものと一致 + - **カスタムフィールド**: + - **Customer Number**(カスタムフィールドID 198)— 重要なリンクフィールド。SalesforceアカウントNumber(`SF_Account_No__c`)を保存し、WHMCSクライアントとSalesforceアカウントを接続します。 + - **Date of Birth**(カスタムフィールドID 201)— 登録時に入力したものと一致 + - **Gender**(カスタムフィールドID 200)— 登録時に選択したものと一致 +- 新規顧客の場合、クライアントレコードは**Active**ステータスになります。 +- WHMCS移行ユーザーの場合、既存のクライアントレコードがリンクされます。新しいクライアントは作成されません。 + +### WHMCSとSalesforceの接続方法 + +WHMCSとSalesforceの接続は**Customer Number**を通じて確立されます: + +1. SalesforceアカウントにはフィールドCustomer Number(`SF_Account_No__c`)があります。 +2. WHMCSクライアントは同じ値を**カスタムフィールド198**(Customer Number)に保存します。 +3. ポータルの内部データベースは、**ポータルユーザーID** ↔ **WHMCSクライアントID** ↔ **SalesforceアカウントID**をリンクするマッピングレコードを保存します。 + +この3方向リンクは登録時に作成され、すべてのクロスシステム操作に不可欠です。 + +## Salesforceで起こること + +登録に成功すると、Salesforceで以下が確認できます: + +- メールアドレスまたはCustomer Number(`SF_Account_No__c`)で**Account**レコードを検索します。 +- アカウントの以下のフィールドを確認します: + +| フィールド | API名 | 期待値 | +| --- | --- | --- | +| Customer Number | `SF_Account_No__c` | 一意の顧客ID(WHMCSカスタムフィールド198と一致) | +| Portal Status | `Portal_Status__c` | **Active** | +| Portal Registration Source | `Portal_Registration_Source__c` | **New Signup**(新規顧客)または **Migrated**(WHMCS移行) | +| WHMCS Account | `WH_Account__c` | 形式: "#1234 - Customer Name"(WHMCSクライアントIDを含む) | +| Portal Last Sign-In | `Portal_Last_SignIn__c` | 登録のタイムスタンプ | +| Email | `PersonEmail` | 認証済みメールアドレス | +| Phone | `Phone` | 入力された電話番号 | + +## 確認すべき重要事項 + +- 既にポータルアカウントがあるメールアドレスで登録を試みてください。「Account Found」メッセージが表示され、ログインに誘導されるはずです。 +- WHMCSに存在するがポータルにないメールアドレスで登録を試みてください。事前入力されたデータでWHMCS移行フローが表示されるはずです。 +- Salesforceにのみ存在するメールアドレスで登録を試みてください。事前入力された名前と住所データが表示されるはずです。 +- 完全に新しいメールアドレスで登録を試みてください。事前入力なしの完全なアカウント作成フォームが表示されるはずです。 +- パスワード検証の動作を確認してください:8文字未満、大文字なし、数字なしのパスワードを試してください。各ケースでエラーが表示されるはずです。 +- 利用規約チェックボックスが必須であることを確認してください — チェックなしではフォームが送信されないはずです。 +- 認証コードが10分後に期限切れになること、「Resend code」オプションが機能することを確認してください。 +- 登録後、WHMCSとSalesforceを確認してレコードが正しく作成または更新されたことを確認してください。 +- WHMCS移行の場合、WHMCSに重複クライアントが作成されていないことを確認してください。 +- アカウント作成成功後、自動的にログインされ、再度ログインすることなくダッシュボードにアクセスできることを確認してください。 diff --git a/docs/ja/uat/support-cases.md b/docs/ja/uat/support-cases.md new file mode 100644 index 00000000..22b6c166 --- /dev/null +++ b/docs/ja/uat/support-cases.md @@ -0,0 +1,74 @@ +--- +sidebar_position: 10 +title: "サポートケース" +--- + +# サポートケース + +## 概要 + +このガイドでは、ポータルを通じてサポートチケットを作成・管理する方法を説明します。ケースはSalesforceにOrigin「Portal Support」のCaseレコードとして保存されます。新しいケースの作成、フィルター付きケース一覧の表示、会話履歴の確認、返信の追加が可能です。ポータルでは顧客向けのステータス(New、In Progress、Awaiting Customer、Closed)のみを表示し、Salesforceの内部ワークフローステータスは非表示です。 + +## ポータルフロー + +### サポートケース一覧の表示 + +1. **Account > Support**(`/account/support`)に移動します。 +2. 右上に**New Case**ボタンが表示されます。 +3. 上部に**サマリー統計**:合計ケース、オープン、高優先度、解決済み。 +4. **検索・フィルターバー**:ケース番号や件名での検索、ステータスフィルター、優先度フィルター。 +5. 各ケースにステータスアイコン、ケース番号、件名、ステータス/優先度バッジ、最終更新タイムスタンプが表示されます。 + +### 新しいサポートケースの作成 + +1. **New Case**ボタンをクリック。 +2. フォームには以下のフィールドがあります: + - **Subject**(件名)(必須):問題の簡潔な説明。最大255文字。 + - **Priority**(優先度)(任意、デフォルトMedium): + - Low — 一般的な質問 + - Medium — 業務に影響する問題 + - High — 緊急 / サービス中断 + - **Description**(説明)(必須):問題の詳細な説明。 +3. **Create Case**をクリックして送信。 + +### ケース詳細の表示 + +1. ケースをクリックして詳細ページを開きます。 +2. ヘッダーにケース件名、番号、ステータスバッジ、優先度バッジ、作成日・更新日が表示。 +3. **Conversation**(会話)セクションにチャット形式ですべてのメッセージが表示: + - 顧客メッセージは右側 + - サポートチームメッセージは左側 + - 日付ごとにグループ化 + - 添付ファイルはクリップアイコンで表示 +4. メッセージは30秒ごとに自動更新されます。 + +### ケースへの返信 + +1. ケース詳細ページ下部に**返信フォーム**が表示されます(オープンケースのみ)。 +2. メッセージを入力して**Send Reply**をクリック。 +3. クローズ済みケースでは返信フォームの代わりに「This case is closed.」の通知が表示されます。 + +### ステータスの意味 + +| ポータルステータス | 意味 | +| --- | --- | +| New | ケース作成直後、未レビュー | +| In Progress | サポートチームが対応中 | +| Awaiting Customer | サポートが返信し、顧客の回答待ち | +| Closed | ケースが解決しクローズ | + +## Salesforceで起こること + +- **ケース作成**:常に新しいCaseレコードがSalesforceに作成されます。Origin = "Portal Support"。 +- **ケースコメント**:顧客の返信はCaseComment レコードとして作成されます。 +- **メールメッセージ**:サポートがメールで返信するとEmailMessageレコードが添付されます。 +- **ステータス変更**:エージェントがCaseステータスを変更するとPlatform Event(`Case_Status_Update__e`)が発火し、ポータルがリアルタイムで更新されます。 + +## 確認すべき重要事項 + +- 新しいケースを作成し、SalesforceにSubject、Description、Priority、Status(New)、Origin(「Portal Support」)が正しく表示されることを確認 +- ケース一覧がSalesforceのケースと一致すること +- ステータスフィルターが正しく機能すること +- 返信を投稿しSalesforceにCaseCommentレコードが作成されることを確認 +- Salesforceでのステータス変更がポータルにリアルタイムで反映されること +- クローズ済みケースで返信フォームが非表示になること From 9736e96cb31d46f8dfca4da827dfccd589a28bc0 Mon Sep 17 00:00:00 2001 From: barsa Date: Tue, 24 Feb 2026 13:15:35 +0900 Subject: [PATCH 2/2] refactor: consolidate error handling to safeOperation - Enhance safeOperation with rethrow and fallbackMessage options for CRITICAL operations - Migrate all 19 withErrorHandling calls across 5 services to safeOperation - Remove safeAsync from error.util.ts - Delete error-handler.util.ts (withErrorHandling, withErrorSuppression, withErrorLogging) - Update barrel exports in core/utils/index.ts --- apps/bff/src/core/utils/error-handler.util.ts | 122 ------------------ apps/bff/src/core/utils/error.util.ts | 16 --- apps/bff/src/core/utils/index.ts | 2 +- .../bff/src/core/utils/safe-operation.util.ts | 17 ++- .../workflows/password-workflow.service.ts | 21 +-- .../workflows/whmcs-link-workflow.service.ts | 9 +- .../services/invoice-retrieval.service.ts | 27 ++-- .../subscriptions-orchestrator.service.ts | 46 ++++--- .../users/infra/user-profile.service.ts | 31 +++-- 9 files changed, 104 insertions(+), 187 deletions(-) delete mode 100644 apps/bff/src/core/utils/error-handler.util.ts diff --git a/apps/bff/src/core/utils/error-handler.util.ts b/apps/bff/src/core/utils/error-handler.util.ts deleted file mode 100644 index d1e6cccb..00000000 --- a/apps/bff/src/core/utils/error-handler.util.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Centralized error handling utilities for consistent error handling across services - */ -import { - NotFoundException, - BadRequestException, - InternalServerErrorException, - type Type, -} from "@nestjs/common"; -import { Logger } from "nestjs-pino"; -import { extractErrorMessage } from "./error.util.js"; - -/** - * Types of exceptions that can be rethrown without wrapping - */ -type RethrowableException = Type; - -/** - * Options for error handling - */ -interface ErrorHandlerOptions { - /** - * Context string for logging (e.g., "Get invoices for user xyz") - */ - context: string; - - /** - * Exception types that should be rethrown as-is without wrapping - * @default [NotFoundException, BadRequestException] - */ - rethrow?: RethrowableException[]; - - /** - * Custom fallback message for InternalServerErrorException - * @default `${context} failed` - */ - fallbackMessage?: string; -} - -/** - * Wraps an async operation with consistent error handling. - * - * - Logs errors with context - * - Rethrows specified exception types as-is - * - Wraps other errors in InternalServerErrorException - * - * @example - * return withErrorHandling( - * () => this.whmcsService.getInvoices(clientId, userId), - * this.logger, - * { context: `Get invoices for user ${userId}`, rethrow: [NotFoundException] } - * ); - */ -export async function withErrorHandling( - operation: () => Promise, - logger: Logger, - options: ErrorHandlerOptions -): Promise { - const { context, rethrow = [NotFoundException, BadRequestException], fallbackMessage } = options; - - try { - return await operation(); - } catch (error) { - logger.error(context, { error: extractErrorMessage(error) }); - - for (const ExceptionType of rethrow) { - if (error instanceof ExceptionType) { - throw error; - } - } - - throw new InternalServerErrorException(fallbackMessage ?? `${context} failed`); - } -} - -/** - * Safe wrapper that returns null instead of throwing. - * Useful for optional operations where failure is acceptable. - * - * @example - * const cached = await withErrorSuppression( - * () => this.cache.get(key), - * this.logger, - * "Get cached value" - * ); - */ -export async function withErrorSuppression( - operation: () => Promise, - logger: Logger, - context: string -): Promise { - try { - return await operation(); - } catch (error) { - logger.warn(`${context} (suppressed)`, { error: extractErrorMessage(error) }); - return null; - } -} - -/** - * Wraps an async operation and logs errors, but always rethrows them. - * Useful when you want consistent logging but don't want to change the error type. - * - * @example - * return withErrorLogging( - * () => this.externalService.call(), - * this.logger, - * "Call external service" - * ); - */ -export async function withErrorLogging( - operation: () => Promise, - logger: Logger, - context: string -): Promise { - try { - return await operation(); - } catch (error) { - logger.error(context, { error: extractErrorMessage(error) }); - throw error; - } -} diff --git a/apps/bff/src/core/utils/error.util.ts b/apps/bff/src/core/utils/error.util.ts index ce5c73a9..5e85d20d 100644 --- a/apps/bff/src/core/utils/error.util.ts +++ b/apps/bff/src/core/utils/error.util.ts @@ -185,19 +185,3 @@ export function createDeferredPromise(): { return { promise, resolve, reject }; } - -/** - * Safe async operation wrapper with proper error handling - */ -export async function safeAsync( - operation: () => Promise, - fallback?: T -): Promise<{ data: T | null; error: EnhancedError | null }> { - try { - const data = await operation(); - return { data, error: null }; - } catch (unknownError) { - const error = toError(unknownError); - return { data: fallback ?? null, error }; - } -} diff --git a/apps/bff/src/core/utils/index.ts b/apps/bff/src/core/utils/index.ts index 80a7c974..0c1cee6c 100644 --- a/apps/bff/src/core/utils/index.ts +++ b/apps/bff/src/core/utils/index.ts @@ -9,7 +9,7 @@ export { chunkArray, groupBy, uniqueBy } from "./array.util.js"; // Error utilities export { extractErrorMessage } from "./error.util.js"; -export { withErrorHandling, withErrorSuppression, withErrorLogging } from "./error-handler.util.js"; +export { safeOperation, OperationCriticality, type SafeOperationOptions, type SafeOperationResult } from "./safe-operation.util.js"; // Retry utilities export { diff --git a/apps/bff/src/core/utils/safe-operation.util.ts b/apps/bff/src/core/utils/safe-operation.util.ts index 6490ae50..4b2c6391 100644 --- a/apps/bff/src/core/utils/safe-operation.util.ts +++ b/apps/bff/src/core/utils/safe-operation.util.ts @@ -1,3 +1,4 @@ +import { InternalServerErrorException } from "@nestjs/common"; import type { Logger } from "nestjs-pino"; import { extractErrorMessage } from "./error.util.js"; @@ -36,6 +37,12 @@ export interface SafeOperationOptions { /** Additional metadata for logging */ metadata?: Record; + + /** Exception types to rethrow as-is (only applicable when criticality is CRITICAL) */ + rethrow?: Array Error>; + + /** Custom message for InternalServerErrorException wrapper (only applicable when criticality is CRITICAL) */ + fallbackMessage?: string; } /** @@ -82,7 +89,7 @@ export async function safeOperation( executor: () => Promise, options: SafeOperationOptions ): Promise { - const { criticality, fallback, context, logger, metadata = {} } = options; + const { criticality, fallback, context, logger, metadata = {}, rethrow, fallbackMessage } = options; try { return await executor(); @@ -98,6 +105,14 @@ export async function safeOperation( switch (criticality) { case OperationCriticality.CRITICAL: logger.error(logPayload, `Critical operation failed: ${context}`); + if (rethrow) { + for (const ExceptionType of rethrow) { + if (error instanceof ExceptionType) { + throw error; + } + } + throw new InternalServerErrorException(fallbackMessage ?? `${context} failed`); + } throw error; case OperationCriticality.DEGRADABLE: diff --git a/apps/bff/src/modules/auth/infra/workflows/password-workflow.service.ts b/apps/bff/src/modules/auth/infra/workflows/password-workflow.service.ts index aa0d0aa1..f3b7d339 100644 --- a/apps/bff/src/modules/auth/infra/workflows/password-workflow.service.ts +++ b/apps/bff/src/modules/auth/infra/workflows/password-workflow.service.ts @@ -22,7 +22,7 @@ import { changePasswordRequestSchema, } from "@customer-portal/domain/auth"; import { mapPrismaUserToDomain } from "@bff/infra/mappers/index.js"; -import { withErrorHandling } from "@bff/core/utils/error-handler.util.js"; +import { safeOperation, OperationCriticality } from "@bff/core/utils/safe-operation.util.js"; import type { AuthResultInternal } from "@bff/modules/auth/auth.types.js"; @Injectable() @@ -64,7 +64,7 @@ export class PasswordWorkflowService { const passwordHash = await argon2.hash(password); - return withErrorHandling( + return safeOperation( async () => { try { await this.usersService.update(user.id, { passwordHash }); @@ -102,9 +102,11 @@ export class PasswordWorkflowService { tokens, }; }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Set password for user ${user.id}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Failed to set password", } ); @@ -151,7 +153,7 @@ export class PasswordWorkflowService { // This will throw BadRequestException if token is invalid, expired, or already used const { userId } = await this.passwordResetTokenService.consume(token); - return withErrorHandling( + return safeOperation( async () => { const prismaUser = await this.usersService.findByIdInternal(userId); if (!prismaUser) throw new BadRequestException("Invalid token"); @@ -168,11 +170,12 @@ export class PasswordWorkflowService { // Revoke all trusted devices - attacker could have set up a trusted device await this.trustedDeviceService.revokeAllUserDevices(freshUser.id); }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: "Reset password", - fallbackMessage: "Failed to reset password", + logger: this.logger, rethrow: [BadRequestException], + fallbackMessage: "Failed to reset password", } ); } @@ -213,7 +216,7 @@ export class PasswordWorkflowService { const passwordHash = await argon2.hash(newPassword); - return withErrorHandling( + return safeOperation( async () => { await this.usersService.update(user.id, { passwordHash }); const prismaUser = await this.usersService.findByIdInternal(user.id); @@ -244,9 +247,11 @@ export class PasswordWorkflowService { tokens, }; }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Change password for user ${user.id}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Failed to change password", } ); diff --git a/apps/bff/src/modules/auth/infra/workflows/whmcs-link-workflow.service.ts b/apps/bff/src/modules/auth/infra/workflows/whmcs-link-workflow.service.ts index b28b03d8..eac8e708 100644 --- a/apps/bff/src/modules/auth/infra/workflows/whmcs-link-workflow.service.ts +++ b/apps/bff/src/modules/auth/infra/workflows/whmcs-link-workflow.service.ts @@ -14,7 +14,7 @@ import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforc import { extractErrorMessage } from "@bff/core/utils/error.util.js"; import { mapPrismaUserToDomain } from "@bff/infra/mappers/index.js"; import { getCustomFieldValue } from "@customer-portal/domain/customer/providers"; -import { withErrorHandling } from "@bff/core/utils/error-handler.util.js"; +import { safeOperation, OperationCriticality } from "@bff/core/utils/safe-operation.util.js"; import type { User } from "@customer-portal/domain/customer"; import { PORTAL_SOURCE_MIGRATED, @@ -50,7 +50,7 @@ export class WhmcsLinkWorkflowService { ); } - return withErrorHandling( + return safeOperation( async () => { const clientDetails = await this.discoveryService.findClientByEmail(email); if (!clientDetails) { @@ -133,11 +133,12 @@ export class WhmcsLinkWorkflowService { needsPasswordSet: true, }; }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: "WHMCS account linking", - fallbackMessage: "Failed to link WHMCS account", + logger: this.logger, rethrow: [BadRequestException, ConflictException, UnauthorizedException], + fallbackMessage: "Failed to link WHMCS account", } ); } diff --git a/apps/bff/src/modules/billing/services/invoice-retrieval.service.ts b/apps/bff/src/modules/billing/services/invoice-retrieval.service.ts index 1b43a5cb..154dbac6 100644 --- a/apps/bff/src/modules/billing/services/invoice-retrieval.service.ts +++ b/apps/bff/src/modules/billing/services/invoice-retrieval.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Inject, BadRequestException } from "@nestjs/common"; +import { Injectable, Inject, BadRequestException, NotFoundException } from "@nestjs/common"; import { Logger } from "nestjs-pino"; import type { Invoice, @@ -9,7 +9,7 @@ import type { import { INVOICE_PAGINATION, VALID_INVOICE_QUERY_STATUSES } from "@customer-portal/domain/billing"; import { WhmcsInvoiceService } from "@bff/integrations/whmcs/services/whmcs-invoice.service.js"; import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js"; -import { withErrorHandling } from "@bff/core/utils/error-handler.util.js"; +import { safeOperation, OperationCriticality } from "@bff/core/utils/safe-operation.util.js"; import { parseUuidOrThrow } from "@bff/core/utils/validation.util.js"; /** @@ -40,7 +40,7 @@ export class InvoiceRetrievalService { parseUuidOrThrow(userId, "Invalid user ID format"); const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(userId); - return withErrorHandling( + return safeOperation( async () => { const invoiceList = await this.whmcsInvoiceService.getInvoices(whmcsClientId, userId, { page, @@ -57,8 +57,13 @@ export class InvoiceRetrievalService { return invoiceList; }, - this.logger, - { context: `Get invoices for user ${userId}`, fallbackMessage: "Failed to retrieve invoices" } + { + criticality: OperationCriticality.CRITICAL, + context: `Get invoices for user ${userId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], + fallbackMessage: "Failed to retrieve invoices", + } ); } @@ -69,7 +74,7 @@ export class InvoiceRetrievalService { parseUuidOrThrow(userId, "Invalid user ID format"); const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(userId); - return withErrorHandling( + return safeOperation( async () => { const invoice = await this.whmcsInvoiceService.getInvoiceById( whmcsClientId, @@ -79,9 +84,11 @@ export class InvoiceRetrievalService { this.logger.log(`Retrieved invoice ${invoiceId} for user ${userId}`); return invoice; }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Get invoice ${invoiceId} for user ${userId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Failed to retrieve invoice", } ); @@ -108,11 +115,13 @@ export class InvoiceRetrievalService { ); } - return withErrorHandling( + return safeOperation( async () => this.getInvoices(userId, { page, limit, status }), - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Get ${status} invoices for user ${userId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Failed to retrieve invoices", } ); diff --git a/apps/bff/src/modules/subscriptions/subscriptions-orchestrator.service.ts b/apps/bff/src/modules/subscriptions/subscriptions-orchestrator.service.ts index 52b66ba4..2869b67b 100644 --- a/apps/bff/src/modules/subscriptions/subscriptions-orchestrator.service.ts +++ b/apps/bff/src/modules/subscriptions/subscriptions-orchestrator.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Inject, BadRequestException } from "@nestjs/common"; +import { Injectable, Inject, BadRequestException, NotFoundException } from "@nestjs/common"; import { subscriptionListSchema, subscriptionStatusSchema, @@ -17,7 +17,7 @@ import { WhmcsInvoiceService } from "@bff/integrations/whmcs/services/whmcs-invo import { WhmcsSubscriptionService } from "@bff/integrations/whmcs/services/whmcs-subscription.service.js"; import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js"; import { Logger } from "nestjs-pino"; -import { withErrorHandling } from "@bff/core/utils/error-handler.util.js"; +import { safeOperation, OperationCriticality } from "@bff/core/utils/safe-operation.util.js"; import { extractErrorMessage } from "@bff/core/utils/error.util.js"; export interface GetSubscriptionsOptions { @@ -53,7 +53,7 @@ export class SubscriptionsOrchestrator { const { status } = options; const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(userId); - return withErrorHandling( + return safeOperation( async () => { const subscriptionList = await this.whmcsSubscriptionService.getSubscriptions( whmcsClientId, @@ -74,9 +74,11 @@ export class SubscriptionsOrchestrator { totalCount: subscriptions.length, }); }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Get subscriptions for user ${userId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Failed to retrieve subscriptions", } ); @@ -94,7 +96,7 @@ export class SubscriptionsOrchestrator { // Get WHMCS client ID from user mapping const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(userId); - return withErrorHandling( + return safeOperation( async () => { const subscription = await this.whmcsSubscriptionService.getSubscriptionById( whmcsClientId, @@ -111,9 +113,11 @@ export class SubscriptionsOrchestrator { return subscription; }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Get subscription ${subscriptionId} for user ${userId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Failed to retrieve subscription", } ); @@ -164,7 +168,7 @@ export class SubscriptionsOrchestrator { completed: number; cancelled: number; }> { - return withErrorHandling( + return safeOperation( async () => { const subscriptionList = await this.getSubscriptions(userId); const subscriptions: Subscription[] = subscriptionList.subscriptions; @@ -180,9 +184,11 @@ export class SubscriptionsOrchestrator { return subscriptionStatsSchema.parse(stats); }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Generate subscription stats for user ${userId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], } ); } @@ -191,7 +197,7 @@ export class SubscriptionsOrchestrator { * Get subscriptions expiring soon (within next 30 days) */ async getExpiringSoon(userId: string, days: number = 30): Promise { - return withErrorHandling( + return safeOperation( async () => { const subscriptionList = await this.getSubscriptions(userId); const subscriptions: Subscription[] = subscriptionList.subscriptions; @@ -213,9 +219,11 @@ export class SubscriptionsOrchestrator { ); return expiringSoon; }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Get expiring subscriptions for user ${userId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], } ); } @@ -224,7 +232,7 @@ export class SubscriptionsOrchestrator { * Get recent subscription activity (newly created or status changed) */ async getRecentActivity(userId: string, days: number = 30): Promise { - return withErrorHandling( + return safeOperation( async () => { const subscriptionList = await this.getSubscriptions(userId); const subscriptions = subscriptionList.subscriptions; @@ -242,9 +250,11 @@ export class SubscriptionsOrchestrator { ); return recentActivity; }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Get recent subscription activity for user ${userId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], } ); } @@ -257,7 +267,7 @@ export class SubscriptionsOrchestrator { throw new BadRequestException("Search query must be at least 2 characters long"); } - return withErrorHandling( + return safeOperation( async () => { const subscriptionList = await this.getSubscriptions(userId); const subscriptions = subscriptionList.subscriptions; @@ -275,9 +285,11 @@ export class SubscriptionsOrchestrator { ); return matches; }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Search subscriptions for user ${userId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], } ); } @@ -292,7 +304,7 @@ export class SubscriptionsOrchestrator { ): Promise { const { page = 1, limit = 10 } = options; - return withErrorHandling( + return safeOperation( async () => { const cachedResult = await this.tryGetCachedInvoices(userId, subscriptionId, page, limit); if (cachedResult) return cachedResult; @@ -326,9 +338,11 @@ export class SubscriptionsOrchestrator { return result; }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Get invoices for subscription ${subscriptionId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Failed to retrieve subscription invoices", } ); diff --git a/apps/bff/src/modules/users/infra/user-profile.service.ts b/apps/bff/src/modules/users/infra/user-profile.service.ts index 49ed9042..9afb133d 100644 --- a/apps/bff/src/modules/users/infra/user-profile.service.ts +++ b/apps/bff/src/modules/users/infra/user-profile.service.ts @@ -38,7 +38,7 @@ import { WhmcsClientService } from "@bff/integrations/whmcs/services/whmcs-clien import { WhmcsInvoiceService } from "@bff/integrations/whmcs/services/whmcs-invoice.service.js"; import { WhmcsSubscriptionService } from "@bff/integrations/whmcs/services/whmcs-subscription.service.js"; import { SalesforceFacade } from "@bff/integrations/salesforce/facades/salesforce.facade.js"; -import { withErrorHandling } from "@bff/core/utils/error-handler.util.js"; +import { safeOperation, OperationCriticality } from "@bff/core/utils/safe-operation.util.js"; import { parseUuidOrThrow } from "@bff/core/utils/validation.util.js"; import { UserAuthRepository } from "./user-auth.repository.js"; import { AddressReconcileQueueService } from "../queue/address-reconcile.queue.js"; @@ -279,7 +279,7 @@ export class UserProfileAggregator { const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(validId); - return withErrorHandling( + return safeOperation( async () => { await this.whmcsClientService.updateClientAddress(whmcsClientId, parsed); await this.whmcsClientService.invalidateUserCache(validId); @@ -297,9 +297,11 @@ export class UserProfileAggregator { const refreshedAddress = await this.whmcsClientService.getClientAddress(whmcsClientId); return addressSchema.parse(refreshedAddress ?? {}); }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Update address for user ${validId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Unable to update address", } ); @@ -320,7 +322,7 @@ export class UserProfileAggregator { ): Promise
{ const validId = parseUuidOrThrow(userId, ERROR_INVALID_USER_ID); - return withErrorHandling( + return safeOperation( async () => { const mapping = await this.mappingsService.findByUserId(validId); if (!mapping?.whmcsClientId) { @@ -353,9 +355,11 @@ export class UserProfileAggregator { return this.fetchRefreshedAddress(validId, mapping.whmcsClientId); }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Update bilingual address for user ${validId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Unable to update address", } ); @@ -438,7 +442,7 @@ export class UserProfileAggregator { const validId = parseUuidOrThrow(userId, ERROR_INVALID_USER_ID); const parsed = updateCustomerProfileRequestSchema.parse(update); - return withErrorHandling( + return safeOperation( async () => { // Explicitly disallow name changes from portal if (parsed.firstname !== undefined || parsed.lastname !== undefined) { @@ -482,17 +486,22 @@ export class UserProfileAggregator { return this.getProfile(validId); }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Update profile for user ${validId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Unable to update profile", } ); } async getUserSummary(userId: string): Promise { - return withErrorHandling(async () => this.buildUserSummary(userId), this.logger, { + return safeOperation(async () => this.buildUserSummary(userId), { + criticality: OperationCriticality.CRITICAL, context: `Get user summary for ${userId}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Unable to retrieve dashboard summary", }); } @@ -641,7 +650,7 @@ export class UserProfileAggregator { private async getProfileForUser(user: PrismaUser): Promise { const whmcsClientId = await this.mappingsService.getWhmcsClientIdOrThrow(user.id); - return withErrorHandling( + return safeOperation( async () => { const whmcsClient = await this.whmcsClientService.getClientDetails(whmcsClientId); const userAuth = mapPrismaUserToUserAuth(user); @@ -676,9 +685,11 @@ export class UserProfileAggregator { gender, }; }, - this.logger, { + criticality: OperationCriticality.CRITICAL, context: `Fetch client profile from WHMCS for user ${user.id}`, + logger: this.logger, + rethrow: [NotFoundException, BadRequestException], fallbackMessage: "Unable to retrieve customer profile from billing system", } );