Error Handling & Troubleshooting

Comprehensive guide to handling errors gracefully and debugging common issues with AnySpend to provide the best user experience.

📊 Order Status Lifecycle

Understanding order states is crucial for proper error handling and user experience.

Order Status Types

Order Status Enum
enum OrderStatus {
  // Initial States
  SCANNING_DEPOSIT_TRANSACTION = "scanning_deposit_transaction",
  WAITING_STRIPE_PAYMENT = "waiting_stripe_payment",
  EXPIRED = "expired",

  // Processing States
  SENDING_TOKEN_FROM_VAULT = "sending_token_from_vault",
  RELAY = "relay",

  // Success States
  EXECUTED = "executed",

  // Failure States
  REFUNDING = "refunding",
  REFUNDED = "refunded",
  FAILURE = "failure",
}

Status Descriptions

StatusDescriptionUser Action Required
scanning_deposit_transactionWaiting for payment confirmationNone - wait for blockchain confirmation
waiting_stripe_paymentProcessing credit card paymentMay need to complete 3D Secure
sending_token_from_vaultSending tokens for swapNone - automatic process
relayCross-chain transaction in progressNone - wait for completion
executedTransaction completed successfullyNone - success!
expiredOrder expired before completionCreate new order
refundingAutomatic refund in progressNone - wait for refund
refundedRefund completedCheck wallet for refunded tokens
failureTransaction failedReview error details, retry

⚠️ Common Error Codes

Payment Errors

Network Errors

Contract Errors

🛠️ Error Handling Patterns

Component-Level Error Handling

Payment Component with Error Handling
import { useAnyspendCreateOrder } from "@b3dotfun/sdk/anyspend";

function PaymentComponent() {
  const [error, setError] = useState<string | null>(null);
  const [retryCount, setRetryCount] = useState(0);

  const { createOrder, isCreatingOrder } = useAnyspendCreateOrder({
    onError: error => {
      console.error("Payment failed:", error);

      // Handle specific errors
      switch (error.message) {
        case "INSUFFICIENT_BALANCE":
          setError("Insufficient balance. Please add funds to your wallet.");
          break;

        case "SLIPPAGE":
          if (retryCount < 3) {
            setError("Price moved unfavorably. Retrying...");
            setTimeout(() => {
              setRetryCount(prev => prev + 1);
              retryPayment();
            }, 2000);
          } else {
            setError("Price too volatile. Please try again later.");
          }
          break;

        case "NETWORK_ERROR":
          setError("Network issue. Please check your connection and try again.");
          break;

        case "QUOTE_EXPIRED":
          setError("Price quote expired. Getting fresh quote...");
          refreshQuote();
          break;

        default:
          setError("Payment failed. Please try again or contact support.");
      }

      // Track errors for monitoring
      analytics.track("payment_error", {
        error: error.message,
        retryCount,
        timestamp: new Date().toISOString(),
      });
    },

    onSuccess: () => {
      setError(null);
      setRetryCount(0);
    },
  });

  return (
    <div className="payment-component">
      {error && (
        <div className="error-banner">
          <span className="error-icon">⚠️</span>
          <span>{error}</span>
          <button onClick={() => setError(null)}>Dismiss</button>
        </div>
      )}

      <button onClick={handlePayment} disabled={isCreatingOrder}>
        {isCreatingOrder ? "Processing..." : "Pay Now"}
      </button>
    </div>
  );
}

Order Status Monitoring

Order Status Monitor
import { useAnyspendOrderAndTransactions } from "@b3dotfun/sdk/anyspend";

function OrderStatusMonitor({ orderId }: { orderId: string }) {
  const { orderAndTransactions, getOrderAndTransactionsError } = useAnyspendOrderAndTransactions(true, orderId);

  if (getOrderAndTransactionsError) {
    return (
      <div className="error-state">
        <h3>Unable to load order status</h3>
        <p>Please check your connection and try again.</p>
        <button onClick={() => window.location.reload()}>Retry</button>
      </div>
    );
  }

  if (!orderAndTransactions) {
    return <div>Loading order status...</div>;
  }

  const { order, depositTxs, executeTx, refundTxs } = orderAndTransactions.data;

  const renderStatusMessage = () => {
    switch (order.status) {
      case "scanning_deposit_transaction":
        return (
          <div className="status-pending">
            <div className="spinner" />
            <div>
              <h3>⏳ Waiting for payment confirmation</h3>
              <p>This usually takes 1-2 minutes. Please don't close this window.</p>
              {depositTxs.length > 0 && (
                <a
                  href={getExplorerUrl(depositTxs[0].txHash, depositTxs[0].chainId)}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  View payment transaction
                </a>
              )}
            </div>
          </div>
        );

      case "relay":
        return (
          <div className="status-processing">
            <div className="spinner" />
            <div>
              <h3>🔄 Processing cross-chain transaction</h3>
              <p>Your payment is being processed. This may take a few minutes.</p>
            </div>
          </div>
        );

      case "executed":
        return (
          <div className="status-success">
            <div className="success-icon"></div>
            <div>
              <h3>Transaction completed successfully!</h3>
              <p>Your order has been processed.</p>
              {executeTx && (
                <a href={getExplorerUrl(executeTx.txHash, executeTx.chainId)} target="_blank" rel="noopener noreferrer">
                  View transaction
                </a>
              )}
            </div>
          </div>
        );

      case "failure":
      case "obtain_failed":
        return (
          <div className="status-error">
            <div className="error-icon"></div>
            <div>
              <h3>Transaction failed</h3>
              <p>{order.errorDetails || "An error occurred while processing your order."}</p>
              <div className="error-actions">
                <button onClick={() => createNewOrder()}>Try Again</button>
                <button onClick={() => contactSupport(orderId)}>Contact Support</button>
              </div>
            </div>
          </div>
        );

      case "refunded":
        return (
          <div className="status-refunded">
            <div className="refund-icon">↩️</div>
            <div>
              <h3>Refund processed</h3>
              <p>Your payment has been refunded automatically.</p>
              {refundTxs.length > 0 && (
                <a
                  href={getExplorerUrl(refundTxs[0].txHash, refundTxs[0].chainId)}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  View refund transaction
                </a>
              )}
            </div>
          </div>
        );

      case "expired":
        return (
          <div className="status-expired">
            <div className="expired-icon"></div>
            <div>
              <h3>Order expired</h3>
              <p>This order expired before payment was received.</p>
              <button onClick={() => createNewOrder()}>Create New Order</button>
            </div>
          </div>
        );

      default:
        return (
          <div className="status-unknown">
            <div className="spinner" />
            <div>
              <h3>Processing...</h3>
              <p>Order status: {order.status}</p>
            </div>
          </div>
        );
    }
  };

  return (
    <div className="order-status-monitor">
      <div className="order-header">
        <h2>Order #{orderId.slice(0, 8)}</h2>
        <div className="order-meta">
          <span>Created: {new Date(order.createdAt).toLocaleString()}</span>
          <span>Status: {order.status}</span>
        </div>
      </div>

      {renderStatusMessage()}

      {/* Debug information in development */}
      {process.env.NODE_ENV === "development" && (
        <details className="debug-info">
          <summary>Debug Information</summary>
          <pre>{JSON.stringify(order, null, 2)}</pre>
        </details>
      )}
    </div>
  );
}

Global Error Boundary

Error Boundary Component
import React, { Component, ErrorInfo } from "react";

interface Props {
  children: React.ReactNode;
  fallback?: React.ComponentType<{ error: Error; resetError: () => void }>;
}

interface State {
  hasError: boolean;
  error?: Error;
}

class AnySpendErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error("AnySpend Error Boundary caught an error:", error, errorInfo);

    // Report to error tracking service
    if (typeof window !== "undefined") {
      // Example: Sentry.captureException(error, { contexts: { errorInfo } });
    }
  }

  resetError = () => {
    this.setState({ hasError: false, error: undefined });
  };

  render() {
    if (this.state.hasError) {
      const FallbackComponent = this.props.fallback || DefaultErrorFallback;
      return <FallbackComponent error={this.state.error!} resetError={this.resetError} />;
    }

    return this.props.children;
  }
}

function DefaultErrorFallback({ error, resetError }: { error: Error; resetError: () => void }) {
  return (
    <div className="error-fallback">
      <h2>Something went wrong</h2>
      <p>An unexpected error occurred in the payment component.</p>
      <details className="error-details">
        <summary>Error details</summary>
        <pre>{error.message}</pre>
      </details>
      <div className="error-actions">
        <button onClick={resetError}>Try Again</button>
        <button onClick={() => window.location.reload()}>Reload Page</button>
      </div>
    </div>
  );
}

// Usage
function App() {
  return (
    <AnySpendErrorBoundary>
      <AnySpendProvider>
        <YourApp />
      </AnySpendProvider>
    </AnySpendErrorBoundary>
  );
}

📊 Error Monitoring & Analytics

Error Tracking Setup

Error Analytics
// Track errors for monitoring and improvement
function trackPaymentError(error: Error, context: any) {
  // Analytics tracking
  analytics.track("payment_error", {
    error_message: error.message,
    error_stack: error.stack,
    user_agent: navigator.userAgent,
    timestamp: new Date().toISOString(),
    context,
  });

  // Error reporting service (e.g., Sentry)
  if (window.Sentry) {
    window.Sentry.captureException(error, {
      tags: {
        component: "anyspend",
        error_type: "payment_error",
      },
      extra: context,
    });
  }

  // Custom error logging
  console.error("AnySpend Payment Error:", {
    message: error.message,
    context,
    timestamp: new Date().toISOString(),
  });
}

User-Friendly Error Messages

Error Message Helper
function getErrorMessage(error: Error): { title: string; message: string; action?: string } {
  switch (error.message) {
    case "INSUFFICIENT_BALANCE":
      return {
        title: "Insufficient Balance",
        message: "You don't have enough funds for this transaction.",
        action: "Add funds to your wallet or choose a different payment method.",
      };

    case "SLIPPAGE":
      return {
        title: "Price Changed",
        message: "The price moved while processing your transaction.",
        action: "We'll retry automatically with updated pricing.",
      };

    case "NETWORK_ERROR":
      return {
        title: "Connection Issue",
        message: "Unable to connect to the blockchain network.",
        action: "Please check your internet connection and try again.",
      };

    case "QUOTE_EXPIRED":
      return {
        title: "Quote Expired",
        message: "The price quote is no longer valid.",
        action: "Getting a fresh quote automatically...",
      };

    default:
      return {
        title: "Transaction Failed",
        message: "An unexpected error occurred.",
        action: "Please try again or contact support if the problem persists.",
      };
  }
}

// Usage in component
function ErrorDisplay({ error }: { error: Error }) {
  const errorInfo = getErrorMessage(error);

  return (
    <div className="error-display">
      <h3>{errorInfo.title}</h3>
      <p>{errorInfo.message}</p>
      {errorInfo.action && <p className="error-action">{errorInfo.action}</p>}
    </div>
  );
}

🔧 Debugging Tools

Debug Mode

Debug Helper
// Enable debug mode for detailed logging
function useAnyspendDebug() {
  const isDebug = process.env.NODE_ENV === "development" || localStorage.getItem("anyspend_debug") === "true";

  const log = (message: string, data?: any) => {
    if (isDebug) {
      console.log(`[AnySpend Debug] ${message}`, data);
    }
  };

  const error = (message: string, error?: Error) => {
    if (isDebug) {
      console.error(`[AnySpend Debug] ${message}`, error);
    }
  };

  return { log, error, isDebug };
}

// Usage
function PaymentComponent() {
  const debug = useAnyspendDebug();

  const { createOrder } = useAnyspendCreateOrder({
    onSuccess: data => {
      debug.log("Order created successfully", data);
    },
    onError: error => {
      debug.error("Order creation failed", error);
    },
  });

  // ... rest of component
}

Network Status Checker

Network Status
function useNetworkStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);
  const [networkSpeed, setNetworkSpeed] = useState<"slow" | "fast" | "unknown">("unknown");

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);

    // Check network speed
    const connection = (navigator as any).connection;
    if (connection) {
      const speed = connection.effectiveType;
      setNetworkSpeed(speed === "4g" ? "fast" : "slow");
    }

    return () => {
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, []);

  return { isOnline, networkSpeed };
}

// Usage in component
function PaymentComponent() {
  const { isOnline, networkSpeed } = useNetworkStatus();

  if (!isOnline) {
    return (
      <div className="network-error">
        <h3>No Internet Connection</h3>
        <p>Please check your connection and try again.</p>
      </div>
    );
  }

  if (networkSpeed === "slow") {
    return (
      <div className="network-warning">
        <p>⚠️ Slow network detected. Transactions may take longer than usual.</p>
      </div>
    );
  }

  // ... rest of component
}

📋 Best Practices

🆘 Getting Help

When reporting issues, please include: - Error messages and codes - Steps to reproduce the issue - Browser and device information - Network conditions (if relevant) - Order IDs (if applicable)
Remember: Good error handling is about more than just catching errors—it’s about providing a smooth, understandable experience for your users even when things go wrong.