Rebasely Beta: data may be purged

examples/storefront Public

TypeScript · 50 lines · 2.3 KiB · at 8b37e66

// Unpaid orders hold stock (checkout takes it), so they cannot wait forever. Every five minutes// the cron trigger cancels orders still pending_payment after PAYMENT_WINDOW_MS, cancelling the// PaymentIntent first so a late payment cannot land on a cancelled order.import type { OrderEvent } from "./events.ts";import { cancelOrder } from "./orders.ts";import type { Payments } from "./payments.ts";export const PAYMENT_WINDOW_MS = 30 * 60 * 1000;const BATCH = 50;// One run looks at no more than this many orders, so its calls to Stripe stay bounded. The next// run starts again from the oldest.export const MAX_PER_RUN = 200;export type ExpiryReport = { cancelled: string[]; left_to_webhook: string[] };type Expired = { id: string; payment_intent_id: string | null; created_at: number };export async function expireOrders(db: D1Database, events: Queue<OrderEvent>, payments: Payments, now: number): Promise<ExpiryReport> {  const report: ExpiryReport = { cancelled: [], left_to_webhook: [] };  // Orders left to the webhook stay pending_payment, so a query that always starts at the oldest  // order would keep returning them and never reach the ones behind. The run pages through the  // expired orders by (created_at, id) instead, past every order it has already looked at.  let after = null as Expired | null;  let seen = 0;  while (seen < MAX_PER_RUN) {    const { results } = await db      .prepare(        `SELECT id, payment_intent_id, created_at FROM orders         WHERE status = 'pending_payment' AND created_at < ?1           AND (?2 IS NULL OR (created_at, id) > (?2, ?3))         ORDER BY created_at, id LIMIT ?4`,      )      .bind(now - PAYMENT_WINDOW_MS, after?.created_at ?? null, after?.id ?? null, Math.min(BATCH, MAX_PER_RUN - seen))      .all<Expired>();    for (const o of results) {      // The intent succeeded or is processing: the payment won the race, and the webhook will      // mark the order paid. Leave it alone.      if (o.payment_intent_id && !(await payments.cancelIntent(o.payment_intent_id))) {        report.left_to_webhook.push(o.id);        continue;      }      if ((await cancelOrder(db, events, o.id, now)).changed) report.cancelled.push(o.id);    }    seen += results.length;    after = results.at(-1) ?? null;    if (results.length < BATCH) break;  }  return report;}