EC-CUBE / EC-CUBE/ec-cube2

郵便番号・デジタルアドレス API 連携(未登録時は CSV/DB 検索へフォールバック)

Open
#1,424 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
PHP
Stars
92
Forks
97
Avg merge
4d 2h
Merged PRs (30d)
9

Description

## 背景・目的

日本郵便「郵便番号・デジタルアドレス for Biz」API に対応し、住所自動補完を API 経由でも行えるようにする。ただし OSS 配布の性質上、**本体に共通の認証キーを同梱することは規約違反**(第8条(8)(9)、第5条4項)となるため、**各店舗が管理画面から自店のクライアントID/シークレットを登録**する方式とする。**未登録の店舗は従来どおり `mtb_zip`(KEN_ALL.CSV 由来)での DB 検索にフォールバック**し、既存挙動を完全に維持する。

## 規約上の前提・制約(設計の制約条件)

「郵便番号・デジタルアドレス API 個別利用規約」+「第三者提供のための特約」より、以下を設計不変条件とする:

1. **認証キーを本体に同梱・ハードコードしない**(第8条(8)(9)、第5条4項)。→ 各店舗が管理画面で登録。
2. **API はサーバサイドから呼ぶ**。シークレットをブラウザに露出させない。
3. **取得データを網羅蓄積して独自DB化しない**(第8条(5)、第7条)。→ API 結果を `mtb_zip` へキャッシュ保存しない。トークン等のキャッシュは可。
4. **個人情報として扱う**(第9・10条、特約第2条)。デジタルアドレス復号住所やシークレットをログ出力しない。
5. **レートリミット対応**(第6条2項、特約第3条)。タイムアウト・失敗時フォールバックを実装。
6. **規約同意の明示**。各店舗が自店で連携システムを登録し規約に同意する必要があるため、設定画面に規約リンクと注意書きを掲示。

## 現状の実装(調査済み)

| 項目 | 場所 |
|---|---|
| エンドポイント | `html/input_zip.php` → `LC_Page_InputZip::process()`(`data/class/pages/LC_Page_InputZip.php:50`) |
| DB 検索本体 | `SC_Utils_Ex::sfGetAddress($zipcode)`(`data/class/util/SC_Utils.php:1616`)→ `SELECT state,city,town FROM mtb_zip WHERE zipcode=?` |
| 別ルート(JSON) | `API_AddrFromZip::doAction()`(`data/class/api/operations/AddrFromZip.php:40`)も同じ `sfGetAddress` を使用 |
| レスポンス | `input_zip.php` は `state\|city\|town` のパイプ区切りテキスト(state は `mtb_pref` で都道府県ID化) |
| JS | `eccube.getAddress()`(`data/eccube.js:111`)が `$.get` → `data.split("\|")` → `eccube.putAddress()` |
| CSV→DB | `LC_Page_Admin_Basis_ZipInstall`(`mtb_zip` へ登録) |
| 設定保存の慣例 | 店舗固有設定は `dtb_baseinfo`(1行)+ `SC_Helper_DB::registerBasisData/getBasisDataFromDB`(SHOPマスター `LC_Page_Admin_Basis` が代表例) |

## 設計方針

### 全体アーキテクチャ

```mermaid
flowchart TD
Browser["ブラウザ
eccube.getAddress(JS/テンプレート変更なし)"]
Endpoint["html/input_zip.php
→ LC_Page_InputZip::process()"]
Helper["SC_Helper_Zip(新規)
分岐を一元化"]
ApiClient["日本郵便 API クライアント
SC_Helper_JpAddressApi"]
Db["SC_Utils_Ex::sfGetAddress()
mtb_zip DB 検索(従来)"]
Result["戻り値は従来と同一形式
[['state'=>都道府県ID,'city'=>...,'town'=>...], ...]
→ パイプ区切り応答も JSON API 応答も無改修で成立"]

Browser -->|"$.get input_zip.php?zip1=&zip2="| Endpoint
Endpoint -->|"SC_Helper_Zip_Ex::getAddress($zipcode)"| Helper
Helper -->|"認証情報あり"| ApiClient
Helper -->|"認証情報なし"| Db
ApiClient -.->|"失敗/タイムアウト時はフォールバック(ログ記録)"| Db
ApiClient --> Result
Db --> Result
```

**分岐を新ヘルパー `SC_Helper_Zip` に一元化**し、`LC_Page_InputZip` と `API_AddrFromZip` の両呼び出し元をこのヘルパー経由に差し替える。API 応答の都道府県名は `mtb_pref` 反転で ID 化し、既存の戻り値契約(都道府県ID)に正規化する。これにより下流(JS・テンプレート・JSON API)は無改修。

### 設定保存

- 格納先: `dtb_baseinfo` に新カラム追加(既存慣例に準拠、`extractOnlyColsOf` で自動反映)。
- 例: `jp_addr_api_client_id TEXT`, `jp_addr_api_client_secret TEXT`, `jp_addr_api_enabled SMALLINT`(同意フラグ兼用も可)
- 管理画面: `data/class/pages/admin/basis/` に **専用ページ `LC_Page_Admin_Basis_AddressApi` を新設**(SHOPマスターに混ぜず、規約リンク・同意チェック・接続テストを載せられるようにする)。
- 保存は `SC_Helper_DB_Ex::registerBasisData()` を再利用。
- 読取は `SC_Helper_DB_Ex::sfGetBasisData()`。
- シークレット表示欄はマスク(`●●●●`+再入力方式、値未変更時は保持)。
- CSRF は `LC_Page_Admin::init()` の `doValidToken` を継承(既存パターン)。
- 管理メニューへのリンク追加(basis 配下)。

### API クライアント(新規 `SC_Helper_JpAddressApi` 等)

- OAuth2 client credentials フロー: client_id/secret → トークンエンドポイント → Bearer トークン取得 → 住所検索エンドポイント。
- **トークンはキャッシュ**(取得データではなく認証情報のため蓄積可。有効期限まで再利用)。
- HTTP クライアント: 既存依存(`guzzlehttp/*` が composer にあり)を優先。タイムアウト短め・リトライ最小。→ **要確認: 標準化する HTTP クライアント**。
- エラー時はフォールバック(zip→住所は DB があるため安全に切替可能)。シークレット・復号住所はログに残さない。

### デジタルアドレス対応(Phase 2)

デジタルアドレス(英数字コード)→ 復号住所は `mtb_zip` に相当データが無くフォールバック不可、かつ住所詳細(番地・建物)まで返るため、`state|city|town` の3項目では表現できない。

- 新モード/新エンドポイント(例 `input_zip.php?mode=digital` か新 `input_digital_address.php`)で **JSON レスポンス**(addr01/addr02/建物等を含む)を返す。
- 対応する入力欄・JS を各フォーム(会員登録・受注編集等)に追加。
- 認証情報未登録時は当機能を非表示(フォールバック先が無いため)。

## 変更・新規ファイル一覧(想定)

**新規**
- `data/class/helper/SC_Helper_Zip.php`(+ `_Ex` 拡張スタブ)— 分岐の一元化
- `data/class/helper/SC_Helper_JpAddressApi.php`(+ `_Ex`)— APIクライアント
- `data/class/pages/admin/basis/LC_Page_Admin_Basis_AddressApi.php`(+ `_Ex`)
- `html/admin/basis/address_api.php`
- `data/Smarty/templates/admin/basis/address_api.tpl`
- DBマイグレーション SQL(`dtb_baseinfo` カラム追加、`html/install/sql/` および更新用スクリプト)

**変更**
- `data/class/pages/LC_Page_InputZip.php` — `sfGetAddress` → `SC_Helper_Zip_Ex::getAddress`
- `data/class/api/operations/AddrFromZip.php` — 同上
- 管理メニュー定義(basis リンク追加)
- Phase 2: `data/eccube.js` 等にデジタルアドレス用関数追加

## フォールバック/エラー時挙動

| 状況 | 郵便番号→住所 | デジタルアドレス |
|---|---|---|
| 認証情報 未登録 | `mtb_zip` DB 検索(従来通り) | 機能非表示 |
| 認証情報 登録済・API 正常 | API 結果 | API 結果 |
| API タイムアウト/エラー | `mtb_zip` DB 検索にフォールバック(ログ) | エラーメッセージ表示 |
| レートリミット超過 | 同上フォールバック | エラー表示+再試行案内 |

## 段階実装

- **Phase 1**: 設定画面+郵便番号→住所の API/DB 分岐+フォールバック(既存挙動を壊さない最小実装)
- **Phase 2**: デジタルアドレス復号(新入力欄・新レスポンス)

## テスト計画

- Unit: `SC_Helper_Zip::getAddress` の分岐(認証あり/なし/API失敗時フォールバック)を HTTP モックで検証。都道府県名→ID 正規化。
- Unit: 設定保存(`registerBasisData` 経由)、シークレットのマスク保持。
- E2E: 未登録店舗で従来どおり住所補完される(回帰)。登録店舗で API 経路(要モックまたはサンドボックス)。
- MySQL / PostgreSQL 両系で実施。

## 未確定・要確認事項

- [ ] 日本郵便 API の正式エンドポイント URL・認証仕様・レスポンススキーマ(公式リファレンス入手後に確定)
- [ ] 標準化する HTTP クライアント(guzzle か既存 `HTTP_Request` か)
- [ ] シークレットの暗号化要否(既存はメールパスワード等も平文。規約第5条4項との兼ね合い)
- [ ] 設定画面での規約同意 UI の要否・文言

## 受け入れ条件

- 認証情報未登録の既存店舗で住所補完の挙動が変わらない(回帰なし)
- 認証情報を登録すると郵便番号検索が API 経由になる
- API 障害時に DB 検索へ自動フォールバックする
- 認証キーがブラウザ・ログ・リポジトリに露出しない

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading data/class/pages/LC_Page_InputZip.php, data/class/util/SC_Utils.php, and data/class/api/operations/AddrFromZip.php to trace the existing address lookup contract. Then review the dtb_baseinfo persistence patterns and the listed API specification questions before narrowing the work to Phase 1. Done means existing DB lookup remains unchanged for unconfigured stores, configured stores can use the API, and API failures fall back safely without exposing secrets.

Written by the indexing model from the issue text.

Assessment

Tech stack
mysql, php, postgresql
Domain
api, backend, database
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.