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
63 lines
1.6 KiB
TypeScript
63 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import type { PaymentMethod } from "@/lib/saleor/payments/types";
|
|
import { PaymentMethodCard } from "./PaymentMethodCard";
|
|
import { useTranslations } from "next-intl";
|
|
|
|
interface PaymentMethodSelectorProps {
|
|
methods: PaymentMethod[];
|
|
selectedMethodId: string;
|
|
onSelectMethod: (methodId: string) => void;
|
|
locale: string;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export function PaymentMethodSelector({
|
|
methods,
|
|
selectedMethodId,
|
|
onSelectMethod,
|
|
locale,
|
|
disabled = false,
|
|
}: PaymentMethodSelectorProps) {
|
|
const t = useTranslations("Payment");
|
|
|
|
// Filter to only available methods
|
|
const availableMethods = methods.filter((m) => m.available);
|
|
|
|
if (availableMethods.length === 0) {
|
|
return (
|
|
<div className="rounded-lg border border-gray-200 p-4 text-center text-gray-500">
|
|
{t("noMethodsAvailable")}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// If only one method, show it as selected but don't allow changing
|
|
const isSingleMethod = availableMethods.length === 1;
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<h3 className="text-lg font-medium">{t("title")}</h3>
|
|
|
|
<div className="space-y-3">
|
|
{availableMethods.map((method) => (
|
|
<PaymentMethodCard
|
|
key={method.id}
|
|
method={method}
|
|
isSelected={selectedMethodId === method.id}
|
|
onSelect={() => onSelectMethod(method.id)}
|
|
disabled={disabled || isSingleMethod}
|
|
locale={locale}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{isSingleMethod && (
|
|
<p className="text-sm text-gray-500">
|
|
{t("singleMethodNotice")}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|