Skip to content

Commit

Permalink
Connect-ified rest of API. Finished Connect standalone support. Revam…
Browse files Browse the repository at this point in the history
…ped a bit of doc to add links. Merged in latest from rob's repo. Completed move of subscriptions/invoices to their own module.
  • Loading branch information
nicrioux committed Dec 22, 2015
1 parent f6c13c0 commit 9857705
Show file tree
Hide file tree
Showing 17 changed files with 1,030 additions and 262 deletions.
95 changes: 83 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
# Stripe for Elixir

An Elixir library for working with Stripe. With this library you can:
An Elixir library for working [Stripe](https://stripe.com/).

- manage Customers
- Create, list, cancel, update and delete Subscriptions
- Create, list, update and delete Plans
- Create, list, and update Invoices
- Create and retrieve tokens for credit card and bank account
- List and retrieve stripe events (paged, max 100 per page, up to 30 days kept on stripe for retrieve)
- And yes, run charges with or without an existing Customer
- Manage accounts [Stripe.Accounts](https://github.com/robconery/stripity-stripe/blob/master/lib/stripe/accounts.ex)
- Manage customers [Stripe.Customers](https://github.com/robconery/stripity-stripe/blob/master/lib/stripe/customers.ex)
- Manage Subscriptions[Stripe.Subscriptions](https://github.com/robconery/stripity-stripe/blob/master/lib/stripe/subscriptions.ex)
- Manage plans[Stripe.Plans](https://github.com/robconery/stripity-stripe/blob/master/lib/stripe/plans.ex)
- Manage Invoices[Stripe.Invoices](https://github.com/robconery/stripity-stripe/blob/master/lib/stripe/invoices.ex)
- Manage Invoice Items[Stripe.InvoiceItems](https://github.com/robconery/stripity-stripe/blob/master/lib/stripe/invoice_items.ex)
- Manage tokens for credit card and bank account[Stripe.Tokens](https://github.com/robconery/stripity-stripe/blob/master/lib/stripe/tokens.ex)
- List and retrieve stripe events (paged, max 100 per page, up to 30 days kept on stripe for retrieve)[Stripe.Events](https://github.com/robconery/stripity-stripe/blob/master/lib/stripe/events.ex)
- Manage/capture charges with or without an existing Customer[Stripe.Charges](https://github.com/robconery/stripity-stripe/blob/master/lib/stripe/charges.ex)

- Facilitate using the Connect API (for standalone accounts)[Stripe Connect Standalone account](https://stripe.com/docs/connect/standalone-accounts) by allowing you to supply your own key. The oauth callback processor (not endpoint) is supplied by this library as well as a connect button url generator. See below for [Instructions](#connect). [Stripe Connect API reference](https://stripe.com/docs/connect/reference)

- All functions are available with a parameter that allow a stripe api key to be passed in and be used for the underlying request. This api key would be the one obtained by the oauth connect authorize workflow.

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:

Expand All @@ -28,7 +34,7 @@ I've tested this library against Stripe API v1 and above. [The docs are up at He
Install the dependency:

```ex
{:stripity_stripe, "~> 0.5.0"}
{:stripity_stripe, "~> 1.0.0"}
```

Next, add to your applications:
Expand All @@ -45,16 +51,18 @@ Then create a config folder and add a Stripe secret key:
use Mix.Config

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

Then add Stripe to your supervisor tree or, to play around, make sure you start it up:

```ex
Stripe.start
```
HTTPoison is started automatically in [Stripe.ex](https://github.com/robconery/stripity-stripe/blob/master/lib/stripe.ex)

## 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.
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 minize runtime. I've tried having each tests isolated but this made it take ~10 times longer.
```
export STRIPE_SECRET_KEY="yourkey"
mix test
Expand All @@ -74,9 +82,13 @@ For optional arguments, you can send in a Keyword list that will get translated

```ex
# Change customer to the Premium subscription
{:ok, result} = Stripe.Customers.change_subscription "customer_id", "sub_id", plan: "premium"
{: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](https://github.com/robconery/stripity-stripe/blob/master/test/stripe/customer_test.exs) 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 #2nd arg is a marker for paging
Expand All @@ -91,5 +103,64 @@ case events[:has_more] do
false -> events[:data]
end
```
<a name="connect"></a>
# [Connect](https://stripe.com/docs/connect/standalone-accounts)

Stripe Connect allows you to provide your customers with an easy onboarding to
their own Stripe account. This is useful when you run an ecommerce as a service platform. Each merchant can transact using their own account using your platform. Then your platform uses stripity API with their own API key obtained by 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:
<a href="https://connect.stripe.com/oauth/authorize?response_type=code&client_id=ca_32D88BD1qLklliziD7gYQvctJIhWBSQ7&scope=read_write">Connect with Stripe</a>

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.[Demo](https://github.com/nicrioux/stripity-phoenix-connect)

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

First, log in your account. Then go to 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.

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.
35 changes: 16 additions & 19 deletions lib/stripe.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule Stripe do
use HTTPoison.Base

def start(_type, _args) do
start #start HTTPoison.Base.start inherited from use statement
Stripe.Supervisor.start_link
end

Expand Down Expand Up @@ -59,15 +60,7 @@ defmodule Stripe do
|> Dict.merge(headers)
|> Dict.to_list

{:ok, response} = case method do
:get -> get( endpoint, rh, options)
:put -> put( endpoint, rb, rh, options)
:head -> head( endpoint, rh, options)
:post -> post( endpoint, rb, rh, options)
:patch -> patch( endpoint, rb, rh, options)
:delete -> delete( endpoint, rh, options)
:options -> options( endpoint, rh, options)
end
{:ok, response} = request(method, endpoint, rb, rh, options)
response.body
end

Expand All @@ -86,24 +79,28 @@ defmodule Stripe do
make_request_with_key( method, endpoint, config_or_env_key, body, headers, options )
end


def make_oauth_token_callback_request(body) do
IO.puts "=====================oauthB"
rb = Stripe.URI.encode_query(body)
rh = req_headers( "sk_test_ZQ1ofROPQQjS23vI8qQhKwi0" )
rh = req_headers( Stripe.config_or_env_key )
|> Dict.to_list
options = []
HTTPoison.request(:post, "https://connect.stripe.com/oauth/token", rb, rh, options)
options = []
HTTPoison.request(:post, "#{Stripe.Connect.base_url}oauth/token", rb, rh, options)
end

def make_oauth_authorize_request(body) do
IO.puts "=====================authorizeA"
rb = Stripe.URI.encode_query(body)
rh = req_headers( Stripe.config_or_env_key )
|> Dict.to_list
def make_oauth_deauthorize_request(stripe_user_id) do
rb = Stripe.URI.encode_query([
stripe_user_id: stripe_user_id,
client_id: Stripe.config_or_env_platform_client_id])
rh = req_headers( Stripe.config_or_env_key)
|> Dict.to_list

options = []
HTTPoison.request(:post, "https://connect.stripe.com/oauth/authorize", rb, rh, options)
HTTPoison.request(:post, "#{Stripe.Connect.base_url}oauth/deauthorize", rb, rh, options)
end



@doc """
Grabs STRIPE_SECRET_KEY from system ENV
Returns binary
Expand Down
103 changes: 103 additions & 0 deletions lib/stripe/connect.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
defmodule Stripe.Connect do
require HashDict

@moduledoc """
Helper module for Connect related features at Stripe.
Through this API you can:
- retrieve the oauth access token or the full response, using the code received from the oauth flow return
(reference https://stripe.com/docs/connect/standalone-accounts)
"""

def base_url do
"https://connect.stripe.com/"
end


@doc """
Generate the URL to start a stripe workflow. You can pass in a
crsf token to be sent to stripe, which they send you back at the end of the workflow to further secure the interaction. Make sure you verify this token yourself on reception of the workflow callback.
"""
def generate_button_url( csrf_token ) do
client_id = Stripe.config_or_env_platform_client_id
url = base_url <> "oauth/authorize?response_type=code"
url = url <> "&scope=read_write"
url = url <> "&client_id=#{Stripe.config_or_env_platform_client_id}"

if String.length(csrf_token) > 0 do
url = url <> "&state=#{csrf_token}"
end
url
end

@doc """
Execute the oauth callback to Stripe using the code supplied in the request parameter of the oauth redirect at the end of the onboarding workflow.
# Example
```
{:ok, resp} = Stripe.Connect.oauth_token_callback code
IO.inspect resp
%{
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"
}
```
"""
def oauth_token_callback(code) do
req = [
client_secret: Stripe.config_or_env_key,
code: code,
grant_type: "authorization_code"
]

case Stripe.make_oauth_token_callback_request req do
{:ok, resp} ->
case resp.status_code do
200 ->
{:ok, Stripe.Util.string_map_to_atoms Poison.decode!(resp.body)}
_ -> {:error, Stripe.Util.string_map_to_atoms Poison.decode!(resp.body)}
end
end
end

@doc """
Same as oauth_token_callback, but gives you back only the access token which you can then use with the rest of the API.
```
{:ok, token} = Stripe.Connect.get_token code
```
"""
def get_token(code) do
case oauth_token_callback code do
{:ok, resp} -> {:ok,resp["access_token"]}
{:error, err} -> {:error, err}
end
end

@doc """
De-authorize an account with your connect entity. A kind of oauth reset which invalidates all tokens and requires the customer to re-establish the link using the onboarding workflow.
# Example
```
case Stripe.Connect.oauth_deauthorize stripe_user_id do
{:ok, success} -> assert success == true
{:error, msg} -> flunk msg
end
```
"""
def oauth_deauthorize( stripe_user_id ) do
{:ok, resp} = Stripe.make_oauth_deauthorize_request stripe_user_id
body = Stripe.Util.string_map_to_atoms Poison.decode!( resp.body )

case body[:stripe_user_id] == stripe_user_id do
true -> {:ok, true}
false -> {:error, body[:error_description]}
end
end
end

Loading

0 comments on commit 9857705

Please sign in to comment.