CODENIK

Menu

Payload CMS cash on delivery integration

The Ecommerce Plugin now supports Cash on Delivery (COD) as a payment method through the built-in codAdapter.

This guide explains how to migrate from the default ecommerce plugin, configure the COD adapter, and update your storefront checkout flow.

Prerequisites

Create a new Payload Ecommerce application

npx create-payload-app

Select: ecommerce and Complete the installation process and start the application.

Replace the Ecommerce Plugin

The default ecommerce template installs:

"@payloadcms/plugin-ecommerce": "^3.85.1"

Remove the official package:

pnpm remove @payloadcms/plugin-ecommerce

Install the updated Ecommerce Plugin with COD support:

pnpm add @shadowmkj/plugin-ecommerce

Update Imports

Replace all imports from:

@payloadcms/plugin-ecommerce

with:

@shadowmkj/plugin-ecommerce

For example:

import { ecommercePlugin } from '@shadowmkj/plugin-ecommerce'

Configure Payment Adapters

Open your Payload configuration and register both Stripe and COD payment methods.

payments: {
  paymentMethods: [
    stripeAdapter({
      secretKey: process.env.STRIPE_SECRET_KEY!,
      publishableKey:
        process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
      webhookSecret:
        process.env.STRIPE_WEBHOOKS_SIGNING_SECRET!,
    }),

    codAdapter({}),
  ],
},

Example:

import {
  ecommercePlugin,
  stripeAdapter,
  codAdapter,
} from '@shadowmkj/plugin-ecommerce'

plugins: [
  ecommercePlugin({
    payments: {
      paymentMethods: [
        stripeAdapter({
          secretKey: process.env.STRIPE_SECRET_KEY!,
          publishableKey:
            process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
          webhookSecret:
            process.env.STRIPE_WEBHOOKS_SIGNING_SECRET!,
        }),

        codAdapter({}),
      ],
    },
  }),
]

Configure Frontend Payment Providers

Update your Ecommerce Provider configuration.

Replace existing payment methods with:

paymentMethods={[
  stripeAdapterClient({
    publishableKey:
      process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || '',
  }),

  codAdapterClient({}),
]}

Example:

<EcommerceProvider
  paymentMethods={[
    stripeAdapterClient({
      publishableKey:
        process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY || '',
    }),

    codAdapterClient({}),
  ]}
>
  {children}
</EcommerceProvider>

Update Checkout Payment Flow

Update your checkout page to support COD orders.

Replace your existing payment initialization logic with:

const initiatePaymentIntent = useCallback(
  async (method: string) => {
    try {
      const paymentData = await initiatePayment(method, {
        additionalData: {
          ...(email ? { customerEmail: email } : {}),
          billingAddress,
          shippingAddress: billingAddressSameAsShipping
            ? billingAddress
            : shippingAddress,
        },
      })

      if (!paymentData) return

      setPaymentData(paymentData)

      if (method === 'cod') {
        const result = await confirmOrder('cod', {
          additionalData: {
            paymentIntentID: paymentData.paymentIntentID,
          },
        })

        if (result?.orderID) {
          clearCart()

          const queryParams = new URLSearchParams()

          if (email) {
            queryParams.set('email', email)
          }

          if (result.accessToken) {
            queryParams.set(
              'accessToken',
              result.accessToken,
            )
          }

          router.push(
            `/orders/${result.orderID}?${queryParams.toString()}`
          )
        }
      }
    } catch (error) {
      const errorData =
        error instanceof Error
          ? JSON.parse(error.message)
          : {}

      let errorMessage = 'Payment failed'

      if (errorData?.cause?.code === 'OutOfStock') {
        errorMessage = 'Some items are out of stock'
      }

      setError(errorMessage)
      toast.error(errorMessage)
    }
  },
  [
    email,
    billingAddress,
    shippingAddress,
    billingAddressSameAsShipping,
    initiatePayment,
    confirmOrder,
  ],
)

Add Cash on Delivery to Checkout

Add a payment method selector to your checkout page.

<div className="space-y-3">
  <h2 className="font-medium text-3xl">
    Payment Method
  </h2>

  <label className="flex items-center gap-2">
    <input
      type="radio"
      checked={paymentMethod === 'stripe'}
      disabled={!canGoToPayment}
      onChange={() => setPaymentMethod('stripe')}
    />
    Pay Online (Stripe)
  </label>

  <label className="flex items-center gap-2">
    <input
      type="radio"
      checked={paymentMethod === 'cod'}
      disabled={!canGoToPayment}
      onChange={() => setPaymentMethod('cod')}
    />
    Cash on Delivery
  </label>

  <Button
    disabled={!canGoToPayment}
    onClick={() => {
      if (paymentMethod === 'stripe') {
        void initiatePaymentIntent('stripe')
      } else {
        void initiatePaymentIntent('cod')
      }
    }}
  >
    Continue
  </Button>
</div>

COD Order Flow

Customer
   │
   ▼
Checkout
   │
   ▼
Select Cash on Delivery
   │
   ▼
initiatePayment('cod')
   │
   ▼
Create COD Payment Intent
   │
   ▼
confirmOrder('cod')
   │
   ▼
Order Created
   │
   ▼
Cart Cleared
   │
   ▼
Redirect To Order Page

Payment Status

Orders created through the COD adapter are not charged immediately.

Initial order state:

{
  "paymentMethod": "cod",
  "paymentStatus": "pending"
}

After payment is collected during delivery:

{
  "paymentMethod": "cod",
  "paymentStatus": "paid"
}

Migration Checklist

  • Create a Payload Ecommerce project
  • Remove @payloadcms/plugin-ecommerce
  • Install @shadowmkj/plugin-ecommerce
  • Update all imports
  • Register codAdapter
  • Register codAdapterClient
  • Update checkout payment flow
  • Add Cash on Delivery payment option to the checkout page
  • Test order creation and confirmation flow

You're Ready

After completing these steps, customers can choose Cash on Delivery during checkout and place orders without making an online payment. The Ecommerce Plugin will create the order, track the payment status, and allow payment collection during delivery.