Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
robconery committed Oct 2, 2015
0 parents commit 1d5a868
Show file tree
Hide file tree
Showing 19 changed files with 1,005 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/_build
/deps
erl_crash.dump
*.ez
config
20 changes: 20 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
The MIT License (MIT)

Copyright (c) 2014 Shane Logsdon

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Stripe for Elixir

An Elixir 1.0.5 library for working with Stripe. With this library you can:

- manage Customers
- Create, list, cancel, update and delete Subscriptions
- Create, list, update and delete Plans
- Create, list, and update Invoices
- And yes, run charges with or without an existing Customer

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.

## Stripe API

I've tested this library against Stripe API v1 and above.

## Usage

First, create a config folder and add a Stripe secret key:

```
use Mix.Config
config :stripe, secret_key: "YOUR SECRET KEY"
```

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

```
Stripe.start
```

## 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 arrity 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"
```

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.

### Customers
87 changes: 87 additions & 0 deletions lib/stripe.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
defmodule Stripe do
@moduledoc """
A HTTP client for Stripe.
"""

# Let's build on top of HTTPoison
use Application
use HTTPoison.Base

def start(_type, _args) do
Stripe.Supervisor.start_link
end

@doc """
Creates the URL for our endpoint.
Args:
* endpoint - part of the API we're hitting
Returns string
"""
def process_url(endpoint) do
"https://api.stripe.com/v1/" <> endpoint
end

@doc """
Set our request headers for every request.
"""
def req_headers do
HashDict.new
|> Dict.put("Authorization", "Bearer #{key}")
|> Dict.put("User-Agent", "Stripe/v1 stripe-elixir/0.1.0")
|> Dict.put("Content-Type", "application/x-www-form-urlencoded")
end

@doc """
Converts the binary keys in our response to atoms.
Args:
* body - string binary response
Returns Record or ArgumentError
"""
def process_response_body(body) do
Poison.decode! body
end

@doc """
Boilerplate code to make requests.
Args:
* endpoint - string requested API endpoint
* body - request body
Returns dict
"""
def make_request(method, endpoint, body \\ [], headers \\ [], options \\ []) do
# rb = Enum.map(body, &url_encode_keyvalue(&1))
# |> Enum.join("&")
rb = Stripe.URI.encode_query(body)
rh = req_headers
|> 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


response.body
end

@doc """
Grabs STRIPE_SECRET_KEY from system ENV
Returns binary
"""
def key do
Application.get_env(:stripe, :secret_key) ||
System.get_env "STRIPE_SECRET_KEY"
end

defp url_encode_keyvalue({k, v}) do
key = Atom.to_string(k)
"#{key}=#{v}"
end
end
107 changes: 107 additions & 0 deletions lib/stripe/charges.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
defmodule Stripe.Charges do
@moduledoc """
Handles charges to the Stripe API.
"""

@endpoint "charges"


@doc """
Creates a charge for a customer or card. You must pass in the amount, and also a source for the charge
that can be a token or customer. See the Stripe docs for proper source specs.
## Examples
params = [
source: [
object: "card",
number: "4111111111111111",
exp_month: 10,
exp_year: 2020,
country: "US",
name: "Ducky Test",
cvc: 123
],
description: "1000 Widgets"
]
{:ok, result} = Stripe.Charges.create 1000,params
"""
def create(amount, params) do
#default currency
params = Keyword.put_new params, :currency, "USD"
#drop in the amount
params = Keyword.put_new params, :amount, amount

Stripe.make_request(:post, @endpoint, params)
|> Stripe.Util.handle_stripe_response
end


@doc """
Lists out charges from your account with a default limit of 10. You can override this by passing in a limit.
## Examples
{:ok, charges} = Stripe.Charges.list(100)
"""
def list(limit \\ 10) do
Stripe.make_request(:get, "#{@endpoint}?limit=#{limit}")
|> Stripe.Util.handle_stripe_response
end


@doc """
Updates a charge with changeable information (see the Stripe docs on what you can change)
## Examples
params = [description: "Changed charge"]
Stripe.Charges.change("charge_id", params)
"""
def change(id, params) do
Stripe.make_request(:post, "#{@endpoint}/#{id}",params)
|> Stripe.Util.handle_stripe_response
end

@doc """
Captures a charge that is currently pending. Note: you can default a charge to be automatically captured by setting `capture: true` in the charge create params.
## Example
Stripe.Charges.capture("charge_id")
"""
def capture(id) do
Stripe.make_request(:post, "#{@endpoint}/#{id}/capture")
|> Stripe.Util.handle_stripe_response
end


@doc """
Retrieves a given charge.
## Example
Stripe.Charges.get("charge_id")
"""
def get(id) do
Stripe.make_request(:get, "#{@endpoint}/#{id}")
|> Stripe.Util.handle_stripe_response
end

@doc """
Refunds a charge completely. Use `refund_partial` if you just want to... you know... partially refund
## Example
Stripe.Charges.refund("charge_id")
"""
def refund(id) do
Stripe.make_request(:post, "#{@endpoint}/#{id}/refunds")
|> Stripe.Util.handle_stripe_response
end

@doc """
Refunds a charge partially; the amount is required.
## Example
Stripe.Charges.refund_partial("charge_id",500)
"""
def refund_partial(id, amount) do
params = [amount: amount]
Stripe.make_request(:post, "#{@endpoint}/#{id}/refunds", params)
|> Stripe.Util.handle_stripe_response
end
end
Loading

0 comments on commit 1d5a868

Please sign in to comment.