Created ORDER_CALCULATION_PLAN.md with: - Backend endpoints documentation - Frontend implementation steps - Code examples for OrderForm.tsx - Testing checklist - Expected results Next: Implement frontend integration
188 lines
5.1 KiB
Markdown
188 lines
5.1 KiB
Markdown
# Order Calculation - WooCommerce Native Implementation
|
|
|
|
## ✅ BACKEND COMPLETE
|
|
|
|
### New Endpoints:
|
|
|
|
1. **POST `/woonoow/v1/shipping/calculate`**
|
|
- Input: `{ items: [], shipping: {} }`
|
|
- Output: `{ methods: [{ id, method_id, instance_id, label, cost, taxes, meta_data }] }`
|
|
- Returns live rates from UPS, FedEx, etc.
|
|
- Returns service-level options (UPS Ground, UPS Express)
|
|
|
|
2. **POST `/woonoow/v1/orders/preview`**
|
|
- Input: `{ items: [], billing: {}, shipping: {}, shipping_method: '', coupons: [] }`
|
|
- Output: `{ subtotal, shipping_total, total_tax, total, ... }`
|
|
- Calculates taxes correctly
|
|
- Applies coupons
|
|
- Uses WooCommerce cart engine
|
|
|
|
---
|
|
|
|
## 🔄 FRONTEND TODO
|
|
|
|
### 1. Update OrderForm.tsx
|
|
|
|
#### A. Add Shipping Rate Calculation Query
|
|
|
|
```tsx
|
|
// Query shipping rates when address changes
|
|
const { data: shippingRates, refetch: refetchShipping } = useQuery({
|
|
queryKey: ['shipping-rates', items, shippingData],
|
|
queryFn: async () => {
|
|
if (!hasPhysicalProduct || !shippingData.country) return null;
|
|
return api.post('/shipping/calculate', {
|
|
items: items.map(i => ({ product_id: i.product_id, qty: i.qty })),
|
|
shipping: shippingData,
|
|
});
|
|
},
|
|
enabled: hasPhysicalProduct && !!shippingData.country,
|
|
});
|
|
```
|
|
|
|
#### B. Add Order Preview Query
|
|
|
|
```tsx
|
|
// Query order preview for totals
|
|
const { data: orderPreview } = useQuery({
|
|
queryKey: ['order-preview', items, bCountry, shippingData, shippingMethod, validatedCoupons],
|
|
queryFn: async () => {
|
|
if (items.length === 0) return null;
|
|
return api.post('/orders/preview', {
|
|
items: items.map(i => ({ product_id: i.product_id, qty: i.qty })),
|
|
billing: { country: bCountry, state: bState, postcode: bPost, city: bCity },
|
|
shipping: shipDiff ? shippingData : undefined,
|
|
shipping_method: shippingMethod,
|
|
coupons: validatedCoupons.map(c => c.code),
|
|
});
|
|
},
|
|
enabled: items.length > 0,
|
|
});
|
|
```
|
|
|
|
#### C. Update Shipping Method Dropdown
|
|
|
|
**Current:**
|
|
```tsx
|
|
<Select value={shippingMethod} onValueChange={setShippingMethod}>
|
|
{shippings.map(s => (
|
|
<SelectItem value={s.id}>{s.title} - {s.cost}</SelectItem>
|
|
))}
|
|
</Select>
|
|
```
|
|
|
|
**New:**
|
|
```tsx
|
|
<Select value={shippingMethod} onValueChange={setShippingMethod}>
|
|
{shippingRates?.methods?.map(rate => (
|
|
<SelectItem value={rate.id}>
|
|
{rate.label} - {money(rate.cost)}
|
|
</SelectItem>
|
|
))}
|
|
</Select>
|
|
```
|
|
|
|
#### D. Update Order Summary Display
|
|
|
|
**Add tax breakdown:**
|
|
```tsx
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between">
|
|
<span>Items</span>
|
|
<span>{items.length}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span>Subtotal</span>
|
|
<span>{money(orderPreview?.subtotal || 0)}</span>
|
|
</div>
|
|
{orderPreview?.shipping_total > 0 && (
|
|
<div className="flex justify-between">
|
|
<span>Shipping</span>
|
|
<span>{money(orderPreview.shipping_total)}</span>
|
|
</div>
|
|
)}
|
|
{orderPreview?.total_tax > 0 && (
|
|
<div className="flex justify-between">
|
|
<span>Tax</span>
|
|
<span>{money(orderPreview.total_tax)}</span>
|
|
</div>
|
|
)}
|
|
{orderPreview?.discount_total > 0 && (
|
|
<div className="flex justify-between text-green-600">
|
|
<span>Discount</span>
|
|
<span>-{money(orderPreview.discount_total)}</span>
|
|
</div>
|
|
)}
|
|
<div className="flex justify-between font-bold text-lg border-t pt-2">
|
|
<span>Total</span>
|
|
<span>{money(orderPreview?.total || 0)}</span>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
#### E. Trigger Recalculation
|
|
|
|
```tsx
|
|
// Refetch shipping when address changes
|
|
useEffect(() => {
|
|
if (hasPhysicalProduct && shippingData.country) {
|
|
refetchShipping();
|
|
}
|
|
}, [shippingData.country, shippingData.postcode, shippingData.state]);
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 Implementation Steps
|
|
|
|
1. ✅ Backend endpoints created
|
|
2. ⏳ Add shipping rate calculation query
|
|
3. ⏳ Add order preview query
|
|
4. ⏳ Update shipping method dropdown to show services
|
|
5. ⏳ Update order summary to show tax
|
|
6. ⏳ Add loading states
|
|
7. ⏳ Test with UPS Live Rates
|
|
8. ⏳ Test tax calculation
|
|
|
|
---
|
|
|
|
## 🎯 Expected Result
|
|
|
|
### Before:
|
|
- Shipping: "UPS Live Rates - RM0.00"
|
|
- Total: RM97,000 (no tax)
|
|
|
|
### After:
|
|
- Shipping dropdown shows:
|
|
- UPS Ground - RM15,000
|
|
- UPS Express - RM25,000
|
|
- UPS Next Day Air - RM35,000
|
|
- Order summary shows:
|
|
- Subtotal: RM97,000
|
|
- Shipping: RM15,000
|
|
- Tax (11%): RM12,320
|
|
- **Total: RM124,320**
|
|
|
|
---
|
|
|
|
## 🔧 Testing Checklist
|
|
|
|
- [ ] Select UPS Live Rates → Shows service options
|
|
- [ ] Select UPS Ground → Updates total
|
|
- [ ] Change address → Recalculates rates
|
|
- [ ] Add item → Recalculates totals
|
|
- [ ] Apply coupon → Updates discount and total
|
|
- [ ] Tax shows 11% of subtotal + shipping
|
|
- [ ] Digital products → No shipping, no shipping tax
|
|
- [ ] Physical products → Shipping + tax calculated
|
|
|
|
---
|
|
|
|
## ⚠️ Important Notes
|
|
|
|
1. **Don't reinvent calculation** - Use WooCommerce cart engine
|
|
2. **Clean up cart** - Always `WC()->cart->empty_cart()` after calculation
|
|
3. **Session handling** - Use `WC()->session` for chosen shipping method
|
|
4. **Tax context** - Set both billing and shipping addresses for accurate tax
|
|
5. **Live rates** - May take 1-2 seconds to calculate, show loading state
|