<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: tubone24</title>
    <description>The latest articles on Forem by tubone24 (@tubone24).</description>
    <link>https://forem.com/tubone24</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F321733%2F3166d2b4-1381-471a-934a-3a65fabbf5d3.jpeg</url>
      <title>Forem: tubone24</title>
      <link>https://forem.com/tubone24</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tubone24"/>
    <language>en</language>
    <item>
      <title>Getting Started with AP2(Agent Payments Protocol)</title>
      <dc:creator>tubone24</dc:creator>
      <pubDate>Mon, 12 Jan 2026 00:29:53 +0000</pubDate>
      <link>https://forem.com/tubone24/getting-started-with-ap2-2e39</link>
      <guid>https://forem.com/tubone24/getting-started-with-ap2-2e39</guid>
      <description>&lt;h2&gt;
  
  
  Note
&lt;/h2&gt;

&lt;p&gt;The purpose of this article and demo app is to provide an &lt;strong&gt;introduction&lt;/strong&gt; to AP2, so some explanations may not be technically precise (prioritizing clarity over accuracy). Additionally, since there aren't many implementation references for AP2, much of this contains my own interpretations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm writing this with the hope that you'll get a general feel for how it works&lt;/strong&gt;. My apologies in advance!&lt;/p&gt;

&lt;h2&gt;
  
  
  For Those in a Hurry
&lt;/h2&gt;

&lt;p&gt;I've created a demo app that conforms to AP2 as much as possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprvh0s1fzrzldpqberzl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprvh0s1fzrzldpqberzl.gif" alt="demo" width="600" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker Compose launches all the services needed to discuss AP2, allowing you to experience AP2 shopping in a simulated environment. (Of course, you can't actually make real purchases.)&lt;/p&gt;

&lt;p&gt;Clone the demo app from here: &lt;a href="https://github.com/tubone24/AP2_demo_app/tree/main" rel="noopener noreferrer"&gt;https://github.com/tubone24/AP2_demo_app/tree/main&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose build
docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should start the app at &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;. However, the local LLM used internally runs on &lt;a href="https://www.docker.com/ja-jp/products/model-runner/" rel="noopener noreferrer"&gt;Docker Model Runner&lt;/a&gt;, so you'll need to set that up first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Wanted to Learn AP2
&lt;/h2&gt;

&lt;p&gt;I'm currently on &lt;strong&gt;parental leave&lt;/strong&gt;. While I'm grateful to watch my child grow every day, I'll be honest—it's tough seeing my talented colleagues challenge themselves with new things on X (Twitter).&lt;/p&gt;

&lt;p&gt;So, I decided to get off my butt and learn &lt;strong&gt;AP2&lt;/strong&gt; (&lt;a href="https://ap2-protocol.org/" rel="noopener noreferrer"&gt;Agent Payments Protocol&lt;/a&gt;) because I'd be crushed with anxiety when I return to work if I don't learn something new.&lt;/p&gt;

&lt;p&gt;The generative AI world keeps churning out mysterious protocols one after another... I just finished &lt;a href="https://amzn.asia/d/2B2zPtM" rel="noopener noreferrer"&gt;learning about MCP&lt;/a&gt;, and now there's &lt;a href="https://cloud.google.com/blog/ja/products/ai-machine-learning/a2a-a-new-era-of-agent-interoperability" rel="noopener noreferrer"&gt;A2A&lt;/a&gt;, and &lt;strong&gt;AP2&lt;/strong&gt;... What a predicament.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://cloud.google.com/blog/ja/products/ai-machine-learning/announcing-agents-to-payments-ap2-protocol" rel="noopener noreferrer"&gt;Google&lt;/a&gt;, &lt;strong&gt;AP2&lt;/strong&gt; is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An open protocol developed jointly with major payment and technology companies to securely initiate and execute agent-driven payments across platforms&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In essence, it's a system that elegantly solves various &lt;strong&gt;inconveniences&lt;/strong&gt;, &lt;strong&gt;problems&lt;/strong&gt;, and &lt;strong&gt;disputes&lt;/strong&gt; that can arise when considering shopping and payments involving &lt;strong&gt;AI agents&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So what problems can actually occur when shopping with AI agents?&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's First Imagine Human Shopping
&lt;/h2&gt;

&lt;p&gt;Before thinking about AP2, let's imagine how humans shop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4185uksszrmhj8l5k2hh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4185uksszrmhj8l5k2hh.png" alt="a" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's say Person A is thinking, "I want some &lt;strong&gt;cute dog goods&lt;/strong&gt;." By the way, in AP2, this feeling ("I want cute dog goods") or &lt;strong&gt;intention&lt;/strong&gt; is called an &lt;strong&gt;Intent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;While walking around, they discover a shop called &lt;strong&gt;Mugibo Shop&lt;/strong&gt; featuring a cute Shiba Inu motif. Let's go inside.&lt;/p&gt;

&lt;p&gt;Mugibo Shop has excellent staff who &lt;strong&gt;present optimal products tailored to customer preferences&lt;/strong&gt;. After hearing specific requests and checking inventory, they pick out several products within budget and create a &lt;strong&gt;recommended set&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4e8rvehn56l5u4xv9i4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4e8rvehn56l5u4xv9i4t.png" alt="a" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Person A is satisfied with the recommended set and proceeds to &lt;strong&gt;payment&lt;/strong&gt;. They complete the transaction using an xxPay terminal installed in the shop. They receive their products and receipt, completing the shopping experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes241xxr9iiphjiu7nmv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fes241xxr9iiphjiu7nmv.png" alt="a" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's human shopping.&lt;/p&gt;

&lt;p&gt;An important point is that the shopping scenario AP2 envisions is not a &lt;a href="https://md-next.jp/yougo/%e3%82%bb%e3%83%ab%e3%83%95%e3%82%b5%e3%83%bc%e3%83%93%e3%82%b9" rel="noopener noreferrer"&gt;self-service style&lt;/a&gt; like supermarkets, but rather &lt;a href="https://md-next.jp/yougo/%E5%AF%BE%E9%9D%A2%E8%B2%A9%E5%A3%B2" rel="noopener noreferrer"&gt;over-the-counter sales&lt;/a&gt; like pharmacies.&lt;/p&gt;

&lt;p&gt;So the staff puts items in the shopping cart, the user confirms the cart and gives approval to proceed with the purchase.&lt;/p&gt;

&lt;p&gt;In other words, it's like the AI-generated image below, where the staff &lt;strong&gt;picks items from the shelves&lt;/strong&gt; saying, "For these symptoms, this combination of medicines would be best!" (Understanding this will significantly change your comprehension.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8l0krgn5428q2qvivuo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8l0krgn5428q2qvivuo.png" alt="Pharmacy" width="653" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when we introduce AI agents into this shopping process?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Made the Mistake in This Purchase...?
&lt;/h2&gt;

&lt;p&gt;Now, let's imagine &lt;strong&gt;delegating part of this shopping process to AI agents&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, let's consider a scenario with a &lt;strong&gt;Shopping Agent&lt;/strong&gt; (SA) that shops on behalf of the user, and a &lt;strong&gt;Merchant Agent&lt;/strong&gt; (MA) that handles the shop staff's duties.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hallucination
&lt;/h3&gt;

&lt;p&gt;The first thing that comes to mind is AI agent &lt;strong&gt;hallucination&lt;/strong&gt;. This is the phenomenon where LLMs confidently spout incorrect information as if they know it.&lt;/p&gt;

&lt;p&gt;When this happens in a shopping situation, it's a big problem. Moreover, there are many patterns of potential mistakes.&lt;/p&gt;

&lt;p&gt;The easiest to imagine is the pattern where &lt;strong&gt;the SA misunderstands the user's intent and conveys it incorrectly to the MA&lt;/strong&gt;. Like a game of telephone, the purchase proceeds based on the wrong intent. It might be acceptable for small purchases, but having millions of yen in transactions processed without permission would be a problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbjpepyvwfkuapiqxvd2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbjpepyvwfkuapiqxvd2.png" alt="ia" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There could also be cases where the MA misinterprets the intent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu14fgu5ad1buni2rv5oo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu14fgu5ad1buni2rv5oo.png" alt="aa" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Considering more complex cases, even if the user's intent is correctly conveyed, there could be mistakes due to &lt;strong&gt;insufficient context&lt;/strong&gt; on the MA's side—for example, selling products that should only be sold to regular customers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr4skjstj2dvdsvr95v3e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr4skjstj2dvdsvr95v3e.png" alt="aaa" width="800" height="274"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Even More Troublesome Patterns
&lt;/h3&gt;

&lt;p&gt;Even more troublesome patterns are conceivable. For example, Person A tells the AI agent "I want cute dog goods" but then &lt;strong&gt;changes their mind midway&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Whether it's a change of heart, mischief, or misunderstanding, there could be various reasons, but if the user says after the transaction is complete, "Actually, I wanted cute cat goods," it becomes &lt;strong&gt;unclear who should take responsibility&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjox8s37ccrg474dtufv9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjox8s37ccrg474dtufv9.png" alt="aaa" width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, similar problems could arise if someone &lt;strong&gt;impersonates the user&lt;/strong&gt; and instructs the AI agent to make purchases. (Impersonation problem)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F877spry18orat8id01ah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F877spry18orat8id01ah.png" alt="aaa" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem of AI Agents Knowing Too Much Information
&lt;/h2&gt;

&lt;p&gt;Also, on a slightly different note, when an AI agent (SA) shops on behalf of the user, it becomes problematic if it can &lt;strong&gt;freely access the user's wallet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When assuming credit card payments, the agent would know the card number, expiration date, and CVC (Card Validation Code, the 3-digit code on the back of the card). This creates risks of &lt;strong&gt;card information leakage&lt;/strong&gt; in case of incidents, or the AI agent going rogue and buying luxury items indiscriminately.&lt;/p&gt;

&lt;p&gt;Additionally, there's a system maintenance concern about the AI agent (MA) becoming &lt;strong&gt;tightly coupled&lt;/strong&gt; with the shop's payment system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fquey891n7kx87iml61dh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fquey891n7kx87iml61dh.png" alt="aa" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does AP2 Solve These Problems?
&lt;/h2&gt;

&lt;p&gt;AP2 solves these issues by &lt;strong&gt;cleverly extending digital signatures combining advanced cryptographic techniques to A2A (Agent2Agent) communication&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sequence
&lt;/h3&gt;

&lt;p&gt;First, let me show you an easy-to-understand (?) picture story without difficult terminology, and then cover the specific concepts and terms that appear in it—I'm confident this approach will be easier to understand. Please bear with me through this picture story.&lt;/p&gt;

&lt;p&gt;(For explanation purposes, I've rearranged some steps. Also, while I've referenced the currently published &lt;a href="https://ap2-protocol.org/specification/#71-illustrative-transaction-flow" rel="noopener noreferrer"&gt;Illustrative Transaction Flow&lt;/a&gt;, some specifications are unclear and contain considerable speculation. Please understand this.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FlKKSQnI.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FlKKSQnI.png" alt="a" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, Person A instructs the Shopping Agent (SA) with specific &lt;strong&gt;purchase intentions&lt;/strong&gt; like &lt;strong&gt;"I want cute dog goods" and "within 5,000 yen"&lt;/strong&gt;. The SA then grasps the purchase intention and begins shopping.&lt;/p&gt;

&lt;p&gt;Before actually shopping, the user and SA exchange an &lt;strong&gt;Intent Mandate&lt;/strong&gt;—a &lt;strong&gt;delegation document&lt;/strong&gt; of the purchase intention—essentially saying, "I, Person A, hereby delegate to SA the purchase of 'cute dog goods within 5,000 yen.'" I'll explain &lt;strong&gt;Intent Mandate&lt;/strong&gt; in more detail later.&lt;/p&gt;

&lt;p&gt;At the same time, the &lt;strong&gt;Merchant Agent&lt;/strong&gt; (MA) of "Mugibo Shop" &lt;strong&gt;exchanges business cards&lt;/strong&gt; with the SA, introducing their shop.&lt;/p&gt;

&lt;p&gt;Those familiar with A2A will understand this—this business card exchange is the A2A &lt;strong&gt;Agent Card&lt;/strong&gt;. It conveys their inventory status and that they can provide an AP2-compatible shopping experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F0KxrKpp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F0KxrKpp.png" alt="c" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, based on the business card exchange results with the MA, the SA decides to start a transaction with Mugibo Shop. (In reality, not just the Agent Card itself, but trust domains and certificate chains would also be checked to verify transaction eligibility, but I'll skip that here.)&lt;/p&gt;

&lt;p&gt;Agent-to-agent communication between SA-MA is conducted using &lt;strong&gt;A2A Messages&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By sending the &lt;strong&gt;Intent Mandate&lt;/strong&gt; exchanged with the user, the SA demonstrates to the MA that it's making a purchase that truly captures the user's intent, while asking them to create the target shopping cart.&lt;/p&gt;

&lt;p&gt;The MA receives the user's Intent and begins considering &lt;strong&gt;what product set would be best&lt;/strong&gt;. It examines the shop's (Merchant's) inventory status, prices, and descriptions of each product to create a shopping cart with products perfect for the user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FQVFkhfa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FQVFkhfa.png" alt="d" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, the MA has finally created a shopping cart with products perfect for the user. This shopping cart creation should originally be done by the shop side (Merchant), but the MA is creating it on behalf of the shop.&lt;/p&gt;

&lt;p&gt;Therefore, it's necessary to show that &lt;strong&gt;the shop's sales activities are being delegated in the form of a delegation document&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, the MA creates a shopping cart delegation document (&lt;strong&gt;Cart Mandate&lt;/strong&gt;), and the shop confirms it and adds their &lt;strong&gt;digital signature&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FwPZgNbR.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FwPZgNbR.png" alt="b" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the delegation document exchange between the shop and MA is complete, that delegation document (&lt;strong&gt;Cart Mandate&lt;/strong&gt;) is &lt;strong&gt;sent to the SA&lt;/strong&gt;. The SA will present this shopping cart to the user while proceeding with the purchase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FqNfLrKJ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FqNfLrKJ.png" alt="h" width="800" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Person A &lt;strong&gt;finalizes the shopping cart they're purchasing&lt;/strong&gt; based on the Cart Mandate presented by the SA. (Cart Mandates can be presented as multiple A2A Artifacts, so a UI for selecting from multiple candidates is assumed. This demo app presents multiple carts.)&lt;/p&gt;

&lt;p&gt;For the finalized shopping cart delegation document, ~the user adds their signature, meaning "Person A has definitely confirmed this."~ the user is asked to &lt;strong&gt;confirm&lt;/strong&gt; it. (Actually, Device Attestation operations occur, but I'll explain this in the Payment Mandate section.)&lt;/p&gt;

&lt;p&gt;(As mentioned earlier, &lt;strong&gt;it was structurally difficult to add a user signature to Cart Mandate&lt;/strong&gt;. This is because adding a user signature would break the JSON structure and invalidate the Merchant's signature before it. While it's defined in the specification, the implementation handling requires some ingenuity.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FrXcmmUi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FrXcmmUi.png" alt="i" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the same time, the SA needs to confirm the payment method for the user's product purchase. In AP2, a mysterious concept called &lt;strong&gt;Credential Provider&lt;/strong&gt; (&lt;strong&gt;CP&lt;/strong&gt;) appears here.&lt;/p&gt;

&lt;p&gt;According to the official documentation, CP is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The User's Credentials Provider (CP): A specialized entity responsible for the secure management and execution of payments credentials (e.g. a digital Wallet). It holds knowledge of the User's available payment methods, gets user consent (if deemed necessary) to share credentials with the SA, selects the optimal payment method based on user preferences and transaction context, and handles payment scenarios like errors, declines and transaction challenges gracefully.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, CP means a &lt;strong&gt;specialized entity that securely manages and executes user payment and ID credentials&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can think of it as a &lt;strong&gt;digital wallet&lt;/strong&gt; combining an &lt;strong&gt;ID wallet&lt;/strong&gt; and &lt;strong&gt;payment wallet&lt;/strong&gt;. (To be precise, since the main purpose is for CP to handle &lt;strong&gt;secure management of payment credentials and user consent flows&lt;/strong&gt;, ID wallets could be independent from CP.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.corbado.com/blog/digital-credentials-payments#2-understanding-the-current-landscape-identity-vs-payment-stacks" rel="noopener noreferrer"&gt;Google and Apple&lt;/a&gt; seem to be working toward supporting this. The point is that it handles not only payments but also proving the user's identity.&lt;/p&gt;

&lt;p&gt;To later ask the user about their payment method, we &lt;strong&gt;check the payment methods associated with CP&lt;/strong&gt;. According to the current AP2 specification (v0.1), &lt;strong&gt;credit cards and debit cards&lt;/strong&gt; are selectable, and &lt;a href="https://ap2-protocol.org/roadmap/" rel="noopener noreferrer"&gt;various payment services will be supported in the future&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The key point here is that the SA receives only the minimum information needed to have the user select a payment method (such as the last 4 digits of the credit card), and receives only information that &lt;strong&gt;does not constitute PCI data&lt;/strong&gt;, excluding card numbers and CVC.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FGyc2QU9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FGyc2QU9.png" alt="f" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's proceed to payment. The SA has the user &lt;strong&gt;finalize the payment method&lt;/strong&gt; to use from the payment information received from CP. Once the user finalizes which card to use for payment, they request CP to issue a &lt;strong&gt;token&lt;/strong&gt; (&lt;strong&gt;payment method token&lt;/strong&gt;) for that card.&lt;/p&gt;

&lt;p&gt;This token becomes &lt;strong&gt;temporary card information usable for payment&lt;/strong&gt;. However, the token contains no payment-required information (PCI data, card numbers, etc.). Since CP has a mapping between tokens and actual card information, &lt;strong&gt;payments can be made through CP&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once the payment method token is obtained, the SA creates a Payment Mandate (&lt;strong&gt;Payment Mandate&lt;/strong&gt;). This is a delegation document for payment that specifies the &lt;strong&gt;total amount&lt;/strong&gt; and &lt;strong&gt;payment method&lt;/strong&gt; without detailed item specifics. First, the SA presents this delegation document to the user saying, "I, SA, will make payment as per this delegation document on behalf of Person A," and &lt;strong&gt;obtains the user's signature&lt;/strong&gt; to confirm user intent.&lt;/p&gt;

&lt;p&gt;At this point, &lt;a href="https://fidoalliance.org/fido-technotes-the-truth-about-attestation/" rel="noopener noreferrer"&gt;Attestation&lt;/a&gt; operations occur in the app. While this deviates from official documentation, according to a CSA blog article, &lt;a href="https://cloudsecurityalliance.org/blog/2025/10/06/secure-use-of-the-agent-payments-protocol-ap2-a-framework-for-trustworthy-ai-driven-transactions" rel="noopener noreferrer"&gt;password authentication is not recommended and stronger authentication is required&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Guidelines for secure implementation include integration with Strong Customer Authentication (SCA), configuration of Intent Mandate TTLs, and dispute resolution protocols.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By executing through hardware-backed keys and in-session authentication (such as biometrics) using &lt;a href="https://en.wikipedia.org/wiki/Trusted_Platform_Module" rel="noopener noreferrer"&gt;TPM&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Secure_Enclave" rel="noopener noreferrer"&gt;Secure Enclave&lt;/a&gt;, it becomes a &lt;strong&gt;blood oath&lt;/strong&gt; that Person A has definitely approved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FATRxVXv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FATRxVXv.png" alt="e" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the &lt;strong&gt;user-signed Payment Mandate&lt;/strong&gt; is complete, it's sent to the MA, and finally, the payment process begins.&lt;/p&gt;

&lt;p&gt;What's important here is that the MA doesn't perform the payment processing itself—the &lt;strong&gt;Merchant Payment Processor&lt;/strong&gt; (MPP), a &lt;strong&gt;payment entity associated with the shop&lt;/strong&gt;, handles it. This is an implementation following AP2's basic concept of &lt;strong&gt;clear separation of duties&lt;/strong&gt; that separates payment-related processing from the MA.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F2sJ5eqV.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2F2sJ5eqV.png" alt="g" width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once payment is complete, &lt;strong&gt;MPP issues a receipt and sends it to CP and SA&lt;/strong&gt;. The SA receives the receipt and notifies the user of the transaction completion. This is what AP2 accomplishes.&lt;/p&gt;

&lt;p&gt;Great work!&lt;/p&gt;

&lt;h3&gt;
  
  
  Mandates
&lt;/h3&gt;

&lt;p&gt;Let's revisit the &lt;strong&gt;Mandate&lt;/strong&gt;—the &lt;strong&gt;delegation document with digital signature&lt;/strong&gt; mechanism that's essential to discussing AP2.&lt;/p&gt;

&lt;p&gt;Users and shops &lt;strong&gt;digitally sign&lt;/strong&gt; delegation documents using their respective &lt;strong&gt;private keys&lt;/strong&gt;, and by verifying these at each step, we &lt;strong&gt;guarantee that the delegation was definitely performed by each entity&lt;/strong&gt;. The mechanism where each entity approves, signs, and verifies delegation documents using digital signatures is arguably the most interesting part of AP2.&lt;/p&gt;

&lt;p&gt;There are 3 types of Mandates, and necessary information is sent and received at each sequence. Additionally, Mandates have &lt;strong&gt;chains&lt;/strong&gt;, with Payment Mandate referencing Cart Mandate, and Cart Mandate referencing Intent Mandate. This creates a relationship where each delegation document is created based on the previous one.&lt;/p&gt;

&lt;h4&gt;
  
  
  Intent Mandate
&lt;/h4&gt;

&lt;p&gt;First is the &lt;strong&gt;Intent Mandate&lt;/strong&gt;. This is where the SA represents the user's purchase intention (&lt;strong&gt;Intent&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;The SA creates it and confirms with the user: "&lt;strong&gt;Your purchase intention is this, right? Is it okay if I send this to the MA?&lt;/strong&gt;" and then sends the confirmed delegation document to the MA.&lt;/p&gt;

&lt;p&gt;I mentioned taking "&lt;strong&gt;user confirmation&lt;/strong&gt;," but depending on how the user is involved (the &lt;strong&gt;transaction modality&lt;/strong&gt;), whether a user signature is required or just confirmation varies. I'll explain transaction modalities later.&lt;/p&gt;

&lt;p&gt;This demo app is created using the &lt;strong&gt;Human Present&lt;/strong&gt; transaction modality, which has detailed sequence breakdowns, so it's not using the more innovative &lt;strong&gt;Human Not Present&lt;/strong&gt; implementation. My apologies. Once I understand the specifications better, I'd like to try &lt;strong&gt;Human Not Present&lt;/strong&gt; as well.&lt;/p&gt;

&lt;h4&gt;
  
  
  Cart Mandate
&lt;/h4&gt;

&lt;p&gt;Next is the &lt;strong&gt;Cart Mandate&lt;/strong&gt;. This refers to the cart containing products that the MA has created.&lt;/p&gt;

&lt;p&gt;To proceed with the purchase, the MA obtains approval from the shop side (Merchant) in the form of a signature, confirming "&lt;strong&gt;whether the shop can really sell these products, whether it's an appropriate shopping cart&lt;/strong&gt;."&lt;/p&gt;

&lt;p&gt;The Cart Mandate includes &lt;strong&gt;the shop's signature&lt;/strong&gt; as a &lt;strong&gt;Base64 string of the Cart Mandate JSON signed with JWT&lt;/strong&gt;. (Details will be explained later in the demo app explanation.)&lt;/p&gt;

&lt;p&gt;Also, for &lt;strong&gt;Cart Mandate&lt;/strong&gt; in the &lt;strong&gt;Human Present&lt;/strong&gt; transaction modality, there's &lt;a href="https://ap2-protocol.org/specification/#411-the-cart-mandate" rel="noopener noreferrer"&gt;language suggesting user signature is required&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is generated by the Merchant based on the user's request and is cryptographically signed by the user, typically using a hardware-backed key on their device with in-session authentication. This signature binds the user's identity and authorization to their intent. The Cart Mandate is a structured object containing critical parameters that define the scope of the transaction.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, when actually creating the demo app, it was &lt;strong&gt;difficult to add a signature to Cart Mandate itself&lt;/strong&gt; (it would invalidate the Merchant's signature), and reliable implementation is currently difficult with official documentation alone.&lt;/p&gt;

&lt;p&gt;By the way, in the &lt;a href="https://ap2-protocol.org/specification/#sample-cartmandate" rel="noopener noreferrer"&gt;official documentation Cart Mandate sample&lt;/a&gt;, there's a &lt;code&gt;user_signature_required&lt;/code&gt; field set to &lt;code&gt;false&lt;/code&gt;. This means the sample assumes no user signature on Cart Mandate, but this sample refers to Human Not Present Cart Mandate, so it's not useful for reference here.&lt;/p&gt;

&lt;p&gt;If you absolutely want to add a &lt;strong&gt;user signature&lt;/strong&gt; to &lt;strong&gt;Cart Mandate&lt;/strong&gt;, while not specified in the specification, there are two possible implementation methods:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep Cart Mandate unchanged (add &lt;code&gt;merchant_authorization&lt;/code&gt;), put the user signature in &lt;strong&gt;PaymentMandate&lt;/strong&gt;'s &lt;strong&gt;user_authorization&lt;/strong&gt;, and guarantee user consent for Cart Mandate through the Mandate chain. (&lt;a href="https://github.com/google-agentic-commerce/AP2/blob/main/src/ap2/types/mandate.py" rel="noopener noreferrer"&gt;The official GitHub&lt;/a&gt; seems to follow this specification.)&lt;/li&gt;
&lt;li&gt;Add a &lt;strong&gt;user_authorization part&lt;/strong&gt; as a &lt;strong&gt;detached signature&lt;/strong&gt; within Cart Mandate (completely my own interpretation. A way to add user signature without breaking Merchant's signature.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This area seems still ambiguous as a signature schema, so let's look forward to future developments. The demo app proceeds with implementation using option 1.&lt;/p&gt;

&lt;h4&gt;
  
  
  Payment Mandate
&lt;/h4&gt;

&lt;p&gt;Finally, the &lt;strong&gt;Payment Mandate&lt;/strong&gt;. This is a delegation document necessary for the SA to make payments on behalf of the user, characterized by including the total amount as well as &lt;strong&gt;user signature and payment-related information&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This Mandate allows us to show that &lt;strong&gt;the user has given final agreement&lt;/strong&gt; to all transactions. User signature is required for Payment Mandate.&lt;/p&gt;

&lt;p&gt;Also, as processing proceeds from Intent Mandate → Cart Mandate → Payment Mandate, &lt;strong&gt;the ID and hash of the previous Mandate are recorded&lt;/strong&gt;. (Creating a chain.) This allows the shop's payment processing system and payment network to correctly convey risks and see what process the transaction went through as an agent-based transaction by looking at the Mandate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transaction Modalities
&lt;/h3&gt;

&lt;p&gt;AP2 has mainly 2 transaction modalities: &lt;strong&gt;Human Present&lt;/strong&gt; (payment with human present) and &lt;strong&gt;Human Not Present&lt;/strong&gt; (payment without human present). The way users are involved differs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Human Present
&lt;/h4&gt;

&lt;p&gt;First, &lt;strong&gt;Human Present&lt;/strong&gt; applies to scenarios where the user delegates tasks to the AI agent but is &lt;strong&gt;present (available) to approve the final payment&lt;/strong&gt;. The image is like &lt;strong&gt;chatting with a chatbot to select products and completing the purchase in that flow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this case, &lt;strong&gt;Payment Mandate should prove user intent as non-repudiable&lt;/strong&gt;. Meanwhile, user signature on Intent Mandate can be omitted. (As far as I can tell from reading the documentation. You can sign it, but since signature requests involve passkey authentication with pop-ups appearing each time, avoiding it in this demo app as it would be quite redundant from a UI/UX perspective.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Human Not Present
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Human Not Present&lt;/strong&gt; applies to scenarios where the user delegates tasks to the agent and allows the agent to autonomously execute payments &lt;strong&gt;without the user present&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, telling the SA conditions in advance like "&lt;strong&gt;Buy these shoes if the price drops below $100&lt;/strong&gt;" and having the SA proceed with the purchase when those conditions are met while the user is absent.&lt;/p&gt;

&lt;p&gt;In this case, &lt;strong&gt;Intent Mandate should prove user intent as non-repudiable&lt;/strong&gt;. Therefore, user signature is &lt;strong&gt;required&lt;/strong&gt; on Intent Mandate for Human Not Present.&lt;/p&gt;

&lt;p&gt;That said, the Human Not Present specification isn't detailed in official documentation yet, so I don't know how Payment Mandate is handled afterward, or how dispute resolution works if inappropriate purchases proceed. My apologies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Look at What I Built (Exploring the Demo App)
&lt;/h2&gt;

&lt;p&gt;My knowledge of AP2 from documentation is &lt;strong&gt;honestly at this level&lt;/strong&gt;, so I'd like to dig deeper into the detailed behavior, signature mechanisms, content actually exchanged in A2A, UI/UX, etc., while actually building.&lt;/p&gt;

&lt;p&gt;Since there was no &lt;strong&gt;Human Not Present&lt;/strong&gt; sequence in official documentation, I haven't supported that.&lt;/p&gt;

&lt;p&gt;Also, as someone unfamiliar with this field, I haphazardly built many things, and it hasn't been reviewed by experts for production readiness. &lt;strong&gt;Please note that I take no responsibility for any services or products created using this app.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture Diagram
&lt;/h3&gt;

&lt;p&gt;First, look at this architecture diagram I had Claude draw.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FKbSA2Lp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FKbSA2Lp.png" alt="ig" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Very complex, right...&lt;/p&gt;

&lt;p&gt;It might actually be easier to understand by looking at the Docker Compose YAML file. Let's look at an excerpt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.8"&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Init Keys - Key pair initialization (runs once at startup)&lt;/span&gt;
  &lt;span class="na"&gt;init-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Init Seeds - Seed data injection (runs once at startup)&lt;/span&gt;
  &lt;span class="na"&gt;init-seeds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Shopping Agent - User-facing agent&lt;/span&gt;
  &lt;span class="na"&gt;shopping_agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Shopping Agent MCP - MCP tools (LangGraph node)&lt;/span&gt;
  &lt;span class="na"&gt;shopping_agent_mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Merchant Agent - Product search, CartMandate creation&lt;/span&gt;
  &lt;span class="na"&gt;merchant_agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Merchant Agent MCP - MCP tools (LangGraph node)&lt;/span&gt;
  &lt;span class="na"&gt;merchant_agent_mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Merchant - CartMandate signing&lt;/span&gt;
  &lt;span class="na"&gt;merchant&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Credential Provider 1 - WebAuthn verification, token issuance&lt;/span&gt;
  &lt;span class="na"&gt;credential_provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Credential Provider 2 - WebAuthn verification, token issuance (multiple CP support)&lt;/span&gt;
  &lt;span class="na"&gt;credential_provider_2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Payment Processor - Payment processing&lt;/span&gt;
  &lt;span class="na"&gt;payment_processor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Payment Network - Payment network (Agent Token issuance)&lt;/span&gt;
  &lt;span class="na"&gt;payment_network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Frontend - Next.js&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Meilisearch - Full-text search engine (for product search)&lt;/span&gt;
  &lt;span class="na"&gt;meilisearch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Jaeger - Distributed tracing backend (OpenTelemetry)&lt;/span&gt;
  &lt;span class="na"&gt;jaeger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Redis - KV store (temporary data, session management)&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's &lt;strong&gt;SA&lt;/strong&gt; and &lt;strong&gt;MA&lt;/strong&gt;, plus a &lt;strong&gt;frontend connecting SA and user&lt;/strong&gt;. In some documentation, there might be a UA (User Agent) between SA and frontend, but this demo app directly connects SA and frontend.&lt;/p&gt;

&lt;p&gt;Also, the &lt;strong&gt;tools&lt;/strong&gt; that SA and MA use as agents to create Mandates and perform product searches are implemented as independent &lt;strong&gt;MCP servers&lt;/strong&gt; as Docker Compose services, available via &lt;strong&gt;Streamable HTTP&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Additionally, &lt;strong&gt;CP&lt;/strong&gt;, &lt;strong&gt;MPP&lt;/strong&gt;, and stub services for the payment network are launched.&lt;/p&gt;

&lt;p&gt;Each entity has its own &lt;strong&gt;DB&lt;/strong&gt;, implemented with &lt;a href="https://sqlite.org/" rel="noopener noreferrer"&gt;SQLite&lt;/a&gt;. When &lt;strong&gt;temporary KV store&lt;/strong&gt; is needed, it accesses &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;.&lt;br&gt;
Also, since a full-text search engine is suitable for product searches, &lt;a href="https://www.meilisearch.com/" rel="noopener noreferrer"&gt;Meilisearch&lt;/a&gt; is used.&lt;/p&gt;

&lt;p&gt;Furthermore, &lt;a href="https://www.jaegertracing.io/" rel="noopener noreferrer"&gt;Jaeger&lt;/a&gt; is introduced as the &lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; backend.&lt;/p&gt;

&lt;p&gt;Public keys, private keys, and &lt;a href="https://www.w3.org/TR/did-1.1/" rel="noopener noreferrer"&gt;DIDs&lt;/a&gt; used for signing/verification by all services are created by initialization scripts. (I'll explain DID later.)&lt;/p&gt;

&lt;p&gt;Also, from an LLMOps perspective, &lt;a href="https://langfuse.com/" rel="noopener noreferrer"&gt;Langfuse&lt;/a&gt; has been introduced. This made building &lt;a href="https://www.langchain.com/langgraph" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt; graphs for SA and MA much easier. (Thanks, Langfuse!)&lt;/p&gt;
&lt;h3&gt;
  
  
  Preparation
&lt;/h3&gt;

&lt;p&gt;First, let's start by &lt;strong&gt;creating a user&lt;/strong&gt;. In this demo app, users are created with email and password. This becomes authentication information primarily used as &lt;strong&gt;HTTP Session for communication with SA&lt;/strong&gt;. To put it more clearly, it's authentication for the shopping chatbot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t690quf6jahu9p3gj0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5t690quf6jahu9p3gj0y.png" alt="a" width="490" height="861"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Separately, there's a &lt;strong&gt;passkey registration&lt;/strong&gt; screen. This is used for attestation. Passkeys are managed not by SA but &lt;strong&gt;by CP&lt;/strong&gt;. However, for simplicity, registration is done through the frontend that communicates with SA. (Normally, it should be registered from a different domain's screen.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnfgoosfkrbmcejv96rfc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnfgoosfkrbmcejv96rfc.png" alt="img" width="509" height="806"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Furthermore, &lt;strong&gt;credit card information&lt;/strong&gt; is registered. This is also registered &lt;strong&gt;with CP&lt;/strong&gt;, not SA, but for the same reason as passkeys, it's registered from the same frontend. (So credit card information doesn't flow to SA. The flow where SA accesses credit card information will come later.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65mfq073haxzqpzbqvio.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65mfq073haxzqpzbqvio.png" alt="img" width="581" height="708"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Frontend Overview
&lt;/h3&gt;

&lt;p&gt;The frontend consists of 3 screens: &lt;code&gt;/chat&lt;/code&gt;, &lt;code&gt;/merchant&lt;/code&gt;, and &lt;code&gt;/payment-methods&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/chat&lt;/code&gt; provides the chat UI between user and SA. It's arguably the central screen of this demo app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59mrzjlm0qzkrseh3xn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F59mrzjlm0qzkrseh3xn9.png" alt="img" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/payment-methods&lt;/code&gt; is a screen where you can edit, delete, or add credit cards you added earlier. Since it's under CP's jurisdiction, it should normally be a different frontend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6751qimkjhaj3d4wqu1q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6751qimkjhaj3d4wqu1q.png" alt="gg" width="800" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/merchant&lt;/code&gt; is literally the shop-side admin screen. Product management, order history management, manual shop-side signing, etc., are possible. This should normally be a shop-dedicated frontend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldedqhhbzmz2yj2ams4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fldedqhhbzmz2yj2ams4t.png" alt="aa" width="800" height="613"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's start chatting from &lt;code&gt;/chat&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;Type "Hello" to get started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5yo4s5y39q26pmaajkbp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5yo4s5y39q26pmaajkbp.png" alt="aaa" width="800" height="239"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  SA is LangGraph
&lt;/h2&gt;

&lt;p&gt;Suddenly, but SA runs on &lt;a href="https://www.langchain.com/langgraph" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt; and &lt;a href="https://www.docker.com/ja-jp/products/model-runner/" rel="noopener noreferrer"&gt;Docker Model Runner&lt;/a&gt;. &lt;strong&gt;Docker Model Runner&lt;/strong&gt; is a convenient Docker Desktop feature that lets you easily use local LLMs. The reason I chose Docker Model Runner is that you get a fully working environment with Docker Compose without API key setup (since it's a local LLM), and &lt;strong&gt;I'm broke being on parental leave&lt;/strong&gt; so I can't casually hit LLM APIs for testing. (Very broke, please help.)&lt;/p&gt;

&lt;p&gt;So, &lt;strong&gt;this demo is free&lt;/strong&gt; to try! Instead, since we're using a local LLM (&lt;a href="https://github.com/QwenLM/Qwen3" rel="noopener noreferrer"&gt;Qwen3&lt;/a&gt;), overall operation is sluggish. If that bothers you, please use a different model.&lt;/p&gt;

&lt;p&gt;The SA's LangGraph graph looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2d9iex8wz2e5hds5szw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2d9iex8wz2e5hds5szw.png" alt="graph" width="437" height="2670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's quite long + linear graph, but &lt;strong&gt;AP2 scenarios have signatures interspersed throughout&lt;/strong&gt;, and the signature order is important, so the operation is quite complex. Therefore, nodes with autonomous LLM operation are limited to just a part (the blue node in the figure, where user Intent is extracted). It's basically a picture story graph with lots of fixed text.&lt;/p&gt;

&lt;p&gt;Unfortunately, error recovery processing isn't implemented. So if an error occurs somewhere, it moves to the error node, and you need to type "Hello" to reset. (I'd like to refine this part of the graph too, but implementation time would run out.)&lt;/p&gt;
&lt;h3&gt;
  
  
  Intent Extraction &amp;amp; Intent Mandate Draft Creation (collect_intent)
&lt;/h3&gt;

&lt;p&gt;Skipping the greeting node explanation, the important one is the &lt;code&gt;collect_intent&lt;/code&gt; node. This crucial node receives the user's purchase intention in &lt;strong&gt;natural language&lt;/strong&gt; and &lt;strong&gt;creates the Intent Mandate draft&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It extracts purchase intention, budget, keywords, product category, brand, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wcpsojxwewb0cpanc2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4wcpsojxwewb0cpanc2a.png" alt="aaa" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This corresponds to Steps 1-3 in the official documentation sequence &lt;a href="https://ap2-protocol.org/specification/#core-principles" rel="noopener noreferrer"&gt;7.1 Illustrative Transaction Flow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Originally, looking at Step 3 in the official documentation sequence, it shows &lt;code&gt;Confirm&lt;/code&gt;, so it seems necessary to &lt;strong&gt;ask the user for confirmation&lt;/strong&gt; via popup or similar, but AP2's shopping experience can result in &lt;strong&gt;constant popups requesting user signatures&lt;/strong&gt; if not designed well. So here, I avoid dedicated popups, assuming the user will naturally correct via chat typing like "No, what I want is~". (The graph isn't refined enough for correction input to actually work though...)&lt;/p&gt;

&lt;p&gt;Looking at the demo app's operation logs, you can see that the user's Intent is extracted &lt;strong&gt;using LLM&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_shopping_agent         | [2025-11-01 00:49:37,548] INFO in services.shopping_agent.langgraph_shopping_flow: [route_by_step] Routing decision
ap2_shopping_agent         |   current_step: ask_intent
ap2_shopping_agent         |   user_input: I want cute goods. Within 5000 yen
ap2_shopping_agent         |   is_step_up_completion: False

ap2_shopping_agent         | [2025-11-01 00:50:12,592] INFO in services.shopping_agent.langgraph_shopping_flow: [collect_intent_node] LLM result: {'intent': 'I want to buy cute goods', 'max_amount': 5000, 'keywords': ['cute', 'goods', 'stylish']}

ap2_shopping_agent         | {"timestamp": "2025-11-01T00:50:12.593597Z", "level": "INFO", "logger": "agent", "message": "[_create_intent_mandate] Reconstructed intent: I want to buy cute goods. Within 5000 yen", "module": "agent", "function": "_create_intent_mandate", "line": 1733}

ap2_shopping_agent         | {"timestamp": "2025-11-01T00:50:12.593977Z", "level": "INFO", "logger": "agent", "message": "[_build_intent_mandate_from_session] Constructed natural_language_description: I want to buy cute goods. Within 5000 yen", "module": "agent", "function": "_build_intent_mandate_from_session", "line": 1812}

ap2_shopping_agent         | {"timestamp": "2025-11-01T00:50:12.594677Z", "level": "INFO", "logger": "agent", "message": "[ShoppingAgent] IntentMandate created (AP2-compliant): intent='I want to buy cute goods...', expiry=2025-11-01T01:50:12.593363Z", "module": "agent", "function": "_build_intent_mandate_from_session", "line": 1824}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Finalizing Shipping Address (collect_shipping)
&lt;/h3&gt;

&lt;p&gt;What's important in AP2 is &lt;strong&gt;finalizing the amount&lt;/strong&gt; by the time of Cart Mandate exchange.&lt;/p&gt;

&lt;p&gt;This is because it would be troublesome to have a last-minute surprise like "&lt;strong&gt;Actually, there's a 500 yen shipping fee on top of the total!&lt;/strong&gt;" after proceeding with the transaction. Therefore, shipping address finalization is done early in the sequence. This corresponds to Step 5 in &lt;a href="https://ap2-protocol.org/specification/#core-principles" rel="noopener noreferrer"&gt;7.1 Illustrative Transaction Flow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If the SA already has shipping address information, this processing can be done on the SA side, so it's &lt;strong&gt;Optional&lt;/strong&gt; in the sequence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkog19lfw7dazaopqve7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkog19lfw7dazaopqve7x.png" alt="aaa" width="754" height="737"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CP Selection (select_cp)
&lt;/h3&gt;

&lt;p&gt;This is also Optional, but &lt;strong&gt;users can select from multiple CPs&lt;/strong&gt;. The image is probably close to choosing whether to use Google Pay or PayPal. (Probably.)&lt;/p&gt;

&lt;p&gt;Normally, the list of available CPs should be registered by the user in advance, but in this demo, it's fixed to choose from 2 CPs.&lt;/p&gt;

&lt;p&gt;Selecting CP early is necessary because Step 6 in the official documentation sequence requires querying CP for payment methods. CP selection corresponds to Step 4 in the sequence.&lt;/p&gt;

&lt;p&gt;The demo app implementation swaps Steps 4 and 5 from &lt;a href="https://ap2-protocol.org/specification/#core-principles" rel="noopener noreferrer"&gt;7.1 Illustrative Transaction Flow&lt;/a&gt; to smoothly transition to the next payment method confirmation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdl0mvncbtlok0egv3m6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvdl0mvncbtlok0egv3m6.png" alt="aaa" width="777" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Payment Method Confirmation (get_payment_method)
&lt;/h3&gt;

&lt;p&gt;Since the CP to use is decided, now we retrieve payment methods. Since we're using the &lt;strong&gt;Human Present&lt;/strong&gt; transaction modality, the actual user decision on payment method comes later in the sequence, but it's retrieved internally at this timing.&lt;/p&gt;

&lt;p&gt;For the same reason as shipping costs, &lt;strong&gt;fees, discounts, or loyalty information related to the selected payment method may vary&lt;/strong&gt;, so the payment method candidates need to be presented to MA before Cart Mandate creation.&lt;/p&gt;

&lt;p&gt;Looking at the demo app logs, while it doesn't appear on screen since it's internal operation, logs showing communication with the selected CP are output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_shopping_agent         | [2025-11-01 00:50:29,045] INFO in services.shopping_agent.langgraph_shopping_flow: [select_cp_node] AP2 Step 4: User selected Credential Provider
ap2_shopping_agent         |   User ID: usr_ee3014bbc51f4156
ap2_shopping_agent         |   CP ID: did:ap2:cp:demo_cp
ap2_shopping_agent         |   CP Name: AP2 Demo Credential Provider
ap2_shopping_agent         | [2025-11-01 00:50:29,255] INFO in services.shopping_agent.langgraph_shopping_flow: [get_payment_methods_node] AP2 Step 6-7: Requesting payment methods from CP
ap2_shopping_agent         |   User ID: usr_ee3014bbc51f4156
ap2_shopping_agent         |   CP ID: did:ap2:cp:demo_cp
ap2_shopping_agent         |   CP URL: http://credential_provider:8003
ap2_shopping_agent         | {"timestamp": "2025-11-01T00:50:29.257749Z", "level": "INFO", "logger": "agent", "message": "[ShoppingAgent] Requesting payment methods from Credential Provider (http://credential_provider:8003) for user: usr_ee3014bbc51f4156", "module": "agent", "function": "_get_payment_methods_from_cp", "line": 2316}
ap2_shopping_agent         | {"timestamp": "2025-11-01T00:50:29.261011Z", "level": "INFO", "logger": "agent", "message": "HTTP Request: GET http://credential_provider:8003/payment-methods", "module": "logger", "function": "log_http_request", "line": 182}
ap2_shopping_agent         | {"timestamp": "2025-11-01T00:50:29.261495Z", "level": "DEBUG", "logger": "agent", "message": "HTTP_REQUEST_RAW: {\"type\": \"HTTP_REQUEST\", \"method\": \"GET\", \"url\": \"http://credential_provider:8003/payment-methods\", \"headers\": {}, \"body\": null}", "module": "logger", "function": "log_http_request", "line": 193}
ap2_credential_provider    | {"timestamp": "2025-11-01T00:50:29.569821Z", "level": "INFO", "logger": "provider", "message": "[get_payment_methods] Retrieved 1 payment methods for user: usr_ee3014bbc51f4156", "module": "provider", "function": "get_payment_methods", "line": 631}
ap2_credential_provider    | INFO:     172.18.0.9:34006 - "GET /payment-methods?user_id=usr_ee3014bbc51f4156 HTTP/1.1" 200 OK
ap2_shopping_agent         | {"timestamp": "2025-11-01T00:50:29.576076Z", "level": "INFO", "logger": "agent", "message": "HTTP Response: 200 (317.37ms)", "module": "logger", "function": "log_http_response", "line": 215}
ap2_shopping_agent         | {"timestamp": "2025-11-01T00:50:29.576146Z", "level": "DEBUG", "logger": "agent", "message": "HTTP_RESPONSE_RAW: {\"type\": \"HTTP_RESPONSE\", \"status_code\": 200, \"headers\": {\"date\": \"Sat, 01 Nov 2025 00:50:28 GMT\", \"server\": \"uvicorn\", \"content-length\": \"252\", \"content-type\": \"application/json\"}, \"body\": {\"user_id\": \"usr_ee3014bbc51f4156\", \"payment_methods\": [{\"id\": \"pm_e6367cd7\", \"type\": \"basic-card\", \"display_name\": \"Visa Card (****1111)\", \"brand\": \"Visa\", \"last4\": \"1111\", \"requires_step_up\": false, \"billing_address\": {\"country\": \"JP\", \"postal_code\": \"111-1111\"}}]}, \"duration_ms\": 317.3692226409912}", "module": "logger", "function": "log_http_response", "line": 226}
ap2_shopping_agent         | {"timestamp": "2025-11-01T00:50:29.576201Z", "level": "INFO", "logger": "agent", "message": "[ShoppingAgent] Retrieved 1 payment methods from Credential Provider", "module": "agent", "function": "_get_payment_methods_from_cp", "line": 2334}
ap2_shopping_agent         | [2025-11-01 00:50:29,576] INFO in services.shopping_agent.langgraph_shopping_flow: [get_payment_methods_node] AP2 Step 7: Received 1 payment methods from CP
ap2_shopping_agent         |   Payment Methods: ['pm_e6367cd7']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key point is that while the response from CP contains card information, &lt;strong&gt;PCI data (card number itself, CVC, etc.) is not included&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By the way, I debated whether communication between SA and CP should be A2A or regular REST API. Here, it's implemented as a REST API with &lt;code&gt;GET /payment-methods&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we consider CP as an agentic entity, A2A seems appropriate, but there's a lot of WebAuthn communication with CP, and it feels closer to an e-commerce backend service rather than an agentic entity. However, using &lt;strong&gt;A2A&lt;/strong&gt; would have the advantage of unifying &lt;strong&gt;signatures&lt;/strong&gt; and &lt;strong&gt;DID&lt;/strong&gt;-based mutual authentication, so maybe A2A is better after all. (I don't know.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Intent Mandate Transmission (fetch_cart)
&lt;/h3&gt;

&lt;p&gt;Now we finally &lt;strong&gt;send the Intent Mandate&lt;/strong&gt; to &lt;strong&gt;MA&lt;/strong&gt;. Transmission is done via &lt;strong&gt;A2A Message&lt;/strong&gt;. This corresponds to Step 8 in the official documentation sequence.&lt;/p&gt;

&lt;p&gt;The Intent Mandate + additional content sent via A2A looks like this: (Extracted from httpx debug logs when sending A2A messages)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTTP_REQUEST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://merchant_agent:8001/a2a/message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a2d45408-afbc-4891-af66-647e82665f25"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sender"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"recipient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:merchant_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-01T00:50:29.785905Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"nonce"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cc624e26345bfe79c092580578dbba04a14f46c27d44077b2d89303a35970833"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"proof"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"algorithm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ed25519"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"signatureValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aMCMUarSscFWY8/j+NKdKflvyzdMjpZHJJqGXuKWsW/XqO0loXpUIfrFFTBzenXXPAajkw7IpQYMWtv4ytyNDg=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"publicKeyMultibase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"z6MkwMTmaSbsecH3zoTBSAEY2vuMxguAebRnruND2a8oVvcq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent#key-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-01T11:42:36.177985Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"proofPurpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"authentication"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dataPart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ap2.mandates.IntentMandate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"intent_a97064a3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"intent_mandate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"intent_a97064a3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IntentMandate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_ee3014bbc51f4156"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"user_cart_confirmation_required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"natural_language_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I want to buy cute goods. Within 5000 yen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"requires_refundability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"intent_expiry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-01T01:50:12.593363Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-01T00:50:12.593363Z"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"shipping_address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"recipient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Taro Yamada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"postal_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123-4567"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Toshima-ku"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tokyo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"address_line1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1-1-1 Kitaotsuka"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Japan"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"artifact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, let's look at the &lt;strong&gt;Intent Mandate&lt;/strong&gt; itself, which is easiest to understand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"intent_a97064a3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IntentMandate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_ee3014bbc51f4156"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_cart_confirmation_required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"natural_language_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I want to buy cute goods. Within 5000 yen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requires_refundability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"intent_expiry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-01T01:50:12.593363Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-01T00:50:12.593363Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;~Unfortunately, the official documentation itself doesn't have an Intent Mandate example~, but looking at the &lt;a href="https://github.com/google-agentic-commerce/AP2/blob/f404a8ddf2e2a5ddf76a6f3fd990b4cb78a71200/src/ap2/types/mandate.py#L32C7-L32C20" rel="noopener noreferrer"&gt;type definitions&lt;/a&gt; in the official GitHub reveals the specification. Let's look at the important fields.&lt;/p&gt;

&lt;p&gt;(There is a description in the official documentation &lt;a href="https://ap2-protocol.org/a2a-extension/#intentmandate-message" rel="noopener noreferrer"&gt;A2A Extension for AP2&lt;/a&gt;. My apologies for the oversight.)&lt;/p&gt;

&lt;h4&gt;
  
  
  user_cart_confirmation_required
&lt;/h4&gt;

&lt;p&gt;Looking at the type definition:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If false, the agent can make purchases on the user's behalf once all purchase conditions have been satisfied. This must be true if the intent mandate is not signed by the user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, for Intent Mandates without user signatures, meaning Human Present transaction modalities, this needs to be &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  natural_language_description
&lt;/h4&gt;

&lt;p&gt;In this demo app, since the SA doesn't do deep intent exploration, the user's input "I want to buy cute goods. Within 5000 yen" is entered as-is.&lt;/p&gt;

&lt;p&gt;Looking at the type definition:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The natural language description of the user's intent. This is generated by the shopping agent, and confirmed by the user. The goal is to have informed consent by the user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, originally the user's text itself shouldn't be entered directly, but rather the &lt;strong&gt;intent interpreted by SA enters in natural language form&lt;/strong&gt;. Also, it's specified that &lt;strong&gt;confirmation should be requested before sending&lt;/strong&gt; that intent.&lt;/p&gt;

&lt;h4&gt;
  
  
  intent_expiry
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Intent Mandate&lt;/strong&gt; has an &lt;strong&gt;expiration&lt;/strong&gt;. This is to guarantee that the user's intent is definitely valid by setting a time limit. &lt;strong&gt;Human intentions also have a shelf life&lt;/strong&gt;, in other words.&lt;/p&gt;

&lt;p&gt;Here it's set to 1 hour after Intent Mandate creation, but for Human Not Present, it might be longer. (Or it might confirm with the user.)&lt;/p&gt;

&lt;h4&gt;
  
  
  shipping_address
&lt;/h4&gt;

&lt;p&gt;Not part of Intent Mandate itself, but &lt;code&gt;shipping_address&lt;/code&gt; is also communicated so MA can create a cart with exact pricing. Considering AP2's philosophy, payment information should also be communicated, but it's omitted in this demo.&lt;/p&gt;

&lt;p&gt;That concludes the Intent Mandate explanation.&lt;/p&gt;

&lt;h4&gt;
  
  
  SA's Signature
&lt;/h4&gt;

&lt;p&gt;Separate from the &lt;strong&gt;Intent Mandate&lt;/strong&gt; itself, there's a &lt;strong&gt;Header field&lt;/strong&gt; that follows the &lt;strong&gt;proof structure&lt;/strong&gt; of &lt;a href="https://www.w3.org/TR/vc-data-model-2.0/" rel="noopener noreferrer"&gt;W3C Verifiable Credentials&lt;/a&gt;, equivalent to SA's &lt;strong&gt;Verifiable Presentation&lt;/strong&gt; (VP).&lt;/p&gt;

&lt;p&gt;While there's no clear documentation in official sources, according to &lt;a href="https://developer.paypal.com/community/blog/PayPal-Agent-Payments-Protocol/" rel="noopener noreferrer"&gt;PayPal's blog&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All mandates are expressed as W3C Verifiable Credentials, ensuring tamper resistance, portability, and interoperability across the ecosystem. Mandates embed cryptographically verifiable consent into authorization flows, providing merchants with dispute-grade evidence, issuers with consistent agent-presence signals and consumers with non-repudiable proof of intent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I've created it to conform to that specification.&lt;/p&gt;

&lt;p&gt;The signature follows the proof specification (RFC 8032 Ed25519).&lt;/p&gt;

&lt;p&gt;The signing algorithm uses the pre-created &lt;strong&gt;SA's private key&lt;/strong&gt; with &lt;a href="https://en.wikipedia.org/wiki/EdDSA" rel="noopener noreferrer"&gt;ED25519&lt;/a&gt; to sign the DataPart converted to &lt;a href="https://www.rfc-editor.org/rfc/rfc8785.html" rel="noopener noreferrer"&gt;canonicalized JSON (RFC8785)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The public key for signature verification is attached in publicKeyMultibase format. Since the value starts with &lt;code&gt;z&lt;/code&gt;, we know it uses &lt;a href="https://en.wikipedia.org/wiki/Base58" rel="noopener noreferrer"&gt;base58btc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, &lt;code&gt;nonce&lt;/code&gt; is generated to prevent signature replay and replacement.&lt;/p&gt;

&lt;p&gt;Like this, with various processing including signatures, we can finally &lt;strong&gt;send the Intent Mandate&lt;/strong&gt; to &lt;strong&gt;MA&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  About VP
&lt;/h4&gt;

&lt;p&gt;I briefly explained above, but &lt;strong&gt;Verifiable Presentation&lt;/strong&gt; (VP) might be hard to understand. I didn't understand it until now either.&lt;/p&gt;

&lt;p&gt;First, before discussing VP, we need to touch on the concept of &lt;strong&gt;VC&lt;/strong&gt; (&lt;strong&gt;Verifiable Credential&lt;/strong&gt;). Remember that there are 2 concepts: &lt;strong&gt;VC&lt;/strong&gt; and &lt;strong&gt;VP&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;VC proves "&lt;strong&gt;This person/thing is xx!&lt;/strong&gt;"&lt;/p&gt;

&lt;p&gt;For example, &lt;strong&gt;handling a university graduation certificate with VC&lt;/strong&gt;. The university (issuer) cryptographically issues a certificate to the graduate (holder) that they &lt;strong&gt;graduated&lt;/strong&gt;. So VC is like the graduation certificate itself.&lt;/p&gt;

&lt;p&gt;VP proves "&lt;strong&gt;This is definitely a certificate about me being presented by me&lt;/strong&gt;" when presenting this graduation certificate. It's achieved by adding the holder's signature to the VC.&lt;/p&gt;

&lt;p&gt;With this mechanism, you can prove you &lt;strong&gt;graduated from university&lt;/strong&gt; without having to &lt;a href="https://news.yahoo.co.jp/articles/cce572f31eebf26a09ee7fa6f054744c4d38ab38" rel="noopener noreferrer"&gt;briefly flash it for about 19.2 seconds&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  MA is Also LangGraph
&lt;/h2&gt;

&lt;p&gt;Now that the Intent Mandate has been sent to MA, MA moves to &lt;strong&gt;creating the shopping cart&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;MA is also built with LangGraph + Docker Model Runner. This graph starts from where it &lt;strong&gt;receives the Intent Mandate from SA&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0s25o4ob1u9rntf5u5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0s25o4ob1u9rntf5u5r.png" alt="graph" width="800" height="923"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Signature Verification Before Graph Starts
&lt;/h3&gt;

&lt;p&gt;While not represented in the LangGraph graph (since it's done in the A2A message handler in the demo), we need to verify that the received A2A message was &lt;strong&gt;really sent from SA&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The verification steps basically do the reverse of what was done for signing. Let's look in detail.&lt;/p&gt;

&lt;h4&gt;
  
  
  Replay Attack Countermeasures
&lt;/h4&gt;

&lt;p&gt;First, after checking (Validation) that the proof structure is correct, &lt;strong&gt;Timestamp verification&lt;/strong&gt; is performed. The mechanism is simple—confirming whether the proof was created &lt;strong&gt;within 300 seconds = 5 minutes of the current time&lt;/strong&gt;. This is a basic countermeasure against &lt;a href="https://en.wikipedia.org/wiki/Replay_attack" rel="noopener noreferrer"&gt;replay attacks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, &lt;strong&gt;Nonce verification&lt;/strong&gt; is performed. It checks its own KV store to confirm there's no same Nonce recently, that the &lt;strong&gt;same Nonce hasn't been used&lt;/strong&gt; within a unit time (300 seconds = 5 minutes here), meaning no &lt;strong&gt;reuse&lt;/strong&gt; of A2A communication.&lt;/p&gt;

&lt;h4&gt;
  
  
  Obtaining the Public Key
&lt;/h4&gt;

&lt;p&gt;Next, we proceed to &lt;strong&gt;verify the message signature&lt;/strong&gt; from the proof structure.&lt;/p&gt;

&lt;p&gt;First, to verify the signature, we need &lt;strong&gt;SA's public key&lt;/strong&gt;. AP2 recommends using &lt;a href="https://www.w3.org/TR/did-1.0/" rel="noopener noreferrer"&gt;DID (Decentralized Identifier)&lt;/a&gt; to obtain this public key.&lt;/p&gt;

&lt;p&gt;The demo app supports using &lt;code&gt;publicKeyMultibase&lt;/code&gt; in addition to DID. Since you might not be familiar with DID, let's look in detail.&lt;/p&gt;

&lt;h5&gt;
  
  
  What is DID?
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://www.w3.org/TR/did-1.0/" rel="noopener noreferrer"&gt;DID (Decentralized Identifier)&lt;/a&gt; is literally a &lt;strong&gt;decentralized&lt;/strong&gt; (not centralized) &lt;strong&gt;entity identification method&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With A2A communication having proof structures, signature verification can determine that &lt;strong&gt;communication completed without tampering&lt;/strong&gt; using &lt;code&gt;publicKeyMultibase&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, the downside is that signatures alone can't tell &lt;strong&gt;whose key signed it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We probably know it's from SA based on the destination, but we don't know details about what kind of agent SA is. DID can create a "context of trust."&lt;/p&gt;

&lt;p&gt;With DID, you start by making a &lt;strong&gt;did.json&lt;/strong&gt; JSON file accessible from each entity's &lt;code&gt;.well-known/did.json&lt;/code&gt;. For example, SA's did.json looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://www.w3.org/ns/did/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://w3id.org/security/suites/jws-2020/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"https://w3id.org/security/suites/ed25519-2020/v1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verificationMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent#key-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EcdsaSecp256r1VerificationKey2019"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"controller"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"publicKeyPem"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-----BEGIN PUBLIC KEY-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPlqNMbMKh/8HoX2356uZmKM2lVuB&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Y71rBhcg1lpuUBncM7LmNAEJO/9WcKboqL+KHKpwGCIEr/oWsizgd89hvA==&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;-----END PUBLIC KEY-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"publicKeyMultibase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"z2oAtKWnMsubf5MPr6XqWVuLeXVipQ84i4jj2VV9Vu5EZjtQ8"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent#key-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ed25519VerificationKey2020"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"controller"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"publicKeyPem"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-----BEGIN PUBLIC KEY-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;MCowBQYDK2VwAyEAkr3srUb1CmKJq6G0h0PXPnOUtJrTQKL/a8u0J3Ob1wk=&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;-----END PUBLIC KEY-----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"publicKeyMultibase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"z6MkpL5YFLHxAcp6LSJboXQ3nBnNGrQ4TiZRmWBZamPo7t8x"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent#key-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent#key-2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"assertionMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent#key-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent#key-2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-02T00:16:30.663059Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"updated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-02T00:16:30.663091Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent#a2aendpoint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A2AEndpoint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"serviceEndpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://shopping_agent:8000/a2a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Shopping Agent A2A Endpoint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A2A communication endpoint (user shopping proxy agent)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By obtaining the &lt;strong&gt;public key&lt;/strong&gt; from &lt;strong&gt;did.json&lt;/strong&gt;, we can show that the message signer is definitely &lt;strong&gt;SA&lt;/strong&gt;. By the way, I don't really understand the standard specification for &lt;strong&gt;DID resolvers&lt;/strong&gt; in AP2, so here I extract the Docker Network hostname &lt;code&gt;shopping_agent&lt;/code&gt; from the DID ID &lt;code&gt;did:ap2:agent:shopping_agent&lt;/code&gt; and access &lt;code&gt;.well-known/did.json&lt;/code&gt;. (Imagining use alongside DNS.)&lt;/p&gt;

&lt;h5&gt;
  
  
  publicKeyMultibase
&lt;/h5&gt;

&lt;p&gt;While probably not recommended by AP2, the demo app also supports signature verification using &lt;strong&gt;publicKeyMultibase&lt;/strong&gt; included in the proof structure. I've made it prioritize DID, and if DID is the assumed architecture, including publicKeyMultibase in the proof structure might be unnecessary.&lt;/p&gt;

&lt;h4&gt;
  
  
  Verification
&lt;/h4&gt;

&lt;p&gt;Now we proceed to verification. We know it was signed with ED25519 on the DataPart as &lt;strong&gt;canonicalized JSON&lt;/strong&gt; (RFC8785), so we verify using the public key.&lt;/p&gt;

&lt;p&gt;Once verification completes successfully, we know it's a Mandate definitely sent from SA and can proceed to the next processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intent Extraction from Intent Mandate (analyze_intent)
&lt;/h3&gt;

&lt;p&gt;Processing to extract keywords, prices, etc., from the received Intent Mandate's &lt;code&gt;natural_language_description&lt;/code&gt; into DB-searchable information is done using &lt;strong&gt;LLM&lt;/strong&gt; power.&lt;/p&gt;

&lt;p&gt;In this node, a prompt like the following runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=========System Prompt==========
You are the Merchant Agent's intent analysis expert.
Analyze the user's IntentMandate (purchase intention) and extract the following information:

primary_need: The user's main request (concisely in 1 sentence)
budget_strategy: Budget strategy ("low"=lowest price priority, "balanced"=balanced type, "premium"=high quality priority)
key_factors: List of important factors (e.g.: ["quality", "price", "brand", "design"])
search_keywords: Keyword list for product search (3-5 items, words likely to be in product names)
Important:

Please always respond in JSON format.

=========User Prompt===========
Please analyze the following IntentMandate:

Natural language description: I want to buy cute goods. Within 5000 yen
Constraints: {}

Please respond in JSON format:
{
"primary_need": "...",
"budget_strategy": "low/balanced/premium",
"key_factors": ["...", "..."],
"search_keywords": ["...", "...", "..."]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since I'm using a local LLM while on parental leave due to lack of funds, I don't feel it's extracting Intent correctly, but with the latest models, more advanced extraction should be possible. The LLM response becomes JSON like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"primary_need"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I want to buy cute goods within 5000 yen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"budget_strategy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"key_factors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"design"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"search_keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"cute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"goods"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"accessories"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"additional_kwargs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"refusal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Product Search via MCP Server (search_products)
&lt;/h3&gt;

&lt;p&gt;AP2 is emphasized in the &lt;a href="https://ap2-protocol.org/topics/ap2-a2a-and-mcp/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; as something that &lt;strong&gt;extends&lt;/strong&gt;, not competes with, A2A and MCP. So I decided to use &lt;strong&gt;MCP server&lt;/strong&gt; for product search tool usage. (Streamable HTTP)&lt;/p&gt;

&lt;p&gt;Since I wanted to use a &lt;strong&gt;full-text search engine&lt;/strong&gt; for product search itself, I'm using the lightweight &lt;a href="https://www.meilisearch.com/" rel="noopener noreferrer"&gt;Meilisearch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'll skip the sequence explanation for Initialize processing with the MCP server, but you can see that when method &lt;code&gt;tools/call&lt;/code&gt; runs from MA, it sends a search request like the following to the MCP server used by MA (merchant_agent_mcp):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTTP_REQUEST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://merchant_agent_mcp:8011/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Mcp-Session-Id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2ade50e5-f2ae-439c-becb-0ba97bd1a161"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tools/call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search_products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"cute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"goods"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="s2"&gt;"affordable"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;387151&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results come back as follows. The product IDs in the product search DB match the product IDs in the RDB (SQLite) that connects later. So this ID is used for inventory checks, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTTP_RESPONSE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sun, 02 Nov 2025 00:21:39 GMT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvicorn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content-length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3086"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;387151&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;products&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;286acdd4-d1c1-4860-b06a-f87d2f916a8d&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sku&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;MUGI-KEYCHAIN-001&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Mugibo Acrylic Keychain&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;A cute Mugibo acrylic keychain. Attach it to bags and pouches.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_cents&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 80000,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_jpy&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 800.0,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;inventory_count&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 100,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;brand&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;image_url&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;refund_period_days&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 30},{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;9f58d67c-5c45-4cd4-bf10-73f06647c234&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sku&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;MUGI-CLOCK-001&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Mugibo Clock&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;A cute wall clock with Mugibo design. Brightens up your room.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_cents&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 350000,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_jpy&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 3500.0,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;inventory_count&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 30,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;brand&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;image_url&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;refund_period_days&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 30},{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;2faf8370-ada4-45d4-812c-ef5818d526b5&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sku&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;MUGI-POUCH-001&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Mugibo Pouch&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;A cute pouch with Mugibo pattern. Use as a small items holder or pen case.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_cents&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 95000,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_jpy&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 950.0,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;inventory_count&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 120,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;brand&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;image_url&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;refund_period_days&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 30},{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;1d9f08d9-51a9-491e-810b-f0e225ef4f59&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sku&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;MUGI-MUG-001&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Mugibo Mug&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;A cute mug with Mugibo print. Makes your daily tea time enjoyable.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_cents&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 120000,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_jpy&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 1200.0,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;inventory_count&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 80,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;brand&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;image_url&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;refund_period_days&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 30},{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;c688d6ef-615f-43f7-87ce-05568ae4e63c&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sku&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;MUGI-SOCKS-001&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Mugibo Socks&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Cute socks with a Mugibo accent. Soft and comfortable to wear.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_cents&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 85000,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_jpy&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 850.0,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;inventory_count&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 100,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;brand&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;image_url&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;refund_period_days&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 30},{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;6a169d3a-ca5a-4575-a08b-3fb659c628ed&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;sku&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;MUGI-PLATE-001&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Mugibo Plate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;A ceramic plate with Mugibo in the center. Decorates your dining table cutely.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_cents&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 190000,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;price_jpy&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 1900.0,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;inventory_count&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 70,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;brand&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;image_url&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: null,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;refund_period_days&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: 30}]}"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"isError"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1208.6033821105957&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Inventory Check (check_inventory)
&lt;/h3&gt;

&lt;p&gt;Once products are found, we query the inventory status again &lt;strong&gt;via MCP server&lt;/strong&gt;. This is a query to the &lt;strong&gt;RDB&lt;/strong&gt; (SQLite), but communication with the MCP server is done via &lt;strong&gt;Streamable HTTP&lt;/strong&gt; just like the search_products node. We got the following inventory status for each product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"3446cca8-fe68-4354-a518-63eb3e47d27f"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"1530a7db-6b7a-458e-b7b9-f510f6fdaa89"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cf08568b-8115-461c-aa7e-5a2e07bf4476"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"e534386f-c89b-4548-9734-78bd34958f88"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eb0e5de8-679d-4445-8e64-a1d4d40097fd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"f0f41c9f-a31d-4a88-a180-96149a9057fc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All products have no inventory issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cart Candidate Creation (optimize_cart)
&lt;/h3&gt;

&lt;p&gt;Now we &lt;strong&gt;create the cart&lt;/strong&gt;. Processing moves to the optimize_cart node where the local LLM creates 3 cart plans.&lt;/p&gt;

&lt;p&gt;In optimize_cart, a prompt like the following runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;========System Prompt=========
You are the Merchant Agent's cart optimization expert.
From the user's purchase intention and product list, suggest 3 optimal cart plans.

Include the following in each plan:

name: Plan name (including price or features, e.g.: "Budget Plan (5,000 yen)")
description: Plan description (1-2 sentences)
items: Product list [{"product_id": 123, "quantity": 1}, ...]
Plan design guidelines:

Plan 1: Most cost-effective plan within budget
Plan 2: High quality plan even if slightly over budget
Plan 3: Simple plan with only 1-2 products
Please always respond in JSON array format.

=======User Prompt============
Please suggest 3 cart plans under the following conditions:

User's request: I want to buy cute goods within 5000 yen
Budget strategy: low
Important factors: price, design
Budget limit: Not specified

Product list (6 items):
[
  {
    "id": "3446cca8-fe68-4354-a518-63eb3e47d27f",
    "name": "Mugibo Acrylic Keychain",
    "price_jpy": 800.0,
    "category": null,
    "inventory": 100
  },
  {
    "id": "1530a7db-6b7a-458e-b7b9-f510f6fdaa89",
    "name": "Mugibo Clock",
    "price_jpy": 3500.0,
    "category": null,
    "inventory": 30
  },
  {
    "id": "cf08568b-8115-461c-aa7e-5a2e07bf4476",
    "name": "Mugibo Pouch",
    "price_jpy": 950.0,
    "category": null,
    "inventory": 120
  },
  {
    "id": "e534386f-c89b-4548-9734-78bd34958f88",
    "name": "Mugibo Mug",
    "price_jpy": 1200.0,
    "category": null,
    "inventory": 80
  },
  {
    "id": "eb0e5de8-679d-4445-8e64-a1d4d40097fd",
    "name": "Mugibo Socks",
    "price_jpy": 850.0,
    "category": null,
    "inventory": 100
  },
  {
    "id": "f0f41c9f-a31d-4a88-a180-96149a9057fc",
    "name": "Mugibo Plate",
    "price_jpy": 1900.0,
    "category": null,
    "inventory": 70
  }
]

Please respond in JSON array format:
[
  {
    "name": "Plan name (including price)",
    "description": "Plan description",
    "items": [{"product_id": 123, "quantity": 1}]
  },
  ...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, with the &lt;strong&gt;local LLM&lt;/strong&gt;'s performance (&lt;a href="https://huggingface.co/Qwen/Qwen3-8B" rel="noopener noreferrer"&gt;Qwen3:8b&lt;/a&gt;), it sometimes creates off-target plans (like a mysterious cart stuffed with clocks) or incomplete JSON...&lt;/p&gt;

&lt;p&gt;When successful, JSON like the following is output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Best Value Plan (1,650 yen)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A price-focused plan combining the cheapest keychain and socks. Balances cuteness and budget."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"286acdd4-d1c1-4860-b06a-f87d2f916a8d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c688d6ef-615f-43f7-87ce-05568ae4e63c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"High Quality Plan (2,150 yen)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A plan emphasizing cuteness with pouch and mug combination. Good balance of quality and design."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2faf8370-ada4-45d4-812c-ef5818d526b5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1d9f08d9-51a9-491e-810b-f0e225ef4f59"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Simple Plan (800 yen)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A single keychain plan recommended for those who want to keep the budget down. Maximizes cuteness with just one item."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"286acdd4-d1c1-4860-b06a-f87d2f916a8d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we're just creating cart candidates (Cart Candidate), so it's not in the formal &lt;strong&gt;Cart Mandate&lt;/strong&gt; format. There's no &lt;strong&gt;shop side&lt;/strong&gt; (Merchant) &lt;strong&gt;signature&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cart Mandate Creation &amp;amp; Adding Shop Signature (build_cart_mandates)
&lt;/h3&gt;

&lt;p&gt;We format each created cart candidate into &lt;strong&gt;Cart Mandate&lt;/strong&gt; format. This operation is also done via &lt;strong&gt;MCP server&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Also, we have &lt;strong&gt;the shop side&lt;/strong&gt; (Merchant) &lt;strong&gt;add their signature&lt;/strong&gt; to the created Cart Mandate.&lt;/p&gt;

&lt;p&gt;Since this processing runs in parallel for 3 carts, pasting the logs directly would be very hard to understand.&lt;/p&gt;

&lt;p&gt;So I made a sequence diagram. (It's convenient that just pasting logs into Claude creates a sequence diagram...)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FZdvcF4x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgur.com%2FZdvcF4x.png" alt="seq" width="800" height="1934"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the created &lt;strong&gt;Cart Mandates&lt;/strong&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"signed_cart_mandate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"contents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cart_70dda49a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"user_cart_confirmation_required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"payment_request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"method_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cart_70dda49a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"display_items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Acrylic Keychain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;800.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"refund_period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2592000&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Consumption Tax (10%)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;80.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"refund_period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Shipping"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;500.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"refund_period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1380.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"shipping_address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cart_expiry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-02T01:23:03.698740Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"merchant_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Shop"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"merchant_authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJhbGciOiJ....."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"_metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"intent_mandate_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"merchant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:merchant:mugibo_merchant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-02T00:23:03.698753Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cart_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Simple Plan (800 yen)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cart_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A single keychain plan recommended for those who want to keep the budget down. Maximizes cuteness with just one item."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"raw_items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"286acdd4-d1c1-4860-b06a-f87d2f916a8d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Acrylic Keychain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A cute Mugibo acrylic keychain. Attach it to bags and pouches."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"unit_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;800.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"total_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;800.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see that in addition to product information, there's a &lt;strong&gt;Base64 string&lt;/strong&gt; in a field called &lt;code&gt;merchant_authorization&lt;/code&gt;. Let's look at the details.&lt;/p&gt;

&lt;h4&gt;
  
  
  Merchant Authorization
&lt;/h4&gt;

&lt;p&gt;I had no idea how to add the shop's signature, but looking at the &lt;a href="https://github.com/google-agentic-commerce/AP2/blob/f404a8ddf2e2a5ddf76a6f3fd990b4cb78a71200/src/ap2/types/mandate.py#L114C3-L114C25" rel="noopener noreferrer"&gt;official GitHub implementation example&lt;/a&gt; gave me roughly the answer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A base64url-encoded JSON Web Token (JWT) that digitally&lt;/p&gt;

&lt;p&gt;signs the cart contents, guaranteeing its authenticity and integrity:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Header includes the signing algorithm and key ID.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payload includes:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;iss, sub, aud: Identifiers for the merchant (issuer) and the intended recipient (audience), like a payment processor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;iat: iat, exp: Timestamps for the token's creation and its short-lived expiration (e.g., 5-15 minutes) to enhance security.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;jti: Unique identifier for the JWT to prevent replay attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;cart_hash: A secure hash of the CartMandate, ensuring integrity. The hash is computed over the canonical JSON representation of the CartContents object.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Signature: A digital signature created with the merchant's private key. It allows anyone with the public key to verify the token's authenticity and confirm that the payload has not been tampered with.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire JWT is base64url encoded to ensure safe transmission.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, just &lt;strong&gt;Base64 encode the JWT containing cart_hash&lt;/strong&gt;. So we create a JWT containing the hash of the cart sent from MA, add a signature, and return it from the shop side (Merchant) entity. So if you verify &lt;code&gt;merchant_authorization&lt;/code&gt; on &lt;a href="https://www.jwt.io/" rel="noopener noreferrer"&gt;https://www.jwt.io/&lt;/a&gt; etc., it should be a valid JWT that indeed contains the cart hash. (Verification uses the shop's public key.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxw9svixdeg0xc5oml51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxw9svixdeg0xc5oml51.png" alt="aa" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this demo app, &lt;strong&gt;shop signing is automatic&lt;/strong&gt;. In many AP2 commerce patterns, mechanical checks should be performed programmatically and proceed automatically. (Having shop staff intervene would also be a cause of lowering response times.)&lt;/p&gt;

&lt;p&gt;However, for clarity in the demo app, by accessing the &lt;code&gt;/merchant&lt;/code&gt; dashboard from the frontend and switching to manual signature mode, you can experience the shop-side cart approval.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqtn53xja6n8et2oibnx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvqtn53xja6n8et2oibnx.gif" alt="img" width="720" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This flow corresponds to Steps 10-11 of &lt;a href="https://ap2-protocol.org/specification/#71-illustrative-transaction-flow" rel="noopener noreferrer"&gt;7.1 Illustrative Transaction Flow&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cart Ranking (rank_and_select)
&lt;/h3&gt;

&lt;p&gt;Implementation is skipped in the demo app, but we rearrange (rerank) the &lt;strong&gt;ranking of selected carts&lt;/strong&gt; to improve the user's shopping experience. After all, better products appearing first increases purchase motivation.&lt;/p&gt;

&lt;p&gt;For example, cart order could be rearranged based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User preference match score&lt;/li&gt;
&lt;li&gt;Inventory certainty&lt;/li&gt;
&lt;li&gt;Price competitiveness&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A2A Message Transmission
&lt;/h3&gt;

&lt;h4&gt;
  
  
  ap2.responses.CartCandidates
&lt;/h4&gt;

&lt;p&gt;Now that the &lt;strong&gt;Cart Mandate&lt;/strong&gt; is complete, we send it to &lt;strong&gt;SA&lt;/strong&gt; via &lt;strong&gt;A2A Message&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this demo, since there are multiple cart candidates, each Cart Mandate is stored in the A2A Message's &lt;a href="https://a2aprotocol.ai/docs/guide/a2a-protocol-specification-python#datapart" rel="noopener noreferrer"&gt;DataPart&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;(I'm not sure if this is &lt;strong&gt;correct according to AP2 specifications&lt;/strong&gt;, but it seemed good for sending multiple candidates at once in an A2A Message...)&lt;/p&gt;

&lt;h4&gt;
  
  
  user_cart_confirmation_required
&lt;/h4&gt;

&lt;p&gt;Like &lt;strong&gt;Intent Mandate&lt;/strong&gt;, &lt;code&gt;user_cart_confirmation_required&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;, indicating operation under the &lt;strong&gt;Human Present&lt;/strong&gt; transaction modality where &lt;strong&gt;user confirmation of the cart&lt;/strong&gt; is required.&lt;/p&gt;

&lt;p&gt;Also, characteristically, cart products follow &lt;a href="https://www.w3.org/TR/payment-request/#paymentrequest-interface" rel="noopener noreferrer"&gt;W3C's Payment Request&lt;/a&gt; as noted in the &lt;a href="https://github.com/google-agentic-commerce/AP2/blob/f404a8ddf2e2a5ddf76a6f3fd990b4cb78a71200/src/ap2/types/payment_request.py#L184" rel="noopener noreferrer"&gt;official GitHub&lt;/a&gt;. PaymentMethodData and PaymentOptions are included following W3C Payment Request specifications.&lt;/p&gt;

&lt;h4&gt;
  
  
  _metadata
&lt;/h4&gt;

&lt;p&gt;A problem here is that Cart Mandate itself has &lt;a href="https://github.com/google-agentic-commerce/AP2/blob/f404a8ddf2e2a5ddf76a6f3fd990b4cb78a71200/src/ap2/types/payment_request.py#L47" rel="noopener noreferrer"&gt;limited information that can be included about products&lt;/a&gt;. Only 4 fields can be defined for products: &lt;code&gt;label&lt;/code&gt;, &lt;code&gt;amount&lt;/code&gt;, &lt;code&gt;pending&lt;/code&gt;, &lt;code&gt;refund_period&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This might be sufficient as &lt;strong&gt;Cart Mandate&lt;/strong&gt;, but considering actual UI/UX, we want to &lt;strong&gt;show&lt;/strong&gt; users things like &lt;strong&gt;product descriptions&lt;/strong&gt; and &lt;strong&gt;product images&lt;/strong&gt; while shopping.&lt;/p&gt;

&lt;p&gt;Therefore, they're recorded in metadata, not the Mandate itself.&lt;/p&gt;

&lt;h4&gt;
  
  
  cart_expiry
&lt;/h4&gt;

&lt;p&gt;Also, like &lt;strong&gt;Intent Mandate&lt;/strong&gt;, &lt;strong&gt;Cart Mandate&lt;/strong&gt; has an &lt;strong&gt;expiration&lt;/strong&gt;. This prevents situations where the user's Cart Mandate confirmation is delayed, too much time passes, and items become &lt;strong&gt;out of stock&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Complete with Signature
&lt;/h4&gt;

&lt;p&gt;We add MA's signature to the A2A Message with this &lt;strong&gt;DataPart&lt;/strong&gt; in proof structure, completing the Cart Mandate. This corresponds to Step 12 of &lt;a href="https://ap2-protocol.org/specification/#71-illustrative-transaction-flow" rel="noopener noreferrer"&gt;official documentation Illustrative Transaction Flow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Looking at httpx debug logs, you can see that Cart Mandate was indeed sent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTTP_RESPONSE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sun, 02 Nov 2025 11:11:45 GMT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvicorn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content-length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10892"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b9db77f4-a7bb-4ca3-bebb-286a29aea4af"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sender"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:merchant_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"recipient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-02T11:14:19.459063Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"nonce"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"d1fb8412e81e1b61a600b72214b114e27f2939169bb6a9650e4dd83093a893f8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"proof"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"algorithm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ed25519"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"signatureValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AHTY0v7VQVALn2H8gsnbRyEot0on4QIcRDBIpDeJeBHm13WpnVauVToqyTey+C6p0/syMBq5y0y/UC8Zotj1Ag=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"publicKeyMultibase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"z6Mko44YAnz8G71TocDDKoBqg86BJTxnqLN86UvGcpjxP47t"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:merchant_agent#key-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-02T11:14:19.459063Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"proofPurpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"authentication"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dataPart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ap2.responses.CartCandidates"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"63ff3cf2-b28c-4a0b-9458-9fa7110cf77c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"intent_mandate_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"intent_21dfc414"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"cart_candidates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"artifactId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"artifact_b66852e4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Best Value Plan (4,500 yen)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"parts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"ap2.mandates.CartMandate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"contents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cart_2d6be3f2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"user_cart_confirmation_required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"payment_request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"method_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"supported_methods"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"basic-card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"supportedNetworks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="s2"&gt;"visa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="s2"&gt;"mastercard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="s2"&gt;"jcb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="s2"&gt;"amex"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"supportedTypes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="s2"&gt;"credit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="s2"&gt;"debit"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"supported_methods"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://a2a-protocol.org/payment-methods/ap2-payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"processor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:payment_processor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"supportedMethods"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="s2"&gt;"credential-based"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="s2"&gt;"attestation-based"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cart_2d6be3f2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"display_items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Acrylic Keychain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;800.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"refund_period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2592000&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Pouch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;950.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"refund_period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2592000&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Socks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;850.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"refund_period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2592000&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Mug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1200.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"refund_period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2592000&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Consumption Tax (10%)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;380.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"refund_period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Shipping"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;500.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"refund_period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4680.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"request_payer_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"request_payer_email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"request_payer_phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"request_shipping"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"shipping_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shipping"&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"shipping_address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"postal_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"111-2222"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"recipient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Taro Yamada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Toshima-ku"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tokyo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"address_line1"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1-1-1 Kitaotsuka"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Japan"&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"cart_expiry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-02T12:14:19.255994Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"merchant_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Shop"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"merchant_authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJhbGx......"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"_metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"intent_mandate_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"intent_21dfc414"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"merchant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:merchant:mugibo_merchant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-02T11:14:19.256092Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"cart_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Best Value Plan (4,500 yen)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"cart_description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A within-budget plan packed with maximum cute items. Total 4,500 yen for 4 items: keychain, pouch, socks, and mug."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="nl"&gt;"raw_items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"286acdd4-d1c1-4860-b06a-f87d2f916a8d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Acrylic Keychain"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A cute Mugibo acrylic keychain. Attach it to bags and pouches."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"unit_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;800.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"total_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;800.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2faf8370-ada4-45d4-812c-ef5818d526b5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Pouch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A cute pouch with Mugibo pattern. Use as a small items holder or pen case."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"unit_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;950.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"total_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;950.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c688d6ef-615f-43f7-87ce-05568ae4e63c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Socks"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cute socks with a Mugibo accent. Soft and comfortable to wear."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"unit_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;850.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"total_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;850.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"product_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1d9f08d9-51a9-491e-810b-f0e225ef4f59"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Mug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A cute mug with Mugibo print. Makes your daily tea time enjoyable."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"unit_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1200.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"total_price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1200.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                          &lt;/span&gt;&lt;span class="nl"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"artifactId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"artifact_3ec2cecd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"High Quality Plan (5,300 yen)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"parts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"artifactId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"artifact_4ea2a38f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Simple Plan (3,500 yen)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"parts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"merchant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:merchant:mugibo_merchant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"merchant_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mugibo Shop"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"artifact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Back to SA for Continued Processing
&lt;/h2&gt;

&lt;p&gt;After receiving the &lt;strong&gt;Cart Mandate&lt;/strong&gt; from &lt;strong&gt;MA&lt;/strong&gt;, now the cart is finalized on the SA and user side, proceeding to payment processing.&lt;/p&gt;

&lt;p&gt;To guarantee that the A2A Message was definitely sent from MA, signature verification from the proof structure is performed. (The method is exactly the same as explained before, so I'll skip the details.)&lt;/p&gt;

&lt;p&gt;Once the A2A Message is successfully received, processing resumes from the next LangGraph node (select_cart).&lt;/p&gt;

&lt;h3&gt;
  
  
  Cart Selection (select_cart)
&lt;/h3&gt;

&lt;p&gt;We've entered the node where the user selects a cart.&lt;/p&gt;

&lt;p&gt;First, we verify the &lt;strong&gt;shop side's&lt;/strong&gt; (Merchant's) &lt;strong&gt;JWT&lt;/strong&gt;. This confirms it's a &lt;strong&gt;shopping cart that the shop has definitely approved&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_shopping_agent         | [2025-11-02 22:41:15,996] INFO in services.shopping_agent.langgraph_shopping_flow: [select_cart_node] Merchant authorization JWT verified: merchant=did:ap2:merchant:mugibo_merchant, cart_hash=0049ff8c19257bed...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The select_cart node has the user &lt;strong&gt;select a cart&lt;/strong&gt; from the sent &lt;strong&gt;Cart Mandate&lt;/strong&gt; candidates (Cart Candidates).&lt;/p&gt;

&lt;p&gt;Shopping carts are displayed in a carousel format on the frontend.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flswqyyuz637zc267f64u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flswqyyuz637zc267f64u.png" alt="aa" width="800" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the user selects a cart, we proceed to the next node.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cart Signature (submit_signature) But Actually Not Signing
&lt;/h3&gt;

&lt;p&gt;Now we proceed to &lt;strong&gt;Cart Mandate selection&lt;/strong&gt; and ~signature~ &lt;strong&gt;confirmation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Since Cart Mandate's &lt;code&gt;user_cart_confirmation_required&lt;/code&gt; was &lt;code&gt;true&lt;/code&gt;, user confirmation is required.&lt;/p&gt;

&lt;p&gt;A characteristic of AP2 is that this &lt;strong&gt;confirmation&lt;/strong&gt; work uses a &lt;strong&gt;Trusted Device Surface&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  What is Trusted Device Surface?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Trusted Device Surface&lt;/strong&gt; literally translates to &lt;strong&gt;trusted device surface&lt;/strong&gt;, or more understandably, a &lt;strong&gt;device surface where a human is definitely operating&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'll admit I'm not well-versed in this area, so I'm &lt;strong&gt;afraid my terminology might be imprecise&lt;/strong&gt;, but it refers to a device surface connecting &lt;strong&gt;trusted key material&lt;/strong&gt; (private key within TPM/Secure Enclave) with &lt;strong&gt;human operation&lt;/strong&gt; (&lt;strong&gt;biometric authentication, etc.&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;In this case, we implement &lt;strong&gt;Trusted Device Surface&lt;/strong&gt; using passkeys registered between CP and user.&lt;/p&gt;

&lt;p&gt;(With passkeys, biometrics are also involved... But I'm not really sure if this is right... I need to properly study this area...)&lt;/p&gt;

&lt;h4&gt;
  
  
  User Doesn't Sign the Cart
&lt;/h4&gt;

&lt;p&gt;I didn't realize this until I implemented it, but Step 20 of &lt;a href="https://ap2-protocol.org/specification/#core-principles" rel="noopener noreferrer"&gt;7.1 Illustrative Transaction Flow&lt;/a&gt; says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Redirect to trusted device surface { PaymentMandate, CartMandate }&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I thought this meant adding user signature to Cart Mandate too, but this was incorrect.&lt;/p&gt;

&lt;p&gt;First, &lt;a href="https://github.com/google-agentic-commerce/AP2/blob/f404a8ddf2e2a5ddf76a6f3fd990b4cb78a71200/src/ap2/types/mandate.py#L107" rel="noopener noreferrer"&gt;the official GitHub Cart Mandate definition&lt;/a&gt; has a &lt;code&gt;merchant_authorization&lt;/code&gt; field but no &lt;code&gt;user_authorization&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;Also, if you add a user signature afterward to the &lt;strong&gt;JWT that the shop side&lt;/strong&gt; (Merchant) &lt;strong&gt;signed&lt;/strong&gt;, the JSON structure changes, naturally changing the hash value and invalidating the shop's signature.&lt;/p&gt;

&lt;p&gt;This is obvious when you think about it, but you can't notice these things just reading the sequence, so I really felt &lt;strong&gt;implementation is what counts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;confirmed&lt;/strong&gt; confirmation using user's &lt;strong&gt;Trusted Device Surface&lt;/strong&gt; is still required for Cart Mandate, so passkey authentication is required during cart selection, and completing authentication shows SA that the user has definitely confirmed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c63kgdm2r7xg7n2zybm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c63kgdm2r7xg7n2zybm.png" alt="aa" width="783" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(The screenshot says "cart signature" but it should correctly be "cart confirmation." I apologize for the correction.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Passkey (WebAuthn) Authentication is Verified by CP
&lt;/h4&gt;

&lt;p&gt;Passkey authentication is performed by CP, not SA.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffva07thngb8bu2zgl7xg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffva07thngb8bu2zgl7xg.png" alt="aa" width="403" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This flow is quite complex just following logs, so let's turn the behavior of SA's &lt;code&gt;/cart/submit-signature&lt;/code&gt; endpoint directly into a sequence diagram. (This is more about implementing AI agents with AP2 rather than AP2 implementation itself, so you can skip if not needed.)&lt;/p&gt;

&lt;p&gt;![&lt;a href="https://i.imgur.com/NRw8CwW.png" rel="noopener noreferrer"&gt;https://i.imgur.com/NRw8CwW.png&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;After cart selection, this demo app sends a &lt;strong&gt;special&lt;/strong&gt; &lt;a href="https://developer.mozilla.org/en/docs/Web/API/Server-sent_events/Using_server-sent_events" rel="noopener noreferrer"&gt;SSE event&lt;/a&gt; &lt;code&gt;signature_request&lt;/code&gt; from SA.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8r875iho9d49lttsgu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8r875iho9d49lttsgu5.png" alt="img" width="783" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After having the user confirm the &lt;strong&gt;Cart Mandate&lt;/strong&gt; via popup, pressing the &lt;strong&gt;Authenticate with Passkey&lt;/strong&gt; button requests &lt;a href="https://developer.mozilla.org/en/docs/Web/API/Web_Authentication_API" rel="noopener noreferrer"&gt;WebAuthn&lt;/a&gt; from the browser via frontend.&lt;/p&gt;

&lt;p&gt;Then, the &lt;strong&gt;Authenticate with Passkey&lt;/strong&gt; popup appears (here from 1Password).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuf54581i3f0ohcqmwfe6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuf54581i3f0ohcqmwfe6.png" alt="g" width="403" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When communication reaches CP from SA and passkey &lt;strong&gt;authentication completes&lt;/strong&gt;, you receive the authentication result along with the following &lt;strong&gt;WebAuthn attestation&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dCDQdGlqx50G-UFWp0ORsF5Y7mzWSAkYow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rawId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dCDQdGlqx50G-UFWp0ORsF5Y7mzWSAkYow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"authenticatorData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"clientDataJSON"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"userHandle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_cdb4ec851bcf4f73"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public-key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attestation_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"passkey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"challenge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxx"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the authentication result is OK, we can proceed to the next step (Payment Mandate creation). Also, the received &lt;strong&gt;WebAuthn attestation&lt;/strong&gt; is used when creating the Payment Mandate.&lt;/p&gt;

&lt;p&gt;This is just performing part of Step 20 (Cart Mandate) from &lt;a href="https://ap2-protocol.org/specification/#71-illustrative-transaction-flow" rel="noopener noreferrer"&gt;official documentation Illustrative Transaction Flow&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Payment Method Selection (payment_method_select)
&lt;/h3&gt;

&lt;p&gt;The order differs from &lt;a href="https://ap2-protocol.org/specification/#71-illustrative-transaction-flow" rel="noopener noreferrer"&gt;official documentation Illustrative Transaction Flow&lt;/a&gt;, but we finalize the payment method retrieved from CP. This screen is the Step 16 behavior.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favsd6ko2lw5qyblhblzk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favsd6ko2lw5qyblhblzk.png" alt="aa" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the &lt;strong&gt;payment method is completely finalized&lt;/strong&gt; (using VISA card this time).&lt;/p&gt;

&lt;h3&gt;
  
  
  Payment Method Tokenization
&lt;/h3&gt;

&lt;p&gt;Since the payment method is finalized, we request &lt;strong&gt;payment token issuance&lt;/strong&gt; from CP for the finalized payment method. This corresponds to Step 17 of &lt;a href="https://ap2-protocol.org/specification/#71-illustrative-transaction-flow" rel="noopener noreferrer"&gt;official documentation Illustrative Transaction Flow&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTTP_REQUEST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://credential_provider:8003/payment-methods/tokenize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_cdb4ec851bcf4f73"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"payment_method_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pm_f4745ec2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;payment token&lt;/strong&gt; linked to the payment method is returned from CP. This token is written to Payment Mandate, and subsequent payment processing proceeds using this token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTTP_RESPONSE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sun, 02 Nov 2025 23:12:26 GMT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvicorn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content-length"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"176"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tok_6c23798f_zfkGVOEF586Lxj9YllxtCFsH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"payment_method_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pm_f4745ec2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Visa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"last4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1111"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"basic-card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-02T23:27:26.645569Z"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;36.832332611083984&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the way, the &lt;code&gt;basic-card&lt;/code&gt; payment type was defined in &lt;a href="https://www.w3.org/TR/2017/WD-payment-method-basic-card-20170721/" rel="noopener noreferrer"&gt;W3C PaymentRequest API&lt;/a&gt; but is already slated for deprecation. For AP2, &lt;a href="https://a2a-protocol.org/payment-methods/ap2-payment" rel="noopener noreferrer"&gt;https://a2a-protocol.org/payment-methods/ap2-payment&lt;/a&gt; seems better. (Though I don't understand the specification yet...)&lt;/p&gt;

&lt;p&gt;The link between payment token and actual payment information is managed in &lt;strong&gt;Redis KV&lt;/strong&gt;. The demo app sets a &lt;strong&gt;15-minute TTL&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Payment Mandate Creation &amp;amp; User Signature Creation
&lt;/h3&gt;

&lt;p&gt;We're almost at the finale! Great work so far...! We create the &lt;strong&gt;Payment Mandate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The final &lt;strong&gt;Payment Mandate&lt;/strong&gt; looks like this. Let's look at each field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HTTP_REQUEST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://merchant_agent:8001/a2a/message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3bde0bf1-d43a-48c0-bdaa-2381440cc126"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sender"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"recipient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:merchant_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-03T04:33:37.122880Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"nonce"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6cd7d5cb64dc0dd79162d1498167400c673ab95991fda75c60253bed2a76fa26"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"proof"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"algorithm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ed25519"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"signatureValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b1AxcN7qA41tPkwbD0DJWxoXXTzb8BBy3rDdynepyw2mefNLI2yqYsYJE+/xmdEXXSIMKL1UIYhAbO5/4MFACw=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"publicKeyMultibase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"z6MkpL5YFLHxAcp6LSJboXQ3nBnNGrQ4TiZRmWBZamPo7t8x"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:shopping_agent#key-2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-03T04:33:37.122880Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"proofPurpose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"authentication"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dataPart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"@type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ap2.mandates.PaymentMandate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payment_8229592a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"payment_mandate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"payment_mandate_contents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"payment_mandate_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payment_8229592a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"payment_details_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order_87b47327"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"payment_details_total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2315.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"payment_response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"methodName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://a2a-protocol.org/payment-methods/ap2-payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"cardBrand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Visa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tok_b75118d6_puyZ-5Oq9MT8m0Abhx3ux__w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"tokenized"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"merchant_agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:merchant:mugibo_merchant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-11-03T04:33:33.924986Z"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"user_authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJpc3N1ZXJfand0Ijxxxx........."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payment_8229592a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"cart_mandate_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cart_fddcfe5d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"intent_mandate_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"intent_88ed322e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"payer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_cdb4ec851bcf4f73"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"payee_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:merchant:mugibo_merchant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2315.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JPY"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"payment_method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"basic-card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tok_b75118d6_puyZ-5Oq9MT8m0Abhx3ux__w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"last4"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1111"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Visa"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"risk_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"fraud_indicators"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"risk_assessment_failed"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"artifact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  proof Structure
&lt;/h4&gt;

&lt;p&gt;You're probably familiar with this flow by now, but since &lt;strong&gt;Payment Mandate&lt;/strong&gt; is created by SA, like &lt;strong&gt;Intent Mandate&lt;/strong&gt;, &lt;strong&gt;SA's signature&lt;/strong&gt; is added in proof structure to send the A2A Message to MA. Since this has been explained before, I'll skip the details.&lt;/p&gt;

&lt;h4&gt;
  
  
  Total Amount (payment_details_total)
&lt;/h4&gt;

&lt;p&gt;A characteristic of Payment Mandate is that since it's a payment delegation document, the focus should be on the &lt;strong&gt;total amount to pay&lt;/strong&gt;. The philosophy is that cart details reference Cart Mandate.&lt;/p&gt;

&lt;p&gt;Therefore, &lt;code&gt;payment_details_total&lt;/code&gt; contains only the &lt;strong&gt;total amount&lt;/strong&gt;, not cart details.&lt;/p&gt;

&lt;h4&gt;
  
  
  Payment Information (payment_response)
&lt;/h4&gt;

&lt;p&gt;Payment information sets the &lt;strong&gt;temporary payment token&lt;/strong&gt; obtained from Payment Method Tokenization in &lt;code&gt;payment_response&lt;/code&gt;. This allows payment without actual card information flowing to entities involved in payment processing (SA, MA, MPP).&lt;/p&gt;

&lt;h4&gt;
  
  
  User Signature (user_authorization)
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;User signature is required for Payment Mandate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I struggled considerably to research and implement how to create this. The thing is, for &lt;strong&gt;shop side&lt;/strong&gt; (Merchant), I could imagine &lt;strong&gt;creating a JWT using pre-prepared public/private keys&lt;/strong&gt;, but what are the user's public/private keys...&lt;/p&gt;

&lt;p&gt;I couldn't figure this out and struggled greatly, but after various research, I learned that user's public/private key pair seems to use &lt;strong&gt;keys&lt;/strong&gt; safely managed at the browser or OS level using WebAuthn (passkeys). (Probably... Please correct me if wrong.)&lt;/p&gt;

&lt;p&gt;So please understand that in this demo, the user's public/private keys are realized using WebAuthn implementation.&lt;/p&gt;

&lt;h4&gt;
  
  
  WebAuthn Assertion
&lt;/h4&gt;

&lt;p&gt;Let's briefly review WebAuthn (passkeys).&lt;/p&gt;

&lt;p&gt;WebAuthn (passkeys) key pairs are &lt;strong&gt;generated on the user's device&lt;/strong&gt;, and the &lt;strong&gt;private key&lt;/strong&gt; never leaves the device's &lt;strong&gt;secure area&lt;/strong&gt; (TPM, Secure Enclave, etc.).&lt;/p&gt;

&lt;p&gt;This happens during passkey registration. Also, the &lt;strong&gt;public key&lt;/strong&gt; is registered with CP in AP2, allowing other entities in the AP2 network to use it when &lt;strong&gt;verifying user signatures&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, if we follow these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sign through WebAuthn using the private key in the device&lt;/li&gt;
&lt;li&gt;Obtain the public key via CP and verify&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we should be able to implement user signature.&lt;/p&gt;

&lt;p&gt;Here's a sequence created from demo app logs implementing this approach. Let's go through it step by step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F78jcq1lz0w8isylsd55d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F78jcq1lz0w8isylsd55d.png" alt="aaa" width="800" height="671"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Get Passkey Public Key from CP
&lt;/h4&gt;

&lt;p&gt;When SA requests the passkey public key from CP, it returns the &lt;strong&gt;public key&lt;/strong&gt; in &lt;a href="https://www.rfc-editor.org/rfc/rfc9052.html" rel="noopener noreferrer"&gt;COSE (CBOR Object Signing and Encryption)&lt;/a&gt; format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_credential_provider    | {"timestamp": "2025-11-03T07:54:36.506247Z", "level": "INFO", "logger": "services.credential_provider.provider", "message": "[get_passkey_public_key] Public key retrieved: credential_id=dCDQdGlqx50G-UFW..., user_id=usr_cdb4ec851bcf4f73", "module": "provider", "function": "get_passkey_public_key", "line": 1536}
ap2_shopping_agent         | {"timestamp": "2025-11-03T07:54:36.507721Z", "level": "DEBUG", "logger": "services.shopping_agent.agent", "message": "HTTP_RESPONSE_RAW: {\"type\": \"HTTP_RESPONSE\", \"status_code\": 200, \"headers\": {\"date\": \"Mon, 03 Nov 2025 07:54:36 GMT\", \"server\": \"uvicorn\", \"content-length\": \"212\", \"content-type\": \"application/json\"}, \"body\": {\"credential_id\": \"dCDQdGlqx50G-UFWp0ORsF5Y7mzWSAkYow\", \"public_key_cose\": \"pQECAyYgASFYIOnePT967mopshGl7tTo53MmMkE/bY6/WZuuZLHSWZYrIlggJj7UcPfh0MaQpNxA5bgmtZPTWi8YVP4x4C8ivq8RFDQ=\", \"user_id\": \"usr_cdb4ec851bcf4f73\"}, \"duration_ms\": 3.767251968383789}", "module": "logger", "function": "log_http_response", "line": 226}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I might expose my ignorance explaining &lt;strong&gt;COSE&lt;/strong&gt;, but I understand it as a structure containing &lt;strong&gt;elliptic curve cryptography&lt;/strong&gt; (&lt;strong&gt;P-256&lt;/strong&gt;) public key X, Y coordinates converted to &lt;strong&gt;binary&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Converting to binary allows for lightweight handling.&lt;/p&gt;

&lt;h4&gt;
  
  
  User Authorization VP Creation
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;User Authorization VP&lt;/strong&gt; (User Authorization Verifiable Presentation) cryptographically proves, &lt;strong&gt;with signature&lt;/strong&gt;, that the user authorized &lt;strong&gt;that transaction&lt;/strong&gt; (Cart Mandate + Payment Mandate).&lt;/p&gt;

&lt;p&gt;The flow is quite complex &amp;amp; I'm not an expert, so bear with my explanation.&lt;/p&gt;

&lt;h5&gt;
  
  
  Get webauthn_challenge
&lt;/h5&gt;

&lt;p&gt;First, authenticate with passkey (assertion), and from the resulting &lt;code&gt;clientDataJSON&lt;/code&gt;, get the &lt;strong&gt;challenge&lt;/strong&gt; (webauthn_challenge).&lt;/p&gt;

&lt;p&gt;A challenge is a &lt;strong&gt;temporary random value&lt;/strong&gt; generated by the server during authentication request, used to prevent replay attacks.&lt;/p&gt;

&lt;p&gt;By performing verification &lt;strong&gt;including this value&lt;/strong&gt;, we can confirm that the user actually &lt;strong&gt;signed in response to the server's request&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_shopping_agent         | [2025-11-03 07:54:36,508] INFO in common.user_authorization: [create_user_authorization_vp] WebAuthn challenge from assertion: eyJtYW5kYXRlX2lk...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Calculate Mandate Hash
&lt;/h5&gt;

&lt;p&gt;Next, normalize the Mandate with &lt;strong&gt;RFC 8785&lt;/strong&gt; (&lt;strong&gt;JSON Canonicalization Scheme&lt;/strong&gt;) and hash with &lt;strong&gt;SHA-256&lt;/strong&gt;. This lets us check if the Mandate was tampered with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_shopping_agent         | [2025-11-03 07:54:36,509] INFO in common.user_authorization: [create_user_authorization_vp] cart_hash: 1b6d38d8ef86cf9f...
ap2_shopping_agent         | [2025-11-03 07:54:36,509] INFO in common.user_authorization: [create_user_authorization_vp] payment_hash: 806da3986f122c64...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Restore COSE Format Public Key
&lt;/h5&gt;

&lt;p&gt;As mentioned earlier, the &lt;strong&gt;COSE&lt;/strong&gt; (CBOR Object Signing and Encryption) format public key is returned from &lt;strong&gt;CP&lt;/strong&gt;, which is hard to use as &lt;strong&gt;JWK&lt;/strong&gt; (JSON Web Key), so we restore it to a usable form for subsequent processing.&lt;/p&gt;

&lt;p&gt;(The internal processing is too complex, so I'll skip it. See the &lt;a href="https://github.com/tubone24/AP2_demo_app/blob/fae9951fe201ee30494a6bae3b6bff7ccfd993bb/common/user_authorization.py#L164" rel="noopener noreferrer"&gt;code&lt;/a&gt; for details.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_shopping_agent         | [2025-11-03 07:54:36,511] INFO in common.user_authorization: [create_user_authorization_vp] Restored public key from COSE format (DB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Generate Issuer JWT and Include cnf claim
&lt;/h5&gt;

&lt;p&gt;With user as Issuer, generate a signed (&lt;strong&gt;actually not signed&lt;/strong&gt;) JWT, and embed the user's &lt;strong&gt;public key&lt;/strong&gt; from passkey in &lt;strong&gt;JWK&lt;/strong&gt; (JSON Web Key) format in the &lt;strong&gt;cnf claim&lt;/strong&gt; (&lt;strong&gt;Confirmation claim&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;This allows &lt;strong&gt;MPP&lt;/strong&gt; (shop's payment process) etc. to verify that &lt;strong&gt;this user definitely signed the JWT&lt;/strong&gt;—the &lt;strong&gt;key relationship&lt;/strong&gt; (Key Binding) is made explicit when combined with the &lt;strong&gt;Key-binding JWT&lt;/strong&gt; created later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_shopping_agent         | [2025-11-03 07:54:36,511] INFO in common.user_authorization: [create_user_authorization_vp] cnf claim with JWK added to Issuer JWT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JWT payload with &lt;strong&gt;cnf claim&lt;/strong&gt; looks like this:&lt;/p&gt;

&lt;p&gt;(iss and sub are the user's &lt;strong&gt;DID&lt;/strong&gt;. The DID is generated from this demo app's user ID, but there probably needs to be &lt;strong&gt;uniqueness measures&lt;/strong&gt; in the AP2 world.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:user:usr_cdb4ec851bcf4f73"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:user:usr_cdb4ec851bcf4f73"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1762210741&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1762211041&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nbf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1762210741&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cnf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jwk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"crv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P-256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxxxxxxxxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxxxxxxxxxxx"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, this is quite detailed, but earlier I wrote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With user as Issuer, generate a signed (actually not signed) JWT&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There's nuance here—we &lt;strong&gt;don't actually sign with Issuer's public key&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;You might think that's a lie, but there's a reason. &lt;strong&gt;WebAuthn API generates signatures only for specific challenges&lt;/strong&gt;, so signing &lt;strong&gt;Issuer JWT&lt;/strong&gt; is &lt;strong&gt;impossible&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you try to sign &lt;strong&gt;Issuer JWT&lt;/strong&gt;, you'd need signatures using &lt;strong&gt;keys generated outside WebAuthn&lt;/strong&gt;, but that wouldn't be a &lt;strong&gt;user experience that completes with just passkeys&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, as a compromise, we &lt;strong&gt;don't add a signature to Issuer JWT&lt;/strong&gt;, and instead solve it by including &lt;code&gt;sd_hash&lt;/code&gt; (&lt;strong&gt;Issuer JWT hash&lt;/strong&gt;) in the &lt;strong&gt;Key-binding JWT&lt;/strong&gt; explained next.&lt;/p&gt;

&lt;p&gt;Therefore, the Issuer JWT header explicitly states &lt;strong&gt;no signature&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;issuer_jwt_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# JWT standard compliant (RFC 7519): Explicitly indicates no signature
&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;typ&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JWT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Generate Key-binding JWT
&lt;/h5&gt;

&lt;p&gt;Next, generate a JWT containing Mandate hash &lt;strong&gt;transaction data&lt;/strong&gt;. This allows expressing that the user has &lt;strong&gt;definitely confirmed and approved&lt;/strong&gt; the transaction involving &lt;strong&gt;Cart Mandate&lt;/strong&gt; and &lt;strong&gt;Payment Mandate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Including &lt;strong&gt;Cart Mandate&lt;/strong&gt; and &lt;strong&gt;Payment Mandate&lt;/strong&gt; hashes in &lt;code&gt;transaction_data&lt;/code&gt; expresses that the user &lt;strong&gt;approved all these transactions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Also, including assertion in the &lt;code&gt;webauthn&lt;/code&gt; field allows &lt;strong&gt;verification just by looking at this Key-binding JWT&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;(I don't know if this format is correct according to specification. Expert help... please.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:payment_processor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nonce"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rH4AgxNBXcnxNlcPXjoP2nLGynP8dmJhdSF96Cngz9w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1762210741&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sd_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transaction_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"xxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"xxxxx"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"webauthn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"credential_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dCDQdGlqx50G-UFWp0ORsF5Y7mzWSAkYow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"authenticator_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"client_data_json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user_handle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_cdb4ec851bcf4f73"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Treat WebAuthn Signature as KB-JWT Signature (Signing)
&lt;/h5&gt;

&lt;p&gt;Now that we have &lt;strong&gt;Issuer JWT&lt;/strong&gt; and &lt;strong&gt;Key-binding JWT&lt;/strong&gt;, we sign the &lt;strong&gt;Key-binding JWT&lt;/strong&gt;. The key point is &lt;strong&gt;performing WebAuthn signature&lt;/strong&gt;. Pass the &lt;strong&gt;Key-binding JWT&lt;/strong&gt; in &lt;strong&gt;Base64url format&lt;/strong&gt; to the passkey &lt;strong&gt;authenticator&lt;/strong&gt; and receive &lt;strong&gt;Signature&lt;/strong&gt; from the authenticator. This is treated as the Key-binding JWT's &lt;strong&gt;signature&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_shopping_agent         | [2025-11-03 22:32:19,412] INFO in common.user_authorization: [create_user_authorization_vp] Generated SD-JWT+KB user_authorization (IETF standard): length=1571, cart_hash=396e1f1ea5518055..., payment_hash=a07655c1e051df12...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, like in the demo app, it's good to show users a popup requesting &lt;strong&gt;Payment Mandate&lt;/strong&gt; signature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foe32rs963rtf9cu6uvst.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foe32rs963rtf9cu6uvst.png" alt="aa" width="523" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Assemble SD-JWT+KB (issuer_jwt~kb_jwt)
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt" rel="noopener noreferrer"&gt;This differs from the SD-JWT+KB standard specification&lt;/a&gt;. I don't know if this implementation is correct, but given the constraints, this is where I ended up... &lt;strong&gt;Implementation is proceeding with completely my own interpretation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now that we have 2 JWTs, we assemble them in &lt;strong&gt;SD-JWT&lt;/strong&gt; (Selective Disclosure JWT) and &lt;strong&gt;KB&lt;/strong&gt; format.&lt;/p&gt;

&lt;p&gt;SD-JWT is &lt;strong&gt;technology for selectively disclosing only parts of JWT&lt;/strong&gt;, but here we attach KB as &lt;strong&gt;Disclosure&lt;/strong&gt; and sign.&lt;/p&gt;

&lt;p&gt;(I don't know if this format is correct from AP2 documentation, but probably... okay... I don't know...)&lt;/p&gt;

&lt;p&gt;Complete! The generated output is Base64 encoded, so to show the User Authorization VP with Base64 decoded for clarity:&lt;/p&gt;

&lt;h6&gt;
  
  
  SD-JWT Header
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"typ"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JWT"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  SD-JWT Body
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:user:usr_cdb4ec851bcf4f73"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:user:usr_cdb4ec851bcf4f73"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1762214326&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1762214626&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nbf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1762214326&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cnf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jwk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"crv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"P-256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxx"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  KB Header
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"typ"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kb+jwt"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  KB Body
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"did:ap2:agent:payment_processor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nonce"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rmPX74BFR6rTBG4Pdu2ec4r9g9BjjkEv_w2l3FAKqh4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1762214326&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sd_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"26e0a50c27ea3ffe1af0d221c969503c212f75c1530ba81c4a6dd8fad76ed0ad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transaction_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"92b50fc46c06554f2eb3786f71917076a8b3a4abf8fdb1c455c0c33f08731408"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"c66727e7d7bbe76654a860e1dcffb09f0c1d32d2065b2efcb54142dc9d92c3ce"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"webauthn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"credential_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dCDQdGlqx50G-UFWp0ORsF5Y7mzWSAkYow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"authenticator_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"client_data_json"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user_handle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"usr_cdb4ec851bcf4f73"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've successfully created the &lt;strong&gt;User Authorization VP&lt;/strong&gt;. Great work!&lt;/p&gt;

&lt;h3&gt;
  
  
  Risk Assessment
&lt;/h3&gt;

&lt;p&gt;The demo app has a simplified implementation, but when &lt;strong&gt;SA&lt;/strong&gt; creates &lt;strong&gt;Payment Mandate&lt;/strong&gt;, it needs to &lt;strong&gt;correctly convey the transaction circumstances&lt;/strong&gt; to entities involved in payment, so SA performs &lt;strong&gt;risk assessment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, it determines as a risk score whether the transaction falls within the &lt;strong&gt;amount range&lt;/strong&gt; specified in user's Intent, whether the purchase is proceeding with the specified &lt;strong&gt;brand&lt;/strong&gt;, whether there are issues with the specified card brand, etc.&lt;/p&gt;

&lt;p&gt;However, since this area has unclear specifications, more research is needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Payment Mandate Transmission
&lt;/h3&gt;

&lt;p&gt;Now we send the &lt;strong&gt;Payment Mandate&lt;/strong&gt; to MA. The payload is what I posted earlier.&lt;/p&gt;

&lt;p&gt;We add proof structure etc. to guarantee that it was &lt;strong&gt;sent from SA&lt;/strong&gt;, and add &lt;strong&gt;User Authorization VP&lt;/strong&gt; to guarantee the user approved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finally Payment Processing! (execute_payment)
&lt;/h2&gt;

&lt;p&gt;Finally, payment processing. It's been so long... Is anyone still reading...?&lt;/p&gt;

&lt;p&gt;It's done in LangGraph's execute_payment, but SA is mostly waiting after sending the request.&lt;/p&gt;

&lt;p&gt;Most actual processing is performed by &lt;strong&gt;MPP&lt;/strong&gt; (Merchant Payment Processor).&lt;/p&gt;

&lt;h3&gt;
  
  
  MPP Handles Payment Processing
&lt;/h3&gt;

&lt;p&gt;Payment-related processing is handled by MPP, not MA. This is because AP2 adopts a &lt;strong&gt;role-based architecture&lt;/strong&gt;, and MPP is solely responsible for payment-related matters.&lt;/p&gt;

&lt;p&gt;However, since SA can't see MPP, SA first sends &lt;strong&gt;Payment Mandate&lt;/strong&gt; to MA, and &lt;strong&gt;MA passes it through to MPP as-is&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;(Verifying SA's signature is done by MA. After that, MA adds its own signature to the A2A message again and sends it to MPP.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Proof &amp;amp; User Authorization VP Verification
&lt;/h3&gt;

&lt;p&gt;MPP first &lt;strong&gt;validates the Payment Mandate&lt;/strong&gt;. This time, in 2 stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proof structure verification proving it was sent from MA&lt;/li&gt;
&lt;li&gt;User Authorization VP verification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Proof structure verification proving it was sent from MA is the same process done elsewhere, so I'll skip the explanation.&lt;/p&gt;

&lt;p&gt;User Authorization VP verification parses SD-JWT+KB, then verifies whether &lt;strong&gt;Cart Mandate&lt;/strong&gt; and &lt;strong&gt;Payment Mandate&lt;/strong&gt; hashes are correct, whether KB signed with WebAuthn is valid, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_payment_processor      | [2025-11-03 22:32:19,725] INFO in services.payment_processor.utils.mandate_helpers: [PaymentProcessor] PaymentMandate validation passed: payment_556c011e, user_authorization present: eyJhbGciOiJFUzI1NiIs...
ap2_payment_processor      | {"timestamp": "2025-11-03T22:32:19.726152Z", "level": "INFO", "logger": "services.payment_processor.processor", "message": "[PaymentProcessor] Mandate chain validation: PaymentMandate(payment_556c011e) → CartMandate(cart_6f9f1032)", "module": "processor", "function": "_validate_mandate_chain", "line": 671}
ap2_payment_processor      | {"timestamp": "2025-11-03T22:32:19.728043Z", "level": "INFO", "logger": "services.payment_processor.processor", "message": "[PaymentProcessor] Verifying SD-JWT-VC user_authorization: cart_hash=396e1f1ea5518055..., payment_hash=a07655c1e051df12...", "module": "processor", "function": "_validate_mandate_chain", "line": 688}
ap2_payment_processor      | [2025-11-03 22:32:19,728] INFO in common.user_authorization: [verify_user_authorization_vp] Parsed SD-JWT+KB format successfully
ap2_payment_processor      | [2025-11-03 22:32:19,728] INFO in common.user_authorization: [verify_user_authorization_vp] Hash verification passed: cart_hash=396e1f1ea5518055..., payment_hash=a07655c1e051df12...
ap2_payment_processor      | [2025-11-03 22:32:19,734] INFO in common.user_authorization: [verify_user_authorization_vp] ✓ WebAuthn signature verified successfully
ap2_payment_processor      | [2025-11-03 22:32:19,734] INFO in common.user_authorization: [verify_user_authorization_vp] Key-binding JWT payload verified
ap2_payment_processor      | [2025-11-03 22:32:19,734] INFO in common.user_authorization: [verify_user_authorization_vp] ✓ SD-JWT+KB verification passed (IETF standard)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  MPP Payment Processing
&lt;/h3&gt;

&lt;p&gt;Now we proceed with payment processing using &lt;strong&gt;Payment Mandate&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  MPP Merchant Signature Verification
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;merchant_authorization&lt;/code&gt; JWT signature attached to &lt;strong&gt;Cart Mandate&lt;/strong&gt; is also verified by &lt;strong&gt;MPP&lt;/strong&gt;, verifying that this transaction has the shop's &lt;strong&gt;definite approval&lt;/strong&gt;. After verifying &lt;code&gt;merchant_authorization&lt;/code&gt; JWT and comparing &lt;strong&gt;Cart Mandate&lt;/strong&gt; &lt;strong&gt;hash&lt;/strong&gt;, we confirmed that Cart Mandate was indeed &lt;strong&gt;signed&lt;/strong&gt; by the shop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_payment_processor      | {"timestamp": "2025-11-03T23:58:46.672231Z", "level": "INFO", "logger": "services.payment_processor.processor", "message": "[_verify_merchant_authorization_jwt] JWT validation passed: iss=did:ap2:merchant:mugibo_merchant, exp=1762217912, jti=b13c2752-9b28-44..., cart_hash=92b50fc46c06554f...", "module": "processor", "function": "_verify_merchant_authorization_jwt", "line": 613}
ap2_payment_processor      | {"timestamp": "2025-11-03T23:58:46.672387Z", "level": "INFO", "logger": "services.payment_processor.processor", "message": "[PaymentProcessor] merchant_authorization JWT verified: iss=did:ap2:merchant:mugibo_merchant", "module": "processor", "function": "_validate_mandate_chain", "line": 725}
ap2_payment_processor      | {"timestamp": "2025-11-03T23:58:46.672493Z", "level": "INFO", "logger": "services.payment_processor.processor", "message": "[PaymentProcessor] CartMandate hash in merchant_authorization: 92b50fc46c06554f...", "module": "processor", "function": "_validate_mandate_chain", "line": 733}
ap2_payment_processor      | {"timestamp": "2025-11-03T23:58:46.673139Z", "level": "INFO", "logger": "services.payment_processor.processor", "message": "[PaymentProcessor] ✓ CartMandate hash verified (merchant_authorization): 92b50fc46c06554f...", "module": "processor", "function": "_validate_mandate_chain", "line": 750}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verify Cart Mandate → Payment Mandate Chain is Correct
&lt;/h4&gt;

&lt;p&gt;Next, verify whether &lt;strong&gt;Payment Mandate&lt;/strong&gt; correctly &lt;strong&gt;references&lt;/strong&gt; &lt;strong&gt;Cart Mandate&lt;/strong&gt; and whether that chain is valid.&lt;/p&gt;

&lt;p&gt;Since Cart Mandate's ID is recorded in Payment Mandate, check that the &lt;strong&gt;IDs match&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Verify Payment Token Validity
&lt;/h4&gt;

&lt;p&gt;The payment method has already been &lt;strong&gt;tokenized by CP&lt;/strong&gt; in the previous step. We need to query CP to verify that token's &lt;strong&gt;validity&lt;/strong&gt; and &lt;strong&gt;ownership&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;CP accesses actual payment information from the token and confirms that &lt;code&gt;payer_id&lt;/code&gt; matches, &lt;strong&gt;hasn't expired&lt;/strong&gt;, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap2_payment_processor      | {"timestamp": "2025-11-03T23:58:46.679844Z", "level": "INFO", "logger": "services.payment_processor.processor", "message": "[PaymentProcessor] Verifying credential with Credential Provider: token=tok_835e197e_AZJUO7_...", "module": "processor", "function": "_verify_credential_with_cp", "line": 916}
ap2_payment_processor      | {"timestamp": "2025-11-03T23:58:46.682497Z", "level": "INFO", "logger": "services.payment_processor.processor", "message": "HTTP Request: POST http://credential_provider:8003/credentials/verify", "module": "logger", "function": "log_http_request", "line": 182}
ap2_payment_processor      | {"timestamp": "2025-11-03T23:58:46.682622Z", "level": "DEBUG", "logger": "services.payment_processor.processor", "message": "HTTP_REQUEST_RAW: {\"type\": \"HTTP_REQUEST\", \"method\": \"POST\", \"url\": \"http://credential_provider:8003/credentials/verify\", \"headers\": {}, \"body\": {\"token\": \"tok_835e197e_AZJUO7_Qat-15UWBHUHtlVG8\", \"payer_id\": \"usr_cdb4ec851bcf4f73\", \"amount\": {\"value\": 6215.0, \"currency\": \"JPY\"}}}", "module": "logger", "function": "log_http_request", "line": 193}
ap2_credential_provider    | {"timestamp": "2025-11-03T23:58:46.721823Z", "level": "INFO", "logger": "services.credential_provider.provider", "message": "[verify_credentials] Verifying token for payer: usr_cdb4ec851bcf4f73", "module": "provider", "function": "verify_credentials", "line": 1611}
ap2_credential_provider    | {"timestamp": "2025-11-03T23:58:46.727054Z", "level": "INFO", "logger": "services.credential_provider.provider", "message": "[verify_credentials] Token verified: payment_method_id=pm_f4745ec2, user_id=usr_cdb4ec851bcf4f73", "module": "provider", "function": "verify_credentials", "line": 1648}
ap2_credential_provider    | INFO:     172.18.0.5:40314 - "POST /credentials/verify HTTP/1.1" 200 OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Risk Assessment
&lt;/h4&gt;

&lt;p&gt;Check the &lt;strong&gt;risk assessment&lt;/strong&gt; &lt;strong&gt;score&lt;/strong&gt; SA performed, and confirm whether to proceed with payment or perform &lt;strong&gt;additional assessment&lt;/strong&gt;. I couldn't understand specific check items from documentation, but I assume check items vary based on AI agent involvement in payment and transaction modality.&lt;/p&gt;

&lt;h4&gt;
  
  
  Payment Processing
&lt;/h4&gt;

&lt;p&gt;In this demo, the payment network is just a stub so I'll skip details, but payment processing is performed against the payment network.&lt;/p&gt;

&lt;h4&gt;
  
  
  Receipt Generation and Notification to CP &amp;amp; MA
&lt;/h4&gt;

&lt;p&gt;Once payment completes successfully, MPP &lt;strong&gt;issues a receipt&lt;/strong&gt;. It also notifies CP of payment completion, transaction status, and receipt information.&lt;/p&gt;

&lt;p&gt;Furthermore, to notify the user as well, it notifies MA → SA → user.&lt;/p&gt;

&lt;h4&gt;
  
  
  All Processing Complete
&lt;/h4&gt;

&lt;p&gt;Present the receipt to the user and all processing is complete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzvf56ig82robl9aw9wk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzvf56ig82robl9aw9wk.png" alt="aa" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The receipt looks like this as a PDF.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6ldoxpiawylf8rdk532.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6ldoxpiawylf8rdk532.png" alt="aa" width="765" height="865"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;AP2 cleverly uses a mechanism called &lt;strong&gt;Mandate&lt;/strong&gt; to achieve a &lt;strong&gt;safe purchase experience with AI agents&lt;/strong&gt;. However, actually implementing it with reference to &lt;a href="https://ap2-protocol.org/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; and &lt;a href="https://github.com/google-agentic-commerce/AP2" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;, I realized that incredibly difficult cryptographic/signature technologies are used that are tough for a layperson, and there's a lot missing beyond Mandates when considering UI/UX. So understanding AP2 felt like mixed martial arts. Very difficult.&lt;/p&gt;

&lt;p&gt;Also, &lt;strong&gt;Human Not Present&lt;/strong&gt; specifications honestly have many areas not yet finalized. Rather, Human Not Present is arguably the essence of AP2.&lt;/p&gt;

&lt;p&gt;I hope various reference implementations come out so we can proceed with these implementations!&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus - What is Mugibo Shop?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mugibo Shop&lt;/strong&gt; that appears in this demo doesn't exist in reality, of course.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;Mugibo&lt;/strong&gt; exists in reality. She's our Mame Shiba (miniature Shiba Inu). Mame Shiba... supposed to be, but she got big and is now a Mame Shiba with one foot in Shiba Inu territory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.instagram.com/mugimugi.cutedog/" rel="noopener noreferrer"&gt;Mugi's Instagram&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The products that appear during cart selection were created from &lt;strong&gt;Mugi's&lt;/strong&gt; photos using &lt;a href="https://gemini.google/jp/overview/image-generation/?hl=ja" rel="noopener noreferrer"&gt;Nano Banana&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubu2ziy7qyqmsjv5ixpo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubu2ziy7qyqmsjv5ixpo.png" alt="mugino" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ap2-protocol.org/specification/" rel="noopener noreferrer"&gt;AP2 Official Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google-agentic-commerce/AP2" rel="noopener noreferrer"&gt;Google AP2 Samples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/payment-request/" rel="noopener noreferrer"&gt;W3C Payment Request API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3.org/TR/webauthn/" rel="noopener noreferrer"&gt;WebAuthn Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://notebooklm.google.com/notebook/19b991d1-6b94-4c12-bb23-c339136139b7" rel="noopener noreferrer"&gt;Agent Payments Protocol (AP2) for AI Commerce (Notebook LLM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.paypal.com/community/blog/PayPal-Agent-Payments-Protocol/" rel="noopener noreferrer"&gt;PayPal Developer blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/czmilo/2025-complete-guide-to-ai-agent-payments-how-the-ap2-protocol-is-reshaping-intelligent-commerce-2imf"&gt;2025 Complete Guide to AI Agent Payments: How the AP2 Protocol is Reshaping Intelligent Commerce&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>ap2</category>
      <category>mcp</category>
      <category>a2a</category>
    </item>
    <item>
      <title>Use depcheck with GitHub Action to output results in GitHub Pull Request comments.</title>
      <dc:creator>tubone24</dc:creator>
      <pubDate>Thu, 25 Nov 2021 13:53:28 +0000</pubDate>
      <link>https://forem.com/tubone24/use-depcheck-with-github-action-to-output-results-in-github-pull-request-comments-487i</link>
      <guid>https://forem.com/tubone24/use-depcheck-with-github-action-to-output-results-in-github-pull-request-comments-487i</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/"&gt;Node Package Manager (npm)&lt;/a&gt;, which is indispensable for developing systems with JavaScript and TypeScript, often has a problem with its large library size.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8E3RLZ69--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/yxDDBOX.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8E3RLZ69--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/yxDDBOX.jpg" alt="img" width="800" height="575"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, there have been a lot of discussions about node module's issue, and I don't want to say anything about it now.&lt;/p&gt;

&lt;p&gt;I'm sure there are many ways to solve the above problem, but for webdev developers like me, the only thing we can do is to delete libraries that we don't use, so let's think of ways delete unuse libraries.&lt;/p&gt;

&lt;h1&gt;
  
  
  depcheck
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/depcheck/depcheck"&gt;depcheck&lt;/a&gt; is a tool to check how each library dependency is used, which dependencies are not used, and which dependencies are missing from package.json for your projects packaged with npm.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;npx&lt;/code&gt; in the project root,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx depcheck
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can easily get the results by running it.&lt;/p&gt;

&lt;p&gt;For example, if you run it in &lt;a href="https://github.com/tubone24/portfolio"&gt;this repository&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; npx depcheck
npx: installed 120 in 12.136s

Unused dependencies
* @fortawesome/free-regular-svg-icons
* @fortawesome/free-solid-svg-icons
* react-error-overlay
* react-flickr-hero
* sharp
* tween
Unused devDependencies
* @storybook/addon-a11y
* @storybook/addon-controls
* @storybook/addon-docs
* @storybook/addon-essentials
* @storybook/addon-info
* @storybook/addon-knobs
* @storybook/addon-links
* @storybook/addon-storyshots
* @types/aws-lambda
* @types/jest
* @types/node
* @types/react-fontawesome
* @types/storybook__addon-info
* babel-preset-gatsby
* babel-preset-react-app
* greenkeeper-lockfile
* identity-obj-proxy
* netlify-cli
* netlify-lambda
* react-test-renderer
* stylelint-config-idiomatic-order
* stylelint-config-prettier
* stylelint-config-recommended
* stylelint-config-styled-components
* stylelint-processor-styled-components
* ts-dedent
* ts-jest
* tslint-react
Missing dependencies
* build-url: .\src\components\flickrHero.tsx
* @fortawesome/fontawesome-common-types: .\src\components\socialIcons.tsx
* axios: .\functions\src\contact.js
* @babel/preset-react: .\.storybook\main.js
* @babel/preset-env: .\.storybook\main.js
* @babel/plugin-proposal-class-properties: .\.storybook\main.js
* babel-plugin-remove-graphql-queries: .\.storybook\main.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this way, you can list libraries that are defined in package.json but not used in the code, or libraries that are found in the code but not defined in package.json.&lt;/p&gt;

&lt;h1&gt;
  
  
  I want to incorporate depcheck into GitHub Actions
&lt;/h1&gt;

&lt;p&gt;After all, it would be nice if the depcheck runs automatically when you submit a PR.&lt;/p&gt;

&lt;p&gt;And it would be even better if you could be notified of the results.&lt;/p&gt;

&lt;p&gt;So, I created GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/marketplace/actions/depcheck-action-with-pr"&gt;https://github.com/marketplace/actions/depcheck-action-with-pr&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's easy to use. First, create a YAML file with a pull request as the trigger in your GitHub Actions.&lt;/p&gt;

&lt;p&gt;The GitHub Token and the URL of the PR comment are required as input, and these can be obtained as environment variables in GitHub Actions.&lt;/p&gt;

&lt;p&gt;Both can be obtained as environment variables in GitHub Actions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GITHUB_TOKEN

&lt;ul&gt;
&lt;li&gt;This can be gotten as &lt;code&gt;secrets.GITHUB_TOKEN&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;PR_COMMENT_URL

&lt;ul&gt;
&lt;li&gt;This can be gotten  from &lt;code&gt;github.event.pull_request.comments_url&lt;/code&gt; for PR events.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout source code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;14.x&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;depcheck"&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tubone24/depcheck_action@v1.0.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;PR_COMMENT_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.comments_url }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You just need to &lt;code&gt;use&lt;/code&gt; it like this.&lt;/p&gt;

&lt;p&gt;Then the result will be output as a PR comment.&lt;/p&gt;

&lt;p&gt;At the time, there were no GitHub Actions in the marketplace that would output the results in a PR comment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Nf7P5Ac2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/x0HzZEF.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Nf7P5Ac2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/x0HzZEF.png" alt="img" width="880" height="787"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Unused dependencies section indicates that the libraries defined in package.json's dependencies are not used in the .js, .ts, .jsx, .tsx, .coffee, .sass, .scss, and .vue files.&lt;/li&gt;
&lt;li&gt;The Unused devDpendencies section indicates that the library defined in devDependencies in package.json is not present in each file.&lt;/li&gt;
&lt;li&gt;The Missing section indicates that the library used in your code is not present in package.json, possibly because you are using a library imported from CDN or a globally declared library.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source code for this can be found at the link below, and is implemented using the Docker-launched version of GitHub Actions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tubone24/depcheck_action"&gt;https://github.com/tubone24/depcheck_action&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>showdev</category>
      <category>devops</category>
      <category>github</category>
    </item>
    <item>
      <title>Get Netlify build time down to 0 hours with GitHub Actions and get rid of the month-end hijinks!</title>
      <dc:creator>tubone24</dc:creator>
      <pubDate>Sun, 28 Feb 2021 16:51:49 +0000</pubDate>
      <link>https://forem.com/tubone24/get-netlify-build-time-down-to-0-hours-with-github-actions-and-get-rid-of-the-month-end-hijinks-3c7l</link>
      <guid>https://forem.com/tubone24/get-netlify-build-time-down-to-0-hours-with-github-actions-and-get-rid-of-the-month-end-hijinks-3c7l</guid>
      <description>&lt;h1&gt;
  
  
  Netlify
&lt;/h1&gt;

&lt;p&gt;Netlify is a very useful and appreciated service that you all know and love, but for the poor people who use it for free**, there is a certain problem that arises every month.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This month's build time is XX minutes left&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FkfZ1gSi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/TSm24w0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FkfZ1gSi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/TSm24w0.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Netlify works with GitHub repositories to run front-end builds and deploy them, but there is a time limit on how long it takes to run these builds.&lt;/p&gt;

&lt;p&gt;The free version is limited to 300 minutes per month. (If you want to use it more than that, you can charge $19/month for the Pro version. I've already paid for it.)&lt;/p&gt;

&lt;p&gt;It's not that I don't think 300 minutes will be enough, but&lt;/p&gt;

&lt;p&gt;If you're using Netlify across multiple repositories, if you're using a lot of images in Gatsby.js and it's taking a long time for &lt;strong&gt;Sharp&lt;/strong&gt; to resize them, or if &lt;strong&gt;Dependabot&lt;/strong&gt; is getting PRs and Preview deploys regularly&lt;br&gt;
You may be surprised to find that you're on the edge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z65ymfNQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/y7ixbEG.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z65ymfNQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/y7ixbEG.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, at the end of the month, poor folks like me are so worried about Netlify build time that they stop writing this blog post** and slow down the pace of &lt;strong&gt;Site Refactor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So at the end of the month, poor folks like me are so worried about Netlify's build time that they don't write any more posts on this blog or slow down the pace of &lt;strong&gt;Site Refactor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, the article I'm writing right now is written from my phone on the train to work, so I'd like to hit commit and save it in detail, but if I hit commit and push it, the build will run, so I'm too lazy to commit with WIP, and as a result, I don't have a work space like at home where I can push it all together. As a result, I don't blog anymore unless I'm at home where I have a work space where I can push it all together.&lt;br&gt;
As a result, I don't write blogs anymore unless I am at home where I have a work space where I can push them all together.&lt;/p&gt;
&lt;h2&gt;
  
  
  Leave this problem to GitHub Actions!
&lt;/h2&gt;

&lt;p&gt;So, I'd like to solve this problem with GitHub Actions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Find out what Netlify is doing when it builds and try to do it yourself.
&lt;/h2&gt;

&lt;p&gt;Basically, what Netlify does when building, for example, Gatsby.js, is to run the gatsby build command and deploy the built JS in a specific directory (usually &lt;code&gt;./public&lt;/code&gt;) and deploys the pre-built JS placed in a specific directory.&lt;br&gt;
However, there is a pattern that Netlify performs post-processing (PostProcess) on the built JS.&lt;/p&gt;

&lt;p&gt;In my case, &lt;strong&gt;Asset optimization&lt;/strong&gt;, which optimizes JS and images, and &lt;strong&gt;Form detection&lt;/strong&gt;, which creates forms automatically by adding attributes to Form tags, were set.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---lf2zRDH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ytjbJQA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---lf2zRDH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ytjbJQA.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_McIGS-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/LfL70Br.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_McIGS-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/LfL70Br.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will no longer be done by Netlify, so you will need to reimplement it here.&lt;/p&gt;
&lt;h2&gt;
  
  
  gatsby-plugin-minify
&lt;/h2&gt;

&lt;p&gt;Among asset optimization, JS and CSS minifers can use &lt;a href="https://www.gatsbyjs.com/plugins/gatsby-plugin-minify/"&gt;gatsby-plugin-minify&lt;/a&gt; to minify html, JS, and CSS can be minified.&lt;/p&gt;

&lt;p&gt;You can install it from NPM (yarn) as usual.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install gatsby-plugin-minify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use it by setting the plugins in gatsby-config.js as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gatsby-plugin-minify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;caseSensitive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;collapseBooleanAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;useShortDoctype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;removeEmptyElements&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;removeComments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;removeAttributeQuotes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;minifyCSS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;minifyJS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By setting minifyCSS and minifyJS to true, the CSS will be minified together using &lt;a href="https://github.com/jakubpawlowicz/clean-css"&gt;clean-css&lt;/a&gt; and the JS will be minified together using &lt;a href="https://github.%20com/mishoo/UglifyJS"&gt;UglifyJS&lt;/a&gt; to minify the JS together. Also, the other side of gatsby-plugin-minify is just multiplying &lt;a href="https://github.com/kangax/html-minifier"&gt;html-minifier&lt;/a&gt; by postbuild in gatsby-node.js. You can set detailed options in &lt;a href="https://github.com/kangax/html-minifier#options-quick-reference"&gt;html-minifier&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By the way, you have to be careful to set the &lt;strong&gt;removeAttributeQuotes&lt;/strong&gt; option to false.&lt;/p&gt;

&lt;p&gt;If you set this option to true, double-quotes will be removed from the attributes in the HTML tags, and the file will be a little lighter, but it will not load well in systems like &lt;a href="https://berss.com/feed/Find.aspx"&gt;berss.com&lt;/a&gt; that retrieve RSS links from sites. I've been using it all day.&lt;/p&gt;

&lt;p&gt;Be careful if you are planting RSS links as page links.&lt;/p&gt;

&lt;h2&gt;
  
  
  By using imgur, you can do image hosting and resizing at the same time.
&lt;/h2&gt;

&lt;p&gt;There is a service called &lt;a href="https://imgur.com/"&gt;imgur&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is mainly known as an image hosting service for Reddit and Gifs, but I use imgur on this blog because it allows you to easily resize and host images.&lt;/p&gt;

&lt;p&gt;This can be done by inserting the keyword that matches the image size after the image URL.&lt;/p&gt;

&lt;p&gt;For example, if you have an image with this URL&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://i.imgur.com/Wfz9G0B.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To resize to 160x160, attach &lt;strong&gt;b&lt;/strong&gt; to the back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://i.imgur.com/Wfz9G0Bb.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This completes the image optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  getform.io
&lt;/h2&gt;

&lt;p&gt;Getform.io](&lt;a href="https://getform.io/"&gt;https://getform.io/&lt;/a&gt;) is a great service that provides a backend for forms.&lt;/p&gt;

&lt;p&gt;You need to pay for the paid version to use the useful integrations, but if you just want to send a notification email to a specified email address when a form is submitted, you can do that for free.&lt;/p&gt;

&lt;p&gt;We will now replace Netlify's Form detection.&lt;/p&gt;

&lt;p&gt;First of all, when you create a new form, you can issue an Action URL for the form.&lt;/p&gt;

&lt;p&gt;The following blog explains how to create a Form in an easy-to-understand manner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.nakamu.life/posts/getform-io"&gt;https://blog.nakamu.life/posts/getform-io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, once the Form is created, you can follow the tutorial and set this URL to the action of the Form tag, but the free version of GetForm does not allow you to &lt;strong&gt;set the Thanks page after submitting the Form&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!--
* Add your getform endpoint into "action" attribute
* Set a unique "name" field
* Start accepting submissions
--&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"{getform-endpoint}"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6TzdUdVT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/sT5vhFE.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6TzdUdVT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/sT5vhFE.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, this is good enough, but since we are using &lt;strong&gt;React&lt;/strong&gt;, let's specify that the URL of getform.io should be POST fetched behind the scenes and sent to your own Thanks URL defined in actions.&lt;/p&gt;

&lt;p&gt;First, we need to set &lt;strong&gt;onSubmit&lt;/strong&gt; on the Form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;
              &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;contact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
              &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
              &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/thanks/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
              &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;icon-user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;nbsp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;Your&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;br&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;
                    &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form-control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;maxLength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;30&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;minLength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;required&lt;/span&gt;
                    &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter your name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/label&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, separately define a function that will fire onSubmit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://getform.io/f/xxxxxxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Contact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we are sending a Form, we need to send the elements appended to &lt;a href="https://developer.mozilla.org/ja/docs/Web/API/FormData"&gt;FormData&lt;/a&gt; in the fetch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  static encode(data) {
    const formData = new FormData();
    // eslint-disable-next-line no-restricted-syntax
    for (const key of Object.keys(data)) {
      formData.append(key, data[key]);
    }
    return formData;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, in React, in addition to actions, onSubmit can be a function in a Form.&lt;/p&gt;

&lt;p&gt;However, when onSubmit is pressed, the input items of the Form must be passed via POST Fetch, so for each changeEvent that occurs on input of the Form, the value of the Form should be saved as a state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;handleAttachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;中略&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;icon-user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;nbsp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;Your&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;br&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;
                    &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form-control&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;maxLength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;30&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;minLength&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;required&lt;/span&gt;
                    &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter your name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
                    &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/label&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, if you use onSubmit, you will not be able to jump to the Thanks page after the Post process is finished using Gatsby's navigate function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://getform.io/f/897f187e-876d-42a7-b300-7c235af72e6d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Contact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;navigateTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;action&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can create your own Thanks page even with GetForm free version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z6qrP8zl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/gumRkbF.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z6qrP8zl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/gumRkbF.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Build and Deploy with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Once you've done this, all you need to do is build and deploy with GitHub Actions.&lt;/p&gt;

&lt;p&gt;We'll create two actions, one for preview deployment with a PR to the master branch, and another for production deployment with a commit to master.&lt;/p&gt;

&lt;p&gt;First, let's do Preview deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DeployToNetlifyPreview&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout source code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache node_modules&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node_modules&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.OS }}-build-&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.OS }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;12.x&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install and build&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_GITHUB_CLIENT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_GITHUB_CLIENT_SECRET}}&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_GITHUB_CLIENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_GITHUB_CLIENT_ID}}&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_ALGOLIA_SEARCH_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_ALGOLIA_SEARCH_API_KEY}}&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_ALGOLIA_INDEX_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_ALGOLIA_INDEX_NAME}}&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_ALGOLIA_APP_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_ALGOLIA_APP_ID}}&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_ALGOLIA_ADMIN_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_ALGOLIA_ADMIN_API_KEY}}&lt;/span&gt;
          &lt;span class="na"&gt;FAUNADB_SERVER_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.FAUNADB_SERVER_SECRET}}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install&lt;/span&gt;
          &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to netlify&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx netlify-cli deploy --dir=./public &amp;gt; cli.txt&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NETLIFY_AUTH_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NETLIFY_SITE_ID }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cat cli.txt&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cat cli.txt&lt;/span&gt;
          &lt;span class="s"&gt;sed -i -z 's/\n/\\n/g' cli.txt&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Post Netlify CLI Comment&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.comments_url }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;curl -X POST \&lt;/span&gt;
               &lt;span class="s"&gt;-H "Authorization: token ${GITHUB_TOKEN}" \&lt;/span&gt;
               &lt;span class="s"&gt;-d "{\"body\": \"$(cat cli.txt)\"}" \&lt;/span&gt;
               &lt;span class="s"&gt;${URL}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The node setup, npm install, and build are the same as usual.&lt;/p&gt;

&lt;p&gt;In GitHub Actions, you can specify a secret, so API keys for Algolia search and FaunaDB are passed as secrets in the environment variable at build time.&lt;/p&gt;

&lt;p&gt;Incidentally, if you set &lt;strong&gt;GATSBY_XXXX&lt;/strong&gt; in the environment variable, the environment variable will also be entered in the built JS. (Don't forget to do this when using environment variables from JS. This is a point that gets stuck quite a bit.&lt;/p&gt;

&lt;p&gt;To deploy, use &lt;a href="https://docs.netlify.com/cli/get-started/"&gt;netlify-cli&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The required environment variables are site ID and AUTH TOKEN.&lt;/p&gt;

&lt;p&gt;One of the features of netlify-cli is that when the deployment is successful, the &lt;strong&gt;deploy URL&lt;/strong&gt; will appear in the standard output**.&lt;/p&gt;

&lt;p&gt;I also send the URL to PR comments.&lt;/p&gt;

&lt;p&gt;The great thing about GitHub Actions is that you can retrieve the GITHUB TOKEN by using secrets.GITHUB_TOKEN without setting it, so you can easily send it to PR comments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to netlify&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx netlify-cli deploy --dir=./public &amp;gt; cli.txt&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NETLIFY_AUTH_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NETLIFY_SITE_ID }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cat cli.txt&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cat cli.txt&lt;/span&gt;
          &lt;span class="s"&gt;sed -i -z 's/\n/\\n/g' cli.txt&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Post Netlify CLI Comment&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.comments_url }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;curl -X POST \&lt;/span&gt;
               &lt;span class="s"&gt;-H "Authorization: token ${GITHUB_TOKEN}" \&lt;/span&gt;
               &lt;span class="s"&gt;-d "{\"body\": \"$(cat cli.txt)\"}" \&lt;/span&gt;
               &lt;span class="s"&gt;${URL}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to deploy to production.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DeployToNetlifyPRD&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout source code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache node_modules&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node_modules&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}&lt;/span&gt;
          &lt;span class="na"&gt;restore-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.OS }}-build-&lt;/span&gt;
            &lt;span class="s"&gt;${{ runner.OS }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Node&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;12.x&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install and build&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_GITHUB_CLIENT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_GITHUB_CLIENT_SECRET}}&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_GITHUB_CLIENT_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_GITHUB_CLIENT_ID}}&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_ALGOLIA_SEARCH_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_ALGOLIA_SEARCH_API_KEY}}&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_ALGOLIA_INDEX_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_ALGOLIA_INDEX_NAME}}&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_ALGOLIA_APP_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_ALGOLIA_APP_ID}}&lt;/span&gt;
          &lt;span class="na"&gt;GATSBY_ALGOLIA_ADMIN_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.GATSBY_ALGOLIA_ADMIN_API_KEY}}&lt;/span&gt;
          &lt;span class="na"&gt;FAUNADB_SERVER_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{secrets.FAUNADB_SERVER_SECRET}}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install&lt;/span&gt;
          &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to netlify&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx netlify-cli deploy --prod --dir=./public&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NETLIFY_AUTH_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NETLIFY_SITE_ID }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is almost the same, but by including the --prod option in the deploy command in netlify-cli, it will be deployed to the production environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to netlify&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx netlify-cli deploy --prod --dir=./public&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NETLIFY_AUTH_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.NETLIFY_SITE_ID }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Now the build time of Netlify is zero, which is a mental relief.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SJEg_syg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ugdUr9l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SJEg_syg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/ugdUr9l.png" alt="img"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And I can get on with refactoring and writing articles!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
