Skip to main content

Version 6: Shipping Integration

Focus: 17track API, Tracking UI, Shipping Notifications


Overview

Version 6 completes the e-commerce platform with shipping tracking integration, enabling real-time package status updates and automated shipping notifications.


6A - 17track Integration

Objective

Real-time package tracking via 17track API.

Setup

File: src/lib/tracking.ts

const TRACK_API_URL = "https://api.17track.net/track/v2.2";

export async function registerTracking(
trackingNumber: string,
carrier?: string
) {
const response = await fetch(`${TRACK_API_URL}/register`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"17token": process.env.TRACK17_API_KEY!,
},
body: JSON.stringify([
{
number: trackingNumber,
carrier: carrier ? getCarrierCode(carrier) : undefined,
},
]),
});

return response.json();
}

export async function getTrackingInfo(trackingNumber: string) {
const response = await fetch(`${TRACK_API_URL}/gettrackinfo`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"17token": process.env.TRACK17_API_KEY!,
},
body: JSON.stringify([{ number: trackingNumber }]),
});

return response.json();
}

// Map common carrier names to 17track codes
function getCarrierCode(carrier: string): number | undefined {
const codes: Record<string, number> = {
ups: 100002,
fedex: 100003,
usps: 21051,
dhl: 100001,
};
return codes[carrier.toLowerCase()];
}

Schema Update

ALTER TABLE orders ADD COLUMN tracking_number TEXT;
ALTER TABLE orders ADD COLUMN carrier TEXT;
ALTER TABLE orders ADD COLUMN tracking_details JSONB;

CREATE INDEX idx_orders_tracking_number ON orders(tracking_number);

Tracking Details Structure

{
"status": "InTransit",
"substatus": "InTransit_PickedUp",
"lastEvent": "Package picked up by carrier",
"lastEventTime": "2024-01-15T10:30:00Z",
"events": [
{
"time": "2024-01-15T10:30:00Z",
"description": "Package picked up by carrier",
"location": "San Francisco, CA"
},
{
"time": "2024-01-14T15:00:00Z",
"description": "Label created",
"location": "San Francisco, CA"
}
]
}

6B - Admin Ship Order

Objective

Admin interface to mark orders as shipped.

API Endpoint

File: src/app/api/admin/orders/[id]/ship/route.ts

export async function POST(req: Request, { params }) {
const session = await auth();
if (session?.user?.role !== "admin") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}

const { id } = await params;
const { trackingNumber, carrier } = await req.json();

// Validate
if (!trackingNumber) {
return NextResponse.json(
{ error: "Tracking number required" },
{ status: 400 }
);
}

// Register with 17track
try {
await registerTracking(trackingNumber, carrier);
} catch (err) {
console.error("Failed to register tracking:", err);
// Continue anyway - tracking registration is optional
}

// Update order
const result = await query(
`
UPDATE orders
SET status = 'shipped',
tracking_number = $1,
carrier = $2,
updated_at = NOW()
WHERE id = $3 AND status = 'paid'
RETURNING id, email
`,
[trackingNumber, carrier, id]
);

if (result.rows.length === 0) {
return NextResponse.json(
{ error: "Order not found or not paid" },
{ status: 404 }
);
}

// Send shipping notification email
const order = result.rows[0];
await sendShippingNotificationEmail({
email: order.email,
orderId: order.id,
trackingNumber,
carrier,
});

return NextResponse.json({ success: true });
}

Admin UI

function ShipOrderModal({ order, onClose }) {
const [trackingNumber, setTrackingNumber] = useState("");
const [carrier, setCarrier] = useState("");

const handleShip = async () => {
await fetch(`/api/admin/orders/${order.id}/ship`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ trackingNumber, carrier }),
});
onClose();
};

return (
<div className="modal">
<h2>Ship Order #{order.id}</h2>
<input
placeholder="Tracking Number"
value={trackingNumber}
onChange={(e) => setTrackingNumber(e.target.value)}
/>
<select value={carrier} onChange={(e) => setCarrier(e.target.value)}>
<option value="">Select Carrier</option>
<option value="ups">UPS</option>
<option value="fedex">FedEx</option>
<option value="usps">USPS</option>
<option value="dhl">DHL</option>
</select>
<button onClick={handleShip}>Ship Order</button>
</div>
);
}

6C - Shipping Notification Email

Objective

Notify customers when their order ships.

Implementation

export async function sendShippingNotificationEmail({
email,
orderId,
trackingNumber,
carrier,
}: ShippingEmailParams) {
const trackingUrl = getTrackingUrl(carrier, trackingNumber);

await resend.emails.send({
from: "Mountify <shipping@mountify.com>",
to: email,
subject: `Your Order #${orderId} Has Shipped!`,
html: `
<h1>Your order is on its way!</h1>
<p>Great news! Your order #${orderId} has been shipped.</p>

<h2>Tracking Information</h2>
<p><strong>Carrier:</strong> ${carrier || "Standard Shipping"}</p>
<p><strong>Tracking Number:</strong> ${trackingNumber}</p>

${
trackingUrl
? `
<p>
<a href="${trackingUrl}" style="...">
Track Your Package →
</a>
</p>
`
: ""
}

<p>You can also track your order in your account.</p>
`,
});
}

function getTrackingUrl(
carrier: string,
trackingNumber: string
): string | null {
const urls: Record<string, string> = {
ups: `https://www.ups.com/track?tracknum=${trackingNumber}`,
fedex: `https://www.fedex.com/fedextrack/?tracknumbers=${trackingNumber}`,
usps: `https://tools.usps.com/go/TrackConfirmAction?tLabels=${trackingNumber}`,
dhl: `https://www.dhl.com/us-en/home/tracking.html?tracking-id=${trackingNumber}`,
};
return urls[carrier?.toLowerCase()] || null;
}

6D - Order Tracking UI

Objective

Display tracking status to customers.

Order Detail Page

export default function OrderDetailPage() {
const { orderId } = useParams();
const [order, setOrder] = useState(null);
const [tracking, setTracking] = useState(null);

useEffect(() => {
// Fetch order
fetch(`/api/orders/${orderId}`)
.then((res) => res.json())
.then((data) => setOrder(data.order));
}, [orderId]);

useEffect(() => {
// Fetch tracking if shipped
if (order?.tracking_number) {
fetch(`/api/orders/${orderId}/tracking`)
.then((res) => res.json())
.then((data) => setTracking(data.tracking));
}
}, [order?.tracking_number]);

return (
<div>
<h1>Order #{order?.id}</h1>

{/* Order status */}
<OrderStatusBadge status={order?.status} />

{/* Tracking timeline */}
{tracking && (
<TrackingTimeline events={tracking.events} status={tracking.status} />
)}

{/* Order items */}
<OrderItems items={order?.items} />
</div>
);
}

Tracking Timeline Component

function TrackingTimeline({ events, status }) {
return (
<div className="tracking-timeline">
<h3>Tracking History</h3>

{/* Current status */}
<div className="current-status">
<StatusIcon status={status} />
<span>{formatStatus(status)}</span>
</div>

{/* Event list */}
<ul className="events">
{events.map((event, i) => (
<li key={i} className="event">
<div className="event-dot" />
<div className="event-content">
<p className="event-description">{event.description}</p>
<p className="event-meta">
{event.location}{formatDate(event.time)}
</p>
</div>
</li>
))}
</ul>
</div>
);
}

function StatusIcon({ status }) {
const icons = {
Delivered: <CheckCircle className="text-green-500" />,
InTransit: <Truck className="text-blue-500" />,
OutForDelivery: <Package className="text-orange-500" />,
Exception: <AlertTriangle className="text-red-500" />,
};
return icons[status] || <Clock className="text-gray-500" />;
}

6E - Tracking API Endpoint

Objective

Fetch latest tracking info for an order.

export async function GET(req: Request, { params }) {
const session = await auth();
const { orderId } = await params;

// Get order
const orderResult = await query(
"SELECT tracking_number, user_id FROM orders WHERE id = $1",
[orderId]
);

if (orderResult.rows.length === 0) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}

const order = orderResult.rows[0];

// Authorization: owner or admin
if (
order.user_id !== parseInt(session?.user?.id) &&
session?.user?.role !== "admin"
) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}

if (!order.tracking_number) {
return NextResponse.json({ error: "No tracking" }, { status: 404 });
}

// Fetch from 17track
const trackingInfo = await getTrackingInfo(order.tracking_number);

// Cache in database
await query("UPDATE orders SET tracking_details = $1 WHERE id = $2", [
JSON.stringify(trackingInfo),
orderId,
]);

return NextResponse.json({ tracking: trackingInfo });
}

6F - Progressive Polling Optimization

Objective

Improve checkout success page polling strategy.

Problem

Fixed 2-second intervals caused:

  • Unnecessary server load when webhook is fast
  • Timeout before webhook arrives when slow
  • ~70% success rate

Solution: Progressive Intervals

function getPollingDelay(attempt: number): number {
if (attempt < 5) return 1000; // 1s × 5 = 5s (fast response)
if (attempt < 15) return 3000; // 3s × 10 = 30s (medium)
if (attempt < 25) return 5000; // 5s × 10 = 50s (slow)
return 0; // Stop at 85s total
}

Results

MetricBeforeAfter
Success rate~70%~95%
Fast webhook (< 5s)3 requests5 requests
Slow webhook (40s)Timeout23 requests, success
Max wait30s85s

Implementation

useEffect(() => {
let isMounted = true;
let timerId: NodeJS.Timeout;

const checkStatus = async (attempt: number) => {
if (!isMounted) return;

const res = await fetch(`/api/orders/session/${sessionId}`);
const data = await res.json();

setStatus(data.status);

if (data.status === "paid") return; // Success!

const delay = getPollingDelay(attempt + 1);
if (delay === 0) {
setTimedOut(true);
return;
}

timerId = setTimeout(() => checkStatus(attempt + 1), delay);
};

checkStatus(0);

return () => {
isMounted = false;
clearTimeout(timerId);
};
}, [sessionId]);

Order Status Flow

pending ──────▶ paid ──────▶ shipped ──────▶ delivered
│ │ │
│ │ └──▶ (17track updates)
│ │
▼ ▼
expired cancelled

Status Triggers

StatusTrigger
pendingOrder created at checkout
paidStripe webhook
expiredStripe session timeout
cancelledManual / Stripe failure
shippedAdmin adds tracking
delivered17track API update

Files Created

FilePurpose
src/lib/tracking.ts17track API client
src/app/api/admin/orders/[id]/ship/route.tsShip order endpoint
src/app/api/orders/[id]/tracking/route.tsGet tracking info
src/app/orders/[id]/page.tsxOrder detail with tracking
src/app/components/TrackingTimeline.tsxTracking UI

Integration Summary

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Admin │────▶│ API │────▶│ 17track │
│ "Ship Order"│ │ /ship │ │ API │
└─────────────┘ └──────┬──────┘ └─────────────┘

┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Update │ │ Send │ │ Register │
│ Order │ │ Email │ │ Tracking │
│ Status │ │ (Resend) │ │ (17track)│
└──────────┘ └──────────┘ └──────────┘

Production Deployment Notes

Environment Variables

# 17track
TRACK17_API_KEY=your_api_key

# Ensure these are set
RESEND_API_KEY=...
STRIPE_WEBHOOK_SECRET=...

Webhook Configuration

# Stripe webhook events to listen for
checkout.session.completed
checkout.session.expired

Rate Limits

ServiceLimit
17track100 requests/day (free tier)
Resend100 emails/day (free tier)

Platform Complete! 🎉

With Version 6, Mountify is a production-ready e-commerce platform:

  • ✅ User authentication (credentials + OAuth)
  • ✅ Product catalog with search and filters
  • ✅ Shopping cart
  • ✅ Secure checkout with Stripe
  • ✅ Order history
  • ✅ Admin dashboard
  • ✅ Inventory management
  • ✅ Email notifications
  • ✅ Shipping tracking
  • ✅ Performance optimization (caching)
  • ✅ Security (rate limiting, validation)

Future Enhancements

FeaturePriority
WishlistMedium
Product reviewsMedium
Coupon codesLow
Multi-currencyLow
Subscription productsLow
Mobile appFuture