Vポイントマーケティング|TECH LABの Tech Blog

TECH LABのエンジニアが技術情報を発信しています

ブログタイトル

UCPのサンプル実装を見て実装ポイントをまとめてみる(Create Checkout編)

UCPのサンプル実装を見て実装ポイントをまとめてみる(Create Checkout編)

こんにちは、VポイントマーケティングAIエンジニアの三浦です。

最近Googleのエージェンティックコマースの共通プロトコルUniversal Commerce Protocol(UCP)のサンプルプログラムを読んでいます。「これ、どういう意味なんだろう??」と色々立ち止まりながら読み進めているのですが、自分の理解を深めるためにも実装ポイントを少しずつまとめていきたいな、と思っています。

実装サンプルは"Flower Shop"という架空の花屋ECのUCP Merchant Serverとしての実装例を表しています。以下のレポジトリで公開されています。

github.com

この中で読んでいるのはPythonのRESTの実装です。ディレクトリはsamples/rest/pythonが対象になります。まずはこのUCP MerchantのUCPプロファイルを把握し、ビジネスロジックのうち、Checkoutセッションを開始するcreate_checkoutについて見ていきます。

/.well-known/ucpで公開されるUCPプロファイルを見てみよう

UCP Merchantは/.well-known/ucpで自分のUCPにおける情報(profile)を公開します。このprofileには、利用できるtransport、対応できるcapabilityやpayment handlerなどが含まれます。エージェントやクライアントはここを見て、このUCP Merchantとのやり取りの仕方を知ることができます。

このエンドポイントの実装はserver/routes/discovery.pyget_merchant_profileが対応していて、その中身を見ると同一フォルダ内にあるdiscovery_profile.jsonの中身を読んでレスポンスで返していることが分かります。

このJSONファイルを見てみると、servicescapabilitiespayment_handlersで公開されるprofileの情報が定義されています。

まずservicesですが、以下のようになっています。

...
    "services": {
      "dev.ucp.shopping": [
        {
          "version": "2026-01-23",
          "spec": "https://ucp.dev/2026-01-23/specification/overview",
          "transport": "rest",
          "endpoint": "{{ENDPOINT}}",
          "schema": "https://ucp.dev/2026-01-23/services/shopping/openapi.json"
        },
        {
          "version": "2026-01-23",
          "spec": "https://ucp.dev/2026-01-23/specification/overview",
          "transport": "mcp",
          "endpoint": "{{ENDPOINT}}/mcp",
          "schema": "https://ucp.dev/2026-01-23/services/shopping/openrpc.json"
        },
    ...
      ]
    }

エージェントやクライアントはここを見ることでこのUCP Merchantがどのtransportに対応していて、エンドポイントのURLを知ることができます。さらにその下にどんなpathがあるのかをschemaのURLの先で知ることができ、全体の仕様はspecのURLの先で知ることができます。

次にcapabilitiesです。ここにはこのUCP Merchantが対応可能なUCP Capabilityが記載されています。このサンプルではcheckout, order, discount, fulfillment, buyer_consentというCapabilityを持っています。

"capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-01-23",
          "spec": "https://ucp.dev/2026-01-23/specification/checkout",
          "schema": "https://ucp.dev/2026-01-23/schemas/shopping/checkout.json"
        }
      ],
      "dev.ucp.shopping.order": [
        {
          "version": "2026-01-23",
          "spec": "https://ucp.dev/2026-01-23/specification/order",
          "schema": "https://ucp.dev/2026-01-23/schemas/shopping/order.json"
        }
      ],
...
}

今回のサンプルには実装されていませんが、UCPではクライアントとサーバーそれぞれが対応しているCapabilityを見て、共通したものだけを選択するネゴシエーションが行われます。

最初私は勘違いしてたのですが、CapabilityとRESTのエンドポイントが対応しているわけではありません。例えばdiscountのCapabilityを利用するには/discountというpathが用意されているわけではありません。/checkout-sessions/{session_id}にPUTでリクエストを送ると注文内容を更新できますが、その時のリクエストにdiscountsフィールドを含めることでdiscount(割引の適用)を利用できる仕組みです。イメージとしてはCapabilityができることでそれを実現するためにRESTのエンドポイントが用意されている、という感じです。

payment_handlersにはこのUCP Merchantがどの支払い処理方式・支払いプロバイダに対応しているかを公開します。今回のサンプルでは実際の処理内容はモック実装の状態のため、ここでは詳細は割愛します。

ビジネスロジックを把握する(create_checkout)

このUCP Merchantのビジネスロジックが実装されているのはserver/services/checkout_service.pyです。具体的には「価格計算・在庫・配送・割引・支払い・注文化」に係る処理が実装されています。今回はこの中でcreate_checkoutという関数を見ていきます。

create_checkoutはcheckout sessionを開始する関数です。RESTでは/checkout-sessionsにPOSTでリクエストを送ると実行されます。ちなみにここでのcheckoutは商品の追加から注文確定までの一連の手続きを指しています。 create_checkoutではまず最初に冪等性のチェック(Idempotency Check)が実行されます。すでに同じリクエストが送信済みかどうかをチェックする処理で、クライアントから送信されるidempotency_keyに基づいてサーバー側で保管されている実行済みのリクエストボディと今回送られてきたリクエストボディハッシュ値の比較を行い、一致した場合は格納済みの値をそのまま返します。一方ハッシュ値が異なる場合はIdempotencyConflictErrorになります。これはクライアントが誤って複数回同じリクエストを実行しても処理自体は1回のみ実行される冪等性が保証されます。

具体的には以下の処理です。

    # Idempotency Check
    request_hash = self._compute_hash(checkout_req)
    existing_record = await db.get_idempotency_record(
      self.transactions_session, idempotency_key
    );

    if existing_record:
      if existing_record.request_hash != request_hash:
        raise IdempotencyConflictError(
          "Idempotency key reused with different parameters"
        )
      # Return cached response
      return Checkout(**existing_record.response_body)
...

リクエストには商品line_itemsや金額合計totalが格納される場合がありますが、クライアントから送られてきた値をそのまま使用するのではなく、サーバー側が持っている情報に上書きします。これによってクライアントからの不正なリクエストを防ぐことができ、信頼できる情報はサーバーのみである"the server is the source of truth."を実現します。金額の再計算は_recalculate_totalsという関数で実装されていますが、この中で商品DBに格納された商品の正確な価格を参照し、配送オプション、discountがリクエストに含まれていれば割引処理を実行し、正確な金額計算を常にサーバーが担うようになっています。

まとめ

今回はUCPの実装サンプルの中でUCPプロファイルを公開する部分とビジネスロジックのうちCheckoutセッションを開始する処理について流れをまとめて見ました。具体的なコードを読んでみると、"the server is the source of truth."のようなクライアントからのセッションの改ざんなどを防ぐ工夫があることがわかって面白かったです。引き続きCheckoutの更新や完了処理についても調べてまとめいきたいと思います。