From 1d5a868d0cf064df0562f4448674680780fcd4b5 Mon Sep 17 00:00:00 2001 From: Rob Conery Date: Fri, 2 Oct 2015 16:10:03 -0700 Subject: [PATCH] Initial --- .gitignore | 5 + LICENSE | 20 ++++ README.md | 59 ++++++++++ lib/stripe.ex | 87 ++++++++++++++ lib/stripe/charges.ex | 107 +++++++++++++++++ lib/stripe/customers.ex | 183 ++++++++++++++++++++++++++++++ lib/stripe/invoice_items.ex | 87 ++++++++++++++ lib/stripe/plans.ex | 53 +++++++++ lib/stripe/supervisor.ex | 18 +++ lib/stripe/uri.ex | 66 +++++++++++ lib/stripe/util.ex | 36 ++++++ mix.exs | 38 +++++++ mix.lock | 12 ++ test/stripe/charge_test.exs | 68 +++++++++++ test/stripe/customer_test.exs | 48 ++++++++ test/stripe/invoice_test.exs | 24 ++++ test/stripe/plan_test.exs | 33 ++++++ test/stripe/subscription_test.exs | 59 ++++++++++ test/test_helper.exs | 2 + 19 files changed, 1005 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 lib/stripe.ex create mode 100644 lib/stripe/charges.ex create mode 100644 lib/stripe/customers.ex create mode 100644 lib/stripe/invoice_items.ex create mode 100644 lib/stripe/plans.ex create mode 100644 lib/stripe/supervisor.ex create mode 100644 lib/stripe/uri.ex create mode 100644 lib/stripe/util.ex create mode 100644 mix.exs create mode 100644 mix.lock create mode 100644 test/stripe/charge_test.exs create mode 100644 test/stripe/customer_test.exs create mode 100644 test/stripe/invoice_test.exs create mode 100644 test/stripe/plan_test.exs create mode 100644 test/stripe/subscription_test.exs create mode 100644 test/test_helper.exs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4019e2a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/_build +/deps +erl_crash.dump +*.ez +config diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..2214bc12 --- /dev/null +++ b/LICENSE @@ -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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..b0933038 --- /dev/null +++ b/README.md @@ -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 diff --git a/lib/stripe.ex b/lib/stripe.ex new file mode 100644 index 00000000..e32d856c --- /dev/null +++ b/lib/stripe.ex @@ -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 diff --git a/lib/stripe/charges.ex b/lib/stripe/charges.ex new file mode 100644 index 00000000..798515d8 --- /dev/null +++ b/lib/stripe/charges.ex @@ -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 diff --git a/lib/stripe/customers.ex b/lib/stripe/customers.ex new file mode 100644 index 00000000..50a40cfe --- /dev/null +++ b/lib/stripe/customers.ex @@ -0,0 +1,183 @@ +defmodule Stripe.Customers do + @moduledoc """ + Main API for working with Customers at Stripe. Through this API you can alter subscriptions, create invoices, create and delete customers, etc. + """ + + @endpoint "customers" + + @doc """ + Creates a Customer with the given parameters - all of which are optional. + + ## Example + new_customer = [ + email: "test@test.com", + description: "An Test Account", + card: [ + number: "4111111111111111", + exp_month: 01, + exp_year: 2018, + cvc: 123, + name: "Joe Test User" + ] + ] + {:ok, res} = Stripe.Customers.create new_customer + """ + def create(params) do + Stripe.make_request(:post, @endpoint, params) + |> Stripe.Util.handle_stripe_response + end + + + @doc """ + Retrieves a given Customer with the specified ID. Returns 404 if not found. + ## Example + Stripe.Customers.get "customer_id" + """ + def get(id) do + Stripe.make_request(:get, "#{@endpoint}/#{id}") + |> Stripe.Util.handle_stripe_response + end + + + @doc """ + Changes a customer's subscription (plan, description, etc - see Stripe API for acceptable options). + Customer ID and Subscription ID are required for this. + + ## Example + Stripe.Customers.change_subscription "customer_id", "subscription_id", plan: "premium" + """ + def change_subscription(id, sub_id, opts) do + Stripe.make_request(:post, "#{@endpoint}/#{id}/subscriptions/#{sub_id}", opts) + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Starts a subscription for the specified customer. Note that if you pass in the customer *and* subscription information, both will be created at the same time. + + ## Example + new_sub = [ + email: "jill@test.com", + description: "Poop on the Potty", + plan: "standard", + source: [ + object: "card", + number: "4111111111111111", + exp_month: 01, + exp_year: 2018, + cvc: 123, + name: "Jill Subscriber" + ] + ] + {:ok, sub} = Stripe.Customers.create_subscription new_sub + + You can also just pass along the customer id and the plan name: + + new_sub = [ + plan: "standard", + customer: "customer_id" + ] + {:ok, sub} = Stripe.Customers.create_subscription new_sub + """ + def create_subscription(opts) do + Stripe.make_request(:post, "#{@endpoint}", opts) + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Creates a subscription for the specified customer. + + ## Example + Stripe.Customers.create_subscription "customer_id", "plan" + """ + def create_subscription(id, opts) do + Stripe.make_request(:post, "#{@endpoint}/#{id}/subscriptions", opts) + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Returns a subscription; customer_id and subscription_id are required. + + ## Example + Stripe.Customers.get_subscription "customer_id", "subscription_id" + """ + def get_subcription(id, sub_id) do + Stripe.make_request(:get, "#{@endpoint}/#{id}/subscriptions/#{sub_id}") + |> Stripe.Util.handle_stripe_response + end + + + @doc """ + Invoices a customer according to Stripe's invoice rules. This is not the same as a charge. + + ## Example + Stripe.Customers.create_invoice "customer_id", "subscription_id" + """ + def create_invoice(id, params) do + params = Keyword.put_new params, :customer, id + Stripe.make_request(:post, "invoices", params) + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Returns a list of invoices for a given customer + + ## Example + Stripe.Customers.get_invoices "customer_id" + """ + def get_invoices(id, params \\ []) do + params = Keyword.put_new params, :limit, 10 + params = [customer: id] + + Stripe.make_request(:get, "invoices", params ) + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Cancels a subscription + + ## Example + Stripe.Customers.cancel_subscription "customer_id", "subscription_id" + """ + def cancel_subscription(id, sub_id) do + Stripe.make_request(:delete, "#{@endpoint}/#{id}/subscriptions/#{sub_id}") + |> Stripe.Util.handle_stripe_response + + end + + + @doc """ + Returns all subscriptions for a Customer. + + ## Example + Stripe.Customers.get_subscriptions "customer_id" + """ + def get_subscriptions(id) do + Stripe.make_request(:get, "#{@endpoint}/#{id}/subscriptions") + |> Stripe.Util.handle_stripe_response + + end + + @doc """ + Returns a list of Customers with a default limit of 10 which you can override with `list/1` + + ## Example + {:ok, customers} = Stripe.Customers.list(20) + """ + def list(limit \\ 10) do + Stripe.make_request(:get, "#{@endpoint}?limit=#{limit}") + |> Stripe.Util.handle_stripe_response + end + + + @doc """ + Deletes a Customer with the specified ID + + ## Example + Stripe.Customers.delete "customer_id" + """ + def delete(id) do + Stripe.make_request(:delete, "#{@endpoint}/#{id}") + |> Stripe.Util.handle_stripe_response + end + +end diff --git a/lib/stripe/invoice_items.ex b/lib/stripe/invoice_items.ex new file mode 100644 index 00000000..db78362b --- /dev/null +++ b/lib/stripe/invoice_items.ex @@ -0,0 +1,87 @@ +defmodule Stripe.InvoiceItems do + @moduledoc """ + The API for tacking on charges to subscriptions. See Stripe docs for more details. + """ + + @endpoint "invoiceitems" + + @doc """ + Returns a list of InvoiceItems + """ + def list do + Stripe.make_request(:get, @endpoint) + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Creates an InvoiceItem for a Customer/Subscription + """ + def create(params) do + Stripe.make_request(:post, @endpoint, params) + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Retrieves the invoice item with the given ID. + + ## Arguments + + - `id` - `String` - (required) - The ID of the desired invoice item. + + ## Returns + + Returns an invoice item if a valid invoice item ID was provided. Returns + an error otherwise. + """ + def retrieve(id) do + Stripe.make_request(:get, @endpoint <> "/#{id}") + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Updates the amount or description of an invoice item on an upcoming invoice. + Updating an invoice item is only possible before the invoice it's attached + to is closed. + + ## Arguments + + - `amount` - `Integer` - (required) - The integer amount in cents of + the charge to be applied to the upcoming invoice. If you want to + apply a credit to the customer's account, pass a negative amount. + - `description` - `String` - (optional), default is `null` - An arbitrary + string which you can attach to the invoice item. The description is + displayed in the invoice for easy tracking. + - `metadata` - `Keyword` - (optional), default is `[]` - A set of + key/value pairs that you can attach to an invoice item object. It can + be useful for storing additional information about the invoice item in + a structured format. + + ## Returns + + The updated invoice item object is returned upon success. Otherwise, this + call returns an error. + """ + def update(params) do + Stripe.make_request(:post, @endpoint <> "/#{params[:id]}", params) + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Removes an invoice item from the upcoming invoice. Removing an invoice + item is only possible before the invoice it's attached to is closed. + + ## Arguments + + - `id` - `String` - (required) - The ID of the desired invoice item. + + ## Returns + + An object with the deleted invoice item's ID and a deleted flag upon + success. This call returns an error otherwise, such as when the invoice + item has already been deleted. + """ + def delete(id) do + Stripe.make_request(:delete, @endpoint <> "/#{id}") + |> Stripe.Util.handle_stripe_response + end +end diff --git a/lib/stripe/plans.ex b/lib/stripe/plans.ex new file mode 100644 index 00000000..2b9d5b3e --- /dev/null +++ b/lib/stripe/plans.ex @@ -0,0 +1,53 @@ +defmodule Stripe.Plans do + @moduledoc """ + Basic List, Create, Delete API for Plans + """ + + @endpoint "plans" + + @doc """ + Creates a Plan. Note that `currency` and `interval` are required parameters, and are defaulted to "USD" and "month" + + ## Example + {:ok, plan} = Stripe.Plans.create [id: "test-plan", name: "Test Plan", amount: 1000, interval: "month"] + """ + def create(params) do + + #default the currency and interval + params = Keyword.put_new params, :currency, "USD" + params = Keyword.put_new params, :interval, "month" + + Stripe.make_request(:post, @endpoint, params) + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Returns a list of Plans. + """ + def list(limit \\ 10) do + Stripe.make_request(:get, "#{@endpoint}?limit=#{limit}") + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Deletes a Plan with the specified ID. + + ## Example + {:ok, res} = Stripe.Plans.delete "test-plan" + """ + def delete(id) do + Stripe.make_request(:delete, "#{@endpoint}/#{id}") + |> Stripe.Util.handle_stripe_response + end + + @doc """ + Changes Plan information. See Stripe docs as to what you can change. + + ## Example + {:ok, plan} = Stripe.Plans.change("test-plan",[name: "Other Plan"]) + """ + def change(id, params) do + Stripe.make_request(:post, "#{@endpoint}/#{id}", params) + |> Stripe.Util.handle_stripe_response + end +end diff --git a/lib/stripe/supervisor.ex b/lib/stripe/supervisor.ex new file mode 100644 index 00000000..182ef624 --- /dev/null +++ b/lib/stripe/supervisor.ex @@ -0,0 +1,18 @@ +defmodule Stripe.Supervisor do + use Supervisor + + def start_link do + :supervisor.start_link(__MODULE__, []) + end + + def init([]) do + children = [ + # Define workers and child supervisors to be supervised + # worker(Stripe.Worker, []) + ] + + # See http://elixir-lang.org/docs/stable/Supervisor.Behaviour.html + # for other strategies and supported options + supervise(children, strategy: :one_for_one) + end +end \ No newline at end of file diff --git a/lib/stripe/uri.ex b/lib/stripe/uri.ex new file mode 100644 index 00000000..57c01531 --- /dev/null +++ b/lib/stripe/uri.ex @@ -0,0 +1,66 @@ +defmodule Stripe.URI do + @moduledoc """ + Stripe URI helpers to encode nested dictionaries as query_params. + """ + + defmacro __using__(_) do + quote do + defp build_url(ext \\ "") do + if ext != "", do: ext = "/" <> ext + + @base <> ext + end + end + end + + @doc """ + Takes a flat or nested HashDict and turns it into proper query values. + + ## Example + card = HashDict.new([ + card: HashDict.new([ + number: 424242424242, + exp_year: 2014 + ]) + ]) + + Stripe.URI.encode_query(card) # card[number]=424242424242&card[exp_year]=2014 + """ + def encode_query(list) do + Enum.map_join list, "&", fn x -> + pair(x) + end + end + + defp pair({key, value}) do + cond do + Enumerable.impl_for(value) -> + pair(to_string(key), [], value) + true -> + param_name = key |> to_string |> URI.encode + param_value = value |> to_string |> URI.encode + + "#{param_name}=#{param_value}" + end + end + + defp pair(root, parents, values) do + Enum.map_join values, "&", fn {key, value} -> + cond do + Enumerable.impl_for(value) -> + pair(root, parents ++ [key], value) + true -> + build_key(root, parents ++ [key]) <> to_string(value) + end + end + end + + defp build_key(root, parents) do + path = Enum.map_join parents, "", fn x -> + param = x |> to_string |> URI.encode + "[#{param}]" + end + + "#{root}#{path}=" + end +end diff --git a/lib/stripe/util.ex b/lib/stripe/util.ex new file mode 100644 index 00000000..3fe650d2 --- /dev/null +++ b/lib/stripe/util.ex @@ -0,0 +1,36 @@ +defmodule Stripe.Util do + def datetime_from_timestamp(ts) when is_binary ts do + ts = case Integer.parse ts do + :error -> 0 + {i, _r} -> i + end + datetime_from_timestamp ts + end + + def datetime_from_timestamp(ts) when is_number ts do + {{year, month, day}, {hour, minutes, seconds}} = :calendar.gregorian_seconds_to_datetime ts + {{year + 1970, month, day}, {hour, minutes, seconds}} + end + + def datetime_from_timestamp(nil) do + datetime_from_timestamp 0 + end + + def string_map_to_atoms([string_key_map]) do + string_map_to_atoms string_key_map + end + + def string_map_to_atoms(string_key_map) do + for {key, val} <- string_key_map, into: %{}, do: {String.to_atom(key), val} + end + + def handle_stripe_response(res) do + cond do + res["error"] -> {:error, res["error"]["message"]} + res["data"] -> {:ok, Enum.map(res["data"], &Stripe.Util.string_map_to_atoms &1)} + true -> {:ok, Stripe.Util.string_map_to_atoms res} + end + + end + +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 00000000..1e0f3c47 --- /dev/null +++ b/mix.exs @@ -0,0 +1,38 @@ +defmodule Stripe.Mixfile do + use Mix.Project + + def project do + [ app: :stripe, + version: "0.2.0", + elixir: "~> 1.0.5", + deps: deps(Mix.env) ] + end + + # Configuration for the OTP application + def application do + [mod: { Stripe, [] }] + end + + # Returns the list of dependencies in the format: + # { :foobar, git: "https://github.com/elixir-lang/foobar.git", tag: "0.1" } + # + # To specify particular versions, regardless of the tag, do: + # { :barbat, "~> 0.1", github: "elixir-lang/barbat" } + # Returns the list of development dependencies + defp deps(:dev) do + deps(:prod) + end + + defp deps(:test) do + deps(:dev) + end + + + defp deps(:prod) do + [ + {:httpoison, "~> 0.7.4" }, + {:hackney, "~> 1.3.2" }, # not included in hex version of httpoison :( + {:poison, "~> 1.5"} + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 00000000..e8e28110 --- /dev/null +++ b/mix.lock @@ -0,0 +1,12 @@ +%{"exactor": {:hex, :exactor, "2.2.0"}, + "exjsx": {:hex, :exjsx, "3.2.0"}, + "exvcr": {:git, "git://github.com/parroty/exvcr.git", "aa61c1899e45b4c2d2299732648f3800b0eac1af", []}, + "hackney": {:hex, :hackney, "1.3.2"}, + "httpoison": {:hex, :httpoison, "0.7.4"}, + "idna": {:hex, :idna, "1.0.2"}, + "jsex": {:hex, :jsex, "2.0.0"}, + "json": {:hex, :json, "0.3.2"}, + "jsx": {:hex, :jsx, "2.6.2"}, + "meck": {:hex, :meck, "0.8.3"}, + "poison": {:hex, :poison, "1.5.0"}, + "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"}} diff --git a/test/stripe/charge_test.exs b/test/stripe/charge_test.exs new file mode 100644 index 00000000..dc534133 --- /dev/null +++ b/test/stripe/charge_test.exs @@ -0,0 +1,68 @@ +defmodule Stripe.ChargeTest do + use ExUnit.Case + + setup do + params = [ + source: [ + object: "card", + number: "4111111111111111", + exp_month: 10, + exp_year: 2020, + country: "US", + name: "Ducky Test", + cvc: 123 + ], + description: "1000 Widgets" + ] + {:ok, [params: params]} + end + + test "A valid charge is successful with card as source", %{params: params} do + + case Stripe.Charges.create(1000,params) do + {:ok, res} -> assert res.id + {:error, err} -> flunk err + end + end + + test "Listing returns charges" do + case Stripe.Charges.list() do + {:ok, charges} -> assert length(charges) > 0 + {:error, err} -> flunk err + end + end + + test "Getting a charge" do + {:ok,[first | _]} = Stripe.Charges.list() + case Stripe.Charges.get(first.id) do + {:ok, charge} -> assert charge.id == first.id + {:error, err} -> flunk err + end + end + + test "Capturing a charge", %{params: params} do + params = Keyword.put_new params, :capture, false + {:ok, charge} = Stripe.Charges.create(1000,params) + case Stripe.Charges.capture(charge.id) do + {:ok, captured} -> assert captured.id == charge.id + {:error, err} -> flunk err + end + end + + test "Changing a charge", %{params: params} do + {:ok, charge} = Stripe.Charges.create(1000,params) + params = [description: "Changed charge"] + case Stripe.Charges.change(charge.id, params) do + {:ok, changed} -> assert changed.description == "Changed charge" + {:error, err} -> flunk err + end + end + + test "Refunding a charge", %{params: params} do + {:ok, charge} = Stripe.Charges.create(1000,params) + case Stripe.Charges.refund_partial(charge.id,500) do + {:ok, refunded} -> assert refunded.amount == 500 + {:error, err} -> flunk err + end + end +end diff --git a/test/stripe/customer_test.exs b/test/stripe/customer_test.exs new file mode 100644 index 00000000..91e3b8ff --- /dev/null +++ b/test/stripe/customer_test.exs @@ -0,0 +1,48 @@ +defmodule Stripe.CustomerTest do + use ExUnit.Case + + setup do + new_customer = [ + email: "test@test.com", + description: "An Elixir Test Account", + card: [ + number: "4111111111111111", + exp_month: 01, + exp_year: 2018, + cvc: 123, + name: "Joe Test User" + ] + ] + res = Stripe.Customers.create new_customer + case res do + {:ok, customer} -> {:ok, [res: customer]} + {:error, err} -> flunk err + end + + end + test "Creating a customer with valid params succeeds", %{res: customer} do + assert customer.id + end + + test "Customers are listed" do + {:ok, customers} = Stripe.Customers.list + assert length(customers) > 0 + end + + test "Finding a customer by id works", %{res: customer} do + case Stripe.Customers.get customer.id do + {:ok, found} -> assert found.id == customer.id + {:error, err} -> flunk err + end + end + + test "Deleting a customer succeeds", %{res: customer} do + case Stripe.Customers.delete customer.id do + {:ok, res} -> assert res.deleted + {:error, err} -> flunk err + end + + end + + +end diff --git a/test/stripe/invoice_test.exs b/test/stripe/invoice_test.exs new file mode 100644 index 00000000..793cd4e7 --- /dev/null +++ b/test/stripe/invoice_test.exs @@ -0,0 +1,24 @@ +defmodule Stripe.InvoiceTest do + use ExUnit.Case + + setup do + {:ok, [customer | _]} = Stripe.Customers.list(1) + {:ok, [customer: customer]} + end + + test "Listing invoices", %{customer: customer} do + case Stripe.Customers.get_invoices(customer.id) do + {:ok, invoices} -> assert invoices + {:error, err} -> flunk err + end + end + + test "Creating an invoice", %{customer: customer} do + sub = List.first(customer.subscriptions["data"])["id"] + params = [subscription: sub, description: "Test Invoice"] + case Stripe.Customers.create_invoice(customer.id, params) do + {:ok, invoice} -> assert invoice.id + {:error, err} -> flunk err + end + end +end diff --git a/test/stripe/plan_test.exs b/test/stripe/plan_test.exs new file mode 100644 index 00000000..cdf7ab1d --- /dev/null +++ b/test/stripe/plan_test.exs @@ -0,0 +1,33 @@ +defmodule Stripe.PlanTest do + use ExUnit.Case + + + test "Plan creation works" do + case Stripe.Plans.create([id: "test-plan", name: "Test Plan", amount: 1000]) do + {:ok, plan} -> assert plan.id == "test-plan" + {:error, err} -> flunk err + end + end + + test "Plan change works" do + case Stripe.Plans.change("test-plan",[name: "Other Plan"]) do + {:ok, plan} -> assert plan.name == "Other Plan" + {:error, err} -> flunk err + end + end + + test "Listing plans" do + case Stripe.Plans.list() do + {:ok, plans} -> assert length(plans) > 0 + {:error, err} -> flunk err + end + end + + test "Plan deletion works" do + case Stripe.Plans.delete "test-plan" do + {:ok, plan} -> assert plan.deleted + {:error, err} -> flunk err + end + end + +end diff --git a/test/stripe/subscription_test.exs b/test/stripe/subscription_test.exs new file mode 100644 index 00000000..727d61ca --- /dev/null +++ b/test/stripe/subscription_test.exs @@ -0,0 +1,59 @@ +defmodule Stripe.SubscriptionTest do + use ExUnit.Case + + setup do + #create a sub with nothing but a card + new_sub = [ + email: "jill@test.com", + description: "Jill Test Account", + plan: "standard", + source: [ + object: "card", + number: "4111111111111111", + exp_month: 01, + exp_year: 2018, + cvc: 123, + name: "Jill Subscriber" + ] + ] + + case Stripe.Customers.create_subscription new_sub do + {:ok, customer} -> {:ok, [customer: customer, sub: List.first(customer.subscriptions["data"])]} + {:error, err} -> flunk err + end + + end + + test "Listing subscriptions works", %{customer: customer, sub: sub} do + case Stripe.Customers.get_subscriptions customer.id do + {:ok, subs} -> assert subs + {:error, err} -> flunk err + end + end + + test "A sub is created", %{customer: customer, sub: sub} do + assert sub["id"] + end + + test "Retrieving the sub works", %{customer: customer, sub: sub} do + case Stripe.Customers.get_subcription customer.id, sub["id"] do + {:ok, found} -> assert found.id + {:error, err} -> flunk err + end + end + + test "Changing the sub works", %{customer: customer, sub: sub} do + case Stripe.Customers.change_subscription customer.id, sub["id"], plan: "premium" do + {:ok, changed} -> assert changed.plan["name"] == "Premium Plan" + {:error, err} -> flunk err + end + end + + test "Sub cancellation works", %{customer: customer, sub: sub} do + case Stripe.Customers.cancel_subscription customer.id, sub["id"] do + {:ok, canceled_sub} -> assert canceled_sub.id + {:error, err} -> flunk err + end + end + +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 00000000..2683c6c3 --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start +Stripe.start