From ff481f18c3da12f2296da9d3c3cf23e8cf9869b4 Mon Sep 17 00:00:00 2001 From: Unchained Date: Sun, 29 Mar 2026 06:02:51 +0200 Subject: [PATCH] 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 --- docs/COD-IMPLEMENTATION-PLAN.md | 320 ++++++++++++++++++ .../checkout/components/PaymentSection.tsx | 47 +++ src/app/[locale]/checkout/page.tsx | 51 ++- src/components/payment/CODInstructions.tsx | 55 +++ src/components/payment/PaymentMethodCard.tsx | 84 +++++ .../payment/PaymentMethodSelector.tsx | 62 ++++ src/components/payment/index.ts | 4 + src/i18n/messages/de.json | 33 +- src/i18n/messages/en.json | 33 +- src/i18n/messages/fr.json | 33 +- src/i18n/messages/sr.json | 33 +- src/lib/config/paymentMethods.ts | 106 ++++++ src/lib/saleor/payments/cod.ts | 149 ++++++++ src/lib/saleor/payments/types.ts | 62 ++++ 14 files changed, 1067 insertions(+), 5 deletions(-) create mode 100644 docs/COD-IMPLEMENTATION-PLAN.md create mode 100644 src/app/[locale]/checkout/components/PaymentSection.tsx create mode 100644 src/components/payment/CODInstructions.tsx create mode 100644 src/components/payment/PaymentMethodCard.tsx create mode 100644 src/components/payment/PaymentMethodSelector.tsx create mode 100644 src/components/payment/index.ts create mode 100644 src/lib/config/paymentMethods.ts create mode 100644 src/lib/saleor/payments/cod.ts create mode 100644 src/lib/saleor/payments/types.ts diff --git a/docs/COD-IMPLEMENTATION-PLAN.md b/docs/COD-IMPLEMENTATION-PLAN.md new file mode 100644 index 0000000..1bf42ea --- /dev/null +++ b/docs/COD-IMPLEMENTATION-PLAN.md @@ -0,0 +1,320 @@ +# Cash on Delivery (COD) Implementation Plan + +**Branch:** `feature/cash-on-delivery` +**Status:** In Development +**Created:** March 29, 2026 + +--- + +## 1. ARCHITECTURE DECISIONS + +### Payment Method Type: Simple Transaction +- Uses Saleor's native `Transaction` objects +- No Payment App required (COD is manual payment) +- Creates transaction with status `NOT_CHARGED` +- Staff marks as paid via Dashboard when cash collected + +### Why This Approach: +- ✅ Native Saleor data structures +- ✅ Appears in Dashboard automatically +- ✅ No metadata hacks +- ✅ Extensible to other simple payments (Bank Transfer) +- ✅ Compatible with Payment Apps later (Stripe, etc.) + +--- + +## 2. FILE STRUCTURE + +``` +src/ +├── lib/ +│ ├── config/ +│ │ └── paymentMethods.ts # Payment methods configuration +│ └── saleor/ +│ └── payments/ +│ ├── types.ts # Payment type definitions +│ ├── cod.ts # COD-specific logic +│ └── createTransaction.ts # Generic transaction creator +│ +├── components/ +│ └── payment/ +│ ├── PaymentMethodSelector.tsx # Payment method selection UI +│ ├── PaymentMethodCard.tsx # Individual payment card +│ └── CODInstructions.tsx # COD-specific instructions +│ +├── app/[locale]/checkout/ +│ ├── page.tsx # Updated checkout page +│ └── components/ +│ └── PaymentSection.tsx # Checkout payment section wrapper +│ +└── i18n/messages/ + ├── en.json # Payment translations + ├── sr.json # Payment translations + ├── de.json # Payment translations + └── fr.json # Payment translations +``` + +--- + +## 3. DATA MODELS + +### PaymentMethod Interface +```typescript +interface PaymentMethod { + id: string; + name: string; + description: string; + type: 'simple' | 'app'; + fee: number; + available: boolean; + availableInChannels: string[]; + icon?: string; +} +``` + +### COD Transaction Structure +```typescript +const codTransaction = { + name: "Cash on Delivery", + pspReference: `COD-${orderNumber}-${timestamp}`, + availableActions: ["CHARGE"], + amountAuthorized: { amount: 0, currency: "RSD" }, + amountCharged: { amount: 0, currency: "RSD" } +}; +``` + +--- + +## 4. IMPLEMENTATION PHASES + +### Phase 1: Configuration & Types (Files 1-3) +**Files:** +1. `lib/config/paymentMethods.ts` - Payment methods config +2. `lib/saleor/payments/types.ts` - Type definitions +3. `lib/saleor/payments/cod.ts` - COD transaction logic + +**Deliverables:** +- [ ] Payment methods configuration +- [ ] TypeScript interfaces +- [ ] COD transaction creation function + +### Phase 2: UI Components (Files 4-6) +**Files:** +4. `components/payment/PaymentMethodCard.tsx` +5. `components/payment/PaymentMethodSelector.tsx` +6. `components/payment/CODInstructions.tsx` + +**Deliverables:** +- [ ] Payment method selection UI +- [ ] COD instructions component +- [ ] Responsive design + +### Phase 3: Checkout Integration (Files 7-8) +**Files:** +7. `app/[locale]/checkout/components/PaymentSection.tsx` +8. `app/[locale]/checkout/page.tsx` (updated) + +**Deliverables:** +- [ ] Payment section in checkout +- [ ] Integration with checkout flow +- [ ] Transaction creation on complete + +### Phase 4: Translations (Files 9-12) +**Files:** +9-12. Update `i18n/messages/{en,sr,de,fr}.json` + +**Deliverables:** +- [ ] All translation keys +- [ ] Serbian, English, German, French + +### Phase 5: Testing +**Tasks:** +- [ ] Test COD flow end-to-end +- [ ] Verify transaction created in Saleor +- [ ] Test mobile responsiveness +- [ ] Test locale switching + +--- + +## 5. CHECKOUT FLOW + +``` +1. User adds items to cart + ↓ +2. User proceeds to checkout + ↓ +3. Checkout page loads with: + - Contact form (email, phone) + - Shipping address form + - Billing address form (same as shipping default) + - Shipping method selector + - PAYMENT METHOD SELECTOR (NEW) + └─ COD selected by default + - Order summary + - Complete Order button + ↓ +4. User fills all required fields + ↓ +5. User clicks "Complete Order" + ↓ +6. System: + a. Validates all fields + b. Creates order via checkoutComplete + c. Creates COD Transaction on order + d. Redirects to order confirmation + ↓ +7. Order Confirmation page shows: + - Order number + - Total amount + - Payment method: "Cash on Delivery" + - Instructions: "Please prepare cash for delivery" + ↓ +8. Staff sees order in Dashboard: + - Status: UNFULFILLED + - Payment Status: NOT_CHARGED + - Transaction: "Cash on Delivery (COD-123)" + ↓ +9. On delivery: + - Delivery person collects cash + - Staff marks order as FULFILLED in Dashboard + - (Optional: Create CHARGE_SUCCESS transaction event) +``` + +--- + +## 6. SALESOR DASHBOARD VIEW + +### Order Details: +``` +Order #1234 +├─ Status: UNFULFILLED +├─ Payment Status: NOT_CHARGED +├─ Transactions: +│ └─ Cash on Delivery (COD-1234-1743214567890) +│ ├─ Status: NOT_CHARGED +│ ├─ Amount: 3,200 RSD +│ └─ Available Actions: [CHARGE] +└─ Actions: [Fulfill] [Cancel] +``` + +### When Cash Collected: +``` +Staff clicks [Fulfill] + ↓ +Order Status: FULFILLED +Payment Status: (still NOT_CHARGED, but order is complete) +``` + +--- + +## 7. TRANSLATION KEYS + +### English (en.json): +```json +{ + "Payment": { + "title": "Payment Method", + "cod": { + "name": "Cash on Delivery", + "description": "Pay when you receive your order", + "instructions": { + "title": "Payment Instructions", + "prepareCash": "Please prepare the exact amount in cash", + "inspectOrder": "You can inspect your order before paying", + "noFee": "No additional fee for cash on delivery" + } + }, + "card": { + "name": "Credit Card", + "description": "Secure online payment", + "comingSoon": "Coming soon" + }, + "selectMethod": "Select payment method", + "securePayment": "Secure payment processing" + } +} +``` + +### Serbian (sr.json): +```json +{ + "Payment": { + "title": "Način Plaćanja", + "cod": { + "name": "Plaćanje Pouzećem", + "description": "Platite kada primite porudžbinu", + "instructions": { + "title": "Uputstva za Plaćanje", + "prepareCash": "Pripremite tačan iznos u gotovini", + "inspectOrder": "Možete pregledati porudžbinu pre plaćanja", + "noFee": "Bez dodatne naknade za plaćanje pouzećem" + } + } + } +} +``` + +--- + +## 8. TESTING CHECKLIST + +### Functional Tests: +- [ ] COD radio button selected by default +- [ ] Payment section visible in checkout +- [ ] Order completes with COD selected +- [ ] Transaction created with correct details +- [ ] Transaction visible in Saleor Dashboard +- [ ] Order confirmation shows COD +- [ ] Translations work in all locales + +### Edge Cases: +- [ ] Checkout validation fails - payment method preserved +- [ ] Network error during transaction creation +- [ ] User switches payment methods (when multiple available) +- [ ] Mobile viewport - payment section responsive + +### Integration Tests: +- [ ] End-to-end COD flow +- [ ] Order appears in Dashboard +- [ ] Staff can fulfill COD order +- [ ] Multiple payment methods display correctly + +--- + +## 9. FUTURE ENHANCEMENTS + +### Phase 2 (Post-MVP): +- [ ] Add Bank Transfer payment method +- [ ] Payment method icons +- [ ] Save payment preference for logged-in users + +### Phase 3 (Advanced): +- [ ] Bitcoin (manual) payment method +- [ ] Bitcoin (automated) via custom handler +- [ ] Payment Apps integration (Stripe, etc.) + +--- + +## 10. NOTES + +### Why No Metadata: +- Saleor has native Transaction objects +- Transactions are typed and validated +- Appear in Dashboard automatically +- Support proper lifecycle (NOT_CHARGED → CHARGED) + +### Why Simple Type (Not App): +- COD doesn't need async processing +- No external API to integrate +- No PCI compliance requirements +- Manual verification by staff + +### Compatibility: +- Current architecture supports Payment Apps later +- Can add Stripe/PayPal as `type: 'app'` without breaking COD +- Bitcoin can be added as `type: 'async'` when ready + +--- + +**Last Updated:** March 29, 2026 +**Next Review:** After Phase 1 completion \ No newline at end of file diff --git a/src/app/[locale]/checkout/components/PaymentSection.tsx b/src/app/[locale]/checkout/components/PaymentSection.tsx new file mode 100644 index 0000000..c54d11f --- /dev/null +++ b/src/app/[locale]/checkout/components/PaymentSection.tsx @@ -0,0 +1,47 @@ +"use client"; + +import { PaymentMethodSelector, CODInstructions } from "@/components/payment"; +import { getPaymentMethodsForChannel } from "@/lib/config/paymentMethods"; +import type { PaymentMethod } from "@/lib/saleor/payments/types"; +import { useTranslations } from "next-intl"; + +interface PaymentSectionProps { + selectedMethodId: string; + onSelectMethod: (methodId: string) => void; + locale: string; + channel?: string; + disabled?: boolean; +} + +export function PaymentSection({ + selectedMethodId, + onSelectMethod, + locale, + channel = "default-channel", + disabled = false, +}: PaymentSectionProps) { + const t = useTranslations("Payment"); + + // Get available payment methods for this channel + const paymentMethods: PaymentMethod[] = getPaymentMethodsForChannel(channel); + + // Get the selected method details + const selectedMethod = paymentMethods.find((m) => m.id === selectedMethodId); + + return ( +
+ + + {/* Show COD instructions when COD is selected */} + {selectedMethod?.id === "cod" && ( + + )} +
+ ); +} diff --git a/src/app/[locale]/checkout/page.tsx b/src/app/[locale]/checkout/page.tsx index efce7f9..d4aea15 100644 --- a/src/app/[locale]/checkout/page.tsx +++ b/src/app/[locale]/checkout/page.tsx @@ -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(null); const [sameAsShipping, setSameAsShipping] = useState(true); + const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(DEFAULT_PAYMENT_METHOD); const [shippingAddress, setShippingAddress] = useState({ 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() { )} + {/* Payment Method Section */} + +