{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "billing-payment-failed",
  "title": "Billing Payment Failed",
  "description": "Display payment failure details with retry and support options.",
  "registryDependencies": [
    "badge",
    "button",
    "card",
    "separator"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/billing/billing-payment-failed.tsx",
      "content": "\"use client\";\n\nimport {\n  AlertTriangle,\n  CreditCard,\n  Loader2,\n  Mail,\n  RefreshCw,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Badge } from \"@/registry/new-york/ui/badge\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport { Separator } from \"@/registry/new-york/ui/separator\";\n\nexport interface PaymentFailureDetails {\n  invoiceId?: string;\n  invoiceNumber?: string;\n  amount: number;\n  currency?: string;\n  failedAt: Date;\n  reason:\n    | \"insufficient_funds\"\n    | \"card_declined\"\n    | \"expired_card\"\n    | \"invalid_card\"\n    | \"processing_error\"\n    | \"fraud_detected\"\n    | \"other\";\n  reasonMessage?: string;\n  paymentMethod?: {\n    type: string;\n    last4?: string;\n    brand?: string;\n    expiresAt?: Date;\n  };\n  gracePeriodEndsAt?: Date;\n  retryAttempts?: number;\n  maxRetryAttempts?: number;\n}\n\nexport interface BillingPaymentFailedProps {\n  failure: PaymentFailureDetails;\n  onRetry?: () => Promise<void>;\n  onUpdatePaymentMethod?: () => void;\n  onContactSupport?: () => void;\n  className?: string;\n  currency?: string;\n}\n\nfunction formatDate(date: Date): string {\n  const year = date.getFullYear();\n  const month = date.toLocaleString(\"en-US\", { month: \"short\" });\n  const day = date.getDate();\n  return `${month} ${day}, ${year}`;\n}\n\nfunction formatPrice(amount: number, currency = \"USD\"): string {\n  return new Intl.NumberFormat(\"en-US\", {\n    style: \"currency\",\n    currency,\n    minimumFractionDigits: 2,\n    maximumFractionDigits: 2,\n  }).format(amount);\n}\n\nfunction getReasonConfig(reason: PaymentFailureDetails[\"reason\"]) {\n  switch (reason) {\n    case \"insufficient_funds\":\n      return {\n        title: \"Insufficient Funds\",\n        message:\n          \"Your payment was declined due to insufficient funds in your account.\",\n        action:\n          \"Please ensure you have sufficient funds or use a different payment method.\",\n      };\n    case \"card_declined\":\n      return {\n        title: \"Card Declined\",\n        message: \"Your card was declined by your bank.\",\n        action: \"Please contact your bank or use a different payment method.\",\n      };\n    case \"expired_card\":\n      return {\n        title: \"Expired Card\",\n        message: \"Your payment card has expired.\",\n        action: \"Please update your payment method with a valid card.\",\n      };\n    case \"invalid_card\":\n      return {\n        title: \"Invalid Card\",\n        message: \"The card information provided is invalid.\",\n        action:\n          \"Please verify your card details or use a different payment method.\",\n      };\n    case \"processing_error\":\n      return {\n        title: \"Processing Error\",\n        message: \"An error occurred while processing your payment.\",\n        action: \"Please try again or contact support if the issue persists.\",\n      };\n    case \"fraud_detected\":\n      return {\n        title: \"Fraud Detection\",\n        message: \"Your payment was flagged for security reasons.\",\n        action: \"Please contact support to resolve this issue.\",\n      };\n    case \"other\":\n      return {\n        title: \"Payment Failed\",\n        message: \"Your payment could not be processed.\",\n        action: \"Please try again or contact support for assistance.\",\n      };\n    default:\n      return {\n        title: \"Payment Failed\",\n        message: \"Your payment could not be processed.\",\n        action: \"Please try again or contact support for assistance.\",\n      };\n  }\n}\n\nexport default function BillingPaymentFailed({\n  failure,\n  onRetry,\n  onUpdatePaymentMethod,\n  onContactSupport,\n  className,\n  currency = \"USD\",\n}: BillingPaymentFailedProps) {\n  const [isRetrying, setIsRetrying] = useState(false);\n\n  const reasonConfig = getReasonConfig(failure.reason);\n  const failureCurrency = failure.currency || currency;\n  const isCardExpired =\n    failure.reason === \"expired_card\" ||\n    (failure.paymentMethod?.expiresAt &&\n      new Date(failure.paymentMethod.expiresAt) < new Date());\n\n  const handleRetry = async () => {\n    if (!onRetry) return;\n\n    setIsRetrying(true);\n    try {\n      await onRetry();\n    } finally {\n      setIsRetrying(false);\n    }\n  };\n\n  return (\n    <Card className={cn(\"w-full border-destructive/50 shadow-xs\", className)}>\n      <CardHeader>\n        <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n          <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n            <div className=\"flex flex-wrap items-center gap-2\">\n              <AlertTriangle className=\"size-5 shrink-0 text-destructive\" />\n              <CardTitle className=\"wrap-break-word text-destructive\">\n                {reasonConfig.title}\n              </CardTitle>\n            </div>\n            <CardDescription className=\"wrap-break-word\">\n              {failure.reasonMessage || reasonConfig.message}\n            </CardDescription>\n          </div>\n          <Badge className=\"shrink-0 text-xs\" variant=\"destructive\">\n            Failed\n          </Badge>\n        </div>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col gap-6\">\n          <div className=\"flex flex-col gap-4 rounded-lg border bg-destructive/5 p-4\">\n            <div className=\"flex flex-col gap-2\">\n              <h3 className=\"font-medium text-sm\">Payment Details</h3>\n              <div className=\"flex flex-col gap-2 text-muted-foreground text-sm\">\n                {failure.invoiceNumber && (\n                  <div className=\"flex flex-wrap items-center gap-2\">\n                    <span>Invoice:</span>\n                    <span className=\"font-medium text-foreground\">\n                      {failure.invoiceNumber}\n                    </span>\n                  </div>\n                )}\n                <div className=\"flex flex-wrap items-center gap-2\">\n                  <span>Amount:</span>\n                  <span className=\"font-medium text-foreground\">\n                    {formatPrice(failure.amount, failureCurrency)}\n                  </span>\n                </div>\n                <div className=\"flex flex-wrap items-center gap-2\">\n                  <span>Failed on:</span>\n                  <span className=\"text-foreground\">\n                    {formatDate(failure.failedAt)}\n                  </span>\n                </div>\n                {failure.paymentMethod && (\n                  <div className=\"flex flex-wrap items-center gap-2\">\n                    <span>Payment Method:</span>\n                    <div className=\"flex flex-wrap items-center gap-2 text-foreground\">\n                      <span className=\"capitalize\">\n                        {failure.paymentMethod.type}\n                      </span>\n                      {failure.paymentMethod.brand && (\n                        <>\n                          <span aria-hidden=\"true\">•</span>\n                          <span className=\"capitalize\">\n                            {failure.paymentMethod.brand}\n                          </span>\n                        </>\n                      )}\n                      {failure.paymentMethod.last4 && (\n                        <>\n                          <span aria-hidden=\"true\">•</span>\n                          <span>•••• {failure.paymentMethod.last4}</span>\n                        </>\n                      )}\n                      {isCardExpired && (\n                        <>\n                          <span aria-hidden=\"true\">•</span>\n                          <Badge className=\"text-xs\" variant=\"destructive\">\n                            Expired\n                          </Badge>\n                        </>\n                      )}\n                    </div>\n                  </div>\n                )}\n                {failure.retryAttempts !== undefined &&\n                  failure.maxRetryAttempts !== undefined && (\n                    <div className=\"flex flex-wrap items-center gap-2\">\n                      <span>Retry Attempts:</span>\n                      <span className=\"text-foreground\">\n                        {failure.retryAttempts} / {failure.maxRetryAttempts}\n                      </span>\n                    </div>\n                  )}\n              </div>\n            </div>\n\n            {failure.gracePeriodEndsAt && (\n              <>\n                <Separator />\n                <div className=\"flex flex-col gap-2\">\n                  <div className=\"flex items-center gap-2 font-medium text-sm text-yellow-600\">\n                    <AlertTriangle className=\"size-4\" />\n                    <span>Grace Period</span>\n                  </div>\n                  <p className=\"text-muted-foreground text-sm\">\n                    Your service will continue until{\" \"}\n                    <span className=\"font-medium text-foreground\">\n                      {formatDate(failure.gracePeriodEndsAt)}\n                    </span>\n                    . Please update your payment method before this date to\n                    avoid service interruption.\n                  </p>\n                </div>\n              </>\n            )}\n          </div>\n\n          <div className=\"flex flex-col gap-2\">\n            <p className=\"wrap-break-word text-muted-foreground text-sm\">\n              {reasonConfig.action}\n            </p>\n          </div>\n\n          <Separator />\n\n          <div className=\"flex flex-col gap-2\">\n            {onRetry && (\n              <Button\n                aria-busy={isRetrying}\n                className=\"w-full\"\n                data-loading={isRetrying}\n                onClick={handleRetry}\n                type=\"button\"\n              >\n                {isRetrying ? (\n                  <>\n                    <Loader2 className=\"size-4 animate-spin\" />\n                    Retrying…\n                  </>\n                ) : (\n                  <>\n                    <RefreshCw className=\"size-4\" />\n                    Retry Payment\n                  </>\n                )}\n              </Button>\n            )}\n            {onUpdatePaymentMethod && (\n              <Button\n                className=\"w-full\"\n                onClick={onUpdatePaymentMethod}\n                type=\"button\"\n                variant=\"outline\"\n              >\n                <CreditCard className=\"size-4\" />\n                Update Payment Method\n              </Button>\n            )}\n            {onContactSupport && (\n              <Button\n                className=\"w-full\"\n                onClick={onContactSupport}\n                type=\"button\"\n                variant=\"ghost\"\n              >\n                <Mail className=\"size-4\" />\n                Contact Support\n              </Button>\n            )}\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "billing"
  ],
  "type": "registry:ui"
}