Skip to main content

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.

Wallet Payments

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>
);
}
Automatic Subscription Creation

When a checkout session includes line items with recurring prices:

  1. The one-time payment is processed immediately
  2. A subscription is automatically created for recurring items
  3. The subscription will handle future recurring billing
  4. 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",
},
});
Managing Subscriptions After Checkout

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

Identity ID vs Customer ID

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
}
Using Existing Payment Methods

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

  1. Validate form data before calling checkout
  2. Show loading states during checkout processing
  3. Handle errors gracefully with user-friendly messages
  4. Store metadata for order tracking and analytics
  5. Use address verification to reduce payment failures
  6. 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!