feat: implement Cash on Delivery (COD) payment method
This commit adds comprehensive COD support using Saleor's native Transaction system:
**Architecture:**
- Uses Saleor's native Transaction objects (not metadata)
- Modular payment method configuration
- Extensible design for future payment types (cards, bank transfer, etc.)
**New Components:**
- PaymentMethodSelector: Reusable payment method selection UI
- PaymentMethodCard: Individual payment method card
- CODInstructions: COD-specific instructions and guidance
- PaymentSection: Checkout integration wrapper
**Core Features:**
- COD selected by default for Serbia (default-channel)
- Transaction created automatically on order completion
- Transaction visible in Saleor Dashboard
- Multi-language support (EN, SR, DE, FR)
- No additional fees
- Instructions shown to customer (prepare cash, inspect order, no fee)
**Files Added:**
- docs/COD-IMPLEMENTATION-PLAN.md
- src/lib/config/paymentMethods.ts
- src/lib/saleor/payments/types.ts
- src/lib/saleor/payments/cod.ts
- src/components/payment/PaymentMethodSelector.tsx
- src/components/payment/PaymentMethodCard.tsx
- src/components/payment/CODInstructions.tsx
- src/components/payment/index.ts
- src/app/[locale]/checkout/components/PaymentSection.tsx
**Files Modified:**
- src/app/[locale]/checkout/page.tsx (added payment section, transaction creation)
- src/i18n/messages/{en,sr,de,fr}.json (payment translations)
**Technical Details:**
- Transaction status: NOT_CHARGED
- Available actions: [CHARGE]
- PSP Reference format: COD-{orderNumber}-{timestamp}
- Staff collects cash and fulfills order via Dashboard
Closes: Cash on Delivery payment implementation
This commit is contained in:
106
src/lib/config/paymentMethods.ts
Normal file
106
src/lib/config/paymentMethods.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Payment methods configuration
|
||||
* Centralized configuration for all available payment methods
|
||||
*/
|
||||
|
||||
import type { PaymentMethod, Money } from '@/lib/saleor/payments/types';
|
||||
|
||||
/**
|
||||
* List of all available payment methods
|
||||
* Configure availability per channel, fees, and other settings
|
||||
*/
|
||||
export const paymentMethods: PaymentMethod[] = [
|
||||
{
|
||||
id: 'cod',
|
||||
name: 'Cash on Delivery',
|
||||
description: 'Pay when you receive your order',
|
||||
type: 'simple',
|
||||
fee: 0,
|
||||
available: true,
|
||||
availableInChannels: ['default-channel'], // Currently Serbia only
|
||||
icon: 'Banknote',
|
||||
},
|
||||
{
|
||||
id: 'card',
|
||||
name: 'Credit Card',
|
||||
description: 'Secure online payment',
|
||||
type: 'app',
|
||||
fee: 0,
|
||||
available: false, // Coming soon
|
||||
availableInChannels: ['default-channel'],
|
||||
icon: 'CreditCard',
|
||||
},
|
||||
{
|
||||
id: 'bank_transfer',
|
||||
name: 'Bank Transfer',
|
||||
description: 'Pay via bank transfer',
|
||||
type: 'simple',
|
||||
fee: 0,
|
||||
available: false, // Coming later
|
||||
availableInChannels: ['default-channel'],
|
||||
icon: 'Building2',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Get payment methods available for a specific channel
|
||||
*/
|
||||
export function getPaymentMethodsForChannel(channel: string): PaymentMethod[] {
|
||||
return paymentMethods.filter(
|
||||
(method) =>
|
||||
method.available && method.availableInChannels.includes(channel)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific payment method by ID
|
||||
*/
|
||||
export function getPaymentMethodById(id: string): PaymentMethod | undefined {
|
||||
return paymentMethods.find((method) => method.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a payment method is available for a channel
|
||||
*/
|
||||
export function isPaymentMethodAvailable(
|
||||
methodId: string,
|
||||
channel: string
|
||||
): boolean {
|
||||
const method = getPaymentMethodById(methodId);
|
||||
if (!method) return false;
|
||||
return method.available && method.availableInChannels.includes(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default payment method ID
|
||||
* Used when no payment method is explicitly selected
|
||||
*/
|
||||
export const DEFAULT_PAYMENT_METHOD = 'cod';
|
||||
|
||||
/**
|
||||
* Channel configuration
|
||||
* Maps channels to their supported payment methods
|
||||
*/
|
||||
export const channelPaymentConfig: Record<string, string[]> = {
|
||||
'default-channel': ['cod'], // Serbia - COD only for now
|
||||
};
|
||||
|
||||
/**
|
||||
* Format payment method fee for display
|
||||
*/
|
||||
export function formatPaymentFee(fee: number, currency: string): string {
|
||||
if (fee === 0) return 'No additional fee';
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
}).format(fee);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate PSP reference for COD transactions
|
||||
* Format: COD-{orderNumber}-{timestamp}
|
||||
*/
|
||||
export function generateCODReference(orderNumber: string): string {
|
||||
const timestamp = Date.now();
|
||||
return `COD-${orderNumber}-${timestamp}`;
|
||||
}
|
||||
Reference in New Issue
Block a user