npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wtree/payload-ecommerce-cod

v3.72.3

Published

Cash on Delivery (COD) payment adapter for Payload CMS Ecommerce Plugin

Readme

@wtree/payload-ecommerce-cod

npm version CI codecov License: MIT

Cash on Delivery (COD) payment adapter for Payload CMS Ecommerce Plugin. This adapter follows the same structure as the official Stripe adapter and integrates seamlessly with Payload's ecommerce plugin.

Features

  • ✅ Full Payload CMS Ecommerce Plugin compatibility
  • ✅ Configurable order limits (minimum/maximum)
  • ✅ Regional availability controls
  • ✅ Currency restrictions
  • ✅ Optional service charges (percentage or fixed)
  • ✅ Delivery status tracking
  • ✅ Payment collection tracking
  • ✅ Admin UI integration
  • ✅ TypeScript support
  • ✅ 80%+ test coverage

Installation

npm install @wtree/payload-ecommerce-cod

Usage

Server-side Configuration

Add the COD adapter to your Payload config:

import { ecommercePlugin } from '@payloadcms/plugin-ecommerce'
import { codAdapter } from '@wtree/payload-ecommerce-cod'

export default buildConfig({
  // ... other config
  plugins: [
    ecommercePlugin({
      payments: {
        paymentMethods: [
          codAdapter({
            label: 'Cash on Delivery',
            minimumOrder: 100, // $1.00 in cents
            maximumOrder: 50000, // $500.00 in cents
            allowedRegions: ['US', 'CA', 'IN'],
            supportedCurrencies: ['USD', 'INR'],
            serviceChargePercentage: 2, // 2% service charge
            fixedServiceCharge: 50, // $0.50 in cents
          }),
        ],
      },
    }),
  ],
})

Client-side Configuration

Add the COD adapter client to your React app:

import { EcommerceProvider } from '@payloadcms/plugin-ecommerce/client'
import { codAdapterClient } from '@wtree/payload-ecommerce-cod'

function App() {
  return (
    <EcommerceProvider
      paymentMethods={[
        codAdapterClient({
          label: 'Cash on Delivery',
        }),
      ]}
    >
      {/* Your app */}
    </EcommerceProvider>
  )
}

Configuration Options

Server-side Options

| Option | Type | Description | |--------|------|-------------| | label | string | Display label for the payment method (default: "Cash on Delivery") | | minimumOrder | number | Minimum order amount in smallest currency unit (e.g., cents) | | maximumOrder | number | Maximum order amount in smallest currency unit | | allowedRegions | string[] | Array of ISO 3166-1 alpha-2 country codes where COD is available | | supportedCurrencies | string[] | Array of ISO 4217 currency codes supported for COD | | serviceChargePercentage | number | Percentage service charge to add to orders | | fixedServiceCharge | number | Fixed service charge in smallest currency unit | | groupOverrides | object | Override default transaction fields |

Client-side Options

| Option | Type | Description | |--------|------|-------------| | label | string | Display label for the payment method (default: "Cash on Delivery") |

Transaction Fields

The COD adapter adds the following fields to transactions:

  • orderID: Unique COD order identifier
  • validationStatus: Order validation status (pending, validated, rejected)
  • deliveryStatus: Delivery tracking (preparing, dispatched, out_for_delivery, delivered, returned)
  • paymentCollected: Boolean flag for payment collection
  • collectionDate: Date when payment was collected

API Endpoints

The adapter uses Payload's built-in ecommerce endpoints:

  • POST /api/payments/cod/initiate - Initiate a COD order
  • POST /api/payments/cod/confirm-order - Confirm and create order

Example: Complete Checkout Flow

Basic Implementation

import { useEcommerce } from '@payloadcms/plugin-ecommerce/client'

function Checkout() {
  const { initiatePayment, confirmOrder } = useEcommerce()

  const handleCheckout = async () => {
    try {
      // Step 1: Initiate COD payment
      const initResult = await initiatePayment('cod', {
        additionalData: {
          // Additional data if needed
        },
      })

      console.log('COD Order ID:', initResult.orderID)

      // Step 2: Confirm order to complete purchase
      const confirmResult = await confirmOrder('cod', {
        additionalData: {
          orderID: initResult.orderID,
        },
      })

      console.log('Order confirmed:', confirmResult.orderID)
    } catch (error) {
      console.error('Checkout failed:', error)
    }
  }

  return (
    <button onClick={handleCheckout}>
      Complete Order (COD)
    </button>
  )
}

Advanced Implementation with Hooks

For more control over the checkout flow, use the ecommerce hooks:

import { useCart, useAddresses, usePayments } from '@payloadcms/plugin-ecommerce/client/react'

function AdvancedCheckout() {
  const cart = useCart()
  const { shippingAddress, billingAddress } = useAddresses()
  const { initiatePayment, confirmOrder } = usePayments()

  const handleCheckout = async () => {
    try {
      // Validate cart
      if (!cart || !cart.items || cart.items.length === 0) {
        throw new Error('Cart is empty')
      }

      // Validate addresses
      if (!shippingAddress) {
        throw new Error('Shipping address is required')
      }

      // Step 1: Initiate COD payment
      const initResult = await initiatePayment('cod', {
        cart,
        shippingAddress,
        billingAddress: billingAddress || shippingAddress,
      })

      console.log('Order initiated:', {
        orderID: initResult.orderID,
        serviceCharge: initResult.serviceCharge,
      })

      // Step 2: Confirm order
      const confirmResult = await confirmOrder('cod', {
        orderID: initResult.orderID,
      })

      console.log('Order confirmed:', {
        orderID: confirmResult.orderID,
        transactionID: confirmResult.transactionID,
      })

      // Order complete, redirect to success page
      window.location.href = `/order/${confirmResult.orderID}`
    } catch (error) {
      console.error('Checkout failed:', error.message)
    }
  }

  return (
    <div>
      <h2>Order Summary</h2>
      <p>Total: ${(cart?.subtotal / 100).toFixed(2)}</p>
      <button onClick={handleCheckout} disabled={!cart?.items?.length}>
        Place Order (Cash on Delivery)
      </button>
    </div>
  )
}

With Form Validation

import { useState } from 'react'
import { useCart, usePayments } from '@payloadcms/plugin-ecommerce/client/react'

function CheckoutWithValidation() {
  const cart = useCart()
  const { initiatePayment, confirmOrder } = usePayments()
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const handleCheckout = async (formData: FormData) => {
    setLoading(true)
    setError(null)

    try {
      // Step 1: Initiate payment
      const initResult = await initiatePayment('cod', {
        additionalData: {
          notes: formData.get('notes'),
          preferredDeliveryDate: formData.get('deliveryDate'),
        },
      })

      // Step 2: Confirm order
      const confirmResult = await confirmOrder('cod', {
        orderID: initResult.orderID,
      })

      // Success - clear cart and redirect
      console.log('Order placed successfully:', confirmResult.orderID)
      return confirmResult.orderID
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Checkout failed'
      setError(message)
      console.error('Checkout error:', err)
    } finally {
      setLoading(false)
    }
  }

  return (
    <form onSubmit={(e) => {
      e.preventDefault()
      handleCheckout(new FormData(e.currentTarget))
    }}>
      <div>
        <label>Special Instructions (optional)</label>
        <textarea name="notes" placeholder="Add delivery instructions..." />
      </div>
      <div>
        <label>Preferred Delivery Date</label>
        <input type="date" name="deliveryDate" />
      </div>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <button type="submit" disabled={loading || !cart?.items?.length}>
        {loading ? 'Processing...' : 'Place Order'}
      </button>
    </form>
  )
}

Admin Interface

The adapter integrates with Payload's admin UI, showing COD-specific fields in the transactions collection:

  • View and update delivery status
  • Track payment collection
  • Manage order validation

Validation

The adapter performs automatic validation:

  • ✅ Currency support check
  • ✅ Order amount limits (min/max)
  • ✅ Regional availability
  • ✅ Service charge calculation
  • ✅ Cart and customer data validation

Development

# Install dependencies
npm install

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

# Build the package
npm run build

# Watch mode for development
npm run dev

# Lint code
npm run lint

# Format code
npm run format

Publishing

Automated Publishing (Recommended)

This package uses GitHub Actions for automated publishing:

  1. Create a release using the workflow:

    • Go to Actions → Release workflow
    • Click "Run workflow"
    • Select version type (patch/minor/major) or specify version
    • The workflow will automatically:
      • Run tests
      • Build the package
      • Bump version
      • Create git tag
      • Trigger npm publish
  2. Or manually tag a version:

    npm version patch  # or minor, major
    git push origin main --follow-tags

Manual Publishing

# Prepare release
./scripts/prepare-release.sh patch  # or minor, major

# Review changes
git log

# Push to trigger automated publish
git push origin main --follow-tags

Setup Requirements

To enable automated publishing, add the following secrets to your GitHub repository:

  1. NPM_TOKEN: Your npm access token

    • Generate at: https://www.npmjs.com/settings/YOUR_USERNAME/tokens
    • Add to: Repository Settings → Secrets → Actions
  2. CODECOV_TOKEN (optional): For coverage reporting

    • Generate at: https://codecov.io/gh/technewwings/payload-ecommerce-cod
    • Add to: Repository Settings → Secrets → Actions

Comparison with Stripe Adapter

This adapter follows the exact same structure as Payload's official Stripe adapter:

  • ✅ Implements PaymentAdapter interface
  • ✅ Provides initiatePayment and confirmOrder methods
  • ✅ Uses GroupField for admin UI integration
  • ✅ Supports transaction tracking
  • ✅ Compatible with Payload's ecommerce hooks

Implementation Verification

Server-side Implementation ✅

The adapter correctly implements the PaymentAdapter interface with:

  1. initiatePayment: Creates a transaction and validates:

    • Currency support (if supportedCurrencies is configured)
    • Order amount limits (minimum and maximum)
    • Regional availability (if allowedRegions is configured)
    • Calculates service charges (percentage and/or fixed)
    • Generates unique COD order ID
  2. confirmOrder: Completes the payment flow:

    • Verifies existing transaction by COD order ID
    • Creates order with cart items and addresses
    • Updates cart as purchased
    • Updates transaction status to 'succeeded'
    • Returns order and transaction IDs
  3. Transaction Fields:

    • cod.orderID: Unique COD identifier
    • cod.validationStatus: Order validation state
    • cod.deliveryStatus: Delivery tracking
    • cod.paymentCollected: Payment confirmation
    • cod.collectionDate: Payment date

Client-side Implementation ✅

The adapter provides a client-compatible implementation with:

  1. codAdapterClient: Exposes payment method capabilities

    • name: 'cod' (payment method identifier)
    • label: Display name (customizable)
    • initiatePayment: Boolean flag indicating support
    • confirmOrder: Boolean flag indicating support
  2. Hook Compatibility: Works with Payload ecommerce hooks

    • usePayments(): Initiates and confirms payments
    • useCart(): Access cart data and amounts
    • useAddresses(): Access shipping/billing addresses
    • useEcommerce(): Generic ecommerce context

Type Safety ✅

All exports are properly typed:

export type CODAdapterArgs = { /* configuration options */ }
export type CODAdapterClientArgs = PaymentAdapterClientArgs
export type InitiatePaymentReturnType = {
  message: string
  orderID: string
  serviceCharge?: number
}
export type ConfirmOrderReturnType = {
  message: string
  orderID: string
  transactionID: string
}

Contributing

See CONTRIBUTING.md for development guidelines.

License

MIT - See LICENSE for details.

Support

For issues and questions:

Changelog

See CHANGELOG.md for version history.