Async Stripe

Pagination

Handle cursor-based pagination in Stripe API responses

Many Stripe API endpoints return lists of objects. Stripe uses cursor-based pagination. async-stripe provides ergonomic abstractions to handle cursor management automatically, exposing the results as an asynchronous stream.

The most common way to paginate is directly from the API request builder. All List* and Search* request structs (e.g., ListCustomer, ListInvoice) generate a .paginate() method.

This method returns a ListPaginator which implements a stream, lazily fetching pages as you iterate.

let paginator = ListCustomer::new().paginate();
let mut stream = paginator.stream(&client);

// take a value out from the stream
if let Some(val) = stream.try_next().await? {
    println!("GOT = {val:?}");
}

// alternatively, you can use stream combinators
let _ = stream.try_collect::<Vec<_>>().await?;

The PaginationExt Trait

Sometimes you already have a List<T> object returned from another API call (e.g., CheckoutSession.line_items or Customer.sources). To paginate continuation from this existing list, you must use the PaginationExt trait.

This trait is often "hidden" from IDE autocompletion until imported.

use futures_util::TryStreamExt;
use stripe::{Client, PaginationExt, StripeError};
use stripe_checkout::{CheckoutSessionId, checkout_session::RetrieveCheckoutSession};

pub async fn print_all_line_items(client: &Client, session_id: &str) -> Result<(), StripeError> {
    // Retrieve the session, expanding line_items
    let session =
        RetrieveCheckoutSession::new(CheckoutSessionId::from_str(session_id).expect("infallible"))
            .expand(["line_items".to_string()])
            .send(client)
            .await?;

    if let Some(line_items_list) = session.line_items {
        // line_items_list is a List<LineItem>.
        // To fetch subsequent pages, convert it into a paginator:
        let mut stream = line_items_list
            .into_paginator() // Available because of PaginationExt
            .stream(client);

        while let Some(item) = stream.try_next().await? {
            println!("Line item: {:?}", item.description);
        }
    }

    Ok(())
}

How it works

Calling .into_paginator() on a List<T> extracts the url and the last object's ID (cursor) from the list to configure the paginator automatically.

Manual Pagination

If you prefer not to use streams, you can handle cursors manually using the starting_after parameter available on all List request structs.

pub async fn manual_pagination_example(client: &Client) -> Result<(), stripe::StripeError> {
    let mut params = ListCustomer::new().limit(10);

    loop {
        let page = params.send(client).await?;
        if page.data.is_empty() {
            break;
        }

        for customer in &page.data {
            println!("{}", customer.id);
        }

        if !page.has_more {
            break;
        }

        // Set cursor for next loop
        if let Some(last) = page.data.last() {
            params = params.starting_after(last.id.as_str());
        }
    }

    Ok(())
}
Have feedback? Let us know here