Payment Failure

PreviousNext

A payment failure card component with retry, home, and support actions.

Docs
billingsdkblock

Preview

Loading preview…
registry/billingsdk/payment-failure.tsx
"use client";

import * as React from "react";
import { XCircle, RefreshCw, Home, Mail } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { cn } from "@/lib/utils";

export interface PaymentFailureProps extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * Optional heading at the top.
   * @default "Payment Failed"
   */
  title?: string;

  /**
   * Short description under the title.
   * @default "We couldn't process your payment."
   */
  subtitle?: string;

  /**
   * Extra explanatory message under the reasons list.
   * @default "Please check your payment details and try again, or contact your bank for more information."
   */
  message?: string;

  /**
   * Bullet points explaining common failure reasons.
   */
  reasons?: string[];

  /**
   * Whether the primary action is in loading / retrying state.
   */
  isRetrying?: boolean;

  /**
   * Label for the primary CTA button.
   * @default "Try Again"
   */
  retryButtonText?: string;

  /**
   * Label for the secondary button (e.g. Home).
   * @default "Home"
   */
  secondaryButtonText?: string;

  /**
   * Label for the tertiary button (e.g. Support).
   * @default "Support"
   */
  tertiaryButtonText?: string;

  /**
   * Called when user clicks the primary CTA (Try Again).
   */
  onRetry?: () => void;

  /**
   * Called when user clicks the secondary button (Home).
   */
  onSecondary?: () => void;

  /**
   * Called when user clicks the tertiary button (Support).
   */
  onTertiary?: () => void;
}

export const PaymentFailure = React.forwardRef<
  HTMLDivElement,
  PaymentFailureProps
>(
  (
    {
      className,
      title = "Payment Failed",
      subtitle = "We couldn't process your payment.",
      message = "Please check your payment details and try again, or contact your bank for more information.",
      reasons = [
        "Insufficient funds in your account",
        "Incorrect card details or expired card",
        "Card declined by your bank",
        "Network connection issues",
      ],
      isRetrying = false,
      retryButtonText = "Try Again",
      secondaryButtonText = "Home",
      tertiaryButtonText = "Support",
      onRetry,
      onSecondary,
      onTertiary,
      ...props
    },
    ref,
  ) => {
    return (
      <Card ref={ref} className={cn("w-full max-w-md", className)} {...props}>
        <CardHeader className="space-y-4 text-center">
          <div className="flex justify-center">
            <div className="bg-destructive/10 rounded-full p-3">
              <XCircle className="text-destructive h-16 w-16" />
            </div>
          </div>
          <div>
            <CardTitle className="text-2xl font-bold">{title}</CardTitle>
            <CardDescription className="mt-2 text-base">
              {subtitle}
            </CardDescription>
          </div>
        </CardHeader>

        <CardContent className="space-y-4">
          {reasons.length > 0 && (
            <div className="bg-muted space-y-2 rounded-lg p-4">
              <h3 className="text-sm font-semibold">
                Common reasons for payment failure:
              </h3>
              <ul className="text-muted-foreground list-inside list-disc space-y-1 text-sm">
                {reasons.map((reason) => (
                  <li key={reason}>{reason}</li>
                ))}
              </ul>
            </div>
          )}

          {message && (
            <p className="text-muted-foreground text-center text-sm">
              {message}
            </p>
          )}
        </CardContent>

        <CardFooter className="flex flex-col space-y-2">
          <Button
            onClick={onRetry}
            className="w-full"
            disabled={isRetrying || !onRetry}
          >
            {isRetrying ? (
              <>
                <RefreshCw className="mr-2 h-4 w-4 animate-spin" />
                Retrying...
              </>
            ) : (
              <>
                <RefreshCw className="mr-2 h-4 w-4" />
                {retryButtonText}
              </>
            )}
          </Button>

          {(onSecondary || onTertiary) && (
            <div className="flex w-full gap-2">
              {onSecondary && (
                <Button
                  onClick={onSecondary}
                  variant="outline"
                  className="flex-1"
                >
                  <Home className="mr-2 h-4 w-4" />
                  {secondaryButtonText}
                </Button>
              )}

              {onTertiary && (
                <Button
                  onClick={onTertiary}
                  variant="outline"
                  className="flex-1"
                >
                  <Mail className="mr-2 h-4 w-4" />
                  {tertiaryButtonText}
                </Button>
              )}
            </div>
          )}
        </CardFooter>
      </Card>
    );
  },
);

PaymentFailure.displayName = "PaymentFailure";

Installation

npx shadcn@latest add @billingsdk/payment-failure

Usage

import { PaymentFailure } from "@/components/payment-failure"
<PaymentFailure />