Your First Integration
This tutorial walks you through creating a minimal ACP implementation. By the end, you’ll have a working integration that AI agents can use to browse products and complete purchases.
What We’re Building
A simple store with:
- 3 products in a product feed
- Basic checkout session management
- Stripe payment processing
Time required: ~30 minutes
Prerequisites
- Node.js 18+
- npm or pnpm
- A Stripe account (we’ll use test mode)
- Basic JavaScript knowledge
Step 1: Project Setup
Create the project
mkdir my-acp-store
cd my-acp-store
npm init -yInstall dependencies
npm install express stripe dotenv uuidCreate project structure
- index.js
- products.js
- checkout.js
- .env
- package.json
Set up environment variables
Create a .env file:
STRIPE_SECRET_KEY=sk_test_your_test_key_here
PORT=3000Get your Stripe test key from the Stripe Dashboard . Never commit real keys!
Step 2: Define Your Products
Create products.js:
// products.js
const products = [
{
id: 'prod_001',
title: 'Classic T-Shirt',
description: 'Comfortable cotton t-shirt, perfect for everyday wear.',
price: 2999, // cents
currency: 'USD',
availability: 'in_stock',
category: 'Clothing > T-Shirts',
images: ['https://example.com/tshirt.jpg'],
variants: [
{ id: 'var_001_s', size: 'S', price: 2999 },
{ id: 'var_001_m', size: 'M', price: 2999 },
{ id: 'var_001_l', size: 'L', price: 2999 },
]
},
{
id: 'prod_002',
title: 'Wireless Earbuds',
description: 'High-quality wireless earbuds with 24-hour battery life.',
price: 7999,
currency: 'USD',
availability: 'in_stock',
category: 'Electronics > Audio',
images: ['https://example.com/earbuds.jpg']
},
{
id: 'prod_003',
title: 'Coffee Mug',
description: 'Large ceramic mug, microwave and dishwasher safe.',
price: 1499,
currency: 'USD',
availability: 'in_stock',
category: 'Home > Kitchen',
images: ['https://example.com/mug.jpg']
}
];
module.exports = { products };Step 3: Create the Product Feed Endpoint
Create index.js:
// index.js
require('dotenv').config();
const express = require('express');
const { products } = require('./products');
const { createCheckoutRoutes } = require('./checkout');
const app = express();
app.use(express.json());
// Product Feed Endpoint
app.get('/acp/v1/products', (req, res) => {
// Support optional filtering
const { category, min_price, max_price, search } = req.query;
let filtered = [...products];
if (category) {
filtered = filtered.filter(p =>
p.category.toLowerCase().includes(category.toLowerCase())
);
}
if (min_price) {
filtered = filtered.filter(p => p.price >= parseInt(min_price));
}
if (max_price) {
filtered = filtered.filter(p => p.price <= parseInt(max_price));
}
if (search) {
const searchLower = search.toLowerCase();
filtered = filtered.filter(p =>
p.title.toLowerCase().includes(searchLower) ||
p.description.toLowerCase().includes(searchLower)
);
}
res.json({
products: filtered,
total: filtered.length,
currency: 'USD'
});
});
// Single product endpoint
app.get('/acp/v1/products/:id', (req, res) => {
const product = products.find(p => p.id === req.params.id);
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
res.json(product);
});
// Mount checkout routes
const checkoutRoutes = createCheckoutRoutes(products);
app.use('/acp/v1', checkoutRoutes);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`ACP Store running on http://localhost:${PORT}`);
console.log(`Product feed: http://localhost:${PORT}/acp/v1/products`);
});Step 4: Implement Checkout Sessions
Create checkout.js:
// checkout.js
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const Stripe = require('stripe');
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
// In-memory session storage (use a database in production!)
const sessions = new Map();
function createCheckoutRoutes(products) {
const router = express.Router();
// Create checkout session
router.post('/checkout_sessions', async (req, res) => {
try {
const { cart, buyer_context } = req.body;
// Validate cart items
const validatedItems = [];
let subtotal = 0;
for (const item of cart.items) {
const product = products.find(p => p.id === item.product_id);
if (!product) {
return res.status(400).json({
error: `Product not found: ${item.product_id}`
});
}
const lineTotal = product.price * item.quantity;
subtotal += lineTotal;
validatedItems.push({
product_id: product.id,
title: product.title,
quantity: item.quantity,
unit_price: product.price,
line_total: lineTotal
});
}
// Calculate totals
const shipping = subtotal >= 5000 ? 0 : 500; // Free shipping over $50
const taxRate = 0.0825; // 8.25%
const tax = Math.round(subtotal * taxRate);
const total = subtotal + shipping + tax;
// Create session
const sessionId = `cs_${uuidv4().replace(/-/g, '')}`;
const session = {
checkout_session_id: sessionId,
state: buyer_context?.shipping_address ? 'ready_for_payment' : 'not_ready_for_payment',
cart: {
items: validatedItems
},
buyer_context: buyer_context || {},
totals: {
subtotal,
shipping,
tax,
total,
currency: 'USD'
},
created_at: new Date().toISOString(),
expires_at: new Date(Date.now() + 30 * 60 * 1000).toISOString() // 30 min
};
sessions.set(sessionId, session);
res.status(201).json(session);
} catch (error) {
console.error('Create session error:', error);
res.status(500).json({ error: 'Failed to create session' });
}
});
// Get checkout session
router.get('/checkout_sessions/:id', (req, res) => {
const session = sessions.get(req.params.id);
if (!session) {
return res.status(404).json({ error: 'Session not found' });
}
// Check expiration
if (new Date(session.expires_at) < new Date()) {
session.state = 'expired';
}
res.json(session);
});
// Update checkout session
router.patch('/checkout_sessions/:id', (req, res) => {
const session = sessions.get(req.params.id);
if (!session) {
return res.status(404).json({ error: 'Session not found' });
}
if (session.state === 'completed' || session.state === 'expired') {
return res.status(400).json({ error: 'Cannot update completed/expired session' });
}
const { buyer_context, cart } = req.body;
// Update buyer context (shipping address, email, etc.)
if (buyer_context) {
session.buyer_context = { ...session.buyer_context, ...buyer_context };
}
// Recalculate if cart updated
if (cart) {
// ... recalculation logic (similar to create)
}
// Update state if we now have shipping address
if (session.buyer_context?.shipping_address) {
session.state = 'ready_for_payment';
}
res.json(session);
});
// Complete checkout session
router.post('/checkout_sessions/:id/complete', async (req, res) => {
const session = sessions.get(req.params.id);
if (!session) {
return res.status(404).json({ error: 'Session not found' });
}
if (session.state !== 'ready_for_payment') {
return res.status(400).json({
error: 'Session not ready for payment',
current_state: session.state
});
}
const { payment_token } = req.body;
if (!payment_token) {
return res.status(400).json({ error: 'Payment token required' });
}
try {
// In a real implementation, you would:
// 1. Verify the SPT with Stripe
// 2. Capture the payment
// For this demo, we'll simulate success
const paymentIntent = await stripe.paymentIntents.create({
amount: session.totals.total,
currency: 'usd',
payment_method: payment_token,
confirm: true,
automatic_payment_methods: {
enabled: true,
allow_redirects: 'never'
}
});
// Update session
session.state = 'completed';
session.order = {
order_id: `ord_${uuidv4().replace(/-/g, '').slice(0, 16)}`,
payment_intent_id: paymentIntent.id,
created_at: new Date().toISOString()
};
res.json({
success: true,
order_id: session.order.order_id,
session
});
} catch (error) {
console.error('Payment error:', error);
res.status(400).json({
error: 'Payment failed',
message: error.message
});
}
});
return router;
}
module.exports = { createCheckoutRoutes };Step 5: Test Your Integration
Start the server
node index.jsTest the product feed
curl http://localhost:3000/acp/v1/productsExpected output:
{
"products": [
{
"id": "prod_001",
"title": "Classic T-Shirt",
"price": 2999,
...
},
...
],
"total": 3,
"currency": "USD"
}Test search functionality
curl "http://localhost:3000/acp/v1/products?search=earbuds"Create a checkout session
curl -X POST http://localhost:3000/acp/v1/checkout_sessions \
-H "Content-Type: application/json" \
-d '{
"cart": {
"items": [
{ "product_id": "prod_001", "quantity": 2 },
{ "product_id": "prod_003", "quantity": 1 }
]
},
"buyer_context": {
"email": "customer@example.com",
"shipping_address": {
"line1": "123 Main St",
"city": "San Francisco",
"state": "CA",
"postal_code": "94102",
"country": "US"
}
}
}'Check session status
curl http://localhost:3000/acp/v1/checkout_sessions/YOUR_SESSION_IDWhat You Built
Congratulations! You now have:
âś… Product Feed API - AI agents can discover your products âś… Checkout Sessions - Manage purchase state âś… Payment Processing - Ready for Stripe payments
Next Steps
This minimal implementation demonstrates core concepts. For production, you’ll need:
- Database storage for sessions
- Proper error handling
- Request validation
- Rate limiting
- Authentication
- Webhooks for order fulfillment
Continue with the next tutorials:
- Building a Product Feed - Advanced feed features
- Implementing Checkout API - Full specification compliance
- Handling Payment Tokens - SPT integration
You’ve completed your first ACP integration! You now understand the core concepts and are ready to build production-ready implementations.