Git Product home page Git Product logo

stripity-stripe's Introduction

Stripe for Elixir

An Elixir library for working with Stripe.

Hex.pm Hex.pm

2.x.x status

Hex Docs Inline docs Coverage Status

Which version should I use?

Below is a list of which Stripe API version recent releases of Stripe Elixir. It only indicates the API version being called, not necessarily its compatibility. See the Stripe API Upgrades page for more details.

Starting with stripity_stripe version 2.5.0, you can specify the Stripe API Version to use for a specific request by including the :api_version option. Note that while this will use a specific Stripe API Version to make the request, the library will still expect a response matching its corresponding default Stripe API Version. See the Shared Options documentation for more details.

:stripity_stripe Stripe API Version
2.0.x 2018-02-28
2.1.0 - 2.2.0 2018-05-21
2.2.2 2018-08-23
2.2.3 - 2.3.0 2018-11-08
2.4.0 - 2.7.0 2019-05-16
master 2019-10-17

Documentation

Installation

Install the dependency by version:

{:stripity_stripe, "~> 2.0"}

Or by commit reference:

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

Next, add to your applications:

Not necessary if using elixir >= 1.4

defp application do
  [applications: [:stripity_stripe]]
end

Configuration

To make API calls, it is necessary to configure your Stripe secret key.

import Config

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

It's possible to use a function or a tuple to resolve the secret:

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

Moreover, if you are using Poison instead of Jason, you can configure the library to use Poison like so:

config :stripity_stripe, json_library: Poison

Timeout

To set timeouts, pass opts for the http client. The default one is Hackney.

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

Request Retries

To set retries, you can pass the number of attempts and range of backoff (time between attempting the request again) in milliseconds.

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

Examples

Stripe supports a token based, and intent based approach for processing payments. The token based approach is simpler, but it is not supported in Europe. The intents API is the way forward, and should be used for new development.

Intents

Create a new SetupIntent object in Stripe. The created intent ID will be passed to the frontend to use with Stripe elements so the end user can enter their payment details. SetupIntents are ephemeral. It is best to create a new one each time the user reaches your payment page.

{: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}

On the frontend, use the setup intent ID you created in conjunction with Stripe elements confirmCardSetup method.

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
})

With the new payment method ID, you can associate the payment method with a Stripe customer.

Get an existing customer.

{:ok, stripe_customer} = Stripe.Customer.retrieve(stripe_customer_id)

Or create a new one.

new_customer = %{
  email: email,
}

{:ok, stripe_customer} = Stripe.Customer.create(new_customer)

Attach the payment method to the customer.

{:ok, _result} = Stripe.PaymentMethod.attach(%{customer: stripe_customer.id, payment_method: payment_method_id})

Now you can charge the customer using a PaymentIntent from Stripe. Since we used a setup intent initially, the payment intent will be authorized to make payments off session, for example to charge for a recurring subscription.

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

Note: Object Expansion

Some Stripe API endpoints support returning related objects via the object expansion query parameter. To take advantage of this feature, stripity_stripe accepts a list of strings to be passed into opts under the :expand key indicating which objects should be expanded.

For example, calling Charge.retrieve("ch_123") would return a charge without expanding any objects.

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

However if we now include an expansion on the balance_transaction field using

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

We will get the full object back as well.

%Charge{
  id: "ch_123",
  balance_transaction: %BalanceTransaction{
    id: "txn_123",
    fee: 125,
    ...
  },
  ...
}

For details on which objects can be expanded check out the stripe object expansion docs.

Testing

Starting stripe-mock

To run the tests you'll need to install stripe-mock. It is a mock HTTP server that responds like the real Stripe API. It's powered by the Stripe OpenAPI specification, which is generated from within Stripe's API.

The stripe-mock instructions have more details, but if you have docker installed already you can quickly and easily start an instance to test against:

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

Running the tests

By default, mix test will start stripe-mock by finding and invoking the stripe-mock executable. If you would prefer to start it yourself, do so and add an env var SKIP_STRIPE_MOCK_RUN to skip starting stripe-mock. Any value will do. Example:

SKIP_STRIPE_MOCK_RUN=1 mix test

To configure your test environment to use the local stripe-mock server, you may need to set the api_base_url field in your config:

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

Documentation for 1.x.x

Click to expand

Stripe API

Works with API version 2015-10-16

Installation

Install the dependency:

{:stripity_stripe, "~> 1.6"}

Next, add to your applications:

defp application do
  [applications: [:stripity_stripe]]
end

Configuration

To make API calls, it is necessary to configure your Stripe secret key (and optional platform client id if you are using Stripe Connect):

import Config

config :stripity_stripe, secret_key: "YOUR SECRET KEY"
config :stripity_stripe, platform_client_id: "YOUR CONNECT PLATFORM CLIENT ID"

Testing

If you start contributing and you want to run mix test, first you need to export STRIPE_SECRET_KEY environment variable in the same shell as the one you will be running mix test in. All tests have the @tag disabled: false and the test runner is configured to ignore disabled: true. This helps to turn tests on/off when working in them. Most of the tests depends on the order of execution (test random seed = 0) to minimize runtime. I've tried having each tests isolated but this made it take ~10 times longer.

export STRIPE_SECRET_KEY="yourkey"
mix test

The API

I've tried to make the API somewhat comprehensive and intuitive. If you'd like to see things in detail be sure to have a look at the tests - they show (generally) the way the API goes together.

In general, if Stripe requires some information for a given API call, you'll find that as part of the arity of the given function. For instance if you want to delete a Customer, you'll find that you must pass the id along:

{:ok, result} = Stripe.Customers.delete "some_id"

For optional arguments, you can send in a Keyword list that will get translated to parameters. So if you want to update a Subscription, for instance, you must send in the customer_id and subscription_id with the list of changes:

# Change customer to the Premium subscription
{:ok, result} = Stripe.Customers.change_subscription "customer_id", "sub_id", [plan: "premium"]

Metadata (metadata:) key is supported on most object type and allow the storage of extra information on the stripe platform. See test for an example.

That's the rule of thumb with this library. If there are any errors with your call, they will bubble up to you in the {:error, message} match.

# Example of paging through events
{:ok, events} = Stripe.Events.list(key, "", 100) # second arg is a marker for paging

case events[:has_more] do
  true ->
    # retrieve marker
    last = List.last( events[:data] )
    case Stripe.Events.list key, last["id"], 100 do
      {:ok, events} -> events[:data]
      # ...
    end
  false -> events[:data]
end

Connect

Stripe Connect allows you to provide your customers with an easy onboarding to their own Stripe account. This is useful when you run an e-commerce as a service platform. Each merchant can transact using their own account using your platform. Then your platform uses Stripe's API with their own API key obtained in the onboarding process.

First, you need to register your platform on Stripe Connect to obtain a client_id. In your account settings, there's a "Connect" tab, select it. Then fill the information to activate your connect platform settings. The select he client_id (notice there's one for dev and one for prod), stash this client_id in the config file under

config :stripity_stripe, platform_client_id: "ac_???"

or in an env var named STRIPE_PLATFORM_CLIENT_ID.

Then you send your users to sign up for the stripe account using a link.

Here's an example of a button to start the workflow: Connect with Stripe

You can generate this URL using:

url = Stripe.Connect.generate_button_url csrf_token

When the user gets back to your platform, the following url (redirect_uri form item on your "Connect" settings) will be used:

//yoursvr/your_endpoint?scope=read_write&code=AUTHORIZATION_CODE

or

//yoursvr/your_endpoint?error=access_denied&error_description=The%20user%20denied%20your%20request

Using the code request parameter, you make the following call:

{:ok, resp} -> Stripe.Connect.oauth_token_callback code
resp[:access_token]

resp will look like this:

%{
  token_type: "bearer",
  stripe_publishable_key: PUBLISHABLE_KEY,
  scope: "read_write",
  livemode: false,
  stripe_user_id: USER_ID,
  refresh_token: REFRESH_TOKEN,
  access_token: ACCESS_TOKEN
}

You can then pass the access_token to the other API modules to act on their behalf.

See a demo using the Phoenix framework with the bare minimum to get this working.

Testing Connect

The tests are currently manual as they require a unique OAuth authorization code per test. You need to obtain this code manually using the stripe connect workflow (that your user would go through using the above url).

First, log in your account. Then go to the following url: https://dashboard.stripe.com/account/applications/settings

Create a connect standalone account. Grab your development client_id. Put it in your config file. Enter a redirect url to your endpoint. Capture the "code" request parameter. Pass it to Stripe.Connect.oauth_token_callback or Stripe.Connect.get_token.

Contributing

Feedback, feature requests, and fixes are welcomed and encouraged. Please make appropriate use of Issues and Pull Requests. All code should have accompanying tests.

License

Please see LICENSE.md for licensing details.

History

Statement from original author

Why another Stripe Library? Currently there are a number of them in the Elixir world that are, well just not "done" yet. I started to fork/help but soon it became clear to me that what I wanted was

  • an existing/better test story
  • an API that didn't just mimic a REST interaction
  • a library that was up to date with Elixir > 1.0 and would, you know, actually compile.
  • function calls that returned a standard {:ok, result} or {:error, message} response

As I began digging things up with these other libraries it became rather apparent that I was not only tweaking the API, but also ripping out a lot of the existing code... and that usually means I should probably do my own thing. So I did.

stripity-stripe's People

Contributors

ascandella avatar asummers avatar begedin avatar davidantaramian avatar deepwalker avatar dkulchenko avatar dmvt avatar dnsbty avatar esse avatar freedomben avatar gmile avatar he9lin avatar jayjun avatar joshsmith avatar juljimm avatar kerryjj avatar maartenvanvliet avatar mcrumm avatar nicrioux avatar pragmaticivan avatar robconery avatar sb8244 avatar schrockwell avatar snewcomer avatar sylver avatar tlvenn avatar tylerwray avatar web-flow avatar williamhogman avatar yordis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stripity-stripe's Issues

Proposal: remove setup/teardown blocks from the tests

TL;DR. Let's stop making API calls in setup_all blocks. Running tests against real API will become much slower, but contribution and running tests locally / on travis will remain as fast as they are right now.

Right now adding new API tests is somewhat overly complicated. Below I will show what it took to add a test for Stripe.Subscription.change/4 (see #70):

  1. write the test,

  2. write the code that fixes it,

  3. run the test against actual API, say:

    STRIPE_SECRET_KEY=qr0yuBikLRa5yJkMGuEO4DNblKhEJqkw mix test test/stripe/subscription_test.exs:134
  4. the test will fail. Here's why:

    1. before test run, a setup_all block is executed,
    2. ExVCR intercepts API calls done in the setup block and substitutes them with canned responses from fixtures/subscription_test/setup.json.
    3. these responses were recorded a while ago, and entities (such as customer) in the responses are no longer active,
    4. the test, however, doesn't know that. it was a now-inactive customer and now-inactive subscription, and tries to perform an operation involving both.
    5. the test fails, obviously (as Stripe returns a response, saying that the request contained not existing / inactive entities).
  5. remove responses for the test in question (I just recorded it, but it contains invalid response), as well as setup/teardown;

    rm fixture/vcr_cassettes/subscription_test/{setup,teardown,general_change}.json
  6. run the test again (same as step 3),

  7. see the test succeed,

  8. cleanup now-changed setup/teardown cassettes:

    git checkout fixture/vcr_cassettes/subscription_test/{setup,teardown}.json
  9. re-run the test to see it fail again:

    1) test General change works (Stripe.SubscriptionTest)
       test/stripe/subscription_test.exs:134
       ** (ExVCR.RequestNotMatchError) Request did not match with any one in the current cassette: fixture/vcr_cassettes/subscription_test/general_change.json.
       Delete the current cassette with [mix vcr.delete] and re-record.
    
       stacktrace:
         lib/exvcr/handler.ex:142: ExVCR.Handler.raise_error_if_cassette_already_exists/1
         lib/exvcr/handler.ex:126: ExVCR.Handler.get_response_from_server/2
         (hackney) :hackney.request(:post, "https://api.stripe.com/v1/customers/cus_8y7pX6pDygSSFA/subscriptions/sub_8y7po6D0QD5y9V", [{"Authorization", "Bearer qr0yuBikLRa5yJkMGuEO4DNblKhEJqkw"}, {"Content-Type", "application/x-www-form-urlencoded"}, {"User-Agent", "Stripe/v1 stripity-stripe/1.4.0"}], "plan[quantity]=3", [])
         (httpoison) lib/httpoison/base.ex:396: HTTPoison.Base.request/9
         (stripity_stripe) lib/stripe.ex:95: Stripe.make_request_with_key/6
         (stripity_stripe) lib/stripe/subscriptions.ex:113: Stripe.Subscriptions.change/4
         test/stripe/subscription_test.exs:136: (test)
    
  10. based on the failure, adjust the cassette recorded for the test so it now relies on old entities from setup.json, e.g. make these changes:

    - "url": "https://api.stripe.com/v1/customers/cus_8y7pX6pDygSSFA/subscriptions/sub_8y7po6D0QD5y9V"
    + "url": "https://api.stripe.com/v1/customers/cus_8r2iLZ3me9G4hU/subscriptions/sub_8r2ikymYYXhIlq"

    and

    - "Authorization": "Bearer qr0yuBikLRa5yJkMGuEO4DNblKhEJqkw",
    + "Authorization": "Bearer non_empty_secret_key_string",

I think this can and should be changed. I'm proposing we get rid of making API calls in setup_all blocks throughout the suite. If we do this, and assume that each test lives on its own, contributing new tests and fixing existing tests, will become significantly easier.

Instead of following the procedure above, ideally I should be doing it this:

  1. write the test,
  2. write the code that fixes it,
  3. run the test (this will record new cassettes with responses),
  4. commit the new code, test & cassette.

To sum up:

  • Advantages:
    • writing a new code & tests becomes really faster,
    • fixing existing code & re-recording cassettes (if needed) will be easier,
    • test order will no longer matter โ€“ each test will truly live on its own,
    • tests will become explicit (I feel this is relatively weak point, but worth mentioning).
  • Drawbacks:
    • running the suite against live API will be much slower (given this will be ran relatively infrequent, it should be ok)
    • there will be more bootstrapping code in tests.

Support Committments

Primarily for @joshsmith: what are your support commitments for non-current versions of Elixir? The latest Elixir release is 1.3, and I would say that 1.2 is the farthest back things should be supported, as 1.2 released a lot of improvements for the ecosystem.

Assign multiple cards (tokens) to an existing customer

Hi There,

Please forgive me for posting a question here. Is there a way to assign multiple credit cards to an existing customer?

URL:
/v1/customers//sources

Parsed Request POST Body
{
card: "tok_17sFEyDzqcXKnkcafX4jkf2s"
}

Response body
{
id: card_17sFEyDzqcXKnkcaO95Kg2iY
object: "card"
address_city: null
address_country: null
address_line1: null
address_line1_check: null
address_line2: null
address_state: null
address_zip: null
address_zip_check: null
brand: "MasterCard"
country: "US"
customer: cus_88S0F9EAp18omi
cvc_check: "pass"
dynamic_last4: null
exp_month: 3
exp_year: 2017
fingerprint: "Pj4eCYNJpZuIHY9i"
funding: "credit"
last4: "4444"
metadata:
name: null
tokenization_method: null
}

README should explain how to add to supervision tree

For the life of me I can't figure out what I have to do to get this working in my app. Explicitly calling Stripe.start works great, but in my app's supervisor, what am I supposed to add as the worker? Can you address this in the readme please?

Test suite hangs

โฏ STRIPE_SECRET_KEY=mykey mix test --trace
Excluding tags: [disabled: true]

warning: this check/guard will always yield the same result
  test/stripe/connect_test.exs:50

warning: this check/guard will always yield the same result
  test/stripe/stripe_test.exs:40


Stripe.SubscriptionTest

It just hangs there. I waited over 4 minutes before aborting.

(Elixir 1.3.1, OS X 10.11.6)

General Code Cleanup

Broad issue name, but the codebase is old and it shows. I'd like to clean things up, move to v2 and see if we can streamline some things.

Proposal: make key usage more consistent

Right now there are basically two occurrences of the same method:

  1. one with API key,
  2. one without API key (relying on API key from configuration).

This means that we have to have:

  1. two actual functions in the source code doing the same thing, with one simply calling another,
  2. two documentation blocks describing essentially the same thing (with exception to the "with explicit key" function, telling it's possible to use the key),
  3. two test testing the same things (implying two cassettes, unless both methods are specified to use the same cassette as in #66).

I think this can be significantly improved & cleaned up:

  1. Leave only a single instance of each function implementing a single API call,
  2. Leave only a single block with docs,
  3. Refactor the usage of keys: push the decision of whether or not to use the key โ€“ down to make_request_with_key/1.

Example

Current implementation of Stripe.Customer.get is done with two functions:

@doc """
Retrieves a given Customer with the specified ID. Returns 404 if not found.
## Example

{:ok, cust} = Stripe.Customers.get "customer_id"

"""
def get(id) do
  get id, Stripe.config_or_env_key
end

@doc """
Retrieves a given Customer with the specified ID. Returns 404 if not found.
Using a given stripe key to apply against the account associated.
## Example

{:ok, cust} = Stripe.Customers.get "customer_id", key

"""
def get(id, key) do
  Stripe.make_request_with_key(:get, "#{@endpoint}/#{id}", key)
  |> Stripe.Util.handle_stripe_response
end

This can be reduced to:

@doc """
Retrieves a given Customer with the specified ID. Returns 404 if not found. Custom can be used as a second optional argument.

## Examples

{:ok, customer} = Stripe.Customers.get("customer_id")
{:ok, customer} = Stripe.Customers.get("customer_id", custom_key)

"""
def get(id, key \\ nil) do
  Stripe.make_request_with_key(:get, "#{@endpoint}/#{id}", key)
  |> Stripe.Util.handle_stripe_response
end

With the above, Stripe.make_request_with_key function would check if the key passed is a nil, and if so โ€“ rely on configuration key.

Replace HTTPoison with Hackney

HTTPoison doesn't offer any benefits over using Hackney directly, and it just adds another dependency to pin against; often times the use of HTTPoison will obfuscate the lower level support provided by Hackney. I would recommend swapping out HTTPoison for Hackney, which is a very simple process. I'm willing to do it if we're in agreement.

Replace dicts with maps

We should generally update the codebase wherever possible to replace dicts with maps where it makes sense:

  • Identify lines that could be replaced
  • Propose a good replacement
  • Make the replacements

RFC - Stripity Stripe 2.0

Start Date: 2016-10-19

Summary

Stripity Stripe is in need of an upgrade to provide a more flexible Elixir wrapper for the amazing Stripe API. There are two main goals of this rewrite. First, Stripity Stripe 2.0 should have a consistent and easy to understand high level API. Given that Elixir was born of Ruby, we will seek to closely match the documented Ruby Interface. Second, there should be full test coverage which does not rely on recorded responses of a specific version of the API. We want to be testing our functionality, not that of the Stripe API itself.

Motivation

The initial desire for 2.0 came out of a conversation between Dan Matthews of Strumber and Josh Smith of Code Corps. Both teams were/are implementing on top of Stripe's Connect functionality and found the existing version's endpoint modules to be generally lacking best practice support for the Stripe-Account header. Additionally, we discovered unnecessary dependencies, unsupported endpoints, inconsistent variable names and a good deal of repetition.

Rob Conery was gracious enough to hand us the reins several days ago.

Detailed design

HTTPoison will be replaced with Hackney at the suggestion of @DavidAntaramian in Issue #100. Along this thought line, dependencies should be generally avoided unless absolutely necessary. Whenever possible, the lowest level dependency should be used.

Stripe provides a common endpoint structure for each primary endpoint. We will attempt to handle the majority of them through the use of common modules supplemented by heavy documentation. An example of this is as follows:

defmodule Stripe.Customers do
  @moduledoc """
  High Level API for working with Stripe Customers.

  Through this API you can:
  -Create a customer [ref](https://stripe.com/docs/api#create_customer)
  -Retrieve a customer [ref](https://stripe.com/docs/api#retrieve_customer)
  -Update a customer [ref](https://stripe.com/docs/api#update_customer)
  -Delete a customer [ref](https://stripe.com/docs/api#delete_customer)
  -List all customers [ref](https://stripe.com/docs/api#list_customers)
  """

  @endpoint "/customers"

  @doc """
  Creates a Customer. Valid attributes can be found in the
  [Stripe API Customer Docs](https://stripe.com/docs/api#create_customer).
  At the time of this writing, all were optional.

  [ref](https://stripe.com/docs/api#create_customer)

  ## Example
  `` `
    attributes = %{
      email: "[email protected]",
      description: "Customer for [email protected]",
      source: "tok_18pPRnBMvBFPIRGmeLiOEwlm"
    }

    {:ok, customer} = Stripe.Customers.create(attributes)
  `` `
  """

  use Stripe.API.Create

  @doc """
  Retrieves a Customer by ID.

  [ref](https://stripe.com/docs/api/ruby#retrieve_customer)

  ## Example
  `` `
  {:ok, customer} = Stripe.Customers.retrieve("cus_9McRYFr9fAGdKm")
  # or
  {:ok, customer} = Stripe.Customers.retrieve(%{id: "cus_9McRYFr9fAGdKm"})
  `` `
  """

  use Stripe.API.Retrieve

  # ...
end  

We will also only provide functions documented in the official Stripe API libraries. No additional functions are allowed directly on the High Level API modules.

Some endpoints have dependency ids in their URLs. These values will be extracted from the attributes map.

defmodule Stripe.Card do
  @endpoint "/customers/{customer_id}/sources"

  # ...
end
attributes = %{
  id: "card_196RVbBMvBFPIRGmREqyLEUh",
  customer_id: "cus_9McRYFr9fAGdKm"
}

{:ok, customer} = Stripe.Card.retrieve(attributes)

All public functions on High Level API modules have an arity of 4, the last 3 of which are optional.

def create(
  attributes,
  headers \\ %{},
  hackney_options \\ %{},
  secret_key \\ ""
) do
  # ...
end

Drawbacks

There is next to no backwards compatibility with Stripity Stripe Version 1. We're singularizing nearly every module name (Stripe.Customers becomes Stripe.Customer), leaving out (moving?) courtesy functions such as all and changing the arity of almost every function that is kept.

We're also intentionally treating fewer dependancies and DRYness as higher priority concerns than code readability when we choose to use Erlang modules such as Hackney directly, abandoning the syntactic sugar and familiarity of their Elixir wrappers. This choice may deter wider contributions in those areas of the code.

Unresolved questions

Should we provide some or all of the non-standard functions from version 1 in a Stripe.Utils module? The main one I personally find useful is the all function which iterates over the list endpoint.

List charges with params not working

When I try to list charges with anything other than a integer limit it throws and error thats not very useful in tracking down whats happening.

Stripe.Charges.list(customer: "customer_number")
** (ArgumentError) cannot convert the given list to a string.

To be converted to a string, a list must contain only:

  * strings
  * integers representing Unicode codepoints
  * or a list containing one of these three elements

Please check the given list or call inspect/1 to get the list representation, got:

[customer: "customer_number"]

             (elixir) lib/list.ex:624: List.to_string/1
    (stripity_stripe) lib/stripe/charges.ex:87: Stripe.Charges.list/2

any idea whats going on?

Stripe.URI Efficiency

The Stripe.URI module code should be reviewed. It's a small issue, but right now it uses ++ with the left operand as the accumulator and the right operand as the added value. ++ is implemented such that the left operand is always copied, so our accumulator is always copied (which is what we don't want). See The Eight Myths of Erlang Performance

Can not run mix.deps.get

Can not rune mix.deps.get in my project.
This is my file Mix.config

defmodule Delivr.Mixfile do
  use Mix.Project

  def project do
    [app: :delivr,
     version: "0.0.1",
     elixir: "~> 1.2",
     elixirc_paths: elixirc_paths(Mix.env),
     compilers: [:phoenix, :gettext] ++ Mix.compilers,
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     aliases: aliases(),
     deps: deps()]
  end

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [mod: {Delivr, []},
     applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :postgrex, :sendgrid, :calendar, :stripity_stripe]]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "web", "test/support"]
  defp elixirc_paths(_),     do: ["lib", "web"]

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [{:phoenix, "~> 1.2.0"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.0"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:cowboy, "~> 1.0"},
     {:exrm, "~> 1.0.6"},
     {:timex, "~> 2.1.0"},
     {:timex_ecto, "~> 1.1.0"},
     {:number, "~> 0.4.2"},
     {:httpoison, "~> 0.9.0"},
     {:floki, "~> 0.9.0"},
     {:sendgrid, "~> 1.0.2"},
     {:hashids, "~> 2.0"},
     {:json, "~> 0.3.0"},
     {:calendar, "~> 0.14.2"},
     {:stripity_stripe, "~> 1.4.0"}]
  end

  # Aliases are shortcuts or tasks specific to the current project.
  # For example, to create, migrate and run the seeds file at once:
  #
  #     $ mix ecto.setup
  #
  # See the documentation for `Mix` for more info on aliases.
  defp aliases do
    ["ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
     "ecto.reset": ["ecto.drop", "ecto.setup"],
     "test": ["ecto.create --quiet", "ecto.migrate", "test"]]
  end
end

This is my error

Failed to use "hackney" (version 1.6.1) because
  httpoison (version 0.9.0) requires ~> 1.6.0
  stripity_stripe (version 1.4.0) requires ~> 1.4.8
  tzdata (version 0.5.8) requires ~> 1.0
  Locked to 1.6.1 in your mix.lock

** (Mix) Hex dependency resolution failed, relax the version requirements of your dependencies or unlock them (by using mix deps.update or mix deps.unlock)

Proposal: Expose headers to class functions for Connect usage

When acting on behalf of a Connected account, Stripe recommends using the Stripe-Account header as seen here: https://stripe.com/docs/connect/payments-fees#charging-directly

I propose updating the class functions to expose headers in the following way:

This:

  def create(params, key) do
    Stripe.make_request_with_key(:post, @endpoint, key, params)
    |> Stripe.Util.handle_stripe_response
  end

would become this:

  def create(params, key_or_headers) when is_bitstring(key_or_headers) do
    Stripe.make_request_with_key(:post, @endpoint, key_or_headers, params)
    |> Stripe.Util.handle_stripe_response
  end

  def create(params, key_or_headers) when is_map(key_or_headers) do
    Stripe.make_request_with_key(
      :post, @endpoint, Stripe.config_or_env_key, params, key_or_headers
    )
    |> Stripe.Util.handle_stripe_response
  end

This still allows a key override but also supports passing any headers required without using the low level API on the Stripe class. With your approval, I'll submit a PR in the next week or so.

Dependency resolution failing for 1.4.0

I added `{:stripity_stripe, "~> 1.4.0"} to my mix.exs, but dependency resolution is failing:

mix deps.get
Running dependency resolution

Failed to use "hackney" because
  stripity_stripe (version 1.4.0) requires ~> 1.4.8
  mix.lock specifies 1.6.2

Not sure why this it says it requires ~> 1.4.8, since the stripity-stripe mix.lock file says:

"hackney": {:hex, :hackney, "1.6.1", ...}

Improve descriptiveness of tests

  • Identify tests that are lacking much explanation.
  • Open issues for individual tests
  • Rewrite those tests to improve their descriptiveness

Ensure all URLs are properly escaped

We should do a runthrough of this package to:

  • Identify any place where URLs lack proper escaping
  • Apply proper escaping to those URLs

If we don't find any, then we can just close this issue.

Dependency Resolution Failed

Failed to use "hackney" because
  stripity_stripe (version 1.4.0) requires ~> 1.4.8
  Locked to 1.6.1 in your mix.lock

Is the solution to use the override: true option in my mix.exs?

What is being supervised?

I'm trying to advise someone on how to use this library, but I don't understand why you're starting a supervision tree. When I actually open the supervisor module's definition, nothing is being supervised. And from what I can tell, no children are ever dynamically added to the supervision tree.

You're also informing consumers that they need to insert Stripe into their own supervision tree, but that's incorrect as you have already started a supervision tree for the application which exists external the to the consumer's application at run-time.

As far as I can tell, a consumer should only have to add :stripity_stripe to their applications list, solely to have the application controller load :httpoison as an application. This also means there's no need to call HTTPoison.start/1 inside your code since this will be handled by ERTS/BEAM.

I'd appreciate pointers on why it's necessary to start a supervisor and what actually is being supervised. Thanks!

Bitcoin receiver

Anyone wanting to tackle this? I'm on the canadian side and don't have a US account yet. The feature is still limited to the US accounts. I want to be ready to use that feature as soon as I can + it would be a nice addition feature-wise.

Thoughts?

List invoices of specific customer

I am looking for a way to list invoices of a given customer. (https://stripe.com/docs/api/ruby#list_invoices)

Looking at the documentation, my first guess was that Stripe.Invoices.list might be usable for this purpose, but as far as I can see this function does not take customer id as a (optional) parameter.

Stripe.Invoices.get specifies the following (as an example):
{:ok, cust} = Stripe.Invoices.get "customer_id", key
Which looks promising, but it seems to be a copy-paste error from some other part of the documentation as Stripe.Invoice.get actually takes an invoice-id and not a customer-id as parameter.

Looking in the source code for the Stripe.Invoices module (https://github.com/code-corps/stripity-stripe/blob/master/lib/stripe/invoices.ex) I can see that there is an commented-out function called "get_invoices". This seemed to be what I was looking for at first glance, but as mentioned this function has been commented out and just copy-pasting the function (to make my own implementation) won't work:


#def get_invoices(id, params, key) do
#  params = Keyword.put_new params, :limit, 10  #params are never used
#  params = Keyword.put_new params, :customer, id #params are never used
#  Stripe.Util.list "invoices", key, starting_after, limit #params not used
#end

Params containing the customer id is never used (Stripe.Util.list does not support a params keyword list).

So how does one get a list of invoices for a specific customer?

Fix URI Borked Tests

The URI Encoding tests use multiple assertions per test, which makes it difficult to understand what's going on. There's also a test failure that needs to be fixed.

Stripe.Connect for managed accounts

Hey,
I see there is a Connect module implemented for standalone accounts and I was wondering if you are planning on adding managed accounts to the connect module?

Thanks,
Alaister

Subscriptions.change() something other than plan id error

With Subscriptions.change() I am unable to change other options (something other than the plan id) of a subscription.

This causes Stripe(.com) to return an error message:

Stripe.Subscriptions.change(user.stripe_customer_id, sub_id, [quantity: 3])
...
%{"error" => %{"message" => "Invalid string: {\"quantity\"=>\"3\"}",
    "param" => "plan", "type" => "invalid_request_error"}}

This works:

Stripe.make_request_with_key(
  :post,
  "customers/#{user.stripe_customer_id}/subscriptions/#{sub_id}",
  Stripe.config_or_env_key,
  [quantity: 3]
) |> Stripe.Util.handle_stripe_response

Multiple test env

We should be able to mock the requests and also if we specify, run all the tests directly into stripe. So that way we're going to be able to use Travis CI.

Update hex package

To use your latest version, github needs to be referenced, like this:

{:stripity_stripe, "~> 1.0.0", github: "robconery/stripity-stripe"},

Last hex package is 0.5.0 (it may also make sense to bump httpoison dependency to 0.8.0 - I had to downgrade my dependency in mix.exs).

Mock Stripe within tests

Right now many of the tests either hit Stripe or have VCR cassettes. The more mocking or dependency injection or any other technique we can use to reduce the external reliance on hitting Stripe, the better.

Values are not escaped properly

Using v1.3.0.

If I submit a Stripe.Charge with an ampersand in the description field, I would expect this library to properly escape values to submit via the API.

This is a pretty serious issue leaving the library open to query-parameter injection / tampering.

Example:

{:ok, uncaptured_payment} = Stripe.Charges.create(amount, [
      capture: false,
      description: "Item1 & Item2",
      receipt_email: appointment.client.email,
      customer: appointment.client.stripe_customer_id,
      source: card_id,
      metadata: %{
        business_id: business.id,
        appointment_id: appointment.id,
        client_name: appointment.client.first_name <> " " <> appointment.client.last_name
      }
    ])
** (MatchError) no match of right hand side value: {:error, %{"error" => %{"message" => "Received unknown parameter:  Item2", "param" => " Item2", "type" => "invalid_request_error"}}}

error connecting

Hello,
I'm brand new to elixir, so pretty sure I'm doing something wrong.

I added config :usestripe, secret_key: "sk_test_XXX"
to config/config.exs

usestripe_test.exs look like:

defmodule UsestripeTest do
  use ExUnit.Case
  doctest Usestripe

  test "Customers are listed" do
    {:ok, customers} = Stripe.Customers.list
    assert length(customers) > 0
  end
end

test_helper.exs looks like:

ExUnit.start()
Stripe.start

mix test results in:

 $ mix test


  1) test Customers are listed (UsestripeTest)
     test/usestripe_test.exs:5
     ** (MatchError) no match of right hand side value: 
{:error, "You did not provide an API key, though you did set your Authorization header to \"Bearer\". 
Using Bearer auth, your Authorization header should look something like 'Authorization: Bearer 
YOUR_SECRET_KEY'. See https://stripe.com/docs/api#authentication for details, or we can help at 
https://support.stripe.com/."}
     stacktrace:
       test/usestripe_test.exs:6



Finished in 0.7 seconds (0.05s on load, 0.7s on tests)
1 test, 1 failure

Randomized with seed 333290

What am I missing? On elixir v1.1.0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.