@shadowmkj/plugin-ecommerce
v3.85.5
Published
Ecommerce plugin for Payload
Maintainers
Readme
@shadowmkj/plugin-ecommerce
A full-featured, modular E-commerce plugin for Payload CMS 3. Effortlessly manage multi-currency products, variants, carts, addresses, transactions, orders, and payment integrations within Payload CMS.
⚡ Features
- 🛍️ Complete E-Commerce Infrastructure: Auto-generated collections for Products, Product Variants, Variant Types, Variant Options, Carts, Addresses, Transactions, and Orders.
- 💳 Plug-and-Play Payment Adapters:
- Stripe: Built-in support for PaymentIntents and Webhooks.
- Razorpay: Full order creation, payment capture confirmation, and HMAC SHA256 webhook validation.
- Cash on Delivery (COD): Complete offline/cash-on-delivery workflow.
- 💱 Multi-Currency Support: Configurable default and supported currencies (USD, EUR, GBP, INR, etc.) with automatic price formatting and admin input controls.
- ⚛️ React Client SDK & Hooks: Modular client-side context provider (
EcommerceProvider) and focused hooks (useCart,useCurrency,useAddresses,usePayments). - 🎨 Custom Admin Components: Includes
PriceInput,FormattedInput,PriceCell, andVariantOptionsSelectorfor Payload Admin UI. - 🌐 Built-in i18n: Multilingual support with built-in translations across 30+ languages.
- 🔒 Granular Access Control: Customizable access functions per collection for guest and authenticated user workflows.
📦 Installation
# Using pnpm
pnpm add @shadowmkj/plugin-ecommerce
# Using npm
npm install @shadowmkj/plugin-ecommerce
# Using yarn
yarn add @shadowmkj/plugin-ecommerce🚀 Quick Start
Add ecommercePlugin to your payload.config.ts:
import { buildConfig } from 'payload'
import { ecommercePlugin } from '@shadowmkj/plugin-ecommerce'
import { stripeAdapter } from '@shadowmkj/plugin-ecommerce/payments/stripe'
import { razorpayAdapter } from '@shadowmkj/plugin-ecommerce/payments/razorpay'
import { codAdapter } from '@shadowmkj/plugin-ecommerce/payments/cod'
export default buildConfig({
// ... your Payload config
plugins: [
ecommercePlugin({
currencies: {
defaultCurrency: 'USD',
supportedCurrencies: [
{ code: 'USD', symbol: '$', label: 'US Dollar', decimals: 2 },
{ code: 'EUR', symbol: '€', label: 'Euro', decimals: 2 },
{ code: 'INR', symbol: '₹', label: 'Indian Rupee', decimals: 2 },
],
},
products: {
variants: true,
},
payments: {
paymentMethods: [
stripeAdapter({
secretKey: process.env.STRIPE_SECRET_KEY!,
}),
razorpayAdapter({
publishableKey: process.env.RAZORPAY_KEY_ID!,
secretKey: process.env.RAZORPAY_KEY_SECRET!,
}),
codAdapter({}),
],
},
}),
],
})⚙️ Configuration Options
The ecommercePlugin function accepts an EcommercePluginConfig object with the following properties:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| currencies | CurrenciesConfig | { defaultCurrency: 'USD', supportedCurrencies: [USD] } | Configures allowed currencies, symbols, and decimal precision. |
| products | boolean \| ProductsConfig | { variants: true } | Enables product management and optional variant options/types collections. |
| payments | PaymentsConfig | { paymentMethods: [] } | List of backend payment adapters (stripeAdapter, razorpayAdapter, codAdapter). |
| addresses | boolean \| AddressesConfig | true | Enables customer address management collection and supported country definitions. |
| carts | boolean \| CartsConfig | true | Configures shopping cart collection options and item matching logic. |
| orders | boolean \| OrdersConfig | true | Configures order fulfillment and transaction history collections. |
| access | AccessConfig | Default access rules | Override collection-level access permissions. |
🔌 Payment Adapters
1. Stripe Adapter
import { stripeAdapter, stripeAdapterClient } from '@shadowmkj/plugin-ecommerce/payments/stripe'
// Server-side (Payload Plugin Config)
stripeAdapter({
secretKey: process.env.STRIPE_SECRET_KEY!,
})
// Client-side (React Provider)
stripeAdapterClient()2. Razorpay Adapter
import { razorpayAdapter, razorpayAdapterClient } from '@shadowmkj/plugin-ecommerce/payments/razorpay'
// Server-side (Payload Plugin Config)
razorpayAdapter({
publishableKey: process.env.RAZORPAY_KEY_ID!,
secretKey: process.env.RAZORPAY_KEY_SECRET!,
})
// Client-side (React Provider)
razorpayAdapterClient({
publishableKey: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID!,
})3. Cash on Delivery (COD) Adapter
import { codAdapter, codAdapterClient } from '@shadowmkj/plugin-ecommerce/payments/cod'
// Server-side
codAdapter({})
// Client-side
codAdapterClient({})⚛️ Client SDK (React Provider & Hooks)
Wrap your frontend application with EcommerceProvider to access stateful cart management, currency switching, address creation, and checkout initiation.
'use client'
import React from 'react'
import { EcommerceProvider, useCart, useCurrency, usePayments } from '@shadowmkj/plugin-ecommerce/client/react'
import { stripeAdapterClient } from '@shadowmkj/plugin-ecommerce/payments/stripe'
import { razorpayAdapterClient } from '@shadowmkj/plugin-ecommerce/payments/razorpay'
export function AppProviders({ children }: { children: React.ReactNode }) {
return (
<EcommerceProvider
paymentMethods={[
stripeAdapterClient(),
razorpayAdapterClient({ publishableKey: 'rzp_test_xxx' }),
]}
>
{children}
</EcommerceProvider>
)
}
function CartSummary() {
const { cart, addItem, removeItem, clearCart } = useCart()
const { currency, setCurrency } = useCurrency()
const { initiatePayment } = usePayments()
return (
<div>
<h2>Shopping Cart ({cart?.items?.length || 0} items)</h2>
<button onClick={() => setCurrency('EUR')}>Switch to EUR</button>
<button onClick={() => initiatePayment({ paymentMethod: 'stripe' })}>
Checkout
</button>
</div>
)
}Exported Hooks
useEcommerce(): Access full context including user, cart, config, and payment methods.useCart(): Methods toaddItem,removeItem,incrementItem,decrementItem, andclearCart.useCurrency(): Access active currency context (code,symbol,decimals) andsetCurrency.useAddresses(): Create and update user billing/shipping addresses.usePayments():initiatePaymentandconfirmOrderflow handlers.
📚 Tutorial: Setting Up & Using Multi-Currency
@shadowmkj/plugin-ecommerce provides end-to-end multi-currency support out of the box, including dynamic schema generation, administrative input controls, real-time cart subtotal recalculation, and client-side React hooks.
Follow this step-by-step tutorial to configure and use multi-currency in your project.
Step 1: Define Currencies in payload.config.ts
Configure your default currency and all supported currencies in your Payload CMS plugin configuration:
import { buildConfig } from 'payload'
import { ecommercePlugin } from '@shadowmkj/plugin-ecommerce'
export default buildConfig({
plugins: [
ecommercePlugin({
currencies: {
defaultCurrency: 'USD',
supportedCurrencies: [
{ code: 'USD', symbol: '$', label: 'US Dollar', decimals: 2 },
{ code: 'EUR', symbol: '€', label: 'Euro', decimals: 2 },
{ code: 'INR', symbol: '₹', label: 'Indian Rupee', decimals: 2 },
],
},
}),
],
})What happens under the hood: For every currency specified in
supportedCurrencies, the plugin automatically generates dedicated price input fields (priceInUSD,priceInEUR,priceInINR) on theproductsandvariantscollections in Payload Admin.
Step 2: Wrap Your Application with EcommerceProvider
In your Next.js / React application root layout, wrap your components with EcommerceProvider:
'use client'
import React from 'react'
import { EcommerceProvider } from '@shadowmkj/plugin-ecommerce/client/react'
export function Providers({ children }: { children: React.ReactNode }) {
return (
<EcommerceProvider>
{children}
</EcommerceProvider>
)
}Step 3: Build a Currency Switcher Component
Use the useCurrency() hook to display a currency dropdown selector:
'use client'
import React from 'react'
import { useCurrency } from '@shadowmkj/plugin-ecommerce/client/react'
export function CurrencySelector() {
const { currency, setCurrency, supportedCurrencies } = useCurrency()
return (
<select
value={currency.code}
onChange={(e) => setCurrency(e.target.value)}
aria-label="Select Currency"
>
{supportedCurrencies.map((c) => (
<option key={c.code} value={c.code}>
{c.label} ({c.symbol})
</option>
))}
</select>
)
}Step 4: Display Dynamic Product Prices
Create a price component that automatically resolves the active currency field on product or variant objects:
'use client'
import React from 'react'
import { useCurrency } from '@shadowmkj/plugin-ecommerce/client/react'
export function ProductPrice({ product }: { product: { [key: string]: any } }) {
const { currency } = useCurrency()
// Dynamically resolve the price field key (e.g. 'priceInUSD', 'priceInEUR')
const priceField = `priceIn${currency.code}`
const rawPrice = product[priceField] ?? 0
return (
<span className="product-price">
{currency.symbol}
{rawPrice.toFixed(currency.decimals)}
</span>
)
}Step 5: Add Items to Cart & Handle Automatic Currency Syncing
Use useCart() alongside useCurrency() to interact with the shopping cart. When a user switches currency, the plugin automatically updates the cart on the backend and recalculates the subtotal in the active currency:
'use client'
import React from 'react'
import { useCart, useCurrency } from '@shadowmkj/plugin-ecommerce/client/react'
export function ProductCard({ product }: { product: any }) {
const { addItem } = useCart()
const { currency } = useCurrency()
const priceField = `priceIn${currency.code}`
const price = product[priceField] ?? 0
return (
<div className="product-card">
<h3>{product.title}</h3>
<p>Price: {currency.symbol}{price.toFixed(currency.decimals)}</p>
<button onClick={() => addItem(product.id, 1)}>
Add to Cart
</button>
</div>
)
}Note on Automatic Subtotal Recalculation: When
setCurrency('EUR')is called:
- A
PATCHrequest updatescart.currencyto'EUR'on the backend.- The backend
beforeChangeCarthook fetches prices (priceInEUR) for all cart items and updatescart.subtotal.- The React provider refetches the cart with EUR price populates, seamlessly updating client state.
🖥️ Development & Scripts
# Build TypeScript declarations and SWC bundle
pnpm build
# Run unit tests with Vitest
npx vitest
# Run linter
pnpm lint📜 License
Distributed under the MIT License.
Developed by shadowmkj
