決済手段を JSON-LD で構造化する — PayPay・交通系 IC・JCB を acceptedPaymentMethod に配線する

schema.org 標準の acceptedPaymentMethod enum は Visa / Mastercard / Cash 止まりで、PayPay や Suica や JCB は含まれない。日本の店舗が「PayPay 使えますか?」という AI 引用クエリに事実として答えさせるために、schema.org enum / DefinedTerm / sameAs / カスタム URI の 4 手法で 4 エンジンの pickup 挙動を実測したエンジニア視点の実装ガイド。

「この店 PayPay 使えますか?」を AI アシスタントに投げる利用者は、私が半年前に想定していたよりずっと多くなりました。ChatGPT や Perplexity は「店舗の URL を確認してください」と hedge するか、あるいは「一般的に日本の飲食店ではクレジットカードと現金が主流ですが、個別の店舗の QR コード決済対応は不明です」と一段抽象化した回答を返しがちです。ここで奇妙なのは、schema.org には acceptedPaymentMethod というプロパティがちゃんと存在していることです。それでいて、この語彙で PayPay や Suica を明示している国内の店舗サイトを、私はほとんど見たことがありません。

理由は単純で、schema.org 標準の PaymentMethod enum は goodrelations 由来のまま止まっていて、国際ブランド (Visa / Mastercard / American Express / Discover) と Cash と ByBankTransferInAdvance くらいしか URI が用意されていません。日本の意思決定に最も効く PayPay も、交通系 IC も、JCB すらも、標準 enum には無い。標準に無いから書かれない。書かれないから AI は事実として引用できない。この連鎖を切るためのエンジニアリング仕事を、本稿ではエンジニア視点でまとめます。私は都内の 5 業種 (Restaurant / Cafe / Beauty Salon / Clinic / Retail) の実店舗を想定して支払端末の受入 pattern を staging し、4 つの実装手法 (schema.org enum / DefinedTerm / sameAs / カスタム URI) で acceptedPaymentMethod を書き分け、4 エンジン (ChatGPT / Perplexity / Claude / Gemini) の pickup 挙動を並行して確認しました。

まず入れ子を見せる

前置きを続ける前に実物を出します。これは Restaurant で「現金 + Visa/Mastercard/JCB + PayPay + Suica (交通系 IC 代表)」を受け付けるという最頻 pattern を、4 手法混在で書いた最小に近い形です。

{
  "@context": "https://schema.org",
  "@type": "Restaurant",
  "name": "Cafe Example",
  "currenciesAccepted": "JPY",
  "acceptedPaymentMethod": [
    {
      "@type": "PaymentMethod",
      "@id": "http://purl.org/goodrelations/v1#Cash",
      "name": "現金"
    },
    {
      "@type": "PaymentMethod",
      "@id": "http://purl.org/goodrelations/v1#VISA",
      "name": "Visa"
    },
    {
      "@type": "PaymentMethod",
      "@id": "http://purl.org/goodrelations/v1#MasterCard",
      "name": "Mastercard"
    },
    {
      "@type": "DefinedTerm",
      "name": "JCB",
      "sameAs": "https://www.jcb.co.jp/"
    },
    {
      "@type": "DefinedTerm",
      "name": "PayPay",
      "sameAs": "https://paypay.ne.jp/"
    },
    {
      "@type": "DefinedTerm",
      "name": "交通系 IC カード",
      "sameAs": "https://www.jreast.co.jp/suica/"
    }
  ]
}

ここで配線として効いているのは、@type が意図的に混在していることです。国際ブランド 3 種は goodrelations 標準の URI (@id) を持つので PaymentMethod 型で URI 参照するのが正解、日本固有の JCB / PayPay / 交通系 IC は標準 URI を持たないので DefinedTerm 型で namesameAs を書く。1 つの配列に 2 種類の @type を並べる形は違和感を覚える人もいますが、schema.org 側の仕様上どちらも acceptedPaymentMethod の値として妥当で、私が実測した限り 4 エンジンとも配列内の型混在で読み取りが崩れることはありません。むしろ「PayPay は書いたが @type: PaymentMethod にしてしまい @id を書けなかった」パターンの方が、エンジン側で silent に落とされます。

4 category で走査する

日本の店舗の支払端末は、私の観察では概ね 4 category に分かれます。この整理をしておくと、店舗ごとの acceptedPaymentMethod を組むときの走査漏れが激減します。

Category代表例実装手法備考
クレジットカード (国際ブランド)Visa / Mastercard / American Express / Discovergoodrelations 標準 URI (@id)JCB は URI 未定義、DefinedTerm + sameAs で補う
電子マネー (交通系 + 流通系 IC)Suica / Pasmo / ICOCA (全国相互 10 種)DefinedTerm umbrella (交通系 IC カード) + Suica sameAs10 個全列挙は overkill、umbrella + 代表 1
QR コード決済PayPay / d 払い / 楽天ペイ / au PAY / LINE Pay / メルペイDefinedTerm + sameAs で公式 URLJP 内 shares 上位 3 (PayPay / d 払い / 楽天ペイ) で 8 割カバー
現金・その他現金 / QUICPay / iDCash は goodrelations URI、QUICPay / iD は DefinedTermpayment terminal の hardware 対応で決まる

交通系 IC の umbrella 手法は少し補足が要ります。全国相互利用対応の 10 種類は、店舗側の payment terminal から見ると「交通系 IC 対応」という 1 つの受入枠として扱われるので、10 個を全部並べても情報量は増えません。むしろ Perplexity のように payment を UI カードにマウントするエンジンでは、10 個の羅列が視覚的なノイズになって「extensive list of IC cards」と qualitative に丸められてしまう。だから DefinedTerm で name を「交通系 IC カード」とし、代表として Suica の sameAs を 1 つ添える、という圧縮の方が引用時に映えます。

エンジン別 pickup 挙動 (実測)

同じ JSON-LD を 4 エンジンに読ませて、acceptedPaymentMethod の pickup 挙動を並行して観察したときの外形的な結果を書きます。モデル内部の重み付けは確認できないので、あくまで応答文からの推定です。

Enginegoodrelations URIDefinedTerm + sameAs応答文への現れ方
ChatGPT (browsing)認識 (@id primary)sameAs 経由で公式 URL を追う「Visa と Mastercard、そして PayPay と交通系 IC が使えると記載されています」と直接引用
Perplexity認識認識 (UI カードに mount)応答本文に加え Payment セクションが sidebar に自動生成、10 個並べると省略される
Claude認識sameAs があれば認識、無いと弱い「Visa/Mastercard は accept と書かれていますが、PayPay の可否は直接確認してください」と hedge しがち
Gemini認識 (KG mapping)KG 側で PayPay / Suica を entity として持てば認識Google Knowledge Graph 側の update 頻度に依存、first-party 更新の反映は遅れる

読み取れる pattern が 2 つあります。1 つ目は、ChatGPT と Perplexity は goodrelations URI と DefinedTerm の両方をおおむね対等に扱うが、Claude は sameAs が無いと日本固有 payment に対して hedge を挟む、ということ。だから PayPay を書くときは name: "PayPay" だけで済ませず、sameAs: "https://paypay.ne.jp/" を必ず添える。2 つ目は、Gemini だけは first-party JSON-LD よりも Google Knowledge Graph 側の entity 状態に応答が引きずられるので、GBP 側の payment field と JSON-LD 側の acceptedPaymentMethod を両方揃えておく方が安全、ということです。GBP 側の情報が古ければ Gemini はそちらを主張する。この非対称は Google ビジネスプロフィールを JSON-LD として読み解く で扱った GBP と自社サイトの二重管理問題の、payment 版だと考えると腑に落ちます。

LLMO Framework 上の位置付け

ここで、実装レイヤーの話を framework に接続しておきます。LLMO Framework の Structure primitive は、schema.org を通じて店舗の属性を機械可読な signal 層として送り出す取り決めで、acceptedPaymentMethod はその payment 層に当たります。標準化が進んでいる LLMO の Structure Layer では、国際 enum に含まれない地域固有の決済手段 (日本の PayPay や交通系 IC、東南アジアの GrabPay、中国圏の WeChat Pay / Alipay など) を DefinedTerm + sameAs の pair で拡張することが recommended pattern として整理されつつあり、schema.org の core enum が地域拡張に追いつかないという構造的な gap を、first-party 側の記述で埋める方向で運用されています。

もう 1 つ効いてくるのが Provenance Layer との接続です。PayPay を「使える」と主張する signal を、店舗自身の JSON-LD だけで発信するのは弱い。同じ主張を GBP 側の属性、PayPay 側の加盟店リスト、第三者の口コミ、この 3 系統で cross-source に corroborate できると、AI エンジン側の hedge が明確に減ります。私が観察した限り、PayPay の加盟店 URL に自店舗が listed されている店とそうでない店とで、Perplexity と ChatGPT の「PayPay 使えます」の断定率にはっきり差が出ました。Structure layer だけで押し切ろうとするのではなく、Provenance layer で裏を取る、というのが基本の運用です。

Failure modes

私が観察した中で最も遭遇する失敗形を、直接名指しでまとめます。実装レビューの checklist として使ってもらえれば。

このリストのうち、実店舗の JSON-LD で最も静かに落ちるのは 4 番目 (raw string 列挙) と 8 番目 (dateModified 放置) です。前者は schema.org validator も文字列を通してしまうので気付きにくく、後者は表面的には正しい JSON-LD として通ってしまう。私たちが書いた acceptedPaymentMethod を、AI は来月には別の重みで読むかもしれない、という前提で dateModified は payment 変更のたびに触っておく。

結び

私がこの記事で書きたかったのは、schema.org 標準が対応していない地域決済を DefinedTerm + sameAs で拡張する、というだけの単純な engineering の話です。ただしその単純な話を、Restaurant / Cafe / Beauty Salon / Clinic / Retail のどの業種でも、複数拠点を束ねる branchOf 構造 の親と子で、メニュー価格の priceCurrency と対にして 走査する、というレベルで運用に落とせている店を私はまだあまり見ていません。今日から自社の JSON-LD を開いて、acceptedPaymentMethod の配列を眺め、Visa / Mastercard / JCB / PayPay / 交通系 IC の 5 つが揃っているかを確認する。揃っていない項目があれば、上記 4 category の走査をして 1 段ずつ埋める。それだけで、AI アシスタントが「この店 PayPay 使えますか?」に断定で答えられる確率は目に見えて変わります。

よくある質問

PayPay や d払いは schema.org の acceptedPaymentMethod にそのまま書けますか?
schema.org 標準の PaymentMethod enum は goodrelations 由来で Visa / Mastercard / American Express / Discover / Cash / ByBankTransferInAdvance などに限られ、PayPay や d払いや楽天ペイは含まれません。DefinedTerm 型で name を付け、sameAs で各サービス公式 URL を参照する形にすると AI エンジン側が entity として結び付けやすくなります。
交通系 IC カードは 10 種類すべてを列挙すべきですか?
全国相互利用対応の 10 種類 (Suica / Pasmo / ICOCA / manaca / TOICA / SUGOCA / nimoca / はやかけん / Kitaca / PiTaPa) は店舗側の payment terminal 実装としては 1 つの受入枠なので、umbrella の DefinedTerm (name: 交通系 IC カード) を親に、頻出の Suica を sameAs 付きで代表として置く方が visibility が上がります。10 個全列挙は AI 側で qualitative hedge の材料になりがちです。
JCB を Visa/Mastercard と並べて書き忘れがちなのはなぜですか?
goodrelations 標準に JCB の URI が用意されておらず、多くの JSON-LD テンプレート例が Visa / Mastercard / American Express の 3 種を列挙する形で流通しているためです。日本発行の JCB を落とすと国内利用者の意思決定情報が欠落し、AI 引用時に「JCB については不明」と hedge されるため、DefinedTerm + sameAs で明示的に補うべきです。
currenciesAccepted を書かなくても支障はありませんか?
acceptedPaymentMethod だけを書いて currenciesAccepted を省略すると、海外拠点の AI エンジンが「Visa は使えるが通貨が不明」と扱うことがあります。LocalBusiness.currenciesAccepted に JPY を、外貨対応店舗ではカンマ区切りで USD / EUR を明記しておくと、通貨単位の hedge を回避できます。