← 활용 사례

주문 및 배송 추적

스스로 갱신되는 상태 페이지와 지도 위를 움직이는 배달원. 위치는 고객이 보고 있을 때만 보내고, 중요한 단계에서는 푸시를 보냅니다.

“내 주문 어디쯤 왔나요?”는 배달, 차량 호출, 음식 주문, 방문 서비스 앱이 가장 많이 받는 질문입니다. 스스로 갱신되는 상태 페이지와 지도 위에서 실제로 움직이는 배달원이 있으면, 누군가 고객센터에 문의하기 전에 답이 됩니다. 같은 방식은 수리 접수, 예약 등 고객이 단계를 기다리는 모든 것에 쓸 수 있습니다.

사용자가 얻는 것

  • 주문 상태가 “준비 중”에서 “배달 중”, “배달 완료”로 저절로 바뀝니다.
  • 배달원의 위치가 몇 초마다 지도 위에서 움직입니다.
  • 앱이 닫혀 있어도 중요한 단계에서는 푸시 알림이 옵니다.

사용하는 기능

  • 프라이빗 채널(private-order-<id>): 주문을 따라갈 수 있는 것은 고객과 직원뿐입니다.
  • 캐시 채널(private-cache-): 페이지를 연 고객에게 최신 상태와 위치를 바로 넘겨 줍니다.
  • channel_vacated 웹훅: 아무도 보고 있지 않으면 위치 전송을 멈춥니다.
  • 푸시 알림(Beams 호환): 놓치면 안 되는 단계에.

1단계: 주문 채널 인가

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));
});

2단계: 상태 변경과 위치 발행

상태 변경은 주문 시스템에서 옵니다. 위치는 배달원 앱이 서버에 보고하고, 누군가 보고 있는 동안에만 서버가 전달합니다.

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);
});

3단계: 앱에 표시

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

캐시 채널은 이벤트 이름과 상관없이 마지막 이벤트 하나만 보관합니다. 두 업데이트 사이에 들어온 고객도 상태와 위치를 모두 받도록, location 이벤트에도 현재 상태를 함께 넣으세요.

4단계: 보고 있을 때만 보내기

웹훅 엔드포인트에서 channel_occupied와 channel_vacated를 받습니다. 고객이 페이지를 닫으면 채널이 비고, 서버는 위치 전달을 멈춥니다.

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);
}

watching은 Redis나 데이터베이스처럼 모든 서버가 공유하는 곳에 두세요.

5단계: 중요한 단계는 푸시로

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}` } },
});

기기 설정은 푸시 알림 가이드를 참고하세요.

팁

  • 지도가 지점 사이를 애니메이션으로 이어 주면 3~5초마다 한 번의 위치로도 부드럽게 보이고, 매초 보내는 것보다 훨씬 저렴합니다.
  • 좌표는 소수점 다섯째 자리(약 1미터)로 반올림하세요. 그 이상의 정밀도는 바이트만 늘립니다.
  • 배달이 끝나면 바로 위치 전송을 멈추고, 늦게 온 사람을 위한 최종 상태는 캐시에 맡기세요.