Skip to main content

useOrderStatus()

Polls GET /events/{eventId}/ticketing/orders/{orderId}/status until the order reaches a terminal state (completed, failed, or refunded), the component unmounts, or the caller invokes stop(). Picks up eventId, orderId, and orderStatusToken from SDK state by default — these are populated automatically during checkout.

Import

import { useOrderStatus } from "@feelrift/react";

Basic usage

import { useOrderStatus } from "@feelrift/react";

function OrderStatusBanner() {
const { data, isTerminal } = useOrderStatus();
if (!data) return null;
if (isTerminal) {
return (
<p>
Order {data.orderId}: {data.status}
</p>
);
}
return <p>Finalizing order {data.orderId}</p>;
}

Parameters

NameTypeRequiredDefaultDescription
optionsUseOrderStatusOptionsNo{}All options optional. Defaults read from SDK state and use a 2-second poll interval.
options.eventIdstringNofrom SDKOverride the event namespace for the order. Defaults to the active reservation's event.
options.orderIdstringNofrom SDKOverride the order to poll. Defaults to the orderId populated by useCheckout.
options.tokenstringNofrom SDKOverride the bearer token. Defaults to the orderStatusToken populated by useCheckout.
options.pollIntervalMsnumberNo2000Poll cadence in milliseconds. Lower bound enforced by the server's rate limiter.

Returns

FieldTypeDescription
dataOrderStatusResponse | nullThe most recent successful poll result. null before the first poll completes or when no order is configured.
isLoadingbooleantrue while a poll request is in flight.
errorRiftApiError | nullThe most recent RiftApiError. Stays set after subsequent successful polls cleared it.
isTerminalbooleantrue when data.status is completed, failed, or refunded. The hook also stops polling at this point.
stop() => voidStops the poll loop. Safe to call multiple times. The loop also stops on unmount.

OrderStatusResponse:

type OrderStatusResponse = {
orderId: string;
status: "awaiting_payment" | "completed" | "failed" | "refunded";
};

Behavior

  • Source of eventId, orderId, and token. Reads from the SDK's persistent state by default. Checkout populates these values on success. Override via options when polling an order outside the checkout flow, such as a post-purchase landing page that receives the token via URL.
  • Auto-start. The poll loop starts in a useEffect as soon as both orderId and token are non-null. Mounting the hook without either is a no-op.
  • Auto-stop on terminal. When data.status is completed, failed, or refunded, the hook clears the interval and sets isTerminal = true. No further polls fire.
  • Auto-stop on unmount. The effect cleanup clears the interval and marks the component as cancelled, so in-flight responses resolved after unmount don't update React state.
  • First poll is immediate. The hook calls the first poll synchronously when the effect runs, then schedules the next at pollIntervalMs.
  • Error behavior. Server errors are caught and assigned to error. The poll loop continues — transient errors don't stop polling. The hook does NOT auto-stop on error; consumers should call stop() for fatal errors (token expired/tampered).
  • Token expiry. The order-status token is ~10 minutes. After expiry the server returns code: "order_status_token_expired"; the hook surfaces it on error but the poll loop keeps attempting. Call stop() and route the user to an email-link recovery flow.
  • Rate limits. The server allows generous polling (60 req/min per identity by default). Setting pollIntervalMs below ~500ms risks tripping the per-identity rate limit and surfacing code: "rate_limited" errors.

Errors

Errors assigned to error (the hook never throws). All are RiftApiError instances. See the errors reference for typed extensions per code.

StatusCodeWhenRecovery
401order_status_token_tamperedToken signature failed verification (or sub mismatch with orderId).stop(); route to an email-link recovery flow.
404order_not_foundThe order ID doesn't exist.stop(); the URL is probably stale.
410order_status_token_expiredToken's TTL passed (~10 minutes).stop(); route to email-link recovery.
429rate_limitedPoll cadence too high for the per-identity limiter.Increase pollIntervalMs.

Examples

Default — pick up from useCheckout automatically

import { useOrderStatus } from "@feelrift/react";

function PostCheckoutBanner() {
const { data, isTerminal, error } = useOrderStatus();
if (error) return <p>{error.detail}</p>;
if (!data) return null;
if (data.status === "completed") return <p>You're in!</p>;
if (data.status === "failed") return <p>Payment failed.</p>;
if (data.status === "refunded") return <p>Refunded.</p>;
return <p>Finalizing…</p>;
}

Poll an order from a URL parameter (post-redirect)

function ReturnPage({
eventId,
orderId,
token,
}: {
eventId: string;
orderId: string;
token: string;
}) {
const { data, isTerminal } = useOrderStatus({ eventId, orderId, token });
return isTerminal ? <Receipt orderId={data!.orderId} /> : <Spinner />;
}

Slower poll cadence (low-priority background)

function BackgroundStatus() {
const { data } = useOrderStatus({ pollIntervalMs: 10_000 });
return data ? <small>Status: {data.status}</small> : null;
}

Stop polling on a fatal error

import { isRiftApiError, useOrderStatus } from "@feelrift/react";

function GuardedPoll() {
const { data, error, stop } = useOrderStatus();

useEffect(() => {
if (
isRiftApiError(error, "order_status_token_expired") ||
isRiftApiError(error, "order_status_token_tampered") ||
isRiftApiError(error, "order_not_found")
) {
stop();
router.push("/orders/recover");
}
}, [error, stop]);

return data ? <p>{data.status}</p> : null;
}
  • useCheckout — persists orderId and orderStatusToken on success.
  • usePaymentStatus — separate, narrower poll for the Stripe authorization side. Public types exported from @feelrift/react.
  • Error handling — the narrowing pattern this hook expects consumers to use.
  • Securing the order-status token — how to carry the token across a Stripe redirect without leaking it into history, the Referer header, or server logs.