From 3b796b2c8a76347f955ca177d00d56b442685655 Mon Sep 17 00:00:00 2001 From: Andrew Walton Date: Fri, 11 Nov 2016 13:18:17 -0500 Subject: [PATCH] Add Stripe.Card --- lib/stripe/card.ex | 134 +++++++++++++++++++++++++++++++++++++++++++++ lib/stripe/util.ex | 16 ++++++ 2 files changed, 150 insertions(+) create mode 100644 lib/stripe/card.ex diff --git a/lib/stripe/card.ex b/lib/stripe/card.ex new file mode 100644 index 00000000..57bb111f --- /dev/null +++ b/lib/stripe/card.ex @@ -0,0 +1,134 @@ +defmodule Stripe.Card do + @moduledoc """ + Work with Stripe card objects. + + You can: + + - Create a card + - Retrieve a card + - Update a card + - Delete a card + + All requests require `owner_type` and `owner_id` parameters to be specified. + + `owner_type` must be one of the following: + * `:customer`, + * `:recipient`. + + `owner_id` must be the ID of the owning object. + + This module does not yet support managed accounts. + + Recipients may be deprecated for your version of the API. They have + been replaced by managed accounts (see + https://stripe.com/docs/connect/managed-accounts), which you should use + if you're creating a new platform. + + Stripe API reference: https://stripe.com/docs/api#cards + """ + + alias Stripe.Util + + @type t :: %__MODULE__{} + @type source :: :customer | :recipient + + defstruct [ + :id, :address_city, :address_country, :address_line1, + :address_line1_check, :address_line2, :address_state, + :address_zip, :address_zip_check, :brand, :country, + :customer, :cvc_check, :dynamic_last4, :exp_month, :exp_year, + :fingerprint, :funding, :last4, :metadata, :name, :recipient, + :tokenization_method + ] + + @valid_update_keys [ + :address_city, :address_country, :address_line1, :address_line2, + :address_state, :address_zip, :exp_month, :exp_year, :metadata, :name + ] + + defp endpoint_for_owner(owner_type, owner_id) do + case owner_type do + :customer -> "customers/#{owner_id}/sources" + :recipient -> "recipients/#{owner_id}/cards" + end + end + + @doc """ + Create a card. + + This requires a `token` created by a library like Stripe.js. + + For PCI compliance reasons you should not send a card's number or CVC + to your own server. + + If you want to create a card with your server without a token, you + can use the low-level API. + """ + @spec create(source, String.t, String.t, Keyword.t) :: {:ok, t} | {:error, Exception.t} + def create(owner_type, owner_id, token, opts \\ []) do + endpoint = endpoint_for_owner(owner_type, owner_id) + body = + to_create_body(owner_type, token) + |> Util.map_keys_to_atoms() + + case Stripe.request(:post, endpoint, body, %{}, opts) do + {:ok, result} -> {:ok, Util.stripe_response_to_struct(%__MODULE__{}, result)} + {:error, error} -> {:error, error} + end + end + + @spec to_create_body(source, String.t) :: map + defp to_create_body(owner_type, token) do + case owner_type do + :customer -> %{source: token} + :recipient -> %{external_account: token} + end + end + + @doc """ + Retrieve a card. + """ + @spec retrieve(source, String.t, String.t, Keyword.t) :: {:ok, t} | {:error, Exception.t} + def retrieve(owner_type, owner_id, card_id, opts \\ []) do + endpoint = endpoint_for_owner(owner_type, owner_id) <> "/" <> card_id + + case Stripe.request(:get, endpoint, %{}, %{}, opts) do + {:ok, result} -> {:ok, Util.stripe_response_to_struct(%__MODULE__{}, result)} + {:error, error} -> {:error, error} + end + end + + @doc """ + Update a card. + + Takes the `id` and a map of changes + """ + @spec update(source, String.t, String.t, map, Keyword.t) :: {:ok, t} | {:error, Exception.t} + def update(owner_type, owner_id, card_id, changes, opts \\ []) do + endpoint = endpoint_for_owner(owner_type, owner_id) <> "/" <> card_id + + card = + changes + |> Util.map_keys_to_atoms() + |> Map.take(@valid_update_keys) + |> Util.drop_nil_keys() + + case Stripe.request(:post, endpoint, card, %{}, opts) do + {:ok, result} -> {:ok, Util.stripe_response_to_struct(%__MODULE__{}, result)} + {:error, error} -> {:error, error} + end + end + + @doc """ + Delete a card. + """ + @spec delete(source, String.t, String.t, Keyword.t) :: :ok | {:error, Exception.t} + def delete(owner_type, owner_id, card_id, opts \\ []) do + endpoint = endpoint_for_owner(owner_type, owner_id) <> "/" <> card_id + + case Stripe.request(:delete, endpoint, %{}, %{}, opts) do + {:ok, _} -> :ok + {:error, error} -> {:error, error} + end + end +end diff --git a/lib/stripe/util.ex b/lib/stripe/util.ex index 474ef6a3..19eba403 100644 --- a/lib/stripe/util.ex +++ b/lib/stripe/util.ex @@ -1,5 +1,21 @@ defmodule Stripe.Util do + @type stripe_response :: %{String.t => any} + + @spec stripe_response_to_struct(struct, stripe_response) :: struct + def stripe_response_to_struct(struct, stripe_response) do + keys = case struct do + %{__struct__: _t} -> struct |> Map.from_struct |> Map.keys + _ -> raise "First argument to 'stripe_response_to_struct' must be a struct." + end + + Enum.reduce keys, struct, fn (atom, acc) -> + str = to_string(atom) + value = Map.get(stripe_response, str) + Map.put(acc, atom, value) + end + end + def drop_nil_keys(map) do Enum.reject(map, fn {_, nil} -> true