/** * What CS has to ship, and what happened to it. One row per expansion, written at submit. * * The Shopify draft order is the interlock that keeps an unpaid device from shipping. A draft is * not fulfillable — only a completed order is — so for `free_subscription` (customer pays $19.99) * the sequence is: * * submit ──► draft order, left open ──► charge succeeds ──► we complete it ──► shippable order * * CS cannot ship an unpaid device even by mistake, because until the charge lands there is nothing * to fulfil. Two consequences worth knowing: * - the charge has to go through our collect-payment endpoint, not the Stripe dashboard; a dashboard * charge leaves the draft open and the order permanently unshippable, * - a declined card fails *silently* — the row just sits in `pending` — hence the reminder columns. * * `free_loaner` ships free, so its draft is completed immediately at submit; there is nothing to wait * for. That asymmetry is useful: `draftOrderCompletedAt IS NULL AND deviceFeeChargedAt IS NULL` * identifies exactly the orders stuck awaiting an up-front charge, with no need to join the parent * row for the arm. * * `shopifyOrderId` comes back from `draftOrderComplete` (the mutation selects `order { id name }`) * and is what the `orders/fulfilled` webhook joins on — a key we wrote ourselves, rather than * trusting custom attributes to survive the draft → order transition. The `expansionId` custom * attribute rides along anyway as a fallback and to make the order legible in the Shopify admin. */ export declare class BillingCampaignExpansionOrder { id: number; /** FK to `billingCampaignExpansion.id`. */ expansionId: number; /** FK to `billingCampaignExpansionAddress.id`. */ addressId: number | null; selectedModelFamily: string | null; selectedDeviceTypeId: number | null; /** One-time hardware price. 1999 for `free_subscription`, 0 for `free_loaner` until day 75. */ devicePriceCents: number | null; planId: string | null; /** * The post-trial monthly rate, snapshotted at submit. Resolved server-side from * `historicalBillingPlan` — never taken from the client — so a later price change cannot * retroactively alter what the customer was shown and agreed to. */ monthlyPriceCents: number | null; shopifyDraftOrderId: string | null; draftOrderCompletedAt: Date | null; /** Set when the draft completes. What the fulfilment webhook matches on. */ shopifyOrderId: string | null; /** Stamped by the `orders/fulfilled` webhook, or by CS for fulfilment outside Shopify. */ shippedAt: Date | null; deliveredAt: Date | null; trackingNumber: string | null; carrier: string | null; /** * The one-time $19.99 hardware charge. **Both arms pay it** — the same money for the same thing, * taken at different points, so it is one set of columns and never two: * - `free_subscription` — up front, and the order cannot be fulfilled until it lands (above). * - `free_loaner` — at day 75, only if the device is kept. * * Never both, and never twice. A row with `deviceFeeChargedAt` set has paid for its hardware * whichever arm it is in, which is also what makes "unpaid" a single condition to query. * * Collected as a single off-session PaymentIntent, so there is no invoice id to keep alongside it. */ deviceFeePaymentIntentId: string | null; deviceFeeChargedAt: Date | null; /** So CS can see *why* a charge declined rather than only that it did not land. */ deviceFeeFailureReason: string | null; deviceFeeReminderSentAt: Date | null; deviceFeeReminderCount: number; csNotes: string | null; createdAt: Date | null; updatedAt: Date | null; }