Skip to content

Latest commit

 

History

History
254 lines (214 loc) · 7.1 KB

File metadata and controls

254 lines (214 loc) · 7.1 KB

Frontend Reference

TL;DR

  • React 19 + TypeScript + Vite + Tailwind
  • App.tsx handles A2A messaging and state
  • Components render UCP data types (Checkout, Product, Payment)

Why a Mock Payment Provider?

Real payment flows involve sensitive credentials and complex integrations. The CredentialProviderProxy mock lets you:

  • Test the full checkout flow without real payment credentials
  • Understand the data contracts before implementing real providers
  • Demonstrate UCP patterns without external dependencies

In production, you'd replace this with your actual payment SDK (Stripe, Adyen, etc.).

Request/Response Cycle

Frontend Request/Response Cycle

Figure 1: Request/response cycle — React App sends POST /api with UCP-Agent header, Vite Proxy rewrites path and forwards to A2A Server at :10999, ADK Agent returns response with parts[] containing text and structured data (checkout, products).

Component Hierarchy

React Component Hierarchy

Figure 2: React component tree — App.tsx manages state and A2A messaging, with Header, ChatMessage, and ChatInput as children. ChatMessage contains UCP data components (green): ProductCard, Checkout, PaymentMethodSelector, and PaymentConfirmation.

App.tsx - State & Handlers

State

const [messages, setMessages] = useState<ChatMessage[]>([initialMessage]);
const [isLoading, setIsLoading] = useState(false);
const [contextId, setContextId] = useState<string | null>(null);
const [taskId, setTaskId] = useState<string | null>(null);

Handler Functions

Handler Purpose
handleSendMessage(content, options) Send A2A message, parse response
handleAddToCheckout(product) Add product to cart
handleStartPayment() Initiate payment flow
handlePaymentMethodSelection(checkout) Fetch available methods
handlePaymentMethodSelected(method) Get payment token
handleConfirmPayment(instrument) Complete checkout

A2A Communication

Request Format

const request = {
  jsonrpc: "2.0",
  id: crypto.randomUUID(),
  method: "message/send",
  params: {
    message: {
      role: "user",
      parts: [{ type: "text", text: "show me cookies" }],
      contextId: contextId, // From previous response
      taskId: taskId, // For multi-turn tasks
    },
    configuration: { historyLength: 0 },
  },
};

fetch("/api", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-A2A-Extensions":
      "https://ucp.dev/2026-01-23/specification/overview?v=2026-01-23",
    "UCP-Agent": `profile="http://localhost:3000/profile/agent_profile.json"`,
  },
  body: JSON.stringify(request),
});

Response Parsing

const data = await response.json();

// Extract context for next request
setContextId(data.result?.contextId);

// Parse response parts
for (const part of data.result?.status?.message?.parts || []) {
  if (part.text) {
    message.text += part.text;
  }
  if (part.data?.["a2a.product_results"]) {
    message.products = part.data["a2a.product_results"].results;
  }
  if (part.data?.["a2a.ucp.checkout"]) {
    message.checkout = part.data["a2a.ucp.checkout"];
  }
}

Key Components

Component Props Renders
ProductCard product, onAddToCart Product image, name, price, stock
Checkout checkout, onCheckout, onCompletePayment Line items, totals, action buttons
PaymentMethodSelector paymentMethods, onSelect Radio list of methods
PaymentConfirmation paymentInstrument, onConfirm Confirm button
ChatMessage message, handlers Combines all above based on data

Types (types.ts)

interface ChatMessage {
  id: string;
  sender: Sender; // USER | MODEL
  text: string;
  products?: Product[];
  isLoading?: boolean;
  paymentMethods?: PaymentMethod[];
  isUserAction?: boolean;
  checkout?: Checkout;
  paymentInstrument?: PaymentInstrument;
}

interface Checkout {
  id: string;
  line_items: CheckoutItem[];
  currency: string;
  continue_url?: string | null;
  status: string; // incomplete | ready_for_complete | completed
  totals: CheckoutTotal[];
  order_id?: string;
  order_permalink_url?: string;
  payment?: Payment;
}

interface Product {
  productID: string;
  name: string;
  image: string[];
  brand: { name: string };
  offers: {
    price: string;
    priceCurrency: string;
    availability: string;
  };
  url: string;
  description: string;
  size: { name: string };
}
interface DisplayInfo {
  brand: string;
  last_digits: string;
  expiry_month: number;
  expiry_year: number;
}

interface PaymentMethod {
  id: string;
  type: string;
  display: DisplayInfo;
}

CredentialProviderProxy

Mock payment provider in mocks/credentialProviderProxy.ts:

class CredentialProviderProxy {
  handler_id = "example_payment_provider";

  // Returns mock payment methods (wrapped in object)
  async getSupportedPaymentMethods(
    user_email: string,
    config: any
  ): Promise<{ payment_method_aliases: PaymentMethod[] }> {
    return {
      payment_method_aliases: [
        {
          id: "instr_1",
          type: "card",
          display: {
            brand: "amex",
            last_digits: "1111",
            expiry_month: 12,
            expiry_year: 2026,
          },
        },
        {
          id: "instr_2",
          type: "card",
          display: {
            brand: "visa",
            last_digits: "8888",
            expiry_month: 12,
            expiry_year: 2026,
          },
        },
      ],
    };
  }

  // Converts method to PaymentInstrument with token
  async getPaymentToken(
    user_email: string,
    payment_method_id: string
  ): Promise<PaymentInstrument | undefined> {
    return {
      ...payment_method,
      handler_id: this.handler_id,
      credential: { type: "token", token: `mock_token_${uuid}` },
    };
  }
}

Configuration

Vite Proxy (vite.config.ts)

server: {
  port: 3000,
  proxy: {
    "/api": {
      target: "http://localhost:10999",
      changeOrigin: true,
      rewrite: (path) => path.replace(/^\/api/, "")
    }
  }
}

App Config (config.ts)

export const appConfig = new AppProperties(
  "Business Agent",
  "Your personal shopping assistant.",
  "/images/logo.jpg",
  "Hello, I am your Business Agent...",
  "Shop with Business Agent"
);