Checkout API
The Checkout API provides a unified way to process payments, create customers, and manage payment instruments in a single operation using cards and bank accounts.
For stablecoin wallet payments, see the Wallet Checkout API documentation.
checkout
The modern, recommended method for processing payments with cards and bank accounts.
const { checkout } = useEasy();
const result = await checkout({
customer_creation: true,
customer_details: {
first_name: "John",
last_name: "Doe",
email: "[email protected]",
},
source: {
type: "PAYMENT_CARD",
cardElement: cardRef,
name: "Primary Card",
},
line_items: [{ price_id: "price_123", quantity: 1 }],
});
Parameters
The checkout method accepts two different parameter types:
New Customer Checkout
interface CreateCheckoutNewCustomer {
customer_creation: true;
customer_details: {
first_name: string;
last_name: string;
email: string;
phone?: string;
personal_address?: Address;
};
source: PaymentCardParams | BankAccountParams;
line_items: LineItem[];
metadata?: Record<string, unknown>;
}
Existing Customer Checkout
interface CreateCheckoutExistingCustomer {
customer_creation: false;
identity_id: string; // Customer ID (note: use identity_id, not customer_id)
source: string | PaymentCardParams | BankAccountParams; // Can be existing PI ID
line_items: LineItem[];
metadata?: Record<string, unknown>;
}
Line Items
interface LineItem {
price_id: string;
quantity: number;
}
Returns
Promise<
ApiResponse<{
transfer?: TransferData;
orderId?: string;
subscriptions: (SubscriptionData & {
order_id: string;
order_number: string;
})[];
}>
>;
Complete Examples
New Customer with Card Payment
import { useEasy, CardElement } from "@easylabs/react";
import { useRef, useState } from "react";
function NewCustomerCheckout() {
const { checkout } = useEasy();
const cardRef = useRef(null);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
first_name: "",
last_name: "",
email: "",
phone: "",
address: {
line1: "",
city: "",
state: "",
postal_code: "",
country: "US",
},
});
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
const result = await checkout({
customer_creation: true,
customer_details: {
first_name: formData.first_name,
last_name: formData.last_name,
email: formData.email,
phone: formData.phone,
personal_address: formData.address,
},
source: {
type: "PAYMENT_CARD",
cardElement: cardRef,
name: `${formData.first_name}'s Card`,
address: formData.address,
},
line_items: [{ price_id: "price_abc123", quantity: 1 }],
metadata: {
order_source: "web",
campaign: "spring-2024",
},
});
console.log("Checkout successful:", result.data);
// Handle success (redirect, show confirmation, etc.)
} catch (error) {
console.error("Checkout failed:", error);
// Handle error
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="text"
placeholder="First Name"
value={formData.first_name}
onChange={(e) =>
setFormData({ ...formData, first_name: e.target.value })
}
required
/>
<input
type="text"
placeholder="Last Name"
value={formData.last_name}
onChange={(e) =>
setFormData({ ...formData, last_name: e.target.value })
}
required
/>
</div>
<div>
<input
type="email"
placeholder="Email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
required
/>
<input
type="tel"
placeholder="Phone"
value={formData.phone}
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
/>
</div>
<div>
<input
type="text"
placeholder="Street Address"
value={formData.address.line1}
onChange={(e) =>
setFormData({
...formData,
address: { ...formData.address, line1: e.target.value },
})
}
required
/>
</div>
<div>
<input
type="text"
placeholder="City"
value={formData.address.city}
onChange={(e) =>
setFormData({
...formData,
address: { ...formData.address, city: e.target.value },
})
}
required
/>
<input
type="text"
placeholder="State"
value={formData.address.state}
onChange={(e) =>
setFormData({
...formData,
address: { ...formData.address, state: e.target.value },
})
}
required
/>
<input
type="text"
placeholder="ZIP Code"
value={formData.address.postal_code}
onChange={(e) =>
setFormData({
...formData,
address: { ...formData.address, postal_code: e.target.value },
})
}
required
/>
</div>
<div>
<label>Card Information</label>
<CardElement ref={cardRef} />
</div>
<button type="submit" disabled={loading}>
{loading ? "Processing..." : "Pay $29.99"}
</button>
</form>
);
}
Existing Customer with Saved Payment Method
import { useEasy } from "@easylabs/react";
import { useState } from "react";
function ExistingCustomerCheckout({ customerId, paymentInstrumentId }) {
const { checkout } = useEasy();
const [loading, setLoading] = useState(false);
const handleCheckout = async () => {
setLoading(true);
try {
const result = await checkout({
customer_creation: false,
identity_id: customerId, // Note: use identity_id
source: paymentInstrumentId, // Just the payment instrument ID
line_items: [{ price_id: "price_xyz789", quantity: 2 }],
metadata: {
repeat_customer: true,
},
});
console.log("Checkout successful:", result.data);
} catch (error) {
console.error("Checkout failed:", error);
} finally {
setLoading(false);
}
};
return (
<button onClick={handleCheckout} disabled={loading}>
{loading ? "Processing..." : "Complete Purchase"}
</button>
);
}
Existing Customer with New Payment Method
import { useEasy, CardElement } from "@easylabs/react";
import { useRef, useState } from "react";
function ExistingCustomerNewCard({ customerId }) {
const { checkout } = useEasy();
const cardRef = useRef(null);
const [loading, setLoading] = useState(false);
const handleCheckout = async (e) => {
e.preventDefault();
setLoading(true);
try {
const result = await checkout({
customer_creation: false,
identity_id: customerId,
source: {
type: "PAYMENT_CARD",
cardElement: cardRef,
name: "New Card",
},
line_items: [{ price_id: "price_def456", quantity: 1 }],
});
console.log("Checkout successful:", result.data);
} catch (error) {
console.error("Checkout failed:", error);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleCheckout}>
<label>New Card</label>
<CardElement ref={cardRef} />
<button type="submit" disabled={loading}>
{loading ? "Processing..." : "Pay with New Card"}
</button>
</form>
);
}
Bank Account Payment
import { useEasy, TextElement } from "@easylabs/react";
import { useRef, useState } from "react";
function BankAccountCheckout() {
const { checkout } = useEasy();
const routingRef = useRef(null);
const accountRef = useRef(null);
const [accountType, setAccountType] = useState("CHECKING");
const handleSubmit = async (e) => {
e.preventDefault();
try {
const result = await checkout({
customer_creation: true,
customer_details: {
first_name: "Bob",
last_name: "Johnson",
email: "[email protected]",
},
source: {
type: "BANK_ACCOUNT",
accountType: accountType,
routingElement: routingRef,
accountElement: accountRef,
name: "Primary Checking",
},
line_items: [{ price_id: "price_ghi012", quantity: 1 }],
});
console.log("Checkout successful:", result.data);
} catch (error) {
console.error("Checkout failed:", error);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Routing Number</label>
<TextElement ref={routingRef} placeholder="123456789" />
</div>
<div>
<label>Account Number</label>
<TextElement ref={accountRef} placeholder="0123456789" />
</div>
<div>
<label>Account Type</label>
<select
value={accountType}
onChange={(e) => setAccountType(e.target.value)}
>
<option value="CHECKING">Checking</option>
<option value="SAVINGS">Savings</option>
</select>
</div>
<button type="submit">Pay with Bank Account</button>
</form>
);
}
Multiple Line Items (Cart Checkout)
import { useEasy, CardElement } from "@easylabs/react";
import { useRef, useState } from "react";
function CartCheckout({ cart }) {
const { checkout } = useEasy();
const cardRef = useRef(null);
// Calculate total from cart
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
const handleCheckout = async (formData) => {
try {
const result = await checkout({
customer_creation: true,
customer_details: formData.customer,
source: {
type: "PAYMENT_CARD",
cardElement: cardRef,
name: "Primary Card",
},
line_items: cart.map((item) => ({
price_id: item.price_id,
quantity: item.quantity,
})),
metadata: {
cart_id: formData.cartId,
discount_code: formData.discountCode,
},
});
console.log("Order completed:", result.data);
} catch (error) {
console.error("Checkout failed:", error);
}
};
return (
<div>
<h2>Cart Summary</h2>
<ul>
{cart.map((item, idx) => (
<li key={idx}>
{item.name} x {item.quantity} - $
{((item.price * item.quantity) / 100).toFixed(2)}
</li>
))}
</ul>
<p>
<strong>Total: ${(total / 100).toFixed(2)}</strong>
</p>
{/* Checkout form */}
<CardElement ref={cardRef} />
<button onClick={() => handleCheckout(/* form data */)}>
Complete Purchase
</button>
</div>
);
}
Checkout with Subscriptions
The Checkout API seamlessly integrates with the Subscriptions API. When you include recurring prices in your line items, EasyLabs automatically creates subscriptions for the customer after successful payment.
Mixed Cart: One-Time + Recurring Items
You can include both one-time purchases and subscription items in a single checkout:
import { useEasy, CardElement } from "@easylabs/react";
import { useRef, useState } from "react";
function MixedCartCheckout() {
const { checkout } = useEasy();
const cardRef = useRef(null);
const [loading, setLoading] = useState(false);
const handleCheckout = async (customerData) => {
setLoading(true);
try {
const result = await checkout({
customer_creation: true,
customer_details: {
first_name: customerData.firstName,
last_name: customerData.lastName,
email: customerData.email,
},
source: {
type: "PAYMENT_CARD",
cardElement: cardRef,
name: "Primary Card",
},
line_items: [
// One-time purchase (e.g., setup fee, product)
{ price_id: "price_onetime_setup", quantity: 1 },
// Recurring subscription (automatically creates subscription)
{ price_id: "price_monthly_plan", quantity: 1 },
],
metadata: {
order_type: "mixed_cart",
campaign: "new_customer",
},
});
console.log("Checkout successful:", result.data);
// Customer now has both an order and an active subscription
} catch (error) {
console.error("Checkout failed:", error);
} finally {
setLoading(false);
}
};
return (
<form
onSubmit={(e) => {
e.preventDefault();
handleCheckout({
/* customer data */
});
}}
>
<CardElement ref={cardRef} />
<div>
<h3>Order Summary:</h3>
<p>Setup Fee: $49.99 (one-time)</p>
<p>Monthly Subscription: $29.99/month (recurring)</p>
</div>
<button type="submit" disabled={loading}>
{loading ? "Processing..." : "Complete Purchase"}
</button>
</form>
);
}
When a checkout session includes line items with recurring prices:
- The one-time payment is processed immediately
- A subscription is automatically created for recurring items
- The subscription will handle future recurring billing
- You can manage the subscription using the Subscriptions API
Subscription-Only Checkout
For pure subscription checkouts with no one-time items:
const result = await checkout({
customer_creation: true,
customer_details: {
first_name: "Jane",
last_name: "Smith",
email: "[email protected]",
},
source: {
type: "PAYMENT_CARD",
cardElement: cardRef,
name: "Primary Card",
},
line_items: [{ price_id: "price_premium_monthly", quantity: 1 }],
metadata: {
trial_applied: true,
plan: "premium",
},
});
After a successful checkout with recurring prices, use the Subscriptions API to:
- View subscription details with
getCustomerSubscriptions() - Cancel subscriptions with
cancelSubscription(subscriptionId) - Track subscription status and billing cycles
Learn more in the Subscriptions API documentation.
Type Definitions
CheckoutSessionData
interface CheckoutSessionData {
id: string;
customer_id: string;
payment_instrument_id: string;
transfer_id?: string;
status: "pending" | "succeeded" | "failed";
line_items: LineItem[];
metadata?: Record<string, unknown>;
created_at: string;
}
Address
interface Address {
line1: string;
line2?: string;
city: string;
state: string;
postal_code: string;
country: string;
}
Important Notes
When checking out with an existing customer, use identity_id, not customer_id:
{
customer_creation: false,
identity_id: 'cust_123', // ✅ Correct
// customer_id: 'cust_123', // ❌ Wrong
}
You can pass either:
- A payment instrument ID string:
source: 'pi_abc123' - A new payment method object:
source: { type: 'PAYMENT_CARD', ... }
Error Handling
const handleCheckout = async (data) => {
try {
const result = await checkout(data);
// Success handling
} catch (error) {
if (error.message.includes("payment instrument")) {
console.error("Invalid payment method");
} else if (error.message.includes("customer")) {
console.error("Customer error");
} else if (error.message.includes("line_items")) {
console.error("Invalid line items");
} else {
console.error("Checkout failed:", error);
}
}
};
Best Practices
- Validate form data before calling checkout
- Show loading states during checkout processing
- Handle errors gracefully with user-friendly messages
- Store metadata for order tracking and analytics
- Use address verification to reduce payment failures
- Test with sandbox mode before going live
Next Steps
This documentation is being migrated from the DEVELOPER_DOCS.md file.
For complete documentation, please see:
Coming soon to this documentation site!