@ss-dev/agnostic-checkout
v1.2.0
Published
Provider-agnostic, embeddable React checkout component with dynamic theming and plugin architecture
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 @ss-dev/agnostic-checkoutQuick Start
import { Checkout } from "@ss-dev/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
licenseKey="your_license_key" // Required for plugins
items={items}
totals={totals}
paymentMethods={paymentMethods}
provider={yourPaymentProvider}
currency="USD"
locale="en"
/>
);
}[!NOTE] While the SDK core is free to use, premium features like Plugins and Themes require a valid license key.
| 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.
[!IMPORTANT] A valid Pro Plan license key is required for plugins to be displayed.
import { createDiscountPlugin, createCartEditPlugin } from "@ss-dev/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 "@ss-dev/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 "@ss-dev/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()} />Action Flow (requires_action)
The checkout supports an intrinsic action lifecycle directly managed by the state machine to handle things like 3D Secure, OTP validation, or redirect-based payments.
When your PaymentProvider returns { status: "requires_action", action: { type: "otp" } }, the SDK will automatically:
- Enter a strict "requires_action" internal step.
- Dim the entire screen and open a full-screen, unskippable Action Overlay modal.
- Automatically lock background scrolling and trap focus to the modal.
- Emit the
ACTION_STARTEDevent with the action payload to youronEventlistener. - Provide built-in timeout protections (a safety countdown out-of-the-box if the integration halts).
- Forward the resolution back into the global checkout flow once completed (
resume({ status: 'success' })), emittingACTION_COMPLETED.
To render your custom Action UI component inside this secure overlay context, provide a renderAction callback to the <Checkout />:
<Checkout
renderAction={({ action, resume }) => {
if (action.type === "otp") return <OtpValidator onSubmit={() => resume({ status: "success" })} />;
if (action.type === "redirect") {
window.location.href = action.url;
return <p>Redirecting to your bank...</p>;
}
}}
onEvent={(event) => {
if (event.type === "ACTION_STARTED") console.log(event.action);
if (event.type === "ACTION_COMPLETED") console.log(event.result);
}}
/>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 |
| licenseKey | string | — | — | License key to enable premium plugins |
| initialState | PaymentResult | — | — | Start in a specific state |
| renderAction | (props) => ReactNode | — | — | Required to display UI for requires_action states. If omitted, the overlay will not render. |
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 |
| ACTION_STARTED | The action overlay was triggered and opened natively |
| ACTION_COMPLETED | An action was successfully resolved by resume() |
| 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
Check out the interactive demo with the Settings toolbar to test theme switching, locales, and more:
License
MIT
