# stripity_stripe: an Elixir client that is regenerated from Stripe's own OpenAPI spec

> stripity_stripe wraps the Stripe API for Elixir, generating most of its surface from Stripe's OpenAPI specification. It covers the setup intent flow, customer and payment method handling, object expansion, retries and a stripe-mock based test suite, though its README still documents the 2.x line while the tagged releases have reached 3.3.2.

**beam-community/stripity-stripe** — An Elixir Library for Stripe

- Repository: https://github.com/beam-community/stripity-stripe
- Stars: 1,078 · Forks: 380
- Language: Elixir
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/beam-community-stripity-stripe

## A Stripe client whose surface is regenerated from Stripe's OpenAPI spec

stripity_stripe is an Elixir library for talking to the Stripe API. The interesting part is not the call syntax but where the code comes from. The repository keeps a file named `.latest-tag-stripe-openapi-sdk`, and the Makefile reads that file to decide which tag of the Stripe OpenAPI repository to download:

```
CURRENT_VERSION := $(shell cat .latest-tag-stripe-openapi-sdk)

download-openapi-current:
	@echo "Tag: $(CURRENT_VERSION)"
	@echo "Downloading https://raw.githubusercontent.com/stripe/openapi/$(CURRENT_VERSION)/openapi/spec3.sdk.json to ./priv/openapi/spec3.sdk.json"
	@curl -o ./priv/openapi/spec3.sdk.json https://raw.githubusercontent.com/stripe/openapi/$(CURRENT_VERSION)/openapi/spec3.sdk.json
```

From that downloaded `spec3.sdk.json` the project runs a Mix task and a formatter pass. The `generate` target does exactly that:

```
generate:
	mix stripe.generate
	mix format
```

So the module set in `lib/` is, in large part, produced from Stripe's own published specification rather than hand maintained. That is the trade-off to keep in mind. A generated client tracks new Stripe endpoints without someone writing a wrapper, but its public names can shift whenever the upstream specification changes shape. The `mix.lock` and `priv/` entries in the repository layout are consistent with that pipeline. The project is MIT licensed, is not archived, and the last push was on 2026-10-13.

## Adding the dependency and pointing it at a secret key

The README says to install the dependency by version, and it gives the constraint in Mix form:

```elixir
{:stripity_stripe, "~> 2.0"}
```

It also documents installing by commit reference, which is the route some teams use when they want a single audited commit rather than a floating range:

```elixir
{:stripity_stripe, git: "https://github.com/beam-community/stripity_stripe", ref: "017d7ecdb5aeadccc03986c02396791079178ba2"}
```

Next comes configuration. API calls need a Stripe secret key, and the key can be a literal, a function, or a tuple resolved at runtime:

```elixir
import Config

config :stripity_stripe, api_key: System.get_env("STRIPE_SECRET")
# OR
config :stripity_stripe, api_key: "YOUR SECRET KEY"
```

The tuple form is the one worth copying for anything beyond a local experiment:

```elixir
config :stripity_stripe, api_key: {MyApp.Secrets, :stripe_secret, []}
# OR
config :stripity_stripe, api_key: fn -> System.get_env("STRIPE_SECRET") end
```

The JSON encoder is also configurable. If your application uses Poison rather than Jason, the README shows how to point the library at it:

```elixir
config :stripity_stripe, json_library: Poison
```

One detail catches newcomers: the README notes that adding the library to your applications list is not necessary if you are on Elixir 1.4 or newer. The example it gives for older projects is `defp application do [applications: [:stripity_stripe]] end`, which most current codebases will never need.

## The intents flow the README tells new projects to use

Stripe supports a token based approach and an intent based approach. The README is direct about which to pick: the token based approach is simpler, is not supported in Europe, and the intents API is the way forward for new development. The walkthrough that follows is therefore a setup intent flow, and it is the clearest worked example in the repository.

First the backend creates a setup intent and hands the identifier to the frontend:

```elixir
{:ok, setup_intent} = Stripe.SetupIntent.create(%{})

# Return the ID to your frontend, and pass it to the confirmCardSetup method from Stripe elements
{:ok, setup_intent.id}
```

The frontend then confirms it with Stripe elements:

```javascript
stripe.confirmCardSetup(setupIntentId, {
  payment_method: {
    ...
  }
})
.then(result => {
  const setupIntentId = result.setupIntent.id
  const paymentMethodId = result.setupIntent.payment_method

  // send the paymentMethodId and optionally (if needed) the setupIntentId
})
```

The backend retrieves or creates a customer, attaches the payment method, and then charges it off session with a payment intent, which is the shape you need for subscriptions:

```elixir
{:ok, charge} = Stripe.PaymentIntent.create(%{
  amount: cents_int,
  currency: "USD",
  customer: stripe_customer.id,
  payment_method: payment_method_id,
  off_session: true,
  confirm: true
})
```

Every call returns an `{:ok, value}` tuple, so error handling in calling code is explicit rather than implicit. One warning from the README deserves repetition: setup intents are ephemeral, and it is best to create a new one each time the user reaches your payment page.

## Expanding related objects through the :expand option

Stripe can inline related objects on some endpoints. The library exposes that through a list of strings passed in the options under the `:expand` key. Without it, retrieving a charge gives you identifiers only:

```elixir
%Charge{
  id: "ch_123",
  balance_transaction: "txn_123",
  ...
}
```

With the expansion requested, the same call returns the nested struct:

```elixir
Charge.retrieve("ch_123", expand: ["balance_transaction"])
```

The result is a `balance_transaction` field holding a `%BalanceTransaction{}` struct rather than the string `txn_123`. Because responses are decoded into structs, the practical difference is not cosmetic: your pattern matches and dialyzer friendly field access change shape depending on whether you asked for the expansion. The README points to the Stripe object expansion documentation for which objects can be expanded at all. Since the module and struct names come from the generated spec, the set of expandable fields follows the Stripe API rather than this library's own choices.

## The README still documents the 2.x line while the tagged releases sit at 3.3.2

This is the sharpest gap between the documentation and the project. The README's version table is titled as the 2.x.x status, and it maps library versions to the Stripe API version each one calls: `2.0.x` to `2018-02-28`, `2.1.0 - 2.2.0` to `2018-05-21`, `2.2.2` to `2018-08-23`, `2.2.3 - 2.3.0` to `2018-11-08`, `2.4.0 - 2.7.0` to `2019-05-16`, and `master` to `2019-10-17`. The installation snippet likewise says `{:stripity_stripe, "~> 2.0"}`.

Meanwhile the recent releases are v3.3.0 on 2026-05-01, v3.3.1 on 2026-05-03 and v3.3.2 on 2026-07-09, with the last push on 2026-10-13. The table therefore does not tell you which Stripe API version a 3.x release calls, and the README's own version constraint would pin you to the old 2.x line. The README itself warns that the table only indicates the API version being called, not necessarily its compatibility, so even the part it does cover is a pointer rather than a guarantee.

Practical consequence: pick the version constraint and the API version mapping from the hexdocs for the release you actually install, not from the README text. Nothing in the repository documents a compatibility matrix for the 3.x series.

## Requesting a specific Stripe API version does not change how responses are parsed

Since version 2.5.0 you can pass an `:api_version` option to force a particular Stripe API version for a single request. The README is careful about the catch, and it is the kind of detail that bites during an incident. The option changes the version used to make the request, but the library still expects the response to match its own default Stripe API version.

In other words, the request and the response contract can disagree. If you pin a request to an older API version and the response comes back in the shape the library expects for its default, the mismatch lands in your code rather than in an error message. The README points to the shared options section of the hexdocs for the details.

Practically, treat `:api_version` as a targeted escape hatch, not as a general way to control the API version your integration speaks. If your application needs a stable API version, decide it once at the library version level rather than per request, and verify the response shapes you depend on against the docs for the version you pinned.

## Retries, backoff and the default Hackney client

The library lets you configure how failed requests are retried. You pass a number of attempts and a backoff range in milliseconds, and the README gives a single configuration block:

```elixir
config :stripity_stripe, :retries, [max_attempts: 3, base_backoff: 500, max_backoff: 2_000]
```

The underlying HTTP client is Hackney by default, and timeouts are set by passing options for that client:

```elixir
config :stripity_stripe, hackney_opts: [{:connect_timeout, 1000}, {:recv_timeout, 5000}]
```

Two constraints come out of this. First, the timeout configuration is expressed in Hackney's option format, so if your application swaps the HTTP client for something else, this setting stops being meaningful and you need to check how the library passes options through. Second, retries against Stripe mean write calls can be attempted more than once, so a client that times out after Stripe has already accepted a request can leave your integration retrying a create. The README does not document idempotency handling, so idempotency keys and your own retry policy are your responsibility rather than the library's.

The `run-all` Makefile target is a good hint about how the project itself expects these paths to be exercised, since it runs the test suite with the mock server disabled:

```
	SKIP_STRIPE_MOCK_RUN=true mix test
```

## Running the suite against stripe-mock

The test setup depends on a separate tool. The README says you need to install `stripe-mock`, a mock HTTP server that responds like the real Stripe API. It is powered by the Stripe OpenAPI specification, which is generated from within Stripe's own API. That coupling to the same specification the library generates from is the reason the mocked responses line up with the generated structs.

The repository layout also shows a `config/` directory, a `test/` directory, a `Makefile`, a `mix.exs` and a `mix.lock`, alongside tooling files such as `.credo.exs`, `.formatter.exs` and `.commitlintrc.yml`. The presence of a formatter config and a Credo config in the root tells you the project enforces style checks, which matters if you plan to send patches.

The `run-all` target is the most complete workflow the repository offers. It resolves the newest OpenAPI tag from GitHub, downloads the spec, regenerates the code, formats it, and then runs the tests:

```
run-all:
	@$(eval LATEST_TAG=$(shell curl -s https://api.github.com/repos/stripe/openapi/releases/latest | jq -r '.tag_name'))
	@echo "Latest tag: $(LATEST_TAG)"
	@echo "Downloading https://raw.githubusercontent.com/stripe/openapi/$(LATEST_TAG)/openapi/spec3.sdk.json to ./priv/openapi/spec3.sdk.json"
	@curl -o ./priv/openapi/spec3.sdk.json https://raw.githubusercontent.com/stripe/openapi/$(LATEST_TAG)/openapi/spec3.sdk.json
	mix stripe.generate
	mix format
	SKIP_STRIPE_MOCK_RUN=true mix test
```

Note what that target pulls in: the latest released OpenAPI specification, not a pinned one. Regenerating against an unpinned upstream tag is how a local build can differ from a tagged release, so treat `run-all` as a maintenance task for the maintainers rather than a step for a consumer of the package.

## Conclusion

stripity_stripe suits Elixir teams that want a typed Stripe client without hand-writing API calls, provided they pin a version, read the generated hexdocs for the exact module names, and can accept that the README still describes the 2.x line. It is a poor fit for a project that needs a non-Elixir client, that wants a stable public API surface across major versions, or that expects the README alone to tell it which Stripe API version a given release calls. Before adopting, check the API version table against the release you pin, confirm your HTTP client still accepts the timeout options shown, and run the suite against stripe-mock to see how the library behaves without touching live Stripe data.

## FAQ

### How do I install stripity_stripe in an Elixir project?

Add it to your dependencies in mix.exs, either by version as the README shows with `{:stripity_stripe, "~> 2.0"}` or by commit reference pointing at the beam-community/stripity_stripe repository. On Elixir 1.4 or newer you do not need to add it to your applications list. The README's version constraint still refers to the 2.x line, so check the hexdocs for the release you intend to pin.

### How do I configure the Stripe secret key and timeouts in stripity_stripe?

Set the key with `config :stripity_stripe, api_key:`, and you may pass a literal, a function, or a tuple such as `{MyApp.Secrets, :stripe_secret, []}` to resolve it at runtime. Timeouts are passed as options for the HTTP client, which is Hackney by default, through `hackney_opts`. Retries are set separately with the `:retries` option, taking `max_attempts`, `base_backoff` and `max_backoff`.

### How do I charge a customer with stripity_stripe using payment intents?

Create a setup intent and pass its id to Stripe elements on the frontend, retrieve or create the customer, attach the payment method, then create a payment intent with `amount`, `currency`, `customer`, `payment_method`, `off_session: true` and `confirm: true`. The README states that the token based approach is simpler but is not supported in Europe, and that the intents API should be used for new development.

### Can I run the stripity_stripe test suite without contacting Stripe?

Yes, the tests are built around `stripe-mock`, a mock HTTP server that responds like the Stripe API and is powered by the Stripe OpenAPI specification. The README says you need to install it separately before running the tests, and the Makefile's `run-all` target runs the suite with the mock server skipped after regenerating the code from the newest OpenAPI tag.

### Does stripity_stripe support requesting a specific Stripe API version?

Since version 2.5.0 you can include the `:api_version` option to make a request against a specific Stripe API version. The README notes that the library will still expect a response matching its corresponding default Stripe API Version, so the option changes the request without changing how the response is parsed.

## Sources

- [beam-community/stripity-stripe on GitHub](https://github.com/beam-community/stripity-stripe)
- [Issues](https://github.com/beam-community/stripity-stripe/issues)
- [License: MIT](https://github.com/beam-community/stripity-stripe/blob/main/LICENSE)
- [README](https://github.com/beam-community/stripity-stripe/blob/main/README.md)
- [Releases](https://github.com/beam-community/stripity-stripe/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/beam-community-stripity-stripe
