← ユースケース

注文と配達の追跡

自動で更新されるステータスページと地図上を動く配達員。位置はお客様が見ている間だけ送り、大事なステップではプッシュ通知を送ります。

「注文はいまどこ?」は、配達、配車、フードデリバリー、出張サービスのアプリが一番よく聞かれる質問です。自動で更新されるステータスページと、地図上で実際に動く配達員があれば、サポートに問い合わせが来る前に答えられます。同じ方法は修理の受付、予約など、お客様が段階を待つあらゆるものに使えます。

ユーザーにとっての価値

  • 注文のステータスが「準備中」から「配達中」、「配達済み」へと自動で変わります。
  • 配達員の位置が数秒ごとに地図上で動きます。
  • アプリを閉じていても、大事なステップではプッシュ通知が届きます。

使う機能

  • プライベートチャンネル(private-order-<id>)。注文を追えるのはお客様とスタッフだけです。
  • キャッシュチャンネル(private-cache-)。ページを開いたお客様に、最新のステータスと位置をすぐ渡します。
  • channel_vacated Webhook。誰も見ていないときは位置の送信を止めます。
  • プッシュ通知(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));

キャッシュチャンネルが保持するのは、名前に関係なく最後のイベント 1 件だけです。2 つの更新の間に来たお客様もステータスと位置の両方を受け取れるよう、location イベントにも現在のステータスを含めましょう。

ステップ 4:見ている間だけ送る

Webhook のエンドポイントで 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 秒ごとの位置でも滑らかに見え、毎秒送るよりずっと安く済みます。
  • 座標は小数点以下 5 桁(約 1 メートル)に丸めましょう。それ以上の精度はバイト数が増えるだけです。
  • 配達が済んだらすぐに位置の送信を止め、遅れて来た人のための最終ステータスはキャッシュに任せます。