← Tình huống

Theo dõi đơn hàng và giao hàng

Trang trạng thái tự cập nhật và tài xế di chuyển trên bản đồ, chỉ gửi vị trí khi khách đang xem, kèm thông báo đẩy ở những bước quan trọng.

“Đơn hàng của tôi đang ở đâu?” là câu hỏi mà ứng dụng giao hàng, gọi xe, đặt đồ ăn và dịch vụ tại nhà nghe nhiều nhất. Một trang trạng thái tự cập nhật, cùng một tài xế thực sự di chuyển trên bản đồ, trả lời câu hỏi đó trước khi có ai phải liên hệ bộ phận hỗ trợ. Cách làm này cũng dùng được cho phiếu sửa chữa, lịch hẹn và mọi thứ có nhiều bước mà khách phải chờ.

Người dùng nhận được gì

  • Trạng thái đơn tự chuyển từ “đang chuẩn bị” sang “đang giao” rồi “đã giao”.
  • Vị trí của tài xế di chuyển trên bản đồ vài giây một lần.
  • Thông báo đẩy ở những bước quan trọng khi ứng dụng đang đóng.

Dùng những gì

  • Kênh riêng tư (private-order-<id>), để chỉ khách hàng và nhân viên của bạn theo dõi được đơn hàng.
  • Kênh cache (private-cache-), để khách mở trang là có ngay trạng thái và vị trí mới nhất.
  • Webhook channel_vacated, để ngừng gửi vị trí khi không ai xem.
  • Thông báo đẩy (tương thích Beams) cho những bước khách không được bỏ lỡ.

Bước 1: cấp quyền kênh đơn hàng

app.post('/pusher/auth', requireLogin, express.urlencoded({ extended: false }), async (req, res) => {
  const { socket_id, channel_name } = req.body;
  const id = channel_name.match(/^private-cache-order-(\d+)$/)?.[1];
  const order = id && (await db.orders.find(id));
  if (!order || (order.customerId !== req.user.id && !req.user.isStaff)) return res.sendStatus(403);
  res.json(pusher.authorizeChannel(socket_id, channel_name));
});

Bước 2: phát thay đổi trạng thái và vị trí

Thay đổi trạng thái đến từ hệ thống đơn hàng của bạn. Vị trí đến từ ứng dụng của tài xế, gửi về máy chủ, và máy chủ chỉ chuyển tiếp khi có người đang xem:

async function setStatus(order, status) {
  await db.orders.update(order.id, { status });
  await pusher.trigger(`private-cache-order-${order.id}`, 'status', { status, at: Date.now() });
}

app.post('/courier/location', requireCourier, async (req, res) => {
  const order = await db.orders.activeFor(req.user.id);
  if (order && watching.has(order.id)) {
    await pusher.trigger(`private-cache-order-${order.id}`, 'location', { lat: req.body.lat, lng: req.body.lng });
  }
  res.sendStatus(204);
});

Bước 3: hiển thị trong ứng dụng

const order = pusher.subscribe(`private-cache-order-${orderId}`);
order.bind('status', ({ status }) => timeline.set(status));
order.bind('location', ({ lat, lng }) => map.moveCourier(lat, lng));

Kênh cache chỉ giữ sự kiện cuối cùng, bất kể tên là gì. Để khách vào giữa hai lần cập nhật vẫn nhận được cả trạng thái lẫn vị trí, hãy kèm trạng thái hiện tại trong mỗi sự kiện location.

Bước 4: chỉ gửi khi có người xem

Cho endpoint webhook nhận channel_occupied và channel_vacated. Khi khách đóng trang, kênh trống và máy chủ của bạn ngừng chuyển tiếp vị trí:

for (const e of webhook.getEvents()) {
  const id = e.channel.match(/^private-cache-order-(\d+)$/)?.[1];
  if (!id) continue;
  if (e.name === 'channel_occupied') watching.add(id);
  if (e.name === 'channel_vacated') watching.delete(id);
}

Đặt watching ở nơi mọi máy chủ của bạn cùng dùng được, như Redis hoặc cơ sở dữ liệu.

Bước 5: đẩy thông báo ở những bước quan trọng

await beams.publishToUsers([String(order.customerId)], {
  fcm: { notification: { title: 'Your order is on the way', body: 'Arriving in about 15 minutes.' } },
  apns: { aps: { alert: { title: 'Your order is on the way', body: 'Arriving in about 15 minutes.' } } },
  web: { notification: { title: 'Your order is on the way', deep_link: `https://your-site.example/orders/${order.id}` } },
});

Thiết lập thiết bị theo hướng dẫn thông báo đẩy.

Mẹo

  • Gửi vị trí mỗi 3 đến 5 giây trông đã mượt khi bản đồ chuyển động giữa các điểm, và chỉ tốn một phần nhỏ so với mỗi giây một lần.
  • Làm tròn tọa độ đến năm chữ số thập phân (khoảng một mét); chính xác hơn chỉ thêm byte.
  • Ngừng gửi vị trí ngay khi giao xong, và để cache giữ trạng thái cuối cho người vào muộn.