diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 000000000..10afd783b --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,27 @@ +export_locals_without_parens = [ + plug: 1, + plug: 2, + forward: 2, + forward: 3, + forward: 4, + match: 2, + match: 3, + get: 2, + get: 3, + post: 2, + post: 3, + put: 2, + put: 3, + patch: 2, + patch: 3, + delete: 2, + delete: 3, + options: 2, + options: 3 +] + +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], + locals_without_parens: export_locals_without_parens, + export: [locals_without_parens: export_locals_without_parens] +] diff --git a/.gitignore b/.gitignore index 2b6cbf85f..8a1641b13 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ erl_crash.dump config/config.secret.exs /doc .DS_Store +tags # elixir LS files @@ -13,4 +14,4 @@ config/config.secret.exs /.idea *.iml -priv/plts/*.plt +priv/plts/* diff --git a/.travis.yml b/.travis.yml index b147f5600..17d65e3c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,19 @@ language: elixir dist: trusty elixir: - - 1.5 - - 1.6 - 1.7 + - 1.8 otp_release: - 19.3 - 20.1 +matrix: + exclude: + - otp_release: 19.3 + elixir: 1.8 sudo: false env: global: - - STRIPE_MOCK_VERSION=0.30.0 + - STRIPE_MOCK_VERSION=0.55.0 - MIX_ENV=test STRIPE_SECRET_KEY=non_empty_secret_key_string cache: directories: @@ -25,6 +28,10 @@ before_install: fi - export PATH=$PATH:$PWD/stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}/ script: + - | + if [ "$TRAVIS_ELIXIR_VERSION" == "1.8" ]; then + mix format --dry-run --check-formatted; + fi - mix coveralls.travis - MIX_ENV=dev mix dialyzer --halt-exit-status after_script: diff --git a/CHANGELOG.md b/CHANGELOG.md index 660425955..f86fe0e2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,59 @@ # Latest +## 2.4.0 + +- [Issues/add missing fields](https://github.com/code-corps/stripity_stripe/pull/511) +- [Support upcoming invoices with subscription only](https://github.com/code-corps/stripity_stripe/pull/515) +- [Warn when unknown objects are processed](https://github.com/code-corps/stripity_stripe/pull/514) +- [Add missing invoice endpoints](https://github.com/code-corps/stripity_stripe/pull/513) +- [Add support for sending invoices](https://github.com/code-corps/stripity_stripe/pull/510) +- [Update Stripe version](https://github.com/code-corps/stripity_stripe/pull/483) +- [Use Jason over Poison](https://github.com/code-corps/stripity_stripe/pull/509) +- [Set minimum elixir version to 1.7](https://github.com/code-corps/stripity_stripe/pull/504) + +## 2.3.0 + +- [Add support for Subscription Schedules](https://github.com/code-corps/stripity_stripe/pull/480) +- [Add Stripe Issuing](https://github.com/code-corps/stripity_stripe/pull/493) +- [Add CreditNote api calls](https://github.com/code-corps/stripity_stripe/pull/492) +- [Add tax_rate struct and api calls](https://github.com/code-corps/stripity_stripe/pull/491) +- [Format mix.exs to pass CI](https://github.com/code-corps/stripity_stripe/pull/498) +- [Reorganize docs](https://github.com/code-corps/stripity_stripe/pull/496) +- [Add PaymentMethods module, with struct, list/2, attach/2, detach/2](https://github.com/code-corps/stripity_stripe/pull/495) + +## 2.2.3 + +- [Configurable json serializer - You can now use Jason instead of Poison](https://github.com/code-corps/stripity_stripe/pull/446) +- [Support for voiding invoices](https://github.com/code-corps/stripity_stripe/pull/444) +- [Use singular time units in System.system_time/1](https://github.com/code-corps/stripity_stripe/pull/450) +- [Expanded configuration options](https://github.com/code-corps/stripity_stripe/pull/447) +- [Add invoice settings to customer/invoice objects](https://github.com/code-corps/stripity_stripe/pull/451) +- [Upgrade to 2018-11-08 Stripe API](https://github.com/code-corps/stripity_stripe/pull/439) +- [Fixed bug in `Usage.retrieve`](https://github.com/code-corps/stripity_stripe/pull/433) +- [Add elixir 1.8 to test matrix](https://github.com/code-corps/stripity_stripe/pull/449) +- [Add authorization header api key to deauthorization endpoint]((https://github.com/code-corps/stripity_stripe/pull/465) +- [Add support for idempotent post requests](https://github.com/code-corps/stripity_stripe/pull/461) +- [Support new Checkout flow](https://github.com/code-corps/stripity_stripe/pull/466) +- [Add `receipt_url` to the return Charge data](https://github.com/code-corps/stripity_stripe/pull/467) +- [Fixed some bugs in `Review` struct fields](https://github.com/code-corps/stripity_stripe/pull/468) +- [Add basic support for payment intents](https://github.com/code-corps/stripity_stripe/pull/470) +- [Make `client_secret` optional](https://github.com/code-corps/stripity_stripe/pull/473) +- [Update hackney requirement to 1.15](https://github.com/code-corps/stripity_stripe/pull/475) +- [Upgrade to ex doc 0.20](https://github.com/code-corps/stripity_stripe/pull/486) +- [Upgrade stripe mock to 0.55](https://github.com/code-corps/stripity_stripe/pull/485) +- [Remove unused Bypass dependency](https://github.com/code-corps/stripity_stripe/pull/88) +- [Implement `assert_stripe_requested/3` test helper](https://github.com/code-corps/stripity_stripe/pull/487) +- Misc documentation improvements + +## 2.2.2 + +- [Upgrade stripe-mock 0.38](https://github.com/code-corps/stripity_stripe/pull/436) +- *New Feature:* [Add delete endpoint to Invoiceitems](https://github.com/code-corps/stripity_stripe/pull/434) +- [Ensure tests wait until stripe_mock started](https://github.com/code-corps/stripity_stripe/pull/427) +- [Upgraded erlexec for OTP 21 support](https://github.com/code-corps/stripity_stripe/pull/426) +- [Added missing invoice attributes](https://github.com/code-corps/stripity_stripe/pull/425) +- [Enabled Dialyxir in CI](https://github.com/code-corps/stripity_stripe/pull/424) + ## 2.2.1 - [Fix: date_query type had fields incorrectly marked as mandatory](https://github.com/code-corps/stripity_stripe/pull/421) diff --git a/README.md b/README.md index a15468ff9..2f7f2458c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ The actively developed line of releases is `2.x.x` and is contained within the ` ------------ | ------------- `2.0.x` | `2018-02-28` `2.1.x - 2.2.x` | `2018-05-21` -`master` | `2018-08-23` +`master` | `2019-03-14` # Documentation @@ -32,12 +32,18 @@ The actively developed line of releases is `2.x.x` and is contained within the ` ## Installation -Install the dependency: +Install the dependency by version: ```ex {:stripity_stripe, "~> 2.0.0"} ``` +Or by commit reference (still awaiting hex publish rights so this is your best best for now): + +```ex +{:stripity_stripe, git: "https://github.com/code-corps/stripity_stripe", ref: "8c091d4278d29a917bacef7bb2f0606317fcc025"} +``` + Next, add to your applications: _Not necessary if using elixir >= 1.4_ @@ -55,9 +61,25 @@ To make API calls, it is necessary to configure your Stripe secret key. ```ex use Mix.Config +config :stripity_stripe, api_key: System.get_env("STRIPE_SECRET") +# OR config :stripity_stripe, api_key: "YOUR SECRET KEY" ``` +It's possible to use a function or a tuple to resolve the secret: + +```ex +config :stripity_stripe, api_key: {MyApp.Secrets, :stripe_secret, []} +# OR +config :stripity_stripe, api_key: fn -> System.get_env("STRIPE_SECRET") end +``` + +Moreover, if you are using Poison instead of Jason, you can configure the library to use Poison like so: + +```ex +config :stripity_stripe, json_library: Poison +``` + ## Note: Object Expansion Some Stripe API endpoints support returning related objects via the object expansion query parameter. To take advantage of this feature, stripity_stripe accepts @@ -95,6 +117,12 @@ We will get the full object back as well. For details on which objects can be expanded check out the [stripe object expansion](https://stripe.com/docs/api#expanding_objects) docs. +# Testing + +To run the tests you'll need to install [`stripe-mock`](https://github.com/stripe/stripe-mock) It is a mock HTTP server that responds like the real Stripe API. It's powered by the [Stripe OpenAPI specification](https://github.com/stripe/openapi), which is generated from within Stripe's API. + +Start `stripe-mock` before running the tests with `mix test`. + # Documentation for 1.x.x
Click to expand @@ -131,11 +159,6 @@ config :stripity_stripe, secret_key: "YOUR SECRET KEY" config :stripity_stripe, platform_client_id: "YOUR CONNECT PLATFORM CLIENT ID" ``` -To customize the underlying HTTPoison library, you may optionally add an `:httpoison_options` key to the stripity_stripe configuration. For a full list of configuration options, please refer to the [HTTPoison documentation](https://github.com/edgurgel/httpoison). - -```ex -config :stripity_stripe, httpoison_options: [timeout: 10000, recv_timeout: 10000, proxy: {"proxy.mydomain.com", 8080}] -``` ## Testing diff --git a/lib/stripe.ex b/lib/stripe.ex index 534ae20db..e080217c6 100644 --- a/lib/stripe.ex +++ b/lib/stripe.ex @@ -17,6 +17,21 @@ defmodule Stripe do config :stripity_stripe, api_key: System.get_env("STRIPE_API_KEY") + ### Shared Options + + Almost all of the requests that can be sent accept the following options: + + * `:api_key` - The Stripe API key to use for the request. See + [https://stripe.com/docs/api/authentication](https://stripe.com/docs/api/authentication) + * `:connect_account` - The ID of a Stripe Connect account for which the + request should be made, passed through as the "Stripe-Account" header. The + preferred authentication method for Stripe Connect. See + [https://stripe.com/docs/connect/authentication#stripe-account-header](https://stripe.com/docs/connect/authentication#stripe-account-header) + * `:expand` - Takes a list of fields that should be expanded in the response + from Stripe. See [https://stripe.com/docs/api/expanding_objects](https://stripe.com/docs/api/expanding_objects) + * `:idempotency_key` - A string that is passed through as the "Idempotency-Key" header on all POST requests. This is used by Stripe's idempotency layer to manage + duplicate requests to the stripe API. See [https://stripe.com/docs/api/idempotent_requests](https://stripe.com/docs/api/idempotent_requests) + ### HTTP Connection Pool Stripity Stripe is set up to use an HTTP connection pool by default. This @@ -49,11 +64,11 @@ defmodule Stripe do @type id :: String.t() @type date_query :: %{ - optional(:gt) => timestamp, - optional(:gte) => timestamp, - optional(:lt) => timestamp, - optional(:lte) => timestamp - } + optional(:gt) => timestamp, + optional(:gte) => timestamp, + optional(:lt) => timestamp, + optional(:lte) => timestamp + } @type options :: Keyword.t() @type timestamp :: pos_integer diff --git a/lib/stripe/api.ex b/lib/stripe/api.ex index a58894a6b..841fab57c 100644 --- a/lib/stripe/api.ex +++ b/lib/stripe/api.ex @@ -5,7 +5,7 @@ defmodule Stripe.API do Usually the utilities in `Stripe.Request` are a better way to write custom interactions with the API. """ - alias Stripe.Error + alias Stripe.{Config, Error} @callback oauth_request(method, String.t(), map) :: {:ok, map} @@ -16,8 +16,17 @@ defmodule Stripe.API do @typep http_failure :: {:error, term} @pool_name __MODULE__ - @api_version "2018-08-23" - @http_module Application.get_env(:stripity_stripe, :http_module) || :hackney + @api_version "2019-05-16; checkout_sessions_beta=v1" + + @doc """ + In config.exs your implicit or expicit configuration is: + config :stripity_stripe, + json_library: Poison # defaults to Jason but can be configured to Poison + """ + @spec json_library() :: module + def json_library() do + Config.resolve(:json_library, Jason) + end def supervisor_children do if use_pool?() do @@ -29,34 +38,33 @@ defmodule Stripe.API do @spec get_pool_options() :: Keyword.t() defp get_pool_options() do - Application.get_env(:stripity_stripe, :pool_options) + Config.resolve(:pool_options) end @spec get_base_url() :: String.t() defp get_base_url() do - Application.get_env(:stripity_stripe, :api_base_url) + Config.resolve(:api_base_url) end @spec get_upload_url() :: String.t() defp get_upload_url() do - Application.get_env(:stripity_stripe, :api_upload_url) + Config.resolve(:api_upload_url) end @spec get_default_api_key() :: String.t() defp get_default_api_key() do - case Application.get_env(:stripity_stripe, :api_key) do - nil -> - # use an empty string and let Stripe produce an error - "" - - key -> - key - end + # if no API key is set default to `""` which will raise a Stripe API error + Config.resolve(:api_key, "") end @spec use_pool?() :: boolean defp use_pool?() do - Application.get_env(:stripity_stripe, :use_connection_pool) + Config.resolve(:use_connection_pool) + end + + @spec http_module() :: module + defp http_module() do + Config.resolve(:http_module, :hackney) end @spec add_common_headers(headers) :: headers @@ -86,6 +94,12 @@ defmodule Stripe.API do |> Map.put("Content-Type", "multipart/form-data") end + @spec maybe_add_auth_header_oauth(headers, String.t(), String.t() | nil) :: headers + defp maybe_add_auth_header_oauth(headers, "deauthorize", api_key), + do: add_auth_header(headers, api_key) + + defp maybe_add_auth_header_oauth(headers, _endpoint, _api_key), do: headers + @spec add_auth_header(headers, String.t() | nil) :: headers defp add_auth_header(existing_headers, api_key) do api_key = fetch_api_key(api_key) @@ -151,9 +165,11 @@ defmodule Stripe.API do def request(body, method, endpoint, headers, opts) do {expansion, opts} = Keyword.pop(opts, :expand) - base_url = get_base_url() + {idempotency_key, opts} = Keyword.pop(opts, :idempotency_key) + base_url = get_base_url() req_url = add_object_expansion("#{base_url}#{endpoint}", expansion) + headers = add_idempotency_header(idempotency_key, headers, method) req_body = body @@ -200,8 +216,9 @@ defmodule Stripe.API do @doc """ A low level utility function to make an OAuth request to the Stripe API """ - @spec oauth_request(method, String.t(), map) :: {:ok, map} | {:error, Stripe.Error.t()} - def oauth_request(method, endpoint, body) do + @spec oauth_request(method, String.t(), map, String.t() | nil) :: + {:ok, map} | {:error, Stripe.Error.t()} + def oauth_request(method, endpoint, body, api_key \\ nil) do base_url = "https://connect.stripe.com/oauth/" req_url = base_url <> endpoint req_body = Stripe.URI.encode_query(body) @@ -209,6 +226,7 @@ defmodule Stripe.API do req_headers = %{} |> add_default_headers() + |> maybe_add_auth_header_oauth(endpoint, api_key) |> Map.to_list() req_opts = @@ -216,7 +234,7 @@ defmodule Stripe.API do |> add_default_options() |> add_pool_option() - @http_module.request(method, req_url, req_headers, req_body, req_opts) + http_module().request(method, req_url, req_headers, req_body, req_opts) |> handle_response() end @@ -238,7 +256,7 @@ defmodule Stripe.API do |> add_default_options() |> add_pool_option() - @http_module.request(method, req_url, req_headers, body, req_opts) + http_module().request(method, req_url, req_headers, body, req_opts) |> handle_response() end @@ -247,7 +265,7 @@ defmodule Stripe.API do decoded_body = body |> decompress_body(headers) - |> Poison.decode!() + |> json_library().decode!() {:ok, decoded_body} end @@ -256,7 +274,7 @@ defmodule Stripe.API do request_id = headers |> List.keyfind("Request-Id", 0) error = - case Poison.decode(body) do + case json_library().decode(body) do {:ok, %{"error_description" => _} = api_error} -> Error.from_stripe_error(status, api_error, request_id) @@ -301,4 +319,12 @@ defmodule Stripe.API do end defp add_object_expansion(url, _), do: url + + defp add_idempotency_header(nil, headers, _), do: headers + + defp add_idempotency_header(idempotency_key, headers, :post) do + Map.put(headers, "Idempotency-Key", idempotency_key) + end + + defp add_idempotency_header(_, headers, _), do: headers end diff --git a/lib/stripe/checkout/session.ex b/lib/stripe/checkout/session.ex new file mode 100644 index 000000000..2180c0268 --- /dev/null +++ b/lib/stripe/checkout/session.ex @@ -0,0 +1,88 @@ +defmodule Stripe.Session do + @moduledoc """ + Work with Stripe Checkout Session objects. + + You can: + + - Create a new session + + Stripe API reference: https://stripe.com/docs/api/checkout/sessions + """ + + use Stripe.Entity + import Stripe.Request + + @type line_item :: %{ + :amount => integer(), + :currency => String.t(), + :name => String.t(), + :quantity => integer(), + optional(:description) => String.t(), + optional(:images) => list(String.t()) + } + + @type capture_method :: :automatic | :manual + + @type transfer_data :: %{ + :destination => String.t() + } + + @type payment_intent_data :: %{ + optional(:application_fee_amount) => integer(), + optional(:capture_method) => capture_method, + optional(:description) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:on_behalf_of) => String.t(), + optional(:receipt_email) => String.t(), + optional(:shipping) => Stripe.Types.shipping(), + optional(:statement_descriptor) => String.t(), + optional(:transfer_data) => transfer_data + } + + @type item :: %{ + :plan => String.t(), + optional(:quantity) => integer() + } + + @type subscription_data :: %{ + :items => list(item), + :metadata => Stripe.Types.metadata(), + :trial_end => integer(), + :trial_period_days => integer() + } + + @type create_params :: %{ + :cancel_url => String.t(), + :payment_method_types => list(String.t()), + :success_url => String.t(), + optional(:client_reference_id) => String.t(), + optional(:customer_email) => String.t(), + optional(:line_items) => list(line_item), + optional(:locale) => String.t(), + optional(:payment_intent_data) => payment_intent_data, + optional(:subscription_data) => subscription_data + } + + @type t :: %__MODULE__{ + :id => Stripe.id(), + :object => String.t(), + :livemode => boolean() + } + + defstruct [ + :id, + :object, + :livemode + ] + + @plural_endpoint "checkout/sessions" + + @spec create(create_params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + def create(params, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint) + |> put_params(params) + |> put_method(:post) + |> make_request() + end +end diff --git a/lib/stripe/config.ex b/lib/stripe/config.ex new file mode 100644 index 000000000..2f90a1675 --- /dev/null +++ b/lib/stripe/config.ex @@ -0,0 +1,36 @@ +defmodule Stripe.Config do + @moduledoc """ + Utility that handles interaction with the application's configuration + """ + + @doc """ + Resolves the given key from the application's configuration returning the + wrapped expanded value. If the value was a function it get's evaluated, if + the value is a touple of three elements it gets applied. + """ + @spec resolve(atom, any) :: any + def resolve(key, default \\ nil) + + def resolve(key, default) when is_atom(key) do + Application.get_env(:stripity_stripe, key, default) + |> expand_value() + end + + def resolve(key, _) do + raise( + ArgumentError, + message: "#{__MODULE__} expected key '#{key}' to be an atom" + ) + end + + defp expand_value({module, function, args}) + when is_atom(function) and is_list(args) do + apply(module, function, args) + end + + defp expand_value(value) when is_function(value) do + value.() + end + + defp expand_value(value), do: value +end diff --git a/lib/stripe/connect/account.ex b/lib/stripe/connect/account.ex index d7a861b1b..e1e958d33 100644 --- a/lib/stripe/connect/account.ex +++ b/lib/stripe/connect/account.ex @@ -20,18 +20,49 @@ defmodule Stripe.Account do cvc_failure: boolean } - @type legal_entity :: %{ - additional_owners: [legal_entity_additional_owner] | nil, - address: legal_entity_address, - address_kana: legal_entity_japan_address | nil, - address_kanji: legal_entity_japan_address | nil, + @type requirements :: %{ + current_deadline: Stripe.timestamp() | nil, + currently_due: Stripe.List.t(String.t()) | nil, + disabled_reason: String.t() | nil, + eventually_due: Stripe.List.t(String.t()) | nil, + past_due: Stripe.List.t(String.t()) | nil + } + + @type settings :: %{ + branding: map | nil, + card_payments: map | nil, + dashboard: map | nil, + payments: map | nil, + payouts: map | nil + } + + @type company :: %{ + address: Stripe.Types.address(), + address_kana: Stripe.Types.japan_address() | nil, + address_kanji: Stripe.Types.japan_address() | nil, + directors_provided: boolean | nil, + name: String.t() | nil, + name_kana: String.t() | nil, + name_kanji: String.t() | nil, + owners_provided: boolean | nil, + phone: String.t() | nil, + tax_id_provided: boolean | nil, + tax_id_registar: String.t(), + vat_id_provided: boolean | nil + } + + @type individual :: %{ + additional_owners: [individual_additional_owner] | nil, + address: Stripe.Types.address(), + address_kana: Stripe.Types.japan_address() | nil, + address_kanji: Stripe.Types.japan_address() | nil, business_name: String.t() | nil, business_name_kana: String.t() | nil, business_name_kanji: String.t() | nil, business_tax_id_provided: boolean, business_vat_id_provided: boolean, deleted: boolean | nil, - dob: legal_entity_dob, + dob: Stripe.Types.dob(), first_name: String.t() | nil, first_name_kana: String.t() | nil, first_name_kanji: String.t() | nil, @@ -40,52 +71,27 @@ defmodule Stripe.Account do last_name_kana: String.t() | nil, last_name_kanji: String.t() | nil, maiden_name: String.t() | nil, - personal_address: legal_entity_address, - personal_address_kana: legal_entity_japan_address | nil, - personal_address_kanji: legal_entity_japan_address | nil, + personal_address: Stripe.Types.address(), + personal_address_kana: Stripe.Types.japan_address() | nil, + personal_address_kanji: Stripe.Types.japan_address() | nil, personal_id_number_provided: boolean, phone_number: String.t() | nil, ssn_last_4_provided: String.t(), tax_id_registar: String.t(), type: String.t() | nil, - verification: legal_entity_verification + verification: individual_verification } - @type legal_entity_additional_owner :: %{ - address: legal_entity_address, - dob: legal_entity_dob, + @type individual_additional_owner :: %{ + address: Stripe.Types.address(), + dob: Stripe.Types.dob(), first_name: String.t() | nil, last_name: String.t() | nil, maiden_name: String.t() | nil, - verification: legal_entity_verification + verification: individual_verification } - @type legal_entity_address :: %{ - city: String.t() | nil, - country: String.t() | nil, - line1: String.t() | nil, - line2: String.t() | nil, - postal_code: String.t() | nil, - state: String.t() | nil - } - - @type legal_entity_dob :: %{ - day: 1..31 | nil, - month: 1..12 | nil, - year: pos_integer | nil - } - - @type legal_entity_japan_address :: %{ - city: String.t() | nil, - country: String.t() | nil, - line1: String.t() | nil, - line2: String.t() | nil, - postal_code: String.t() | nil, - state: String.t() | nil, - town: String.t() | nil - } - - @type legal_entity_verification :: %{ + @type individual_verification :: %{ details: String.t() | nil, details_code: String.t() | nil, document: Stripe.id() | Stripe.FileUpload.t() | nil, @@ -104,106 +110,97 @@ defmodule Stripe.Account do fields_needed: [String.t()] } + @type business_profile :: %{ + mcc: String.t() | nil, + name: String.t() | nil, + product_description: String.t() | nil, + support_address: %{ + city: String.t() | nil, + country: String.t() | nil, + line1: String.t() | nil, + line2: String.t() | nil, + postal_code: String.t() | nil, + state: String.t() | nil + }, + support_email: String.t() | nil, + support_phone: String.t() | nil, + support_url: String.t() | nil, + url: String.t() | nil + } + + @type capabilities :: %{ + card_payments: String.t() | nil, + legacy_payments: String.t() | nil, + platform_payments: String.t() | nil + } + @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), - business_logo: String.t() | nil, - business_name: String.t() | nil, - business_url: String.t() | nil, + business_profile: business_profile | nil, + business_type: String.t() | nil, + capabilities: capabilities | nil, charges_enabled: boolean, - created: Stripe.timestamp() | nil, + company: company | nil, country: String.t(), - debit_negative_balances: boolean, - decline_charge_on: decline_charge_on, + created: Stripe.timestamp() | nil, default_currency: String.t(), details_submitted: boolean, - display_name: String.t() | nil, email: String.t() | nil, external_accounts: Stripe.List.t(Stripe.BankAccount.t() | Stripe.Card.t()), - legal_entity: legal_entity, + individual: individual | nil, metadata: Stripe.Types.metadata(), - payout_schedule: Stripe.Types.transfer_schedule(), - payout_statement_descriptor: String.t() | nil, - payouts_enabled: boolean, - product_description: String.t() | nil, - statement_descriptor: String.t() | nil, - support_email: String.t() | nil, - support_phone: String.t() | nil, - timezone: String.t() | nil, - tos_acceptance: tos_acceptance, - type: String.t(), - verification: verification + payouts_enabled: boolean | nil, + requirements: requirements | nil, + settings: settings | nil, + tos_acceptance: tos_acceptance | nil, + type: String.t() } defstruct [ :id, :object, - :business_logo, - :business_name, - :business_url, + :business_profile, + :business_type, + :capabilities, :charges_enabled, + :company, :country, :created, - :debit_negative_balances, - :decline_charge_on, :default_currency, - :deleted, :details_submitted, - :display_name, :email, :external_accounts, - :legal_entity, + :individual, :metadata, - :payout_schedule, - :payout_statement_descriptor, :payouts_enabled, - :product_description, - :statement_descriptor, - :support_email, - :support_phone, - :timezone, + :requirements, + :settings, :tos_acceptance, - :transfers_enabled, - :type, - :verification + :type ] @singular_endpoint "account" @plural_endpoint "accounts" - @type create_params :: %{ - :type => String.t(), - optional(:account_token) => String.t(), - optional(:business_logo) => String.t(), - optional(:business_name) => String.t(), - optional(:business_primary_color) => String.t(), - optional(:business_url) => String.t(), - optional(:country) => String.t(), - optional(:debit_negative_balances) => boolean, - optional(:decline_charge_on) => decline_charge_on, - optional(:default_currency) => String.t(), - optional(:email) => String.t(), - optional(:external_account) => String.t(), - optional(:legal_entity) => legal_entity, - optional(:metadata) => Stripe.Types.metadata(), - optional(:payout_schedule) => Stripe.Types.transfer_schedule(), - optional(:payout_statement_descriptor) => String.t(), - optional(:product_description) => String.t(), - optional(:statement_descriptor) => String.t(), - optional(:support_email) => String.t(), - optional(:support_phone) => String.t(), - optional(:support_url) => String.t(), - optional(:tos_acceptance) => tos_acceptance - } - @doc """ Create an account. """ @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ + :type => String.t(), optional(:country) => String.t(), + optional(:account_token) => String.t(), + optional(:business_profile) => business_profile, + optional(:business_type) => String.t(), + optional(:company) => company, optional(:email) => String.t(), - optional(:type) => String.t() + optional(:external_account) => String.t(), + optional(:individual) => individual, + optional(:metadata) => Stripe.Types.metadata(), + optional(:requested_capabilities) => capabilities, + optional(:settings) => settings, + optional(:tos_acceptance) => tos_acceptance } def create(params, opts \\ []) do new_request(opts) @@ -240,30 +237,6 @@ defmodule Stripe.Account do |> make_request() end - @type update_params :: %{ - optional(:account_token) => String.t(), - optional(:business_logo) => String.t(), - optional(:business_name) => String.t(), - optional(:business_primary_color) => String.t(), - optional(:business_url) => String.t(), - optional(:country) => String.t(), - optional(:debit_negative_balances) => boolean, - optional(:decline_charge_on) => decline_charge_on, - optional(:default_currency) => String.t(), - optional(:email) => String.t(), - optional(:external_account) => String.t(), - optional(:legal_entity) => legal_entity, - optional(:metadata) => Stripe.Types.metadata(), - optional(:payout_schedule) => Stripe.Types.transfer_schedule(), - optional(:payout_statement_descriptor) => String.t(), - optional(:product_description) => String.t(), - optional(:statement_descriptor) => String.t(), - optional(:support_email) => String.t(), - optional(:support_phone) => String.t(), - optional(:support_url) => String.t(), - optional(:tos_acceptance) => tos_acceptance - } - @doc """ Update an account. @@ -271,24 +244,17 @@ defmodule Stripe.Account do """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - optional(:business_logo) => String.t(), - optional(:business_name) => String.t(), - optional(:business_primary_color) => String.t(), - optional(:business_url) => String.t(), - optional(:double_negative_balances) => boolean, - optional(:decline_charge_on) => decline_charge_on, + optional(:account_token) => String.t(), + optional(:business_profile) => business_profile, + optional(:business_type) => String.t(), + optional(:company) => company, optional(:default_currency) => String.t(), optional(:email) => String.t(), optional(:external_accounts) => String.t(), - optional(:legal_entity) => legal_entity, + optional(:individual) => individual, optional(:metadata) => Stripe.Types.metadata(), - optional(:payout_schedule) => Stripe.Types.transfer_schedule(), - optional(:payout_statement_descriptor) => String.t(), - optional(:product_description) => String.t(), - optional(:statement_descriptor) => String.t(), - optional(:support_phone) => String.t(), - optional(:support_email) => String.t(), - optional(:support_url) => String.t(), + optional(:requested_capabilities) => capabilities, + optional(:settings) => settings, optional(:tos_acceptance) => tos_acceptance } def update(id, params, opts \\ []) do diff --git a/lib/stripe/connect/application_fee.ex b/lib/stripe/connect/application_fee.ex index a851ef301..deb74f80a 100644 --- a/lib/stripe/connect/application_fee.ex +++ b/lib/stripe/connect/application_fee.ex @@ -59,13 +59,15 @@ defmodule Stripe.ApplicationFee do List all application fees """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} - when params: %{ - optional(:charge) => Stripe.id(), - optional(:created) => Stripe.date_query(), - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id() - } | %{} + when params: + %{ + optional(:charge) => Stripe.id(), + optional(:created) => Stripe.date_query(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } + | %{} def list(params \\ %{}, opts \\ []) do new_request(opts) |> prefix_expansions() diff --git a/lib/stripe/connect/country_spec.ex b/lib/stripe/connect/country_spec.ex index 4b2082f39..319b8a3c6 100644 --- a/lib/stripe/connect/country_spec.ex +++ b/lib/stripe/connect/country_spec.ex @@ -56,11 +56,13 @@ defmodule Stripe.CountrySpec do List all country specs. """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} - when params: %{ - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id() - } | %{} + when params: + %{ + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } + | %{} def list(params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) diff --git a/lib/stripe/connect/external_account.ex b/lib/stripe/connect/external_account.ex index 89b370c34..74cb4ce50 100644 --- a/lib/stripe/connect/external_account.ex +++ b/lib/stripe/connect/external_account.ex @@ -93,7 +93,8 @@ defmodule Stripe.ExternalAccount do Takes either `:bank_account` or `:card` to determine which object to list. """ - @spec list(atom, params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + @spec list(atom, params, Stripe.options()) :: + {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} when params: %{ :account => Stripe.id(), optional(:ending_before) => t | Stripe.id(), @@ -101,18 +102,21 @@ defmodule Stripe.ExternalAccount do optional(:starting_after) => t | Stripe.id() } def list(atom, params, opts \\ []) + def list(:bank_account, %{account: _} = params, opts) do endpoint = params |> accounts_plural_endpoint() params = params |> Map.put(:object, "bank_account") do_list(endpoint, params, opts) end + def list(:card, %{account: _} = params, opts) do endpoint = params |> accounts_plural_endpoint() params = params |> Map.put(:object, "card") do_list(endpoint, params, opts) end - @spec do_list(String.t, map, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + @spec do_list(String.t(), map, Stripe.options()) :: + {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} defp do_list(endpoint, params, opts) do new_request(opts) |> put_endpoint(endpoint) diff --git a/lib/stripe/connect/fee_refund.ex b/lib/stripe/connect/fee_refund.ex index 05b233ce3..74dd378bd 100644 --- a/lib/stripe/connect/fee_refund.ex +++ b/lib/stripe/connect/fee_refund.ex @@ -51,7 +51,8 @@ defmodule Stripe.FeeRefund do @doc """ Retrieve a application fee refund. """ - @spec retrieve(Stripe.id() | t, Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + @spec retrieve(Stripe.id() | t, Stripe.id() | t, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} def retrieve(id, fee_id, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint <> "/#{id}/refunds/#{fee_id}") @@ -64,9 +65,10 @@ defmodule Stripe.FeeRefund do Takes the `id` and a map of changes. """ - @spec update(Stripe.id() | t, Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + @spec update(Stripe.id() | t, Stripe.id() | t, params, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - optional(:metadata) => Stripe.Types.metadata(), + optional(:metadata) => Stripe.Types.metadata() } def update(id, fee_id, params, opts \\ []) do new_request(opts) @@ -79,7 +81,8 @@ defmodule Stripe.FeeRefund do @doc """ List all transfers. """ - @spec list(Stripe.id() | t, params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + @spec list(Stripe.id() | t, params, Stripe.options()) :: + {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} when params: %{ optional(:ending_before) => t | Stripe.id(), optional(:limit) => 1..100, diff --git a/lib/stripe/connect/oauth.ex b/lib/stripe/connect/oauth.ex index d3a89d677..394d6797c 100644 --- a/lib/stripe/connect/oauth.ex +++ b/lib/stripe/connect/oauth.ex @@ -11,7 +11,7 @@ defmodule Stripe.Connect.OAuth do Stripe API reference: https://stripe.com/docs/connect/reference """ - alias Stripe.Converter + alias Stripe.{Config, Converter} @callback token(code :: String.t()) :: {:ok, map} @callback authorize_url(map) :: String.t() @@ -188,12 +188,12 @@ defmodule Stripe.Connect.OAuth do @spec get_client_id() :: String.t() defp get_client_id() do - Application.get_env(:stripity_stripe, :connect_client_id) + Config.resolve(:connect_client_id) end @spec get_client_secret() :: String.t() defp get_client_secret() do - Application.get_env(:stripity_stripe, :api_key) + Config.resolve(:api_key) end @spec get_default_authorize_map() :: map diff --git a/lib/stripe/connect/person.ex b/lib/stripe/connect/person.ex new file mode 100644 index 000000000..7c80620f7 --- /dev/null +++ b/lib/stripe/connect/person.ex @@ -0,0 +1,236 @@ +defmodule Stripe.Person do + @moduledoc """ + Work with Stripe Connect person objects. + + You can: + + - Create a person + - Retrieve a person with a specified `id` + - Update a person + - Delete a person + - List all persons on an account + + Stripe API reference: https://stripe.com/docs/api/persons + """ + + use Stripe.Entity + import Stripe.Request + + @spec accounts_plural_endpoint(params) :: String.t() when params: %{:account => Stripe.id()} + defp accounts_plural_endpoint(%{account: id}) do + "accounts/#{id}/persons" + end + + @type relationship :: %{ + account_opener: boolean() | nil, + director: boolean() | nil, + owner: boolean() | nil, + percent_ownership: float() | nil, + title: String.t() | nil + } + + @type requirements :: %{ + currently_due: Stripe.List.t(String.t()) | nil, + eventually_due: Stripe.List.t(String.t()) | nil, + past_due: Stripe.List.t(String.t()) | nil + } + + @type verification :: %{ + details: String.t() | nil, + details_code: String.t() | nil, + document: verification_document() | nil, + status: String.t() | nil + } + + @type verification_document :: %{ + back: String.t() | nil, + details: String.t() | nil, + details_code: String.t() | nil, + front: String.t() | nil + } + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + account: Stripe.id(), + address: Stripe.Types.address() | nil, + address_kana: Stripe.Types.japan_address() | nil, + address_kanji: Stripe.Types.japan_address() | nil, + created: Stripe.timestamp() | nil, + dob: Stripe.Types.dob() | nil, + email: String.t() | nil, + first_name: String.t() | nil, + first_name_kana: String.t() | nil, + first_name_kanji: String.t() | nil, + gender: String.t() | nil, + id_number_provided: boolean(), + last_name: String.t() | nil, + last_name_kana: String.t() | nil, + last_name_kanji: String.t() | nil, + metadata: Stripe.Types.metadata(), + phone: String.t() | nil, + relationship: relationship() | nil, + requirements: requirements() | nil, + ssn_last_4_provided: boolean(), + verification: verification() | nil + } + + defstruct [ + :id, + :object, + :account, + :address, + :address_kana, + :address_kanji, + :created, + :dob, + :email, + :first_name, + :first_name_kana, + :first_name_kanji, + :gender, + :id_number_provided, + :last_name, + :last_name_kana, + :last_name_kanji, + :metadata, + :phone, + :relationship, + :requirements, + :ssn_last_4_provided, + :verification + ] + + @type create_params :: %{ + optional(:account) => String.t(), + optional(:address) => Stripe.Types.address(), + optional(:address_kana) => Stripe.Types.japan_address(), + optional(:address_kanji) => Stripe.Types.japan_address(), + optional(:dob) => Stripe.Types.dob(), + optional(:email) => String.t(), + optional(:first_name) => String.t(), + optional(:first_name_kana) => String.t(), + optional(:first_name_kanji) => String.t(), + optional(:gender) => String.t(), + optional(:id_number) => String.t(), + optional(:last_name) => String.t(), + optional(:last_name_kana) => String.t(), + optional(:last_name_kanji) => String.t(), + optional(:maiden_name) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:person_token) => String.t(), + optional(:phone) => String.t(), + optional(:relationship) => relationship(), + optional(:ssn_last_4) => String.t(), + optional(:verification) => verification() + } + + @doc """ + Create a person. + """ + @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: create_params() + def create(params, opts \\ []) do + endpoint = accounts_plural_endpoint(params) + updated_params = Map.delete(params, :account) + + new_request(opts) + |> put_endpoint(endpoint) + |> put_params(updated_params) + |> put_method(:post) + |> make_request() + end + + @doc """ + Retrieve a person with a specified `id`. + """ + @spec retrieve(String.t(), params) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: %{:account => String.t()} + def retrieve(id, %{account: _} = params, opts \\ []) do + endpoint = accounts_plural_endpoint(params) + + new_request(opts) + |> put_endpoint("#{endpoint}/#{id}") + |> put_method(:get) + |> make_request() + end + + @type update_params :: %{ + optional(:account) => String.t(), + optional(:address) => Stripe.Types.address(), + optional(:address_kana) => Stripe.Types.japan_address(), + optional(:address_kanji) => Stripe.Types.japan_address(), + optional(:dob) => Stripe.Types.dob(), + optional(:email) => String.t(), + optional(:first_name) => String.t(), + optional(:first_name_kana) => String.t(), + optional(:first_name_kanji) => String.t(), + optional(:gender) => String.t(), + optional(:id_number) => String.t(), + optional(:last_name) => String.t(), + optional(:last_name_kana) => String.t(), + optional(:last_name_kanji) => String.t(), + optional(:maiden_name) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:person_token) => String.t(), + optional(:phone) => String.t(), + optional(:relationship) => relationship(), + optional(:ssn_last_4) => String.t(), + optional(:verification) => verification() + } + + @doc """ + Update a person. + + Takes the `id` and a map of changes. + """ + @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: update_params() + def update(id, params, opts \\ []) do + endpoint = accounts_plural_endpoint(params) + updated_params = Map.delete(params, :account) + + new_request(opts) + |> put_endpoint(endpoint <> "/#{get_id!(id)}") + |> put_method(:post) + |> put_params(updated_params) + |> make_request() + end + + @doc """ + Delete a person. + """ + @spec delete(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: %{:account => Stripe.id()} + def delete(id, params, opts \\ []) do + endpoint = accounts_plural_endpoint(params) + + new_request(opts) + |> put_endpoint(endpoint <> "/#{get_id!(id)}") + |> put_method(:delete) + |> make_request() + end + + @doc """ + List all persons on an account. + """ + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + when params: %{ + :account => Stripe.id(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } + def list(params, opts \\ []) do + endpoint = accounts_plural_endpoint(params) + updated_params = Map.delete(params, :account) + + new_request(opts) + |> prefix_expansions() + |> put_endpoint(endpoint) + |> put_method(:get) + |> put_params(updated_params) + |> cast_to_id([:ending_before, :starting_after]) + |> make_request() + end +end diff --git a/lib/stripe/connect/recipient.ex b/lib/stripe/connect/recipient.ex index 5e2c71931..7f5887523 100644 --- a/lib/stripe/connect/recipient.ex +++ b/lib/stripe/connect/recipient.ex @@ -67,15 +67,15 @@ defmodule Stripe.Recipient do """ @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - :name => String.t(), - :type => String.t(), - optional(:bank_account) => Stripe.id() | Stripe.BankAccount.t(), - optional(:recipient) => Stripe.id() | Stripe.Card.t(), - optional(:description) => String.t(), - optional(:email) => String.t(), - optional(:metadata) => Stripe.Types.metadata(), - optional(:tax_id) => String.t() - } + :name => String.t(), + :type => String.t(), + optional(:bank_account) => Stripe.id() | Stripe.BankAccount.t(), + optional(:recipient) => Stripe.id() | Stripe.Card.t(), + optional(:description) => String.t(), + optional(:email) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:tax_id) => String.t() + } def create(%{name: _, type: _} = params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) @@ -102,15 +102,15 @@ defmodule Stripe.Recipient do """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - optional(:bank_account) => Stripe.id() | Stripe.BankAccount.t(), - optional(:card) => Stripe.id() | Stripe.Card.t(), - optional(:default_card) => Stripe.id() | Stripe.Card.t(), - optional(:description) => String.t(), - optional(:email) => String.t(), - optional(:metadata) => Stripe.Types.metadata(), - optional(:name) => String.t(), - optional(:tax_id) => String.t() - } + optional(:bank_account) => Stripe.id() | Stripe.BankAccount.t(), + optional(:card) => Stripe.id() | Stripe.Card.t(), + optional(:default_card) => Stripe.id() | Stripe.Card.t(), + optional(:description) => String.t(), + optional(:email) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:name) => String.t(), + optional(:tax_id) => String.t() + } def update(id, params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") @@ -135,13 +135,13 @@ defmodule Stripe.Recipient do """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} when params: %{ - optional(:created) => Stripe.timestamp(), - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id(), - optional(:type) => String.t(), - optional(:verified) => boolean - } + optional(:created) => Stripe.timestamp(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id(), + optional(:type) => String.t(), + optional(:verified) => boolean + } def list(params \\ %{}, opts \\ []) do new_request(opts) |> prefix_expansions() diff --git a/lib/stripe/connect/transfer_reversal.ex b/lib/stripe/connect/transfer_reversal.ex index a3ff8d278..e803f17ae 100644 --- a/lib/stripe/connect/transfer_reversal.ex +++ b/lib/stripe/connect/transfer_reversal.ex @@ -16,7 +16,9 @@ defmodule Stripe.TransferReversal do created: Stripe.timestamp(), currency: String.t(), description: boolean, + destination_payment_refund: Stripe.id() | Stripe.Refund.t() | nil, metadata: Stripe.Types.metadata(), + source_refund: Stripe.id() | Stripe.Refund.t() | nil, transfer: Stripe.id() | Stripe.Transfer.t() } @@ -28,7 +30,9 @@ defmodule Stripe.TransferReversal do :created, :currency, :description, + :destination_payment_refund, :metadata, + :source_refund, :transfer ] @@ -55,7 +59,8 @@ defmodule Stripe.TransferReversal do @doc """ Retrieve a transfer reversal. """ - @spec retrieve(Stripe.id() | t, Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + @spec retrieve(Stripe.id() | t, Stripe.id() | t, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} def retrieve(id, reversal_id, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint <> "/#{id}/reversals/#{reversal_id}") @@ -68,7 +73,8 @@ defmodule Stripe.TransferReversal do Takes the `id` and a map of changes. """ - @spec update(Stripe.id() | t, Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + @spec update(Stripe.id() | t, Stripe.id() | t, params, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} when params: %{ optional(:metadata) => Stripe.Types.metadata() } @@ -83,7 +89,8 @@ defmodule Stripe.TransferReversal do @doc """ List all transfers. """ - @spec list(Stripe.id() | t, params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + @spec list(Stripe.id() | t, params, Stripe.options()) :: + {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} when params: %{ optional(:ending_before) => t | Stripe.id(), optional(:limit) => 1..100, diff --git a/lib/stripe/converter.ex b/lib/stripe/converter.ex index 3db25a4ae..28f96efb7 100644 --- a/lib/stripe/converter.ex +++ b/lib/stripe/converter.ex @@ -19,30 +19,44 @@ defmodule Stripe.Converter do bank_account card charge + checkout.session country_spec coupon + credit_note customer discount dispute event external_account - file_upload + file invoice invoiceitem + issuing.authorization + issuing.card + issuing.card_details + issuing.cardholder + issuing.dispute + issuing.transaction line_item list oauth order order_return + payment_intent + payment_method payout plan product recipient refund + review sku source subscription subscription_item + subscription_schedule + tax_rate + tax_id transfer transfer_reversal token @@ -53,8 +67,12 @@ defmodule Stripe.Converter do @spec convert_value(any) :: any defp convert_value(%{"object" => object_name} = value) when is_binary(object_name) do case Enum.member?(@supported_objects, object_name) do - true -> convert_stripe_object(value) - false -> convert_map(value) + true -> + convert_stripe_object(value) + + false -> + warn_unknown_object(value) + convert_map(value) end end @@ -78,16 +96,16 @@ defmodule Stripe.Converter do processed_map = struct_keys |> Enum.reduce(%{}, fn key, acc -> - string_key = to_string(key) + string_key = to_string(key) - converted_value = - case string_key do - string_key when string_key in @no_convert_maps -> Map.get(value, string_key) - _ -> Map.get(value, string_key) |> convert_value() - end + converted_value = + case string_key do + string_key when string_key in @no_convert_maps -> Map.get(value, string_key) + _ -> Map.get(value, string_key) |> convert_value() + end - Map.put(acc, key, converted_value) - end) + Map.put(acc, key, converted_value) + end) |> module.__from_json__() struct(module, processed_map) @@ -96,6 +114,16 @@ defmodule Stripe.Converter do @spec convert_list(list) :: list defp convert_list(list), do: list |> Enum.map(&convert_value/1) + if Mix.env() == "prod" do + defp warn_unknown_object(_), do: :ok + else + defp warn_unknown_object(%{"object" => object_name} = value) do + require Logger + + Logger.warn("Unknown object received: #{object_name}") + end + end + if Mix.env() == "prod" do defp check_for_extra_keys(_, _), do: :ok else @@ -127,7 +155,7 @@ defmodule Stripe.Converter do details = "#{module_name}: #{inspect(extra_keys)}" message = "Extra keys were received but ignored when converting #{details}" - Logger.debug(message) + Logger.warn(message) end :ok diff --git a/lib/stripe/core_resources/balance.ex b/lib/stripe/core_resources/balance.ex index e145e276c..95336793e 100644 --- a/lib/stripe/core_resources/balance.ex +++ b/lib/stripe/core_resources/balance.ex @@ -55,7 +55,8 @@ defmodule Stripe.Balance do See the [Stripe docs](https://stripe.com/docs/api#balance_transaction_retrieve). """ - @spec retrieve_transaction(String.t(), Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + @spec retrieve_transaction(String.t(), Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} def retrieve_transaction(id, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint <> "/history/" <> id) diff --git a/lib/stripe/core_resources/charge.ex b/lib/stripe/core_resources/charge.ex index d3228e378..2fad4bb14 100644 --- a/lib/stripe/core_resources/charge.ex +++ b/lib/stripe/core_resources/charge.ex @@ -37,6 +37,13 @@ defmodule Stripe.Charge do predicate: String.t() } + @type billing_details :: %{ + email: String.t(), + address: String.t() | nil, + name: String.t(), + phone: String.t() | nil + } + @type card_info :: %{ exp_month: number, exp_year: number, @@ -52,6 +59,11 @@ defmodule Stripe.Charge do address_zip: String.t() | nil } + @type transfer_data :: %{ + :amount => non_neg_integer, + :destination => String.t() + } + @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), @@ -59,13 +71,14 @@ defmodule Stripe.Charge do amount_refunded: non_neg_integer, application: Stripe.id() | nil, application_fee: Stripe.id() | Stripe.ApplicationFee.t() | nil, + application_fee_amount: Stripe.id() | Stripe.ApplicationFee.t() | nil, balance_transaction: Stripe.id() | Stripe.BalanceTransaction.t() | nil, + billing_details: billing_details | nil, captured: boolean, created: Stripe.timestamp(), currency: String.t(), customer: Stripe.id() | Stripe.Customer.t() | nil, description: String.t() | nil, - destination: Stripe.id() | Stripe.Account.t() | nil, dispute: Stripe.id() | Stripe.Dispute.t() | nil, failure_code: Stripe.Error.card_error_code() | nil, failure_message: String.t() | nil, @@ -77,8 +90,12 @@ defmodule Stripe.Charge do order: Stripe.id() | Stripe.Order.t() | nil, outcome: charge_outcome | nil, paid: boolean, + payment_intent: Stripe.id() | Stripe.PaymentIntent.t() | nil, + payment_method: Stripe.id() | Stripe.PaymentMethod.t() | nil, + payment_method_details: map, receipt_email: String.t() | nil, receipt_number: String.t() | nil, + receipt_url: String.t() | nil, refunded: boolean, refunds: Stripe.List.t(Stripe.Refund.t()), review: Stripe.id() | Stripe.Review.t() | nil, @@ -88,6 +105,7 @@ defmodule Stripe.Charge do statement_descriptor: String.t() | nil, status: String.t(), transfer: Stripe.id() | Stripe.Transfer.t() | nil, + transfer_data: transfer_data | nil, transfer_group: String.t() | nil } @@ -98,13 +116,14 @@ defmodule Stripe.Charge do :amount_refunded, :application, :application_fee, + :application_fee_amount, :balance_transaction, + :billing_details, :captured, :created, :currency, :customer, :description, - :destination, :dispute, :failure_code, :failure_message, @@ -116,8 +135,12 @@ defmodule Stripe.Charge do :order, :outcome, :paid, + :payment_intent, + :payment_method, + :payment_method_details, :receipt_email, :receipt_number, + :receipt_url, :refunded, :refunds, :review, @@ -127,6 +150,7 @@ defmodule Stripe.Charge do :statement_descriptor, :status, :transfer, + :transfer_data, :transfer_group ] @@ -142,31 +166,29 @@ defmodule Stripe.Charge do See the [Stripe docs](https://stripe.com/docs/api#create_charge). """ @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - :amount => pos_integer, - :currency => String.t(), - optional(:application_fee) => non_neg_integer, - optional(:capture) => boolean, - optional(:description) => String.t(), - optional(:destination) => %{ - :account => Stripe.id() | Stripe.Account.t(), - optional(:amount) => non_neg_integer - }, - optional(:transfer_group) => String.t(), - optional(:on_behalf_of) => Stripe.id() | Stripe.Account.t(), - optional(:metadata) => map, - optional(:receipt_email) => String.t(), - optional(:shipping) => Stripe.Types.shipping(), - optional(:customer) => Stripe.id() | Stripe.Customer.t(), - optional(:source) => Stripe.id() | Stripe.Card.t() | card_info, - optional(:statement_descriptor) => String.t() - } | %{} + when params: + %{ + :amount => pos_integer, + :currency => String.t(), + optional(:application_fee_amount) => non_neg_integer, + optional(:capture) => boolean, + optional(:customer) => Stripe.id() | Stripe.Customer.t(), + optional(:description) => String.t(), + optional(:on_behalf_of) => Stripe.id() | Stripe.Account.t(), + optional(:metadata) => map, + optional(:receipt_email) => String.t(), + optional(:shipping) => Stripe.Types.shipping(), + optional(:source) => Stripe.id() | Stripe.Card.t() | card_info, + optional(:statement_descriptor) => String.t(), + optional(:transfer_data) => transfer_data, + optional(:transfer_group) => String.t() + } + | %{} def create(params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) |> put_params(params) |> put_method(:post) - |> cast_path_to_id([:destination, :account]) |> cast_to_id([:on_behalf_of, :customer, :source]) |> make_request() end @@ -203,15 +225,17 @@ defmodule Stripe.Charge do See the [Stripe docs](https://stripe.com/docs/api#update_charge). """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:customer) => Stripe.id() | Stripe.Customer.t(), - optional(:description) => String.t(), - optional(:fraud_details) => user_fraud_report, - optional(:metadata) => Stripe.Types.metadata(), - optional(:receipt_email) => String.t(), - optional(:shipping) => Stripe.Types.shipping(), - optional(:transfer_group) => String.t() - } | %{} + when params: + %{ + optional(:customer) => Stripe.id() | Stripe.Customer.t(), + optional(:description) => String.t(), + optional(:fraud_details) => user_fraud_report, + optional(:metadata) => Stripe.Types.metadata(), + optional(:receipt_email) => String.t(), + optional(:shipping) => Stripe.Types.shipping(), + optional(:transfer_group) => String.t() + } + | %{} def update(id, params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") @@ -237,12 +261,11 @@ defmodule Stripe.Charge do {:ok, t} | {:error, Stripe.Error.t()} when params: %{ optional(:amount) => non_neg_integer, - optional(:application_fee) => non_neg_integer, - optional(:destination) => %{ - optional(:amount) => non_neg_integer - }, + optional(:application_fee_amount) => non_neg_integer, optional(:receipt_email) => String.t(), - optional(:statement_descriptor) => String.t() + optional(:statement_descriptor) => String.t(), + optional(:transfer_data) => transfer_data, + optional(:transfer_group) => String.t() } def capture(id, params, opts) do new_request(opts) diff --git a/lib/stripe/core_resources/customer.ex b/lib/stripe/core_resources/customer.ex index 605d60a7b..83c13685d 100644 --- a/lib/stripe/core_resources/customer.ex +++ b/lib/stripe/core_resources/customer.ex @@ -29,8 +29,12 @@ defmodule Stripe.Customer do discount: Stripe.Discount.t() | nil, email: String.t() | nil, invoice_prefix: String.t() | nil, + invoice_settings: Stripe.Invoice.invoice_settings() | nil, livemode: boolean, metadata: Stripe.Types.metadata(), + name: String.t(), + phone: String.t(), + preferred_locales: list(String.t()), shipping: Stripe.Types.shipping() | nil, sources: Stripe.List.t(Stripe.Source.t()), subscriptions: Stripe.List.t(Stripe.Subscription.t()), @@ -55,8 +59,12 @@ defmodule Stripe.Customer do :discount, :email, :invoice_prefix, + :invoice_settings, :livemode, :metadata, + :name, + :phone, + :preferred_locales, :shipping, :sources, :subscriptions, @@ -81,6 +89,7 @@ defmodule Stripe.Customer do optional(:description) => String.t(), optional(:email) => String.t(), optional(:invoice_prefix) => String.t(), + optional(:invoice_settings) => Stripe.Invoice.invoice_settings(), optional(:metadata) => Stripe.Types.metadata(), optional(:shipping) => Stripe.Types.shipping(), optional(:source) => Stripe.Source.t(), @@ -120,6 +129,7 @@ defmodule Stripe.Customer do optional(:description) => String.t(), optional(:email) => String.t(), optional(:invoice_prefix) => String.t(), + optional(:invoice_settings) => Stripe.Invoice.invoice_settings(), optional(:metadata) => Stripe.Types.metadata(), optional(:shipping) => Stripe.Types.shipping(), optional(:source) => Stripe.Source.t(), diff --git a/lib/stripe/core_resources/dispute.ex b/lib/stripe/core_resources/dispute.ex index 78176cc7e..7ee1782ae 100644 --- a/lib/stripe/core_resources/dispute.ex +++ b/lib/stripe/core_resources/dispute.ex @@ -109,11 +109,13 @@ defmodule Stripe.Dispute do Update a dispute. """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:evidence) => dispute_evidence, - optional(:metadata) => Stripe.Types.metadata(), - optional(:submit) => boolean - } | %{} + when params: + %{ + optional(:evidence) => dispute_evidence, + optional(:metadata) => Stripe.Types.metadata(), + optional(:submit) => boolean + } + | %{} def update(id, params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") @@ -137,12 +139,14 @@ defmodule Stripe.Dispute do List all disputes. """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} - when params: %{ - optional(:created) => String.t() | Stripe.date_query(), - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id() - } | %{} + when params: + %{ + optional(:created) => String.t() | Stripe.date_query(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } + | %{} def list(params \\ %{}, opts \\ []) do new_request(opts) |> prefix_expansions() diff --git a/lib/stripe/core_resources/event.ex b/lib/stripe/core_resources/event.ex index 82d1a0253..8b4b5f853 100644 --- a/lib/stripe/core_resources/event.ex +++ b/lib/stripe/core_resources/event.ex @@ -87,14 +87,16 @@ defmodule Stripe.Event do List all events, going back up to 30 days. """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} - when params: %{ - optional(:created) => Stripe.date_query(), - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id(), - optional(:type) => String.t(), - optional(:types) => list - } | %{} + when params: + %{ + optional(:created) => Stripe.date_query(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id(), + optional(:type) => String.t(), + optional(:types) => list + } + | %{} def list(params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) diff --git a/lib/stripe/core_resources/file_upload.ex b/lib/stripe/core_resources/file_upload.ex index de45d4245..51e9716c0 100644 --- a/lib/stripe/core_resources/file_upload.ex +++ b/lib/stripe/core_resources/file_upload.ex @@ -8,22 +8,22 @@ defmodule Stripe.FileUpload do - Retrieve a file - List all files - Stripe API reference: https://stripe.com/docs/api#file_uploads + Stripe API reference: https://stripe.com/docs/api/files """ use Stripe.Entity import Stripe.Request @type t :: %__MODULE__{ - id: Stripe.id, - object: String.t, - created: Stripe.timestamp, - filename: String.t | nil, - purpose: String.t, - size: integer, - type: String.t | nil, - url: String.t | nil - } + id: Stripe.id(), + object: String.t(), + created: Stripe.timestamp(), + filename: String.t() | nil, + purpose: String.t(), + size: integer, + type: String.t() | nil, + url: String.t() | nil + } defstruct [ :id, @@ -43,7 +43,7 @@ defmodule Stripe.FileUpload do Takes the filepath and the purpose. """ - @spec create(map, Keyword.t) :: {:ok, t} | {:error, Stripe.Error.t} + @spec create(map, Keyword.t()) :: {:ok, t} | {:error, Stripe.Error.t()} def create(%{file: _, purpose: _} = params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) @@ -55,7 +55,7 @@ defmodule Stripe.FileUpload do @doc """ Retrieve a file_upload. """ - @spec retrieve(Stripe.id | t, Stripe.options) :: {:ok, t} | {:error, Stripe.Error.t} + @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)}") @@ -66,13 +66,15 @@ defmodule Stripe.FileUpload do @doc """ List all file uploads, going back up to 30 days. """ - @spec list(params, Stripe.options) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t} - when params: %{ - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:purpose) => String.t(), - optional(:starting_after) => t | Stripe.id() - } | %{} + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:purpose) => String.t(), + optional(:starting_after) => t | Stripe.id() + } + | %{} def list(params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) diff --git a/lib/stripe/core_resources/payment_intent.ex b/lib/stripe/core_resources/payment_intent.ex new file mode 100644 index 000000000..77d1815d5 --- /dev/null +++ b/lib/stripe/core_resources/payment_intent.ex @@ -0,0 +1,297 @@ +defmodule Stripe.PaymentIntent do + @moduledoc """ + Work with [Stripe `payment_intent` objects](https://stripe.com/docs/api/payment_intents). + You can: + - [Create a payment_intent](https://stripe.com/docs/api/payment_intents/create) + - [Retrieve a payment_intent](https://stripe.com/docs/api/payment_intents/retrieve) + - [Update a payment_intent](https://stripe.com/docs/api/payment_intents/update) + - [Confirm a payment_intent](https://stripe.com/docs/api/payment_intents/confirm) + - [Capture a payment_intent](https://stripe.com/docs/api/payment_intents/capture) + - [Cancel a payment_intent](https://stripe.com/docs/api/payment_intents/cancel) + - [List all payment_intent](https://stripe.com/docs/api/payment_intents/list) + """ + + use Stripe.Entity + import Stripe.Request + require Stripe.Util + + @type last_payment_error :: %{ + type: String.t(), + charge: String.t(), + code: String.t(), + decline_code: String.t(), + doc_url: String.t(), + message: String.t(), + param: String.t(), + payment_intent: Stripe.PaymentIntent.t() | map, + source: Stripe.Card.t() | map + } + + @type next_action :: %{ + redirect_to_url: redirect_to_url | nil, + type: String.t(), + use_stripe_sdk: map | nil + } + + @type redirect_to_url :: %{ + return_url: String.t(), + url: String.t() + } + + @type transfer_data :: %{ + :destination => String.t() + } + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + amount: non_neg_integer, + amount_capturable: non_neg_integer, + amount_received: non_neg_integer, + application: Stripe.id() | nil, + application_fee_amount: non_neg_integer | nil, + canceled_at: Stripe.timestamp() | nil, + cancellation_reason: String.t() | nil, + capture_method: String.t(), + charges: Stripe.List.t(Stripe.Charge.t()), + client_secret: String.t(), + confirmation_method: String.t(), + created: Stripe.timestamp(), + currency: String.t(), + customer: Stripe.id() | Stripe.Customer.t() | nil, + description: String.t() | nil, + invoice: Stripe.id() | Stripe.Invoice.t() | nil, + last_payment_error: last_payment_error | nil, + livemode: boolean, + metadata: Stripe.Types.metadata(), + next_action: next_action | nil, + on_behalf_of: Stripe.id() | Stripe.Account.t() | nil, + payment_method: Stripe.id() | Stripe.PaymentMethod.t() | nil, + payment_method_types: list(String.t()), + receipt_email: String.t() | nil, + review: Stripe.id() | Stripe.Review.t() | nil, + shipping: Stripe.Types.shipping() | nil, + source: Stripe.Card.t() | map, + statement_descriptor: String.t() | nil, + status: String.t(), + transfer_data: transfer_data | nil, + transfer_group: String.t() | nil + } + + defstruct [ + :id, + :object, + :amount, + :amount_capturable, + :amount_received, + :application, + :application_fee_amount, + :canceled_at, + :cancellation_reason, + :capture_method, + :charges, + :client_secret, + :confirmation_method, + :created, + :currency, + :customer, + :description, + :invoice, + :last_payment_error, + :livemode, + :metadata, + :next_action, + :on_behalf_of, + :payment_method, + :payment_method_types, + :receipt_email, + :review, + :shipping, + :source, + :statement_descriptor, + :status, + :transfer_data, + :transfer_group + ] + + @plural_endpoint "payment_intents" + + @doc """ + Create a payment intent. + See the [Stripe docs](https://stripe.com/docs/api/payment_intents/create). + """ + @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + :amount => pos_integer, + :currency => String.t(), + :payment_method_types => [String.t()], + optional(:application_fee_amount) => non_neg_integer, + optional(:capture_method) => String.t(), + optional(:confirm) => boolean, + optional(:customer) => Stripe.id() | Stripe.Customer.t(), + optional(:description) => String.t(), + optional(:metadata) => map, + optional(:on_behalf_of) => Stripe.id() | Stripe.Account.t(), + optional(:receipt_email) => String.t(), + optional(:return_url) => String.t(), + optional(:save_payment_method) => boolean, + optional(:shipping) => Stripe.Types.shipping(), + optional(:source) => Stripe.id() | Stripe.Card.t(), + optional(:statement_descriptor) => String.t(), + optional(:transfer_data) => transfer_data, + optional(:transfer_group) => String.t() + } + | %{} + def create(params, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint) + |> put_params(params) + |> put_method(:post) + |> cast_to_id([:on_behalf_of, :customer, :source]) + |> make_request() + end + + @doc """ + Retrieves the details of a PaymentIntent that has previously been created. + Client-side retrieval using a publishable key is allowed when the client_secret is provided in the query string. + When retrieved with a publishable key, only a subset of properties will be returned. Please refer to the payment intent object reference for more details. + See the [Stripe docs](https://stripe.com/docs/api/payment_intents/retrieve). + """ + @spec retrieve(Stripe.id() | t, params, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:client_secret) => String.t() + } + | %{} + def retrieve(id, params, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") + |> put_params(params) + |> put_method(:get) + |> make_request() + end + + @doc """ + Updates a PaymentIntent object. + See the [Stripe docs](https://stripe.com/docs/api/payment_intents/update). + """ + @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:amount) => non_neg_integer, + optional(:application_fee_amount) => non_neg_integer, + optional(:currency) => String.t(), + optional(:customer) => Stripe.id() | Stripe.Customer.t(), + optional(:description) => String.t(), + optional(:metadata) => map, + optional(:payment_method_types) => [Stripe.id()], + optional(:receipt_email) => String.t(), + optional(:save_payment_method) => boolean, + optional(:shipping) => Stripe.Types.shipping(), + optional(:source) => Stripe.id() | Stripe.Card.t(), + optional(:transfer_group) => 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 """ + Confirm that your customer intends to pay with current or provided source. Upon confirmation, + the PaymentIntent will attempt to initiate a payment. + If the selected source requires additional authentication steps, the PaymentIntent will transition to + the requires_action status and suggest additional actions via next_source_action. + If payment fails, the PaymentIntent will transition to the requires_payment_method status. + If payment succeeds, the PaymentIntent will transition to the succeeded status (or requires_capture, + if capture_method is set to manual). Read the expanded documentation to learn more about server-side confirmation. + See the [Stripe docs](https://stripe.com/docs/api/payment_intents/confirm). + """ + @spec confirm(Stripe.id() | t, params, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:client_secret) => String.t(), + optional(:receipt_email) => String.t(), + optional(:return_url) => String.t(), + optional(:save_payment_method) => boolean, + optional(:shipping) => Stripe.Types.shipping(), + optional(:source) => Stripe.id() | Stripe.Card.t() + } + | %{} + def confirm(id, params, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}" <> "/confirm") + |> put_method(:post) + |> put_params(params) + |> make_request() + end + + @doc """ + Capture the funds of an existing uncaptured PaymentIntent where required_action="requires_capture". + Uncaptured PaymentIntents will be canceled exactly seven days after they are created. + See the [Stripe docs](https://stripe.com/docs/api/payment_intents/capture). + """ + @spec capture(Stripe.id() | t, params, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:amount_to_capture) => non_neg_integer, + optional(:application_fee_amount) => non_neg_integer + } + | %{} + def capture(id, params, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/capture") + |> put_params(params) + |> put_method(:post) + |> make_request() + end + + @doc """ + A PaymentIntent object can be canceled when it is in one of these statuses: requires_payment_method, + requires_capture, requires_confirmation, requires_action. + Once canceled, no additional charges will be made by the PaymentIntent and any operations on the PaymentIntent will fail with an error. + For PaymentIntents with status='requires_capture', the remaining amount_capturable will automatically be refunded. + See the [Stripe docs](https://stripe.com/docs/api/payment_intents/cancel). + """ + @spec cancel(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:cancellation_reason) => String.t() + } + | %{} + def cancel(id, params, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}" <> "/cancel") + |> put_method(:post) + |> put_params(params) + |> make_request() + end + + @doc """ + Returns a list of PaymentIntents. + See the [Stripe docs](https://stripe.com/docs/api/payment_intents/list). + """ + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + when params: %{ + optional(:created) => Stripe.date_query(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } + def list(params \\ %{}, opts \\ []) do + new_request(opts) + |> prefix_expansions() + |> put_endpoint(@plural_endpoint) + |> put_method(:get) + |> put_params(params) + |> cast_to_id([:ending_before, :starting_after]) + |> make_request() + end +end diff --git a/lib/stripe/core_resources/payout.ex b/lib/stripe/core_resources/payout.ex index bbebe34e1..c94490a13 100644 --- a/lib/stripe/core_resources/payout.ex +++ b/lib/stripe/core_resources/payout.ex @@ -68,16 +68,19 @@ defmodule Stripe.Payout do See the [Stripe docs](https://stripe.com/docs/api#create_payout). """ @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - :amount => pos_integer, - :currency => String.t(), - optional(:description) => String.t(), - optional(:destination) => Stripe.id() | Stripe.Card.t() | Stripe.BankAccount.t() | String.t(), - optional(:metadata) => Stripe.Types.metadata(), - optional(:method) => String.t(), - optional(:source_type) => String.t(), - optional(:statement_descriptor) => String.t() - } | %{} + when params: + %{ + :amount => pos_integer, + :currency => String.t(), + optional(:description) => String.t(), + optional(:destination) => + Stripe.id() | Stripe.Card.t() | Stripe.BankAccount.t() | String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:method) => String.t(), + optional(:source_type) => String.t(), + optional(:statement_descriptor) => String.t() + } + | %{} def create(params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) @@ -112,9 +115,11 @@ defmodule Stripe.Payout do See the [Stripe docs](https://stripe.com/docs/api#update_payout). """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:metadata) => Stripe.Types.metadata() - } | %{} + when params: + %{ + optional(:metadata) => Stripe.Types.metadata() + } + | %{} def update(id, params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") @@ -132,15 +137,17 @@ defmodule Stripe.Payout do See the [Stripe docs](https://stripe.com/docs/api#list_payouts). """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} - when params: %{ - optional(:arrival_date) => Stripe.date_query(), - optional(:created) => Stripe.date_query(), - optional(:destination) => String.t(), - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id(), - optional(:status) => String.t() - } | %{} + when params: + %{ + optional(:arrival_date) => Stripe.date_query(), + optional(:created) => Stripe.date_query(), + optional(:destination) => String.t(), + 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() diff --git a/lib/stripe/core_resources/refund.ex b/lib/stripe/core_resources/refund.ex index dfaefdd01..191983443 100644 --- a/lib/stripe/core_resources/refund.ex +++ b/lib/stripe/core_resources/refund.ex @@ -26,7 +26,9 @@ defmodule Stripe.Refund do payment: Stripe.id() | Stripe.Charge.t() | nil, reason: String.t() | nil, receipt_number: String.t() | nil, - status: String.t() | nil + source_transfer_reversal: Stripe.id() | Stripe.TransferReversal.t() | nil, + status: String.t() | nil, + transfer_reversal: Stripe.id() | Stripe.TransferReversal.t() | nil } defstruct [ @@ -43,7 +45,9 @@ defmodule Stripe.Refund do :payment, :reason, :receipt_number, - :status + :source_transfer_reversal, + :status, + :transfer_reversal ] @plural_endpoint "refunds" @@ -67,14 +71,16 @@ defmodule Stripe.Refund do See the [Stripe docs](https://stripe.com/docs/api#create_refund). """ @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - :charge => Stripe.Charge.t() | Stripe.id(), - optional(:amount) => pos_integer, - optional(:metadata) => Stripe.Types.metadata(), - optional(:reason) => String.t(), - optional(:refund_application_fee) => boolean, - optional(:reverse_transfer) => boolean - } | %{} + when params: + %{ + :charge => Stripe.Charge.t() | Stripe.id(), + optional(:amount) => pos_integer, + optional(:metadata) => Stripe.Types.metadata(), + optional(:reason) => String.t(), + optional(:refund_application_fee) => boolean, + optional(:reverse_transfer) => boolean + } + | %{} def create(params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) @@ -110,9 +116,11 @@ defmodule Stripe.Refund do See the [Stripe docs](https://stripe.com/docs/api#update_refund). """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:metadata) => Stripe.Types.metadata() - } | %{} + when params: + %{ + optional(:metadata) => Stripe.Types.metadata() + } + | %{} def update(id, params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") @@ -132,12 +140,14 @@ defmodule Stripe.Refund do See the [Stripe docs](https://stripe.com/docs/api#list_refunds). """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} - when params: %{ - optional(:charget) => Stripe.id() | Stripe.Charge.t(), - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id() - } | %{} + when params: + %{ + optional(:charget) => Stripe.id() | Stripe.Charge.t(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } + | %{} def list(params \\ %{}, opts \\ []) do new_request(opts) |> prefix_expansions() diff --git a/lib/stripe/core_resources/tax_id.ex b/lib/stripe/core_resources/tax_id.ex new file mode 100644 index 000000000..c702546f5 --- /dev/null +++ b/lib/stripe/core_resources/tax_id.ex @@ -0,0 +1,150 @@ +defmodule Stripe.TaxID do + @moduledoc """ + You can add one or multiple tax IDs to a customer. A customer's tax IDs are + displayed on invoices and credit notes issued for the customer. + + See [Stripe Tax IDs docs](https://stripe.com/docs/api/customer_tax_ids) + """ + + use Stripe.Entity + import Stripe.Request + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + created: Stripe.timestamp(), + country: String.t(), + customer: Stripe.id() | Stripe.Customer.t() | nil, + livemode: boolean, + type: String.t(), + value: String.t(), + verification: tax_info_verification | nil + } + + @type tax_info_verification :: %{ + status: String.t() | nil, + verified_name: String.t() | nil, + verified_address: String.t() | nil + } + + defstruct [ + :id, + :object, + :created, + :country, + :customer, + :livemode, + :type, + :value, + :verification + ] + + defp plural_endpoint(%{customer: id}) do + "customers/" <> id <> "/tax_ids" + end + + @doc """ + Creates a new `TaxID` object for a customer. + + ### Example + + Stripe.TaxId.create(%{customer: "cus_FDVoXj36NmFrao", type: "eu_vat", value: "DE123456789"}) + + Stripe.TaxId.create(%{customer: %Stripe.Customer{id: "cus_FDVoXj36NmFrao"}, type: "eu_vat", value: "DE123456789"}) + + See [Stripe docs](https://stripe.com/docs/api/customer_tax_ids/create) + """ + @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + :customer => Stripe.id(), + :type => String.t(), + :value => String.t() + } + | %{} + def create(params, opts \\ []) do + updated_params = + params + |> Map.delete(:customer) + + new_request(opts) + |> put_endpoint(plural_endpoint(params)) + |> put_method(:post) + |> put_params(updated_params) + |> make_request() + end + + @doc """ + Retrieves the `TaxID` object with the given identifier. + + ### Example + + Stripe.TaxId.retrieve("txi_123456789", %{customer: "cus_FDVoXj36NmFrao")}) + + Stripe.TaxId.retrieve("txi_123456789", %{customer: %Stripe.Customer{id: "cus_FDVoXj36NmFrao"}}) + + See [Stripe docs](https://stripe.com/docs/api/customer_tax_ids/retrieve) + """ + @spec retrieve(Stripe.id() | t, map, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + def retrieve(id, %{customer: _} = params, opts \\ []) do + endpoint = params |> plural_endpoint() + + new_request(opts) + |> put_endpoint(endpoint <> "/#{get_id!(id)}") + |> put_method(:get) + |> make_request() + end + + @doc """ + Deletes an existing `TaxID` object. + + ### Example + + Stripe.TaxId.delete("txi_123456789", %{customer: "cus_FDVoXj36NmFrao")}) + + Stripe.TaxId.delete("txi_123456789", %{customer: %Stripe.Customer{id: "cus_FDVoXj36NmFrao"}}) + + Stripe.TaxId.delete(%Stripe.TaxID{id: "txi_123456789"}, %{customer: "cus_FDVoXj36NmFrao"}) + + See [Stripe docs](https://stripe.com/docs/api/customer_tax_ids/delete) + """ + @spec delete(Stripe.id() | t, map, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + def delete(id, %{customer: _} = params, opts \\ []) do + endpoint = plural_endpoint(params) + + new_request(opts) + |> put_endpoint(endpoint <> "/#{get_id!(id)}") + |> put_method(:delete) + |> make_request() + end + + @doc """ + Returns a list of tax IDs for a customer. + + ### Example + + Stripe.TaxId.list(%{customer: "cus_FDVoXj36NmFrao")}) + + See [Stripe docs](https://stripe.com/docs/api/customer_tax_ids/list) + """ + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + when params: %{ + :customer => Stripe.id() | Stripe.Customer.t(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } + def list(%{customer: _} = params, opts \\ []) do + updated_params = + params + |> Map.delete(:customer) + + new_request(opts) + |> prefix_expansions() + |> put_endpoint(plural_endpoint(params)) + |> put_method(:get) + |> put_params(updated_params) + |> cast_to_id([:customer, :ending_before, :starting_after]) + |> make_request() + end +end diff --git a/lib/stripe/ephemeral_key.ex b/lib/stripe/ephemeral_key.ex index 25618f353..c5b82d018 100644 --- a/lib/stripe/ephemeral_key.ex +++ b/lib/stripe/ephemeral_key.ex @@ -38,8 +38,8 @@ defmodule Stripe.EphemeralKey do """ @spec create(params, String.t(), Keyword.t()) :: {:ok, t} | {:error, %Stripe.Error{}} when params: %{ - :customer => Stripe.id(), - } + :customer => Stripe.id() + } def create(params, api_version, opts \\ []) do new_request(opts, %{"Stripe-Version": api_version}) |> put_endpoint(@plural_endpoint) diff --git a/lib/stripe/issuing/authorization.ex b/lib/stripe/issuing/authorization.ex new file mode 100644 index 000000000..046d23360 --- /dev/null +++ b/lib/stripe/issuing/authorization.ex @@ -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 diff --git a/lib/stripe/issuing/card.ex b/lib/stripe/issuing/card.ex new file mode 100644 index 000000000..087963a24 --- /dev/null +++ b/lib/stripe/issuing/card.ex @@ -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()} + 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 diff --git a/lib/stripe/issuing/card_details.ex b/lib/stripe/issuing/card_details.ex new file mode 100644 index 000000000..46568ca60 --- /dev/null +++ b/lib/stripe/issuing/card_details.ex @@ -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 + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}" <> "/details") + |> put_method(:get) + |> make_request() + end +end diff --git a/lib/stripe/issuing/cardholder.ex b/lib/stripe/issuing/cardholder.ex new file mode 100644 index 000000000..0762ec6a0 --- /dev/null +++ b/lib/stripe/issuing/cardholder.ex @@ -0,0 +1,137 @@ +defmodule Stripe.Issuing.Cardholder do + @moduledoc """ + Work with Stripe Issuing cardholder objects. + + You can: + + - Create a cardholder + - Retrieve a cardholder + - Update a cardholder + + Stripe API reference: https://stripe.com/docs/api/issuing/cardholders + """ + + use Stripe.Entity + import Stripe.Request + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + authorization_controls: Stripe.Issuing.Types.authorization_controls() | nil, + billing: Stripe.Issuing.Types.billing(), + created: Stripe.timestamp(), + email: String.t() | nil, + is_default: boolean | nil, + livemode: boolean, + metadata: Stripe.Types.metadata(), + name: String.t(), + phone_number: String.t() | nil, + status: String.t() | nil, + type: atom() | String.t() + } + + defstruct [ + :id, + :object, + :authorization_controls, + :billing, + :created, + :email, + :is_default, + :livemode, + :metadata, + :name, + :phone_number, + :status, + :type + ] + + @plural_endpoint "issuing/cardholders" + + @doc """ + Create a cardholder. + """ + @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + :billing => Stripe.Issuing.Types.billing(), + :name => String.t(), + :type => :individual | :business_entity, + optional(:authorization_controls) => + Stripe.Issuing.Types.authorization_controls(), + optional(:email) => String.t(), + optional(:is_default) => boolean, + optional(:metadata) => Stripe.Types.metadata(), + optional(:phone_number) => String.t(), + 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 cardholder. + """ + @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 cardholder. + """ + @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(:email) => String.t(), + optional(:is_default) => boolean, + optional(:metadata) => Stripe.Types.metadata(), + optional(:phone_number) => String.t(), + 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 cardholders. + """ + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:created) => String.t() | Stripe.date_query(), + optional(:email) => String.t(), + optional(:ending_before) => t | Stripe.id(), + optional(:is_default) => boolean, + optional(:limit) => 1..100, + optional(:phone_number) => 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([:ending_before, :starting_after]) + |> make_request() + end +end diff --git a/lib/stripe/issuing/dispute.ex b/lib/stripe/issuing/dispute.ex new file mode 100644 index 000000000..4a6d16c13 --- /dev/null +++ b/lib/stripe/issuing/dispute.ex @@ -0,0 +1,130 @@ +defmodule Stripe.Issuing.Dispute do + @moduledoc """ + Work with Stripe Issuing dispute objects. + + You can: + + - Create a dispute + - Retrieve a dispute + - Update a dispute + - List all disputes + + Stripe API reference: https://stripe.com/docs/api/issuing/disputes + """ + + use Stripe.Entity + import Stripe.Request + + @type evidence :: %{ + fraudulent: evidence_detail() | nil, + other: evidence_detail() | nil + } + + @type evidence_detail :: %{ + dispute_explanation: String.t(), + uncategorized_file: String.t() + } + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + amount: integer, + created: Stripe.timestamp(), + currency: String.t() | nil, + disputed_transaction: Stripe.id() | Stripe.Issuing.Transaction.t(), + evidence: evidence(), + livemode: boolean, + metadata: Stripe.Types.metadata(), + reason: atom() | String.t(), + status: String.t() + } + + defstruct [ + :id, + :object, + :amount, + :created, + :currency, + :disputed_transaction, + :evidence, + :livemode, + :metadata, + :reason, + :status + ] + + @plural_endpoint "issuing/disputes" + + @doc """ + Create a dispute. + """ + @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + :disputed_transaction => Stripe.id() | Stripe.Issuing.Transaction.t(), + :reason => :other | :fradulent, + optional(:amount) => non_neg_integer, + optional(:evidence) => evidence(), + optional(:metadata) => Stripe.Types.metadata() + } + | %{} + def create(params, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint) + |> put_params(params) + |> put_method(:post) + |> cast_to_id([:disputed_transaction]) + |> make_request() + end + + @doc """ + Retrieve a dispute. + """ + @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 dispute. + """ + @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 """ + List all disputes. + """ + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:created) => String.t() | Stripe.date_query(), + optional(:disputed_transaction) => Stripe.Issuing.Transaction.t() | Stripe.id(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } + | %{} + def list(params \\ %{}, opts \\ []) do + new_request(opts) + |> prefix_expansions() + |> put_endpoint(@plural_endpoint) + |> put_method(:get) + |> put_params(params) + |> cast_to_id([:disputed_transaction, :ending_before, :starting_after]) + |> make_request() + end +end diff --git a/lib/stripe/issuing/transaction.ex b/lib/stripe/issuing/transaction.ex new file mode 100644 index 000000000..006b7f518 --- /dev/null +++ b/lib/stripe/issuing/transaction.ex @@ -0,0 +1,106 @@ +defmodule Stripe.Issuing.Transaction do + @moduledoc """ + Work with Stripe Issuing transaction objects. + + You can: + + - Retrieve a transaction + - Update a transaction + - List all transactions + + Stripe API reference: https://stripe.com/docs/api/issuing/transactions + """ + + use Stripe.Entity + import Stripe.Request + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + amount: integer, + authorization: Stripe.id() | Stripe.Issuing.Authorization.t(), + balance_transaction: String.t(), + card: Stripe.id() | Stripe.Issuing.Card.t(), + cardholder: Stripe.id() | Stripe.Issuing.Cardholder.t(), + created: Stripe.timestamp(), + currency: String.t() | nil, + dispute: Stripe.id() | Stripe.Issuing.Dispute.t(), + livemode: boolean, + merchant_data: Stripe.Issuing.Types.merchant_data(), + metadata: Stripe.Types.metadata(), + type: String.t() + } + + defstruct [ + :id, + :object, + :amount, + :authorization, + :balance_transaction, + :card, + :cardholder, + :created, + :currency, + :dispute, + :livemode, + :merchant_data, + :metadata, + :type + ] + + @plural_endpoint "issuing/transactions" + + @doc """ + Retrieve a transaction. + """ + @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 transaction. + """ + @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 """ + List all transactions. + """ + @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(:dispute) => Stripe.Issuing.Dispute.t() | Stripe.id(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:settlement) => String.t(), + optional(:starting_after) => t | Stripe.id() + } + | %{} + 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, :dispute, :ending_before, :starting_after]) + |> make_request() + end +end diff --git a/lib/stripe/issuing/types.ex b/lib/stripe/issuing/types.ex new file mode 100644 index 000000000..007c9a466 --- /dev/null +++ b/lib/stripe/issuing/types.ex @@ -0,0 +1,35 @@ +defmodule Stripe.Issuing.Types do + @moduledoc """ + A module that contains shared issuing types matching Stripe schemas. + """ + + @type authorization_controls :: %{ + allowed_categories: list() | nil, + blocked_categories: list() | nil, + spending_limits: list(spending_limits()) | nil, + currency: String.t() | nil, + max_amount: non_neg_integer | nil, + max_approvals: non_neg_integer | nil + } + + @type billing :: %{ + address: Stripe.Types.address(), + name: String.t() + } + + @type merchant_data :: %{ + category: String.t(), + city: String.t(), + country: String.t(), + name: String.t(), + network_id: String.t(), + postal_code: String.t(), + state: String.t() + } + + @type spending_limits :: %{ + amount: non_neg_integer, + categories: list(), + interval: String.t() + } +end diff --git a/lib/stripe/list.ex b/lib/stripe/list.ex index 2ba33c2b8..9e8c5c34a 100644 --- a/lib/stripe/list.ex +++ b/lib/stripe/list.ex @@ -17,12 +17,12 @@ defmodule Stripe.List do @type value :: term @type t(value) :: %__MODULE__{ - object: String.t(), - data: [value], - has_more: boolean, - total_count: integer | nil, - url: String.t() - } + object: String.t(), + data: [value], + has_more: boolean, + total_count: integer | nil, + url: String.t() + } defstruct [:object, :data, :has_more, :total_count, :url] end diff --git a/lib/stripe/payment_methods/bank_account.ex b/lib/stripe/payment_methods/bank_account.ex index eb6f76f49..24cb94897 100644 --- a/lib/stripe/payment_methods/bank_account.ex +++ b/lib/stripe/payment_methods/bank_account.ex @@ -55,10 +55,10 @@ defmodule Stripe.BankAccount do """ @spec create(params, Keyword.t()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - :customer => Stripe.id() | Stripe.Customer.t(), - :source => Stripe.id() | Stripe.Source.t(), - optional(:metadata) => Stripe.Types.metadata() - } + :customer => Stripe.id() | Stripe.Customer.t(), + :source => Stripe.id() | Stripe.Source.t(), + optional(:metadata) => Stripe.Types.metadata() + } def create(%{customer: _, source: _} = params, opts \\ []) do new_request(opts) |> put_endpoint(params |> plural_endpoint()) @@ -85,11 +85,11 @@ defmodule Stripe.BankAccount do """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - :customer => Stripe.id() | Stripe.Customer.t(), - optional(:metadata) => Stripe.Types.metadata(), - optional(:account_holder_name) => String.t(), - optional(:account_holder_type) => String.t() - } + :customer => Stripe.id() | Stripe.Customer.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:account_holder_name) => String.t(), + optional(:account_holder_type) => String.t() + } def update(id, %{customer: _} = params, opts \\ []) do endpoint = params |> plural_endpoint() @@ -118,10 +118,10 @@ defmodule Stripe.BankAccount do """ @spec verify(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - :customer => Stripe.id() | Stripe.Customer.t(), - optional(:amounts) => list(integer), - optional(:verification_method) => String.t() - } + :customer => Stripe.id() | Stripe.Customer.t(), + optional(:amounts) => list(integer), + optional(:verification_method) => String.t() + } def verify(id, %{customer: _} = params, opts \\ []) do endpoint = params |> plural_endpoint() @@ -137,14 +137,14 @@ defmodule Stripe.BankAccount do """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} when params: %{ - :customer => Stripe.id() | Stripe.Customer.t(), - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id(), - } + :customer => Stripe.id() | Stripe.Customer.t(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } def list(%{customer: _} = params, opts \\ []) do endpoint = params |> plural_endpoint() - params = params |> Map.put(:object, "card") + params = params |> Map.put(:object, "bank_account") new_request(opts) |> put_endpoint(endpoint) diff --git a/lib/stripe/payment_methods/card.ex b/lib/stripe/payment_methods/card.ex index 9d82c1942..5355496d3 100644 --- a/lib/stripe/payment_methods/card.ex +++ b/lib/stripe/payment_methods/card.ex @@ -102,10 +102,10 @@ defmodule Stripe.Card do """ @spec create(params, Keyword.t()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - :customer => Stripe.id() | Stripe.Customer.t(), - :source => Stripe.id() | Stripe.Source.t(), - optional(:metadata) => Stripe.Types.metadata(), - } + :customer => Stripe.id() | Stripe.Customer.t(), + :source => Stripe.id() | Stripe.Source.t(), + optional(:metadata) => Stripe.Types.metadata() + } def create(%{customer: _, source: _} = params, opts \\ []) do new_request(opts) |> put_endpoint(params |> plural_endpoint()) @@ -134,19 +134,19 @@ defmodule Stripe.Card do """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - :id => String.t(), - :customer => String.t(), - optional(:address_city) => String.t(), - optional(:address_country) => String.t(), - optional(:address_line1) => String.t(), - optional(:address_line2) => String.t(), - optional(:address_state) => String.t(), - optional(:address_zip) => String.t(), - optional(:exp_month) => String.t(), - optional(:exp_year) => String.t(), - optional(:metadata) => Stripe.Types.metadata(), - optional(:name) => String.t(), - } + :id => String.t(), + :customer => String.t(), + optional(:address_city) => String.t(), + optional(:address_country) => String.t(), + optional(:address_line1) => String.t(), + optional(:address_line2) => String.t(), + optional(:address_state) => String.t(), + optional(:address_zip) => String.t(), + optional(:exp_month) => String.t(), + optional(:exp_year) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:name) => String.t() + } def update(id, %{customer: _} = params, opts \\ []) do endpoint = params |> plural_endpoint() @@ -175,11 +175,11 @@ defmodule Stripe.Card do """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} when params: %{ - :customer => Stripe.id() | Stripe.Customer.t(), - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id(), - } + :customer => Stripe.id() | Stripe.Customer.t(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } def list(%{customer: _} = params, opts \\ []) do endpoint = params |> plural_endpoint() params = params |> Map.put(:object, "card") diff --git a/lib/stripe/payment_methods/payment_method.ex b/lib/stripe/payment_methods/payment_method.ex new file mode 100644 index 000000000..9d71acd5f --- /dev/null +++ b/lib/stripe/payment_methods/payment_method.ex @@ -0,0 +1,162 @@ +defmodule Stripe.PaymentMethod do + @moduledoc """ + Work with Stripe payment method objects. + + Stripe API reference: https://stripe.com/docs/api/payment_methods + """ + + use Stripe.Entity + import Stripe.Request + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + billing_details: %{ + address: Stripe.Types.address(), + email: String.t() | nil, + name: String.t() | nil, + phone: String.t() | nil + }, + card: Stripe.Card.t() | nil, + created: Stripe.timestamp(), + customer: Stripe.id() | Stripe.Customer.t() | nil, + livemode: boolean, + metadata: Stripe.Types.metadata(), + type: String.t() + } + + defstruct [ + :id, + :object, + :billing_details, + :card, + :created, + :customer, + :livemode, + :metadata, + :type + ] + + defp plural_endpoint() do + "payment_methods" + end + + defp plural_endpoint(%{payment_method: payment_method}) do + plural_endpoint() <> "/" <> get_id!(payment_method) + end + + @type billing_details :: %{ + optional(:address) => Stripe.Types.address(), + optional(:email) => String.t(), + optional(:name) => String.t(), + optional(:phone) => String.t() + } + + @type card :: %{ + :exp_month => integer, + :exp_year => integer, + :number => String.t(), + :cvc => String.t() + } + + @doc """ + Create a payment method. + """ + @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: %{ + :type => String.t(), + optional(:billing_details) => billing_details(), + optional(:card) => card(), + optional(:metadata) => Stripe.Types.metadata() + } + def create(%{} = params, opts \\ []) do + new_request(opts) + |> put_endpoint(plural_endpoint()) + |> put_params(params) + |> put_method(:post) + |> make_request() + end + + @doc """ + Retrieve a payment method. + """ + @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. + + Takes the `id` and a map of changes + """ + @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: %{ + optional(:billing_details) => billing_details(), + optional(:card) => card(), + 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 """ + List all payment methods. + """ + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + when params: %{ + :customer => Stripe.id() | Stripe.Customer.t(), + :type => String.t(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } + def list(%{customer: _} = params, opts \\ []) do + endpoint = plural_endpoint() + + new_request(opts) + |> put_endpoint(endpoint) + |> put_method(:get) + |> put_params(params |> Map.update!(:customer, &get_id!/1)) + |> make_request() + end + + @doc """ + Attach payment_method to customer + """ + @spec attach(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: %{ + :customer => Stripe.id() | Stripe.Customer.t(), + :payment_method => Stripe.id() | t() + } + def attach(%{customer: customer, payment_method: _} = params, opts \\ []) do + endpoint = plural_endpoint(params) <> "/attach" + + new_request(opts) + |> put_endpoint(endpoint) + |> put_method(:post) + |> put_params(%{customer: get_id!(customer)}) + |> make_request() + end + + @doc """ + Detach payment_method from customer + """ + @spec detach(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: %{:payment_method => Stripe.id() | t()} + def detach(%{payment_method: _} = params, opts \\ []) do + endpoint = plural_endpoint(params) <> "/detach" + + new_request(opts) + |> put_endpoint(endpoint) + |> put_method(:post) + |> make_request() + end +end diff --git a/lib/stripe/payment_methods/source.ex b/lib/stripe/payment_methods/source.ex index 1e413587f..5d3195cfe 100644 --- a/lib/stripe/payment_methods/source.ex +++ b/lib/stripe/payment_methods/source.ex @@ -238,20 +238,20 @@ defmodule Stripe.Source do Create a source. """ @spec create(params, Keyword.t()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - :type => String.t(), - optional(:amount) => String.t(), - optional(:currency) => String.t(), - optional(:flow) => String.t(), - optional(:mandate) => map, - optional(:metadata) => Stripe.Types.metadata(), - optional(:owner) => owner, - optional(:receiver) => receiver_flow, - optional(:redirect) => redirect_flow, - optional(:statement_descriptor) => String.t(), - optional(:token) => String.t(), - optional(:usage) => String.t() - } + when params: %{ + :type => String.t(), + optional(:amount) => String.t(), + optional(:currency) => String.t(), + optional(:flow) => String.t(), + optional(:mandate) => map, + optional(:metadata) => Stripe.Types.metadata(), + optional(:owner) => owner, + optional(:receiver) => receiver_flow, + optional(:redirect) => redirect_flow, + optional(:statement_descriptor) => String.t(), + optional(:token) => String.t(), + optional(:usage) => String.t() + } def create(%{} = params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) @@ -263,10 +263,11 @@ defmodule Stripe.Source do @doc """ Retrieve a source. """ - @spec retrieve(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + @spec retrieve(Stripe.id() | t, params, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - optional(:client_secret) => String.t(), - } + optional(:client_secret) => String.t() + } def retrieve(id, %{} = params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") @@ -282,10 +283,10 @@ defmodule Stripe.Source do """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - optional(:mandate) => map, - optional(:metadata) => Stripe.Types.metadata(), - optional(:owner) => owner - } + optional(:mandate) => map, + optional(:metadata) => Stripe.Types.metadata(), + optional(:owner) => owner + } def update(id, %{} = params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") diff --git a/lib/stripe/radar/review.ex b/lib/stripe/radar/review.ex index 5b226d67a..54cd781cf 100644 --- a/lib/stripe/radar/review.ex +++ b/lib/stripe/radar/review.ex @@ -7,23 +7,52 @@ defmodule Stripe.Review do use Stripe.Entity + @type session :: %{ + browser: String.t(), + device: String.t(), + platform: String.t(), + version: String.t() + } + + @type ip_address_location :: %{ + city: String.t(), + country: String.t(), + latitude: float, + longitude: float, + region: String.t() + } + @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), + billing_zip: String.t(), charge: Stripe.id() | Stripe.Charge.t(), + closed_reason: String.t(), created: Stripe.timestamp(), + ip_address: String.t(), + ip_address_location: ip_address_location, livemode: boolean, open: boolean, - reason: String.t() + opened_reason: String.t(), + payment_intent: String.t(), + reason: String.t(), + session: session } defstruct [ :id, :object, + :billing_zip, :charge, + :closed_reason, :created, + :ip_address, + :ip_address_location, :livemode, :open, - :reason + :opened_reason, + :payment_intent, + :reason, + :session ] end diff --git a/lib/stripe/relay/order.ex b/lib/stripe/relay/order.ex index 80ea03d53..6705c5ac0 100644 --- a/lib/stripe/relay/order.ex +++ b/lib/stripe/relay/order.ex @@ -31,6 +31,7 @@ defmodule Stripe.Order do application: Stripe.id(), application_fee: non_neg_integer, charge: Stripe.id() | Stripe.Charge.t(), + created: Stripe.timestamp(), currency: String.t(), customer: Stripe.id() | Stripe.Customer.t(), email: String.t(), @@ -87,6 +88,7 @@ defmodule Stripe.Order do :application, :application_fee, :charge, + :created, :currency, :customer, :email, @@ -111,14 +113,14 @@ defmodule Stripe.Order do """ @spec create(params, Keyword.t()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - :currency => String.t(), - optional(:coupon) => Stripe.id() | Stripe.Coupon.t(), - optional(:customer) => Stripe.id() | Stripe.Customer.t(), - optional(:email) => String.t(), - optional(:items) => Stripe.List.t(Stripe.OrderItem.t()), - optional(:metadata) => Stripe.Types.metadata(), - optional(:shipping) => map - } + :currency => String.t(), + optional(:coupon) => Stripe.id() | Stripe.Coupon.t(), + optional(:customer) => Stripe.id() | Stripe.Customer.t(), + optional(:email) => String.t(), + optional(:items) => Stripe.List.t(Stripe.OrderItem.t()), + optional(:metadata) => Stripe.Types.metadata(), + optional(:shipping) => map + } def create(%{currency: _} = params, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint) @@ -145,12 +147,12 @@ defmodule Stripe.Order do """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - optional(:coupon) => Stripe.id() | Stripe.Coupon.t(), - optional(:metadata) => Stripe.Types.metadata(), - optional(:selected_shipping_method) => String.t(), - optional(:shipping) => map, - optional(:status) => String.t() - } + optional(:coupon) => Stripe.id() | Stripe.Coupon.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:selected_shipping_method) => String.t(), + optional(:shipping) => map, + optional(:status) => String.t() + } def update(id, params, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint <> "/#{get_id!(id)}") @@ -164,12 +166,13 @@ defmodule Stripe.Order do """ @spec pay(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - optional(:application_fee) => non_neg_integer, - optional(:customer) => Stripe.id() | Stripe.Customer.t(), - optional(:source) => Stripe.id() | Stripe.Card.t() | Stripe.Customer.t() | card_info, - optional(:email) => String.t(), - optional(:metadata) => Stripe.Types.metadata() - } + optional(:application_fee) => non_neg_integer, + optional(:customer) => Stripe.id() | Stripe.Customer.t(), + optional(:source) => + Stripe.id() | Stripe.Card.t() | Stripe.Customer.t() | card_info, + optional(:email) => String.t(), + optional(:metadata) => Stripe.Types.metadata() + } def pay(id, params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint <> "/#{get_id!(id)}/" <> "pay") @@ -183,8 +186,8 @@ defmodule Stripe.Order do """ @spec return(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - optional(:items) => Stripe.List.t(Stripe.OrderItem.t()) - } + optional(:items) => Stripe.List.t(Stripe.OrderItem.t()) + } def return(id, params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint <> "/#{get_id!(id)}/" <> "returns") @@ -198,15 +201,15 @@ defmodule Stripe.Order do """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} when params: %{ - optional(:customer) => Stripe.id() | Stripe.Customer.t(), - optional(:ending_before) => t | Stripe.id(), - optional(:ids) => Stripe.List.t(Stripe.id()), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id(), - optional(:status) => String.t(), - optional(:status_transitions) => map, - optional(:upstream_ids) => Stripe.List.t(Stripe.id()) - } + optional(:customer) => Stripe.id() | Stripe.Customer.t(), + optional(:ending_before) => t | Stripe.id(), + optional(:ids) => Stripe.List.t(Stripe.id()), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id(), + optional(:status) => String.t(), + optional(:status_transitions) => map, + optional(:upstream_ids) => Stripe.List.t(Stripe.id()) + } def list(params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint) diff --git a/lib/stripe/relay/order_return.ex b/lib/stripe/relay/order_return.ex index 97922422f..eb8598171 100644 --- a/lib/stripe/relay/order_return.ex +++ b/lib/stripe/relay/order_return.ex @@ -50,13 +50,13 @@ defmodule Stripe.OrderReturn do """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} when params: %{ - optional(:created) => Stripe.date_query(), - optional(:ending_before) => t | Stripe.id(), - optional(:ids) => Stripe.List.t(Stripe.id()), - optional(:limit) => 1..100, - optional(:order) => Stripe.Order.t(), - optional(:starting_after) => t | Stripe.id() - } + optional(:created) => Stripe.date_query(), + optional(:ending_before) => t | Stripe.id(), + optional(:ids) => Stripe.List.t(Stripe.id()), + optional(:limit) => 1..100, + optional(:order) => Stripe.Order.t(), + optional(:starting_after) => t | Stripe.id() + } def list(params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) diff --git a/lib/stripe/relay/product.ex b/lib/stripe/relay/product.ex index 2be6b8349..2f87aa880 100644 --- a/lib/stripe/relay/product.ex +++ b/lib/stripe/relay/product.ex @@ -67,21 +67,23 @@ defmodule Stripe.Relay.Product do Create a product. """ @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:caption) => String.t(), - optional(:deactive_on) => [Stripe.id()], - optional(:description) => String.t(), - optional(:id) => String.t(), - optional(:images) => [Stripe.id()], - optional(:description) => String.t(), - optional(:attributes) => list, - :name => String.t(), - :type => String.t(), - optional(:metadata) => Stripe.Types.metadata(), - optional(:package_dimensions) => map, - optional(:shippable) => boolean, - optional(:url) => String.t() - } | %{} + when params: + %{ + optional(:caption) => String.t(), + optional(:deactive_on) => [Stripe.id()], + optional(:description) => String.t(), + optional(:id) => String.t(), + optional(:images) => [Stripe.id()], + optional(:description) => String.t(), + optional(:attributes) => list, + :name => String.t(), + :type => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:package_dimensions) => map, + optional(:shippable) => boolean, + optional(:url) => String.t() + } + | %{} def create(params, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint) @@ -107,19 +109,21 @@ defmodule Stripe.Relay.Product do Takes the `id` and a map of changes. """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:active) => boolean, - optional(:attributes) => list, - optional(:caption) => String.t(), - optional(:deactive_on) => [Stripe.id()], - optional(:description) => String.t(), - optional(:images) => [Stripe.id()], - optional(:metadata) => Stripe.Types.metadata(), - optional(:name) => String.t(), - optional(:package_dimensions) => map, - optional(:shippable) => boolean, - optional(:url) => String.t() - } | %{} + when params: + %{ + optional(:active) => boolean, + optional(:attributes) => list, + optional(:caption) => String.t(), + optional(:deactive_on) => [Stripe.id()], + optional(:description) => String.t(), + optional(:images) => [Stripe.id()], + optional(:metadata) => Stripe.Types.metadata(), + optional(:name) => String.t(), + optional(:package_dimensions) => map, + optional(:shippable) => boolean, + optional(:url) => String.t() + } + | %{} def update(id, params, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint <> "/#{get_id!(id)}") @@ -143,17 +147,19 @@ defmodule Stripe.Relay.Product do List all product. """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} - when params: %{ - optional(:active) => boolean, - optional(:created) => Stripe.date_query(), - optional(:ending_before) => t | Stripe.id(), - optional(:ids) => Stripe.List.t(Stripe.id()), - optional(:limit) => 1..100, - optional(:shippable) => boolean, - optional(:starting_after) => t | Stripe.id(), - optional(:type) => String.t(), - optional(:url) => String.t() - } | %{} + when params: + %{ + optional(:active) => boolean, + optional(:created) => Stripe.date_query(), + optional(:ending_before) => t | Stripe.id(), + optional(:ids) => Stripe.List.t(Stripe.id()), + optional(:limit) => 1..100, + optional(:shippable) => boolean, + optional(:starting_after) => t | Stripe.id(), + optional(:type) => String.t(), + optional(:url) => String.t() + } + | %{} def list(params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint) diff --git a/lib/stripe/relay/sku.ex b/lib/stripe/relay/sku.ex index 4271090ad..e2ee958a0 100644 --- a/lib/stripe/relay/sku.ex +++ b/lib/stripe/relay/sku.ex @@ -62,16 +62,16 @@ defmodule Stripe.Sku do """ @spec create(params, Keyword.t()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - :currency => String.t(), - :inventory => map, - :price => non_neg_integer, - :product => Stripe.id() | Stripe.Relay.Product.t(), - optional(:active) => boolean, - optional(:attributes) => map, - optional(:image) => String.t(), - optional(:metadata) => Stripe.Types.metadata(), - optional(:package_dimensions) => map - } + :currency => String.t(), + :inventory => map, + :price => non_neg_integer, + :product => Stripe.id() | Stripe.Relay.Product.t(), + optional(:active) => boolean, + optional(:attributes) => map, + optional(:image) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:package_dimensions) => map + } def create(%{currency: _, inventory: _, price: _, product: _} = params, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint) @@ -98,16 +98,16 @@ defmodule Stripe.Sku do """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - optional(:active) => boolean, - optional(:attributes) => map, - optional(:currency) => String.t(), - optional(:image) => String.t(), - optional(:inventory) => map, - optional(:metadata) => Stripe.Types.metadata(), - optional(:package_dimensions) => map, - optional(:price) => non_neg_integer, - optional(:product) => Stripe.id() | Stripe.Relay.Product.t() - } + optional(:active) => boolean, + optional(:attributes) => map, + optional(:currency) => String.t(), + optional(:image) => String.t(), + optional(:inventory) => map, + optional(:metadata) => Stripe.Types.metadata(), + optional(:package_dimensions) => map, + optional(:price) => non_neg_integer, + optional(:product) => Stripe.id() | Stripe.Relay.Product.t() + } def update(id, params, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint <> "/#{get_id!(id)}") @@ -132,15 +132,15 @@ defmodule Stripe.Sku do """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} when params: %{ - optional(:active) => boolean, - optional(:attributes) => map, - optional(:ending_before) => t | Stripe.id(), - optional(:ids) => Stripe.List.t(Stripe.id()), - optional(:in_stock) => boolean, - optional(:limit) => 1..100, - optional(:product) => Stripe.id() | Stripe.Relay.Product.t(), - optional(:starting_after) => t | Stripe.id() - } + optional(:active) => boolean, + optional(:attributes) => map, + optional(:ending_before) => t | Stripe.id(), + optional(:ids) => Stripe.List.t(Stripe.id()), + optional(:in_stock) => boolean, + optional(:limit) => 1..100, + optional(:product) => Stripe.id() | Stripe.Relay.Product.t(), + optional(:starting_after) => t | Stripe.id() + } def list(params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@endpoint) diff --git a/lib/stripe/subscriptions/credit_note.ex b/lib/stripe/subscriptions/credit_note.ex new file mode 100644 index 000000000..2a71ceeaa --- /dev/null +++ b/lib/stripe/subscriptions/credit_note.ex @@ -0,0 +1,188 @@ +defmodule Stripe.CreditNote do + @moduledoc """ + Work with Stripe Credit Note objects. + + You can: + + - Create a credit note + - Retrieve a credit note + - Update a credit note + - Void a credit note + - List credit notes + + ``` + { + "id": "ivory-extended-580", + "object": "plan", + "active": true, + "aggregate_usage": null, + "amount": 999, + "billing_scheme": "per_unit", + "created": 1531234812, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": { + }, + "nickname": null, + "product": "prod_DCmtkptv7qHXGE", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "licensed" + } + ``` + """ + + use Stripe.Entity + import Stripe.Request + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + amount: integer, + created: Stripe.timestamp(), + currency: String.t(), + currency: String.t(), + customer: Stripe.id() | nil, + invoice: Stripe.id(), + livemode: boolean, + metadata: Stripe.Types.metadata(), + number: String.t(), + pdf: String.t(), + reason: String.t() | nil, + refund: Stripe.id() | Stripe.Refund.t() | nil, + status: String.t(), + type: String.t() + } + + defstruct [ + :id, + :object, + :amount, + :created, + :currency, + :customer, + :invoice, + :livemode, + :memo, + :metadata, + :number, + :pdf, + :reason, + :refund, + :status, + :type + ] + + @plural_endpoint "credit_notes" + + @doc """ + Create a credit note. + + Stripe.CreditNote.create(%{ + invoice: "in_173uNd4Wq104wst7Gf4dgq1Y", + amount: 500, + }) + + """ + @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + :amount => number, + :invoice => Stripe.id(), + optional(:credit_amount) => number, + optional(:memo) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:reason) => String.t(), + optional(:refund_amount) => number, + optional(:refund) => Stripe.id() + } + | %{} + def create(params, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint) + |> put_params(params) + |> put_method(:post) + |> make_request() + end + + @doc """ + Retrieve a Credit Note. + + Stripe.CreditNote.retrieve("cn_1EXwJk4Wq104wst7IISdh9ed") + """ + @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 credit note. + + Takes the `id` and a map of changes. + + Stripe.CreditNote.update( + "cn_1EXwJk4Wq104wst7IISdh9ed", + %{ + metadata: {order_id: "6735"}, + } + ) + """ + @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:memo) => String.t(), + 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 """ + Void a credit note. + + Stripe.CreditNote.void("cn_1EXwJk4Wq104wst7IISdh9ed") + + """ + @spec void(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + def void(id, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/void") + |> put_method(:post) + |> make_request() + end + + @doc """ + List all credit notes. + + Stripe.CreditNote.list(limit: 3) + """ + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:invoice) => Stripe.id(), + optional(:starting_after) => t | Stripe.id() + } + | %{} + def list(params \\ %{}, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint) + |> put_method(:get) + |> put_params(params) + |> cast_to_id([:ending_before, :starting_after]) + |> make_request() + end +end diff --git a/lib/stripe/subscriptions/invoice.ex b/lib/stripe/subscriptions/invoice.ex index c8f49e94f..23b6fab0c 100644 --- a/lib/stripe/subscriptions/invoice.ex +++ b/lib/stripe/subscriptions/invoice.ex @@ -7,6 +7,7 @@ defmodule Stripe.Invoice do - Create an invoice - Retrieve an invoice - Update an invoice + - Void an invoice Does not take options yet. @@ -19,10 +20,12 @@ defmodule Stripe.Invoice do @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), + account_country: String.t(), + account_name: String.t(), amount_due: integer, amount_paid: integer, amount_remaining: integer, - application_fee: integer | nil, + application_fee_amount: integer | nil, attempt_count: non_neg_integer, attempted: boolean, auto_advance: boolean, @@ -31,14 +34,25 @@ defmodule Stripe.Invoice do charge: Stripe.id() | Stripe.Charge.t() | nil, closed: boolean, currency: String.t(), + customer_address: Stripe.Types.address() | nil, + customer_email: String.t() | nil, + customer_name: String.t() | nil, + customer_phone: String.t() | nil, + customer_shipping: Stripe.Types.shipping() | nil, + customer_tax_exempt: String.t() | nil, + customer_tax_ids: Stripe.List.t(map) | nil, + custom_fields: custom_fields() | nil, customer: Stripe.id() | Stripe.Customer.t(), - date: Stripe.timestamp(), + created: Stripe.timestamp(), + default_payment_method: String.t() | nil, default_source: String.t() | nil, + default_tax_rates: Stripe.List.t(map) | nil, description: String.t() | nil, discount: Stripe.Discount.t() | nil, due_date: Stripe.timestamp() | nil, ending_balance: integer | nil, finalized_at: Stripe.timestamp() | nil, + footer: String.t() | nil, forgiven: boolean, hosted_invoice_url: String.t() | nil, invoice_pdf: String.t() | nil, @@ -48,28 +62,54 @@ defmodule Stripe.Invoice do next_payment_attempt: Stripe.timestamp() | nil, number: String.t() | nil, paid: boolean, + payment_intent: String.t() | nil, period_end: Stripe.timestamp(), period_start: Stripe.timestamp(), + post_payment_credit_notes_amount: integer, + pre_payment_credit_notes_amount: integer, receipt_number: String.t() | nil, starting_balance: integer, statement_descriptor: String.t() | nil, status: String.t() | nil, + status_transitions: status_transitions() | nil, subscription: Stripe.id() | Stripe.Subscription.t() | nil, subscription_proration_date: Stripe.timestamp(), subtotal: integer, tax: integer | nil, tax_percent: number | nil, + total_tax_amounts: Stripe.List.t(map) | nil, total: integer, webhooks_delivered_at: Stripe.timestamp() | nil } + @type custom_fields :: + list(%{ + name: String.t(), + value: String.t() + }) + + @type invoice_settings :: %{ + custom_fields: custom_fields | nil, + footer: String.t() | nil + } + + @type status_transitions :: + list(%{ + finalized_at: Stripe.timestamp() | nil, + marked_uncollectible_at: Stripe.timestamp() | nil, + paid_at: Stripe.timestamp() | nil, + voided_at: Stripe.timestamp() | nil + }) + defstruct [ :id, :object, + :account_country, + :account_name, :amount_due, :amount_paid, :amount_remaining, - :application_fee, + :application_fee_amount, :attempt_count, :attempted, :auto_advance, @@ -77,15 +117,26 @@ defmodule Stripe.Invoice do :billing_reason, :charge, :closed, + :created, + :customer_address, + :customer_email, + :customer_name, + :customer_phone, + :customer_shipping, + :customer_tax_exempt, + :customer_tax_ids, :currency, + :custom_fields, :customer, - :date, + :default_payment_method, :default_source, + :default_tax_rates, :description, :discount, :due_date, :ending_balance, :finalized_at, + :footer, :forgiven, :hosted_invoice_url, :invoice_pdf, @@ -95,10 +146,14 @@ defmodule Stripe.Invoice do :next_payment_attempt, :number, :paid, + :payment_intent, :period_end, :period_start, + :post_payment_credit_notes_amount, + :pre_payment_credit_notes_amount, :receipt_number, :status, + :status_transitions, :starting_balance, :statement_descriptor, :subscription, @@ -106,6 +161,7 @@ defmodule Stripe.Invoice do :subtotal, :tax, :tax_percent, + :total_tax_amounts, :total, :webhooks_delivered_at ] @@ -113,18 +169,28 @@ defmodule Stripe.Invoice do @plural_endpoint "invoices" @doc """ - Create an invoice. + Create an invoice + + This endpoint creates a draft invoice for a given customer. The draft invoice + created pulls in all pending invoice items on that customer, including + prorations. + + See [Stripe docs](https://stripe.com/docs/api/invoices/update) """ @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - optional(:application_fee) => integer, + optional(:application_fee_amount) => integer, + optional(:auto_advance) => boolean, optional(:billing) => String.t(), :customer => Stripe.id() | Stripe.Customer.t(), + optional(:custom_fields) => custom_fields, optional(:days_until_due) => integer, + optional(:default_payment_method) => String.t(), optional(:default_source) => String.t(), optional(:description) => String.t(), optional(:due_date) => Stripe.timestamp(), + optional(:footer) => String.t(), optional(:metadata) => Stripe.Types.metadata(), optional(:statement_descriptor) => String.t(), optional(:subscription) => Stripe.id() | Stripe.Subscription.t(), @@ -142,6 +208,10 @@ defmodule Stripe.Invoice do @doc """ Retrieve an invoice. + + Retrieves the invoice with the given ID. + + See [Stripe docs](https://stripe.com/docs/api/invoices/retrieve) """ @spec retrieve(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} def retrieve(id, opts \\ []) do @@ -154,17 +224,25 @@ defmodule Stripe.Invoice do @doc """ Update an invoice. - Takes the `id` and a map of changes. + Takes the `id` and a map of changes. Draft invoices are fully editable. Once + an invoice is finalized, monetary values, as well as billing, become + uneditable. + + See [Stripe docs](https://stripe.com/docs/api/invoices/update) """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ - optional(:application_fee) => integer, + optional(:application_fee_amount) => integer, + optional(:auto_advance) => boolean, optional(:closed) => boolean, + optional(:custom_fields) => custom_fields, optional(:days_until_due) => integer, + optional(:default_payment_method) => String.t(), optional(:default_source) => String.t(), optional(:description) => String.t(), optional(:due_date) => Stripe.timestamp(), + optional(:footer) => String.t(), optional(:forgiven) => boolean, optional(:metadata) => Stripe.Types.metadata(), optional(:paid) => boolean, @@ -181,10 +259,21 @@ defmodule Stripe.Invoice do end @doc """ - Retrieve an upcoming invoice. + Retrieve an upcoming invoice + + At any time, you can preview the upcoming invoice for a customer. This will + show you all the charges that are pending, including subscription renewal + charges, invoice item charges, etc. It will also show you any discount that is + applicable to the customer. + + See [Stripe docs](https://stripe.com/docs/api/invoices/upcoming) """ @spec upcoming(map, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - def upcoming(params, opts \\ []) do + def upcoming(params, opts \\ []) + def upcoming(params = %{customer: _customer}, opts), do: get_upcoming(params, opts) + def upcoming(params = %{subscription: _subscription}, opts), do: get_upcoming(params, opts) + + defp get_upcoming(params, opts) do new_request(opts) |> put_endpoint(@plural_endpoint <> "/upcoming") |> put_method(:get) @@ -193,14 +282,20 @@ defmodule Stripe.Invoice do end @doc """ - List all invoices. + List all invoices + + You can list all invoices, or list the invoices for a specific customer. The + invoices are returned sorted by creation date, with the most recently created + invoices appearing first. + + See [Stripe docs](https://stripe.com/docs/api/invoices/list) """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} when params: %{ optional(:billing) => String.t(), optional(:customer) => Stripe.id() | Stripe.Customer.t(), - optional(:date) => Stripe.date_query(), + optional(:created) => Stripe.date_query(), optional(:due_date) => Stripe.timestamp(), optional(:ending_before) => t | Stripe.id(), optional(:limit) => 1..100, @@ -218,14 +313,50 @@ defmodule Stripe.Invoice do end @doc """ - Pay an invoice. + Finalize an invoice + + Stripe automatically finalizes drafts before sending and attempting payment on + invoices. However, if you’d like to finalize a draft invoice manually, you can + do so using this method. + + See [Stripe docs](https://stripe.com/docs/api/invoices/finalize) + """ + @spec finalize(Stripe.id() | t, params, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + :id => String.t(), + optional(:auto_advance) => boolean + } + | %{} + def finalize(id, params, opts \\ []) do + new_request(opts) + |> prefix_expansions() + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/finalize") + |> put_method(:post) + |> put_params(params) + |> cast_to_id([:source]) + |> make_request() + end + + @doc """ + Pay an invoice + + Stripe automatically creates and then attempts to collect payment on invoices + for customers on subscriptions according to your subscriptions settings. + However, if you’d like to attempt payment on an invoice out of the normal + collection schedule or for some other reason, you can do so. + + See [Stripe docs](https://stripe.com/docs/api/invoices/delete) """ @spec pay(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ :id => String.t(), optional(:forgive) => boolean, - optional(:source) => Stripe.id() | Stripe.Source.t() | nil + optional(:paid_out_of_band) => boolean, + optional(:payment_method) => String.t(), + optional(:source) => Stripe.id() | Stripe.Source.t() } | %{} def pay(id, params, opts \\ []) do @@ -237,4 +368,90 @@ defmodule Stripe.Invoice do |> cast_to_id([:source]) |> make_request() end + + @doc """ + Void an invoice + + Mark a finalized invoice as void. This cannot be undone. Voiding an invoice is + similar to deletion, however it only applies to finalized invoices and + maintains a papertrail where the invoice can still be found. + + See [Stripe docs](https://stripe.com/docs/api/invoices/void) + """ + @spec void(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + def void(id, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/void") + |> put_method(:post) + |> make_request() + end + + @doc """ + Send an invoice + + Stripe will automatically send invoices to customers according to your + subscriptions settings. However, if you’d like to manually send an invoice to + your customer out of the normal schedule, you can do so. When sending invoices + that have already been paid, there will be no reference to the payment in the + email. + + Requests made in test-mode result in no emails being sent, despite sending an + `invoice.sent` event. + + + See [Stripe docs](https://stripe.com/docs/api/invoices/send) + """ + @spec send(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + def send(id, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/send") + |> put_method(:post) + |> make_request() + end + + @doc """ + Delete an invoice + + Permanently deletes a draft invoice. This cannot be undone. Attempts to delete + invoices that are no longer in a draft state will fail; once an invoice has + been finalized, it must be voided. + + ## Example + + {:ok, _} = Stripe.Invoice.delete("in_16vEXC2eZvKYlo2CU9MyflAA") + + {:ok, _} = Stripe.Invoice.delete(%Stripe.Invoice{id: "in_16vEXC2eZvKYlo2CU9MyflAA"}) + + See [Stripe docs](https://stripe.com/docs/api/invoices/delete) + """ + @spec delete(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + def delete(id, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") + |> put_method(:delete) + |> make_request() + end + + @doc """ + Mark an invoice as uncollectible + + Marking an invoice as uncollectible is useful for keeping track of bad debts + that can be written off for accounting purposes. + + ## Example + + {:ok, _} = Stripe.Invoice.mark_as_uncollectible("in_16vEXC2eZvKYlo2CU9MyflAA") + + {:ok, _} = Stripe.Invoice.mark_as_uncollectible(%Stripe.Invoice{id: "in_16vEXC2eZvKYlo2CU9MyflAA"}) + + See [Stripe docs](https://stripe.com/docs/api/invoices/mark_uncollectible) + """ + @spec mark_as_uncollectible(Stripe.id() | t, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} + def mark_as_uncollectible(id, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/mark_uncollectible") + |> put_method(:post) + |> make_request() + end end diff --git a/lib/stripe/subscriptions/invoiceitem.ex b/lib/stripe/subscriptions/invoiceitem.ex index 6e72a5b8d..e28e04171 100644 --- a/lib/stripe/subscriptions/invoiceitem.ex +++ b/lib/stripe/subscriptions/invoiceitem.ex @@ -62,18 +62,20 @@ defmodule Stripe.Invoiceitem do Create an invoiceitem. """ @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:amount) => integer, - :currency => String.t(), - :customer => Stripe.id() | Stripe.Customer.t(), - optional(:description) => String.t(), - optional(:discountable) => boolean, - optional(:invoice) => Stripe.id() | Stripe.Invoice.t(), - optional(:metadata) => Stripe.Types.metadata(), - optional(:quantity) => integer, - optional(:subscription) => Stripe.id() | Stripe.Subscription.t(), - optional(:unit_amount) => integer - } | %{} + when params: + %{ + optional(:amount) => integer, + :currency => String.t(), + :customer => Stripe.id() | Stripe.Customer.t(), + optional(:description) => String.t(), + optional(:discountable) => boolean, + optional(:invoice) => Stripe.id() | Stripe.Invoice.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:quantity) => integer, + optional(:subscription) => Stripe.id() | Stripe.Subscription.t(), + optional(:unit_amount) => integer + } + | %{} def create(params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) @@ -100,14 +102,16 @@ defmodule Stripe.Invoiceitem do Takes the `id` and a map of changes. """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:amount) => integer, - optional(:description) => String.t(), - optional(:discountable) => boolean, - optional(:metadata) => Stripe.Types.metadata(), - optional(:quantity) => integer, - optional(:unit_amount) => integer - } | %{} + when params: + %{ + optional(:amount) => integer, + optional(:description) => String.t(), + optional(:discountable) => boolean, + optional(:metadata) => Stripe.Types.metadata(), + optional(:quantity) => integer, + optional(:unit_amount) => integer + } + | %{} def update(id, params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") @@ -116,18 +120,33 @@ defmodule Stripe.Invoiceitem do |> make_request() end + @doc """ + Delete and invoiceitem + + Takes the `id` of the invoiceitem to delete. + """ + @spec delete(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + def delete(id, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") + |> put_method(:delete) + |> make_request() + end + @doc """ List all invoiceitems. """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} - when params: %{ - optional(:created) => Stripe.timestamp(), - optional(:customer) => Stripe.id() | Stripe.Customer.t(), - optional(:ending_before) => t | Stripe.id(), - optional(:invoice) => Stripe.id() | Stripe.Invoice.t(), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id() - } | %{} + when params: + %{ + optional(:created) => Stripe.timestamp(), + optional(:customer) => Stripe.id() | Stripe.Customer.t(), + optional(:ending_before) => t | Stripe.id(), + optional(:invoice) => Stripe.id() | Stripe.Invoice.t(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } + | %{} def list(params \\ %{}, opts \\ []) do new_request(opts) |> prefix_expansions() diff --git a/lib/stripe/subscriptions/line_item.ex b/lib/stripe/subscriptions/line_item.ex index b933683fb..573833b6f 100644 --- a/lib/stripe/subscriptions/line_item.ex +++ b/lib/stripe/subscriptions/line_item.ex @@ -8,6 +8,12 @@ defmodule Stripe.LineItem do use Stripe.Entity import Stripe.Request + @type tax_amount :: %{ + amount: integer, + inclusive: boolean, + tax_rate: Stripe.id() | Stripe.TaxRate.t() + } + @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), @@ -27,6 +33,8 @@ defmodule Stripe.LineItem do quantity: integer, subscription: Stripe.id() | nil, subscription_item: Stripe.id() | nil, + tax_amounts: list(tax_amount), + tax_rates: list(Stripe.TaxRate.t()), type: String.t() } @@ -46,30 +54,35 @@ defmodule Stripe.LineItem do :quantity, :subscription, :subscription_item, + :tax_rates, + :tax_amounts, :type ] @doc """ Retrieve an invoice line item. """ - @spec retrieve(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:coupon) => Stripe.id() | Stripe.Coupon.t(), - optional(:customer) => Stripe.id() | Stripe.Customer.t(), - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:starting_after) => t | Stripe.id(), - optional(:subscription) => Stripe.id() | Stripe.Subscription.t(), - optional(:subscription_billing_cycle_anchor) => integer, - optional(:subscription_items) => Stripe.List.t(Stripe.SubscriptionItem.t()), - optional(:subscription_prorate) => boolean, - optional(:subscription_proration_date) => Stripe.timestamp(), - optional(:subscription_tax_percent) => integer, - optional(:subscription_trial_from_plan) => boolean - } | %{} + @spec retrieve(Stripe.id() | t, params, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:coupon) => Stripe.id() | Stripe.Coupon.t(), + optional(:customer) => Stripe.id() | Stripe.Customer.t(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id(), + optional(:subscription) => Stripe.id() | Stripe.Subscription.t(), + optional(:subscription_billing_cycle_anchor) => integer, + optional(:subscription_items) => Stripe.List.t(Stripe.SubscriptionItem.t()), + optional(:subscription_prorate) => boolean, + optional(:subscription_proration_date) => Stripe.timestamp(), + optional(:subscription_tax_percent) => integer, + optional(:subscription_trial_from_plan) => boolean + } + | %{} def retrieve(id, params \\ %{}, opts \\ []) do new_request(opts) - |> put_endpoint("invoices" <> "/#{get_id!(id)}" <> "lines") + |> put_endpoint("invoices" <> "/#{get_id!(id)}/" <> "lines") |> put_method(:get) |> put_params(params) |> make_request() diff --git a/lib/stripe/subscriptions/plan.ex b/lib/stripe/subscriptions/plan.ex index 86c6872dd..79463947c 100644 --- a/lib/stripe/subscriptions/plan.ex +++ b/lib/stripe/subscriptions/plan.ex @@ -65,7 +65,7 @@ defmodule Stripe.Plan do tiers_mode: boolean | nil, transform_usage: map | nil, trial_period_days: non_neg_integer | nil, - usage_type: String.t() | nil, + usage_type: String.t() | nil } defstruct [ @@ -89,7 +89,7 @@ defmodule Stripe.Plan do :tiers_mode, :transform_usage, :trial_period_days, - :usage_type, + :usage_type ] @plural_endpoint "plans" @@ -98,23 +98,25 @@ defmodule Stripe.Plan do Create a plan. """ @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - :currency => String.t(), - :interval => String.t(), - :product => Stripe.id() | Stripe.Product.t(), - optional(:id) => String.t(), - optional(:amount) => non_neg_integer, - optional(:active) => boolean, - optional(:billing_scheme) => String.t(), - optional(:interval_count) => pos_integer, - optional(:metadata) => Stripe.Types.metadata(), - optional(:nickname) => String.t(), - optional(:tiers) => Stripe.List.t(map), - optional(:tiers_mode) => String.t(), - optional(:transform_usage) => map, - optional(:trial_period_days) => non_neg_integer, - optional(:usage_type) => String.t(), - } | %{} + when params: + %{ + :currency => String.t(), + :interval => String.t(), + :product => Stripe.id() | Stripe.Product.t(), + optional(:id) => String.t(), + optional(:amount) => non_neg_integer, + optional(:active) => boolean, + optional(:billing_scheme) => String.t(), + optional(:interval_count) => pos_integer, + optional(:metadata) => Stripe.Types.metadata(), + optional(:nickname) => String.t(), + optional(:tiers) => Stripe.List.t(map), + optional(:tiers_mode) => String.t(), + optional(:transform_usage) => map, + optional(:trial_period_days) => non_neg_integer, + optional(:usage_type) => String.t() + } + | %{} def create(%{currency: _, interval: _, product: _} = params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) @@ -140,13 +142,15 @@ defmodule Stripe.Plan do Takes the `id` and a map of changes. """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:active) => boolean, - optional(:metadata) => Stripe.Types.metadata(), - optional(:nickname) => String.t(), - optional(:product) => Stripe.id() | Stripe.Product.t(), - optional(:trial_period_days) => non_neg_integer, - } | %{} + when params: + %{ + optional(:active) => boolean, + optional(:metadata) => Stripe.Types.metadata(), + optional(:nickname) => String.t(), + optional(:product) => Stripe.id() | Stripe.Product.t(), + optional(:trial_period_days) => non_neg_integer + } + | %{} def update(id, params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") @@ -170,14 +174,16 @@ defmodule Stripe.Plan do List all plans. """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} - when params: %{ - optional(:active) => boolean, - optional(:created) => Stripe.date_query(), - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:product) => Stripe.Product.t() | Stripe.id(), - optional(:starting_after) => t | Stripe.id(), - } | %{} + when params: + %{ + optional(:active) => boolean, + optional(:created) => Stripe.date_query(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:product) => Stripe.Product.t() | Stripe.id(), + optional(:starting_after) => t | Stripe.id() + } + | %{} def list(params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) diff --git a/lib/stripe/subscriptions/product.ex b/lib/stripe/subscriptions/product.ex index 0537fba00..24c24a4db 100644 --- a/lib/stripe/subscriptions/product.ex +++ b/lib/stripe/subscriptions/product.ex @@ -37,7 +37,7 @@ defmodule Stripe.Product do unit_label: String.t() | nil, updated: Stripe.timestamp(), url: String.t() | nil - } + } defstruct [ :id, @@ -68,15 +68,17 @@ defmodule Stripe.Product do Create a product. """ @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:id) => String.t(), - optional(:attributes) => list, - :name => String.t(), - :type => String.t(), - optional(:metadata) => Stripe.Types.metadata(), - optional(:statement_descriptor) => String.t(), - optional(:unit_label) => String.t() - } | %{} + when params: + %{ + optional(:id) => String.t(), + optional(:attributes) => list, + :name => String.t(), + :type => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:statement_descriptor) => String.t(), + optional(:unit_label) => String.t() + } + | %{} def create(params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) @@ -102,12 +104,14 @@ defmodule Stripe.Product do Takes the `id` and a map of changes. """ @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} - when params: %{ - optional(:attributes) => list, - optional(:name) => String.t(), - optional(:metadata) => Stripe.Types.metadata(), - optional(:statement_descriptor) => String.t() - } | %{} + when params: + %{ + optional(:attributes) => list, + optional(:name) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:statement_descriptor) => String.t() + } + | %{} def update(id, params, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") @@ -131,16 +135,18 @@ defmodule Stripe.Product do List all products. """ @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} - when params: %{ - optional(:active) => boolean, - optional(:created) => Stripe.date_query(), - optional(:ending_before) => t | Stripe.id(), - optional(:limit) => 1..100, - optional(:shippable) => boolean, - optional(:starting_after) => t | Stripe.id(), - optional(:type) => String.t(), - optional(:url) => String.t() - } | %{} + when params: + %{ + optional(:active) => boolean, + optional(:created) => Stripe.date_query(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:shippable) => boolean, + optional(:starting_after) => t | Stripe.id(), + optional(:type) => String.t(), + optional(:url) => String.t() + } + | %{} def list(params \\ %{}, opts \\ []) do new_request(opts) |> put_endpoint(@plural_endpoint) diff --git a/lib/stripe/subscriptions/subscription.ex b/lib/stripe/subscriptions/subscription.ex index 44a8d32b5..7d0d21ee0 100644 --- a/lib/stripe/subscriptions/subscription.ex +++ b/lib/stripe/subscriptions/subscription.ex @@ -22,6 +22,7 @@ defmodule Stripe.Subscription do automatic_tax: map, billing: String.t() | nil, billing_cycle_anchor: Stripe.timestamp() | nil, + billing_thresholds: Stripe.Types.subscription_billing_thresholds() | nil, cancel_at_period_end: boolean, canceled_at: Stripe.timestamp() | nil, cancellation_details: map, @@ -30,15 +31,20 @@ defmodule Stripe.Subscription do current_period_start: Stripe.timestamp() | nil, customer: Stripe.id() | Stripe.Customer.t(), days_until_due: integer | nil, + default_payment_method: Stripe.id() | Stripe.PaymentMethod.t() | nil, + default_source: Stripe.id() | Stripe.Source.t() | nil, + default_tax_rates: list(Stripe.TaxRate), discount: Stripe.Discount.t() | nil, ended_at: Stripe.timestamp() | nil, items: Stripe.List.t(Stripe.SubscriptionItem.t()), - latest_invoice: Stripe.Invoice.t() | nil, + latest_invoice: Stripe.id() | Stripe.Invoice.t() | nil, livemode: boolean, metadata: Stripe.Types.metadata(), plan: Stripe.Plan.t() | nil, quantity: integer | nil, + schedule: String.t() | nil, start: Stripe.timestamp(), + start_date: Stripe.timestamp(), status: String.t(), tax_percent: float | nil, trial_end: Stripe.timestamp() | nil, @@ -53,6 +59,7 @@ defmodule Stripe.Subscription do :automatic_tax, :billing, :billing_cycle_anchor, + :billing_thresholds, :cancel_at_period_end, :canceled_at, :cancellation_details, @@ -61,6 +68,9 @@ defmodule Stripe.Subscription do :current_period_start, :customer, :days_until_due, + :default_payment_method, + :default_source, + :default_tax_rates, :discount, :ended_at, :items, @@ -69,7 +79,9 @@ defmodule Stripe.Subscription do :metadata, :plan, :quantity, + :schedule, :start, + :start_date, :status, :tax_percent, :trial_end, diff --git a/lib/stripe/subscriptions/subscription_item.ex b/lib/stripe/subscriptions/subscription_item.ex index 3651e0648..c603a43f3 100644 --- a/lib/stripe/subscriptions/subscription_item.ex +++ b/lib/stripe/subscriptions/subscription_item.ex @@ -11,6 +11,7 @@ defmodule Stripe.SubscriptionItem do @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), + billing_thresholds: Stripe.Types.subscription_billing_thresholds() | nil, created: Stripe.timestamp(), deleted: boolean | nil, metadata: Stripe.Types.metadata(), @@ -22,6 +23,7 @@ defmodule Stripe.SubscriptionItem do defstruct [ :id, :object, + :billing_thresholds, :created, :deleted, :metadata, @@ -108,7 +110,8 @@ defmodule Stripe.SubscriptionItem do @doc """ List all subscriptions. """ - @spec list(Stripe.id(), params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + @spec list(Stripe.id(), params, Stripe.options()) :: + {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} when params: %{ optional(:ending_before) => t | Stripe.id(), optional(:limit) => 1..100, diff --git a/lib/stripe/subscriptions/subscription_schedule.ex b/lib/stripe/subscriptions/subscription_schedule.ex new file mode 100644 index 000000000..0b3b2b813 --- /dev/null +++ b/lib/stripe/subscriptions/subscription_schedule.ex @@ -0,0 +1,233 @@ +defmodule Stripe.SubscriptionSchedule do + @moduledoc """ + Work with Stripe subscription schedule objects. + + """ + + use Stripe.Entity + import Stripe.Request + + @type plans :: %{ + plan: String.t(), + quantity: pos_integer + } + + @type phases :: %{ + application_fee_percent: float | nil, + end_date: Stripe.timestamp(), + start_date: Stripe.timestamp(), + tax_percent: float | nil, + trial_end: Stripe.timestamp(), + plans: list(plans) + } + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + billing_thresholds: Stripe.Types.subscription_billing_thresholds() | nil, + created: Stripe.timestamp(), + canceled_at: Stripe.timestamp() | nil, + released_at: Stripe.timestamp() | nil, + completed_at: Stripe.timestamp() | nil, + livemode: boolean, + metadata: Stripe.Types.metadata(), + invoice_settings: %{ + days_until_due: integer + }, + current_phase: %{ + start_date: Stripe.timestamp(), + end_date: Stripe.timestamp() + }, + renewal_behavior: String.t(), + renewal_interval: String.t(), + revision: String.t(), + status: String.t(), + subscription: Stripe.id() | Stripe.Subscription.t(), + customer: Stripe.id() | Stripe.Customer.t(), + released_subscription: Stripe.id() | Stripe.Subscription.t() | nil, + phases: list(phases) + } + + defstruct [ + :id, + :object, + :billing, + :billing_thresholds, + :created, + :canceled_at, + :completed_at, + :current_phase, + :customer, + :phases, + :released_at, + :released_subscription, + :status, + :subscription, + :invoice_settings, + :livemode, + :metadata, + :renewal_behavior, + :renewal_interval, + :revision + ] + + @plural_endpoint "subscription_schedules" + + @doc """ + Create a subscription schedule. + + ### Examples + + # Create subscription schedule from existing subscription + Stripe.SubscriptionSchedule.create(%{from_subscription: "sub_1234"}) + + """ + @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: %{ + optional(:customer) => Stripe.id(), + optional(:billing) => String.t(), + optional(:from_subscription) => Stripe.id(), + optional(:invoice_settings) => %{ + optional(:days_until_due) => non_neg_integer + }, + optional(:phases) => [ + %{ + :plans => [ + %{ + :plan => Stripe.id() | Stripe.Plan.t(), + optional(:quantity) => non_neg_integer + } + ], + optional(:application_fee_percent) => non_neg_integer, + optional(:coupon) => String.t(), + optional(:end_date) => Stripe.timestamp(), + optional(:iterations) => non_neg_integer, + optional(:start_date) => Stripe.timestamp(), + optional(:tax_percent) => float, + optional(:trial) => boolean(), + optional(:trial_end) => Stripe.timestamp() + } + ], + optional(:renewal_behavior) => String.t(), + optional(:renewal_interval) => %{ + :renewal_interval => String.t(), + :length => non_neg_integer + }, + optional(:start_date) => Stripe.timestamp() + } + def create(params, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint) + |> put_params(params) + |> put_method(:post) + |> make_request() + end + + @doc """ + Retrieve a subscription schedule. + """ + @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 subscription schedule. + Takes the `id` and a map of changes. + """ + @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: %{ + optional(:billing) => String.t(), + optional(:invoice_settings) => %{ + optional(:days_until_due) => non_neg_integer + }, + optional(:phases) => [ + %{ + :plans => [ + %{ + :plan => Stripe.id() | Stripe.Plan.t(), + optional(:quantity) => non_neg_integer + } + ], + optional(:application_fee_percent) => non_neg_integer, + optional(:coupon) => String.t(), + optional(:end_date) => Stripe.timestamp(), + optional(:iterations) => non_neg_integer, + optional(:start_date) => Stripe.timestamp(), + optional(:tax_percent) => float, + optional(:trial) => boolean(), + optional(:trial_end) => Stripe.timestamp() + } + ], + optional(:renewal_behavior) => String.t(), + optional(:prorate) => boolean(), + optional(:renewal_interval) => %{ + :renewal_interval => String.t(), + :length => non_neg_integer + } + } + 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 """ + Cancels a subscription schedule and its associated subscription immediately + (if the subscription schedule has an active subscription). A subscription + schedule can only be canceled if its status is not_started or active. + + Takes the subscription schedule `id`. + """ + + @spec cancel(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + def cancel(id, opts \\ []) when is_list(opts) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/cancel") + |> put_method(:post) + |> make_request() + end + + @doc """ + Releases a subscription schedule + + Takes the subscription schedule `id`. + """ + + @spec release(Stripe.id() | t, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + def release(id, opts \\ []) when is_list(opts) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/release") + |> put_method(:post) + |> make_request() + end + + @doc """ + Retrieves the list of your subscription schedules. + """ + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + when params: %{ + optional(:canceled_at) => Stripe.date_query(), + optional(:completed_at) => Stripe.date_query(), + optional(:created) => Stripe.date_query(), + optional(:customer) => Stripe.Customer.t() | Stripe.id(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:released_at) => Stripe.date_query(), + optional(:scheduled) => boolean(), + optional(:starting_after) => t | Stripe.id() + } + def list(params \\ %{}, opts \\ []) do + new_request(opts) + |> prefix_expansions() + |> put_endpoint(@plural_endpoint) + |> put_method(:get) + |> put_params(params) + |> make_request() + end +end diff --git a/lib/stripe/subscriptions/tax_rate.ex b/lib/stripe/subscriptions/tax_rate.ex new file mode 100644 index 000000000..3750685a5 --- /dev/null +++ b/lib/stripe/subscriptions/tax_rate.ex @@ -0,0 +1,122 @@ +defmodule Stripe.TaxRate do + @moduledoc """ + Work with Stripe TaxRate objects. + + """ + + use Stripe.Entity + import Stripe.Request + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + active: boolean, + created: Stripe.timestamp(), + description: String.t() | nil, + display_name: String.t(), + inclusive: boolean, + jurisdiction: String.t() | nil, + livemode: boolean, + metadata: Stripe.Types.metadata(), + percentage: integer + } + + defstruct [ + :id, + :object, + :active, + :created, + :description, + :display_name, + :inclusive, + :jurisdiction, + :livemode, + :metadata, + :percentage + ] + + @plural_endpoint "tax_rates" + + @doc """ + Create a tax rate. + """ + @spec create(params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + :percentage => number, + :display_name => String.t(), + :inclusive => boolean, + optional(:active) => boolean, + optional(:description) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:jurisdiction) => 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 tax rate. + """ + @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 tax rate. + + Takes the `id` and a map of changes. + """ + @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:percentage) => number, + optional(:display_name) => String.t(), + optional(:inclusive) => boolean, + optional(:active) => boolean, + optional(:description) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:jurisdiction) => 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 tax rates. + """ + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + when params: + %{ + optional(:active) => boolean, + optional(:created) => Stripe.date_query(), + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:inclusive) => boolean, + optional(:percentage) => number, + optional(:starting_after) => t | Stripe.id() + } + | %{} + def list(params \\ %{}, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint) + |> put_method(:get) + |> put_params(params) + |> cast_to_id([:ending_before, :starting_after]) + |> make_request() + end +end diff --git a/lib/stripe/subscriptions/usage.ex b/lib/stripe/subscriptions/usage.ex new file mode 100644 index 000000000..57da2c4f4 --- /dev/null +++ b/lib/stripe/subscriptions/usage.ex @@ -0,0 +1,73 @@ +defmodule Stripe.SubscriptionItem.Usage do + @moduledoc """ + Work with Stripe usage record objects. + + Stripe API reference: https://stripe.com/docs/api/usage_records + """ + + use Stripe.Entity + import Stripe.Request + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + invoice: Stripe.id() | nil, + livemode: boolean, + quantity: non_neg_integer, + subscription_item: Stripe.id() | Stripe.SubscriptionItem.t(), + timestamp: Stripe.timestamp() + } + + defstruct [ + :id, + :object, + :invoice, + :livemode, + :quantity, + :subscription_item, + :timestamp + ] + + @plural_endpoint "subscription_items" + + @doc """ + Creates a usage record for a specified subscription item id and date, and fills it with a quantity. + """ + def create(id, params, opts \\ []) + + @doc """ + Creates a usage record for a specified subscription item id and date, and fills it with a quantity. + """ + @spec create(Stripe.id(), params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + when params: %{ + :quantity => float | pos_integer | 0, + :timestamp => Stripe.timestamp() | non_neg_integer, + optional(:action) => String.t() + } + def create(id, params, opts) do + new_request(opts) + |> put_endpoint("#{@plural_endpoint}/#{id}/usage_records") + |> put_method(:post) + |> put_params(params) + |> make_request() + end + + @doc """ + List all subscription item period summaries + """ + @spec list(Stripe.id(), params, Stripe.options()) :: + {:ok, Stripe.List.t(t)} | {:error, Stripe.Error.t()} + when params: %{ + optional(:ending_before) => t | Stripe.id(), + optional(:limit) => 1..100, + optional(:starting_after) => t | Stripe.id() + } + def list(id, params \\ %{}, opts \\ []) do + new_request(opts) + |> put_endpoint("#{@plural_endpoint}/#{id}/usage_record_summaries") + |> put_method(:get) + |> put_params(params) + |> cast_to_id([:ending_before, :starting_after]) + |> make_request() + end +end diff --git a/lib/stripe/types.ex b/lib/stripe/types.ex index 1f2fd8c1d..1cac6df94 100644 --- a/lib/stripe/types.ex +++ b/lib/stripe/types.ex @@ -12,6 +12,12 @@ defmodule Stripe.Types do state: String.t() | nil } + @type dob :: %{ + day: 1..31 | nil, + month: 1..12 | nil, + year: pos_integer | nil + } + @type fee :: %{ amount: integer, application: String.t() | nil, @@ -20,6 +26,16 @@ defmodule Stripe.Types do type: String.t() } + @type japan_address :: %{ + city: String.t() | nil, + country: String.t() | nil, + line1: String.t() | nil, + line2: String.t() | nil, + postal_code: String.t() | nil, + state: String.t() | nil, + town: String.t() | nil + } + @type location :: %{ country: String.t(), source: String.t(), @@ -33,9 +49,17 @@ defmodule Stripe.Types do @type shipping :: %{ address: Stripe.Types.address(), carrier: String.t() | nil, + eta: Stripe.timestamp() | nil, name: String.t(), phone: String.t() | nil, - tracking_number: String.t() | nil + status: String.t() | nil, + tracking_number: String.t() | nil, + tracking_url: String.t() | nil + } + + @type subscription_billing_thresholds :: %{ + amount_gte: integer | nil, + reset_billing_cycle_anchor: boolean | nil } @type tax :: %{ @@ -46,13 +70,13 @@ defmodule Stripe.Types do @type tax_info :: %{ type: String.t(), - tax_id: String.t() | nil, - } + tax_id: String.t() | nil + } @type tax_info_verification :: %{ status: String.t() | nil, - verified_name: String.t() | nil, - } + verified_name: String.t() | nil + } @type transfer_schedule :: %{ delay_days: non_neg_integer, diff --git a/lib/stripe/util.ex b/lib/stripe/util.ex index 9374de7e3..59e4fbca7 100644 --- a/lib/stripe/util.ex +++ b/lib/stripe/util.ex @@ -51,13 +51,23 @@ defmodule Stripe.Util do def atomize_key(k), do: k @spec object_name_to_module(String.t()) :: module + def object_name_to_module("checkout.session"), do: Stripe.Session + def object_name_to_module("file"), do: Stripe.FileUpload + def object_name_to_module("issuing.authorization"), do: Stripe.Issuing.Authorization + def object_name_to_module("issuing.card"), do: Stripe.Issuing.Card + def object_name_to_module("issuing.card_details"), do: Stripe.Issuing.CardDetails + def object_name_to_module("issuing.cardholder"), do: Stripe.Issuing.Cardholder + def object_name_to_module("issuing.dispute"), do: Stripe.Issuing.Dispute + def object_name_to_module("issuing.transaction"), do: Stripe.Issuing.Transaction + def object_name_to_module("tax_id"), do: Stripe.TaxID + def object_name_to_module(object_name) do module_name = object_name |> String.split("_") |> Enum.map_join("", &String.capitalize/1) - Module.concat("Stripe", module_name) + Module.concat(Stripe, module_name) end @spec module_to_string(module) :: String.t() diff --git a/lib/stripe/webhook.ex b/lib/stripe/webhook.ex index e69a53773..7984f0297 100644 --- a/lib/stripe/webhook.ex +++ b/lib/stripe/webhook.ex @@ -141,7 +141,7 @@ defmodule Stripe.Webhook do defp convert_to_event!(payload) do payload - |> Poison.decode!() + |> Stripe.API.json_library().decode!() |> Stripe.Converter.convert_result() end end diff --git a/mix.exs b/mix.exs index a37c39c77..36d768c64 100644 --- a/mix.exs +++ b/mix.exs @@ -10,7 +10,7 @@ defmodule Stripe.Mixfile do plt_add_apps: [:mix], plt_file: {:no_warn, "priv/plts/stripity_stripe.plt"} ], - elixir: "~> 1.5", + elixir: "~> 1.7", package: package(), elixirc_paths: elixirc_paths(Mix.env()), preferred_cli_env: [ @@ -20,8 +20,14 @@ defmodule Stripe.Mixfile do "coveralls.html": :test ], test_coverage: [tool: ExCoveralls], - version: "2.2.1", - source_url: "https://github.com/code-corps/stripity_stripe/" + version: "2.4.0", + source_url: "https://github.com/code-corps/stripity_stripe/", + docs: [ + main: "readme", + extras: ["README.md"], + groups_for_modules: groups_for_modules(), + nest_modules_by_prefix: nest_modules_by_prefix() + ] ] end @@ -50,20 +56,19 @@ defmodule Stripe.Mixfile do ] end - defp apps(:test), do: [:bypass | apps()] + defp apps(:test), do: apps() defp apps(_), do: apps() - defp apps(), do: [:hackney, :logger, :poison, :uri_query] + defp apps(), do: [:hackney, :logger, :jason, :uri_query] defp deps do [ - {:bypass, "~> 1.0", only: :test}, - {:dialyxir, "1.0.0-rc.6", only: [:dev, :test], runtime: false}, - {:ex_doc, "~> 0.20", only: :dev}, - {:excoveralls, "~> 0.8.1", only: :test}, - {:hackney, "~> 1.13"}, + {:dialyxir, "1.0.0-rc.4", only: [:dev, :test], runtime: false}, + {:ex_doc, "~> 0.20.2", only: :dev}, + {:excoveralls, "~> 0.11.1", only: :test}, + {:hackney, "~> 1.15"}, {:inch_ex, "~> 2.0", only: [:dev, :test]}, - {:mox, "~> 0.5", only: :test}, - {:poison, ">= 3.0.0"}, + {:mox, "~> 0.4", only: :test}, + {:jason, "~> 1.1"}, {:uri_query, "~> 0.1.2"}, {:exexec, "~> 0.1.0", only: :test} ] @@ -85,4 +90,96 @@ defmodule Stripe.Mixfile do maintainers: ["Dan Matthews", "Josh Smith", "Nikola Begedin", "Scott Newcomer"] ] end + + defp groups_for_modules do + [ + "Core Resources": [ + Stripe.Balance, + Stripe.BalanceTransaction, + Stripe.Charge, + Stripe.Customer, + Stripe.Dispute, + Stripe.Event, + Stripe.FileUpload, + Stripe.PaymentIntent, + Stripe.Payout, + Stripe.Product, + Stripe.Refund, + Stripe.TaxID, + Stripe.Token + ], + "Payment Methods": [ + Stripe.BankAccount, + Stripe.Card, + Stripe.PaymentMethod, + Stripe.Source + ], + Checkout: [ + Stripe.Session + ], + Billing: [ + Stripe.Coupon, + Stripe.CreditNote, + Stripe.Discount, + Stripe.Invoice, + Stripe.Invoiceitem, + Stripe.LineItem, + Stripe.Product, + Stripe.Plan, + Stripe.Subscription, + Stripe.SubscriptionItem, + Stripe.SubscriptionItem.Usage, + Stripe.SubscriptionSchedule, + Stripe.TaxRate + ], + Connect: [ + Stripe.Account, + Stripe.ApplicationFee, + Stripe.CountrySpec, + Stripe.ExternalAccount, + Stripe.FeeRefund, + Stripe.LoginLink, + Stripe.Connect.OAuth, + Stripe.Connect.OAuth.AuthorizeResponse, + Stripe.Connect.OAuth.TokenResponse, + Stripe.Connect.OAuth.DeauthorizeResponse, + Stripe.Recipient, + Stripe.Transfer, + Stripe.TransferReversal + ], + Fraud: [ + Stripe.Review + ], + Issuing: [ + Stripe.Issuing.Authorization, + Stripe.Issuing.Card, + Stripe.Issuing.CardDetails, + Stripe.Issuing.Cardholder, + Stripe.Issuing.Dispute, + Stripe.Issuing.Transaction, + Stripe.Issuing.Types + ], + "Relay/Orders": [ + Stripe.Order, + Stripe.OrderItem, + Stripe.OrderReturn, + Stripe.Relay.Product, + Stripe.Sku + ], + Utilities: [ + Stripe.Config, + Stripe.Converter, + Stripe.Types + ] + ] + end + + def nest_modules_by_prefix() do + [ + Stripe.Connect.OAuth, + Stripe.Connect.OAuth.AuthorizeResponse, + Stripe.Connect.OAuth.TokenResponse, + Stripe.Connect.OAuth.DeauthorizeResponse + ] + end end diff --git a/mix.lock b/mix.lock index 42ce0fd98..97f857bed 100644 --- a/mix.lock +++ b/mix.lock @@ -1,44 +1,25 @@ %{ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, - "bypass": {:hex, :bypass, "1.0.0", "b78b3dcb832a71aca5259c1a704b2e14b55fd4e1327ff942598b4e7d1a7ad83d", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm"}, - "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, - "cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, - "cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"}, - "dialyxir": {:hex, :dialyxir, "1.0.0-rc.6", "78e97d9c0ff1b5521dd68041193891aebebce52fc3b93463c0a6806874557d7d", [:mix], [{:erlex, "~> 0.2.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, + "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "1.0.0-rc.4", "71b42f5ee1b7628f3e3a6565f4617dfb02d127a0499ab3e72750455e986df001", [:mix], [{:erlex, "~> 0.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"}, "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, - "erlex": {:hex, :erlex, "0.2.2", "cb0e6878fdf86dc63509eaf2233a71fa73fc383c8362c8ff8e8b6f0c2bb7017c", [:mix], [], "hexpm"}, + "erlex": {:hex, :erlex, "0.1.6", "c01c889363168d3fdd23f4211647d8a34c0f9a21ec726762312e08e083f3d47e", [:mix], [], "hexpm"}, "erlexec": {:hex, :erlexec, "1.9.3", "3d72ac65424ced35b9658a50e5a0c9dbd5f383e28ac9096e557f0d62926dd8e4", [:rebar3], [], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, - "exactor": {:hex, :exactor, "2.2.1", "89bc6798f7ed5b5a090f8e4a0b6997478a3266e831d2ff31677ac764ffa75973", [:mix], []}, - "excoveralls": {:hex, :excoveralls, "0.8.1", "0bbf67f22c7dbf7503981d21a5eef5db8bbc3cb86e70d3798e8c802c74fa5e27", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "excoveralls": {:hex, :excoveralls, "0.11.1", "dd677fbdd49114fdbdbf445540ec735808250d56b011077798316505064edb2c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, "exexec": {:hex, :exexec, "0.1.0", "4061bffc3845cb1fcc68eb68943335c75d64cfb4b4553c78f41be457df4a8d34", [:mix], [{:erlexec, "~> 1.7", [hex: :erlexec, optional: false]}]}, - "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"}, - "exvcr": {:hex, :exvcr, "0.8.1", "4f46bb82e1c4eaa3d7dabec058a80fdaa7dbbcce4fa56e4a6a5885f662e5b5b1", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, optional: false]}, {:exjsx, "~> 3.2", [hex: :exjsx, optional: false]}, {:httpoison, "~> 0.8", [hex: :httpoison, optional: true]}, {:httpotion, "~> 3.0", [hex: :httpotion, optional: true]}, {:ibrowse, "~> 4.2.2", [hex: :ibrowse, optional: true]}, {:meck, "~> 0.8.3", [hex: :meck, optional: false]}]}, - "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "httpoison": {:hex, :httpoison, "0.9.0", "68187a2daddfabbe7ca8f7d75ef227f89f0e1507f7eecb67e4536b3c516faddb", [:mix], [{:hackney, "~> 1.6.0", [hex: :hackney, optional: false]}]}, - "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "inch_ex": {:hex, :inch_ex, "2.0.0", "24268a9284a1751f2ceda569cd978e1fa394c977c45c331bb52a405de544f4de", [:mix], [{:bunt, "~> 0.2", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, - "jsex": {:hex, :jsex, "2.0.0"}, - "json": {:hex, :json, "0.3.2"}, - "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"}, "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, - "meck": {:hex, :meck, "0.8.4", "59ca1cd971372aa223138efcf9b29475bde299e1953046a0c727184790ab1520", [:rebar, :make], []}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, - "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, - "mock": {:hex, :mock, "0.1.3", "657937b03f88fce89b3f7d6becc9f1ec1ac19c71081aeb32117db9bc4d9b3980", [:mix], [{:meck, "~> 0.8.2", [hex: :meck, optional: false]}]}, - "mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, + "mox": {:hex, :mox, "0.4.0", "7f120840f7d626184a3d65de36189ca6f37d432e5d63acd80045198e4c5f7e6e", [:mix], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, - "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, - "plug": {:hex, :plug, "1.8.2", "0bcce1daa420f189a6491f3940cc77ea7fb1919761175c9c3b59800d897440fc", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.0.2", "6055f16868cc4882b24b6e1d63d2bada94fb4978413377a3b32ac16c18dffba2", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, - "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm"}, - "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, - "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, "uri_query": {:hex, :uri_query, "0.1.2", "ae35b83b472f3568c2c159eee3f3ccf585375d8a94fb5382db1ea3589e75c3b4", [:mix], []}, } diff --git a/test/fixtures/review_opened.json b/test/fixtures/review_opened.json new file mode 100644 index 000000000..d572b4052 --- /dev/null +++ b/test/fixtures/review_opened.json @@ -0,0 +1,42 @@ +{ + "id": "evt_1EAFvk73iNyGMfguDACcQT5X", + "object": "event", + "account": "acct_445dwy73iNyGMfgu", + "api_version": "2019-02-19", + "created": 1551702604, + "data": { + "object": { + "id": "prv_1EAFvj73iNyGMfgu81yzEb0D", + "object": "review", + "billing_zip": null, + "charge": "ch_1EAFvj73iNyGMfgutk44a8nD", + "closed_reason": null, + "created": 1551702603, + "ip_address": "172.16.1.4", + "ip_address_location": { + "city": "Samara", + "country": "RU", + "latitude": 34, + "longitude": 33, + "region": "65" + }, + "livemode": false, + "open": true, + "opened_reason": "rule", + "reason": "rule", + "session": { + "browser": "Firefox", + "device": "Other", + "platform": "Mac OS X 10.14", + "version": "65.0" + } + } + }, + "livemode": false, + "pending_webhooks": 4, + "request": { + "id": "req_3zmMCPEWZ2R24x", + "idempotency_key": "1551702602859892291_xgpCTQ" + }, + "type": "review.opened" +} diff --git a/test/fixtures/session.json b/test/fixtures/session.json new file mode 100644 index 000000000..fa2247703 --- /dev/null +++ b/test/fixtures/session.json @@ -0,0 +1,5 @@ +{ + "id": "CdWP8EBmSp1tJNIw4ZLF6w3XKd8MNKkEvlnSK7QmwFlDZ8rrjqBn9VI9vKiVdhfE", + "livemode": false, + "object": "checkout.session" +} diff --git a/test/stripe/api_test.exs b/test/stripe/api_test.exs index 216014847..bc1e9db96 100644 --- a/test/stripe/api_test.exs +++ b/test/stripe/api_test.exs @@ -12,8 +12,31 @@ defmodule Stripe.APITest do verify_on_exit!() Stripe.APIMock - |> expect(:oauth_request, fn(method, _endpoint, _body) -> method end) + |> expect(:oauth_request, fn method, _endpoint, _body -> method end) assert Stripe.APIMock.oauth_request(:post, "www", %{body: "body"}) == :post end + + test "oauth_request sets authorization header for deauthorize request" do + defmodule HackneyMock do + def request(_, _, headers, _, _) do + kv_headers = + headers + |> Enum.reduce(%{}, fn {k, v}, acc -> Map.put(acc, k, v) end) + + {:ok, 200, headers, Jason.encode!(kv_headers)} + end + end + + Application.put_env(:stripity_stripe, :http_module, HackneyMock) + + {:ok, body} = Stripe.API.oauth_request(:post, "deauthorize", %{}) + assert body["Authorization"] == "Bearer sk_test_123" + + {:ok, body} = Stripe.API.oauth_request(:post, "deauthorize", %{}, "1234") + assert body["Authorization"] == "Bearer 1234" + + {:ok, body} = Stripe.API.oauth_request(:post, "token", %{}) + assert Map.keys(body) |> Enum.member?("Authorization") == false + end end diff --git a/test/stripe/checkout/session_test.exs b/test/stripe/checkout/session_test.exs new file mode 100644 index 000000000..7f1fc860e --- /dev/null +++ b/test/stripe/checkout/session_test.exs @@ -0,0 +1,14 @@ +defmodule Stripe.SessionTest do + use Stripe.StripeCase, async: true + + test "is creatable" do + params = %{ + cancel_url: "https://stripe.com", + payment_method_types: ["card"], + success_url: "https://stripe.com" + } + + assert {:ok, %Stripe.Session{}} = Stripe.Session.create(params) + assert_stripe_requested(:post, "/v1/checkout/sessions") + end +end diff --git a/test/stripe/config_test.exs b/test/stripe/config_test.exs new file mode 100644 index 000000000..d7b9ffb6b --- /dev/null +++ b/test/stripe/config_test.exs @@ -0,0 +1,51 @@ +defmodule Stripe.ConfigTest do + use ExUnit.Case + + defmodule ValueExpansionTestModule do + def value do + "test-test" + end + end + + test "returns the requested configuration value" do + Application.put_env(:stripity_stripe, :__test, "test-test") + assert(Stripe.Config.resolve(:__test) == "test-test") + end + + test "evaluates functions" do + Application.put_env(:stripity_stripe, :__test, fn -> "test-test" end) + assert(Stripe.Config.resolve(:__test) == "test-test") + end + + test "applies tuples" do + Application.put_env( + :stripity_stripe, + :__test, + {ValueExpansionTestModule, :value, []} + ) + + assert(Stripe.Config.resolve(:__test) == "test-test") + end + + test "if no value exists for the given key it uses the default value" do + Application.put_env( + :stripity_stripe, + :__test, + {ValueExpansionTestModule, :value, []} + ) + + assert(Stripe.Config.resolve(:__fake_test, "test-test") == "test-test") + end + + test "raises if the key isn't an atom" do + Application.put_env( + :stripity_stripe, + :__test, + {ValueExpansionTestModule, :value, []} + ) + + assert_raise ArgumentError, ~r/to be an atom/, fn -> + Stripe.Config.resolve("__test", "test-test") + end + end +end diff --git a/test/stripe/connect/account_test.exs b/test/stripe/connect/account_test.exs index f4d4a59a9..80f9cb3cf 100644 --- a/test/stripe/connect/account_test.exs +++ b/test/stripe/connect/account_test.exs @@ -38,12 +38,13 @@ defmodule Stripe.AccountTest do test "is rejectable" do {:ok, account} = Stripe.Account.create(%{metadata: %{}, type: "standard"}) + assert_stripe_requested(:post, "/v1/accounts") + assert {:ok, %Stripe.Account{} = rejected_account} = Stripe.Account.reject(account, "terms_of_service") assert_stripe_requested(:post, "/v1/accounts/#{account.id}/reject") assert account.id == rejected_account.id - refute rejected_account.transfers_enabled refute rejected_account.charges_enabled end diff --git a/test/stripe/connect/external_account_test.exs b/test/stripe/connect/external_account_test.exs index 22f313757..e4af4ab16 100644 --- a/test/stripe/connect/external_account_test.exs +++ b/test/stripe/connect/external_account_test.exs @@ -3,7 +3,9 @@ defmodule Stripe.ExternalAccountTest do describe "create/2" do test "creates a bank account for an account" do - {:ok, _} = Stripe.ExternalAccount.create(%{account: "acct_123", token: "tok_stripetestbank"}) + {:ok, _} = + Stripe.ExternalAccount.create(%{account: "acct_123", token: "tok_stripetestbank"}) + assert_stripe_requested(:post, "/v1/accounts/acct_123/external_accounts") end @@ -52,14 +54,18 @@ defmodule Stripe.ExternalAccountTest do describe "list/3" do @tag :skip test "lists all bank accounts for an account" do - {:ok, %Stripe.List{data: bank_accounts}} = Stripe.ExternalAccount.list(:bank_account, %{account: "acct_123"}) + {:ok, %Stripe.List{data: bank_accounts}} = + Stripe.ExternalAccount.list(:bank_account, %{account: "acct_123"}) + assert_stripe_requested(:get, "/v1/accounts/acct_123/external_accounts?object=bank_account") assert is_list(bank_accounts) end @tag :skip test "lists all cards for an account" do - {:ok, %Stripe.List{data: cards}} = Stripe.ExternalAccount.list(:card, %{account: "acct_123"}) + {:ok, %Stripe.List{data: cards}} = + Stripe.ExternalAccount.list(:card, %{account: "acct_123"}) + assert_stripe_requested(:get, "/v1/accounts/acct_123/external_accounts?object=card") assert is_list(cards) end diff --git a/test/stripe/connect/fee_refund_test.exs b/test/stripe/connect/fee_refund_test.exs index 9feb00ed9..eceefac26 100644 --- a/test/stripe/connect/fee_refund_test.exs +++ b/test/stripe/connect/fee_refund_test.exs @@ -3,8 +3,9 @@ defmodule Stripe.FeeRefundTest do describe "retrieve/2" do test "retrieves a transfer" do - assert {:ok, %Stripe.FeeRefund{}} = Stripe.FeeRefund.retrieve("transf_123", "rev_123") - assert_stripe_requested(:get, "/v1/appliction_fees/trasnf_123/reversals/rev_123") + assert {:ok, %Stripe.FeeRefund{}} = Stripe.FeeRefund.retrieve("fee_123", "fr_123") + + assert_stripe_requested(:get, "/v1/application_fees/fee_123/refunds/fr_123") end end @@ -14,25 +15,25 @@ defmodule Stripe.FeeRefundTest do amount: 123 } - assert {:ok, %Stripe.FeeRefund{}} = Stripe.FeeRefund.create("transf_123", params) - assert_stripe_requested(:post, "/v1/appliction_fees/transf_123/reversals") + assert {:ok, %Stripe.FeeRefund{}} = Stripe.FeeRefund.create("fee_123", params) + assert_stripe_requested(:post, "/v1/application_fees/fee_123/refunds") end end describe "update/2" do test "updates a transfer" do params = %{metadata: %{foo: "bar"}} - assert {:ok, transfer} = Stripe.FeeRefund.update("trasnf_123", "rev_123", params) - assert_stripe_requested(:post, "/v1/appliction_fees/#{transfer.id}/reversals/rev_123") + assert {:ok, _transfer} = Stripe.FeeRefund.update("fee_123", "fr_123", params) + assert_stripe_requested(:post, "/v1/application_fees/fee_123/refunds/fr_123") end end describe "list/2" do - test "lists all appliction_fees refunds" do - assert {:ok, %Stripe.List{data: appliction_fees}} = Stripe.FeeRefund.list("transf_123") - assert_stripe_requested(:get, "/v1/appliction_fees/transf_123/reversals") - assert is_list(appliction_fees) - assert %Stripe.FeeRefund{} = hd(appliction_fees) + test "lists all application_fees refunds" do + assert {:ok, %Stripe.List{data: application_fees}} = Stripe.FeeRefund.list("fee_123") + assert_stripe_requested(:get, "/v1/application_fees/fee_123/refunds") + assert is_list(application_fees) + assert %Stripe.FeeRefund{} = hd(application_fees) end end end diff --git a/test/stripe/connect/oauth_test.exs b/test/stripe/connect/oauth_test.exs index 0f497c8d4..f104a6c46 100644 --- a/test/stripe/connect/oauth_test.exs +++ b/test/stripe/connect/oauth_test.exs @@ -7,16 +7,15 @@ defmodule Stripe.Connect.OAuthTest do verify_on_exit!() Stripe.APIMock - |> expect(:oauth_request, fn(method, _endpoint, _body) -> method end) + |> expect(:oauth_request, fn method, _endpoint, _body -> method end) Stripe.Connect.OAuthMock - |> expect(:token, fn(url) -> Stripe.APIMock.oauth_request(:post, url, %{body: "body"}) end) - |> expect(:deauthorize_url, fn(url) -> url end) - |> expect(:authorize_url, fn(%{url: url}) -> url end) + |> expect(:token, fn url -> Stripe.APIMock.oauth_request(:post, url, %{body: "body"}) end) + |> expect(:deauthorize_url, fn url -> url end) + |> expect(:authorize_url, fn %{url: url} -> url end) assert Stripe.Connect.OAuthMock.token("1234") == :post assert Stripe.Connect.OAuthMock.authorize_url(%{url: "www"}) == "www" assert Stripe.Connect.OAuthMock.deauthorize_url("www.google.com") == "www.google.com" end end - diff --git a/test/stripe/connect/person_test.exs b/test/stripe/connect/person_test.exs new file mode 100644 index 000000000..9452aedfe --- /dev/null +++ b/test/stripe/connect/person_test.exs @@ -0,0 +1,41 @@ +defmodule Stripe.PersonTest do + use Stripe.StripeCase, async: true + + describe "create/2" do + test "creates a person for an account" do + {:ok, _} = Stripe.Person.create(%{account: "acct_123", first_name: "Dennis"}) + + assert_stripe_requested(:post, "/v1/accounts/acct_123/persons") + end + end + + describe "retrieve/2" do + test "retrieves a person" do + {:ok, _} = Stripe.Person.retrieve("person_123", %{account: "acct_123", id: "person_123"}) + assert_stripe_requested(:get, "/v1/accounts/acct_123/persons/person_123") + end + end + + describe "update/2" do + test "updates a person" do + {:ok, _} = Stripe.Person.update("person_123", %{account: "acct_123"}) + assert_stripe_requested(:post, "/v1/accounts/acct_123/persons/person_123") + end + end + + describe "delete/2" do + test "deletes a person" do + {:ok, _} = Stripe.Person.delete("person_123", %{account: "acct_123"}) + assert_stripe_requested(:delete, "/v1/accounts/acct_123/persons/person_123") + end + end + + describe "list/3" do + test "lists all persons for an account" do + {:ok, %Stripe.List{data: persons}} = Stripe.Person.list(%{account: "acct_123"}) + + assert_stripe_requested(:get, "/v1/accounts/acct_123/persons") + assert is_list(persons) + end + end +end diff --git a/test/stripe/connect/recipient_test.exs b/test/stripe/connect/recipient_test.exs index a232e437c..be1a33872 100644 --- a/test/stripe/connect/recipient_test.exs +++ b/test/stripe/connect/recipient_test.exs @@ -7,7 +7,9 @@ defmodule Stripe.RecipientTest do end test "is creatable" do - assert {:ok, %Stripe.Recipient{}} = Stripe.Recipient.create(%{name: "scooter", type: "standard"}) + assert {:ok, %Stripe.Recipient{}} = + Stripe.Recipient.create(%{name: "scooter", type: "standard"}) + assert_stripe_requested(:post, "/v1/recipients") end diff --git a/test/stripe/connect/transfer_reversal_test.exs b/test/stripe/connect/transfer_reversal_test.exs index e63786338..1dcef6d67 100644 --- a/test/stripe/connect/transfer_reversal_test.exs +++ b/test/stripe/connect/transfer_reversal_test.exs @@ -3,8 +3,10 @@ defmodule Stripe.TransferReversalTest do describe "retrieve/2" do test "retrieves a transfer" do - assert {:ok, %Stripe.TransferReversal{}} = Stripe.TransferReversal.retrieve("transf_123", "rev_123") - assert_stripe_requested(:get, "/v1/transfers/trasnf_123/reversals/rev_123") + assert {:ok, %Stripe.TransferReversal{}} = + Stripe.TransferReversal.retrieve("tr_123", "trr_456") + + assert_stripe_requested(:get, "/v1/transfers/tr_123/reversals/trr_456") end end @@ -13,23 +15,28 @@ defmodule Stripe.TransferReversalTest do params = %{ amount: 123 } - assert {:ok, %Stripe.TransferReversal{}} = Stripe.TransferReversal.create("transf_123", params) - assert_stripe_requested(:post, "/v1/transfers/transf_123/reversals") + + assert {:ok, %Stripe.TransferReversal{}} = Stripe.TransferReversal.create("tr_123", params) + + assert_stripe_requested(:post, "/v1/transfers/tr_123/reversals") end end describe "update/2" do test "updates a transfer" do params = %{metadata: %{foo: "bar"}} - assert {:ok, transfer} = Stripe.TransferReversal.update("trasnf_123", "rev_123", params) - assert_stripe_requested(:post, "/v1/transfers/#{transfer.id}/reversals/rev_123") + + assert {:ok, _transfer_reversal} = + Stripe.TransferReversal.update("tr_123", "trr_456", params) + + assert_stripe_requested(:post, "/v1/transfers/tr_123/reversals/trr_456") end end describe "list/2" do test "lists all transfers" do - assert {:ok, %Stripe.List{data: transfers}} = Stripe.TransferReversal.list("transf_123") - assert_stripe_requested(:get, "/v1/transfers/transf_123/reversals") + assert {:ok, %Stripe.List{data: transfers}} = Stripe.TransferReversal.list("tr_123") + assert_stripe_requested(:get, "/v1/transfers/tr_123/reversals") assert is_list(transfers) assert %Stripe.TransferReversal{} = hd(transfers) end diff --git a/test/stripe/connect/transfer_test.exs b/test/stripe/connect/transfer_test.exs index 5a1a79300..0529cb45b 100644 --- a/test/stripe/connect/transfer_test.exs +++ b/test/stripe/connect/transfer_test.exs @@ -15,6 +15,7 @@ defmodule Stripe.TransferTest do currency: "curr_123", destination: "dest_123" } + assert {:ok, %Stripe.Transfer{}} = Stripe.Transfer.create(params) assert_stripe_requested(:post, "/v1/transfers") end diff --git a/test/stripe/converter_test.exs b/test/stripe/converter_test.exs index 0afe8bcc5..fa0a7893b 100644 --- a/test/stripe/converter_test.exs +++ b/test/stripe/converter_test.exs @@ -57,6 +57,57 @@ defmodule Stripe.ConverterTest do assert result == expected_result end + test "converts review.opened event properly" do + expected_result = %Stripe.Event{ + account: "acct_445dwy73iNyGMfgu", + api_version: "2019-02-19", + created: 1_551_702_604, + data: %{ + object: %Stripe.Review{ + billing_zip: nil, + charge: "ch_1EAFvj73iNyGMfgutk44a8nD", + closed_reason: nil, + created: 1_551_702_603, + id: "prv_1EAFvj73iNyGMfgu81yzEb0D", + ip_address: "172.16.1.4", + ip_address_location: %{ + city: "Samara", + country: "RU", + latitude: 34, + longitude: 33, + region: "65" + }, + livemode: false, + object: "review", + open: true, + opened_reason: "rule", + payment_intent: nil, + reason: "rule", + session: %{ + browser: "Firefox", + device: "Other", + platform: "Mac OS X 10.14", + version: "65.0" + } + } + }, + id: "evt_1EAFvk73iNyGMfguDACcQT5X", + livemode: false, + object: "event", + pending_webhooks: 4, + request: %{ + id: "req_3zmMCPEWZ2R24x", + idempotency_key: "1551702602859892291_xgpCTQ" + }, + type: "review.opened" + } + + fixture = Helper.load_fixture("review_opened.json") + result = Converter.convert_result(fixture) + + assert result == expected_result + end + test "converts a list response properly" do expected_result = %Stripe.List{ object: "list", @@ -243,4 +294,17 @@ defmodule Stripe.ConverterTest do assert result == expected_result end + + test "converts a checkout.session response properly" do + expected_result = %Stripe.Session{ + id: "CdWP8EBmSp1tJNIw4ZLF6w3XKd8MNKkEvlnSK7QmwFlDZ8rrjqBn9VI9vKiVdhfE", + livemode: false, + object: "checkout.session" + } + + fixture = Helper.load_fixture("session.json") + result = Converter.convert_result(fixture) + + assert result == expected_result + end end diff --git a/test/stripe/core_resources/charge_test.exs b/test/stripe/core_resources/charge_test.exs index b73d6d3f5..4e3ed4744 100644 --- a/test/stripe/core_resources/charge_test.exs +++ b/test/stripe/core_resources/charge_test.exs @@ -26,14 +26,30 @@ defmodule Stripe.ChargeTest do test "is captureable" do {:ok, %Stripe.Charge{} = charge} = Stripe.Charge.retrieve("ch_123") + assert_stripe_requested(:get, "/v1/charges/ch_123") + assert {:ok, %Stripe.Charge{}} = Stripe.Charge.capture(charge, %{amount: 1000}) assert_stripe_requested(:post, "/v1/charges/ch_123/capture") end + test "is captureable with idempotency opts" do + opts = [idempotency_key: "test"] + {:ok, %Stripe.Charge{} = charge} = Stripe.Charge.retrieve("ch_123") + assert_stripe_requested(:get, "/v1/charges/ch_123") + + assert {:ok, %Stripe.Charge{}} = Stripe.Charge.capture(charge, %{amount: 1000}, opts) + + assert_stripe_requested(:post, "/v1/charges/ch_123/capture", + headers: {"Idempotency-Key", "test"} + ) + end + test "is retrievable with expansions opts" do opts = [expand: ["balance_transaction"]] assert {:ok, %Stripe.Charge{}} = Stripe.Charge.retrieve("ch_123", opts) - assert_stripe_requested(:get, "/v1/charges/ch_123") + assert_stripe_requested(:get, "/v1/charges/ch_123", + query: %{"expand[0]": "balance_transaction"} + ) end end diff --git a/test/stripe/core_resources/customer_test.exs b/test/stripe/core_resources/customer_test.exs index 85b6d4c03..9b7f67e09 100644 --- a/test/stripe/core_resources/customer_test.exs +++ b/test/stripe/core_resources/customer_test.exs @@ -19,6 +19,8 @@ defmodule Stripe.CustomerTest do test "is deletable" do {:ok, customer} = Stripe.Customer.retrieve("cus_123") + assert_stripe_requested(:get, "/v1/customers/#{customer.id}") + assert {:ok, %Stripe.Customer{}} = Stripe.Customer.delete(customer) assert_stripe_requested(:delete, "/v1/customers/#{customer.id}") end @@ -33,6 +35,8 @@ defmodule Stripe.CustomerTest do describe "delete_discount/2" do test "deletes a customer's discount" do {:ok, customer} = Stripe.Customer.retrieve("sub_123") + assert_stripe_requested(:get, "/v1/customers/#{customer.id}") + assert {:ok, _} = Stripe.Customer.delete_discount("sub_123") assert_stripe_requested(:delete, "/v1/customers/#{customer.id}/discount") end diff --git a/test/stripe/core_resources/dispute_test.exs b/test/stripe/core_resources/dispute_test.exs index 20e3d3fdf..29c7cb023 100644 --- a/test/stripe/core_resources/dispute_test.exs +++ b/test/stripe/core_resources/dispute_test.exs @@ -14,6 +14,8 @@ defmodule Stripe.DisputeTest do test "is closeable" do {:ok, dispute} = Stripe.Dispute.retrieve("cus_123") + assert_stripe_requested(:get, "/v1/disputes/#{dispute.id}") + assert {:ok, %Stripe.Dispute{}} = Stripe.Dispute.close(dispute) assert_stripe_requested(:post, "/v1/disputes/#{dispute.id}/close") end diff --git a/test/stripe/core_resources/file_upload_test.exs b/test/stripe/core_resources/file_upload_test.exs index bf4e126a5..4699b54cc 100644 --- a/test/stripe/core_resources/file_upload_test.exs +++ b/test/stripe/core_resources/file_upload_test.exs @@ -4,25 +4,27 @@ defmodule Stripe.FileUploadTest do describe "create/2" do @tag :skip test "creates a file" do - assert {:ok, %Stripe.FileUpload{}} = Stripe.FileUpload.create(%{ - file: "@/path/to/a/file.jpg", - purpose: "dispute_evidence" - }) - assert_stripe_requested :post, "/v1/files" + assert {:ok, %Stripe.FileUpload{}} = + Stripe.FileUpload.create(%{ + file: "@/path/to/a/file.jpg", + purpose: "dispute_evidence" + }) + + assert_stripe_requested(:post, "/v1/files") end end describe "retrieve/2" do test "retrieves an file" do - assert {:ok, %Stripe.FileUpload{}} = Stripe.FileUpload.retrieve("file_123") - assert_stripe_requested :get, "/v1/files/file_123" + assert {:ok, _} = Stripe.FileUpload.retrieve("file_123") + assert_stripe_requested(:get, "/v1/files/file_123") end end describe "list/2" do test "lists all files" do - assert {:ok, %Stripe.List{data: [%Stripe.FileUpload{}]}} = Stripe.FileUpload.list() - assert_stripe_requested :get, "/v1/files" + assert {:ok, _} = Stripe.FileUpload.list() + assert_stripe_requested(:get, "/v1/files") end end end diff --git a/test/stripe/core_resources/payment_intent_test.exs b/test/stripe/core_resources/payment_intent_test.exs new file mode 100644 index 000000000..8d7f0dca6 --- /dev/null +++ b/test/stripe/core_resources/payment_intent_test.exs @@ -0,0 +1,52 @@ +defmodule Stripe.PaymentIntentTest do + use Stripe.StripeCase, async: true + + test "is listable" do + assert {:ok, %Stripe.List{data: payment_intents}} = Stripe.PaymentIntent.list() + assert_stripe_requested(:get, "/v1/payment_intents") + assert is_list(payment_intents) + assert %Stripe.PaymentIntent{} = hd(payment_intents) + end + + test "is retrievable" do + assert {:ok, %Stripe.PaymentIntent{}} = Stripe.PaymentIntent.retrieve("pi_123", %{}) + assert_stripe_requested(:get, "/v1/payment_intents/pi_123") + end + + test "is creatable" do + params = %{amount: 100, currency: "USD", payment_method_types: ["card"]} + assert {:ok, %Stripe.PaymentIntent{}} = Stripe.PaymentIntent.create(params) + assert_stripe_requested(:post, "/v1/payment_intents") + end + + test "is confirmable" do + assert {:ok, %Stripe.PaymentIntent{}} = Stripe.PaymentIntent.confirm("pi_123", %{}) + + assert_stripe_requested(:post, "/v1/payment_intents/pi_123/confirm") + end + + test "is cancelable" do + assert {:ok, %Stripe.PaymentIntent{}} = + Stripe.PaymentIntent.cancel("pi_123", %{cancellation_reason: "requested_by_customer"}) + + assert_stripe_requested(:post, "/v1/payment_intents/pi_123/cancel") + end + + test "is updateable" do + assert {:ok, %Stripe.PaymentIntent{}} = + Stripe.PaymentIntent.update("pi_123", %{metadata: %{foo: "bar"}}) + + assert_stripe_requested(:post, "/v1/payment_intents/pi_123") + end + + test "is captureable" do + {:ok, %Stripe.PaymentIntent{} = payment_intent} = Stripe.PaymentIntent.retrieve("pi_123", %{}) + + assert_stripe_requested(:get, "/v1/payment_intents/pi_123") + + assert {:ok, %Stripe.PaymentIntent{}} = + Stripe.PaymentIntent.capture(payment_intent, %{amount_to_capture: 1000}) + + assert_stripe_requested(:post, "/v1/payment_intents/pi_123/capture") + end +end diff --git a/test/stripe/core_resources/payout_test.exs b/test/stripe/core_resources/payout_test.exs index a2ddf36a6..a168913f8 100644 --- a/test/stripe/core_resources/payout_test.exs +++ b/test/stripe/core_resources/payout_test.exs @@ -35,7 +35,7 @@ defmodule Stripe.PayoutTest do describe "cancel/1" do test "cancels a payout" do assert {:ok, %Stripe.Payout{}} = Stripe.Payout.cancel("py_123") - assert_stripe_requested(:get, "/v1/payouts/cancel") + assert_stripe_requested(:post, "/v1/payouts/py_123/cancel") end end end diff --git a/test/stripe/core_resources/tax_id_test.exs b/test/stripe/core_resources/tax_id_test.exs new file mode 100644 index 000000000..71a0d0ec9 --- /dev/null +++ b/test/stripe/core_resources/tax_id_test.exs @@ -0,0 +1,31 @@ +defmodule Stripe.TaxIDTest do + use Stripe.StripeCase, async: true + + test "is listable" do + assert {:ok, %Stripe.List{data: charges}} = + Stripe.TaxID.list(%{customer: "cus_FDVoXj36NmFrao"}) + + assert_stripe_requested(:get, "/v1/customers/cus_FDVoXj36NmFrao/tax_ids") + assert is_list(charges) + assert %Stripe.TaxID{} = hd(charges) + end + + test "is retrievable" do + assert {:ok, %Stripe.TaxID{}} = + Stripe.TaxID.retrieve("txi_123456789", %{customer: "cus_FDVoXj36NmFrao"}) + + assert_stripe_requested(:get, "/v1/customers/cus_FDVoXj36NmFrao/tax_ids/txi_123456789") + end + + test "is creatable" do + params = %{customer: "cus_FDVoXj36NmFrao", type: "eu_vat", value: "DE123456789"} + assert {:ok, %Stripe.TaxID{}} = Stripe.TaxID.create(params) + assert_stripe_requested(:post, "/v1/customers/cus_FDVoXj36NmFrao/tax_ids") + end + + test "is deletable" do + params = %{customer: "cus_FDVoXj36NmFrao"} + assert {:ok, %Stripe.TaxID{}} = Stripe.TaxID.delete("txi_123456789", params) + assert_stripe_requested(:delete, "/v1/customers/cus_FDVoXj36NmFrao/tax_ids/txi_123456789") + end +end diff --git a/test/stripe/issuing/authorization_test.exs b/test/stripe/issuing/authorization_test.exs new file mode 100644 index 000000000..2d441afa6 --- /dev/null +++ b/test/stripe/issuing/authorization_test.exs @@ -0,0 +1,40 @@ +defmodule Stripe.Issuing.AuthorizationTest do + use Stripe.StripeCase, async: true + + test "is retrievable" do + assert {:ok, %Stripe.Issuing.Authorization{}} = + Stripe.Issuing.Authorization.retrieve("iauth_123") + + assert_stripe_requested(:get, "/v1/issuing/authorizations/iauth_123") + end + + test "is updateable" do + params = %{metadata: %{key: "value"}} + + assert {:ok, %Stripe.Issuing.Authorization{}} = + Stripe.Issuing.Authorization.update("iauth_123", params) + + assert_stripe_requested(:post, "/v1/issuing/authorizations/iauth_123") + end + + test "is approvable" do + assert {:ok, %Stripe.Issuing.Authorization{}} = + Stripe.Issuing.Authorization.approve("iauth_123") + + assert_stripe_requested(:post, "/v1/issuing/authorizations/iauth_123/approve") + end + + test "is declinable" do + assert {:ok, %Stripe.Issuing.Authorization{}} = + Stripe.Issuing.Authorization.decline("iauth_123") + + assert_stripe_requested(:post, "/v1/issuing/authorizations/iauth_123/decline") + end + + test "is listable" do + assert {:ok, %Stripe.List{data: authorizations}} = Stripe.Issuing.Authorization.list() + assert_stripe_requested(:get, "/v1/issuing/authorizations") + assert is_list(authorizations) + assert %Stripe.Issuing.Authorization{} = hd(authorizations) + end +end diff --git a/test/stripe/issuing/card_details_test.exs b/test/stripe/issuing/card_details_test.exs new file mode 100644 index 000000000..db8b07e2d --- /dev/null +++ b/test/stripe/issuing/card_details_test.exs @@ -0,0 +1,8 @@ +defmodule Stripe.Issuing.CardDetailsTest do + use Stripe.StripeCase, async: true + + test "is retrievable" do + assert {:ok, %Stripe.Issuing.CardDetails{}} = Stripe.Issuing.CardDetails.retrieve("ic_123") + assert_stripe_requested(:get, "/v1/issuing/cards/ic_123/details") + end +end diff --git a/test/stripe/issuing/card_test.exs b/test/stripe/issuing/card_test.exs new file mode 100644 index 000000000..2b5b82c2b --- /dev/null +++ b/test/stripe/issuing/card_test.exs @@ -0,0 +1,32 @@ +defmodule Stripe.Issuing.CardTest do + use Stripe.StripeCase, async: true + + test "is creatable" do + params = %{ + currency: "usd", + type: :virtual + } + + assert {:ok, %Stripe.Issuing.Card{}} = Stripe.Issuing.Card.create(params) + + assert_stripe_requested(:post, "/v1/issuing/cards") + end + + test "is retrievable" do + assert {:ok, %Stripe.Issuing.Card{}} = Stripe.Issuing.Card.retrieve("ic_123") + assert_stripe_requested(:get, "/v1/issuing/cards/ic_123") + end + + test "is updateable" do + params = %{metadata: %{key: "value"}} + assert {:ok, %Stripe.Issuing.Card{}} = Stripe.Issuing.Card.update("ic_123", params) + assert_stripe_requested(:post, "/v1/issuing/cards/ic_123") + end + + test "is listable" do + assert {:ok, %Stripe.List{data: cards}} = Stripe.Issuing.Card.list() + assert_stripe_requested(:get, "/v1/issuing/cards") + assert is_list(cards) + assert %Stripe.Issuing.Card{} = hd(cards) + end +end diff --git a/test/stripe/issuing/cardholder_test.exs b/test/stripe/issuing/cardholder_test.exs new file mode 100644 index 000000000..7413978ba --- /dev/null +++ b/test/stripe/issuing/cardholder_test.exs @@ -0,0 +1,45 @@ +defmodule Stripe.Issuing.CardholderTest do + use Stripe.StripeCase, async: true + + test "is creatable" do + params = %{ + name: "Jenny Rosen", + type: :individual, + billing: %{ + address: %{ + line1: "123 Fake St", + line2: "Apt 3", + city: "Beverly Hills", + state: "CA", + postal_code: "90210", + country: "US" + } + } + } + + assert {:ok, %Stripe.Issuing.Cardholder{}} = Stripe.Issuing.Cardholder.create(params) + + assert_stripe_requested(:post, "/v1/issuing/cardholders") + end + + test "is retrievable" do + assert {:ok, %Stripe.Issuing.Cardholder{}} = Stripe.Issuing.Cardholder.retrieve("ich_123") + assert_stripe_requested(:get, "/v1/issuing/cardholders/ich_123") + end + + test "is updateable" do + params = %{metadata: %{key: "value"}} + + assert {:ok, %Stripe.Issuing.Cardholder{}} = + Stripe.Issuing.Cardholder.update("ich_123", params) + + assert_stripe_requested(:post, "/v1/issuing/cardholders/ich_123") + end + + test "is listable" do + assert {:ok, %Stripe.List{data: cardholders}} = Stripe.Issuing.Cardholder.list() + assert_stripe_requested(:get, "/v1/issuing/cardholders") + assert is_list(cardholders) + assert %Stripe.Issuing.Cardholder{} = hd(cardholders) + end +end diff --git a/test/stripe/issuing/dispute_test.exs b/test/stripe/issuing/dispute_test.exs new file mode 100644 index 000000000..09bd089d1 --- /dev/null +++ b/test/stripe/issuing/dispute_test.exs @@ -0,0 +1,32 @@ +defmodule Stripe.Issuing.DisputeTest do + use Stripe.StripeCase, async: true + + test "is creatable" do + params = %{ + disputed_transaction: "ipi_123", + reason: :fraudulent + } + + assert {:ok, %Stripe.Issuing.Dispute{}} = Stripe.Issuing.Dispute.create(params) + + assert_stripe_requested(:post, "/v1/issuing/disputes") + end + + test "is retrievable" do + assert {:ok, %Stripe.Issuing.Dispute{}} = Stripe.Issuing.Dispute.retrieve("idp_123") + assert_stripe_requested(:get, "/v1/issuing/disputes/idp_123") + end + + test "is updateable" do + params = %{metadata: %{key: "value"}} + assert {:ok, %Stripe.Issuing.Dispute{}} = Stripe.Issuing.Dispute.update("idp_123", params) + assert_stripe_requested(:post, "/v1/issuing/disputes/idp_123") + end + + test "is listable" do + assert {:ok, %Stripe.List{data: disputes}} = Stripe.Issuing.Dispute.list() + assert_stripe_requested(:get, "/v1/issuing/disputes") + assert is_list(disputes) + assert %Stripe.Issuing.Dispute{} = hd(disputes) + end +end diff --git a/test/stripe/issuing/transaction_test.exs b/test/stripe/issuing/transaction_test.exs new file mode 100644 index 000000000..4550aabf7 --- /dev/null +++ b/test/stripe/issuing/transaction_test.exs @@ -0,0 +1,24 @@ +defmodule Stripe.Issuing.TransactionTest do + use Stripe.StripeCase, async: true + + test "is retrievable" do + assert {:ok, %Stripe.Issuing.Transaction{}} = Stripe.Issuing.Transaction.retrieve("ipi_123") + assert_stripe_requested(:get, "/v1/issuing/transactions/ipi_123") + end + + test "is updateable" do + params = %{metadata: %{key: "value"}} + + assert {:ok, %Stripe.Issuing.Transaction{}} = + Stripe.Issuing.Transaction.update("ipi_123", params) + + assert_stripe_requested(:post, "/v1/issuing/transactions/ipi_123") + end + + test "is listable" do + assert {:ok, %Stripe.List{data: transactions}} = Stripe.Issuing.Transaction.list() + assert_stripe_requested(:get, "/v1/issuing/transactions") + assert is_list(transactions) + assert %Stripe.Issuing.Transaction{} = hd(transactions) + end +end diff --git a/test/stripe/payment_methods/payment_method_test.exs b/test/stripe/payment_methods/payment_method_test.exs new file mode 100644 index 000000000..8302674e0 --- /dev/null +++ b/test/stripe/payment_methods/payment_method_test.exs @@ -0,0 +1,99 @@ +defmodule Stripe.PaymentMethodTest do + use Stripe.StripeCase, async: true + + describe "list/2" do + test "lists all cards" do + assert {:ok, %Stripe.List{data: cards}} = + Stripe.PaymentMethod.list(%{customer: "cus_123", type: "card"}) + + assert_stripe_requested(:get, "/v1/payment_methods?customer=cus_123&type=card") + assert is_list(cards) + end + + test "lists all cards with customer provided as struct" do + assert {:ok, %Stripe.List{data: cards}} = + Stripe.PaymentMethod.list(%{ + customer: %Stripe.Customer{id: "cus_123"}, + type: "card" + }) + + assert_stripe_requested(:get, "/v1/payment_methods?customer=cus_123&type=card") + assert is_list(cards) + end + end + + describe "create/2" do + test "creates a payment method" do + assert {:ok, _} = Stripe.PaymentMethod.create(%{type: "card"}) + assert_stripe_requested(:post, "/v1/payment_methods") + end + end + + describe "retrieve/1" do + test "retrieves a payment method" do + assert {:ok, _} = Stripe.PaymentMethod.retrieve("pm_123") + assert_stripe_requested(:get, "/v1/payment_methods/pm_123") + end + end + + describe "update/2" do + test "updates a payment method" do + assert {:ok, _} = Stripe.PaymentMethod.update("pm_123", %{}) + assert_stripe_requested(:post, "/v1/payment_methods/pm_123") + end + end + + describe "attach/2" do + test "attaches payment method to customer" do + assert {:ok, %Stripe.PaymentMethod{}} = + Stripe.PaymentMethod.attach(%{customer: "cus_123", payment_method: "pm_123"}) + + assert_stripe_requested(:post, "/v1/payment_methods/pm_123/attach", + body: %{customer: "cus_123"} + ) + end + + test "attaches payment method to customer with customer provided as struct" do + assert {:ok, %Stripe.PaymentMethod{}} = + Stripe.PaymentMethod.attach(%{ + customer: %Stripe.Customer{id: "cus_123"}, + payment_method: "pm_123" + }) + + assert_stripe_requested(:post, "/v1/payment_methods/pm_123/attach", + body: %{customer: "cus_123"} + ) + end + + test "attaches payment method to customer with payment method provided as struct" do + assert {:ok, %Stripe.PaymentMethod{}} = + Stripe.PaymentMethod.attach(%{ + customer: "cus_123", + payment_method: %Stripe.PaymentMethod{id: "pm_123"} + }) + + assert_stripe_requested(:post, "/v1/payment_methods/pm_123/attach", + body: %{customer: "cus_123"} + ) + end + end + + describe "detach/2" do + test "detaches payment method from customer" do + assert {:ok, %Stripe.PaymentMethod{}} = + Stripe.PaymentMethod.detach(%{customer: "cus_123", payment_method: "pm_123"}) + + assert_stripe_requested(:post, "/v1/payment_methods/pm_123/detach") + end + + test "detaches payment method from customer with payment method provided as struct" do + assert {:ok, %Stripe.PaymentMethod{}} = + Stripe.PaymentMethod.detach(%{ + customer: "cus_123", + payment_method: %Stripe.PaymentMethod{id: "pm_123"} + }) + + assert_stripe_requested(:post, "/v1/payment_methods/pm_123/detach") + end + end +end diff --git a/test/stripe/payment_methods/source_test.exs b/test/stripe/payment_methods/source_test.exs index 7fc72322a..600f0d5a7 100644 --- a/test/stripe/payment_methods/source_test.exs +++ b/test/stripe/payment_methods/source_test.exs @@ -24,8 +24,7 @@ defmodule Stripe.SourceTest do describe "attach/2" do test "attaches a source to a customer" do - assert {:ok, %Stripe.BankAccount{}} = - Stripe.Source.attach(%{customer: "cus_123", source: "src_123"}) + assert {:ok, _} = Stripe.Source.attach(%{customer: "cus_123", source: "src_123"}) assert_stripe_requested(:post, "/v1/customers/cus_123/sources") end @@ -33,8 +32,7 @@ defmodule Stripe.SourceTest do describe "detach/2" do test "detaches a source from a customer" do - assert {:ok, %Stripe.BankAccount{}} = - Stripe.Source.detach("src_123", %{customer: "cus_123"}) + assert {:ok, %{id: "src_123"}} = Stripe.Source.detach("src_123", %{customer: "cus_123"}) assert_stripe_requested(:delete, "/v1/customers/cus_123/sources/src_123") end diff --git a/test/stripe/relay/order_test.exs b/test/stripe/relay/order_test.exs index 6baff70e8..8a680ea3b 100644 --- a/test/stripe/relay/order_test.exs +++ b/test/stripe/relay/order_test.exs @@ -26,7 +26,7 @@ defmodule Stripe.OrderTest do describe "pay/3" do test "is payable" do assert {:ok, %Stripe.Order{}} = Stripe.Order.pay("order_123") - assert_stripe_requested(:pay, "/v1/orders/order_123/pay") + assert_stripe_requested(:post, "/v1/orders/order_123/pay") end @tag :skip @@ -36,14 +36,14 @@ defmodule Stripe.OrderTest do } assert {:ok, %Stripe.Order{}} = Stripe.Order.pay("order_123", params) - assert_stripe_requested(:pay, "/v1/orders/order_123/pay") + assert_stripe_requested(:post, "/v1/orders/order_123/pay") end end describe "return/3" do test "is returnable" do assert {:ok, %Stripe.OrderReturn{}} = Stripe.Order.return("order_123") - assert_stripe_requested(:pay, "/v1/orders/order_123/returns") + assert_stripe_requested(:post, "/v1/orders/order_123/returns") end end @@ -58,7 +58,7 @@ defmodule Stripe.OrderTest do test "is listable with params" do params = %{status: "paid"} assert {:ok, %Stripe.List{data: orders}} = Stripe.Order.list(params) - assert_stripe_requested(:get, "/v1/orders") + assert_stripe_requested(:get, "/v1/orders", query: params) assert is_list(orders) assert %Stripe.Order{} = hd(orders) end diff --git a/test/stripe/relay/product_test.exs b/test/stripe/relay/product_test.exs index a338c839b..ad4506c24 100644 --- a/test/stripe/relay/product_test.exs +++ b/test/stripe/relay/product_test.exs @@ -40,7 +40,7 @@ defmodule Stripe.Relay.ProductTest do test "lists all products with params" do params = %{active: false} assert {:ok, %Stripe.List{data: products}} = Stripe.Product.list(params) - assert_stripe_requested(:get, "/v1/products") + assert_stripe_requested(:get, "/v1/products", query: %{active: false}) assert is_list(products) assert %Stripe.Product{} = hd(products) end @@ -49,6 +49,8 @@ defmodule Stripe.Relay.ProductTest do describe "delete/1" do test "deletes a product" do {:ok, product} = Stripe.Product.retrieve("Plus") + assert_stripe_requested(:get, "/v1/products/#{product.id}") + assert {:ok, _} = Stripe.Product.delete("Plus") assert_stripe_requested(:delete, "/v1/products/#{product.id}") end diff --git a/test/stripe/relay/sku_test.exs b/test/stripe/relay/sku_test.exs index eb4639de5..70539c6e5 100644 --- a/test/stripe/relay/sku_test.exs +++ b/test/stripe/relay/sku_test.exs @@ -3,7 +3,15 @@ defmodule Stripe.SkuTest do test "is creatable" do inventory = %{type: "finite", quantity: 500} - assert {:ok, %Stripe.Sku{}} = Stripe.Sku.create(%{currency: "USD", product: "prod_123", price: 100, inventory: inventory}) + + assert {:ok, %Stripe.Sku{}} = + Stripe.Sku.create(%{ + currency: "USD", + product: "prod_123", + price: 100, + inventory: inventory + }) + assert_stripe_requested(:post, "/v1/skus") end @@ -26,7 +34,7 @@ defmodule Stripe.SkuTest do test "is deletable" do assert {:ok, %Stripe.Sku{}} = Stripe.Sku.delete("sku_123") - assert_stripe_requested(:delete, "/v1/skus/sku_123/delete") + assert_stripe_requested(:delete, "/v1/skus/sku_123") end test "is listable" do @@ -39,7 +47,6 @@ defmodule Stripe.SkuTest do test "is listable with params" do params = %{active: false, in_stock: false} assert {:ok, %Stripe.List{data: _skus}} = Stripe.Sku.list(params) - assert_stripe_requested(:get, "/v1/skus") + assert_stripe_requested(:get, "/v1/skus", query: params) end end - diff --git a/test/stripe/request_test.exs b/test/stripe/request_test.exs index 156dab106..c51614e18 100644 --- a/test/stripe/request_test.exs +++ b/test/stripe/request_test.exs @@ -27,14 +27,4 @@ defmodule Stripe.RequestTest do assert request.opts == opts end end - - describe "new_request/2" do - test "new_request/1 extracts headers from options and puts it on headers" do - new_request = Request.new_request([headers: %{foo: "bar"}]) - - assert new_request.headers == %{ - foo: "bar" - } - end - end end diff --git a/test/stripe/subscriptions/credit_note_test.exs b/test/stripe/subscriptions/credit_note_test.exs new file mode 100644 index 000000000..2654ec03a --- /dev/null +++ b/test/stripe/subscriptions/credit_note_test.exs @@ -0,0 +1,52 @@ +defmodule Stripe.CreditNoteTest do + use Stripe.StripeCase, async: true + doctest Stripe.CreditNote + + describe "create/2" do + test "creates a Credit Note for a customer" do + params = %{ + invoice: "in_173uNd4Wq104wst7Gf4dgq1Y", + amount: 500 + } + + assert {:ok, %Stripe.CreditNote{}} = Stripe.CreditNote.create(params) + assert_stripe_requested(:post, "/v1/credit_notes") + end + end + + describe "retrieve/2" do + test "retrieves a Credit Note" do + assert {:ok, %Stripe.CreditNote{}} = + Stripe.CreditNote.retrieve("cn_1EXwJk4Wq104wst7IISdh9ed") + + assert_stripe_requested(:get, "/v1/credit_notes/cn_1EXwJk4Wq104wst7IISdh9ed") + end + end + + describe "update/2" do + test "updates a Credit Note" do + params = %{metadata: %{foo: "bar"}} + assert {:ok, credit_note} = Stripe.CreditNote.update("cn_1EXwJk4Wq104wst7IISdh9ed", params) + assert_stripe_requested(:post, "/v1/credit_notes/#{credit_note.id}") + end + end + + describe "void/2" do + test "voids a CreditNote" do + {:ok, credit_note} = Stripe.CreditNote.retrieve("cn_1EXwJk4Wq104wst7IISdh9ed") + assert_stripe_requested(:get, "/v1/credit_notes/#{credit_note.id}") + + assert {:ok, %Stripe.CreditNote{}} = Stripe.CreditNote.void(credit_note) + assert_stripe_requested(:post, "/v1/credit_notes/#{credit_note.id}/void") + end + end + + describe "list/2" do + test "lists all Credit Notes" do + assert {:ok, %Stripe.List{data: credit_notes}} = Stripe.CreditNote.list() + assert_stripe_requested(:get, "/v1/credit_notes") + assert is_list(credit_notes) + assert %Stripe.CreditNote{} = hd(credit_notes) + end + end +end diff --git a/test/stripe/subscriptions/invoice_test.exs b/test/stripe/subscriptions/invoice_test.exs index 52460c825..26aa16fee 100644 --- a/test/stripe/subscriptions/invoice_test.exs +++ b/test/stripe/subscriptions/invoice_test.exs @@ -23,10 +23,16 @@ defmodule Stripe.InvoiceTest do assert_stripe_requested( :get, "/v1/invoices/upcoming", - query: %{customer: "cust_123", subscription: "sub_123"} + query: %{customer: "cus_123", subscription: "sub_123"} ) end + test "retrieves an upcoming invoice for a subscription" do + params = %{subscription: "sub_123"} + assert {:ok, %Stripe.Invoice{}} = Stripe.Invoice.upcoming(params) + assert_stripe_requested(:get, "/v1/invoices/upcoming", query: %{subscription: "sub_123"}) + end + test "retrieves an upcoming invoice for a customer with items" do items = [%{plan: "gold", quantity: 2}] params = %{customer: "cus_123", subscription_items: items} @@ -36,9 +42,9 @@ defmodule Stripe.InvoiceTest do :get, "/v1/invoices/upcoming", query: %{ - :customer => "cust_123", - :"susbscription_items[][plan]" => "gold", - :"subscription_items[][quantity]" => 2 + :customer => "cus_123", + :"subscription_items[0][plan]" => "gold", + :"subscription_items[0][quantity]" => 2 } ) end @@ -63,15 +69,29 @@ defmodule Stripe.InvoiceTest do end end + describe "finalize/3" do + test "finalizes an invoice" do + {:ok, invoice} = Stripe.Invoice.retrieve("in_123") + assert_stripe_requested(:get, "/v1/invoices/#{invoice.id}") + + assert {:ok, %Stripe.Invoice{} = _paid_invoice} = Stripe.Invoice.finalize(invoice, %{}) + assert_stripe_requested(:post, "/v1/invoices/#{invoice.id}/finalize") + end + end + describe "pay/3" do test "pays an invoice" do {:ok, invoice} = Stripe.Invoice.retrieve("in_123") + assert_stripe_requested(:get, "/v1/invoices/#{invoice.id}") + assert {:ok, %Stripe.Invoice{} = _paid_invoice} = Stripe.Invoice.pay(invoice, %{}) assert_stripe_requested(:post, "/v1/invoices/#{invoice.id}/pay") end test "pays an invoice with a specific source" do {:ok, invoice} = Stripe.Invoice.retrieve("in_123") + assert_stripe_requested(:get, "/v1/invoices/#{invoice.id}") + params = %{source: "src_123"} assert {:ok, %Stripe.Invoice{} = _paid_invoice} = Stripe.Invoice.pay(invoice, params) @@ -87,4 +107,46 @@ defmodule Stripe.InvoiceTest do assert %Stripe.Invoice{} = hd(invoices) end end + + describe "void/2" do + test "voids an invoice" do + {:ok, invoice} = Stripe.Invoice.retrieve("in_123") + assert_stripe_requested(:get, "/v1/invoices/#{invoice.id}") + + assert {:ok, %Stripe.Invoice{} = _voided_invoice} = Stripe.Invoice.void(invoice) + assert_stripe_requested(:post, "/v1/invoices/#{invoice.id}/void") + end + end + + describe "send/2" do + test "sends an invoice" do + {:ok, invoice} = Stripe.Invoice.retrieve("in_123") + assert_stripe_requested(:get, "/v1/invoices/#{invoice.id}") + + assert {:ok, %Stripe.Invoice{} = _sent_invoice} = Stripe.Invoice.send(invoice) + assert_stripe_requested(:post, "/v1/invoices/#{invoice.id}/send") + end + end + + describe "delete/2" do + test "deletes an invoice" do + {:ok, invoice} = Stripe.Invoice.retrieve("in_123") + assert_stripe_requested(:get, "/v1/invoices/#{invoice.id}") + + assert {:ok, %Stripe.Invoice{} = _sent_invoice} = Stripe.Invoice.delete(invoice) + assert_stripe_requested(:delete, "/v1/invoices/#{invoice.id}") + end + end + + describe "mark_as_uncollectible/2" do + test "marks an invoice as uncollectible" do + {:ok, invoice} = Stripe.Invoice.retrieve("in_123") + assert_stripe_requested(:get, "/v1/invoices/#{invoice.id}") + + assert {:ok, %Stripe.Invoice{} = _sent_invoice} = + Stripe.Invoice.mark_as_uncollectible(invoice) + + assert_stripe_requested(:post, "/v1/invoices/#{invoice.id}/mark_uncollectible") + end + end end diff --git a/test/stripe/subscriptions/invoiceitem_test.exs b/test/stripe/subscriptions/invoiceitem_test.exs index 013a7b0f2..e8bb0de5b 100644 --- a/test/stripe/subscriptions/invoiceitem_test.exs +++ b/test/stripe/subscriptions/invoiceitem_test.exs @@ -3,23 +3,35 @@ defmodule Stripe.InvoiceitemTest do describe "create/2" do test "creates an invoice" do - assert {:ok, %Stripe.Invoiceitem{}} = Stripe.Invoiceitem.create(%{customer: "cus_123", currency: "usd"}) + assert {:ok, %Stripe.Invoiceitem{}} = + Stripe.Invoiceitem.create(%{customer: "cus_123", currency: "usd"}) + assert_stripe_requested(:post, "/v1/invoiceitems") end end describe "retrieve/2" do test "retrieves an invoice" do - assert {:ok, %Stripe.Invoiceitem{}} = Stripe.Invoiceitem.retrieve("in_1234") - assert_stripe_requested(:get, "/v1/invoiceitems/in_1234") + assert {:ok, %Stripe.Invoiceitem{}} = Stripe.Invoiceitem.retrieve("ii_1234") + assert_stripe_requested(:get, "/v1/invoiceitems/ii_1234") end end - describe "update/2" do + describe "update/3" do test "updates an invoice" do params = %{metadata: %{key: "value"}} - assert {:ok, %Stripe.Invoiceitem{}} = Stripe.Invoiceitem.update("in_1234", params) - assert_stripe_requested(:post, "/v1/invoiceitems/in_1234") + assert {:ok, %Stripe.Invoiceitem{}} = Stripe.Invoiceitem.update("ii_1234", params) + assert_stripe_requested(:post, "/v1/invoiceitems/ii_1234") + end + end + + describe "delete/2" do + test "deletes an invoice" do + {:ok, invoiceitem} = Stripe.Invoiceitem.retrieve("ii_1234") + assert_stripe_requested(:get, "/v1/invoiceitems/#{invoiceitem.id}") + + assert {:ok, _} = Stripe.Invoiceitem.delete("ii_1234") + assert_stripe_requested(:delete, "/v1/invoiceitems/#{invoiceitem.id}") end end diff --git a/test/stripe/subscriptions/line_item_test.exs b/test/stripe/subscriptions/line_item_test.exs index da7d0481c..0564b9bba 100644 --- a/test/stripe/subscriptions/line_item_test.exs +++ b/test/stripe/subscriptions/line_item_test.exs @@ -3,8 +3,10 @@ defmodule Stripe.LineItemTest do describe "retrieve/2" do test "retrieves an invoice" do - assert {:ok, %Stripe.Invoice{}} = Stripe.LineItem.retrieve("in_1234") + assert {:ok, %Stripe.List{data: line_items}} = Stripe.LineItem.retrieve("in_1234") assert_stripe_requested(:get, "/v1/invoices/in_1234/lines") + assert is_list(line_items) + assert %Stripe.LineItem{} = hd(line_items) end end end diff --git a/test/stripe/subscriptions/plan_test.exs b/test/stripe/subscriptions/plan_test.exs index c848c92ef..651b262a3 100644 --- a/test/stripe/subscriptions/plan_test.exs +++ b/test/stripe/subscriptions/plan_test.exs @@ -52,6 +52,8 @@ defmodule Stripe.PlanTest do describe "delete/2" do test "deletes a Plan" do {:ok, plan} = Stripe.Plan.retrieve("sapphire-elite") + assert_stripe_requested(:get, "/v1/plans/#{plan.id}") + assert {:ok, %Stripe.Plan{}} = Stripe.Plan.delete(plan) assert_stripe_requested(:delete, "/v1/plans/#{plan.id}") end diff --git a/test/stripe/subscriptions/product_test.exs b/test/stripe/subscriptions/product_test.exs index 8ee2407df..3ac5fcce9 100644 --- a/test/stripe/subscriptions/product_test.exs +++ b/test/stripe/subscriptions/product_test.exs @@ -35,6 +35,8 @@ defmodule Stripe.ProductTest do describe "delete/1" do test "deletes a product" do {:ok, product} = Stripe.Product.retrieve("Plus") + assert_stripe_requested(:get, "/v1/products/#{product.id}") + assert {:ok, _} = Stripe.Product.delete("Plus") assert_stripe_requested(:delete, "/v1/products/#{product.id}") end diff --git a/test/stripe/subscriptions/subscription_item_test.exs b/test/stripe/subscriptions/subscription_item_test.exs index 675e1752f..a7487d977 100644 --- a/test/stripe/subscriptions/subscription_item_test.exs +++ b/test/stripe/subscriptions/subscription_item_test.exs @@ -4,7 +4,7 @@ defmodule Stripe.SubscriptionItemTest do describe "retrieve/2" do test "retrieves a subscription" do assert {:ok, %Stripe.SubscriptionItem{}} = Stripe.SubscriptionItem.retrieve("sub_123") - assert_stripe_requested(:get, "/v1/subscriptions/sub_123") + assert_stripe_requested(:get, "/v1/subscription_items/sub_123") end end @@ -14,31 +14,34 @@ defmodule Stripe.SubscriptionItemTest do subscription: "sub_123", plan: "plan_123" } + assert {:ok, %Stripe.SubscriptionItem{}} = Stripe.SubscriptionItem.create(params) assert_stripe_requested(:post, "/v1/subscription_items") end end describe "update/2" do - test "updates a subscription" do + test "updates a subscription item" do params = %{metadata: %{foo: "bar"}} - assert {:ok, subscription} = Stripe.SubscriptionItem.update("sub_123", params) - assert_stripe_requested(:post, "/v1/subscriptions/#{subscription.id}") + assert {:ok, subscription_item} = Stripe.SubscriptionItem.update("sub_123", params) + assert_stripe_requested(:post, "/v1/subscription_items/#{subscription_item.id}") end end describe "delete/2" do - test "deletes a subscription" do + test "deletes a subscription item" do {:ok, subscription_item} = Stripe.SubscriptionItem.retrieve("sub_123") + assert_stripe_requested(:get, "/v1/subscription_items/#{subscription_item.id}") + assert {:ok, %Stripe.SubscriptionItem{}} = Stripe.SubscriptionItem.delete("sub_123") - assert_stripe_requested(:delete, "/v1/subscriptions/#{subscription_item.id}") + assert_stripe_requested(:delete, "/v1/subscription_items/#{subscription_item.id}") end end describe "list/2" do test "lists all subscription_items" do assert {:ok, %Stripe.List{data: subscriptions}} = Stripe.SubscriptionItem.list("sub_123") - assert_stripe_requested(:get, "/v1/subscription_items") + assert_stripe_requested(:get, "/v1/subscription_items", query: %{subscription: "sub_123"}) assert is_list(subscriptions) assert %Stripe.SubscriptionItem{} = hd(subscriptions) end diff --git a/test/stripe/subscriptions/subscription_schedule_test.exs b/test/stripe/subscriptions/subscription_schedule_test.exs new file mode 100644 index 000000000..cbddb6a35 --- /dev/null +++ b/test/stripe/subscriptions/subscription_schedule_test.exs @@ -0,0 +1,126 @@ +defmodule Stripe.SubscriptionScheduleTest do + use Stripe.StripeCase, async: true + + @invalid_params %{ + customer: "cus_123", + renewal_behavior: "release", + phases: [ + %{ + coupon: nil, + default_tax_rates: [], + end_date: 1_557_566_037, + start_date: 1_554_974_037, + tax_percent: 0 + } + ] + } + describe "retrieve/2" do + test "retrieves a subscription" do + assert {:ok, %Stripe.SubscriptionSchedule{}} = + Stripe.SubscriptionSchedule.retrieve("sub_sched_123") + + assert_stripe_requested(:get, "/v1/subscription_schedules/sub_sched_123") + end + end + + describe "create/2" do + test "creates a subscription schedule" do + params = %{ + customer: "cus_123", + renewal_behavior: "release", + phases: [ + %{ + coupon: nil, + default_tax_rates: [], + end_date: 1_557_566_037, + plans: [ + %{ + billing_thresholds: nil, + plan: "some plan", + quantity: 2, + tax_rates: [] + } + ], + start_date: 1_554_974_037, + tax_percent: 0 + } + ] + } + + assert {:ok, %Stripe.SubscriptionSchedule{}} = Stripe.SubscriptionSchedule.create(params) + + assert_stripe_requested(:post, "/v1/subscription_schedules") + end + + test "fails with missing plans in phases" do + assert {:error, %Stripe.Error{} = error} = + Stripe.SubscriptionSchedule.create(@invalid_params) + + assert_stripe_requested(:post, "/v1/subscription_schedules") + end + end + + describe "update/2" do + test "updates a subscription" do + params = %{ + renewal_behavior: "release", + phases: [ + %{ + coupon: nil, + default_tax_rates: [], + end_date: 1_557_566_037, + plans: [ + %{ + billing_thresholds: nil, + plan: "some plan", + quantity: 2, + tax_rates: [] + } + ], + start_date: 1_554_974_037, + tax_percent: 0 + } + ] + } + + assert {:ok, subscription} = Stripe.SubscriptionSchedule.update("sub_sched_123", params) + assert_stripe_requested(:post, "/v1/subscription_schedules/#{subscription.id}") + end + + test "fails with missing plans in phases" do + assert {:error, %Stripe.Error{}} = + Stripe.SubscriptionSchedule.update("sub_sched_123", @invalid_params) + + assert_stripe_requested(:post, "/v1/subscription_schedules/sub_sched_123") + end + end + + describe "list/2" do + test "lists all subscriptions" do + assert {:ok, %Stripe.List{data: subscriptions}} = Stripe.SubscriptionSchedule.list() + assert_stripe_requested(:get, "/v1/subscription_schedules") + assert is_list(subscriptions) + assert %Stripe.SubscriptionSchedule{} = hd(subscriptions) + end + end + + describe "cancel/2" do + test "cancels a subscription schedule" do + {:ok, subscription} = Stripe.SubscriptionSchedule.retrieve("sub_sched_123") + assert_stripe_requested(:get, "/v1/subscription_schedules/#{subscription.id}") + + assert {:ok, _} = Stripe.SubscriptionSchedule.cancel("sub_sched_123") + assert_stripe_requested(:post, "/v1/subscription_schedules/#{subscription.id}/cancel") + end + end + + describe "release/2" do + test "releases a subscription schedule" do + {:ok, subscription} = Stripe.SubscriptionSchedule.retrieve("sub_sched_123") + assert_stripe_requested(:get, "/v1/subscription_schedules/#{subscription.id}") + + assert {:ok, _} = Stripe.SubscriptionSchedule.release("sub_sched_123") + assert_stripe_requested(:post, "/v1/subscription_schedules/#{subscription.id}/release") + end + end +end diff --git a/test/stripe/subscriptions/subscription_test.exs b/test/stripe/subscriptions/subscription_test.exs index 833c628de..ddee7fdce 100644 --- a/test/stripe/subscriptions/subscription_test.exs +++ b/test/stripe/subscriptions/subscription_test.exs @@ -81,7 +81,7 @@ defmodule Stripe.SubscriptionTest do assert subscription.cancel_at_period_end # The deprecated function acts as a facade for `cancel_at_period_end: true`. - assert_stripe_requested(:update, "/v1/subscriptions/#{subscription.id}") + assert_stripe_requested(:post, "/v1/subscriptions/#{subscription.id}") end end @@ -91,7 +91,7 @@ defmodule Stripe.SubscriptionTest do Stripe.Subscription.delete("sub_123", %{at_period_end: true}, []) # The deprecated function acts as a facade for `cancel_at_period_end: true`. - assert_stripe_requested(:update, "/v1/subscriptions/sub_123") + assert_stripe_requested(:post, "/v1/subscriptions/sub_123") end end @@ -107,6 +107,8 @@ defmodule Stripe.SubscriptionTest do describe "delete_discount/2" do test "deletes a subscription's discount" do {:ok, subscription} = Stripe.Subscription.retrieve("sub_123") + assert_stripe_requested(:get, "/v1/subscriptions/#{subscription.id}") + assert {:ok, _} = Stripe.Subscription.delete_discount("sub_123") assert_stripe_requested(:delete, "/v1/subscriptions/#{subscription.id}/discount") end diff --git a/test/stripe/subscriptions/tax_rate_test.exs b/test/stripe/subscriptions/tax_rate_test.exs new file mode 100644 index 000000000..44ca4ebb7 --- /dev/null +++ b/test/stripe/subscriptions/tax_rate_test.exs @@ -0,0 +1,54 @@ +defmodule Stripe.TaxRateTest do + use Stripe.StripeCase, async: true + + describe "create/2" do + test "creates a TaxRate for a customer" do + params = %{ + display_name: "VAT", + description: "VAT Germany", + jurisdiction: "DE", + percentage: 19.0, + inclusive: false + } + + assert {:ok, %Stripe.TaxRate{}} = Stripe.TaxRate.create(params) + assert_stripe_requested(:post, "/v1/tax_rates") + end + + test "returns an error TaxRate" do + params = %{ + display_name: "VAT", + description: "VAT Germany", + jurisdiction: "DE", + inclusive: false + } + + assert {:error, %Stripe.Error{}} = Stripe.TaxRate.create(params) + assert_stripe_requested(:post, "/v1/tax_rates") + end + end + + describe "retrieve/2" do + test "retrieves a TaxRate" do + assert {:ok, %Stripe.TaxRate{}} = Stripe.TaxRate.retrieve("txr_1EXapq2eZvKYlo2CHmXqULaR") + assert_stripe_requested(:get, "/v1/tax_rates/txr_1EXapq2eZvKYlo2CHmXqULaR") + end + end + + describe "update/2" do + test "updates a TaxRate" do + params = %{metadata: %{foo: "bar"}} + assert {:ok, plan} = Stripe.TaxRate.update("txr_1EXapq2eZvKYlo2CHmXqULaR", params) + assert_stripe_requested(:post, "/v1/tax_rates/#{plan.id}") + end + end + + describe "list/2" do + test "lists all TaxRates" do + assert {:ok, %Stripe.List{data: plans}} = Stripe.TaxRate.list() + assert_stripe_requested(:get, "/v1/tax_rates") + assert is_list(plans) + assert %Stripe.TaxRate{} = hd(plans) + end + end +end diff --git a/test/stripe/subscriptions/usage_test.exs b/test/stripe/subscriptions/usage_test.exs new file mode 100644 index 000000000..c49046b1f --- /dev/null +++ b/test/stripe/subscriptions/usage_test.exs @@ -0,0 +1,65 @@ +defmodule Stripe.SubscriptionItem.UsageTest do + use Stripe.StripeCase, async: true + + describe "create/2" do + test "create usage record" do + item_id = "si_123" + + params = %{ + quantity: 10, + timestamp: 1_543_335_582 + } + + assert {:ok, record} = Stripe.SubscriptionItem.Usage.create(item_id, params) + assert %{subscription_item: _sub_id} = record + assert_stripe_requested(:post, "/v1/subscription_items/#{item_id}/usage_records") + end + + test "create usage record with subsctiption item" do + item = %Stripe.SubscriptionItem{ + id: "si_123" + } + + params = %{ + quantity: 10, + timestamp: 1_543_335_582 + } + + assert {:ok, record} = Stripe.SubscriptionItem.Usage.create(Map.get(item, :id), params) + assert %{subscription_item: _sub_id} = record + assert_stripe_requested(:post, "/v1/subscription_items/#{item.id}/usage_records") + end + end + + describe "list/1" do + test "list usage records for subscription items" do + item_id = "si_123" + + assert {:ok, %Stripe.List{data: usages}} = Stripe.SubscriptionItem.Usage.list(item_id) + assert_stripe_requested(:get, "/v1/subscription_items/#{item_id}/usage_record_summaries") + assert is_list(usages) + assert %{subscription_item: _sub_item_id} = hd(usages) + end + end + + describe "list/2" do + test "list usage records for subscription items with params" do + item_id = "si_123" + + params = %{ + limit: 10 + } + + assert {:ok, %Stripe.List{data: usages}} = + Stripe.SubscriptionItem.Usage.list(item_id, params) + + assert_stripe_requested( + :get, + "/v1/subscription_items/#{item_id}/usage_record_summaries?limit=10" + ) + + assert is_list(usages) + assert %{subscription_item: _sub_item_id} = hd(usages) + end + end +end diff --git a/test/stripe/util_test.exs b/test/stripe/util_test.exs index 7d6f20524..30addac09 100644 --- a/test/stripe/util_test.exs +++ b/test/stripe/util_test.exs @@ -15,13 +15,14 @@ defmodule Stripe.UtilTest do assert object_name_to_module("dispute") == Stripe.Dispute assert object_name_to_module("event") == Stripe.Event assert object_name_to_module("external_account") == Stripe.ExternalAccount - assert object_name_to_module("file_upload") == Stripe.FileUpload + assert object_name_to_module("file") == Stripe.FileUpload assert object_name_to_module("invoice") == Stripe.Invoice assert object_name_to_module("invoiceitem") == Stripe.Invoiceitem assert object_name_to_module("line_item") == Stripe.LineItem assert object_name_to_module("list") == Stripe.List assert object_name_to_module("order") == Stripe.Order assert object_name_to_module("order_return") == Stripe.OrderReturn + assert object_name_to_module("payment_intent") == Stripe.PaymentIntent assert object_name_to_module("plan") == Stripe.Plan assert object_name_to_module("product") == Stripe.Product assert object_name_to_module("refund") == Stripe.Refund diff --git a/test/stripe/webhook_test.exs b/test/stripe/webhook_test.exs index 4e33f6b91..b4c66c5bd 100644 --- a/test/stripe/webhook_test.exs +++ b/test/stripe/webhook_test.exs @@ -20,7 +20,7 @@ defmodule Stripe.WebhookTest do end test "payload with a valid signature should return event" do - timestamp = System.system_time(:seconds) + timestamp = System.system_time(:second) payload = @valid_payload signature = generate_signature(timestamp, payload) signature_header = create_signature_header(timestamp, @valid_scheme, signature) @@ -29,7 +29,7 @@ defmodule Stripe.WebhookTest do end test "payload with an invalid signature should fail" do - timestamp = System.system_time(:seconds) + timestamp = System.system_time(:second) payload = @valid_payload signature = generate_signature(timestamp, "random") signature_header = create_signature_header(timestamp, @valid_scheme, signature) @@ -38,7 +38,7 @@ defmodule Stripe.WebhookTest do end test "payload with wrong secret should fail" do - timestamp = System.system_time(:seconds) + timestamp = System.system_time(:second) payload = @valid_payload signature = generate_signature(timestamp, payload, "wrong") signature_header = create_signature_header(timestamp, @valid_scheme, signature) @@ -47,7 +47,7 @@ defmodule Stripe.WebhookTest do end test "payload with missing signature scheme should fail" do - timestamp = System.system_time(:seconds) + timestamp = System.system_time(:second) payload = @valid_payload signature = generate_signature(timestamp, payload) signature_header = create_signature_header(timestamp, @invalid_scheme, signature) diff --git a/test/support/stripe_case.ex b/test/support/stripe_case.ex index 023a6af80..652866a0e 100644 --- a/test/support/stripe_case.ex +++ b/test/support/stripe_case.ex @@ -5,19 +5,73 @@ defmodule Stripe.StripeCase do use ExUnit.CaseTemplate - def assert_stripe_requested(_method, _url, _extra \\ []) do - # TODO: use something akin to WebMock to check the API calls are correct - assert true + def assert_stripe_requested(expected_method, path, extra \\ []) do + expected_url = build_url(path, Keyword.get(extra, :query)) + expected_body = Keyword.get(extra, :body) + expected_headers = Keyword.get(extra, :headers) + + assert_received({method, url, headers, body, _}) + + assert expected_method == method + assert expected_url == url + + assert_stripe_request_body(expected_body, body) + assert_stripe_request_headers(expected_headers, headers) end def stripe_base_url() do Application.get_env(:stripity_stripe, :api_base_url) end + defp assert_stripe_request_headers(nil, _), do: nil + + defp assert_stripe_request_headers(expected_headers, headers) when is_list(expected_headers) do + assert Enum.all?(expected_headers, &assert_stripe_request_headers(&1, headers)) + end + + defp assert_stripe_request_headers(expected_header, headers) do + assert Enum.any?(headers, fn header -> expected_header == header end), + """ + Expected the header `#{inspect(expected_header)}` to be in the headers of the request. + + Headers: + #{inspect(headers)} + """ + end + + defp assert_stripe_request_body(nil, _), do: nil + + defp assert_stripe_request_body(expected_body, body) do + assert body == Stripe.URI.encode_query(expected_body) + end + + defp build_url("/v1/" <> path, nil) do + stripe_base_url() <> path + end + + defp build_url("/v1/" <> path, query_params) do + stripe_base_url() <> path <> "?" <> URI.encode_query(query_params) + end + + defmodule HackneyMock do + @doc """ + Send message to the owning process for each request so we can assert that + the request was made. + + """ + def request(method, path, headers, body, opts) do + send(self(), {method, path, headers, body, opts}) + + :hackney.request(method, path, headers, body, opts) + end + end + using do quote do import Stripe.StripeCase, only: [assert_stripe_requested: 2, assert_stripe_requested: 3, stripe_base_url: 0] + + Application.put_env(:stripity_stripe, :http_module, HackneyMock) end end end diff --git a/test/test_helper.exs b/test/test_helper.exs index 8c8bda0f7..f78c739c5 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -8,7 +8,6 @@ Logger.configure(level: :info) {:ok, pid} = Stripe.StripeMock.start_link(port: 12123, global: true) - Application.put_env(:stripity_stripe, :api_base_url, "http://localhost:12123/v1/") Application.put_env(:stripity_stripe, :api_upload_url, "http://localhost:12123/v1/") Application.put_env(:stripity_stripe, :api_key, "sk_test_123") @@ -21,7 +20,7 @@ defmodule Helper do @fixture_path "./test/fixtures/" def load_fixture(filename) do - File.read!(@fixture_path <> filename) |> Poison.decode!() + File.read!(@fixture_path <> filename) |> Stripe.API.json_library().decode!() end def wait_until_stripe_mock_launch() do @@ -30,6 +29,7 @@ defmodule Helper do # It might be connection refused. Process.sleep(250) wait_until_stripe_mock_launch() + _ -> true end