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:
@@ -19,6 +19,9 @@ import {
|
||||
CHECKOUT_METADATA_UPDATE,
|
||||
CHECKOUT_SHIPPING_METHOD_UPDATE,
|
||||
} from "@/lib/saleor/mutations/Checkout";
|
||||
import { CREATE_TRANSACTION_MUTATION } from "@/lib/saleor/payments/cod";
|
||||
import { PaymentSection } from "./components/PaymentSection";
|
||||
import { DEFAULT_PAYMENT_METHOD } from "@/lib/config/paymentMethods";
|
||||
import { GET_CHECKOUT_BY_ID } from "@/lib/saleor/queries/Checkout";
|
||||
import type { Checkout } from "@/types/saleor";
|
||||
|
||||
@@ -38,7 +41,7 @@ interface BillingAddressUpdateResponse {
|
||||
|
||||
interface CheckoutCompleteResponse {
|
||||
checkoutComplete?: {
|
||||
order?: { number: string };
|
||||
order?: { id: string; number: string };
|
||||
errors?: Array<{ message: string }>;
|
||||
};
|
||||
}
|
||||
@@ -104,6 +107,7 @@ export default function CheckoutPage() {
|
||||
const [orderNumber, setOrderNumber] = useState<string | null>(null);
|
||||
|
||||
const [sameAsShipping, setSameAsShipping] = useState(true);
|
||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string>(DEFAULT_PAYMENT_METHOD);
|
||||
const [shippingAddress, setShippingAddress] = useState<AddressForm>({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
@@ -277,6 +281,11 @@ export default function CheckoutPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedPaymentMethod) {
|
||||
setError(t("errorSelectPayment"));
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
@@ -377,6 +386,37 @@ export default function CheckoutPage() {
|
||||
const order = completeResult.data?.checkoutComplete?.order;
|
||||
if (order) {
|
||||
setOrderNumber(order.number);
|
||||
|
||||
// Create COD transaction for the order
|
||||
if (selectedPaymentMethod === 'cod') {
|
||||
console.log("Step 6: Creating COD transaction...");
|
||||
try {
|
||||
await saleorClient.mutate({
|
||||
mutation: CREATE_TRANSACTION_MUTATION,
|
||||
variables: {
|
||||
id: order.id,
|
||||
transaction: {
|
||||
name: "Cash on Delivery",
|
||||
pspReference: `COD-${order.number}-${Date.now()}`,
|
||||
availableActions: ["CHARGE"],
|
||||
amountAuthorized: {
|
||||
amount: 0,
|
||||
currency: "RSD"
|
||||
},
|
||||
amountCharged: {
|
||||
amount: 0,
|
||||
currency: "RSD"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("Step 6: COD transaction created successfully");
|
||||
} catch (txError) {
|
||||
console.error("Failed to create COD transaction:", txError);
|
||||
// Don't fail the order if transaction creation fails
|
||||
}
|
||||
}
|
||||
|
||||
setOrderComplete(true);
|
||||
|
||||
// Track order completion
|
||||
@@ -660,6 +700,15 @@ export default function CheckoutPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Payment Method Section */}
|
||||
<PaymentSection
|
||||
selectedMethodId={selectedPaymentMethod}
|
||||
onSelectMethod={setSelectedPaymentMethod}
|
||||
locale={locale}
|
||||
channel="default-channel"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading || lines.length === 0 || !selectedShippingMethod}
|
||||
|
||||
Reference in New Issue
Block a user