diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..4ed959f8f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,75 @@ +name: Continuous Integration + +on: + pull_request: + types: [opened, reopened, synchronize] + push: + branches: + - 'master' + +jobs: + test: + env: + MIX_ENV: test + STRIPE_MOCK_VERSION: 0.116.0 + STRIPE_SECRET_KEY: non_empty_string + SKIP_STRIPE_MOCK_RUN: true + runs-on: ubuntu-latest + name: Test (OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}) + strategy: + matrix: + elixir: ['1.12'] + # All of the above can use this version. For details see: https://hexdocs.pm/elixir/compatibility-and-deprecations.html#compatibility-between-elixir-and-erlang-otp + otp: [24] + services: + stripe-mock: + image: stripe/stripe-mock:v0.116.0 + ports: + - 12111:12111 + - 12112:12112 + steps: + - name: Clone code + uses: actions/checkout@v2 + - name: Setup Elixir and Erlang + uses: erlef/setup-beam@v1 + with: + otp-version: ${{ matrix.otp }} + elixir-version: ${{ matrix.elixir }} + - name: Mix Dependencies + run: mix deps.get + - name: Test + run: mix test + + lint: + runs-on: ubuntu-latest + name: Linting + strategy: + matrix: + elixir: ['1.12', '1.11', '1.10'] + # All of the above can use this version. For details see: https://hexdocs.pm/elixir/compatibility-and-deprecations.html#compatibility-between-elixir-and-erlang-otp + otp: [24, 23, 22] + steps: + - uses: actions/checkout@v2 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{matrix.otp}} + elixir-version: ${{matrix.elixir}} + - uses: actions/cache@v1 + env: + cache-name: mix + with: + path: ~/.mix + key: cache-${{ runner.os }}-${{ env.cache-name }}-${{ matrix.otp }}-${{ matrix.elixir }} + restore-keys: | + cache-${{ runner.os }}-${{ env.cache-name }}- + - uses: actions/cache@v1 + env: + cache-name: build + with: + path: _build + key: cache-${{ runner.os }}-${{ env.cache-name }}-${{ matrix.otp }}-${{ matrix.elixir }} + restore-keys: | + cache-${{ runner.os }}-${{ env.cache-name }}- + - run: mix deps.get + - run: mix compile --warnings-as-errors + - run: mix dialyzer --halt-exit-status diff --git a/.gitignore b/.gitignore index a353b8c89..03f88067f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,39 @@ -/_build -/deps +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). *.ez -config/config.secret.exs -/doc -.DS_Store -tags -# elixir LS files +# Ignore package tarball (built via "mix hex.build"). +stripity_stripe-*.tar -/.elixir_ls -/.devcontainer -# IntelliJ IDEA files +# Temporary files, for example, from tests. +/tmp/ -/.idea +# Elixir LS files. +.elixir_ls +.devcontainer + +# IntelliJ IDEA files +.idea *.iml -priv/plts/* -/cover + +# Misc. +.DS_Store +config/config.secret.exs +tags diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a6c6536fd..000000000 --- a/.travis.yml +++ /dev/null @@ -1,45 +0,0 @@ -language: elixir -dist: trusty -elixir: - - 1.7 - - 1.8 - - 1.9 -otp_release: - - 19.3 - - 20.1 - - 21.1 -matrix: - exclude: - - otp_release: 19.3 - elixir: 1.8 - - otp_release: 19.3 - elixir: 1.9 -sudo: false -env: - global: - - STRIPE_MOCK_VERSION=0.95.0 - - MIX_ENV=test STRIPE_SECRET_KEY=non_empty_secret_key_string -cache: - directories: - - priv/plts - - stripe-mock -before_install: - - | - if [ ! -d "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}" ]; then - mkdir -p stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}/ - curl -L "https://github.com/stripe/stripe-mock/releases/download/v${STRIPE_MOCK_VERSION}/stripe-mock_${STRIPE_MOCK_VERSION}_linux_amd64.tar.gz" -o "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}_linux_amd64.tar.gz" - tar -zxf "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}_linux_amd64.tar.gz" -C "stripe-mock/stripe-mock_${STRIPE_MOCK_VERSION}/" - 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: - - mix inch.report -notifications: - email: - on_success: never diff --git a/CHANGELOG.md b/CHANGELOG.md index 84ffcc37b..1588a063f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,82 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [2.12.1](https://github.com/code-corps/stripity_stripe/compare/2.10.0...2.12.1) + +- Add missing opts to Stripe.ApplicationFee [`#693`](https://github.com/code-corps/stripity_stripe/pull/693) +- Adds missing params and types for Stripe Checkout Sessions [`#695`](https://github.com/code-corps/stripity_stripe/pull/695) +- Add stripe Identity endpoints [`#692`](https://github.com/code-corps/stripity_stripe/pull/692) +- Add customer field to PromotionCode [`#676`](https://github.com/code-corps/stripity_stripe/pull/676) +- Add application to webhook_endpoint [`#691`](https://github.com/code-corps/stripity_stripe/pull/691) +- Add the issuing field to Stripe.Balance [`#689`](https://github.com/code-corps/stripity_stripe/pull/689) +- [bug]: extra applications exexec bad [`#688`](https://github.com/code-corps/stripity_stripe/pull/688) +- using non_neg_integer instead of float [`#681`](https://github.com/code-corps/stripity_stripe/pull/681) +- Add fields to Stripe.Session struct [`#683`](https://github.com/code-corps/stripity_stripe/pull/683) +- [CI]: actions instead of travisci [`#677`](https://github.com/code-corps/stripity_stripe/pull/677) +- Support klarna payments in sources better [`#651`](https://github.com/code-corps/stripity_stripe/pull/651) +- Add Stripe.WebhookPlug for easier handling of webhook events [`#658`](https://github.com/code-corps/stripity_stripe/pull/658) +- Add missing PromotionCode.expires_at field [`#675`](https://github.com/code-corps/stripity_stripe/pull/675) +- add sepa debig [`#674`](https://github.com/code-corps/stripity_stripe/pull/674) +- Update Stripe.Session line_item typespec [`#670`](https://github.com/code-corps/stripity_stripe/pull/670) +- CHANGELOG 2.10.0 [`c6aeb31`](https://github.com/code-corps/stripity_stripe/commit/c6aeb31bb8e3821349fdef2c95c5f59e2735244b) +- Update changelog [`305996d`](https://github.com/code-corps/stripity_stripe/commit/305996dab116adf741ad290d45d95ff25ab7759c) +- bump ex_docs 0.24.2 [`e89b0b9`](https://github.com/code-corps/stripity_stripe/commit/e89b0b926ac36906bc036284e6828d2b71960de6) + +#### [2.10.0](https://github.com/code-corps/stripity_stripe/compare/2.8.0...2.10.0) + +> 24 May 2021 + +- fix(deprecation): :crypto.hmac -> :crypto.mac, Bitwise.^^^ -> Bitwise.bxor [`#669`](https://github.com/code-corps/stripity_stripe/pull/669) +- Fix Stripe.Price unit_amount_decimal and lookup_keys type [`#668`](https://github.com/code-corps/stripity_stripe/pull/668) +- Add Account Capabilities [`#667`](https://github.com/code-corps/stripity_stripe/pull/667) +- Remove :customer from typespec in CustomerBalanceTransaction.create [`#662`](https://github.com/code-corps/stripity_stripe/pull/662) +- Add mode to Session Create Params typespec [`#660`](https://github.com/code-corps/stripity_stripe/pull/660) +- Oauth integration improvements [`#657`](https://github.com/code-corps/stripity_stripe/pull/657) +- Allow api_version to be set from config [`#656`](https://github.com/code-corps/stripity_stripe/pull/656) +- Adds header as an option to new_request [`#654`](https://github.com/code-corps/stripity_stripe/pull/654) +- Added applies_to and expansion to Coupon [`#653`](https://github.com/code-corps/stripity_stripe/pull/653) +- Update typespec for Stripe.Customer [`#650`](https://github.com/code-corps/stripity_stripe/pull/650) +- Add credit note preview [`#648`](https://github.com/code-corps/stripity_stripe/pull/648) +- Support :status param in typespec for Stripe.Invoice.list/2 [`#644`](https://github.com/code-corps/stripity_stripe/pull/644) +- Add source_type to parameter types for transfers/create [`#645`](https://github.com/code-corps/stripity_stripe/pull/645) +- Add Country/State to TaxRate [`#647`](https://github.com/code-corps/stripity_stripe/pull/647) +- Allow passing pause_collection params to Subscription.update [`#649`](https://github.com/code-corps/stripity_stripe/pull/649) +- Add promotion codes [`#641`](https://github.com/code-corps/stripity_stripe/pull/641) +- Fix typespec for Stripe.Invoice.pay/3 [`#643`](https://github.com/code-corps/stripity_stripe/pull/643) +- Adding endpoint for checkout session line items. [`#633`](https://github.com/code-corps/stripity_stripe/pull/633) +- Remove duplicate doc tag causing a compile warning [`#639`](https://github.com/code-corps/stripity_stripe/pull/639) +- Add promotion_code to Discount [`#638`](https://github.com/code-corps/stripity_stripe/pull/638) +- Add quick example of test config for stripe-mock [`#637`](https://github.com/code-corps/stripity_stripe/pull/637) +- Add payment_intent to Stripe.Charge.params type [`#635`](https://github.com/code-corps/stripity_stripe/pull/635) +- Add default_tax_rate to typespecs for Subscription and Invoice [`#631`](https://github.com/code-corps/stripity_stripe/pull/631) +- Add payment_method to confirm payment intent [`#625`](https://github.com/code-corps/stripity_stripe/pull/625) +- Add :metadata to Stripe.Session.create/2 [`#615`](https://github.com/code-corps/stripity_stripe/pull/615) +- Fix return of the ephemeral key creation [`#624`](https://github.com/code-corps/stripity_stripe/pull/624) +- Update link to Stripe docs [`#622`](https://github.com/code-corps/stripity_stripe/pull/622) +- Add webhook endpoint to core resources [`#621`](https://github.com/code-corps/stripity_stripe/pull/621) +- Update subscription.ex [`#620`](https://github.com/code-corps/stripity_stripe/pull/620) +- support extra params for subscription cancellation [`#618`](https://github.com/code-corps/stripity_stripe/pull/618) +- VERSION BUMP 2.9.0 [`#614`](https://github.com/code-corps/stripity_stripe/pull/614) +- Fix multiple Stripe-Version headers when creating ephemeral key [`#613`](https://github.com/code-corps/stripity_stripe/pull/613) +- makes `:previous_attributes` in `Event.event_data` optional [`#612`](https://github.com/code-corps/stripity_stripe/pull/612) +- Allow creating refunds by payment intent or charge. [`#610`](https://github.com/code-corps/stripity_stripe/pull/610) +- Add price to Subscription [`#609`](https://github.com/code-corps/stripity_stripe/pull/609) +- changed params for subscription to accept default payment method [`#592`](https://github.com/code-corps/stripity_stripe/pull/592) +- Added Terminal functionality [`#605`](https://github.com/code-corps/stripity_stripe/pull/605) +- Add support for BillingPortal.Session [`#604`](https://github.com/code-corps/stripity_stripe/pull/604) +- Update various stripe objects with missing fields, remove deprecated fields, and add support for some missing objects [`#602`](https://github.com/code-corps/stripity_stripe/pull/602) +- Add optional `customer` to session create params [`#601`](https://github.com/code-corps/stripity_stripe/pull/601) +- Replace `@deprecated` with `Stripe.Util.log_deprecation/1` [`#598`](https://github.com/code-corps/stripity_stripe/pull/598) +- Update invoice.ex [`#591`](https://github.com/code-corps/stripity_stripe/pull/591) +- Make subscription_data keys for Checkout optional [`#593`](https://github.com/code-corps/stripity_stripe/pull/593) +- Update README.md [`#596`](https://github.com/code-corps/stripity_stripe/pull/596) +- Corrected mis-placed parens in README [`#587`](https://github.com/code-corps/stripity_stripe/pull/587) +- Update README to suggest the latest version [`#588`](https://github.com/code-corps/stripity_stripe/pull/588) +- Add metadata to Stripe.Session [`#595`](https://github.com/code-corps/stripity_stripe/pull/595) +- add metadata to checkout session [`288202c`](https://github.com/code-corps/stripity_stripe/commit/288202c252931c489a7b1681658e6fbdd6fe44e3) +- remove unnecessary test [`6269722`](https://github.com/code-corps/stripity_stripe/commit/62697227649612bd45710469ea4b252558ff1710) +- Add new fields in the subscription data API [`20ce7d8`](https://github.com/code-corps/stripity_stripe/commit/20ce7d8d7825d2633c05dc2bf6f94bb0e1835084) + #### [2.8.0](https://github.com/code-corps/stripity_stripe/compare/2.7.1...2.8.0) > 4 April 2020 @@ -53,7 +129,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). #### [v2.6.0](https://github.com/code-corps/stripity_stripe/compare/2.4.0...v2.6.0) -> 7 September 2019 +> 15 September 2019 - CHANGELOG 2.6.0 [`#540`](https://github.com/code-corps/stripity_stripe/pull/540) - Add support for SetupIntent [`#522`](https://github.com/code-corps/stripity_stripe/pull/522) @@ -124,7 +200,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - Add formatter [`#453`](https://github.com/code-corps/stripity_stripe/pull/453) - Typo for retrieving invoice items + Adds handler for Usage endpoint [`#433`](https://github.com/code-corps/stripity_stripe/pull/433) - Update Configuration instructions in README.md [`#457`](https://github.com/code-corps/stripity_stripe/pull/457) -- Move aroud README [`#455`](https://github.com/code-corps/stripity_stripe/pull/455) +- Move around README [`#455`](https://github.com/code-corps/stripity_stripe/pull/455) - Upgrade to 2018-11-08 Stripe API [`#439`](https://github.com/code-corps/stripity_stripe/pull/439) - Add invoice settings to customer/invoice objects [`#451`](https://github.com/code-corps/stripity_stripe/pull/451) - add shared options to documentation [`#452`](https://github.com/code-corps/stripity_stripe/pull/452) @@ -333,7 +409,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). > 31 May 2017 - Added Stripe.Charge.capture [`#224`](https://github.com/code-corps/stripity_stripe/pull/224) -- Added Invoice.upcoming for retreiving upcoming invoices. [`#233`](https://github.com/code-corps/stripity_stripe/pull/233) +- Added Invoice.upcoming for retrieving upcoming invoices. [`#233`](https://github.com/code-corps/stripity_stripe/pull/233) - Added Stripe.Refund module [`#226`](https://github.com/code-corps/stripity_stripe/pull/226) - adds Stripe.Refund module [`608c5b3`](https://github.com/code-corps/stripity_stripe/commit/608c5b3f701150ee9ba2b112b612f95a5b923650) - Add upcoming invoice path [`3483c55`](https://github.com/code-corps/stripity_stripe/commit/3483c55e0f4dbfa3f73716da590b13a9407f489e) @@ -341,7 +417,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). #### [v2.0.0-alpha.7](https://github.com/code-corps/stripity_stripe/compare/v2.0.0-alpha.6...v2.0.0-alpha.7) -> 1 May 2017 +> 15 May 2017 - Add parameters to DELETE requests (#222) [`#223`](https://github.com/code-corps/stripity_stripe/pull/223) - Correctly parsing responses from non-object responses [`#229`](https://github.com/code-corps/stripity_stripe/pull/229) @@ -410,7 +486,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). #### [v2.0.0-alpha.1](https://github.com/code-corps/stripity_stripe/compare/v1.6.2...v2.0.0-alpha.1) -> 13 November 2016 +> 12 November 2016 - Up the version number [`#123`](https://github.com/code-corps/stripity_stripe/pull/123) - Add Subscription and Token [`#122`](https://github.com/code-corps/stripity_stripe/pull/122) @@ -442,17 +518,17 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - Publish v1.6.0 [`98e0b16`](https://github.com/code-corps/stripity_stripe/commit/98e0b16977b651385728c5697a0ca47586a1a49c) - Update readme with branch info [`9a070f8`](https://github.com/code-corps/stripity_stripe/commit/9a070f8fcd4ed47c731a80b2f526cc06c20c41f5) -#### [v1.6.0](https://github.com/code-corps/stripity_stripe/compare/v1.5.0...v1.6.0) +#### [v1.6.0](https://github.com/code-corps/stripity_stripe/compare/1.5.0...v1.6.0) > 6 July 2017 - Add change invoices ability [`#241`](https://github.com/code-corps/stripity_stripe/pull/241) -#### [v1.5.0](https://github.com/code-corps/stripity_stripe/compare/1.5.0...v1.5.0) +#### [1.5.0](https://github.com/code-corps/stripity_stripe/compare/v1.5.0...1.5.0) -> 29 June 2020 +> 4 July 2017 -#### [1.5.0](https://github.com/code-corps/stripity_stripe/compare/v1.1.0...1.5.0) +#### [v1.5.0](https://github.com/code-corps/stripity_stripe/compare/v1.3...v1.5.0) > 4 July 2017 @@ -500,15 +576,23 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - allow `poison ~> 1.5` [`#34`](https://github.com/code-corps/stripity_stripe/pull/34) - Update `poison` and `httpoison`. [`#33`](https://github.com/code-corps/stripity_stripe/pull/33) - Add upcoming invoice API [`#31`](https://github.com/code-corps/stripity_stripe/pull/31) +- Allow changing subscription options [`#35`](https://github.com/code-corps/stripity_stripe/issues/35) +- Removed app start up mess, removed supervisor. Closes #50 [`#50`](https://github.com/code-corps/stripity_stripe/issues/50) +- Record new responses for all tests [`7d4530a`](https://github.com/code-corps/stripity_stripe/commit/7d4530ab64be5740f7c66b552d1f409f7802cb33) +- Remove current recorded responses [`11d9cdd`](https://github.com/code-corps/stripity_stripe/commit/11d9cdddfc2904bcbd702ce587ef97ef2db49fa6) +- Change real token to fake token [`d25542b`](https://github.com/code-corps/stripity_stripe/commit/d25542b4379f8d1601cd8acd01a6a97c9e33c7ff) + +#### [v1.3](https://github.com/code-corps/stripity_stripe/compare/v1.1.0...v1.3) + +> 24 February 2016 + - Add update customer API and small fixes [`#28`](https://github.com/code-corps/stripity_stripe/pull/28) - README referenced nonexistent hex package version [`#29`](https://github.com/code-corps/stripity_stripe/pull/29) - Cards [`#27`](https://github.com/code-corps/stripity_stripe/pull/27) - Relax Elixir dependency to ~> 1.1 [`#25`](https://github.com/code-corps/stripity_stripe/pull/25) -- Allow changing subscription options [`#35`](https://github.com/code-corps/stripity_stripe/issues/35) -- Removed app start up mess, removed supervisor. Closes #50 [`#50`](https://github.com/code-corps/stripity_stripe/issues/50) -- Record new responses for all tests [`7d4530a`](https://github.com/code-corps/stripity_stripe/commit/7d4530ab64be5740f7c66b552d1f409f7802cb33) - updated readme [`eb4fdff`](https://github.com/code-corps/stripity_stripe/commit/eb4fdffa0c66bf3ae03d02130320a874102398c5) -- Remove current recorded responces [`11d9cdd`](https://github.com/code-corps/stripity_stripe/commit/11d9cdddfc2904bcbd702ce587ef97ef2db49fa6) +- Stripe.Cards resource [`0ed1a50`](https://github.com/code-corps/stripity_stripe/commit/0ed1a506601fd346a7cfd9a9dbdd46d88b545916) +- Add create card for an existing customer [`dfb5762`](https://github.com/code-corps/stripity_stripe/commit/dfb5762eb26dca1245baa827fa90e1597e0d6c8c) #### v1.1.0 @@ -526,6 +610,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - Tokens API, improved testing and small extra utils [`#5`](https://github.com/code-corps/stripity_stripe/pull/5) - Refreshed elixir version and updated doc [`#3`](https://github.com/code-corps/stripity_stripe/pull/3) - Bumped to v1.1.0. Fixes #22 [`#22`](https://github.com/code-corps/stripity_stripe/issues/22) -- Updated README and bumpted version [`8da4b18`](https://github.com/code-corps/stripity_stripe/commit/8da4b18b8f1db304240b06bbe436b04074460ac3) +- Updated README and bumped version [`8da4b18`](https://github.com/code-corps/stripity_stripe/commit/8da4b18b8f1db304240b06bbe436b04074460ac3) - Connect-ified rest of API. Finished Connect standalone support. Revamped a bit of doc to add links. Merged in latest from rob's repo. Completed move of subscriptions/invoices to their own module. [`9857705`](https://github.com/code-corps/stripity_stripe/commit/9857705bfbbf048c28d35012690dc995f01a5f93) - Initial [`1d5a868`](https://github.com/code-corps/stripity_stripe/commit/1d5a868d0cf064df0562f4448674680780fcd4b5) diff --git a/LICENSE b/LICENSE.md similarity index 80% rename from LICENSE rename to LICENSE.md index 04a68d87d..35bcec884 100644 --- a/LICENSE +++ b/LICENSE.md @@ -1,20 +1,24 @@ -New BSD License +# New BSD License + http://www.opensource.org/licenses/bsd-license.php + Copyright (c) 2015-2016, Rob Conery - 2016, Code Corps PBC and Strumber + +Copyright (c) 2016, Code Corps PBC and Strumber + All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -Redistributions of source code must retain the above copyright notice, this list +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -Neither the names Code Corps nor Strumber, nor the names of its contributors may +3. Neither the names Code Corps nor Strumber, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/README.md b/README.md index 7cb092fcb..08708b982 100644 --- a/README.md +++ b/README.md @@ -41,13 +41,13 @@ Starting with stripity_stripe version 2.5.0, you can specify the Stripe API Vers Install the dependency by version: -```ex +```elixir {:stripity_stripe, "~> 2.0"} ``` Or by commit reference (still awaiting hex publish rights so this is your best best for now): -```ex +```elixir {:stripity_stripe, git: "https://github.com/code-corps/stripity_stripe", ref: "8c091d4278d29a917bacef7bb2f0606317fcc025"} ``` @@ -55,7 +55,7 @@ Next, add to your applications: _Not necessary if using elixir >= 1.4_ -```ex +```elixir defp application do [applications: [:stripity_stripe]] end @@ -65,7 +65,7 @@ end To make API calls, it is necessary to configure your Stripe secret key. -```ex +```elixir use Mix.Config config :stripity_stripe, api_key: System.get_env("STRIPE_SECRET") @@ -75,7 +75,7 @@ config :stripity_stripe, api_key: "YOUR SECRET KEY" It's possible to use a function or a tuple to resolve the secret: -```ex +```elixir config :stripity_stripe, api_key: {MyApp.Secrets, :stripe_secret, []} # OR config :stripity_stripe, api_key: fn -> System.get_env("STRIPE_SECRET") end @@ -83,7 +83,7 @@ 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 +```elixir config :stripity_stripe, json_library: Poison ``` @@ -91,7 +91,7 @@ config :stripity_stripe, json_library: Poison To set timeouts, pass opts for the http client. The default one is Hackney. -```ex +```elixir config :stripity_stripe, hackney_opts: [{:connect_timeout, 1000}, {:recv_timeout, 5000}] ``` @@ -99,7 +99,7 @@ config :stripity_stripe, hackney_opts: [{:connect_timeout, 1000}, {:recv_timeout To set retries, you can pass the number of attempts and range of backoff (time between attempting the request again) in milliseconds. -```ex +```elixir config :stripity_stripe, :retries, [max_attempts: 3, base_backoff: 500, max_backoff: 2_000] ``` @@ -237,13 +237,13 @@ Works with API version 2015-10-16 Install the dependency: -```ex +```elixir {:stripity_stripe, "~> 1.6"} ``` Next, add to your applications: -```ex +```elixir defp application do [applications: [:stripity_stripe]] end @@ -253,7 +253,7 @@ end To make API calls, it is necessary to configure your Stripe secret key (and optional platform client id if you are using Stripe Connect): -```ex +```elixir use Mix.Config config :stripity_stripe, secret_key: "YOUR SECRET KEY" @@ -275,13 +275,13 @@ I've tried to make the API somewhat comprehensive and intuitive. If you'd like t In general, if Stripe requires some information for a given API call, you'll find that as part of the arity of the given function. For instance if you want to delete a Customer, you'll find that you _must_ pass the id along: -```ex +```elixir {:ok, result} = Stripe.Customers.delete "some_id" ``` For optional arguments, you can send in a Keyword list that will get translated to parameters. So if you want to update a Subscription, for instance, you must send in the `customer_id` and `subscription_id` with the list of changes: -```ex +```elixir # Change customer to the Premium subscription {:ok, result} = Stripe.Customers.change_subscription "customer_id", "sub_id", [plan: "premium"] ``` @@ -290,7 +290,7 @@ Metadata (metadata:) key is supported on most object type and allow the storage That's the rule of thumb with this library. If there are any errors with your call, they will bubble up to you in the `{:error, message}` match. -```ex +```elixir # Example of paging through events {:ok, events} = Stripe.Events.list(key, "", 100) # second arg is a marker for paging @@ -312,7 +312,7 @@ Stripe Connect allows you to provide your customers with an easy onboarding to t First, you need to register your platform on Stripe Connect to obtain a `client_id`. In your account settings, there's a "Connect" tab, select it. Then fill the information to activate your connect platform settings. The select he `client_id` (notice there's one for dev and one for prod), stash this `client_id` in the config file under -```ex +```elixir config :stripity_stripe, platform_client_id: "ac_???" ``` @@ -325,7 +325,7 @@ Here's an example of a button to start the workflow: You can generate this URL using: -```ex +```elixir url = Stripe.Connect.generate_button_url csrf_token ``` @@ -343,14 +343,14 @@ or Using the code request parameter, you make the following call: -```ex +```elixir {:ok, resp} -> Stripe.Connect.oauth_token_callback code resp[:access_token] ``` `resp` will look like this: -```ex +```elixir %{ token_type: "bearer", stripe_publishable_key: PUBLISHABLE_KEY, @@ -383,7 +383,7 @@ Feedback, feature requests, and fixes are welcomed and encouraged. Please make a # License -Please see [LICENSE](LICENSE) for licensing details. +Please see [LICENSE.md](./LICENSE.md) for licensing details. # History diff --git a/lib/stripe/api.ex b/lib/stripe/api.ex index 27197a61b..ac19a2a7e 100644 --- a/lib/stripe/api.ex +++ b/lib/stripe/api.ex @@ -25,7 +25,7 @@ defmodule Stripe.API do @default_max_backoff 2_000 @doc """ - In config.exs your implicit or expicit configuration is: + In config.exs your implicit or explicit configuration is: config :stripity_stripe, json_library: Poison # defaults to Jason but can be configured to Poison """ diff --git a/lib/stripe/checkout/session.ex b/lib/stripe/checkout/session.ex index ceddd817d..4dd1fa098 100644 --- a/lib/stripe/checkout/session.ex +++ b/lib/stripe/checkout/session.ex @@ -13,13 +13,176 @@ defmodule Stripe.Session do use Stripe.Entity import Stripe.Request + @type customer_update_param :: %{ + optional(:address) => String.t(), + optional(:name) => String.t(), + optional(:shipping) => String.t() + } + + @type discount_param :: %{ + optional(:coupon) => String.t(), + optional(:promotion_code) => String.t() + } + + @type setup_intent_data :: %{ + optional(:description) => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:on_behalf_of) => String.t() + } + + @type shipping_address_collection :: %{ + allowed_countries: [String.t()] + } + + @typedoc """ + For sessions in `payment` mode only. + One of `"auto"`, `"pay"`, `"book"`, or `"donate"`. + """ + @type submit_type :: String.t() + + @type breakdown_discount :: %{ + amount: integer(), + discount: Stripe.Discount.t() + } + + @type breakdown_tax :: %{ + amount: integer(), + rate: Stripe.TaxID.t() + } + + @type total_details :: %{ + :amount_discount => integer(), + :amount_shipping => integer(), + :amount_tax => integer(), + optional(:breakdown) => %{ + discounts: [breakdown_discount()], + taxes: [breakdown_tax()] + } + } + + @type tax_id_collection :: %{ + enabled: boolean() + } + + @typedoc """ + One of `"personal"` or "business"`. + """ + @type acss_mandate_transaction_type :: String.t() + + @typedoc """ + One of `"interval"`, `"sporadic"`, or "combined"`. + """ + @type acss_mandate_payment_schedule :: String.t() + + @type acss_mandate_options :: %{ + url: String.t(), + default_for: [String.t()], + interval_description: String.t() | nil, + payment_schedule: acss_mandate_payment_schedule() | nil, + transaction_type: acss_mandate_transaction_type() + } + + @typedoc """ + One of `"automatic"`, `"instant"`, or "microdeposits"`. + """ + @type acss_verification_method :: String.t() + + @type acss_debit :: %{ + currency: String.t() | nil, + mandate_options: acss_mandate_options() | nil, + verification_method: acss_verification_method() + } + + @type boleto :: %{ + expires_after_days: non_neg_integer() | nil + } + + @type oxxo :: %{ + expires_after_days: non_neg_integer() | nil + } + + @type payment_method_options :: %{ + acss_debit: acss_debit() | nil, + boleto: boleto() | nil, + oxxo: oxxo() | nil + } + + @type customer_details :: %{ + email: String.t() | nil, + tax_exempt: String.t() | nil, + tax_ids: [Stripe.TaxID.tax_id_data()] + } + + @type consent :: %{ + promotions: String.t() + } + + @type consent_collection :: %{ + promotions: String.t() + } + + @typedoc """ + One of `"requires_location_inputs"`, `"complete"`, `"failed"`. + """ + @type automatic_tax_status :: String.t() + + @type automatic_tax :: %{ + enabled: boolean(), + status: automatic_tax_status() | nil + } + + @type automatic_tax_param :: %{ + enabled: boolean() + } + + @type expiration :: %{ + optional(:recovery) => %{ + optional(:allow_promotion_codes) => boolean(), + optional(:enabled) => boolean(), + :expires_at => Stripe.timestamp(), + :url => String.t() + } + } + @type line_item :: %{ - :amount => integer(), + optional(:name) => String.t(), + optional(:quantity) => integer(), + optional(:adjustable_quantity) => adjustable_quantity(), + optional(:amount) => integer(), + optional(:currency) => String.t(), + optional(:description) => String.t(), + optional(:dynamic_tax_rates) => list(String.t()), + optional(:images) => list(String.t()), + optional(:price) => String.t(), + optional(:price_data) => price_data, + optional(:tax_rates) => list(String.t()) + } + + @type adjustable_quantity :: %{ + :enabled => boolean(), + optional(:maximum) => integer(), + optional(:minimum) => integer() + } + + @type price_data :: %{ :currency => String.t(), + optional(:product) => String.t(), + optional(:product_data) => product_data(), + optional(:unit_amount) => integer(), + optional(:unit_amount_decimal) => integer(), + optional(:recurring) => recurring() + } + + @type product_data :: %{ :name => String.t(), - :quantity => integer(), optional(:description) => String.t(), - optional(:images) => list(String.t()) + optional(:images) => list(String.t()), + optional(:metadata) => Stripe.Types.metadata() + } + + @type recurring :: %{ + :interval => String.t(), + :interval_count => integer() } @type capture_method :: :automatic | :manual @@ -35,6 +198,7 @@ defmodule Stripe.Session do optional(:metadata) => Stripe.Types.metadata(), optional(:on_behalf_of) => String.t(), optional(:receipt_email) => String.t(), + optional(:setup_future_usage) => String.t(), optional(:shipping) => Stripe.Types.shipping(), optional(:statement_descriptor) => String.t(), optional(:transfer_data) => transfer_data @@ -58,7 +222,7 @@ defmodule Stripe.Session do @type create_params :: %{ :cancel_url => String.t(), - :payment_method_types => list(String.t()), + optional(:payment_method_types) => list(String.t()), :success_url => String.t(), optional(:mode) => String.t(), optional(:client_reference_id) => String.t(), @@ -67,59 +231,111 @@ defmodule Stripe.Session do optional(:line_items) => list(line_item), optional(:locale) => String.t(), optional(:metadata) => Stripe.Types.metadata(), + optional(:after_expiration) => expiration(), + optional(:allow_promotion_codes) => boolean(), + optional(:automatic_tax) => automatic_tax_param(), + optional(:consent_collection) => consent_collection(), + optional(:customer_update) => customer_update_param(), + optional(:discounts) => [discount_param()], + optional(:expires_at) => Stripe.timestamp(), optional(:payment_intent_data) => payment_intent_data, - optional(:subscription_data) => subscription_data + optional(:payment_method_options) => payment_method_options(), + optional(:setup_intent_data) => setup_intent_data(), + optional(:shipping_address_collection) => shipping_address_collection(), + optional(:submit_type) => submit_type(), + optional(:subscription_data) => subscription_data, + optional(:tax_id_collection) => tax_id_collection() } + @typedoc """ + One of `"payment"`, `"setup"`, or `"subscription"`. + """ + @type mode :: String.t() + + @typedoc """ + One of `"paid"`, `"unpaid"`, or `"no_payment_required"`. + """ + @type payment_status :: String.t() + @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), + after_expiration: expiration() | nil, + allow_promotion_codes: boolean() | nil, + amount_subtotal: integer() | nil, + amount_total: integer() | nil, + automatic_tax: automatic_tax(), billing_address_collection: String.t(), cancel_url: boolean(), client_reference_id: String.t(), + consent: consent() | nil, + consent_collection: consent_collection() | nil, + currency: String.t(), customer: Stripe.id() | Stripe.Customer.t() | nil, + customer_details: customer_details() | nil, customer_email: String.t(), display_items: list(line_item), + expires_at: Stripe.timestamp() | nil, livemode: boolean(), locale: boolean(), metadata: Stripe.Types.metadata(), - mode: String.t(), + mode: mode(), payment_intent: Stripe.id() | Stripe.PaymentIntent.t() | nil, + payment_method_options: payment_method_options() | nil, payment_method_types: list(String.t()), + payment_status: payment_status(), + recovered_from: Stripe.id() | nil, setup_intent: Stripe.id() | Stripe.SetupIntent.t() | nil, shipping: %{ address: Stripe.Types.shipping(), name: String.t() }, - shipping_address_collection: %{ - allowed_countries: [String.t()] - }, - submit_type: String.t() | nil, + shipping_address_collection: shipping_address_collection(), + submit_type: submit_type() | nil, subscription: Stripe.id() | Stripe.Subscription.t() | nil, - success_url: String.t() + success_url: String.t(), + tax_id_collection: tax_id_collection() | nil, + total_details: total_details() | nil, + url: String.t() } defstruct [ :id, :object, + :after_expiration, + :allow_promotion_codes, + :amount_subtotal, + :amount_total, + :automatic_tax, :billing_address_collection, :cancel_url, :client_reference_id, + :consent, + :consent_collection, + :currency, :customer, + :customer_details, :customer_email, :display_items, + :expires_at, :livemode, :locale, :metadata, :mode, :payment_intent, + :payment_method_options, :payment_method_types, + :recovered_from, :setup_intent, :shipping, :shipping_address_collection, :submit_type, :subscription, - :success_url + :success_url, + :tax_id_collection, + :total_details, + :url, + :payment_status ] @plural_endpoint "checkout/sessions" @@ -144,5 +360,16 @@ defmodule Stripe.Session do |> make_request() end + @doc """ + Invalidates a session + """ + @spec expire(Stripe.id() | t) :: {:ok, t} | {:error, Stripe.Error.t()} + def expire(id, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/expire") + |> put_method(:post) + |> make_request() + end + defdelegate list_line_items(id, opts \\ []), to: Stripe.Checkout.Session.LineItems, as: :list end diff --git a/lib/stripe/config.ex b/lib/stripe/config.ex index 2f90a1675..09d0c5674 100644 --- a/lib/stripe/config.ex +++ b/lib/stripe/config.ex @@ -5,8 +5,8 @@ defmodule Stripe.Config do @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. + wrapped expanded value. If the value was a function it gets evaluated, if + the value is a tuple of three elements it gets applied. """ @spec resolve(atom, any) :: any def resolve(key, default \\ nil) diff --git a/lib/stripe/connect/account.ex b/lib/stripe/connect/account.ex index d7ce22a96..b211e1c8f 100644 --- a/lib/stripe/connect/account.ex +++ b/lib/stripe/connect/account.ex @@ -133,6 +133,26 @@ defmodule Stripe.Account do transfers: String.t() | nil } + @type future_requirements :: %{ + alternatives: list(alternatives) | nil, + current_deadline: Stripe.timestamp() | nil, + currently_due: Stripe.List.t(String.t()) | nil, + disabled_reason: String.t() | nil, + errors: Stripe.List.t(error) | nil, + eventually_due: Stripe.List.t(String.t()) | nil, + past_due: Stripe.List.t(String.t()) | nil, + pending_verification: Stripe.List.t(String.t()) | nil + } + + @type alternatives :: %{ + alternative_fields_due: Stripe.List.t(String.t()) | nil, + original_fields_due: Stripe.List.t(String.t()) | nil + } + + @type error :: %{ + code: String.t() + } + @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), @@ -141,6 +161,7 @@ defmodule Stripe.Account do capabilities: capabilities | nil, charges_enabled: boolean, company: company | nil, + controller: map, country: String.t(), created: Stripe.timestamp() | nil, default_currency: String.t(), @@ -148,6 +169,7 @@ defmodule Stripe.Account do details_submitted: boolean, email: String.t() | nil, external_accounts: Stripe.List.t(Stripe.BankAccount.t() | Stripe.Card.t()), + future_requirements: future_requirements | nil, individual: individual | nil, metadata: Stripe.Types.metadata(), payouts_enabled: boolean | nil, @@ -165,6 +187,7 @@ defmodule Stripe.Account do :capabilities, :charges_enabled, :company, + :controller, :country, :created, :default_currency, @@ -172,6 +195,7 @@ defmodule Stripe.Account do :details_submitted, :email, :external_accounts, + :future_requirements, :individual, :metadata, :payouts_enabled, diff --git a/lib/stripe/connect/application_fee.ex b/lib/stripe/connect/application_fee.ex index deb74f80a..115bef648 100644 --- a/lib/stripe/connect/application_fee.ex +++ b/lib/stripe/connect/application_fee.ex @@ -47,9 +47,9 @@ defmodule Stripe.ApplicationFee do @doc """ Retrieves the details of the application fees """ - @spec retrieve(Stripe.id()) :: {:ok, t} | {:error, Stripe.Error.t()} - def retrieve(id) do - new_request() + @spec retrieve(Stripe.id(), Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} + def retrieve(id, opts \\ []) do + new_request(opts) |> put_endpoint(@endpoint <> "/#{get_id!(id)}") |> put_method(:get) |> make_request() diff --git a/lib/stripe/connect/oauth.ex b/lib/stripe/connect/oauth.ex index 2ffd483d8..4a6d6a397 100644 --- a/lib/stripe/connect/oauth.ex +++ b/lib/stripe/connect/oauth.ex @@ -122,7 +122,7 @@ defmodule Stripe.Connect.OAuth do @doc ~S""" Generate the URL to start a Stripe workflow. - ## Paremeter Map Keys + ## Parameter Map Keys The parameter map keys are derived from the [valid request parameter](https://stripe.com/docs/connect/reference) for the Stripe Connect authorize endpoint. A parameter only needs to be provided if diff --git a/lib/stripe/connect/person.ex b/lib/stripe/connect/person.ex index 1a5674b35..77f4e8c51 100644 --- a/lib/stripe/connect/person.ex +++ b/lib/stripe/connect/person.ex @@ -70,6 +70,7 @@ defmodule Stripe.Person do last_name_kanji: String.t() | nil, maiden_name: String.t() | nil, metadata: Stripe.Types.metadata(), + nationality: String.t(), phone: String.t() | nil, relationship: relationship() | nil, requirements: requirements() | nil, @@ -98,6 +99,7 @@ defmodule Stripe.Person do :last_name_kanji, :maiden_name, :metadata, + :nationality, :phone, :relationship, :requirements, diff --git a/lib/stripe/converter.ex b/lib/stripe/converter.ex index 9914e0680..e5ef77a34 100644 --- a/lib/stripe/converter.ex +++ b/lib/stripe/converter.ex @@ -36,6 +36,8 @@ defmodule Stripe.Converter do external_account file file_link + identity.verification_session + identity.verification_report invoice invoiceitem issuing.authorization diff --git a/lib/stripe/core_resources/balance.ex b/lib/stripe/core_resources/balance.ex index 8b7a2ea8d..4d9dbbed2 100644 --- a/lib/stripe/core_resources/balance.ex +++ b/lib/stripe/core_resources/balance.ex @@ -17,10 +17,20 @@ defmodule Stripe.Balance do } } + @type issuing_funds :: %{ + currency: String.t(), + amount: integer + } + + @type issuing :: %{ + available: list(issuing_funds) + } + @type t :: %__MODULE__{ object: String.t(), available: list(funds), connect_reserved: list(funds) | nil, + issuing: issuing, livemode: boolean, pending: list(funds) } @@ -29,6 +39,7 @@ defmodule Stripe.Balance do :object, :available, :connect_reserved, + :issuing, :livemode, :pending ] diff --git a/lib/stripe/core_resources/charge.ex b/lib/stripe/core_resources/charge.ex index e338a65ee..4fe0883fd 100644 --- a/lib/stripe/core_resources/charge.ex +++ b/lib/stripe/core_resources/charge.ex @@ -68,6 +68,7 @@ defmodule Stripe.Charge do id: Stripe.id(), object: String.t(), amount: non_neg_integer, + amount_captured: non_neg_integer, amount_refunded: non_neg_integer, application: Stripe.id() | nil, application_fee: Stripe.id() | Stripe.ApplicationFee.t() | nil, @@ -114,6 +115,7 @@ defmodule Stripe.Charge do :id, :object, :amount, + :amount_captured, :amount_refunded, :application, :application_fee, diff --git a/lib/stripe/core_resources/file_link.ex b/lib/stripe/core_resources/file_link.ex index fea119791..b8b52e9ce 100644 --- a/lib/stripe/core_resources/file_link.ex +++ b/lib/stripe/core_resources/file_link.ex @@ -76,7 +76,7 @@ defmodule Stripe.FileLink do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(file_link)}") |> put_method(:get) - |> make_file_upload_request() + |> make_request() end @doc """ @@ -125,6 +125,6 @@ defmodule Stripe.FileLink do |> put_method(:get) |> put_params(params) |> cast_to_id([:ending_before, :starting_after, :limit, :purpose]) - |> make_file_upload_request() + |> make_request() end end diff --git a/lib/stripe/core_resources/file_upload.ex b/lib/stripe/core_resources/file_upload.ex index fe455513f..034561a47 100644 --- a/lib/stripe/core_resources/file_upload.ex +++ b/lib/stripe/core_resources/file_upload.ex @@ -18,6 +18,7 @@ defmodule Stripe.FileUpload do id: Stripe.id(), object: String.t(), created: Stripe.timestamp(), + expires_at: Stripe.timestamp(), filename: String.t() | nil, links: Stripe.List.t(Stripe.FileLink.t()), purpose: String.t(), @@ -31,6 +32,7 @@ defmodule Stripe.FileUpload do :id, :object, :created, + :expires_at, :filename, :links, :purpose, @@ -64,7 +66,7 @@ defmodule Stripe.FileUpload do new_request(opts) |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") |> put_method(:get) - |> make_file_upload_request() + |> make_request() end @doc """ @@ -85,6 +87,6 @@ defmodule Stripe.FileUpload do |> put_method(:get) |> put_params(params) |> cast_to_id([:ending_before, :starting_after, :limit, :purpose]) - |> make_file_upload_request() + |> make_request() end end diff --git a/lib/stripe/core_resources/setup_intent.ex b/lib/stripe/core_resources/setup_intent.ex index d14423a5b..943d13b6f 100644 --- a/lib/stripe/core_resources/setup_intent.ex +++ b/lib/stripe/core_resources/setup_intent.ex @@ -55,6 +55,7 @@ defmodule Stripe.SetupIntent do customer: Stripe.id() | Stripe.Customer.t() | nil, description: String.t() | nil, last_setup_error: last_setup_error | nil, + latest_attempt: String.t(), livemode: boolean, mandate: Stripe.id() | Stripe.Mandate.t() | nil, metadata: Stripe.Types.metadata(), @@ -78,6 +79,7 @@ defmodule Stripe.SetupIntent do :customer, :description, :last_setup_error, + :latest_attempt, :livemode, :mandate, :metadata, diff --git a/lib/stripe/core_resources/webhook_endpoint.ex b/lib/stripe/core_resources/webhook_endpoint.ex index 5c9fa2178..d5d2ad3c0 100644 --- a/lib/stripe/core_resources/webhook_endpoint.ex +++ b/lib/stripe/core_resources/webhook_endpoint.ex @@ -13,6 +13,7 @@ defmodule Stripe.WebhookEndpoint do require Stripe.Util @type t :: %__MODULE__{ + application: String.t() | nil, created: Stripe.timestamp(), deleted: boolean, description: String.t(), @@ -27,6 +28,7 @@ defmodule Stripe.WebhookEndpoint do } defstruct [ + :application, :created, :deleted, :description, diff --git a/lib/stripe/identity/verification_report.ex b/lib/stripe/identity/verification_report.ex new file mode 100644 index 000000000..27c4d2e6e --- /dev/null +++ b/lib/stripe/identity/verification_report.ex @@ -0,0 +1,147 @@ +defmodule Stripe.Identity.VerificationReport do + @moduledoc """ + Work with Stripe Identity VerificationReport objects. + + You can: + - Retrieve a Verification Report with a specified `id`. + - List all Verification Reports. + + Stripe API reference: https://stripe.com/docs/api/identity/verification_reports + """ + + use Stripe.Entity + import Stripe.Request + + alias Stripe.Identity.VerificationSession + + @type options :: %{ + document: %{ + allowed_types: list(String.t()), + require_id_number: boolean(), + require_live_capture: boolean(), + require_matching_selfie: boolean() + }, + id_number: map() + } + + @type document :: %{ + address: %{ + city: String.t(), + country: String.t(), + line1: String.t(), + line2: String.t(), + postal_code: String.t(), + state: String.t() + }, + dob: %{ + day: integer(), + month: integer(), + year: integer() + }, + error: %{ + code: String.t(), + reason: String.t() + }, + expiration_date: %{ + day: integer(), + month: integer(), + year: integer() + }, + files: [Stripe.id()], + first_name: String.t(), + issued_date: String.t(), + issuing_country: String.t(), + last_name: String.t(), + number: String.t(), + status: String.t(), + type: String.t() + } + + @type id_number :: %{ + dob: %{ + day: integer(), + month: integer(), + year: integer() + }, + error: %{ + code: String.t(), + reason: String.t() + }, + first_name: String.t(), + id_number: String.t(), + id_number_type: String.t(), + last_name: String.t(), + status: String.t() + } + + @type selfie :: %{ + document: String.t(), + error: %{ + code: String.t(), + reason: String.t() + }, + selfie: String.t(), + status: String.t() + } + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + created: Stripe.timestamp(), + livemode: boolean(), + options: options(), + type: String.t(), + verification_session: Stripe.id(), + document: document(), + id_number: id_number(), + selfie: selfie() + } + + defstruct [ + :id, + :object, + :created, + :livemode, + :options, + :type, + :verification_session, + :document, + :id_number, + :selfie + ] + + @plural_endpoint "identity/verification_reports" + + @doc """ + Retrieves an existing VerificationReport. + """ + @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 """ + List all Verification Reports. + """ + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t())} | {:error, Stripe.Error.t()} + when params: %{ + optional(:created) => Stripe.date_query(), + optional(:type) => String.t(), + optional(:verification_session) => VerificationSession.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([:verification_session, :ending_before, :starting_after]) + |> make_request() + end +end diff --git a/lib/stripe/identity/verification_session.ex b/lib/stripe/identity/verification_session.ex new file mode 100644 index 000000000..0fbbff939 --- /dev/null +++ b/lib/stripe/identity/verification_session.ex @@ -0,0 +1,190 @@ +defmodule Stripe.Identity.VerificationSession do + @moduledoc """ + Work with Stripe Identity VerificationSession objects. + + You can: + - Create a Verification Session. + - List all Verification Sessions. + - Retrieve a verification session with a specified `id`. + - Update a Verification Session. + - Cancel a Verification Session. + - Redact a Verification Session. + + Stripe API reference: https://stripe.com/docs/api/identity/verification_sessions + """ + + use Stripe.Entity + import Stripe.Request + + @type last_error :: %{ + code: list(String.t()), + reason: String.t() + } + + @type options :: %{ + document: %{ + allowed_types: list(String.t()), + require_id_number: boolean(), + require_live_capture: boolean(), + require_matching_selfie: boolean() + }, + id_number: map() + } + + @type redaction :: %{ + status: String.t() + } + + @type verified_outputs :: %{ + address: %{ + city: String.t(), + country: String.t(), + line1: String.t(), + line2: String.t(), + postal_code: String.t(), + state: String.t() + }, + dob: %{ + day: integer(), + month: integer(), + year: integer() + }, + first_name: String.t(), + id_number: String.t(), + id_number_type: String.t(), + last_name: String.t() + } + + @type t :: %__MODULE__{ + id: Stripe.id(), + object: String.t(), + client_secret: String.t() | nil, + created: Stripe.timestamp(), + last_error: last_error() | nil, + last_verification_report: String.t(), + livemode: boolean(), + metadata: Stripe.Types.metadata(), + options: options(), + redaction: redaction() | nil, + status: String.t(), + type: String.t(), + url: String.t() | nil, + verified_outputs: verified_outputs() | nil + } + + defstruct [ + :id, + :object, + :client_secret, + :created, + :last_error, + :last_verification_report, + :livemode, + :metadata, + :options, + :redaction, + :status, + :type, + :url, + :verified_outputs + ] + + @plural_endpoint "identity/verification_sessions" + + @doc """ + Create a VerificationSession + """ + @spec create(params, Stripe.options()) :: {:ok, t()} | {:error, Stripe.Error.t()} + when params: %{ + :type => String.t(), + optional(:metadata) => Stripe.Types.metadata(), + optional(:options) => options(), + optional(:return_url) => String.t() + } + def create(params, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint) + |> put_params(params) + |> put_method(:post) + |> make_request() + end + + @doc """ + Returns a list of VerificationSessions + """ + @spec list(params, Stripe.options()) :: {:ok, Stripe.List.t(t())} | {:error, Stripe.Error.t()} + when params: %{ + optional(:created) => Stripe.date_query(), + optional(:status) => String.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() + |> put_endpoint(@plural_endpoint) + |> put_method(:get) + |> put_params(params) + |> cast_to_id([:ending_before, :starting_after]) + |> make_request() + end + + @doc """ + Retrieves the details of a VerificationSession that was previously created. + + When the session status is requires_input, + you can use this method to retrieve a valid + client_secret or url to allow re-submission. + """ + @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 """ + Updates a VerificationSession. + + When the session status is requires_input, + you can use this method to update the verification check and options. + """ + @spec update(Stripe.id() | t(), params, Stripe.options()) :: + {:ok, t} | {:error, Stripe.Error.t()} + when params: %{ + optional(:metadata) => Stripe.Types.metadata(), + optional(:options) => options(), + optional(:type) => String.t() + } + def update(id, params \\ %{}, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}") + |> put_method(:post) + |> put_params(params |> Map.delete(:metadata)) + |> make_request() + end + + @doc """ + Cancel a VerificationSession. + """ + @spec cancel(Stripe.id() | t(), Stripe.options()) :: {:ok, t()} | {:error, Stripe.Error.t()} + def cancel(id, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/cancel") + |> put_method(:post) + |> make_request() + end + + @doc """ + Redact a VerificationSession to remove all collected information from Stripe. + """ + @spec redact(Stripe.id() | t(), Stripe.options()) :: {:ok, t()} | {:error, Stripe.Error.t()} + def redact(id, opts \\ []) do + new_request(opts) + |> put_endpoint(@plural_endpoint <> "/#{get_id!(id)}/redact") + |> put_method(:post) + |> make_request() + end +end diff --git a/lib/stripe/payment_methods/payment_method.ex b/lib/stripe/payment_methods/payment_method.ex index 9d71acd5f..2e42c21e5 100644 --- a/lib/stripe/payment_methods/payment_method.ex +++ b/lib/stripe/payment_methods/payment_method.ex @@ -8,6 +8,14 @@ defmodule Stripe.PaymentMethod do use Stripe.Entity import Stripe.Request + @type sepa_debit :: %{ + bank_code: String.t() | nil, + branch_code: String.t() | nil, + country: String.t() | nil, + fingerprint: String.t() | nil, + last4: String.t() | nil + } + @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), @@ -22,6 +30,7 @@ defmodule Stripe.PaymentMethod do customer: Stripe.id() | Stripe.Customer.t() | nil, livemode: boolean, metadata: Stripe.Types.metadata(), + sepa_debit: sepa_debit() | nil, type: String.t() } @@ -34,6 +43,7 @@ defmodule Stripe.PaymentMethod do :customer, :livemode, :metadata, + :sepa_debit, :type ] diff --git a/lib/stripe/payment_methods/source.ex b/lib/stripe/payment_methods/source.ex index aab6adfb7..ba9fdf340 100644 --- a/lib/stripe/payment_methods/source.ex +++ b/lib/stripe/payment_methods/source.ex @@ -229,7 +229,8 @@ defmodule Stripe.Source do :status, :three_d_secure, :type, - :usage + :usage, + :klarna ] @plural_endpoint "sources" diff --git a/lib/stripe/subscriptions/credit_note.ex b/lib/stripe/subscriptions/credit_note.ex index fa0869026..b37098136 100644 --- a/lib/stripe/subscriptions/credit_note.ex +++ b/lib/stripe/subscriptions/credit_note.ex @@ -23,6 +23,11 @@ defmodule Stripe.CreditNote do tax_rate: Stripe.id() | Stripe.TaxRate.t() } + @type discount :: %{ + amount: integer, + discount: String.t(), + } + @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), @@ -32,6 +37,7 @@ defmodule Stripe.CreditNote do customer: Stripe.id() | Stripe.Customer.t() | nil, customer_balance_transaction: Stripe.id() | Stripe.CustomerBalanceTransaction.t() | nil, discount_amount: integer, + discount_amounts: [discount], invoice: Stripe.id() | Stripe.Invoice.t(), lines: Stripe.List.t(Stripe.LineItem.t()), livemode: boolean, @@ -59,6 +65,7 @@ defmodule Stripe.CreditNote do :customer, :customer_balance_transaction, :discount_amount, + :discount_amounts, :invoice, :lines, :livemode, diff --git a/lib/stripe/subscriptions/credit_note_line_item.ex b/lib/stripe/subscriptions/credit_note_line_item.ex index 44619a57a..5eb78871a 100644 --- a/lib/stripe/subscriptions/credit_note_line_item.ex +++ b/lib/stripe/subscriptions/credit_note_line_item.ex @@ -16,12 +16,18 @@ defmodule Stripe.CreditNoteLineItem do tax_rate: Stripe.id() | Stripe.TaxRate.t() } + @type discount :: %{ + amount: integer, + discount: String.t(), + } + @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), amount: integer, description: String.t(), discount_amount: integer, + discount_amounts: [discount], invoice_line_item: Stripe.id() | nil, livemode: boolean, quantity: integer, @@ -38,6 +44,7 @@ defmodule Stripe.CreditNoteLineItem do :amount, :description, :discount_amount, + :discount_amounts, :invoice_line_item, :livemode, :quantity, diff --git a/lib/stripe/subscriptions/discount.ex b/lib/stripe/subscriptions/discount.ex index 1a7851418..b67b7dce9 100644 --- a/lib/stripe/subscriptions/discount.ex +++ b/lib/stripe/subscriptions/discount.ex @@ -8,6 +8,7 @@ defmodule Stripe.Discount do use Stripe.Entity @type t :: %__MODULE__{ + id: Stripe.id(), object: String.t(), coupon: Stripe.Coupon.t(), customer: Stripe.id() | Stripe.Customer.t() | nil, @@ -19,6 +20,7 @@ defmodule Stripe.Discount do } defstruct [ + :id, :object, :coupon, :customer, diff --git a/lib/stripe/subscriptions/invoice.ex b/lib/stripe/subscriptions/invoice.ex index 66c757b88..a37b598a4 100644 --- a/lib/stripe/subscriptions/invoice.ex +++ b/lib/stripe/subscriptions/invoice.ex @@ -22,6 +22,7 @@ defmodule Stripe.Invoice do object: String.t(), account_country: String.t(), account_name: String.t(), + account_tax_ids: list(String.t()), amount_due: integer, amount_paid: integer, amount_remaining: integer, @@ -29,6 +30,7 @@ defmodule Stripe.Invoice do attempt_count: non_neg_integer, attempted: boolean, auto_advance: boolean, + automatic_tax: map, billing_reason: String.t() | nil, charge: Stripe.id() | Stripe.Charge.t() | nil, closed: boolean, @@ -50,19 +52,22 @@ defmodule Stripe.Invoice do deleted: boolean | nil, description: String.t() | nil, discount: Stripe.Discount.t() | nil, + discounts: list(String.t()), due_date: Stripe.timestamp() | nil, ending_balance: integer | nil, footer: String.t() | nil, - forgiven: boolean, hosted_invoice_url: String.t() | nil, invoice_pdf: String.t() | nil, + last_finalization_error: map, lines: Stripe.List.t(Stripe.LineItem.t()), livemode: boolean, metadata: Stripe.Types.metadata() | nil, next_payment_attempt: Stripe.timestamp() | nil, number: String.t() | nil, + on_behalf_of: String.t(), paid: boolean, payment_intent: String.t() | nil, + payment_settings: map, period_end: Stripe.timestamp(), period_start: Stripe.timestamp(), post_payment_credit_notes_amount: integer, @@ -89,7 +94,9 @@ defmodule Stripe.Invoice do ] }, total: integer, + total_discount_amounts: Stripe.List.t(map) | nil, total_tax_amounts: Stripe.List.t(map) | nil, + transfer_data: map, webhooks_delivered_at: Stripe.timestamp() | nil } @@ -118,6 +125,7 @@ defmodule Stripe.Invoice do :object, :account_country, :account_name, + :account_tax_ids, :amount_due, :amount_paid, :amount_remaining, @@ -125,6 +133,7 @@ defmodule Stripe.Invoice do :attempt_count, :attempted, :auto_advance, + :automatic_tax, :billing_reason, :charge, :closed, @@ -146,19 +155,22 @@ defmodule Stripe.Invoice do :deleted, :description, :discount, + :discounts, :due_date, :ending_balance, :footer, - :forgiven, :hosted_invoice_url, :invoice_pdf, + :last_finalization_error, :lines, :livemode, :metadata, :next_payment_attempt, :number, + :on_behalf_of, :paid, :payment_intent, + :payment_settings, :period_end, :period_start, :post_payment_credit_notes_amount, @@ -175,7 +187,9 @@ defmodule Stripe.Invoice do :tax_percent, :threshold_reason, :total, + :total_discount_amounts, :total_tax_amounts, + :transfer_data, :webhooks_delivered_at ] @@ -286,6 +300,7 @@ defmodule Stripe.Invoice do @spec upcoming(map, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} def upcoming(params, opts \\ []) def upcoming(params = %{customer: _customer}, opts), do: get_upcoming(params, opts) + def upcoming(params = %{customer_details: _customer_details}, opts), do: get_upcoming(params, opts) def upcoming(params = %{subscription: _subscription}, opts), do: get_upcoming(params, opts) defp get_upcoming(params, opts) do diff --git a/lib/stripe/subscriptions/invoiceitem.ex b/lib/stripe/subscriptions/invoiceitem.ex index c698224ae..c131a7bae 100644 --- a/lib/stripe/subscriptions/invoiceitem.ex +++ b/lib/stripe/subscriptions/invoiceitem.ex @@ -21,6 +21,7 @@ defmodule Stripe.Invoiceitem do deleted: boolean | nil, description: String.t(), discountable: boolean, + discounts: list(String.t()), invoice: Stripe.id() | Stripe.Invoice.t(), livemode: boolean, metadata: Stripe.Types.metadata(), @@ -33,7 +34,6 @@ defmodule Stripe.Invoiceitem do proration: boolean, quantity: integer, subscription: Stripe.id() | Stripe.Subscription.t() | nil, - subscription_item: Stripe.id() | Stripe.SubscriptionItem.t() | nil, tax_rates: list(Stripe.TaxRate.t()), unit_amount: integer, unit_amount_decimal: String.t() @@ -49,6 +49,7 @@ defmodule Stripe.Invoiceitem do :deleted, :description, :discountable, + :discounts, :invoice, :livemode, :metadata, @@ -58,7 +59,6 @@ defmodule Stripe.Invoiceitem do :proration, :quantity, :subscription, - :subscription_item, :tax_rates, :unit_amount, :unit_amount_decimal diff --git a/lib/stripe/subscriptions/price.ex b/lib/stripe/subscriptions/price.ex index 6ba7fe07b..aff9d90e8 100644 --- a/lib/stripe/subscriptions/price.ex +++ b/lib/stripe/subscriptions/price.ex @@ -43,8 +43,10 @@ defmodule Stripe.Price do "trial_period_days": null, "usage_type": "licensed" }, + "tax_behavior": "unspecified", "tiers": null, "tiers_mode": null, + "transform_lookup_key": false, "transform_quantity": null, "type": "recurring", "unit_amount": 999, @@ -57,11 +59,11 @@ defmodule Stripe.Price do import Stripe.Request @type recurring :: %{ - aggregate_usage: String.t(), - interval: String.t(), - interval_count: pos_integer, - trial_period_days: pos_integer, - usage_type: String.t() + optional(:aggregate_usage) => String.t(), + optional(:interval) => String.t(), + optional(:interval_count) => pos_integer, + optional(:trial_period_days) => pos_integer, + optional(:usage_type) => String.t() } @type price_tier :: %{ @@ -77,6 +79,15 @@ defmodule Stripe.Price do round: String.t() } + @type product_data :: %{ + :name => String.t(), + optional(:active) => boolean, + optional(:metadata) => map, + optional(:statement_descriptor) => String.t(), + optional(:tax_code) => String.t(), + optional(:unit_label) => String.t() + } + @type t :: %__MODULE__{ id: Stripe.id(), object: String.t(), @@ -90,8 +101,10 @@ defmodule Stripe.Price do nickname: String.t(), product: Stripe.id() | Stripe.Product.t(), recurring: recurring(), + tax_behavior: String.t(), tiers: [price_tier()], tiers_mode: String.t(), + transform_lookup_key: boolean(), transform_quantity: transform_quantity(), type: String.t(), unit_amount: pos_integer, @@ -111,8 +124,10 @@ defmodule Stripe.Price do :nickname, :product, :recurring, + :tax_behavior, :tiers, :tiers_mode, + :transform_lookup_key, :transform_quantity, :type, :unit_amount, @@ -134,7 +149,9 @@ defmodule Stripe.Price do optional(:metadata) => Stripe.Types.metadata(), optional(:nickname) => String.t(), optional(:product) => Stripe.id() | Stripe.Product.t(), + optional(:product_data) => product_data, optional(:recurring) => recurring(), + optional(:tax_behavior) => String.t(), optional(:tiers) => [price_tier()], optional(:tiers_mode) => String.t(), optional(:billing_scheme) => String.t(), diff --git a/lib/stripe/subscriptions/product.ex b/lib/stripe/subscriptions/product.ex index 24c24a4db..6cc3e382c 100644 --- a/lib/stripe/subscriptions/product.ex +++ b/lib/stripe/subscriptions/product.ex @@ -106,10 +106,18 @@ defmodule Stripe.Product do @spec update(Stripe.id() | t, params, Stripe.options()) :: {:ok, t} | {:error, Stripe.Error.t()} when params: %{ + optional(:active) => boolean, optional(:attributes) => list, - optional(:name) => String.t(), + optional(:description) => String.t(), + optional(:images) => list(String.t()), optional(:metadata) => Stripe.Types.metadata(), - optional(:statement_descriptor) => String.t() + optional(:name) => String.t(), + optional(:package_dimensions) => map, + optional(:shippable) => boolean, + optional(:statement_descriptor) => String.t(), + optional(:tax_code) => String.t(), + optional(:unit_label) => String.t(), + optional(:url) => String.t() } | %{} def update(id, params, opts \\ []) do diff --git a/lib/stripe/subscriptions/promotion_code.ex b/lib/stripe/subscriptions/promotion_code.ex index c7d843850..a3a02e274 100644 --- a/lib/stripe/subscriptions/promotion_code.ex +++ b/lib/stripe/subscriptions/promotion_code.ex @@ -21,8 +21,10 @@ defmodule Stripe.PromotionCode do active: boolean, code: String.t() | nil, coupon: Stripe.Coupon.t(), + customer: String.t() | nil, created: Stripe.timestamp(), deleted: boolean | nil, + expires_at: Stripe.timestamp() | nil, livemode: boolean, max_redemptions: pos_integer | nil, metadata: Stripe.Types.metadata(), @@ -43,8 +45,10 @@ defmodule Stripe.PromotionCode do :active, :code, :coupon, + :customer, :created, :deleted, + :expires_at, :livemode, :max_redemptions, :metadata, @@ -64,6 +68,7 @@ defmodule Stripe.PromotionCode do optional(:code) => String.t(), optional(:active) => boolean, optional(:customer) => Stripe.id(), + optional(:expires_at) => Stripe.timestamp(), optional(:max_redemptions) => pos_integer, optional(:metadata) => Stripe.Types.metadata(), optional(:restrictions) => restrictions() diff --git a/lib/stripe/subscriptions/subscription.ex b/lib/stripe/subscriptions/subscription.ex index 4811a0eb1..4639b1005 100644 --- a/lib/stripe/subscriptions/subscription.ex +++ b/lib/stripe/subscriptions/subscription.ex @@ -39,16 +39,14 @@ defmodule Stripe.Subscription do object: String.t(), application_fee_percent: float | nil, automatic_tax: map, - billing: String.t() | nil, billing_cycle_anchor: Stripe.timestamp() | nil, billing_thresholds: map | nil, - cancel_at: Stripe.timestamp() | nil, - cancel_at_period_end: boolean, - canceled_at: Stripe.timestamp() | nil, - cancellation_details: map, collection_method: String.t() | nil, collection_method_cycle_anchor: Stripe.timestamp() | nil, collection_method_thresholds: Stripe.Types.collection_method_thresholds() | nil, + cancel_at: Stripe.timestamp() | nil, + cancel_at_period_end: boolean, + canceled_at: Stripe.timestamp() | nil, created: Stripe.timestamp(), current_period_end: Stripe.timestamp() | nil, current_period_start: Stripe.timestamp() | nil, @@ -74,9 +72,9 @@ defmodule Stripe.Subscription do start_date: Stripe.timestamp(), status: String.t(), tax_percent: float | nil, + transfer_data: map, trial_end: Stripe.timestamp() | nil, - trial_start: Stripe.timestamp() | nil, - trial_settings: map + trial_start: Stripe.timestamp() | nil } defstruct [ @@ -84,16 +82,14 @@ defmodule Stripe.Subscription do :object, :application_fee_percent, :automatic_tax, - :billing, :billing_cycle_anchor, :billing_thresholds, - :cancel_at, - :cancel_at_period_end, - :canceled_at, - :cancellation_details, :collection_method, :collection_method_cycle_anchor, :collection_method_thresholds, + :cancel_at, + :cancel_at_period_end, + :canceled_at, :created, :current_period_end, :current_period_start, @@ -119,9 +115,9 @@ defmodule Stripe.Subscription do :start_date, :status, :tax_percent, + :transfer_data, :trial_end, - :trial_start, - :trial_settings + :trial_start ] @plural_endpoint "subscriptions" @@ -135,10 +131,11 @@ defmodule Stripe.Subscription do optional(:application_fee_percent) => integer, optional(:billing_cycle_anchor) => Stripe.timestamp(), optional(:billing_thresholds) => map, + optional(:collection_method) => String.t(), + optional(:collection_method_cycle_anchor) => Stripe.timestamp(), optional(:cancel_at) => Stripe.timestamp(), optional(:cancel_at_period_end) => boolean, optional(:collection_method) => String.t(), - optional(:collection_method_cycle_anchor) => Stripe.timestamp(), optional(:coupon) => Stripe.id() | Stripe.Coupon.t(), optional(:days_until_due) => non_neg_integer, :items => [ @@ -153,7 +150,9 @@ defmodule Stripe.Subscription do ], optional(:default_payment_method) => Stripe.id(), optional(:default_tax_rates) => [Stripe.id()], + optional(:expand) => [String.t()], optional(:metadata) => Stripe.Types.metadata(), + optional(:payment_behavior) => String.t(), optional(:prorate) => boolean, optional(:proration_behavior) => String.t(), optional(:promotion_code) => Stripe.id(), @@ -192,10 +191,11 @@ defmodule Stripe.Subscription do optional(:application_fee_percent) => float, optional(:billing_cycle_anchor) => Stripe.timestamp(), optional(:billing_thresholds) => map, + optional(:collection_method) => String.t(), + optional(:collection_method_cycle_anchor) => Stripe.timestamp(), optional(:cancel_at) => Stripe.timestamp(), optional(:cancel_at_period_end) => boolean(), optional(:collection_method) => String.t(), - optional(:collection_method_cycle_anchor) => Stripe.timestamp(), optional(:coupon) => Stripe.id() | Stripe.Coupon.t(), optional(:days_until_due) => non_neg_integer, optional(:items) => [ diff --git a/lib/stripe/subscriptions/subscription_item.ex b/lib/stripe/subscriptions/subscription_item.ex index c888ce323..08c3fe46c 100644 --- a/lib/stripe/subscriptions/subscription_item.ex +++ b/lib/stripe/subscriptions/subscription_item.ex @@ -49,7 +49,7 @@ defmodule Stripe.SubscriptionItem do optional(:metadata) => Stripe.Types.metadata(), optional(:prorate) => boolean, optional(:proration_date) => Stripe.timestamp(), - optional(:quantity) => float, + optional(:quantity) => non_neg_integer, optional(:tax_rates) => list(String.t()) } def create(%{subscription: _} = params, opts \\ []) do @@ -84,7 +84,7 @@ defmodule Stripe.SubscriptionItem do optional(:price) => Stripe.id() | Stripe.Price.t(), optional(:prorate) => boolean, optional(:proration_date) => Stripe.timestamp(), - optional(:quantity) => float, + optional(:quantity) => non_neg_integer, optional(:tax_rates) => list(String.t()) } def update(id, params, opts \\ []) do diff --git a/lib/stripe/util.ex b/lib/stripe/util.ex index 61e470731..518829313 100644 --- a/lib/stripe/util.ex +++ b/lib/stripe/util.ex @@ -54,6 +54,13 @@ defmodule Stripe.Util do def object_name_to_module("billing_portal.session"), do: Stripe.BillingPortal.Session def object_name_to_module("checkout.session"), do: Stripe.Session def object_name_to_module("file"), do: Stripe.FileUpload + + def object_name_to_module("identity.verification_session"), + do: Stripe.Identity.VerificationSession + + def object_name_to_module("identity.verification_report"), + do: Stripe.Identity.VerificationReport + 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.cardholder"), do: Stripe.Issuing.Cardholder diff --git a/lib/stripe/webhook_handler.ex b/lib/stripe/webhook_handler.ex new file mode 100644 index 000000000..ba19ec28a --- /dev/null +++ b/lib/stripe/webhook_handler.ex @@ -0,0 +1,11 @@ +defmodule Stripe.WebhookHandler do + @moduledoc """ + Webhook handler specification. + See `Stripe.WebhookPlug` for more details. + """ + @type error_reason :: binary() | atom() + + @doc "Handles a Stripe webhook event within your application." + @callback handle_event(event :: Stripe.Event.t()) :: + {:ok, term} | :ok | {:error, error_reason} | :error +end diff --git a/lib/stripe/webhook_plug.ex b/lib/stripe/webhook_plug.ex new file mode 100644 index 000000000..50fd04a4a --- /dev/null +++ b/lib/stripe/webhook_plug.ex @@ -0,0 +1,205 @@ +defmodule Stripe.WebhookPlug do + @moduledoc """ + Helper `Plug` to process webhook events and send them to a custom handler. + + ## Installation + + To handle webhook events, you must first configure your application's endpoint. + Add the following to `endpoint.ex`, **before** `Plug.Parsers` is loaded. + + ```elixir + plug Stripe.WebhookPlug, + at: "/webhook/stripe", + handler: MyAppWeb.StripeHandler, + secret: "whsec_******" + ``` + + If you have not yet added a webhook to your Stripe account, you can do so + by visiting `Developers > Webhooks` in the Stripe dashboard. Use the route + you configured in the endpoint above and copy the webhook secret into your + app's configuration. + + ### Supported options + + - `at`: The URL path your application should listen for Stripe webhooks on. + Configure this to match whatever you set in the webhook. + - `handler`: Custom event handler module that accepts `Stripe.Event` structs + and processes them within your application. You must create this module. + - `secret`: Webhook secret starting with `whsec_` obtained from the Stripe + dashboard. This can also be a function or a tuple for runtime configuration. + - `tolerance`: Maximum age (in seconds) allowed for the webhook event. + See `Stripe.Webhook.construct_event/4` for more information. + + ## Handling events + + You will need to create a custom event handler module to handle events. + + Your event handler module should implement the `Stripe.WebhookHandler` + behavior, defining a `handle_event/1` function which takes a `Stripe.Event` + struct and returns either `{:ok, term}` or `:ok`. This will mark the event as + successfully processed. Alternatively handler can signal an error by returning + `:error` or `{:error, reason}` tuple, where reason is an atom or a string. + HTTP status code 400 will be used for errors. + + ### Example + + ```elixir + # lib/myapp_web/stripe_handler.ex + + defmodule MyAppWeb.StripeHandler do + @behaviour Stripe.WebhookHandler + + @impl true + def handle_event(%Stripe.Event{type: "charge.succeeded"} = event) do + # TODO: handle the charge.succeeded event + end + + @impl true + def handle_event(%Stripe.Event{type: "invoice.payment_failed"} = event) do + # TODO: handle the invoice.payment_failed event + end + + # Return HTTP 200 for unhandled events + @impl true + def handle_event(_event), do: :ok + end + ``` + + ## Configuration + + You can configure the webhook secret in your app's own config file. + For example: + + ```elixir + config :myapp, + # [...] + stripe_webhook_secret: "whsec_******" + ``` + + You may then include the secret in your endpoint: + + ```elixir + plug Stripe.WebhookPlug, + at: "/webhook/stripe", + handler: MyAppWeb.StripeHandler, + secret: Application.get_env(:myapp, :stripe_webhook_secret) + ``` + + ### Runtime configuration + + If you're loading config dynamically at runtime (eg with `runtime.exs` + or an OTP app) you must pass a tuple or function as the secret. + + ```elixir + # With a tuple + plug Stripe.WebhookPlug, + at: "/webhook/stripe", + handler: MyAppWeb.StripeHandler, + secret: {Application, :get_env, [:myapp, :stripe_webhook_secret]} + + # Or, with a function + plug Stripe.WebhookPlug, + at: "/webhook/stripe", + handler: MyAppWeb.StripeHandler, + secret: fn -> Application.get_env(:myapp, :stripe_webhook_secret) end + ``` + """ + + import Plug.Conn + alias Plug.Conn + + @behaviour Plug + + @impl true + def init(opts) do + path_info = String.split(opts[:at], "/", trim: true) + + opts + |> Enum.into(%{}) + |> Map.put_new(:path_info, path_info) + end + + @impl true + def call( + %Conn{method: "POST", path_info: path_info} = conn, + %{ + path_info: path_info, + secret: secret, + handler: handler + } = opts + ) do + secret = parse_secret!(secret) + + with [signature] <- get_req_header(conn, "stripe-signature"), + {:ok, payload, _} = Conn.read_body(conn), + {:ok, %Stripe.Event{} = event} <- construct_event(payload, signature, secret, opts), + :ok <- handle_event!(handler, event) do + send_resp(conn, 200, "Webhook received.") |> halt() + else + {:handle_error, reason} -> send_resp(conn, 400, reason) |> halt() + _ -> send_resp(conn, 400, "Bad request.") |> halt() + end + end + + @impl true + def call(%Conn{path_info: path_info} = conn, %{path_info: path_info}) do + send_resp(conn, 400, "Bad request.") |> halt() + end + + @impl true + def call(conn, _), do: conn + + defp construct_event(payload, signature, secret, %{tolerance: tolerance}) do + Stripe.Webhook.construct_event(payload, signature, secret, tolerance) + end + + defp construct_event(payload, signature, secret, _opts) do + Stripe.Webhook.construct_event(payload, signature, secret) + end + + defp handle_event!(handler, %Stripe.Event{} = event) do + case handler.handle_event(event) do + {:ok, _} -> + :ok + + :ok -> + :ok + + {:error, reason} when is_binary(reason) -> + {:handle_error, reason} + + {:error, reason} when is_atom(reason) -> + {:handle_error, Atom.to_string(reason)} + + :error -> + {:handle_error, ""} + + resp -> + raise """ + #{inspect(handler)}.handle_event/1 returned an invalid response. Expected {:ok, term}, :ok, {:error, reason} or :error + Got: #{inspect(resp)} + + Event data: #{inspect(event)} + """ + end + end + + defp parse_secret!({m, f, a}), do: apply(m, f, a) + defp parse_secret!(fun) when is_function(fun), do: fun.() + defp parse_secret!(secret) when is_binary(secret), do: secret + + defp parse_secret!(secret) do + raise """ + The Stripe webhook secret is invalid. Expected a string, tuple, or function. + Got: #{inspect(secret)} + + If you're setting the secret at runtime, you need to pass a tuple or function. + For example: + + plug Stripe.WebhookPlug, + at: "/webhook/stripe", + handler: MyAppWeb.StripeHandler, + secret: {Application, :get_env, [:myapp, :stripe_webhook_secret]} + """ + end +end diff --git a/mix.exs b/mix.exs index 7c0be1833..7d71013a4 100644 --- a/mix.exs +++ b/mix.exs @@ -1,16 +1,16 @@ defmodule Stripe.Mixfile do use Mix.Project + @source_url "https://github.com/code-corps/stripity_stripe" + @version "2.12.1" + def project do [ app: :stripity_stripe, + version: @version, + elixir: "~> 1.10", deps: deps(), - description: description(), - dialyzer: [ - plt_add_apps: [:mix], - plt_file: {:no_warn, "priv/plts/stripity_stripe.plt"} - ], - elixir: "~> 1.7", + docs: docs(), package: package(), elixirc_paths: elixirc_paths(Mix.env()), preferred_cli_env: [ @@ -19,15 +19,11 @@ defmodule Stripe.Mixfile do "coveralls.post": :test, "coveralls.html": :test ], - test_coverage: [tool: ExCoveralls], - version: "2.9.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() - ] + dialyzer: [ + plt_add_apps: [:mix], + plt_file: {:no_warn, "priv/plts/stripity_stripe.plt"} + ], + test_coverage: [tool: ExCoveralls] ] end @@ -35,6 +31,7 @@ defmodule Stripe.Mixfile do def application do [ applications: apps(Mix.env()), + extra_applications: [:plug], env: env(), mod: {Stripe, []} ] @@ -44,7 +41,7 @@ defmodule Stripe.Mixfile do defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_), do: ["lib"] - defp env() do + defp env do [ api_base_url: "https://api.stripe.com/v1/", api_upload_url: "https://files.stripe.com/v1/", @@ -58,36 +55,53 @@ defmodule Stripe.Mixfile do defp apps(:test), do: apps() defp apps(_), do: apps() - defp apps(), do: [:hackney, :logger, :jason, :uri_query] + defp apps, do: [:hackney, :logger, :jason, :uri_query] defp deps do [ - {:dialyxir, "1.0.0-rc.4", only: [:dev, :test], runtime: false}, - {:ex_doc, "~> 0.20.2", only: :dev}, - {:excoveralls, "~> 0.11.1", only: :test}, + {:dialyxir, "1.1.0", only: [:dev, :test], runtime: false}, + {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, + {:excoveralls, "~> 0.14.1", only: :test}, {:hackney, "~> 1.15"}, {:inch_ex, "~> 2.0", only: [:dev, :test]}, {:mox, "~> 0.4", only: :test}, {:jason, "~> 1.1"}, {:uri_query, "~> 0.1.2"}, - {:exexec, "~> 0.1.0", only: :test} + {:exexec, "~> 0.1.0", only: :test}, + {:plug, "~> 1.0", optional: true} ] end - defp description do - """ - A Stripe client for Elixir. - """ + defp docs do + [ + extras: [ + "CHANGELOG.md": [title: "Changelog"], + "LICENSE.md": [title: "License"], + "README.md": [title: "Overview"] + ], + main: "readme", + source_url: @source_url, + source_ref: "master", + formatters: ["html"], + groups_for_modules: groups_for_modules(), + nest_modules_by_prefix: nest_modules_by_prefix() + ] end defp package do [ - files: ["lib", "LICENSE*", "mix.exs", "README*"], - licenses: ["New BSD"], + description: "A Stripe client for Elixir.", + files: ["lib", "LICENSE*", "mix.exs", "README*", "CHANGELOG*"], + licenses: ["BSD-3-Clause"], + maintainers: [ + "Dan Matthews", + "Josh Smith", + "Nikola Begedin", + "Scott Newcomer" + ], links: %{ - "GitHub" => "https://github.com/code-corps/stripity_stripe" - }, - maintainers: ["Dan Matthews", "Josh Smith", "Nikola Begedin", "Scott Newcomer"] + "GitHub" => @source_url + } ] end @@ -161,6 +175,10 @@ defmodule Stripe.Mixfile do Fraud: [ Stripe.Review ], + Identity: [ + Stripe.Identity.VerificationSession, + Stripe.Identity.VerificationReport + ], Issuing: [ Stripe.Issuing.Authorization, Stripe.Issuing.Card, diff --git a/mix.lock b/mix.lock index 4f7c88cd5..65bddc4ac 100644 --- a/mix.lock +++ b/mix.lock @@ -1,25 +1,31 @@ %{ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, - "dialyxir": {:hex, :dialyxir, "1.0.0-rc.4", "71b42f5ee1b7628f3e3a6565f4617dfb02d127a0499ab3e72750455e986df001", [:mix], [{:erlex, "~> 0.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "4bba10c6f267a0dd127d687d1295f6a11af6a7f160cc0e261c46f1962a98d7d8"}, + "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, + "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm", "e3be2bc3ae67781db529b80aa7e7c49904a988596e2dbff897425b48b3581161"}, - "erlex": {:hex, :erlex, "0.1.6", "c01c889363168d3fdd23f4211647d8a34c0f9a21ec726762312e08e083f3d47e", [:mix], [], "hexpm", "f9388f7d1a668bee6ebddc040422ed6340af74aced153e492330da4c39516d92"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.18", "e1b2be73eb08a49fb032a0208bf647380682374a725dfb5b9e510def8397f6f2", [:mix], [], "hexpm", "114a0e85ec3cf9e04b811009e73c206394ffecfcc313e0b346de0d557774ee97"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "erlexec": {:hex, :erlexec, "1.9.3", "3d72ac65424ced35b9658a50e5a0c9dbd5f383e28ac9096e557f0d62926dd8e4", [:rebar3], [], "hexpm", "5e3886952e987b63d9136a0bef5ceb0091b8a0550a81d221d0e55538ee2aa1ab"}, - "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", "8e24fc8ff9a50b9f557ff020d6c91a03cded7e59ac3e0eec8a27e771430c7d27"}, - "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", "493daf5a2dd92d022a1c29e7edcc30f1bce1ffe10fb3690fac63889346d3af2f"}, + "ex_doc": {:hex, :ex_doc, "0.26.0", "1922164bac0b18b02f84d6f69cab1b93bc3e870e2ad18d5dacb50a9e06b542a3", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2775d66e494a9a48355db7867478ffd997864c61c65a47d31c4949459281c78d"}, + "excoveralls": {:hex, :excoveralls, "0.14.1", "14140e4ef343f2af2de33d35268c77bc7983d7824cb945e6c2af54235bc2e61f", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4a588f9f8cf9dc140cc1f3d0ea4d849b2f76d5d8bee66b73c304bb3d3689c8b0"}, "exexec": {:hex, :exexec, "0.1.0", "4061bffc3845cb1fcc68eb68943335c75d64cfb4b4553c78f41be457df4a8d34", [:mix], [{:erlexec, "~> 1.7", [hex: :erlexec, repo: "hexpm", optional: false]}], "hexpm", "b2a3a4bf3aadbc3b4a277cd503c25f39b5bf2bab7c8929a1c7969094b03fe4ec"}, - "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [: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.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, - "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, + "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "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", "96d0ec5ecac8cf63142d02f16b7ab7152cf0f0f1a185a80161b758383c9399a8"}, - "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"}, - "makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5fbc8e549aa9afeea2847c0769e3970537ed302f93a23ac612602e805d9d1e7f"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "adf0218695e22caeda2820eaba703fa46c91820d53813a2223413da3ef4ba515"}, + "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mox": {:hex, :mox, "0.4.0", "7f120840f7d626184a3d65de36189ca6f37d432e5d63acd80045198e4c5f7e6e", [:mix], [], "hexpm", "64fca8aca4699cc1acf7db19237ce781cfa46683082a440e9f4e1ac29e290d89"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm", "5c040b8469c1ff1b10093d3186e2e10dbe483cd73d79ec017993fb3985b8a9b3"}, - "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"}, + "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, + "plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "uri_query": {:hex, :uri_query, "0.1.2", "ae35b83b472f3568c2c159eee3f3ccf585375d8a94fb5382db1ea3589e75c3b4", [:mix], [], "hexpm", "e3bc81816c98502c36498b9b2f239b89c71ce5eadfff7ceb2d6c0a2e6ae2ea0c"}, } diff --git a/test/stripe/checkout/session_test.exs b/test/stripe/checkout/session_test.exs index cf628c42c..f9775f27d 100644 --- a/test/stripe/checkout/session_test.exs +++ b/test/stripe/checkout/session_test.exs @@ -19,6 +19,13 @@ defmodule Stripe.SessionTest do end end + describe "expire/2" do + test "expires a session" do + assert {:ok, session = %Stripe.Session{}} = Stripe.Session.expire("cs_123") + assert_stripe_requested(:post, "/v1/checkout/sessions/cs_123/expire") + end + end + describe "list_line_items/2" do test "lists line items" do assert {:ok, %Stripe.List{}} = Stripe.Session.list_line_items("cs_123") diff --git a/test/stripe/core_resources/file_upload_test.exs b/test/stripe/core_resources/file_upload_test.exs index 4699b54cc..af5244d2f 100644 --- a/test/stripe/core_resources/file_upload_test.exs +++ b/test/stripe/core_resources/file_upload_test.exs @@ -16,8 +16,8 @@ defmodule Stripe.FileUploadTest do describe "retrieve/2" do test "retrieves an file" do - assert {:ok, _} = Stripe.FileUpload.retrieve("file_123") - assert_stripe_requested(:get, "/v1/files/file_123") + assert {:ok, _} = Stripe.FileUpload.retrieve("file_19yVPO2eZvKYlo2CIrGjfyCO") + assert_stripe_requested(:get, "/v1/files/file_19yVPO2eZvKYlo2CIrGjfyCO") end end diff --git a/test/stripe/identity/verification_report_test.exs b/test/stripe/identity/verification_report_test.exs new file mode 100644 index 000000000..1168dbcc1 --- /dev/null +++ b/test/stripe/identity/verification_report_test.exs @@ -0,0 +1,19 @@ +defmodule Stripe.Identity.VerificationReportTest do + use Stripe.StripeCase, async: true + + test "is retrievable" do + assert {:ok, %Stripe.Identity.VerificationReport{}} = + Stripe.Identity.VerificationReport.retrieve("vs_123xxx") + + assert_stripe_requested(:get, "/v1/identity/verification_reports/vs_123xxx") + end + + test "is listable" do + assert {:ok, %Stripe.List{data: verification_reports}} = + Stripe.Identity.VerificationReport.list() + + assert_stripe_requested(:get, "/v1/identity/verification_reports") + assert is_list(verification_reports) + assert %Stripe.Identity.VerificationReport{} = hd(verification_reports) + end +end diff --git a/test/stripe/identity/verification_session_test.exs b/test/stripe/identity/verification_session_test.exs new file mode 100644 index 000000000..3a6a762db --- /dev/null +++ b/test/stripe/identity/verification_session_test.exs @@ -0,0 +1,47 @@ +defmodule Stripe.Identity.VerificationSessionTest do + use Stripe.StripeCase, async: true + + test "is creatable" do + assert {:ok, %Stripe.Identity.VerificationSession{}} = + Stripe.Identity.VerificationSession.create(%{type: "document"}) + + assert_stripe_requested(:post, "/v1/identity/verification_sessions") + end + + test "is listable" do + assert {:ok, %Stripe.List{data: verification_sessions}} = + Stripe.Identity.VerificationSession.list() + + assert_stripe_requested(:get, "/v1/identity/verification_sessions") + assert is_list(verification_sessions) + assert %Stripe.Identity.VerificationSession{} = hd(verification_sessions) + end + + test "is retrievable" do + assert {:ok, %Stripe.Identity.VerificationSession{}} = + Stripe.Identity.VerificationSession.retrieve("vs_123xxx") + + assert_stripe_requested(:get, "/v1/identity/verification_sessions/vs_123xxx") + end + + test "is updatable" do + assert {:ok, %Stripe.Identity.VerificationSession{}} = + Stripe.Identity.VerificationSession.update("vs_123xxx", %{type: "document"}) + + assert_stripe_requested(:post, "/v1/identity/verification_sessions/vs_123xxx") + end + + test "is cancelable" do + assert {:ok, %Stripe.Identity.VerificationSession{}} = + Stripe.Identity.VerificationSession.cancel("vs_123xxx") + + assert_stripe_requested(:post, "/v1/identity/verification_sessions/vs_123xxx/cancel") + end + + test "is redactable" do + assert {:ok, %Stripe.Identity.VerificationSession{}} = + Stripe.Identity.VerificationSession.redact("vs_123xxx") + + assert_stripe_requested(:post, "/v1/identity/verification_sessions/vs_123xxx/redact") + end +end diff --git a/test/stripe/relay/product_test.exs b/test/stripe/relay/product_test.exs index bdd920424..2c0cfbf7b 100644 --- a/test/stripe/relay/product_test.exs +++ b/test/stripe/relay/product_test.exs @@ -4,13 +4,13 @@ defmodule Stripe.Relay.ProductTest do describe "create/2" do test "creates an product" do assert {:ok, %Stripe.Product{}} = - Stripe.Relay.Product.create(%{name: "Plus", type: "service"}) + Stripe.Relay.Product.create(%{name: "Plus"}) assert_stripe_requested(:post, "/v1/products") end test "creates an product with more params" do - params = %{name: "Plus", type: "service", description: "dowat?"} + params = %{name: "Plus", description: "dowat?"} assert {:ok, %Stripe.Product{}} = Stripe.Relay.Product.create(params) assert_stripe_requested(:post, "/v1/products") end diff --git a/test/stripe/request_test.exs b/test/stripe/request_test.exs index c51614e18..21ecc38ae 100644 --- a/test/stripe/request_test.exs +++ b/test/stripe/request_test.exs @@ -27,4 +27,14 @@ 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/product_test.exs b/test/stripe/subscriptions/product_test.exs index 3ac5fcce9..d8940a792 100644 --- a/test/stripe/subscriptions/product_test.exs +++ b/test/stripe/subscriptions/product_test.exs @@ -3,7 +3,7 @@ defmodule Stripe.ProductTest do describe "create/2" do test "creates an product" do - assert {:ok, %Stripe.Product{}} = Stripe.Product.create(%{name: "Plus", type: "service"}) + assert {:ok, %Stripe.Product{}} = Stripe.Product.create(%{name: "Plus"}) assert_stripe_requested(:post, "/v1/products") end end diff --git a/test/stripe/subscriptions/subscription_schedule_test.exs b/test/stripe/subscriptions/subscription_schedule_test.exs index 2783aae94..c9f4b9d41 100644 --- a/test/stripe/subscriptions/subscription_schedule_test.exs +++ b/test/stripe/subscriptions/subscription_schedule_test.exs @@ -33,9 +33,8 @@ defmodule Stripe.SubscriptionScheduleTest do coupon: nil, default_tax_rates: [], end_date: 1_557_566_037, - plans: [ + items: [ %{ - billing_thresholds: nil, plan: "some plan", quantity: 2, tax_rates: [] @@ -69,9 +68,8 @@ defmodule Stripe.SubscriptionScheduleTest do coupon: nil, default_tax_rates: [], end_date: 1_557_566_037, - plans: [ + items: [ %{ - billing_thresholds: nil, plan: "some plan", quantity: 2, tax_rates: [] diff --git a/test/stripe/subscriptions/subscription_test.exs b/test/stripe/subscriptions/subscription_test.exs index 12ca6ca8a..4fd97a5f9 100644 --- a/test/stripe/subscriptions/subscription_test.exs +++ b/test/stripe/subscriptions/subscription_test.exs @@ -101,7 +101,7 @@ defmodule Stripe.SubscriptionTest do assert_stripe_requested(:post, "/v1/subscriptions/sub_123") end - test "deletes a subscription with provided cancelation params" do + test "deletes a subscription with provided cancellation params" do params = %{invoice_now: true, prorate: true} assert {:ok, %Stripe.Subscription{} = subscription} = diff --git a/test/stripe/webhook_plug_test.exs b/test/stripe/webhook_plug_test.exs new file mode 100644 index 000000000..a1463e7df --- /dev/null +++ b/test/stripe/webhook_plug_test.exs @@ -0,0 +1,189 @@ +defmodule Stripe.WebhookPlugTest do + use ExUnit.Case + use Plug.Test + alias Stripe.WebhookPlug + + @valid_payload ~S({"object": "event"}) + @secret "secret" + + @opts WebhookPlug.init( + at: "/webhook/stripe", + handler: __MODULE__.Handler, + secret: @secret + ) + + defmodule Handler do + @behaviour Stripe.WebhookHandler + + @impl true + def handle_event(%Stripe.Event{object: "event"}), do: :ok + end + + defmodule ErrorTupleStringHandler do + @behaviour Stripe.WebhookHandler + + @impl true + def handle_event(%Stripe.Event{object: "event"}), do: {:error, "string error message"} + end + + defmodule ErrorTupleAtomHandler do + @behaviour Stripe.WebhookHandler + + @impl true + def handle_event(%Stripe.Event{object: "event"}), do: {:error, :atom_error_message} + end + + defmodule ErrorAtomHandler do + @behaviour Stripe.WebhookHandler + + @impl true + def handle_event(%Stripe.Event{object: "event"}), do: :error + end + + defmodule BadHandler do + def handle_event(_), do: nil + end + + def get_value(:secret), do: @secret + + defp generate_signature_header(payload) do + timestamp = System.system_time(:second) + + # TODO: remove when we require OTP 22 + code = + case System.otp_release() >= "22" do + true -> :crypto.mac(:hmac, :sha256, @secret, "#{timestamp}.#{payload}") + false -> :crypto.mac(:sha256, @secret, "#{timestamp}.#{payload}") + end + + signature = + code + |> Base.encode16(case: :lower) + + "t=#{timestamp},v1=#{signature}" + end + + describe "WebhookPlug" do + setup do + signature_header = generate_signature_header(@valid_payload) + + conn = + conn(:post, "/webhook/stripe", @valid_payload) + |> put_req_header("stripe-signature", signature_header) + + {:ok, %{conn: conn}} + end + + test "accepts valid signature", %{conn: conn} do + result = WebhookPlug.call(conn, @opts) + assert result.state == :sent + assert result.status == 200 + end + + test "rejects invalid signature", %{conn: conn} do + signature_header = generate_signature_header("random") + + conn = put_req_header(conn, "stripe-signature", signature_header) + + result = WebhookPlug.call(conn, @opts) + assert result.state == :sent + assert result.status == 400 + end + + test "nil secret should raise an error", %{conn: conn} do + opts = + WebhookPlug.init( + at: "/webhook/stripe", + handler: __MODULE__.Handler, + secret: nil + ) + + assert_raise RuntimeError, fn -> + WebhookPlug.call(conn, opts) + end + end + + test "function secret should be evaluated", %{conn: conn} do + opts = + WebhookPlug.init( + at: "/webhook/stripe", + handler: __MODULE__.Handler, + secret: fn -> @secret end + ) + + result = WebhookPlug.call(conn, opts) + assert result.state == :sent + assert result.status == 200 + end + + test "{m, f, a} secret should be evaluated", %{conn: conn} do + opts = + WebhookPlug.init( + at: "/webhook/stripe", + handler: __MODULE__.Handler, + secret: {__MODULE__, :get_value, [:secret]} + ) + + result = WebhookPlug.call(conn, opts) + assert result.state == :sent + assert result.status == 200 + end + + test "returns 400 status code with string message if handler returns error tuple", %{ + conn: conn + } do + opts = + WebhookPlug.init( + at: "/webhook/stripe", + handler: __MODULE__.ErrorTupleStringHandler, + secret: @secret + ) + + result = WebhookPlug.call(conn, opts) + assert result.state == :sent + assert result.status == 400 + assert result.resp_body == "string error message" + end + + test "returns 400 status code with atom message if handler returns error tuple", %{conn: conn} do + opts = + WebhookPlug.init( + at: "/webhook/stripe", + handler: __MODULE__.ErrorTupleAtomHandler, + secret: @secret + ) + + result = WebhookPlug.call(conn, opts) + assert result.state == :sent + assert result.status == 400 + assert result.resp_body == "atom_error_message" + end + + test "returns 400 status code with no message if handler returns :error atom", %{conn: conn} do + opts = + WebhookPlug.init( + at: "/webhook/stripe", + handler: __MODULE__.ErrorAtomHandler, + secret: @secret + ) + + result = WebhookPlug.call(conn, opts) + assert result.state == :sent + assert result.status == 400 + assert result.resp_body == "" + end + + test "crash hard if handler fails", %{conn: conn} do + opts = + WebhookPlug.init( + at: "/webhook/stripe", + handler: __MODULE__.BadHandler, + secret: @secret + ) + + assert_raise RuntimeError, fn -> + WebhookPlug.call(conn, opts) + end + end + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 0444d487b..f596d14bf 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -7,11 +7,11 @@ ExUnit.configure(exclude: [disabled: true], seed: 0) Logger.configure(level: :info) unless System.get_env("SKIP_STRIPE_MOCK_RUN") do - {:ok, pid} = Stripe.StripeMock.start_link(port: 12123, global: true) + {:ok, _pid} = Stripe.StripeMock.start_link(port: 12111, global: true) end -api_base_url = System.get_env("STRIPE_API_BASE_URL") || "http://localhost:12123/v1/" -api_upload_url = System.get_env("STRIPE_API_UPLOAD_URL") || "http://localhost:12123/v1/" +api_base_url = System.get_env("STRIPE_API_BASE_URL") || "http://localhost:12111/v1/" +api_upload_url = System.get_env("STRIPE_API_UPLOAD_URL") || "http://localhost:12112/v1/" Application.put_env(:stripity_stripe, :api_base_url, api_base_url) Application.put_env(:stripity_stripe, :api_upload_url, api_upload_url) @@ -41,4 +41,6 @@ defmodule Helper do end end -Helper.wait_until_stripe_mock_launch() +unless System.get_env("SKIP_STRIPE_MOCK_RUN") do + Helper.wait_until_stripe_mock_launch() +end