Async Stripe

Error Handling

Understanding and handling Stripe API errors effectively

Stripe API requests can fail for various reasons: invalid parameters, authentication issues, card declines, rate limits, and network failures. The StripeError enum provides structured error information to help you handle these cases appropriately.

Error Types

The StripeError enum has several variants:

/// An error encountered when communicating with the Stripe API.
#[derive(Debug, Error)]
pub enum StripeError {
    /// Stripe returned a client error.
    #[error("error reported by stripe: {0:#?}, status code: {1}")]
    Stripe(Box<ApiErrors>, u16),
    /// An error occurred when parsing the Stripe response.
    #[error("error deserializing a request: {0}")]
    JSONDeserialize(String),
    /// An error occurred communicating with Stripe.
    #[error("error communicating with stripe: {0}")]
    ClientError(String),
    /// The client configuration was invalid.
    #[error("configuration error: {0}")]
    ConfigError(String),
    /// A blocking request timed out
    #[error("timeout communicating with stripe")]
    Timeout,
}

Handling API Errors

The most common error type is StripeError::Stripe, which contains the error details from Stripe's API along with the HTTP status code.

Basic Error Handling

use stripe::{Client, Customer, CreateCustomer, StripeError};

let client = Client::new(secret_key);

match Customer::create(&client, CreateCustomer::new()).send().await {
    Ok(customer) => println!("Created customer: {}", customer.id),
    Err(err) => match err {
        StripeError::Stripe(api_error, status_code) => {
            eprintln!("Stripe API error ({}): {:?}", status_code, api_error.message);
        }
        StripeError::ClientError(msg) => {
            eprintln!("Network error: {}", msg);
        }
        _ => {
            eprintln!("Other error: {}", err);
        }
    }
}

Handling Specific HTTP Status Codes

Different status codes indicate different types of failures:

use stripe::{Client, PaymentIntent, StripeError};

match PaymentIntent::retrieve(&client, &payment_intent_id, &[]).await {
    Ok(payment) => {
        println!("Payment status: {:?}", payment.status);
    }
    Err(StripeError::Stripe(api_error, status)) => {
        match status {
            400 => {
                // Bad Request - Invalid parameters
                eprintln!("Invalid request: {:?}", api_error.message);
            }
            401 => {
                // Unauthorized - Invalid API key
                eprintln!("Authentication failed - check your API key");
            }
            402 => {
                // Payment Required - Card declined or insufficient funds
                eprintln!("Payment failed: {:?}", api_error.message);

                // The error may contain a decline code
                if let Some(code) = &api_error.code {
                    match code.as_str() {
                        "card_declined" => eprintln!("Card was declined"),
                        "insufficient_funds" => eprintln!("Insufficient funds"),
                        "expired_card" => eprintln!("Card has expired"),
                        _ => eprintln!("Payment error code: {}", code),
                    }
                }
            }
            404 => {
                // Not Found - Resource doesn't exist
                eprintln!("Resource not found");
            }
            429 => {
                // Too Many Requests - Rate limited
                eprintln!("Rate limited - slow down requests");
            }
            500 | 502 | 503 | 504 => {
                // Server errors - retry with backoff
                eprintln!("Stripe server error - retry later");
            }
            _ => {
                eprintln!("HTTP {}: {:?}", status, api_error.message);
            }
        }
    }
    Err(e) => {
        eprintln!("Non-API error: {}", e);
    }
}

Common Error Codes

Stripe includes error codes in the ApiErrors struct that provide more specific information about what went wrong:

Payment Errors (402)

  • card_declined - The card was declined
  • expired_card - The card has expired
  • incorrect_cvc - The CVC is incorrect
  • processing_error - An error occurred while processing the card
  • insufficient_funds - Insufficient funds in the account

Request Errors (400)

  • parameter_invalid_empty - A required parameter was empty
  • parameter_unknown - An unknown parameter was provided
  • resource_missing - The requested resource doesn't exist

Authentication Errors (401)

  • invalid_api_key - The API key is invalid

For a complete list of error codes, see the Stripe Error Codes documentation.

Retry Strategies

For transient errors (network issues, server errors), use the built-in retry strategies:

use stripe::{Client, RequestStrategy};

let client = Client::builder(secret_key)
    .request_strategy(RequestStrategy::ExponentialBackoff(3))
    .build();

// This request will automatically retry up to 3 times with backoff
let customer = Customer::retrieve(&client, &customer_id, &[]).await?;

See the Request Strategies documentation for more details on retry behavior.

Best Practices

1. Handle Specific Errors

Don't just log all errors the same way. Handle payment failures differently from configuration errors:

match result {
    Err(StripeError::Stripe(api_error, 402)) => {
        // Show user-friendly message for payment failures
        show_payment_error_to_user(&api_error);
    }
    Err(StripeError::Stripe(api_error, 400)) => {
        // Log parameter errors for debugging
        log::error!("Invalid parameters: {:?}", api_error);
    }
    Err(e) => {
        // Log unexpected errors and alert monitoring
        log::error!("Unexpected Stripe error: {}", e);
    }
    Ok(result) => {
        // Success
    }
}

2. Use Idempotency Keys

For critical operations (especially payment creation), always use idempotency keys to prevent accidental duplicate charges:

use stripe::{RequestStrategy, IdempotencyKey};

let key = IdempotencyKey::new("order_12345").unwrap();

let payment = CreatePaymentIntent::new(amount, currency)
    .request_strategy(RequestStrategy::Idempotent(key))
    .send(&client)
    .await?;

3. Log Error Details

The ApiErrors struct contains useful debugging information:

Err(StripeError::Stripe(api_error, status)) => {
    log::error!(
        "Stripe error: status={}, type={:?}, code={:?}, message={:?}, param={:?}",
        status,
        api_error.error_type,
        api_error.code,
        api_error.message,
        api_error.param
    );
}

4. Don't Retry Client Errors

4xx errors (except 429 rate limits) usually indicate a problem with your request that won't be fixed by retrying. Only retry 5xx server errors and network failures.

The built-in RequestStrategy::ExponentialBackoff handles this correctly for you.

Never retry failed payments without user confirmation. A failed payment could be intentional (e.g., user canceled) or indicate fraud prevention.

Have feedback? Let us know here