rustauth-stripe 0.3.0

Stripe integration for RustAuth.
Documentation

rustauth-stripe

Server-side Stripe billing integration for RustAuth.

What It Is

rustauth-stripe adds Stripe customer and subscription behavior to an RustAuth server. It is server-side only: browser helpers should call the HTTP endpoints exposed by this plugin.

What It Provides

  • User and organization Stripe customer linking.
  • Customer creation on sign-up.
  • Checkout Session creation for subscription upgrades.
  • Subscription cancel and restore endpoints.
  • Billing portal session creation.
  • Active subscription listing.
  • Stripe webhook signature verification.
  • Local subscription synchronization from checkout and subscription lifecycle webhooks.
  • Schema contributions for user.stripeCustomerId, organization.stripeCustomerId, and subscription storage.

Quick Start

use rustauth::stripe::{
    stripe, StripeClient, StripeOptions, StripePlan, SubscriptionOptions,
};
use rustauth::RustAuth;

let auth = RustAuth::builder()
    .secret("secret-a-at-least-32-chars-long!!")
    .base_url("https://app.example.com/api/auth")
    .plugin(stripe(
        StripeOptions::new(
            StripeClient::new(std::env::var("STRIPE_SECRET_KEY")?),
            std::env::var("STRIPE_WEBHOOK_SECRET")?,
        )
        .create_customer_on_sign_up(true)
        .subscription(SubscriptionOptions::enabled(vec![
            StripePlan::new("pro").price_id("price_pro_monthly"),
            StripePlan::new("team")
                .price_id("price_team_monthly")
                .annual_discount_price_id("price_team_yearly")
                .seat_price_id("price_team_seat"),
        ])),
    )?)
    .build()?;
# let _ = auth;
# Ok::<(), Box<dyn std::error::Error>>(())

Run your adapter migration flow after enabling the plugin so the Stripe fields and subscription table exist.

Endpoints

When subscriptions are enabled, the plugin registers:

  • POST /subscription/upgrade
  • POST /subscription/cancel
  • POST /subscription/restore
  • GET /subscription/list
  • GET /subscription/success
  • POST /subscription/billing-portal
  • POST /stripe/webhook

Mount these under your RustAuth base path. With /api/auth, Stripe webhooks should target /api/auth/stripe/webhook.

Webhooks

The webhook endpoint validates the stripe-signature header with the secret passed to StripeOptions::new. Use the signing secret from the Stripe dashboard as-is (including the whsec_ prefix); the plugin uses it verbatim as the HMAC key, matching Stripe's official libraries. It currently handles checkout completion and subscription created, updated, canceled, and deleted events.

Use StripeOptions::on_event when your application needs to observe raw Stripe events after RustAuth has processed them.

Database hooks that create or sync Stripe customers are best-effort: failures are not returned to the sign-up or update flow, so monitor Stripe API errors in your application logs if you rely on automatic customer creation.

Organization Billing

use rustauth::stripe::{OrganizationStripeOptions, StripeOptions};

let options = StripeOptions::new(stripe_client, webhook_secret)
    .organization(OrganizationStripeOptions::enabled());

Lifecycle hooks accept plain async closures (no Box::pin at the call site):

use rustauth::stripe::{StripeOptions, SubscriptionOptions};

let options = StripeOptions::new(stripe_client, webhook_secret).subscription(
    SubscriptionOptions::enabled(vec![/* plans */])
        .on_subscription_complete(|input| async move {
            let _ = input;
            Ok(())
        }),
);

Organization support contributes organization.stripeCustomerId and uses the organization as the billing reference when requests specify customerType = "organization".

Testing

Tests can inject a fake transport without network calls:

use rustauth_stripe::stripe_api::StripeClient;
use std::sync::Arc;

let client = StripeClient::with_transport("sk_test", Arc::new(my_transport));
cargo nextest run -p rustauth-stripe

Test-mode smoke (manual)

For end-to-end validation against Stripe test mode (real API + Checkout + webhooks), use the server-side runbook — no example app required:

Database hooks are best-effort. Built-in webhook handlers skip non-actionable events, but retryable processing failures return an error so Stripe can retry. During smoke, grep logs for the messages listed in SMOKE.md §10.

Status

Experimental beta. Customer, subscription, billing portal, and webhook behavior exist, but public APIs may change before stable release.

Stripe failures during database hooks (sign-up customer, email sync, seat sync) are best-effort: they are logged and do not fail the underlying user operation, matching Better Auth 1.6.9. Built-in webhook handlers skip non-actionable events, but retryable processing failures and the optional on_event hook can fail the webhook response.

Webhook events handled

Built-in handlers (non-actionable skips return HTTP 200; retryable processing failures return an error so Stripe can retry):

  • checkout.session.completed
  • customer.subscription.created
  • customer.subscription.updated (includes cancel-at-period-end sync)
  • customer.subscription.deleted

Other event types invoke on_event only; a failing on_event returns STRIPE_WEBHOOK_ERROR.

Organization billing

customerType: "organization" requires configuring .authorize_reference(...) on SubscriptionOptions. Without it, endpoints return AUTHORIZE_REFERENCE_REQUIRED.

Better Auth compatibility

Server-side Stripe billing plugin. Aligned with Better Auth 1.6.9 where it matters; RustAuth is not a line-by-line port. For route-level parity, test counts, differences, and gaps, see UPSTREAM.md.

Links