@anpayeras/agnostic-checkout
v0.1.11
Published
Provider-agnostic, embeddable React checkout component with dynamic theming and plugin architecture
Downloads
1,007
Maintainers
Readme
⚡ Agnostic Checkout SDK
A provider-agnostic, embeddable React checkout component with dynamic theming, plugin architecture, and full TypeScript support.
Features
- 🛒 Embeddable checkout component — drop into any React app
- 🎨 Theme system — 5 built-in presets (
light,dark,minimal,corporate,neon) - 🎯 Brand Auto Theme — generate a full theme from a single brand color
- 🔌 Plugin architecture — extend checkout behavior without modifying core code
- 🏷️ Discount Plugin — coupon input, apply/remove with event-driven updates
- ✏️ Cart Edit Plugin — quantity stepper, item removal with configurable limits
- 🌍 i18n — built-in locales (
en,es,pt-BR,auto) - 💱 Multi-currency — 25+ currencies with automatic formatting
- 📱 Responsive — mobile-first layout with collapsible order summary
- 🔒 Provider-agnostic — works with any payment backend
Installation
npm install agnostic-checkoutQuick Start
import { Checkout } from "agnostic-checkout";
function App() {
const items = [
{
id: "item_1",
name: "Pro Plan",
unitPrice: 49.00,
quantity: 1,
total: 49.00,
image: "https://example.com/pro-plan.png",
},
];
const totals = {
subtotal: 49.00,
taxes: 4.90,
total: 53.90,
};
const paymentMethods = [
{
id: "pm_card",
label: "Credit Card",
type: "card",
},
];
return (
<Checkout
items={items}
totals={totals}
paymentMethods={paymentMethods}
provider={yourPaymentProvider}
currency="USD"
locale="en"
/>
);
}| Prop | Description |
|---|---|
| items | Array of cart items to display |
| totals | Object with subtotal, taxes, shipping, discount, and total |
| paymentMethods | Available payment methods |
| provider | Your PaymentProvider implementation |
| currency | Currency code for formatting ("USD", "EUR", etc.) |
| locale | Language for UI text ("en", "es", "pt-BR", "auto") |
Theming
The checkout uses CSS variables internally and supports three theming strategies with the following priority chain:
customTheme (overrides)
↓
brandColor (auto-generated theme)
↓
theme preset ("light" | "dark" | ...)Preset Themes
Choose from 5 built-in themes:
<Checkout theme="light" />
<Checkout theme="dark" />
<Checkout theme="minimal" />
<Checkout theme="corporate" />
<Checkout theme="neon" />Brand Auto Theme
Generate a complete theme from a single brand color. The SDK automatically derives background, surface, text, border, and accent colors:
<Checkout brandColor="#6366F1" />Custom Theme Overrides
Override specific tokens on top of any base theme:
<Checkout
theme="light"
customTheme={{
colors: {
primary: "#ff0055",
success: "#00cc88",
},
radius: "1rem",
}}
/>Combine with brandColor:
<Checkout
brandColor="#6366F1"
customTheme={{
radius: "20px",
fontFamily: "'Poppins', sans-serif",
}}
/>Theme Tokens
| Token | CSS Variable | Description |
|---|---|---|
| colors.primary | --color-primary | Buttons, links, active states |
| colors.background | --color-background | Page background |
| colors.surface | --color-surface | Card/panel backgrounds |
| colors.text | --color-text | Primary text color |
| colors.border | --color-border | Borders and dividers |
| colors.success | --color-success | Success states, discounts |
| colors.error | --color-error | Error states, remove actions |
| radius | --theme-radius | Border radius for cards/buttons |
| fontFamily | --theme-font | Font family stack |
Plugin System
Plugins extend checkout functionality through a slot-based architecture. Plugins inject UI into predefined areas and communicate via events — they never modify state directly.
import { createDiscountPlugin, createCartEditPlugin } from "agnostic-checkout/plugins";
<Checkout
plugins={[
createDiscountPlugin(handleEvent),
createCartEditPlugin(handleEvent, { allowRemove: true }),
]}
onEvent={handleEvent}
/>Plugins emit events. Your onEvent handler processes them and updates items/totals externally:
const handleEvent = (event) => {
switch (event.type) {
case "COUPON_APPLY_REQUESTED":
// Validate event.code, update items/totals
break;
case "ITEM_REMOVE_REQUESTED":
// Remove item by event.itemId
break;
case "ITEM_QUANTITY_UPDATE_REQUESTED":
// Update quantity for event.itemId to event.quantity
break;
}
};Discount Plugin
Adds a coupon input field to the order summary. Users can type a discount code, apply it, and remove it via a trash icon.
import { createDiscountPlugin } from "agnostic-checkout/plugins";
const plugins = [
createDiscountPlugin(handleEvent),
];Events emitted:
| Event | Payload | Description |
|---|---|---|
| COUPON_APPLY_REQUESTED | { code: string } | User submitted a coupon code |
| COUPON_REMOVE_REQUESTED | — | User clicked the remove button |
The plugin does not calculate discounts. Your onEvent handler validates the code and updates items/totals with the new discount values.
Cart Edit Plugin
Allows users to modify item quantities and/or remove items from the cart.
import { createCartEditPlugin } from "agnostic-checkout/plugins";
const plugins = [
createCartEditPlugin(handleEvent, {
allowRemove: true,
allowQuantityEdit: true,
minQuantity: 1,
maxQuantity: 10,
}),
];Options
| Option | Type | Default | Description |
|---|---|---|---|
| allowRemove | boolean | false | Show remove button per item |
| allowQuantityEdit | boolean | false | Show quantity stepper per item |
| minQuantity | number | 1 | Minimum allowed quantity |
| maxQuantity | number | 99 | Maximum allowed quantity |
Events emitted:
| Event | Payload | Description |
|---|---|---|
| ITEM_QUANTITY_UPDATE_REQUESTED | { itemId, quantity } | User changed item quantity |
| ITEM_REMOVE_REQUESTED | { itemId } | User clicked remove |
Payment Provider
The checkout is payment-agnostic. Implement the PaymentProvider interface to connect any backend:
interface PaymentProvider {
initialize?(config: unknown): Promise<void>;
createPayment(data: unknown): Promise<PaymentResult>;
confirmPayment?(data: unknown): Promise<PaymentResult>;
}class StripeProvider implements PaymentProvider {
async createPayment(data) {
const response = await fetch("/api/payments", {
method: "POST",
body: JSON.stringify(data),
});
return { status: "success" };
}
}
<Checkout provider={new StripeProvider()} />API Reference
<Checkout /> Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| items | CheckoutItem[] | ✅ | — | Cart items |
| totals | CheckoutTotals | ✅ | — | Order totals |
| paymentMethods | PaymentMethod[] | ✅ | — | Available payment methods |
| provider | PaymentProvider | ✅ | — | Payment provider implementation |
| currency | SupportedCurrency | ✅ | — | Currency code |
| locale | CheckoutLocale | — | "es" | UI language |
| theme | CheckoutThemeName \| Partial<CheckoutTheme> | — | "light" | Theme preset or partial theme object |
| brandColor | string | — | — | Auto-generate theme from hex color |
| customTheme | Partial<CheckoutTheme> | — | — | Override specific theme tokens |
| onEvent | EventHandler | — | — | Callback for checkout events |
| plugins | CheckoutPlugin[] | — | [] | Plugins to extend functionality |
| messages | Partial<CheckoutMessages> | — | — | Override default UI text |
| formatters | CheckoutFormatters | — | — | Custom currency formatter |
| devTools | boolean | — | false | Show dev tools panel |
| initialState | PaymentResult | — | — | Start in a specific state |
CheckoutItem
interface CheckoutItem {
id: string;
name: string;
quantity: number;
unitPrice: number;
total: number;
image?: string;
discount?: {
type: "percentage" | "fixed";
value: number;
label?: string;
};
}CheckoutTotals
interface CheckoutTotals {
subtotal: number;
discount?: number;
taxes?: number;
shipping?: number;
total: number;
}Events
| Event | Description |
|---|---|
| CHECKOUT_VIEWED | Checkout component mounted |
| STEP_CHANGED | User navigated to a new step |
| PAYMENT_METHOD_SELECTED | User selected a payment method |
| PAYMENT_SUBMITTED | Payment form submitted |
| PAYMENT_SUCCESS | Payment completed successfully |
| PAYMENT_ERROR | Payment failed |
| COUPON_APPLY_REQUESTED | Coupon code submitted (Discount Plugin) |
| COUPON_REMOVE_REQUESTED | Coupon removed (Discount Plugin) |
| ITEM_QUANTITY_UPDATE_REQUESTED | Quantity changed (Cart Edit Plugin) |
| ITEM_REMOVE_REQUESTED | Item removed (Cart Edit Plugin) |
Internationalization
Built-in support for three locales plus automatic detection:
<Checkout locale="en" /> // English
<Checkout locale="es" /> // Spanish
<Checkout locale="pt-BR" /> // Portuguese (Brazil)
<Checkout locale="auto" /> // Auto-detect from browserOverride specific messages:
<Checkout
messages={{
payNow: "Complete Purchase",
continueToPay: "Proceed to Payment",
}}
/>Custom currency formatter:
<Checkout
formatters={{
currency: (amount) => `US$ ${amount.toFixed(2)}`,
}}
/>Demo
A built-in demo page is available with interactive controls:
npm run devThe demo includes a Settings toolbar to test:
- Theme switching (5 presets + Brand Auto Theme)
- Locale and currency changes
- Item count adjustments
- Plugin enable/disable toggles
- Brand color picker with 12 presets
Development
# Install dependencies
npm install
# Start development server
npm run dev
# Type checking
npx tsc --noEmit
# Build
npm run buildProject Structure
src/
├── core/ # State machine, context, events, types
├── theme/ # Theme system, tokens, color utilities
├── locales/ # i18n messages (en, es, pt-BR)
├── plugins/ # Plugin system, discount & cart-edit plugins
├── ui/ # React components (Checkout, Layout, Summary, etc.)
├── hooks/ # useCheckout hook
├── lib/ # Utilities (cn, formatNumber)
└── mock/ # Demo app, mock provider, toolbarLicense
MIT
