Skip to main content

Payment Intents

Payment Intents implement a two-phase commit pattern for reliable payments. First create an intent to reserve the amount, then confirm when the customer is ready to pay.

Why Use Payment Intents?

  • Reliability: Prevent double-charges with idempotent confirmations
  • Flexibility: Separate payment creation from execution
  • User Experience: Show payment details before charging
  • Session Integration: Validate spending limits before committing

Basic Flow

import { zendfi } from '@zendfi/sdk';

// Step 1: Create intent (reserves amount)
const intent = await zendfi.intents.create({
amount: 99.99,
description: 'Premium subscription',
});

console.log(`Intent ID: ${intent.id}`);
console.log(`Client Secret: ${intent.client_secret}`);
console.log(`Status: ${intent.status}`); // "requires_payment"

// Step 2: Confirm when ready (charges the customer)
const confirmed = await zendfi.intents.confirm(intent.id, {
client_secret: intent.client_secret,
customer_wallet: 'Hx7B...abc',
});

console.log(`Payment ID: ${confirmed.payment_id}`);
console.log(`Status: ${confirmed.status}`); // "succeeded"

Intent Statuses

StatusDescriptionNext Actions
requires_paymentWaiting for confirmationConfirm or Cancel
processingPayment in progressWait for completion
succeededPayment completeDone ✅
canceledCanceled by user/merchantCreate new intent
failedPayment failedRetry or cancel

Creating an Intent

const intent = await zendfi.intents.create({
amount: 99.99,
currency: 'USD', // Optional, default: USD
description: 'Pro Plan',
capture_method: 'automatic', // or 'manual' for auth-only
metadata: {
order_id: 'ORD-123',
product: 'pro_monthly',
},
});

Intent Options

FieldTypeDescription
amountnumberPayment amount in USD
currencystringCurrency code (default: USD)
descriptionstringDescription shown to customer
capture_methodstringautomatic or manual
metadataobjectCustom key-value data

Confirming an Intent

const confirmed = await zendfi.intents.confirm(intent.id, {
client_secret: intent.client_secret,
customer_wallet: 'Hx7B...abc',
session_token: session.token, // Optional: for session-based limits
auto_gasless: true, // Optional: pay gas for user
metadata: {
confirmed_by: 'agent',
ip_address: '192.168.1.1',
},
});

Confirmation Options

FieldTypeDescription
client_secretstringSecret from intent creation
customer_walletstringSolana wallet address
session_tokenstringOptional session for limit enforcement
auto_gaslessbooleanPay gas fees for the customer
metadataobjectAdditional metadata to store

Canceling an Intent

await zendfi.intents.cancel(intent.id);
console.log('Intent canceled');
tip

You can only cancel intents in the requires_payment status. Processing or succeeded intents cannot be canceled.

Using with Agent Sessions

Combine intents with sessions for spending limit enforcement:

// Create session with limits
const session = await zendfi.agent.createSession({
agent_id: 'shopping-bot',
user_wallet: userWallet,
limits: {
max_per_transaction: 50,
max_per_day: 200,
},
duration_hours: 24,
});

// Create and confirm intent with session
const intent = await zendfi.intents.create({
amount: 45.00,
description: 'Coffee machine',
agent_id: 'shopping-bot',
});

const confirmed = await zendfi.intents.confirm(intent.id, {
client_secret: intent.client_secret,
customer_wallet: userWallet,
session_token: session.token, // Enforces spending limits
});

Listing Intents

const intents = await zendfi.intents.list({
status: 'requires_payment',
limit: 20,
});

intents.forEach(intent => {
console.log(`${intent.id}: $${intent.amount} - ${intent.status}`);
});

Filter Options

FieldTypeDescription
statusstringFilter by status
limitnumberMax results (default: 20)
starting_afterstringCursor for pagination

CLI Commands

# Create an intent
zendfi intents create --amount 99.99 --description "Premium Plan"

# List intents
zendfi intents list
zendfi intents list --status requires_payment

# Get intent details
zendfi intents get pi_abc123

# Confirm an intent
zendfi intents confirm pi_abc123 --wallet Hx7B...abc

# Cancel an intent
zendfi intents cancel pi_abc123

Webhook Events

EventDescription
intent.createdNew intent created
intent.confirmedIntent confirmed, payment processing
intent.succeededPayment completed successfully
intent.failedPayment failed
intent.canceledIntent was canceled

Error Handling

try {
const confirmed = await zendfi.intents.confirm(intent.id, {
client_secret: intent.client_secret,
customer_wallet: userWallet,
session_token: session.token,
});
} catch (error) {
if (error.code === 'LIMIT_EXCEEDED') {
console.log('Spending limit exceeded');
} else if (error.code === 'SESSION_EXPIRED') {
console.log('Session has expired');
} else if (error.code === 'INSUFFICIENT_FUNDS') {
console.log('Customer has insufficient funds');
} else {
console.log('Payment failed:', error.message);
}
}

Best Practices

  1. Store client_secret securely - Never expose in client-side code
  2. Set appropriate timeouts - Intents expire after 24 hours by default
  3. Use idempotency keys - Prevent duplicate confirmations
  4. Handle all statuses - Implement proper error handling
  5. Combine with sessions - Enforce spending limits for agents

API Reference

Create Intent

POST /api/v1/ai/intents

Confirm Intent

POST /api/v1/ai/intents/:id/confirm

Cancel Intent

POST /api/v1/ai/intents/:id/cancel

List Intents

GET /api/v1/ai/intents

Next Steps

Ask AI about the docs...