Repository: joshnuss/commerce_billing Branch: master Commit: 8e227b16a387 Files: 18 Total size: 28.5 KB Directory structure: gitextract_ee89c674/ ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── config/ │ └── config.exs ├── lib/ │ ├── commerce_billing/ │ │ ├── address.ex │ │ ├── credit_card.ex │ │ ├── gateways/ │ │ │ ├── base.ex │ │ │ ├── bogus.ex │ │ │ └── stripe.ex │ │ ├── response.ex │ │ └── worker.ex │ └── commerce_billing.ex ├── mix.exs └── test/ ├── commerce_billing_test.exs ├── gateways/ │ ├── bogus_test.exs │ └── stripe_test.exs └── test_helper.exs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /_build /deps erl_crash.dump *.ez NOTES docs ================================================ FILE: .travis.yml ================================================ language: elixir elixir: - 1.3.0 env: MIX_ENV=test otp_release: - 19.0 ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Joshua Nussbaum 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. ================================================ FILE: README.md ================================================ Commerce.Billing ================= [![Build Status](https://secure.travis-ci.org/joshnuss/commerce_billing.svg?branch=master "Build Status")](https://travis-ci.org/joshnuss/commerce_billing) Payment processing library for Elixir. Based on [Shopify's](http://shopify.com) [ActiveMerchant](http://github.com/Shopify/active_merchant) ruby gem ## Supported Gateways - Bogus - Stripe ## Advantages of Elixir - **Fault tolerant**: Each worker is supervised, so a new worker is started in the event of errors. Network errors are caught and payment is retried (not yet working). - **Distributed**: Run workers on different machines. - **Scalable**: Run multiple workers and adjust number of workers as needed. - **Throughput**: Takes advantage of all cores. For example on my laptop with 4 cores (2 threads per core), I can do 100 authorizations with Stripe in 10 seconds. Thats 864,000 transactions per day. ebay does 1.4M/day. - **Hot code swap**: Update code while the system is running ## Card processing example ```elixir alias Commerce.Billing alias Billing.{CreditCard, Address, Worker, Gateways} config = %{credentials: {"sk_test_BQokikJOvBiI2HlWgH4olfQ2", ""}, default_currency: "USD"} Worker.start_link(Gateways.Stripe, config, name: :my_gateway) card = %CreditCard{ name: "John Smith", number: "4242424242424242", expiration: {2017, 12}, cvc: "123" } address = %Address{ street1: "123 Main", city: "New York", region: "NY", country: "US", postal_code: "11111" } case Billing.authorize(:my_gateway, 199.95, card, billing_address: address, description: "Amazing T-Shirt") do {:ok, %{authorization: authorization}} -> IO.puts("Payment authorized #{authorization}") {:error, %{code: :declined, reason: reason}} -> IO.puts("Payment declined #{reason}") {:error, %{code: error}} -> IO.puts("Payment error #{error}") end ``` ## Road Map - Support multiple gateways (PayPal, Stripe, Authorize.net, Braintree etc..) - Support gateways that bill directly and those that use html integrations. - Support recurring billing - Each gateway is hosted in a worker process and supervised. - Workers can be pooled. (using poolboy) - Workers can be spread on multiple nodes - The gateway is selected by first calling the "Gateway Factory" process. The "Gateway Factory" decides which gateway to use. Usually it will just be one type based on configuration setting in mix.exs (i.e. Stripe), but the Factory can be replaced with something fancier. It will enable scenarios like: - Use one gateway for visa another for mastercard - Use primary gateway (i.e PayPal), but when PayPal is erroring switch to secondary/backup gateway (i.e. Authorize.net) - Currency specific gateway, i.e. use one gateway type for USD another for CAD - Retry on network failure ## License MIT @joshnuss is a freelance software consultant. joshnuss@gmail.com ================================================ FILE: config/config.exs ================================================ # This file is responsible for configuring your application # and its dependencies. The Mix.Config module provides functions # to aid in doing so. use Mix.Config # Note this file is loaded before any dependency and is restricted # to this project. If another project depends on this project, this # file won't be loaded nor affect the parent project. # Sample configuration: # # config :my_dep, # key: :value, # limit: 42 # It is also possible to import configuration files, relative to this # directory. For example, you can emulate configuration per environment # by uncommenting the line below and defining dev.exs, test.exs and such. # Configuration from the imported file will override the ones defined # here (which is why it is important to import them last). # # import_config "#{Mix.env}.exs" ================================================ FILE: lib/commerce_billing/address.ex ================================================ defmodule Commerce.Billing.Address do defstruct [:street1, :street2, :city, :region, :country, :postal_code, :phone] end ================================================ FILE: lib/commerce_billing/credit_card.ex ================================================ defmodule Commerce.Billing.CreditCard do defstruct [:name, :number, :expiration, :cvc] end ================================================ FILE: lib/commerce_billing/gateways/base.ex ================================================ defmodule Commerce.Billing.Gateways.Base do alias Commerce.Billing.Response @doc false defmacro __using__(_) do quote location: :keep do def purchase(_amount, _card_or_id, _opts) do not_implemented end def authorize(_amount, _card_or_id, _opts) do not_implemented end def capture(_id, _opts) do not_implemented end def void(_id, _opts) do not_implemented end def refund(_amount, _id, _opts) do not_implemented end def store(_card, _opts) do not_implemented end def unstore(_customer_id, _card_id, _opts) do not_implemented end defp http(method, path, params \\ [], opts \\ []) do credentials = Keyword.get(opts, :credentials) headers = [{"Content-Type", "application/x-www-form-urlencoded"}] data = params_to_string(params) HTTPoison.request(method, path, data, headers, [hackney: [basic_auth: credentials]]) end defp money_to_cents(amount) when is_float(amount) do trunc(amount * 100) end defp money_to_cents(amount) do amount end defp params_to_string(params) do params |> Enum.filter(fn {_k, v} -> v != nil end) |> URI.encode_query end @doc false defp not_implemented do {:error, Response.error(code: :not_implemented)} end defoverridable [purchase: 3, authorize: 3, capture: 2, void: 2, refund: 3, store: 2, unstore: 3] end end end ================================================ FILE: lib/commerce_billing/gateways/bogus.ex ================================================ defmodule Commerce.Billing.Gateways.Bogus do use Commerce.Billing.Gateways.Base alias Commerce.Billing.{ CreditCard, Response } def authorize(_amount, _card_or_id, _opts), do: success def purchase(_amount, _card_or_id, _opts), do: success def capture(id, _opts), do: success(id) def void(id, _opts), do: success(id) def refund(_amount, id, _opts), do: success(id) def store(_card=%CreditCard{}, _opts), do: success def unstore(customer_id, nil, _opts), do: success(customer_id) def unstore(_customer_id, card_id, _opts), do: success(card_id) defp success, do: {:ok, Response.success(authorization: random_string)} defp success(id), do: {:ok, Response.success(authorization: id)} defp random_string(length \\ 10), do: 1..length |> Enum.map(&random_char/1) |> Enum.join defp random_char(_), do: to_string(:crypto.rand_uniform(0,9)) end ================================================ FILE: lib/commerce_billing/gateways/stripe.ex ================================================ defmodule Commerce.Billing.Gateways.Stripe do @base_url "https://api.stripe.com/v1" @cvc_code_translator %{ "pass" => "M", "fail" => "N", "unchecked" => "P" } @avs_code_translator %{ {"pass", "pass"} => "Y", {"pass", "fail"} => "A", {"pass", "unchecked"} => "B", {"fail", "pass"} => "Z", {"fail", "fail"} => "N", {"unchecked", "pass"} => "P", {"unchecked", "unchecked"} => "I" } use Commerce.Billing.Gateways.Base alias Commerce.Billing.{ CreditCard, Address, Response } import Poison, only: [decode!: 1] def purchase(amount, card_or_id, opts), do: authorize(amount, card_or_id, [{:capture, true} | opts]) def authorize(amount, card_or_id, opts) do config = Keyword.fetch!(opts, :config) description = Keyword.get(opts, :description) address = Keyword.get(opts, :billing_address) customer_id = Keyword.get(opts, :customer_id) currency = Keyword.get(opts, :currency, config.default_currency) capture = Keyword.get(opts, :capture, false) params = [capture: capture, description: description, currency: currency, customer: customer_id] ++ amount_params(amount) ++ card_params(card_or_id) ++ address_params(address) ++ connect_params(opts) commit(:post, "charges", params, opts) end def capture(id, opts) do params = opts |> Keyword.get(:amount) |> amount_params commit(:post, "charges/#{id}/capture", params, opts) end def void(id, opts), do: commit(:post, "charges/#{id}/refund", [], opts) def refund(amount, id, opts) do params = amount_params(amount) commit(:post, "charges/#{id}/refund", params, opts) end def store(card=%CreditCard{}, opts) do customer_id = Keyword.get(opts, :customer_id) params = card_params(card) path = if customer_id, do: "customers/#{customer_id}/card", else: "customers" commit(:post, path, params, opts) end def unstore(customer_id, nil, opts), do: commit(:delete, "customers/#{customer_id}", [], opts) def unstore(customer_id, card_id, opts), do: commit(:delete, "customers/#{customer_id}/#{card_id}", [], opts) defp amount_params(amount), do: [amount: money_to_cents(amount)] defp card_params(card=%CreditCard{}) do {expiration_year, expiration_month} = card.expiration ["card[number]": card.number, "card[exp_year]": expiration_year, "card[exp_month]": expiration_month, "card[cvc]": card.cvc, "card[name]": card.name] end defp card_params(id), do: [card: id] defp address_params(address=%Address{}) do ["card[address_line1]": address.street1, "card[address_line2]": address.street2, "card[address_city]": address.city, "card[address_state]": address.region, "card[address_zip]": address.postal_code, "card[address_country]": address.country] end defp address_params(_), do: [] defp connect_params(opts), do: Keyword.take(opts, [:destination, :application_fee]) defp commit(method, path, params, opts) do config = Keyword.fetch!(opts, :config) method |> http("#{@base_url}/#{path}", params, credentials: config.credentials) |> respond end defp respond({:ok, %{status_code: 200, body: body}}) do data = decode!(body) {cvc_result, avs_result} = verification_result(data) {:ok, Response.success(authorization: data["id"], raw: data, cvc_result: cvc_result, avs_result: avs_result)} end defp respond({:ok, %{body: body, status_code: status_code}}) do data = decode!(body) {code, reason} = error(status_code, data["error"]) {cvc_result, avs_result} = verification_result(data) {:error, Response.error(code: code, reason: reason, raw: data, cvc_result: cvc_result, avs_result: avs_result)} end defp verification_result(%{"card" => card}) do cvc_result = @cvc_code_translator[card["cvc_check"]] avs_result = @avs_code_translator[{card["address_line1_check"], card["address_zip_check"]}] {cvc_result, avs_result} end defp verification_result(_), do: {"N","N"} defp error(status, _) when status >= 500, do: {:server_error, nil} defp error(_, %{"type" => "invalid_request_error"}), do: {:invalid_request, nil} defp error(_, %{"code" => "incorrect_number"}), do: {:declined, :invalid_number} defp error(_, %{"code" => "invalid_expiry_year"}), do: {:declined, :invalid_expiration} defp error(_, %{"code" => "invalid_expiry_month"}), do: {:declined, :invalid_expiration} defp error(_, %{"code" => "invalid_cvc"}), do: {:declined, :invalid_cvc} defp error(_, %{"code" => "rate_limit"}), do: {:rate_limit, nil} defp error(_, _), do: {:declined, :unknown} end ================================================ FILE: lib/commerce_billing/response.ex ================================================ defmodule Commerce.Billing.Response do defstruct [:success, :authorization, :code, :reason, :avs_result, :cvc_result, :raw] def success(opts \\ []) do new(true, opts) end def error(opts \\ []) do new(false, opts) end defp new(success, opts) do Map.merge(%__MODULE__{success: success}, Enum.into(opts, %{})) end end ================================================ FILE: lib/commerce_billing/worker.ex ================================================ defmodule Commerce.Billing.Worker do use GenServer def start_link(gateway, config, opts \\ []) do GenServer.start_link(__MODULE__, [gateway, config], opts) end def init([gateway, config]) do {:ok, %{config: config, gateway: gateway}} end def handle_call({:authorize, amount, card, opts}, _from, state) do response = state.gateway.authorize(amount, card, [{:config, state.config} | opts]) {:reply, response, state} end def handle_call({:purchase, amount, card, opts}, _from, state) do response = state.gateway.purchase(amount, card, [{:config, state.config} | opts]) {:reply, response, state} end def handle_call({:capture, id, opts}, _from, state) do response = state.gateway.capture(id, [{:config, state.config} | opts]) {:reply, response, state} end def handle_call({:void, id, opts}, _from, state) do response = state.gateway.void(id, [{:config, state.config} | opts]) {:reply, response, state} end def handle_call({:refund, amount, id, opts}, _from, state) do response = state.gateway.refund(amount, id, [{:config, state.config} | opts]) {:reply, response, state} end def handle_call({:store, card, opts}, _from, state) do response = state.gateway.store(card, [{:config, state.config} | opts]) {:reply, response, state} end def handle_call({:unstore, customer_id, card_id, opts}, _from, state) do response = state.gateway.unstore(customer_id, card_id, [{:config, state.config} | opts]) {:reply, response, state} end end ================================================ FILE: lib/commerce_billing.ex ================================================ defmodule Commerce.Billing do use Application import GenServer, only: [call: 2] # See http://elixir-lang.org/docs/stable/elixir/Application.html # for more information on OTP Applications def start(_type, _args) do import Supervisor.Spec, warn: false children = [ # Define workers and child supervisors to be supervised # worker(Commerce.Billing.Worker, [arg1, arg2, arg3]) ] # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html # for other strategies and supported options opts = [strategy: :one_for_one, name: Commerce.Billing.Supervisor] Supervisor.start_link(children, opts) end def authorize(worker, amount, card, opts \\ []), do: call(worker, {:authorize, amount, card, opts}) def purchase(worker, amount, card, opts \\ []), do: call(worker, {:purchase, amount, card, opts}) def capture(worker, id, opts \\ []), do: call(worker, {:capture, id, opts}) def void(worker, id, opts \\ []), do: call(worker, {:void, id, opts}) def refund(worker, amount, id, opts \\ []), do: call(worker, {:refund, amount, id, opts}) def store(worker, card, opts \\ []), do: call(worker, {:store, card, opts}) def unstore(worker, customer_id, card_id, opts \\ []), do: call(worker, {:unstore, customer_id, card_id, opts}) end ================================================ FILE: mix.exs ================================================ defmodule Commerce.Billing.Mixfile do use Mix.Project def project do [app: :commerce_billing, version: "0.0.2", description: "Credit card processing library", package: [ contributors: ["Joshua Nussbaum"], licenses: ["MIT"], links: %{github: "https://github.com/joshnuss/commerce_billing"} ], elixir: ">= 1.2.0", deps: deps] end # Configuration for the OTP application # # Type `mix help compile.app` for more information def application do [applications: [:httpoison, :hackney], mod: {Commerce.Billing, []}] end # Dependencies can be hex.pm packages: # # {:mydep, "~> 0.3.0"} # # Or git/path repositories: # # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1"} # # Type `mix help deps` for more examples and options defp deps do [{:poison, "~> 3.0"}, {:httpoison, ">= 0.7.1"}, {:ex_doc, ">= 0.6.0", only: :dev}, {:mock, ">= 0.1.0", only: :test}] end end ================================================ FILE: test/commerce_billing_test.exs ================================================ defmodule Commerce.BillingTest do use ExUnit.Case alias Commerce.Billing.Worker import Commerce.Billing defmodule FakeGateway do def authorize(100, :card, _) do :authorization_response end def purchase(100, :card, _) do :purchase_response end def capture(1234, _) do :capture_response end def void(1234, _) do :void_response end def refund(100, 1234, _) do :refund_response end def store(:card, _) do :store_response end def unstore(123, 456, _) do :unstore_response end end setup do {:ok, worker} = Worker.start_link(FakeGateway, :config) {:ok, worker: worker} end test "authorization", %{worker: worker} do assert authorize(worker, 100, :card, []) == :authorization_response end test "purchase", %{worker: worker} do assert purchase(worker, 100, :card, []) == :purchase_response end test "capture", %{worker: worker} do assert capture(worker, 1234, []) == :capture_response end test "void", %{worker: worker} do assert void(worker, 1234, []) == :void_response end test "refund", %{worker: worker} do assert refund(worker, 100, 1234, []) == :refund_response end test "store", %{worker: worker} do assert store(worker, :card, []) == :store_response end test "unstore", %{worker: worker} do assert unstore(worker, 123, 456, []) == :unstore_response end end ================================================ FILE: test/gateways/bogus_test.exs ================================================ defmodule Commerce.Billing.Gateways.BogusTest do use ExUnit.Case alias Commerce.Billing.Response alias Commerce.Billing.Gateways.Bogus, as: Gateway test "authorize" do {:ok, %Response{authorization: authorization, success: success}} = Gateway.authorize(10.95, :card, []) assert success assert authorization != nil end test "purchase" do {:ok, %Response{authorization: authorization, success: success}} = Gateway.purchase(10.95, :card, []) assert success assert authorization != nil end test "capture" do {:ok, %Response{authorization: authorization, success: success}} = Gateway.capture(1234, []) assert success assert authorization != nil end test "void" do {:ok, %Response{authorization: authorization, success: success}} = Gateway.void(1234, []) assert success assert authorization != nil end test "store" do {:ok, %Response{success: success}} = Gateway.store(%Commerce.Billing.CreditCard{}, []) assert success end test "unstore with customer" do {:ok, %Response{success: success}} = Gateway.unstore(1234, nil, []) assert success end test "unstore with card" do {:ok, %Response{success: success}} = Gateway.unstore(nil, 456, []) assert success end end ================================================ FILE: test/gateways/stripe_test.exs ================================================ defmodule Commerce.Billing.Gateways.StripeTest do use ExUnit.Case, async: false import Mock alias Commerce.Billing.{ CreditCard, Address, Response } alias Commerce.Billing.Gateways.Stripe, as: Gateway defmacrop with_post(url, {status, response}, statement, do: block) do quote do {:ok, agent} = Agent.start_link(fn -> nil end) requestFn = fn(:post, unquote(url), params, [{"Content-Type", "application/x-www-form-urlencoded"}], [hackney: [basic_auth: {'user', 'pass'}]]) -> Agent.update(agent, fn(_) -> params end) {:ok, %{status_code: unquote(status), body: unquote(response)}} end with_mock HTTPoison, [request: requestFn] do unquote(statement) var!(params) = Agent.get(agent, &(URI.decode_query(&1))) unquote(block) Agent.stop(agent) end end end defmacrop with_delete(url, {status, response}, do: block) do quote do requestFn = fn(:delete, unquote(url), params, [{"Content-Type", "application/x-www-form-urlencoded"}], [hackney: [basic_auth: {'user', 'pass'}]]) -> {:ok, %{status_code: unquote(status), body: unquote(response)}} end with_mock HTTPoison, [request: requestFn], do: unquote(block) end end setup do config = %{credentials: {'user', 'pass'}, default_currency: "USD"} {:ok, config: config} end test "authorize success with credit card", %{config: config} do raw = ~S/ { "id": "1234", "card": { "cvc_check": "pass", "address_line1_check": "unchecked", "address_zip_check": "pass" } } / card = %CreditCard{name: "John Smith", number: "123456", cvc: "123", expiration: {2015, 11}} address = %Address{street1: "123 Main", street2: "Suite 100", city: "New York", region: "NY", country: "US", postal_code: "11111"} with_post "https://api.stripe.com/v1/charges", {200, raw}, response = Gateway.authorize(10.95, card, billing_address: address, config: config) do {:ok, %Response{authorization: authorization, success: success, avs_result: avs_result, cvc_result: cvc_result}} = response assert success assert params["capture"] == "false" assert params["currency"] == "USD" assert params["amount"] == "1095" assert params["card[name]"] == "John Smith" assert params["card[number]"] == "123456" assert params["card[exp_month]"] == "11" assert params["card[exp_year]"] == "2015" assert params["card[cvc]"] == "123" assert params["card[address_line1]"] == "123 Main" assert params["card[address_line2]"] == "Suite 100" assert params["card[address_city]"] == "New York" assert params["card[address_state]"] == "NY" assert params["card[address_country]"] == "US" assert params["card[address_zip]"] == "11111" assert authorization == "1234" assert avs_result == "P" assert cvc_result == "M" end end test "purchase success with credit card", %{config: config} do raw = ~S/ { "id": "1234", "card": { "cvc_check": "pass", "address_line1_check": "unchecked", "address_zip_check": "pass" } } / card = %CreditCard{name: "John Smith", number: "123456", cvc: "123", expiration: {2015, 11}} address = %Address{street1: "123 Main", street2: "Suite 100", city: "New York", region: "NY", country: "US", postal_code: "11111"} with_post "https://api.stripe.com/v1/charges", {200, raw}, response = Gateway.purchase(10.95, card, billing_address: address, config: config) do {:ok, %Response{authorization: authorization, success: success, avs_result: avs_result, cvc_result: cvc_result}} = response assert success assert params["capture"] == "true" assert params["currency"] == "USD" assert params["amount"] == "1095" assert params["card[name]"] == "John Smith" assert params["card[number]"] == "123456" assert params["card[exp_month]"] == "11" assert params["card[exp_year]"] == "2015" assert params["card[cvc]"] == "123" assert params["card[address_line1]"] == "123 Main" assert params["card[address_line2]"] == "Suite 100" assert params["card[address_city]"] == "New York" assert params["card[address_state]"] == "NY" assert params["card[address_country]"] == "US" assert params["card[address_zip]"] == "11111" assert authorization == "1234" assert avs_result == "P" assert cvc_result == "M" end end test "purchase success with credit card to a Connect account", %{config: config} do raw = ~S/ { "id": "1234", "card": { "cvc_check": "pass", "address_line1_check": "unchecked", "address_zip_check": "pass" } } / card = %CreditCard{name: "John Smith", number: "123456", cvc: "123", expiration: {2015, 11}} address = %Address{street1: "123 Main", street2: "Suite 100", city: "New York", region: "NY", country: "US", postal_code: "11111"} destination = "stripe_id" application_fee = 123 with_post "https://api.stripe.com/v1/charges", {200, raw}, response = Gateway.purchase(10.95, card, billing_address: address, config: config, destination: destination, application_fee: application_fee) do {:ok, %Response{authorization: authorization, success: success, avs_result: avs_result, cvc_result: cvc_result}} = response assert success assert params["capture"] == "true" assert params["currency"] == "USD" assert params["amount"] == "1095" assert params["card[name]"] == "John Smith" assert params["card[number]"] == "123456" assert params["card[exp_month]"] == "11" assert params["card[exp_year]"] == "2015" assert params["card[cvc]"] == "123" assert params["card[address_line1]"] == "123 Main" assert params["card[address_line2]"] == "Suite 100" assert params["card[address_city]"] == "New York" assert params["card[address_state]"] == "NY" assert params["card[address_country]"] == "US" assert params["card[address_zip]"] == "11111" assert params["destination"] == destination assert params["application_fee"] == "123" assert authorization == "1234" assert avs_result == "P" assert cvc_result == "M" end end test "capture success", %{config: config} do raw = ~S/{"id": "1234"}/ with_post "https://api.stripe.com/v1/charges/1234/capture", {200, raw}, response = Gateway.capture(1234, amount: 19.95, config: config) do {:ok, %Response{authorization: authorization, success: success}} = response assert success assert params["amount"] == "1995" assert authorization == "1234" end end test "void success", %{config: config} do raw = ~S/{"id": "1234"}/ with_post "https://api.stripe.com/v1/charges/1234/refund", {200, raw}, response = Gateway.void(1234, config: config) do {:ok, %Response{authorization: authorization, success: success}} = response assert success assert params["amount"] == nil assert authorization == "1234" end end test "refund success", %{config: config} do raw = ~S/{"id": "1234"}/ with_post "https://api.stripe.com/v1/charges/1234/refund", {200, raw}, response = Gateway.refund(19.95, 1234, config: config) do {:ok, %Response{authorization: authorization, success: success}} = response assert success assert params["amount"] == "1995" assert authorization == "1234" end end test "store credit card without customer", %{config: config} do raw = ~S/{"id": "1234"}/ card = %CreditCard{name: "John Smith", number: "123456", cvc: "123", expiration: {2015, 11}} with_post "https://api.stripe.com/v1/customers", {200, raw}, response = Gateway.store(card, config: config) do {:ok, %Response{authorization: authorization, success: success}} = response assert success assert params["card[name]"] == "John Smith" assert params["card[number]"] == "123456" assert params["card[exp_month]"] == "11" assert params["card[exp_year]"] == "2015" assert params["card[cvc]"] == "123" assert authorization == "1234" end end test "store credit card with customer", %{config: config} do raw = ~S/{"id": "1234"}/ card = %CreditCard{name: "John Smith", number: "123456", cvc: "123", expiration: {2015, 11}} with_post "https://api.stripe.com/v1/customers/1234/card", {200, raw}, response = Gateway.store(card, customer_id: 1234, config: config) do {:ok, %Response{authorization: authorization, success: success}} = response assert success assert params["card[name]"] == "John Smith" assert params["card[number]"] == "123456" assert params["card[exp_month]"] == "11" assert params["card[exp_year]"] == "2015" assert params["card[cvc]"] == "123" assert authorization == "1234" end end test "unstore credit card", %{config: config} do with_delete "https://api.stripe.com/v1/customers/123/456", {200, "{}"} do {:ok, %Response{success: success}} = Gateway.unstore(123, 456, config: config) assert success end end test "unstore customer", %{config: config} do with_delete "https://api.stripe.com/v1/customers/123", {200, "{}"} do {:ok, %Response{success: success}} = Gateway.unstore(123, nil, config: config) assert success end end end ================================================ FILE: test/test_helper.exs ================================================ ExUnit.start()