stripity_stripe

repository·main·Indexed 22 days ago

https://github.com/beam-community/stripity-stripe

An Elixir library for interacting with the Stripe API, supporting token-based and intent-based payment flows. It provides utilities for the Intents API, Stripe Connect onboarding via OAuth, and low-level API request handling with support for object expansion, idempotency keys, and automatic request retries with exponential backoff.

Tokens
8.9K
Snippets
43
Records
46
Agent score
77%

What's inside stripity_stripe

  1. Use Object Expansion in API calls

    main

    Stripe allows you to expand related objects in a single request. In stripity_stripe, you can do this by passing a list of strings to the :expand key in the opts argument of an API call.

    # Returns a charge with only the balance_transaction ID
    Charge.retrieve("ch_123")
    
    # Returns the full BalanceTransaction object nested within the Charge
    Charge.retrieve("ch_123", expand: ["balance_transaction"])
  2. Implement the Stripe Connect onboarding workflow

    main

    Stripe Connect allows users to onboard their own Stripe accounts to your platform. The workflow follows these steps:

    1. Generate Onboarding URL: Use Stripe.Connect.generate_button_url/1 to create a link that sends users to Stripe to authorize your platform.
    2. User Redirection: After authorization, Stripe redirects the user back to your redirect_uri with a code parameter in the URL.
    3. Exchange Code for Tokens: Use the code from the redirect to exchange it for an access token using Stripe.Connect.oauth_token_callback/1.
    4. Act on Behalf of User: Use the returned access_token in other Stripe API modules to perform actions on the user's behalf.
    # 1. Generate the URL
    url = Stripe.Connect.generate_button_url csrf_token
    
    # 2. After redirect, handle the code (e.g., from params['code'])
    {:ok, resp} = Stripe.Connect.oauth_token_callback code
    access_token = resp[:access_token]
  3. Set up stripe-mock for testing

    main

    To run tests against a mock server, use stripe-mock. You can run it via Docker:

    docker run --rm -it -p 12111-12112:12111-12112 stripe/stripe-mock:latest

    To point stripity_stripe to your local mock server, configure the api_base_url in your test environment:

    config :stripity_stripe,
      api_key: "sk_test_thisisaboguskey",
      api_base_url: "http://localhost:12111"

    If you are running mix test and want to prevent the library from attempting to start its own stripe-mock instance, set the SKIP_STRIPE_MOCK_RUN environment variable to any value.

    # Run tests skipping the automatic stripe-mock start
    SKIP_STRIPE_MOCK_RUN=1 mix test
  4. Implement the Intents API workflow

    main

    For new development, use the Intents API. The typical workflow involves:

    1. Creating a SetupIntent on the server.
    2. Passing the setup_intent.id to the frontend to use with Stripe Elements (confirmCardSetup).
    3. Using the resulting payment_method_id to attach to a Customer.
    4. Creating a PaymentIntent to charge the customer (using off_session: true for recurring/future payments).
    # 1. Create SetupIntent
    {:ok, setup_intent} = Stripe.SetupIntent.create(%{})
    setup_intent_id = setup_intent.id
    
    # 2. (Frontend) stripe.confirmCardSetup(setup_intent_id, ...)
    # Assume payment_method_id is returned from frontend
    
    # 3. Retrieve or create a customer and attach the method
    {:ok, stripe_customer} = Stripe.Customer.retrieve(stripe_customer_id)
    {:ok, _result} = Stripe.PaymentMethod.attach(%{customer: stripe_customer.id, payment_method: payment_method_id})
    
    # 4. Charge the customer
    {:ok, charge} = Stripe.PaymentIntent.create(%{
      amount: 1000,
      currency: "USD",
      customer: stripe_customer.id,
      payment_method: payment_method_id,
      off_session: true,
      confirm: true
    })
  5. Configure Stripe Connect platform_client_id

    main

    To use Stripe Connect, you must register your platform on Stripe to obtain a client_id. This ID can be configured in your Elixir application via the config/config.exs file or an environment variable.

    Config key: platform_client_id
    Environment variable: STRIPE_PLATFORM_CLIENT_ID

    config :stripity_stripe, platform_client_id: "ac_???"
  6. Install stripity_stripe

    main

    Add stripity_stripe to your project's dependencies in mix.exs. You can install it by version or by a specific git commit reference.

    If you are using Elixir >= 1.4, you do not need to manually add :stripity_stripe to your application's applications list.

    # By version
    {:stripity_stripe, "~> 2.0"}
    
    # By commit reference
    {:stripity_stripe, git: "https://github.com/beam-community/stripity_stripe", ref: "017d7ecdb5aeadccc03986c02396791079178ba2"}
  7. Implement the Stripe.WebhookHandler behavior

    main

    To handle incoming events, create a module that implements the Stripe.WebhookHandler behavior. You must define the handle_event/1 function.

    Return Values

    Your handle_event/1 function must return one of the following:

    • {:ok, term} or :ok: Marks the event as successfully processed. The Plug will return an HTTP 200.
    • {:error, reason} or :error: Signals a processing error. The Plug will return an HTTP 400 with the reason (if a string/atom).

    Note: It is a best practice to implement a catch-all clause that returns :ok to ensure unhandled event types do not trigger error responses.

    # lib/myapp_web/stripe_handler.ex
    
    defmodule MyAppWeb.StripeHandler do
      @behaviour Stripe.WebhookHandler
    
      @impl true
      def handle_event(%Stripe.Event{type: "charge.succeeded"} = event) do
        # Handle the specific event
        :ok
      end
    
      @impl true
      def handle_event(%Stripe.Event{type: "invoice.payment_failed"} = event) do
        # Handle the specific event
        :ok
      end
    
      # Return HTTP 200 for unhandled events
      @impl true
      def handle_event(_event), do: :ok
    end
  8. How request retries and backoff work

    main

    The library includes built-in retry logic for intermittent failures.

    When it retries: It automatically retries on the following conditions:

    • HTTP 409 (Conflict)
    • HTTP 429 (Too Many Requests)
    • :econnrefused (Connection refused)
    • :connect_timeout (Connection timeout)
    • :timeout (Read timeout)

    Retry Configuration: Retries are controlled via the retries configuration key in config.exs. The should_retry?/3 function checks if the maximum number of attempts (defaulting to 3) has been reached.

    Backoff Logic: When a retry occurs, the library uses an exponential backoff with jitter. The delay is calculated as (base_backoff * 2^attempts), capped at max_backoff, and then randomized within the range of [n/2, n] to prevent thundering herd problems.

  9. Construct a Stripe API request with Stripe.Request

    main

    The Stripe.Request module allows you to manually compose Stripe API requests. This is useful for working around missing endpoints or building custom requests. Requests are composed functionally and do not execute until make_request/1 is called.

    At a minimum, a request must have an endpoint and a method specified.

    Stripe.Request.new_request(opts)
      |> Stripe.Request.put_endpoint("charges")
      |> Stripe.Request.put_method(:post)
      |> Stripe.Request.put_params(%{amount: 1000, currency: "usd"})
      |> Stripe.Request.make_request()
  10. Configure JSON library and HTTP timeouts

    main

    By default, the library uses Jason for JSON processing. If you prefer Poison, you can configure it via json_library.

    For HTTP client timeouts, pass options for the default client, Hackney, using the hackney_opts key.

    # Use Poison instead of Jason
    config :stripity_stripe, json_library: Poison
    
    # Set Hackney timeouts
    config :stripity_stripe, hackney_opts: [{:connect_timeout, 1000}, {:recv_timeout, 5000}]
  11. Configure request retries

    main

    You can configure automatic retries for API requests by setting the :retries option. This allows you to specify the maximum number of attempts and a backoff range (in milliseconds) between attempts.

    config :stripity_stripe, :retries, [max_attempts: 3, base_backoff: 500, max_backoff: 2_000]
  12. Configure stripity_stripe API key

    main

    To make API calls, you must configure your Stripe secret key using the api_key option in your configuration file. You can provide the key as a string, retrieve it from an environment variable, or use a function/tuple to resolve it dynamically.

    import Config
    
    # Using an environment variable
    config :stripity_stripe, api_key: System.get_env("STRIPE_SECRET")
    
    # Using a hardcoded string
    config :stripity_stripe, api_key: "YOUR SECRET KEY"
    
    # Using a tuple to resolve via a module
    config :stripity_stripe, api_key: {MyApp.Secrets, :stripe_secret, []}
    
    # Using a function to resolve
    config :stripity_stripe, api_key: fn -> System.get_env("STRIPE_SECRET") end