🚛

Stripe Connectでアカウントを作成する際の住所の正規化に、GeoloniaのOSSライブラリで挑んでみる

2021/12/29に公開
4

年末のJP_Stripe配信で、Stripe Connectでの住所の書き方の記事が話題になりました。

https://gist.github.com/toruf-stripe/60293ec99a333a7af1daaca31b562741

その中で、「Geoloniaがリリースしている住所正規化ライブラリが使えるのでは?」と思いつきで言っちゃったので、どこまでいけるか試してみました。

使用するライブラリ

https://github.com/geolonia/normalize-japanese-addresses

表記揺れや新旧字体など、住所入力でありがちな揺らぎを吸収してくれるOSS(MITライセンス)です。

Stripeでは、Customerに登録する住所(請求・配送)データの名寄せ・正規化にも利用できそうかなと思います。

もし実際に動かしてみたい場合は、以下のようにtsdxでセットアップしておくと手軽です。

% npx tsdx create --template basic connect-address
% cd connect-address
% yarn add @geolonia/normalize-japanese-addresses

動きを確認する

Jestでテストをまわしながら動きの確認を進めます。
これは本体のテストコードをそのまま持ってきたサンプルですが、「住所を入力すると、正規化したオブジェクトで返ってくる」動きであることがわかります。

import { normalize } from '@geolonia/normalize-japanese-addresses';

test('大阪府堺市北区新金岡町4丁1−8', async () => {
  const res = await normalize('大阪府堺市北区新金岡町4丁1−8')
  expect(res).toStrictEqual({"pref": "大阪府", "city": "堺市北区", "town": "新金岡町四丁", "addr": "1-8", "lat": 34.568184, "lng": 135.519409, "level": 3})
})

Stripe Connectで使うために必要なこと

ざっとみたところ、最低限必要なのは以下の2点です。

  • オブジェクトのプロパティ名をStripe Connect向けに変更する
  • Stripe Connectの「例外」に対応する

スキル的に諦めたこと

以下の3点については、今回は諦めました。
未対応のままですが、「Stripe Connectで、accounts.create APIがエラーを返さない」ことは確認しています。

  • 丁目、番地はアラビア数字。全角でも半角でもよい。
    ->ライブラリが「丁目」を漢数字にして返してきます。ここだけを変換する処理が追加で必要です。
  • 京都市の通り名は、city の最後につける
    ->ライブラリが通り名をDropしますので、入力値からそこだけ抜き出す処理が必要です
  • 住所に、建物名、階、部屋番号が含まれない場合は、line2 を指定しない
    ->ライブラリの処理だけでは、line2に相当する建物情報などは取得できません

コードを抽象化する

処理を追加する前に、メインの処理部分を関数にします。
ついでに型定義も簡単に追加しておきます。

import { normalize } from '@geolonia/normalize-japanese-addresses';

interface StripeConnectAddress {
  state: string;
  city: string;
  town: string;
  line1: string;
  line2?: string;
}

const normalizeForStripeConnect = async (address: string): Promise<StripeConnectAddress> => {
  const res = await normalize(address)
  return {
    state: res.pref,
    city: res.city,
    town: res.town,
    line1: res.addr
  }
}

test('大阪府堺市北区新金岡町4丁1−8', async () => {
  const res = await normalizeForStripeConnect('大阪府堺市北区新金岡町4丁1−8')
  expect(res)
  .toEqual({
    state: "大阪府",
    city: "堺市北区",
    town: "新金岡町四丁",
    line1: "1-8"
  })
})

実装

そして実装したものがこちらです。
「例外」の岩手県と札幌市対応処理を追加しています。

  • 札幌市の「◯条」は、town の最後につける
  • 岩手県の「第◯地割」は、town の最後につける
const convertToStripeConnect = (address: NormalizeResult): StripeConnectAddress => {
  return {
    state: address.pref,
    city: address.city,
    town: address.town,
    line1: address.addr
  }
}

const normalizeForStripeConnect = async (address: string): Promise<StripeConnectAddress> => {
  const res = await normalize(address)
  const { addr, town, pref } = res
  // 岩手県の「第◯地割」は、town の最後につける
  if (pref === '岩手県') {
    if (/地割/.test(addr)) {
      const chiwari = (() => {
        const reg1 = addr.match(/.\d地割/)
        if (reg1) return reg1[0]
        const reg2 = addr.match(/.\d地割/)
        if (reg2) return reg2[0]
        return ''
      })()
      res.addr = addr.replace(/.\d地割/, '').replace(/.\d地割/, '')
      res.town = `${town}${chiwari}`
    }
  }
  // 札幌市の「◯条」は、town の最後につける
  if (pref === '北海道') {
    if (//.test(town)) {
      res.addr = `${town.substring(town.indexOf('条') + 1)}${addr}`
      res.town = town.substring(0, town.indexOf('条') + 1)
    }
  }
  return convertToStripeConnect(res)
}

Gistで見る

どのような動きになるかは、以下のテストコードをご覧ください。


test.each([
  ['大阪府堺市北区新金岡町4丁1−8', {
    state: "大阪府",
    city: "堺市北区",
    town: "新金岡町四丁",
    line1: "1-8"
  }],
  ["神奈川県横浜市中区本町6丁目50−10", {
    "city": "横浜市中区",
    "line1": "50-10",
    "state": "神奈川県",
    "town": "本町六丁目",

  }],
  ['岩手県九戸郡洋野町種市第35地割102−2', {
    "city": "九戸郡洋野町",
    "line1": "102-2",
    "state": "岩手県",
    "town": "種市第35地割",
  }],
  ['京都府 京都市 中京区 御幸町通三条上る 丸屋町326', {
    "city": "京都市中京区",
    "line1": "326",
    "state": "京都府",
    "town": "丸屋町",

  }],
  ['宮城県仙台市太白区鈎取一本杉64−1', {
    "city": "仙台市太白区",
    "line1": "一本杉64-1",
    "state": "宮城県",
    "town": "鈎取",

  }],
  ['北海道札幌市豊平区平岸5条6丁目1−24', {
    "city": "札幌市豊平区",
    "line1": "六丁目1-24",
    "state": "北海道",
    "town": "平岸五条",

  }],
  ['愛知県名古屋市熱田区二番2丁目20−2', {
    "city": "名古屋市熱田区",
    "line1": "20-2",
    "state": "愛知県",
    "town": "二番二丁目",
  }]
])("%p", async (address, expectedResult) => {
  const res = await normalizeForStripeConnect(address)
  expect(res).toEqual(expectedResult)
})

表記揺れやスペースの処理なども行われており、ほぼそのままStripe APIに投げれるようなデータになりました。

Stripe提供UIを使った場合はどうなるか

ちなみに、Stripeが提供するUIを使った場合、郵便番号で自動入力されます。
そもそも入力させないことで、表記揺れを回避しているスタイルかなと思います。

line1 / line2のみ手動入力のイメージです。

やってみて

「諦めた点」にあるように、丁目がtownに漢数字で入っていることなどは、Stripe Connectで組み込みする上では気になります。
が、スペースの混入や数字表記・新旧字体などのゆらぎ調整などが見事に行われていることもわかります。

諦めたと書いた部分も、正規表現での調整がやりきれなかったものばかりですので、ある程度正規表現が扱える人であれば完遂することも不可能ではないと思います。

自前でUIを構築したい場合など、どうしてもStripeの用意するUIにリダイレクトさせることが困難なケースでは、このようなOSSを活用してはいかがでしょうか。

Discussion

宮内@geolonia宮内@geolonia

弊社のnormalize-japanese-addressesを試していただいてありがとうございます!

丁目がtownに漢数字で入っていることなどは気になります。

これにつきましては、たとえば岩手県の花巻市には「十二丁目」という地名があります。どういうことかというと、ここには十一丁目とか十三丁目があるわけではなく、他の町名に混ざって十二丁目だけがぽつんとある感じです。

https://geolonia.github.io/japanese-addresses/api/ja/岩手県/花巻市.json

このケースの場合12丁目としてしまうことは一郎を1郎と書くことになってしまうため、アラビア数字か漢数字に統一するなら、漢数字のほうが誤りではないという意味で、このライブラリでは漢数字に統一しています。(ちなみに万丁目という地名もあります。笑)

一応弊社では別のライブラリでアラビア数字<=>漢数字の相互変換をするライブラリも用意しておりますが、上述のような問題がありますので、現状ではアラビア数字に変換することはとても困難ですが、デジタル庁によるベースレジストリが整備されれば、このあたりの精度が劇的に改善されると思います。(あと数年かかるかもですが。。。)

https://github.com/geolonia/japanese-numeral

京都市の通り名

京都市の通り名については、それがなくても郵送等では実害がないと弊社では判断しておりまして、弊社の不動産共通IDでも同様の仕様になっており、「置き配」ですでに利用されています。

hidetaka okamotohidetaka okamoto

コメントありがとうございます!
「Stripe ConnectとそのAPIが要求する仕様に、このライブラリ +αでどこまで対応できるか。」を試す意図でしたので、ライブラリ側の挙動が気になるような書き方になっていてすみません。(「Connect向けに使う上では」を先ほど追記しました)

丁目の漢数字表記
Connect側の要求がアラビア数字なだけで、一般的には漢数字が標準なのかなと思っていましたが、
花巻市のようなケースがあるからなんですね。そして万丁目を10000丁目にするのもかなり厳しそうですね・・・

japanese-numeralライブラリも試したのですが、「X丁目」部分だけを抜き出す正規表現に手こずって断念しました・・・
漢数字でConnectのAPIに投げてみて、一応処理はされた様子でしたので、元記事ではアラビア数字指定でしたが、記事内では漢数字のまま投げるようにしています。

京都の通り名
住んでいた時にもよく省略していましたので、個人的には省略したままでいいのではと思っている派です。
役所とかで書く時、絶対に入力欄が足りなくなるんですよね・・・・

ただ、元記事の例外項目に上がっていましたので、「この記事のサンプルでは省略されます」と伝える意味で言及することにしました。
Connectでは本人確認フェーズがありますので、そこで必要になるかもしれず、来年もう少し掘り下げて調べてみるようにします。