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(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.