# Architecture & Crates Unlike many API clients that bundle every possible resource into a single heavy compilation unit, **async-stripe** utilizes a modular workspace architecture. This approach significantly improves compile times and reduces binary bloat. ## The Crate Ecosystem The library is split into three layers: * **Client Layer**: The HTTP runtime and configuration * **Shared Layer**: Common types (IDs, Currency, Errors) used across resources * **Resource Layer**: Specific Stripe domains (Billing, Connect, Payments, etc.) ### Available Crates | Crate Name | Description | | ----------------------- | ----------------------------------------------------------------------------------------------------- | | `async-stripe` | **Required**. The core client entry point. Handles HTTP transport, authentication, and configuration. | | `async-stripe-billing` | Subscription logic: Invoices, Plans, Quotes, Subscriptions, Credit Notes, Billing Meters, and Portal. | | `async-stripe-checkout` | Stripe Checkout Sessions and related types. | | `async-stripe-connect` | Connect Accounts, capabilities, transfers, and external accounts. | | `async-stripe-core` | Fundamental resources: Customers, Charges, PaymentIntents, Refunds, Balance, Events. | | `async-stripe-fraud` | Radar fraud prevention: Early fraud warnings, value lists, and reviews. | | `async-stripe-issuing` | Card issuing: Authorizations, cards, cardholders, disputes, tokens, and transactions. | | `async-stripe-misc` | Additional resources: Financial Connections, Identity, Tax, Reporting, Climate, and more. | | `async-stripe-payment` | Payment methods: Cards, bank accounts, payment links, payment method configurations. | | `async-stripe-product` | Product catalog: Products, prices, coupons, promotion codes, shipping rates, tax codes/rates. | | `async-stripe-terminal` | Terminal resources: Readers, locations, configurations, and connection tokens. | | `async-stripe-treasury` | Treasury features: Financial accounts, transfers, payments, and transactions. | | `async-stripe-webhook` | Utilities for verifying and deserializing webhook events securely. | ## Feature Flags To use specific resources, you must add the crate and enable the specific feature for the object you need. This granular control ensures you don't compile code for Stripe products you don't use. ```toml [dependencies] # The main client async-stripe = { version = "1.0.0-alpha.8", features = ["runtime-tokio-hyper"] } # Resource crates with specific objects enabled stripe-core = { version = "1.0.0-alpha.8", features = ["customer", "balance"] } stripe-billing = { version = "1.0.0-alpha.8", features = ["invoice", "subscription"] } ``` # Code Generation & Style The vast majority of this library is procedurally generated from the official Stripe OpenAPI specification. This ensures 100% field accuracy, correct types, and up-to-date coverage with Stripe's rapid API evolution. ## The Generator CLI The code generation logic resides in the `openapi` crate. It is a standalone Rust CLI tool used to fetch specifications, analyze dependencies, and generate the library code. ### Running the Generator To update the library to the latest Stripe API version, run the generator from the `openapi` directory: ```bash cd openapi # Fetches the latest spec from GitHub and regenerates code cargo run --release -- --fetch latest ``` ### CLI Arguments The generator (`openapi/src/main.rs`) supports several flags to control the build process: | Argument | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--fetch ` | Fetches a specific OpenAPI spec version. Options: `latest` (from GitHub releases), `current` (defined in `version.json`), or specific tags like `v171`. | | `--spec-path ` | Input path for the OpenAPI JSON spec. Defaults to `spec3.sdk.json`. | | `--out ` | Output directory for generated code. Defaults to `out`. The tool automatically copies valid output to `generated/`. | | `--graph` | Generates dependency graphs in DOT format (`graphs/crate_graph.txt`, `graphs/components_graph.txt`) to aid in debugging cyclic dependencies. | | `--dry-run` | Runs generation logic without copying files to the final `generated/` destination. | ## Architecture & Workflow To manage the complexity of the Stripe API, the generator performs several optimization passes: 1. **Fetching & Parsing**: Downloads `spec3.sdk.json` and parses thousands of schema definitions. 2. **Dependency Analysis**: Builds a directed graph of all Stripe objects to identify dependencies between resources. 3. **Crate Splitting**: Uses `gen_crates.toml` configuration to group resources into modular crates (e.g., `stripe-billing`, `stripe-connect`). 4. **Cycle Breaking**: Identifies cyclic dependencies (e.g., a Customer has a Subscription, a Subscription has a Customer) and extracts shared types into the `async-stripe-types` crate to resolve them. 5. **Rendering**: Outputs strongly-typed Rust structs, enums, and builder methods using `miniserde` for fast compilation. ### Configuration (gen\_crates.toml) The mapping of Stripe resources to Rust crates is defined in `openapi/gen_crates.toml`. This file controls the modular structure of the library and ensures related resources are bundled together. ```toml # Example from gen_crates.toml [[crates]] name = "billing" packages = ["test_helpers", "billing_portal", "billing"] paths = [ "credit_note", "tax_id", "invoice", # ... ] description = "This crate provides Rust bindings to the Stripe HTTP API..." ``` ## Manual Extensions Because generated code is overwritten on every update, we strictly separate manual logic from generated logic. * **Generated Code**: Lives in `generated/*`. This code is **never edited manually**. * **Extensions**: Hand-written logic lives in `_ext.rs` files inside the resource crates or `async-stripe-types`. This allows us to add convenience methods (like currency helpers or custom serialization logic) without conflicting with the autogenerated structs. # Client Configuration import { CodeFromFile } from "@/components/code-from-file" **async-stripe** is runtime-agnostic but provides convenient defaults. You must select a runtime feature flag in the `async-stripe` crate. ## Runtime Selection * **`runtime-tokio-hyper`**: *(Recommended)* Uses `tokio` and `hyper`. Best for general-purpose async applications. * **`runtime-async-std-surf`**: Uses `async-std` and `surf`. * **`blocking`**: A synchronous blocking client. Note that this wraps Tokio internally to provide a synchronous API. ## Initialization Basic initialization uses your secret key and default settings. ## Advanced Configuration You can use the `ClientBuilder` to configure headers, app info, or specific account masquerading (for Connect platforms). # Contributing # Contributing to async-stripe 1. Fork it! 2. Create your feature branch: `git checkout -b my-new-feature` 3. Test it: `cargo test --features runtime-blocking` 4. Lint it: `cargo +1.82.0 clippy --all --all-targets -- -D warnings` 5. Commit your changes: `git commit -am 'Add some feature'` 6. Push to the branch: `git push origin my-new-feature` 7. Submit a pull request :D We use `rustfmt` to keep our codebase consistently formatted. Please ensure that you have correctly formatted your code (most editors will do this automatically when saving) or it may not pass the CI tests. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as in the README, without any additional terms or conditions. ## Coding standards These are requirements we have that we have not yet lifted to the level of automatic enforcement. ### Import grouping In each file the imports should be grouped into at most 4 groups in the following order: 1. stdlib 2. non-repository local crates 3. repository local other crates 4. this crate Separate each group with a blank line, and rustfmt will sort into a canonical order. Any file that is not grouped like this can be rearranged whenever the file is touched - we're not precious about having it done in a separate commit, though that is helpful. ### Clippy lints We ask that contributors keep the clippy status clean. Minimally, run `cargo clippy` before submitting code. Clippy is also run in GitHub Actions. ### rustfmt It is expected that code is uniformly formatted. Before submitting code, make sure to run `cargo fmt` to make sure it conforms to the standard. ## Code Generation This library is (mostly) authored via code generation by parsing the OpenAPI specification for Stripe. It consists of 3 main pieces: * `async-stripe`: The definition of the `Stripe` client * `async-stripe-types`: Core type definitions, used a ton in generated code * `generated/*`: Generated crates which implement `Stripe` API requests and related types. * `async-stripe-webhook`: Glue code for parsing and validating `Stripe` webhook events and generated code for deserializing the events themselves. No changes should be made to code in a `generated/*` folder. If you'd like to change that code, please see the `README` in the `openapi` crate which explains the code generation process in more detail. If you'd like to update the version of the OpenAPI specification being used to generated code, you can run (in the `openapi` directory) ```sh cargo run --release -- --fetch latest ``` This will automatically pull the latest OpenAPI spec, generate new code, format it, and copy it into library. ## Testing To run the tests, you will need to run a [`stripe-mock`](https://github.com/stripe/stripe-mock) server and select a runtime. CI runs tests against all runtimes, but it is encouraged you test your changes locally against a few runtimes first. ```sh docker run --rm -d -it -p 12111-12112:12111-12112 stripe/stripe-mock:v0.197.0 cargo test --features runtime-blocking ``` ## Communication It is encouraged to open an issue before you create a PR as a place for pre-implementation discussion. If you're unsure about your contribution or simply want to ask a question about anything just open an issue and we'll chat. ## Architecture This project makes wide use of code generation, with API types generated directly from the stripe openapi spec. This is what the openapi tool does. All generated code ends up in the handily named generated folder which is exposed based on features specified. In some cases, it is helpful to have additional logic associated with a datatype to, for example, capture a create `Charge` object. This additional impl goes in the `charge_ext.rs` file in the `resources` folder, to provide a clean seperation between generated and hand maintained files. If you notice that logic is missing, please add it to (or create) the appropriate `ext` file. ## Versioning The project uses automated versioning via [release-plz](https://release-plz.ieme.me/) which provides independent crate versioning based on actual changes. **Manual version bumping is no longer required.** ### How It Works 1. **Automatic Detection**: Release-plz analyzes commits and code changes to determine which crates need version updates 2. **Independent Versioning**: Each crate versions independently - only crates with actual changes get bumped 3. **Dependency Updates**: When a crate is updated, dependent crates automatically get updated to use the new version 4. **Release PR**: All version changes are proposed via a "Release PR" for review before publishing ### Developer Workflow 1. **Make Changes**: Work on features/fixes using conventional commit messages 2. **Merge PR**: Standard PR review and merge process 3. **Release PR Created**: Release-plz automatically creates/updates a Release PR with version bumps 4. **Review & Merge**: Team reviews the Release PR and merges when ready 5. **Automatic Publishing**: Merging triggers publishing to crates.io in dependency order ### Code Generation with Versioning When updating generated code: 1. Run code generation: `cd openapi && cargo run --release -- --fetch latest` 2. Commit with conventional message: `feat(codegen): update to latest Stripe OpenAPI spec` 3. Create PR and merge normally 4. Release-plz will handle version updates automatically **No manual version coordination needed** - the tool handles all internal dependency version updates automatically. # Error Handling import { Callout } from "fumadocs-ui/components/callout" import { CodeFromFile } from "@/components/code-from-file" import { EventParser } from "@/components/event-parser" 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: ## 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 ### Handling Specific HTTP Status Codes Different status codes indicate different types of failures: ## 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](https://stripe.com/docs/error-codes). ## Parsing Errors By default, `async-stripe` uses [`miniserde`](https://github.com/dtolnay/miniserde) for deserializing API responses. This significantly reduces compile times and binary size, but provides minimal error messages when deserialization fails. ### Understanding Deserialization Failures If you receive a deserialization error, it may look like: ``` Error: failed to deserialize response ``` This typically means the JSON response from Stripe doesn't match the expected structure. This can happen when: * Stripe adds new fields to their API * You're using an outdated version of `async-stripe` * The response contains unexpected values ### Getting Better Error Messages For detailed diagnostics about which field failed and why, enable the `deserialize` feature to use `serde` instead: ```toml [dependencies] stripe-core = { version = "1.0.0-alpha.8", features = ["customer", "deserialize"] } ``` This provides comprehensive error context with `serde_path_to_error`, showing exactly where in the JSON structure the error occurred *at the cost of significantly increased compile times, link times, and binary size*. For context, the parser below needs 14MB just to parse webhook data. The compile-time and binary size impact of `serde_json` is substantial due to monomorphization. Each generic serde function gets compiled separately for every type, leading to code bloat. With hundreds of Stripe types, this results in massive binaries and slow compile times, since stripe needs to generate X00,000 lines of code to define how to deserialize each type. `miniserde` avoids this by using trait objects instead of generics, dramatically reducing the amount of generated code. For a deep dive into this topic, see [The Dark Side of Inlining and Monomorphization](https://nickb.dev/blog/the-dark-side-of-inlining-and-monomorphization/). ### Testing Event Parsing You can test how `async-stripe` parses Stripe events using the interactive parser below. This uses the actual async-stripe parser compiled to WebAssembly with `serde_path_to_error`, which reports exactly where deserialization failed: See the [Performance](/docs/performance) documentation for more details on the hybrid serialization strategy and when to use the `deserialize` feature. ## Retry Strategies For transient errors (network issues, server errors), use the built-in retry strategies: See the [Request Strategies](/docs/request-strategy) 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: ### 2. Use Idempotency Keys For critical operations (especially payment creation), always use idempotency keys to prevent accidental duplicate charges: ### 3. Log Error Details The `ApiErrors` struct contains useful debugging information: ### 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. # Quick Start import { Cards, Card } from "fumadocs-ui/components/card" import { BookOpen, Code, ExternalLink, FileText } from "lucide-react" import { CodeFromFile } from "@/components/code-from-file" import { Callout } from "fumadocs-ui/components/callout" import { CurrentVersion } from "@/components/current-version" Welcome to the **async-stripe** documentation. This library provides strongly-typed, safe, and performant Rust bindings for the Stripe API. It is designed to be modular, allowing you to pull in only the specific Stripe resources you need for your application. } title="API Documentation" description="Complete API reference and type documentation on docs.rs" href="https://docs.rs/async-stripe/latest/stripe" external /> } title="Examples" description="Browse real-world examples for common Stripe operations" href="https://github.com/arlyon/async-stripe/tree/master/examples" external /> } title="Stripe API Reference" description="Official Stripe API documentation and reference" href="https://docs.stripe.com/api" external /> } title="Stripe Docs" description="Official guides, tutorials, and integration docs from Stripe" href="https://docs.stripe.com" external /> Using an LLM? See [llms-full.txt](/llms-full.txt). ## API Version The current API version for async-stripe is: This means all API requests will use this version regardless of your Stripe account's default API version. This ensures consistent behavior and prevents breaking changes from affecting your application unexpectedly. `async-stripe` is pinned to a specific Stripe API version. Each week, we sync against the official APIs and bump our types to match. **It is critically important** that you understand which version you're using, as mismatches between the library version and your expectations can lead to unexpected behavior, missing features, or parsing errors. As a result, you should when possible be regularly updating the both this library, and your Stripe account's api, to the latest available version. We do not currently have the resources to backport fixes to older APIs. Any webhooks received that to not match this version will publish a warning via `tracing`. ## Quick Start Here's a quick example of how to create a new Stripe Customer. ### 1. Add dependencies to your `Cargo.toml` You'll need the main `async-stripe` crate for the client and a resource crate for the APIs you want to use (e.g., `stripe-core` for customers). ### 2. Create a Customer The new API uses a builder pattern that flows naturally from request creation to sending. ## Working with Expandable Fields Many Stripe API responses contain related objects that are returned as IDs by default. You can use the `expand` parameter to request the full object instead. The library represents these fields using the `Expandable` type. ### Always Get the ID Regardless of whether a field is expanded or not, you can always safely extract the ID: ```rust use stripe::{Client, Charge}; let client = Client::new(secret_key); let charge = Charge::retrieve(&client, &charge_id, &[]).await?; // Works whether customer is an ID or expanded object let customer_id = charge.customer.id(); println!("Customer ID: {}", customer_id); ``` ### Accessing the Expanded Object When you expand a field, you can access the full object: ```rust use stripe::{Client, Charge}; let client = Client::new(secret_key); // Request the customer object to be expanded let charge = Charge::retrieve(&client, &charge_id, &["customer"]).await?; // Check if we got the full object if let Some(customer) = charge.customer.as_object() { println!("Customer email: {:?}", customer.email); } else { println!("Customer was not expanded"); } ``` ### Available Methods The `Expandable` enum provides several convenience methods: * `.id()` - Get the ID (always works, whether expanded or not) * `.is_object()` - Check if the field contains the full object * `.as_object()` - Get a reference to the object if available * `.into_object()` - Take ownership of the object if available * `.into_id()` - Take ownership of the ID For more details on object expansion in the Stripe API, see the [official documentation](https://stripe.com/docs/api/expanding_objects). ## Known Limitations Due to the current architecture of the OpenAPI generator, the following Stripe features are not yet supported by async-stripe: ### File Uploads Endpoints requiring `multipart/form-data` (e.g., uploading Identity verification documents or Dispute evidence) are not currently generated. **Workaround**: Use a raw HTTP client like `reqwest` to upload files directly: ```rust use reqwest::multipart; let form = multipart::Form::new() .text("purpose", "dispute_evidence") .part("file", multipart::Part::bytes(file_data) .file_name("evidence.pdf") .mime_str("application/pdf")?); let response = reqwest::Client::new() .post("https://files.stripe.com/v1/files") .bearer_auth(&secret_key) .multipart(form) .send() .await?; ``` ### Binary Downloads Endpoints returning binary data (e.g., PDF invoices via `/v1/invoices/:id/pdf`) are not currently generated. **Workaround**: Use a raw HTTP client to download binary content: ```rust let response = reqwest::Client::new() .get(format!("https://api.stripe.com/v1/invoices/{}/pdf", invoice_id)) .bearer_auth(&secret_key) .send() .await?; let pdf_bytes = response.bytes().await?; std::fs::write("invoice.pdf", pdf_bytes)?; ``` We're tracking these limitations and plan to add support in future releases. You can use async-stripe for the vast majority of Stripe operations alongside a raw HTTP client for these specific cases. # Migration Guide # Migration Guide: Upgrading to 1.0 Alpha > **Note**: We are still expecting a few breaking changes before RC. This guide will be updated as changes are made. This release overhauls the code generation process to achieve near-complete coverage of the Stripe API ([https://github.com/arlyon/async-stripe/issues/32](https://github.com/arlyon/async-stripe/issues/32)). To avoid further bloating compile times, it also includes crates splitting. Consequently, **there are many breaking changes in this release**. If you have any trouble updating to this release or want help with it, please file an issue! There will likely be multiple release candidates, so suggestions are welcome for ways to improve ergonomics along the release candidate trail. This release should not regress compile times, but will not improve them. However, it paves the way for upcoming improvements! ## Request Migration The main breaking changes center around crate splitting and naming. Please see the [README](README.md#modular-crate-structure) for a more detailed overview of the new crate structure. In short, instead of one `async-stripe` crate, there is now: * `async-stripe`: A client for making Stripe requests * `stripe-*`: A collection of crates implementing requests for different subsets of the Stripe API So while client initialization is unchanged, making a request requires the following general migration: ### Before `Cargo.toml` ```toml async-stripe = { version = "0.28", features = ["runtime-tokio-hyper", "core"] } ``` ```rust use stripe::{Client, CreateCustomer, Customer}; async fn create_customer(client: &Client) -> Result<(), stripe::Error> { let customer = Customer::create( &client, CreateCustomer { email: Some("test@async-stripe.com"), ..Default::default() }, ).await?; Ok(()) } ``` ### After `Cargo.toml` ```toml async-stripe = { version = "TBD", features = ["runtime-tokio-hyper"] } # Note the addition of the `customer` feature as well - each object in the Stripe API # now has its related requests feature-gated. stripe-core = { version = "TBD", features = ["runtime-tokio-hyper", "customer"] } ``` ```rust use stripe::Client; use stripe_core::customer::CreateCustomer; async fn create_customer(client: &Client) -> Result<(), stripe::Error> { let customer = CreateCustomer { email: Some("test@async-stripe.com"), ..Default::default() } .send(client) .await?; Ok(()) } ``` The locations where such a migration is necessary are most easily found due to compiler errors on upgrading. Information on determining the crate a request lives in can be found in the [README](README.md#stripe-request-crates). The general steps will be: 1. Find the required crate and feature for the request by [searching here](crate_info.md) 2. Using `Account` and `create` as an example, convert the general structure: * `Account::create(&client, CreateAccount::new()).await` to `CreateAccount::new().send(&client).await` 3. Resolve any parameter name changes (with help from autocompletion or the docs). Naming should hopefully be more consistent and there will no longer be cases of duplicate names ([https://github.com/arlyon/async-stripe/issues/154](https://github.com/arlyon/async-stripe/issues/154)) ## Webhook Improvements The generated webhook `EventObject` now takes advantage of explicit webhook object types exposed in the OpenAPI spec. This allows keeping generated definitions up to date with the latest events more easily. Destructuring `EventObject` now directly gives the inner object type, rather than requiring matching on both `EventType` and `EventObject`. See the migration example below for how usage changes. ### Before ```rust use stripe::{EventType, EventObject}; fn handle_webhook_event(event: stripe::Event) { match event.type_ { EventType::CheckoutSessionCompleted => { if let EventObject::CheckoutSession(session) = event.data.object { println!("Checkout session completed! {session:?}"); } else { // How should we handle an unexpected object for this event type? println!("Unexpected object for checkout event"); } } _ => {}, } } ``` ### After ```rust use stripe_webhook::{Event, EventObject}; fn handle_webhook_event(event: stripe::Event) { match event.data.object { EventObject::CheckoutSessionCompleted(session) => { println!("Checkout session completed! {session:?}"); } _ => {}, } } ``` The same examples for use with `axum`, `actix-web`, and `rocket` can be found in the [examples](/examples) folder. Instead of importing from `stripe` and enabling the `webhook` feature, you should include the dependency `stripe_webhook` and import from that crate instead. ## Generated Type Definitions * `Deleted<>` objects (such `Deleted`) no longer expose a boolean `deleted`. This was always `true` and only used internally as a discriminant when deserializing. The generic type `Deleted` has been removed and replaced by generated types such as `DeletedAccount`, which sometimes contain additional fields. * Optional `List` no longer deserialized with `serde(default)`, the type has been changed `List` -> `Option>`. The default implementation of `List` produced data not upholding the `List` invariant of having a meaningful `url`. * Types used only for requests now use borrowed types more consistently (and more often). For example, previously the top-level `CreateCustomer.description` expected `Option<&str>`, but `CreateCustomerShipping.phone` expected `Option`. Now both expect `Option<&str>`. In general, the following changes are made to request parameters that required owned data: * `String` -> `&str` * `Metadata` (alias for `HashMap`) -> `&HashMap` * `Vec<>` -> `&[<>]` ## Pagination The required migration is similar to the [migration](#request-migration) for other requests. The main changes are around parameter handling to avoid lifetime issues ([https://github.com/arlyon/async-stripe/issues/246](https://github.com/arlyon/async-stripe/issues/246)). Additionally, obtaining a paginable stream no longer requires a separate request for the first page. `paginate` can be called directly on `List*` parameters. ### Before ``` let params = ListCustomers::new(); let mut stream = Customer::list(&client, ¶ms).await?.paginate(params); // ...Any desired stream operations ``` ### After ``` let mut stream = ListAccount::new().paginate().stream(&client) // ...Any desired stream operations ``` ## Other Breaking Changes * Enums no longer implement `Default` (the OpenAPI spec does not specify that a specific variant should be preferred.) * Some more complex definitions of `*Id` have been relaxed. This should not affect general usage treating such types as opaque identifiers, but types such as `PaymentSourceId` now just are a newtype wrapper around a `String`. This change was made to both simplify code generation, and avoid deserialization errors due to missing or changed prefix specifications (which was common since the Stripe API does not consider this a [breaking change](https://stripe.com/docs/upgrades#what-changes-does-stripe-consider-to-be-backwards-compatible). * To minimize compile time, deserialization implementations are limited to where they are necessary. Previously, some nested parameter types (such as `CreateAccountDocuments`, but not `CreateAccount`) would provide deserialization implementations. Please let us know if such implementations are useful, and they could be added under a feature flag. * Types related to the errors Stripe returns now use generated types, which should ensure they stay up to date, preventing errors error deserialization errors like ([https://github.com/arlyon/async-stripe/issues/381](https://github.com/arlyon/async-stripe/issues/381)) and ([https://github.com/arlyon/async-stripe/issues/384](https://github.com/arlyon/async-stripe/issues/384)) * The main user-facing change will be `StripeError::Stripe(RequestError)` -> `StripeError::Stripe(ApiErrors, StatusCode)` since the autogenerated `ApiErrors` does not include the status code. * The `id` method on `Expandable` now returns a reference: `&T::Id`. All id types implement `Clone` so to achieve the previous behavior, use `.id().clone()`. You can also obtain the id without cloning by consuming an `Expandable` with `into_id`. * `*Id` types no longer derive `default`. The previous default was an empty string, which will never be a valid id * Removed the `AsRef` implementation for enums, use `as_str` instead. Since most of these changes are related to code generation, it is likely there are some breaking changes we missed here. If so, please open an issue (especially for changes that degrade library ergonomics). ## Non-breaking Changes * `List<>` types now are paginable. This allows usage such as paginating the external accounts returned as `List` from retrieving an account. * `SearchList<>` types are now paginable. * The `smart-default` dependency was removed. # Pagination import { Cards, Card } from "fumadocs-ui/components/card" import { ExternalLink } from "lucide-react" import { CodeFromFile } from "@/components/code-from-file" 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. } title="Stream Trait" description="Learn about the Stream trait from the futures crate" href="https://docs.rs/futures/latest/futures/stream/trait.Stream.html" external /> } title="TryStreamExt Trait" description="Extension trait for Stream with convenient combinators" href="https://docs.rs/futures/latest/futures/stream/trait.TryStreamExt.html" external /> ## Request-Based Pagination (Recommended) 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. ## The PaginationExt Trait Sometimes you already have a `List` 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. ### How it works Calling `.into_paginator()` on a `List` 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. # Performance import { Callout } from "fumadocs-ui/components/callout" import { EventParser } from "@/components/event-parser" The Stripe API has a massive surface area, and the code generation process creates a very large number of types. Using `serde` for both serialization and deserialization in previous versions resulted in excessive codegen size and long compile times. To solve this, `async-stripe` now uses a hybrid approach. ## Hybrid Serialization Strategy `async-stripe` uses different libraries for serialization and deserialization to optimize both performance and compile times: | Operation | Library | Why | | ---------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Serialization** (requests → Stripe) | `serde` | Rich feature set ideal for building complex request parameters. Full control over field naming, optional fields, and nested structures. | | **Deserialization** (Stripe → responses) | `miniserde` | Minimal, high-performance JSON library that significantly reduces compile times and binary size. Optimized for the common case of deserializing API responses. | ### Benefits * **Faster Compile Times**: `miniserde` has minimal proc-macro overhead compared to full `serde` derives * **Smaller Binary Size**: Less codegen means smaller final binaries * **Same API Surface**: You don't need to change how you use the library `miniserde` provides minimal error messages when deserialization fails. If you encounter a deserialization error and need detailed diagnostics about which field failed and why, enable the `deserialize` feature to use `serde` instead, which provides comprehensive error context. You can also paste the raw json event into the [Stripe Event Parser](/docs/performance#try-it-out) to see exactly where the error occurred. ## Using `serde::Deserialize` If you need to use `serde::Deserialize` on Stripe response types within your own application (for example, to store them in a database or serialize them for caching), you can enable the `deserialize` feature on any of the `stripe-*` crates. ```toml [dependencies] stripe-core = { version = "1.0.0-alpha.8", features = ["customer", "deserialize"] } ``` This adds `serde::Deserialize` implementations to all Stripe types in that crate, allowing you to use them with any `serde`-compatible serialization format (JSON, TOML, MessagePack, etc.). ## Performance Considerations The hybrid approach means: 1. **Request serialization** remains flexible and feature-rich 2. **Response deserialization** is optimized for speed and compile time 3. **Opt-in `serde` support** for advanced use cases that need it This design ensures `async-stripe` scales well even as the Stripe API continues to grow, without imposing long compile times on all users. ## Try It Out Want to see how async-stripe parses Stripe events? Try pasting an event JSON below. This uses the actual async-stripe parser compiled to WebAssembly with `serde_path_to_error`, which reports exactly where deserialization failed. # Release Workflow # Release Process This document explains the automated release process for async-stripe using release-plz. ## Overview The project uses [release-plz](https://release-plz.ieme.me/) to automate the entire release workflow, including independent versioning, changelog generation, and publishing to crates.io. Release-plz provides Rust-native workspace support with intelligent dependency management. ## Release Workflow ### 1. Development & PRs * Create PRs for features, fixes, or code generation updates * Use conventional commit messages for proper version detection * All PRs follow standard merge practices ### 2. Automated Release PR Creation * After merge to master, release-plz automatically: * Analyzes commit history since last release * Determines version bumps for affected crates using conventional commits + API analysis * Creates/updates a "Release PR" with: * Version bumps in affected Cargo.toml files * Updated workspace-level CHANGELOG.md * Dependency version updates ### 3. Release Review & Approval * Team reviews the Release PR to verify: * Version bump appropriateness * Changelog accuracy * Dependency update correctness * No code changes - just version and changelog updates ### 4. Release Execution * Merging the Release PR triggers automatic publishing: * Creates git tags for each released crate (`-v`) * Publishes crates to crates.io in dependency order * Creates GitHub releases with changelog notes * Skips crates with no changes ## Prerequisites ### GitHub Actions Permissions To enable release-plz to work properly, you need to configure GitHub Actions permissions: 1. **Go to Repository Settings** → **Actions** → **General** 2. **Set Workflow permissions** to "Read and write permissions" 3. **Check "Allow GitHub Actions to create and approve pull requests"** This allows the workflow to: * Create and update Release PRs * Commit version changes * Create Git tags and GitHub releases ### Required Secrets The workflow requires these repository secrets: * `REPO_SCOPED_TOKEN`: Personal Access Token with repo permissions (for triggering CI on Release PRs) * `CARGO_REGISTRY_TOKEN`: Token from crates.io for publishing packages ## Configuration (release-plz.toml) The release process is configured in `release-plz.toml`: ### Workspace Settings ```toml [workspace] semver_check = true # Enable cargo-semver-checks for API analysis changelog_update = true # Single workspace-level changelog dependencies_update = true # Auto-update dependency versions ``` ### Package-Specific Settings ```toml [[package]] name = "async-stripe" publish_features = ["runtime-tokio-hyper"] # Main library features ``` ### Key Features 1. **Independent Versioning** * Each crate versions independently based on actual changes * Only crates with modifications get version bumps * Dependent crates auto-update when dependencies change 2. **Intelligent Semantic Versioning** * Uses conventional commits for initial version determination * Enhanced with `cargo-semver-checks` for actual API breaking change detection * Can promote minor bumps to major if breaking changes detected 3. **Workspace-Aware Publishing** * Publishes crates in correct dependency order * Automatically updates internal dependency version constraints * Skips crates already published at current version 4. **Single Changelog** * Maintains one `CHANGELOG.md` at workspace root * Groups changes by crate and version * Generated using git-cliff ## Commit Message Format Use conventional commit format for proper version detection: * `feat:` - new feature (minor version bump) * `fix:` - bug fix (patch version bump) * `feat!:` or `BREAKING CHANGE:` - breaking change (major version bump) * `chore:`, `docs:`, `test:` - no version bump Examples: ``` feat(types): add cryptocurrency payment methods fix(webhook): resolve signature validation timing feat(client)!: change async API to use new error types ``` ## Manual Intervention The process is mostly automated with a review gate. Manual intervention needed for: * Reviewing and approving Release PRs * Fixing failed releases * Emergency hotfixes outside the normal flow * Adjusting release configuration ## Crate Publishing Order Release-plz automatically determines publishing order based on dependencies: 1. `async-stripe-types` (foundation) 2. `async-stripe-shared` (depends on types) 3. Generated domain crates (billing, checkout, etc.) 4. `async-stripe-client-core` 5. `async-stripe-webhook` 6. `async-stripe` (main library) Each crate only publishes if it has changes or updated dependencies. # Request Strategies import { CodeFromFile } from "@/components/code-from-file" Network requests fail. **async-stripe** provides a robust `RequestStrategy` API to handle idempotency keys and retries automatically, ensuring your application handles transient failures gracefully without double-charging customers. ## Strategy Options The library implements the "Full Jitter" exponential backoff algorithm recommended by Stripe engineering to prevent thundering herd problems. | Strategy | Description | Use Case | | ----------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | | `Once` | Fire and forget. No retries. | You are handling retries manually | | `Idempotent(key)` | Fire once with a specific, user-provided Idempotency Key | Critical payment creation flows where you need control over the idempotency key | | `Retry(n)` | Retry `n` times with a random UUID idempotency key | General retry logic without backoff | | `ExponentialBackoff(n)` | Retry `n` times with exponential backoff (0.5s base, up to 8s max) and randomized jitter | Recommended for production to handle transient failures gracefully | ## Usage You can apply a strategy globally to the client (for all requests) or override it on a per-request basis. ### Global Client Strategy ### Per-Request Strategy ## Custom Idempotency Keys For critical operations where you need precise control over deduplication (e.g., linking payment creation to your own order IDs), you can provide your own idempotency key instead of using an auto-generated UUID. The `IdempotencyKey` type validates that your key: * Is not empty * Does not exceed 255 characters (Stripe's limit) ```rust use stripe::{Client, CreatePaymentIntent, Currency, RequestStrategy, IdempotencyKey}; let client = Client::new(secret_key); // Use your own unique identifier (e.g., from your database) let key = IdempotencyKey::new("order_12345_attempt_1").unwrap(); CreatePaymentIntent::new(1000, Currency::USD) .request_strategy(RequestStrategy::Idempotent(key)) .send(&client) .await?; ``` This ensures that even if your application retries the request due to a network failure, Stripe will recognize it as the same operation and not create duplicate charges. import { Callout } from "fumadocs-ui/components/callout" Idempotency keys are valid for 24 hours. After that, Stripe will treat a request with the same key as a new operation. ### When to Use Custom Keys * **Order processing**: Use `order_{id}_payment` to tie payment creation to order IDs * **Subscription operations**: Use `subscription_{id}_cancel_{timestamp}` for cancellations * **Multi-step flows**: Maintain idempotency across application restarts by persisting keys ### When to Use Auto-Generated Keys For most operations, the auto-generated UUID keys from `RequestStrategy::Retry(n)` or `RequestStrategy::ExponentialBackoff(n)` are sufficient and more convenient. # Testing import { Callout } from "fumadocs-ui/components/callout" import { CodeFromFile } from "@/components/code-from-file" Testing Stripe integrations can be challenging. This guide covers the tools and techniques available for testing your Stripe integration without affecting production data or waiting for real-time events. ## Testing with stripe-mock For fast, offline testing without hitting the real Stripe API, you can use [stripe-mock](https://github.com/stripe/stripe-mock), Stripe's official API mocking server. Point your client to the mock server using the `.url()` method: ```rust use stripe::Client; // Start stripe-mock on port 12111 // docker run -p 12111:12111 stripe/stripe-mock let client = Client::builder("sk_test_123") .url("http://localhost:12111") .build(); // This request will hit stripe-mock instead of the real API let customer = Customer::create(&client, CreateCustomer::new()).send().await?; ``` stripe-mock provides realistic API responses but doesn't maintain state. It's ideal for integration tests that verify request formatting and response parsing without side effects. ### Benefits of stripe-mock * **Fast**: No network latency * **Offline**: Works without internet connection * **Deterministic**: Same inputs always produce same outputs * **Free**: No API usage counted against your account * **Safe**: No risk of accidental charges or data changes ### Running stripe-mock ```bash # Using Docker (recommended) docker run -p 12111:12111 stripe/stripe-mock # Or install directly go install github.com/stripe/stripe-mock@latest stripe-mock -http-port 12111 ``` stripe-mock is for testing only. Always use the real Stripe API in production. ## Time Travel with Test Clocks Testing subscription logic that spans months or years is difficult in real-time. async-stripe supports [Test Clocks](https://stripe.com/docs/billing/testing/test-clocks), allowing you to simulate the passage of time for customers without waiting. ### Creating a Test Clock Create a test clock frozen at a specific time (Unix timestamp): ### Advancing Time Once you have a test clock, you can advance it to trigger time-based events like subscription renewals: Advancing a test clock is an asynchronous operation on Stripe's side. You can poll the status to wait for completion: ### What Test Clocks Affect Test clocks control the timing for: * **Subscription billing cycles** - Trigger renewals without waiting * **Trial periods** - Fast-forward through trial expirations * **Invoice finalization** - Control when invoices are created and charged * **Dunning** - Test retry logic for failed payments * **Prorations** - Verify proration calculations when changing plans Test clocks only work in test mode. All objects created with a test clock will be deleted when the clock is deleted. ### Best Practices **Delete test clocks after testing** to clean up test data: **Use descriptive names** to identify which test created which clock - the example uses `"Example test clock 1"` but in real tests you'd use something like `"test_annual_subscription_renewal"`. **Advance time incrementally** to observe state changes at each step rather than jumping too far ahead at once. For more details, see the [Stripe Test Clocks documentation](https://stripe.com/docs/billing/testing/test-clocks). # TLS: Rustls & Ring Security is paramount when dealing with payments. By default, **async-stripe** may use native system TLS (via OpenSSL on Linux, SecureTransport on macOS, SChannel on Windows). However, for consistent security across platforms or to avoid C-dependency linking issues, we fully support `rustls`. ## Crypto Providers When enabling `rustls`, you must choose a cryptographic backend. This is handled via feature flags in `async-stripe` to avoid conflicts. * **`rustls-aws-lc-rs`**: *(Default/Recommended)* Uses AWS's `aws-lc-rs` crypto provider. Fast and formally verified. * **`rustls-ring`**: Uses the `ring` crypto library. ## Configuring Cargo.toml To use Rustls with the Ring provider, you must disable default features and opt-in explicitly. ```toml [dependencies.async-stripe] version = "1.0.0-alpha.8" default-features = false features = [ "runtime-tokio-hyper", # The runtime "rustls-tls-native", # Use rustls with native root certs # "rustls-tls-webpki-roots", # Alternative: use webpki-roots instead of system certs "rustls-ring" # Explicitly opt-in to Ring provider ] ``` If you encounter compilation errors regarding missing crypto providers when using `rustls`, ensure you have explicitly enabled either `rustls-aws-lc-rs` or `rustls-ring`. # Development Environment ## Development Environment (Mise) This project uses [mise](https://mise.jdx.dev) (formerly rtx) to manage development tool versions. This ensures that all contributors and CI environments use the exact same version of the Rust toolchain, preventing "it works on my machine" issues related to compiler versions. ### Why Mise? * **Reproducible Builds**: Pins the exact Rust version defined in `mise.toml` * **Fast**: Written in Rust, it is significantly faster than alternatives like asdf * **Seamless Integration**: Automatically switches tool versions when you enter the project directory ## Setup ### 1. Install Mise Follow the [official installation guide](https://mise.jdx.dev/getting-started.html) for your OS. ```bash # Example (MacOS/Linux) curl https://mise.run | sh ``` ### 2. Install Project Tools Navigate to the project root and run: ```bash mise install ``` This will read the `mise.toml` file and install the pinned Rust version. ## Configuration The tool versions are defined in `mise.toml` at the root of the repository. We strictly pin the Rust version to match our CI environment and `Cargo.toml` rust-version requirements. ```toml [tools] # We pin to a specific stable version to ensure consistent behavior # across all developer machines. rust = "1.88" ``` ## Workflow Once installed and hooked into your shell, mise automatically activates the correct tool versions when you `cd` into the directory. ### Check active version ```bash mise current # Output: rust 1.88 ``` ### Run a one-off command If you haven't added the shell hook, you can run commands in the environment explicitly: ```bash mise exec -- cargo test ``` # Webhooks import { Cards, Card } from "fumadocs-ui/components/card" import { Code, ExternalLink } from "lucide-react" import { CodeFromFile } from "@/components/code-from-file" import { Callout } from "fumadocs-ui/components/callout" Stripe uses webhooks to notify your application when events happen in your account. async-stripe provides utilities to securely verify and parse webhook events. } title="Stripe Webhooks Guide" description="Official Stripe documentation on webhooks" href="https://stripe.com/docs/webhooks" external /> } title="Testing Webhooks" description="Learn how to test webhooks with the Stripe CLI" href="https://stripe.com/docs/webhooks/test" external /> ## Overview Webhooks allow Stripe to push real-time notifications to your application when events occur, such as successful payments, failed charges, or subscription updates. This is more reliable than polling the API and ensures you can respond to events immediately. ## Webhook Signature Verification Stripe signs webhook payloads with a secret key to prevent tampering and spoofing. You must verify the signature before processing any webhook event. Always verify webhook signatures in production. Processing unverified webhooks is a security risk. ### Using the Webhook Module The `stripe_webhook` crate provides the `Webhook::construct_event` function to verify and parse webhook events in one step: ```rust use stripe_webhook::{Event, Webhook}; let event = Webhook::construct_event( &payload, // Raw request body as string &signature_header, // stripe-signature header value "whsec_xxxxx" // Your webhook signing secret )?; ``` ## Framework Examples async-stripe includes webhook examples for popular Rust web frameworks: } title="Axum" description="Webhook handling with Axum using a custom extractor" href="https://github.com/arlyon/async-stripe/tree/master/examples/webhook-axum" external /> } title="Rocket" description="Webhook handling with Rocket using request guards" href="https://github.com/arlyon/async-stripe/tree/master/examples/webhook-rocket" external /> } title="Actix Web" description="Webhook handling with Actix Web" href="https://github.com/arlyon/async-stripe/tree/master/examples/webhook-actix" external /> ## Axum Integration Here's a complete example of handling webhooks in an Axum application with automatic signature verification: The `StripeEvent` extractor automatically: 1. Extracts the `stripe-signature` header 2. Reads the raw request body 3. Verifies the signature 4. Parses the event 5. Returns a 400 Bad Request if verification fails ### Handling Events Once you have a verified event, pattern match on the event type: ## Testing Webhooks Locally Use the Stripe CLI to forward webhook events to your local development server: ```bash # Install the Stripe CLI # https://stripe.com/docs/stripe-cli # Forward events to your local endpoint stripe listen --forward-to localhost:4242/stripe_webhooks # In another terminal, trigger test events stripe trigger checkout.session.completed stripe trigger payment_intent.succeeded ``` ### Bypassing Signature Verification (Development Only) For early-stage local testing where you don't want to set up the Stripe CLI or need to test with manually crafted payloads, you can use the `insecure()` method to bypass signature verification: ```rust use stripe_webhook::{Event, Webhook}; // ONLY use this in development/testing environments let event = Webhook::insecure(payload)?; ``` **Never use `Webhook::insecure()` in production.** This method completely bypasses signature verification, making your application vulnerable to webhook spoofing and tampering. Always use `construct_event()` with proper signature verification in production environments. This is useful for: * Local development without the Stripe CLI * Unit testing with fixture data * Integration testing in CI/CD environments For production-like testing, always use the Stripe CLI with proper webhook secrets. ## Event Types The `EventObject` enum contains variants for all Stripe webhook events. Common event types include: * `CheckoutSessionCompleted` - A Checkout Session was successfully completed * `PaymentIntentSucceeded` - A PaymentIntent successfully completed * `PaymentIntentFailed` - A PaymentIntent failed * `CustomerCreated` - A new customer was created * `InvoicePaid` - An invoice was successfully paid * `SubscriptionCreated` / `SubscriptionUpdated` / `SubscriptionDeleted` - Subscription lifecycle events For a complete list of all available event types, see the [Stripe Event Types documentation](https://stripe.com/docs/api/events/types). Not all Stripe events may be represented in the `EventObject` enum yet. Use the catch-all pattern `_` to handle unknown events gracefully. ## Best Practices ### Return 200 Quickly Stripe expects a 200 response within a few seconds. Process webhooks asynchronously if they require long-running operations: ```rust async fn handle_webhook(StripeEvent(event): StripeEvent) -> StatusCode { // Spawn background task for heavy processing tokio::spawn(async move { process_event(event).await; }); // Return immediately StatusCode::OK } ``` ### Handle Idempotency Stripe may send the same webhook multiple times. Use the `event.id` to track processed events and avoid duplicate processing: ```rust async fn handle_webhook(StripeEvent(event): StripeEvent) { if already_processed(&event.id).await { return; // Skip duplicate } process_event(event).await; mark_as_processed(&event.id).await; } ``` ### Secure Your Endpoint * Use HTTPS in production * Keep your webhook signing secret secure (use environment variables) * Verify signatures on every request * Return appropriate status codes (200 for success, 400 for bad requests) * Log webhook failures for debugging ## Other Web Frameworks While this guide shows Axum integration, the same principles apply to other frameworks: * Extract the `stripe-signature` header * Read the raw request body as a string * Call `Webhook::construct_event` with the payload, signature, and secret * Return 400 if verification fails, 200 on success