Skip to content

Client Library. #1

@doeixd

Description

@doeixd

Answer

You're asking about creating a JavaScript client library for the Open Commerce Protocol. Based on the codebase, this would be a client-side SDK that abstracts the HTTP interactions and payment flows defined in the OCP specification.

Core Components

A client library would consist of these main modules:

1. Discovery & Capabilities

Handle server capability discovery and feature detection 1 :

  • discoverCapabilities() - Fetch and parse /capabilities endpoint
  • getWellKnown() - Fetch /.well-known/ocp for API discovery
  • Feature detection helpers (e.g., supportsVariants(), supportsDirectOrders())

2. Catalog & Product Management

Browse products and catalogs 2 :

  • getCatalog(catalogId) - Fetch catalog with items
  • getProduct(productId) - Get individual product details
  • parseVariants(product) - Extract variant metadata if capability exists

3. Cart Management

Handle shopping cart operations 3 :

  • createCart(storeId) - Initialize new cart
  • addToCart(cartId, itemId, quantity, variantId?) - Add items
  • updateCartItem(), removeFromCart() - Modify cart contents
  • getCart(cartId) - Retrieve current cart state

4. Payment Flow Abstraction

This is the most complex part - abstracting the x402 payment protocol 4 :

For stateless x402 pattern:

  • placeOrder(orderData) - Handle initial request and 402 response
  • selectPaymentScheme(accepts) - Choose from available payment options
  • completePayment(scheme) - Route to appropriate payment handler:
    • handleStripePayment() - Initialize Stripe.js with clientSecret 5
    • handleCryptoPayment() - Handle blockchain signatures for exact scheme
    • handleWebPayments() - Use Payment Request API 6
    • handleSPC() - Secure Payment Confirmation with biometrics
  • retryWithPayment(orderData, paymentProof) - Retry with X-PAYMENT header

For stateful pattern:

  • createPendingOrder() - Create order in pending_payment state 7
  • confirmPayment(orderId, paymentToken) - Call confirmation endpoint 8

5. Real-Time Updates

SSE stream handling for order updates 9 :

  • subscribeToOrderUpdates(orderId, callback) - Open SSE connection
  • applyJsonPatch(order, patches) - Apply JSON Patch operations
  • Auto-reconnection logic for network failures

6. Hypermedia Actions

Execute server-provided actions dynamically 10 :

  • performAction(action, payload) - Generic action executor
  • Handle 201 Created responses for versioned resources 11
  • Update internal state when resource IDs change

7. Error Handling

RFC 9457 Problem Details parsing and recovery:

  • parseError(response) - Extract error details
  • handleStaleVersion(error) - Fetch latest version via latestVersionUrl
  • handleCartExpired(error) - Execute nextActions for recovery
  • Automatic retry logic with exponential backoff

Implementation Example Structure

class OCPClient {
  constructor(config) {
    this.baseUrl = config.baseUrl;
    this.authToken = config.authToken;
    this.capabilities = null;
  }

  // Discovery
  async init() {
    this.capabilities = await this.discoverCapabilities();
  }

  // Catalog
  async getCatalog(catalogId) { /* ... */ }

  // Cart
  async createCart(storeId) { /* ... */ }
  async addToCart(cartId, itemId, quantity, variantId) { /* ... */ }

  // Orders with payment abstraction
  async placeOrder(orderData) {
    const response = await this.post('/orders', orderData);
    
    if (response.status === 402) {
      const x402 = await response.json();
      const scheme = this.selectPaymentScheme(x402.accepts);
      const paymentProof = await this.completePayment(scheme);
      
      // Retry with same idempotency key
      return this.post('/orders', orderData, {
        'X-PAYMENT': btoa(JSON.stringify(paymentProof))
      });
    }
    
    return response;
  }

  // SSE subscriptions
  subscribeToOrderUpdates(orderId, callback) { /* ... */ }
}

Key Design Considerations

  1. Payment Provider Agnostic: The library should work with any payment scheme the server advertises, not hardcode Stripe-specific logic 12

  2. Idempotency Management: Automatically generate and reuse Idempotency-Key headers for payment retries 13

  3. State Management: Handle resource versioning and ID changes transparently 14

  4. Browser Compatibility: Feature detection for Web Payments API and SPC 15

  5. TypeScript Support: Full type definitions for all OCP schemas 16

Notes

The reference implementation in this repo is server-side (Hono middleware) 17 , but the client examples in index.html show the patterns a JS library would implement 18 . The library would essentially be a wrapper around these patterns with proper error handling, state management, and payment provider integrations. The x402 pattern's universality means the library can support any payment provider without modification 19 .

Wiki pages you might want to explore:

Citations

File: index.html (L1042-1428)

                <h3>Let's Build Something: Your First OCP Client</h3>

                <p>Enough theory. Let's write some code. I'm going to walk you through building a simple client that browses a catalog and places an order. We'll use plain JavaScript and <span class="inline-code">fetch</span>—no frameworks, no magic.</p>

                <h4>Step 1: Discover What the Server Can Do</h4>

                <p>Every OCP interaction starts the same way: ask the server what it supports. This is like walking into a store and asking for the catalog before you order.</p>

                <div class="code-wrapper"><button class="copy-btn" onclick="copyCode(this)">Copy</button>
                    <div class="code-block"><pre><code class="language-javascript">// First, let's see what this server can do
const API_BASE = 'https://api.example.com';
const AUTH_TOKEN = 'your_token_here'; // You'd get this from your auth flow

async function discoverCapabilities() {
  const response = await fetch(`${API_BASE}/capabilities`, {
    headers: {
      'Accept': 'application/ocp+json; version=1.0'
    }
  });
  
  const data = await response.json();
  console.log('Server capabilities:', data.capabilities);
  return data.capabilities;
}

// Let's call it
const capabilities = await discoverCapabilities();

// Output might look like:
// [
//   { id: "dev.ocp.product.variants@1.0", schemaUrl: "..." },
//   { id: "dev.ocp.order.shipment_tracking@1.0", schemaUrl: "..." }
// ]</code></pre></div>
                </div>

                <p>See what we did there? We now know this server supports product variants and shipment tracking. We can use this information to enable features in our UI. No hardcoding, no guessing.</p>

                <h4>Step 2: Browse the Catalog</h4>

                <p>Now let's get the actual products. In OCP, products live in "catalogs".</p>

                <div class="code-wrapper"><button class="copy-btn" onclick="copyCode(this)">Copy</button>
                    <div class="code-block"><pre><code class="language-javascript">// First, let's see what catalogs are available
async function getCatalogs() {
  const response = await fetch(`${API_BASE}/catalogs`, {
    headers: {
      'Accept': 'application/ocp+json; version=1.0'
    }
  });
  
  return await response.json();
}

// Get the first catalog's details
async function getCatalog(catalogId) {
  const response = await fetch(`${API_BASE}/catalogs/${catalogId}`, {
    headers: {
      'Accept': 'application/ocp+json; version=1.0'
    }
  });
  
  return await response.json();
}

// Let's use it
const catalogs = await getCatalogs();
const catalog = await getCatalog(catalogs[0].id);

console.log('Products:', catalog.items);

// Each item looks like:
// {
//   id: "item_123",
//   name: "Classic T-Shirt",
//   price: { amount: "25.00", currency: "USD" },
//   available: true,
//   fulfillmentType: "physical"
// }</code></pre></div>
                </div>

                <p>Notice how clean this is? A product is just a product. Name, price, availability. If you're building a simple client, you're done—just show these to the user.</p>

                <h4>Step 3: Understanding Items and Metadata</h4>

                <p>But what if a product has variants, like different sizes? This is where OCP gets clever. Instead of adding a <span class="inline-code">variants</span> field to every product (even ones that don't need it), we use <span class="inline-code">metadata</span>.</p>

                <div class="code-wrapper"><button class="copy-btn" onclick="copyCode(this)">Copy</button>
                    <div class="code-block"><pre><code class="language-javascript">// A product with variants looks like this:
const tshirt = {
  id: "prod_123",
  name: "Classic T-Shirt",
  price: { amount: "25.00", currency: "USD" },
  available: true,
  fulfillmentType: "physical",
  metadata: {
    // This key matches the base ID from the capability we discovered earlier!
    "dev.ocp.product.variants": {
      "_version": "1.0",
      options: ["Size", "Color"],
      variants: [
        {
          id: "var_large_black",
          values: ["Large", "Black"],
          price: { amount: "25.00", currency: "USD" },
          stock: 42
        },
        {
          id: "var_medium_white",
          values: ["Medium", "White"],
          price: { amount: "25.00", currency: "USD" },
          stock: 15
        }
      ]
    }
  }
};

// In your client, you check if the capability exists:
function hasVariants(item, capabilities) {
  const variantCapability = capabilities.find(
    cap => cap.id === "dev.ocp.product.variants@1.0"
  );

  return variantCapability &&
         item.metadata?.["dev.ocp.product.variants"];
}

// Then render accordingly:
if (hasVariants(tshirt, capabilities)) {
  // Show size/color picker
  const variants = tshirt.metadata["dev.ocp.product.variants"];
  console.log('Available options:', variants.options);
  console.log('Available variants:', variants.variants);
}

// Then render accordingly:
if (hasVariants(tshirt, capabilities)) {
  // Show size/color picker
  const variants = tshirt.metadata["dev.ocp.product.variants@1.0"];
  console.log('Available options:', variants.options);
  console.log('Available variants:', variants.variants);
} else {
  // Just show a simple "Add to Cart" button
  console.log('Simple product, no variants');
}</code></pre></div>
                </div>

                <p>This is the magic of OCP. Your client adapts to what the server supports. If variants exist, you show them. If not, you don't. No special cases, no brittle code.</p>

                <h4>Step 4: Creating a Cart and Adding Items</h4>

                <p>Alright, the user wants to buy something. Let's create a cart and add an item to it.</p>

                <div class="code-wrapper"><button class="copy-btn" onclick="copyCode(this)">Copy</button>
                    <div class="code-block"><pre><code class="language-javascript">// Create a new cart
async function createCart() {
  const response = await fetch(`${API_BASE}/carts`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${AUTH_TOKEN}`,
      'Content-Type': 'application/ocp+json; version=1.0',
      'Accept': 'application/ocp+json; version=1.0'
    },
    body: JSON.stringify({
      storeId: 'store_123' // Which store/vendor
    })
  });
  
  return await response.json();
}

// Add an item to the cart
async function addToCart(cartId, itemId, quantity = 1, variantId = null) {
  const body = {
    itemId: itemId,
    quantity: quantity
  };
  
  // If the user selected a variant, include it
  if (variantId) {
    body.variantId = variantId;
  }
  
  const response = await fetch(`${API_BASE}/carts/${cartId}/items`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${AUTH_TOKEN}`,
      'Content-Type': 'application/ocp+json; version=1.0',
      'Accept': 'application/ocp+json; version=1.0',
      'Idempotency-Key': crypto.randomUUID() // Prevent duplicate adds
    },
    body: JSON.stringify(body)
  });
  
  return await response.json();
}

// Let's use it
const cart = await createCart();
console.log('Created cart:', cart.id);

// Add a specific variant to the cart
await addToCart(cart.id, 'prod_123', 1, 'var_large_black');
console.log('Added item to cart!');</code></pre></div>
                </div>

                <p>Notice the <span class="inline-code">Idempotency-Key</span> header? This is important. If the network hiccups and you retry the request, the server won't add the item twice. This is the kind of detail OCP gets right.</p>

                <h4>Step 5: Placing an Order</h4>

                <p>The user is ready to check out. Let's place the order.</p>

                <div class="code-wrapper"><button class="copy-btn" onclick="copyCode(this)">Copy</button>
                    <div class="code-block"><pre><code class="language-javascript">// Place an order
async function placeOrder(cartId, deliveryAddress = null) {
  const body = {
    cartId: cartId
  };

  // Include delivery address if provided (required for physical items)
  if (deliveryAddress) {
    body.deliveryAddress = deliveryAddress;
  }
  
  const response = await fetch(`${API_BASE}/orders`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${AUTH_TOKEN}`,
      'Content-Type': 'application/ocp+json; version=1.0',
      'Accept': 'application/ocp+json; version=1.0',
      'Idempotency-Key': crypto.randomUUID()
    },
    body: JSON.stringify(body)
  });
  
  // Check if payment is required
  if (response.status === 402) {
    // Server needs payment via x402 protocol
    const paymentInfo = await response.json();
    console.log('Payment required:', paymentInfo);
    // Handle x402 payment flow here...
    return null;
  }
  
  return await response.json();
}

// Place an order (type inferred from cart contents)
const order = await placeOrder(cart.id, {
  address: '123 Main St, City, State 12345',
  latitude: 37.7749,
  longitude: -122.4194
});

console.log('Order placed!', order);
// {
//   id: "order_123",
//   status: "pending",
//   items: [...],
//   total: { amount: "25.00", currency: "USD" },
//   createdAt: "2025-01-15T10:30:00Z"
// }</code></pre></div>
                </div>

                <h4>Step 6: Tracking the Order in Real-Time</h4>

                <p>Here's where it gets cool. OCP supports real-time order updates via Server-Sent Events (SSE). This means you can show the user live updates as their order progresses.</p>

                <div class="code-wrapper"><button class="copy-btn" onclick="copyCode(this)">Copy</button>
                    <div class="code-block"><pre><code class="language-javascript">// Subscribe to order updates
function subscribeToOrderUpdates(orderId, onUpdate) {
  const eventSource = new EventSource(
    `${API_BASE}/orders/${orderId}/updates`,
    {
      headers: {
        'Authorization': `Bearer ${AUTH_TOKEN}`
      }
    }
  );
  
  eventSource.addEventListener('order.patch', (event) => {
    // The server sends JSON Patch operations
    const patches = JSON.parse(event.data);
    console.log('Order updated:', patches);
    
    // Apply the patches to your local order object
    patches.forEach(patch => {
      if (patch.op === 'replace' && patch.path === '/status') {
        console.log('New status:', patch.value);
        onUpdate({ status: patch.value });
      }
      
      // Check for tracking info
      if (patch.path.includes('shipment_tracking')) {
        console.log('Tracking info available!', patch.value);
        onUpdate({ tracking: patch.value });
      }
    });
  });
  
  eventSource.onerror = (error) => {
    console.error('SSE error:', error);
    eventSource.close();
  };
  
  return eventSource;
}

// Use it
const subscription = subscribeToOrderUpdates(order.id, (update) => {
  if (update.status) {
    console.log(`Order is now: ${update.status}`);
    // Update your UI: "Your order is being prepared!"
  }
  
  if (update.tracking) {
    console.log('Track your package:', update.tracking);
    // Show tracking link in UI
  }
});

// When you're done, clean up
// subscription.close();</code></pre></div>
                </div>

                <h3>Why This Approach Works</h3>

                <p>Let's step back and think about what we just built. With less than 200 lines of code, we created a client that can:</p>

                <ul>
                    <li>Discover what any OCP server supports</li>
                    <li>Browse products with or without variants</li>
                    <li>Manage a shopping cart</li>
                    <li>Place orders for physical, digital, or pickup items</li>
                    <li>Track orders in real-time</li>
                </ul>

                <p>And here's the kicker: <strong>this same code works with any OCP-compliant server</strong>. A grocery store, a clothing store, a digital goods marketplace—doesn't matter. The API is the same.</p>

                <div class="info-box success">
                    <strong>The OCP Promise</strong>
                    Write your client once, work with any vendor. Write your server once, work with any client. That's the power of a minimal, extensible standard.
                </div>

                <h3>What Makes OCP Different</h3>

                <p>So why does OCP succeed where other standards fail? A few key decisions:</p>

                <div class="feature-grid">
                    <div class="feature-card">
                        <div class="feature-title">1. Minimal Core</div>
                        <div class="feature-description">The base API is tiny. You can implement a basic OCP server in an afternoon. This means people actually adopt it.</div>
                    </div>

                    <div class="feature-card">
                        <div class="feature-title">2. Discoverable Extensions</div>
                        <div class="feature-description">Instead of version hell, features are discoverable. Clients adapt to what servers support. No breaking changes, no API versions to juggle.</div>
                    </div>

                    <div class="feature-card">
                        <div class="feature-title">3. Structured Metadata</div>
                        <div class="feature-description">Metadata isn't a dumping ground. It's structured, validated, and linked to schemas. This prevents the chaos of "just put it in metadata."</div>
                    </div>

                    <div class="feature-card">
                        <div class="feature-title">4. Real-Time by Default</div>
                        <div class="feature-description">SSE for order updates is built in, not bolted on. Real-time isn't a premium feature—it's how the API works.</div>
                    </div>

                    <div class="feature-card">
                        <div class="feature-title">5. Web3-Native Payments</div>
                        <div class="feature-description">X402 integration means instant, low-fee payments with stablecoins. Perfect for micropayments, AI agents, and global commerce.</div>
                    </div>

                    <div class="feature-card">
                        <div class="feature-title">6. Implementation Freedom</div>
                        <div class="feature-description">OCP doesn't dictate your business logic. It's a transport layer, not a framework. Use any database, any auth system, any payment processor.</div>
                    </div>
                </div>

                <h3>The Bottom Line</h3>

                <p>OCP exists because commerce APIs shouldn't be hard. The core flow—browse, cart, order—is universal. The details—variants, tracking, fulfillment—should be discoverable and optional.</p>

                <p>By keeping the core minimal and making extensions discoverable, OCP gives you the best of both worlds: simplicity when you need it, power when you want it.</p>

                <p>Now go build something cool. The API is simple, the docs are here, and the community is growing. Welcome to open commerce.</p>

File: README.md (L56-140)

## Core Library Implementation

This repository includes a complete reference implementation of the OCP core library, packaged as a reusable Hono middleware.

### Installation

```bash
npm install open-commerce-protocol

Quick Start

import { Hono } from 'hono';
import { createOcpMiddleware } from 'open-commerce-protocol';

const app = new Hono();

// Configure OCP middleware
const ocpConfig = {
  baseUrl: 'http://localhost:3000',
  stores: [{
    id: 'store_123',
    name: 'Sample Restaurant',
    location: {
      address: '123 Main St, Anytown, USA',
      latitude: 40.7128,
      longitude: -74.0060,
    },
    catalogIds: ['catalog_456'],
  }],
  catalogs: [{
    id: 'catalog_456',
    name: 'Main Menu',
    version: '1.0',
    items: [{
      id: 'item_1',
      name: 'Margherita Pizza',
      price: { amount: '12.99', currency: 'USD' },
      available: true,
      fulfillmentType: 'pickup',
    }],
  }],
  capabilities: [{
    id: 'dev.ocp.cart@1.0',
    schemaUrl: 'https://schemas.ocp.dev/cart/v1.json',
    status: 'stable',
  }],
};

// Mount OCP routes
app.route('/api/v1', createOcpMiddleware(ocpConfig));

// Add authentication
app.use('/api/v1/*', async (c, next) => {
  const authHeader = c.req.header('Authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    return c.json({
      type: 'https://schemas.ocp.dev/errors/unauthorized',
      title: 'Unauthorized',
      status: 401,
      detail: 'Valid authorization token required',
      timestamp: new Date().toISOString(),
    }, 401);
  }

  c.set('ocp', { ...c.var.ocp, userId: 'user_123' });
  await next();
});

export default { port: 3000, fetch: app.fetch };

Features

  • Complete OCP Core: All core endpoints with full error handling
  • Hono Middleware: Easy integration with Hono-based applications
  • TypeScript Support: Full type definitions for all OCP schemas
  • RFC 9457 Errors: Proper problem details for all error responses
  • In-Memory Storage: Built-in storage for development and testing
  • Configurable: Enable/disable features as needed
  • Comprehensive Tests: 71+ tests covering all functionality
  • Production Ready: Reference implementation with proper error handling and validation

See examples/hono-usage.ts for a complete example.


**File:** README.md (L182-189)
```markdown
In the OCP order flow:

1. **Client places order** (`POST /orders`).
2. **If payment required:** Server responds with `402 Payment Required`, including x402 payment requirements (amount, asset, network, etc.).
3. **Client authorizes payment:** Signs a blockchain authorization and retries the request with the `X-PAYMENT` header containing the base64-encoded payment payload.
4. **Server verifies & settles:** Forwards to an x402 facilitator for onchain verification and settlement.
5. **Order completes:** Server returns the order details with a `X-PAYMENT-RESPONSE` header confirming the transaction.

File: README.md (L264-307)

This is the most powerful feature. Core resources like `Order` and `CatalogItem` will tell you what actions you can perform on them *in their current state*.

**Example: A Discoverable Returns Workflow**

1. A user views a recently delivered order. Your client makes a `GET /orders/order_123`.

2. The server checks its business logic. Because the order is within the 30-day return window, it includes an `initiate_return` action in the response:

```json
{
  "id": "order_123",
  "status": "completed",
  "actions": [
    {
      "id": "initiate_return",
      "href": "https://api.example.com/v2/order/order_123/return-requests"
    }
  ],
  "items": [...]
}
  1. Your client's logic is now incredibly simple:
// Pseudo-code for a UI component
const returnAction = order.actions.find(action => action.id === 'initiate_return');

if (returnAction) {
  // The action is present, so we show the button
  showReturnButton({
    // We tell the button exactly which URL to POST to
    onClick: () => postTo(returnAction.href)
  });
}

If the same order were 60 days old, the server would simply omit the initiate_return action from the actions array. The exact same client code would run, find nothing, and the "Return Items" button would never be rendered. The business logic lives on the server, where it belongs, and the client intelligently adapts.

This same pattern is used for:

  • Cancellations: The cancel action only appears if an order is cancellable.
  • Writing Reviews: The add_review action only appears on a CatalogItem if the logged-in user has purchased it.

By embracing this hypermedia-driven approach, the Open Commerce Protocol provides a blueprint for a truly modern, resilient, and intelligent commerce ecosystem.


**File:** README.md (L437-469)
```markdown

--- Order is confirmed ---

The programmatic state and detailed status are added atomically.

event: order.patch
data: [
{"op": "replace", "path": "/status", "value": "confirmed"},
{"op": "add", "path": "/metadata/dev.ocp.order.detailed_status", "value": {
"_version": "1.0",
"title": "Order Confirmed",
"description": "Your order has been confirmed and will be processed soon."
}}
]

--- Order is in transit, and a driver is assigned ---

The core status changes, and two new metadata capabilities are added.

event: order.patch
data: [
{"op": "replace", "path": "/status", "value": "in_transit"},
{"op": "replace", "path": "/metadata/dev.ocp.order.detailed_status/title", "value": "Out for Delivery"},
{"op": "add", "path": "/metadata/dev.ocp.order.delivery_tracking", "value": {
"_version": "1.0",
"driver": { "name": "Jane D.", "vehicle": "Blue Sedan" },
"status": "en_route_to_customer",
"liveLocation": { "latitude": 40.7135, "longitude": -74.0050 },
"estimatedArrivalAt": "2025-10-22T19:30:00Z"
}}
]

--- Live location is updated moments later ---

A tiny, efficient patch is sent for only the data that changed.

event: order.patch
data: [{"op": "replace", "path": "/metadata/dev.ocp.order.delivery_tracking/liveLocation", "value": {"latitude": 40.7140, "longitude": -74.0045}}]

File: docs/as-your-used-to.md (L48-89)

### Step 3.1: Create the Order with `pending_payment` Status

Modify your `POST /orders` endpoint. When an order is created, set its initial status to `pending_payment` and include the `add_payment` action.

**Request:**
`POST /orders`
```json
{
  "items": [{ "catalogItemId": "prod_1", "quantity": 1 }],
  "deliveryAddress": { "address": "123 Main St" }
}

Response (201 Created):

{
  "id": "order_abc123",
  "status": "pending_payment",
  "total": { "amount": "25.00", "currency": "USD" },
  "items": [...],
  "actions": [
    {
      "id": "add_payment",
      "href": "/orders/order_abc123/confirm-payment",
      "method": "POST",
      "title": "Add Payment",
      "description": "Provide payment details to complete this order.",
      "metadata": {
        // Metadata will contain provider-specific info
        // See Step 3.2 for examples
      }
    },
    {
      "id": "cancel",
      "href": "/orders/order_abc123/cancel",
      "method": "POST",
      "title": "Cancel Order"
    }
  ]
}

**File:** docs/as-your-used-to.md (L163-193)
```markdown
**Server Logic (Pseudo-code):**
```javascript
app.post('/orders/:id/confirm-payment', async (req, res) => {
  const order = findOrderById(req.params.id);
  const { provider, paymentMethodId } = req.body;

  if (order.status !== 'pending_payment') {
    return res.status(409).json({ error: 'Order is not awaiting payment.' });
  }

  try {
    // Use the provider's SDK to process the payment
    const paymentResult = await stripe.paymentIntents.confirm(
      order.paymentIntentId, // Stored when the order was created
      { payment_method: paymentMethodId }
    );

    if (paymentResult.status === 'succeeded') {
      order.status = 'confirmed';
      order.actions = []; // Remove payment actions
      saveOrder(order);
      res.status(200).json(order);
    } else {
      order.status = 'payment_failed';
      saveOrder(order);
      res.status(400).json({ error: 'Payment failed' });
    }
  } catch (e) {
    res.status(500).json({ error: 'Server error during payment.' });
  }
});

File: docs/as-your-used-to.md (L252-270)

```javascript
async function handleStripePayment(action, metadata) {
  const stripe = Stripe(metadata.publishableKey);

  // Use Stripe Elements to collect card details, then confirm
  const result = await stripe.confirmCardPayment(metadata.clientSecret, {
    payment_method: { card: cardElement }
  });

  if (result.error) {
    alert(result.error.message);
  } else {
    // Payment succeeded, now confirm with your server
    await confirmPaymentWithServer(action.href, {
      provider: 'stripe',
      paymentIntentId: result.paymentIntent.id
    });
  }
}

File: docs/as-your-used-to.md (L441-441)

| **Provider Support** | **Universal.** The client's code is generic. It can support Stripe, PayPal, or any future provider without modification, as long as it can handle the basic schemes (`fiat_intent`, etc.). | **Specific.** The client must contain explicit code branches (`if (provider === 'stripe')`, `if (provider === 'paypal')`) and the corresponding SDKs for every supported provider. |

File: docs/as-your-used-to.md (L456-461)

**Choose the Stateless x402 Pattern if:**
*   You are building a **universal client** designed to work with any OCP server.
*   You prioritize **architectural elegance, atomicity, and simplicity** on the server.
*   Your developers are comfortable learning a slightly unconventional but powerful HTTP pattern.
*   You are building for the future, including potential for **AI agents or other automated systems** that benefit from a simple, stateless protocol.
*   You want to support both **fiat and Web3 payments** through a single, unified flow.

File: docs/web-payments-spc.md (L63-67)

**Client:**
- Browser supporting the APIs:
  - Check via `window.PaymentRequest` for Web Payments
  - Check `'SecurePaymentConfirmationRequest' in window` for SPC
- Graceful fallback handling for unsupported browsers

File: docs/web-payments-spc.md (L301-360)

        metadata: {
          _version: '1.0',
          supportedRps: ['bank.example.com'],
          registrationUrl: 'https://bank.example.com/register',
          fallbackMethods: ['otp', 'web_payments']
        }
      }
    ]
  });
});

3.2 x402 Integration

When payment is required, return a 402 response with payment requirements. Include Web Payments and/or SPC data in the extra field:

app.post('/orders', async (req, res) => {
  // ... order processing logic

  // If payment required:
  return res.status(402).json({
    x402Version: 1,
    error: "Payment required",
    accepts: [
      {
        scheme: "fiat_intent",
        network: "stripe",
        asset: "USD",
        maxAmountRequired: "2500", // $25.00 in cents
        payTo: "acct_123",
        resource: "/orders",
        description: "Pay with card via Stripe",
        maxTimeoutSeconds: 3600,
        extra: {
          clientSecret: "pi_3abc...", // Stripe PaymentIntent client secret

          // Web Payments data
          webPayments: {
            supportedMethods: [
              {
                supportedMethods: "basic-card",
                data: {
                  supportedNetworks: ["visa", "mastercard", "amex"]
                }
              }
            ],
            displayItems: [
              { label: "Coffee Mug", amount: { currency: "USD", value: "15.00" } },
              { label: "Shipping", amount: { currency: "USD", value: "5.00" } },
              { label: "Tax", amount: { currency: "USD", value: "5.00" } }
            ]
          },

          // SPC data (if user has enrolled credential)
          spc: {
            challenge: base64Encode(randomBytes(32)),
            credentialIds: ["Y3JlZGVudGlhbF9pZF8x"], // Base64-encoded credential IDs
            rpId: "bank.example.com",
            instrument: {

File: docs/immutable-resource-versioning.md (L312-337)

### 4.1 The Golden Rule: State Management on `201 Created`
The most critical responsibility for a client interacting with a versioned resource is correctly handling the response to a mutation request. A `201 Created` response is a signal that the identity of the resource has changed.

**Your client logic MUST:**
1.  **Recognize the `201 Created` status code** as a successful versioning event.
2.  **Extract the new resource URL** from the `Location` header. The ID in this URL is the new primary key for the resource.
3.  **Use the JSON response body** as the new, canonical state of the object.
4.  **Update all internal state references.** This is the step where most implementations fail. Any variables, UI components, or data stores (e.g., Redux, React Query cache) that reference the old ID **MUST** be updated to the new ID.

```javascript
async function performMutation(action, payload) {
  const response = await fetch(action.href, { /* ... */ });

  if (response.status === 201) {
    const newVersion = await response.json();
    const newLocation = response.headers.get('Location');
    
    // **CRITICAL:** This function MUST update your application's global state,
    // replacing all instances of the old ID and data.
    updateApplicationState(action.resourceId, newVersion, newLocation);
    
    return newVersion;
  }
  // ... handle errors ...
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions