Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Stripe Issuing #493

Merged
merged 5 commits into from
May 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/stripe/converter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ defmodule Stripe.Converter do
file
invoice
invoiceitem
issuing.authorization
issuing.card
issuing.card_details
issuing.cardholder
issuing.dispute
issuing.transaction
line_item
list
oauth
Expand Down
170 changes: 170 additions & 0 deletions lib/stripe/issuing/authorization.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
defmodule Stripe.Issuing.Authorization do
@moduledoc """
Work with Stripe Issuing authorization objects.

You can:

- Retrieve an authorization
- Update an authorization
- Approve an authorization
- Decline an authorization
- List all authorizations

Stripe API reference: https://stripe.com/docs/api/issuing/authorizations
"""

use Stripe.Entity
import Stripe.Request

@type request_history :: %{
approved: boolean,
authorized_amount: integer,
authorized_currency: String.t(),
created: Stripe.timestamp(),
held_amount: integer,
held_currency: String.t(),
reason: String.t()
}

@type verification_data :: %{
address_line1_check: String.t(),
address_zip_check: String.t(),
cvc_check: String.t()
}

@type t :: %__MODULE__{
id: Stripe.id(),
object: String.t(),
approved: boolean,
authorization_method: String.t(),
authorized_amount: integer,
authorized_currency: String.t() | nil,
balance_transactions: Stripe.List.t(Stripe.BalanceTransaction.t()),
card: Stripe.Issuing.Card.t(),
cardholder: Stripe.id() | Stripe.Issuing.Cardholder.t(),
created: Stripe.timestamp(),
held_amount: integer,
held_currency: String.t() | nil,
is_held_amount_controllable: boolean,
livemode: boolean,
merchant_data: Stripe.Issuing.Types.merchant_data(),
metadata: Stripe.Types.metadata(),
pending_authorized_amount: integer,
pending_held_amount: integer,
request_history: Stripe.List.t(request_history()),
status: String.t(),
transactions: Stripe.List.t(Stripe.Issuing.Transaction.t()),
verification_data: verification_data(),
wallet_provider: String.t() | nil
}

defstruct [
:id,
:object,
:approved,
:authorization_method,
:authorized_amount,
:authorized_currency,
:balance_transactions,
:card,
:cardholder,
:created,
:held_amount,
:held_currency,
:is_held_amount_controllable,
:livemode,
:merchant_data,
:metadata,
:pending_authorized_amount,
:pending_held_amount,
:request_history,
:status,
:transactions,
:verification_data,
:wallet_provider
]

@plural_endpoint "issuing/authorizations"

@doc """
Retrieve an authorization.
"""
@spec retrieve(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
def retrieve(id, opts \\ []) do
new_request(opts)
|> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}")
|> put_method(:get)
|> make_request()
end

@doc """
Update an authorization.
"""
@spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
when params:
%{
optional(:metadata) => Stripe.Types.metadata()
}
| %{}
def update(id, params, opts \\ []) do
new_request(opts)
|> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}")
|> put_method(:post)
|> put_params(params)
|> make_request()
end

@doc """
Approve an authorization.
"""
@spec approve(Stripe.id() | t, params, Stripe.options()) ::
{:ok, t} | {:error, Stripe.Error.t()}
when params:
%{
optional(:held_amount) => non_neg_integer
}
| %{}
def approve(id, params \\ %{}, opts \\ []) do
new_request(opts)
|> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}" <> "/approve")
|> put_method(:post)
|> put_params(params)
|> make_request()
end

@doc """
Decline an authorization.
"""
@spec decline(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
def decline(id, opts \\ []) do
new_request(opts)
|> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}" <> "/decline")
|> put_method(:post)
|> make_request()
end

@doc """
List all authorizations.
"""
@spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()}
when params:
%{
optional(:card) => Stripe.Issuing.Card.t() | Stripe.id(),
optional(:cardholder) => Stripe.Issuing.Cardholder.t() | Stripe.id(),
optional(:created) => String.t() | Stripe.date_query(),
optional(:ending_before) => t | Stripe.id(),
optional(:limit) => 1..100,
optional(:starting_after) => t | Stripe.id(),
optional(:status) => String.t()
}
| %{}
def list(params \\ %{}, opts \\ []) do
new_request(opts)
|> prefix_expansions()
|> put_endpoint(@plural_endpoint)
|> put_method(:get)
|> put_params(params)
|> cast_to_id([:card, :cardholder, :ending_before, :starting_after])
|> make_request()
end
end
151 changes: 151 additions & 0 deletions lib/stripe/issuing/card.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
defmodule Stripe.Issuing.Card do
@moduledoc """
Work with Stripe Issuing card objects.

You can:

- Create a card
- Retrieve a card
- Update a card
- List all cards

Stripe API reference: https://stripe.com/docs/api/issuing/cards
"""

use Stripe.Entity
import Stripe.Request

@type t :: %__MODULE__{
id: Stripe.id(),
object: String.t(),
authorization_controls: Stripe.Issuing.Types.authorization_controls(),
brand: String.t(),
cardholder: Stripe.Issuing.Cardholder.t(),
created: Stripe.timestamp(),
currency: String.t(),
exp_month: pos_integer,
exp_year: pos_integer,
last4: String.t(),
livemode: boolean,
metadata: Stripe.Types.metadata(),
name: String.t(),
replacement_for: t | Stripe.id() | nil,
replacement_reason: String.t() | nil,
shipping: Stripe.Types.shipping() | nil,
status: String.t(),
type: atom() | String.t()
}

defstruct [
:id,
:object,
:authorization_controls,
:brand,
:cardholder,
:created,
:currency,
:exp_month,
:exp_year,
:last4,
:livemode,
:metadata,
:name,
:replacement_for,
:replacement_reason,
:shipping,
:status,
:type
]

@plural_endpoint "issuing/cards"

@doc """
Create a card.
"""
@spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
jcartwright marked this conversation as resolved.
Show resolved Hide resolved
when params:
%{
:currency => String.t(),
:type => :physical | :virtual,
optional(:authorization_controls) =>
Stripe.Issuing.Types.authorization_controls(),
optional(:cardholder) => Stripe.Issuing.Cardholder.t(),
optional(:metadata) => Stripe.Types.metadata(),
optional(:replacement_for) => t | Stripe.id(),
optional(:replacement_reason) => String.t(),
optional(:shipping) => Stripe.Types.shipping(),
optional(:status) => String.t()
}
| %{}
def create(params, opts \\ []) do
new_request(opts)
|> put_endpoint(@plural_endpoint)
|> put_params(params)
|> put_method(:post)
|> make_request()
end

@doc """
Retrieve a card.
"""
@spec retrieve(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
def retrieve(id, opts \\ []) do
new_request(opts)
|> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}")
|> put_method(:get)
|> make_request()
end

@doc """
Update a card.
"""
@spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()}
when params:
%{
optional(:authorization_controls) =>
Stripe.Issuing.Types.authorization_controls(),
optional(:cardholder) => Stripe.Issuing.Cardholder.t(),
optional(:metadata) => Stripe.Types.metadata(),
optional(:replacement_for) => t | Stripe.id(),
optional(:replacement_reason) => String.t(),
optional(:shipping) => Stripe.Types.shipping(),
optional(:status) => String.t()
}
| %{}
def update(id, params, opts \\ []) do
new_request(opts)
|> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}")
|> put_method(:post)
|> put_params(params)
|> make_request()
end

@doc """
List all cards.
"""
@spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()}
when params:
%{
optional(:cardholder) => Stripe.Issuing.Cardholder.t() | Stripe.id(),
optional(:created) => String.t() | Stripe.date_query(),
optional(:ending_before) => t | Stripe.id(),
optional(:exp_month) => String.t(),
optional(:exp_year) => String.t(),
optional(:last4) => String.t(),
optional(:limit) => 1..100,
optional(:source) => String.t(),
optional(:starting_after) => t | Stripe.id(),
optional(:status) => String.t(),
optional(:type) => String.t()
}
| %{}
def list(params \\ %{}, opts \\ []) do
new_request(opts)
|> prefix_expansions()
|> put_endpoint(@plural_endpoint)
|> put_method(:get)
|> put_params(params)
|> cast_to_id([:cardholder, :ending_before, :starting_after])
|> make_request()
end
end
46 changes: 46 additions & 0 deletions lib/stripe/issuing/card_details.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
defmodule Stripe.Issuing.CardDetails do
@moduledoc """
Work with Stripe Issuing card details.

You can:

- Retrieve card details

Stripe API reference: https://stripe.com/docs/api/issuing/cards/retrieve_details
"""

use Stripe.Entity
import Stripe.Request

@type t :: %__MODULE__{
card: Stripe.Issuing.Card.t(),
object: String.t(),
cvc: String.t(),
exp_month: String.t(),
exp_year: String.t(),
number: String.t()
}

defstruct [
:card,
:object,
:cvc,
:exp_month,
:exp_year,
:number
]

@plural_endpoint "issuing/cards"

@doc """
Retrieve card details.
"""
@spec retrieve(Stripe.id() | Stripe.Issuing.Card.t(), Stripe.options()) ::
{:ok, t} | {:error, Stripe.Error.t()}
def retrieve(id, opts \\ []) do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the same as the retrieve endpoint in Stripe.Issuing.Card? Is this module necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CardDetails is a nested resource used to retrieve the full sensitive details for an issued card. I treated it like a separate and distinct resource because the struct is very different from the Card resource used to manage issued cards.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. In their example response, it looks like the response object contains most of what is in the Issuing.Card struct. Am I missing something?

https://stripe.com/docs/api/issuing/cards/retrieve_details

Copy link
Contributor Author

@jcartwright jcartwright May 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some overlap, but the CardDetails has a Card child object and then the sensitive attributes for :cvc and :number. I'm open to suggestions if it makes more sense to define the CardDetails struct and a retrieve_card_details function in the Card module. I see them as distinct structs but concede that it might be unnecessary to have a separate module.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jcartwright Well I guess I am curious - if we limit the attributes to just these few, then won't we be discarding all of them that may come in the response?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the common attributes are in the CardDetails :card child element, so they are not discarded but simply nested.

new_request(opts)
|> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}" <> "/details")
|> put_method(:get)
|> make_request()
end
end
Loading