├── .codeclimate.yml ├── .editorconfig ├── .github ├── issue_template.md ├── pull_request_template.md └── workflows │ └── ruby.yml ├── .gitignore ├── .rubocop.yml ├── CODE_OF_CONDUCT.md ├── Changelog.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── app ├── assets │ └── stripe │ │ ├── stripe_elements.css │ │ └── stripe_elements.js ├── controllers │ └── stripe │ │ ├── application_controller.rb │ │ └── events_controller.rb ├── helpers │ └── stripe │ │ └── javascript_helper.rb ├── models │ └── stripe │ │ └── event_dispatch.rb └── views │ └── stripe │ ├── _elements.html.erb │ ├── _elements_js.html.erb │ └── _js.html.erb ├── config ├── locales │ └── en.yml └── routes.rb ├── gemfiles ├── gemfiles │ └── rails70.gemfile ├── rails60.gemfile ├── rails61.gemfile ├── rails70.gemfile └── rails71.gemfile ├── lib ├── generators │ ├── stripe │ │ └── install_generator.rb │ └── templates │ │ ├── coupons.rb │ │ ├── plans.rb │ │ ├── prices.rb │ │ └── products.rb ├── stripe-rails.rb └── stripe │ ├── billing_tier.rb │ ├── callbacks.rb │ ├── callbacks │ └── builder.rb │ ├── configuration_builder.rb │ ├── coupons.rb │ ├── current_api_version.rb │ ├── engine.rb │ ├── plans.rb │ ├── prices.rb │ ├── products.rb │ ├── rails.rb │ └── rails │ ├── tasks.rake │ ├── testing.rb │ └── version.rb ├── stripe-rails.gemspec └── test ├── callbacks_spec.rb ├── coupon_builder_spec.rb ├── dummy ├── README.rdoc ├── Rakefile ├── app │ ├── assets │ │ ├── javascripts │ │ │ └── application.js │ │ └── stylesheets │ │ │ └── application.css │ ├── controllers │ │ ├── apis_controller.rb │ │ ├── application_controller.rb │ │ └── stripes_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── mailers │ │ └── .gitkeep │ ├── models │ │ ├── .gitkeep │ │ └── dummy │ │ │ └── model_with_callbacks.rb │ └── views │ │ ├── layouts │ │ └── application.html.erb │ │ └── stripes │ │ └── new.html.erb ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── secret_token.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── routes.rb │ └── stripe │ │ ├── plans.rb │ │ └── prices.rb ├── lib │ ├── assets │ │ └── .gitkeep │ └── dummy │ │ └── module_with_callbacks.rb ├── log │ └── .gitkeep ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ └── favicon.ico └── script │ └── rails ├── dummy_apis_controller_spec.rb ├── dummy_stripes_controller_spec.rb ├── event.json ├── events_controller_spec.rb ├── fixtures ├── stripe_plans.json ├── stripe_plans_headers.json ├── stripe_plans_headers_2017.json └── stripe_prices.json ├── invoice.json ├── javascript_helper_spec.rb ├── plan_builder_spec.rb ├── price_builder_spec.rb ├── product_builder_spec.rb ├── spec_helper.rb ├── stripe_initializers_spec.rb ├── support ├── application_system_test_case.rb ├── callback_helpers.rb └── fixture_loader.rb └── testing_spec.rb /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | brakeman: 4 | enabled: true 5 | duplication: 6 | enabled: true 7 | config: 8 | languages: 9 | - ruby 10 | fixme: 11 | enabled: true 12 | rubocop: 13 | enabled: true 14 | ratings: 15 | paths: 16 | - Gemfile.lock 17 | - "**.erb" 18 | - "**.haml" 19 | - "**.rb" 20 | - "**.rhtml" 21 | - "**.slim" 22 | - "**.css" 23 | - "**.inc" 24 | - "**.js" 25 | - "**.jsx" 26 | - "**.module" 27 | exclude_paths: 28 | - config/ 29 | - test/ 30 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | 8 | [*.rb] 9 | indent_style = space 10 | indent_size = 2 11 | charset = utf-8 12 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | continue-on-error: true 16 | strategy: 17 | matrix: 18 | ruby: [3.0.7, 3.1.6, 3.2.5, 3.3.4] 19 | gemfile: [gemfiles/rails71.gemfile, gemfiles/rails70.gemfile, gemfiles/rails61.gemfile, gemfiles/rails60.gemfile] # Gemfile fails on test/callbacks_spec.rb:52 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set up Ruby 23 | uses: ruby/setup-ruby@v1 24 | with: 25 | ruby-version: ${{ matrix.ruby }} 26 | - name: Set up Code Climate 27 | run: | 28 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 29 | chmod +x ./cc-test-reporter 30 | ./cc-test-reporter before-build 31 | - name: Build and Test 32 | env: 33 | BUNDLE_GEMFILE: ${{ matrix.gemfile }} 34 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 35 | RUBY_VERSION: ${{ matrix.ruby }} 36 | run: | 37 | bundle install --jobs 4 --retry 3 38 | bundle exec rake 39 | if [ `basename $BUNDLE_GEMFILE` == "Gemfile" ] && [ $RUBY_VERSION == "3.3.4" ] && [ ! -z ${CC_TEST_REPORTER_ID} ] ; 40 | then ./cc-test-reporter after-build --exit-code $? ; 41 | fi 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | gemfiles/*.lock 3 | *.rbc 4 | .bundle 5 | .config 6 | .yardoc 7 | Gemfile.lock 8 | InstalledFiles 9 | _yardoc 10 | coverage 11 | doc/ 12 | lib/bundler/man 13 | pkg 14 | rdoc 15 | spec/reports 16 | test/tmp 17 | test/version_tmp 18 | test/dummy/**/*.log 19 | tmp 20 | .rvmrc 21 | .DS_Store 22 | .ruby-version 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This Code of Conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at code-of-conduct@frontside.io. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 46 | version 1.3.0, available at 47 | [http://contributor-covenant.org/version/1/3/0/][version] 48 | 49 | [homepage]: http://contributor-covenant.org 50 | [version]: http://contributor-covenant.org/version/1/3/0/ 51 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ## Next Release 2 | 3 | ## 2.6.0 (2024-08-12) 4 | - Add the following callbacks (Thanks @4nd2in !): 5 | * `checkout.session.async_payment_failed` 6 | * `checkout.session.async_payment_succeeded` 7 | * `customer.subscription.paused` 8 | * `customer.subscription.pending_update_applied` 9 | * `customer.subscription.pending_update_expired` 10 | * `customer.subscription.resumed` 11 | * `invoice.deleted` 12 | * `invoice.finalization_failed` 13 | * `invoice.overdue` 14 | * `invoice.will_be_due` 15 | - Fix request handling in rack versions >= 3.1 where `Request#[]` is deprecated (#230). Thanks @4nd2in and 16 | @light-flight ! 17 | - adds testing for Ruby 3 and Rails 7 18 | - stops testing on Ruby < 3 and Rails 5 19 | 20 | ## 2.5.0 (2023-03-21) 21 | 22 | - Allow `stripe_elements_tag` to accept a block. Thanks @chip ! 23 | 24 | ## 2.4.0 (2023-02-04) 25 | 26 | - Add `tax_behavior` attribute to Price. Thanks @szechyjs ! 27 | 28 | ## 2.3.5 (2022-10-01) 29 | 30 | - Fix some deprecation warnings in tests. Thanks @smtlaissezfaire ! 31 | - Fix `NameError when loading JavascriptHelper`. Thanks @smtlaissezfaire ! 32 | 33 | ## 2.3.4 (2022-05-03) 34 | 35 | - Add setup_intent.canceled and setup_intent.requires_action callbacks. Thanks @jamesjason ! 36 | 37 | ## 2.3.3 (2021-12-17) 38 | 39 | - Add checkout.session.expired callback. Thanks @danielwellman ! 40 | 41 | ## 2.3.2 (2021-10-15) 42 | 43 | - Add Subscription Schedule and Tax Rate Callbacks #213 . Thanks @lesliepoolman ! 44 | 45 | ## 2.3.1 (2021-09-17) 46 | 47 | - Add price callbacks. Thanks @StevenElberger ! 48 | 49 | ## 2.3.0 (2021-03-08) 50 | 51 | - Adds testing for Rails 6.0 52 | - Add `name` attribute to coupons. Thanks @ZilvinasKucinskas! 53 | 54 | ## 2.2.1 (2020-12-22) 55 | 56 | - Add payment_intent.requires_action callback, thanks @VadimLeader. 57 | 58 | ## 2.2.0 (2020-12-06) 59 | 60 | - Add Prices as a configuration object. Thanks @jamesprior! 61 | 62 | ## 2.1.0 (2020-10-18) 63 | 64 | - Added option to ignore missing API key and don't show any warning. Thanks @ndbroadbent! 65 | - Handle passing nil to signing_secret= and add tests. Thanks @martron! 66 | 67 | ## 2.0.0 (2020-09-18) 68 | 69 | - Everything from on the 2.0.0.pre release 70 | - includes changes from the 1.10.2 release 71 | 72 | ## 1.10.2 (2020-09-18) 73 | 74 | - adds missing callback `invoice.paid`. Thanks @SyborgStudios. 75 | 76 | ## 2.0.0.pre (2020-05-29) 77 | 78 | * [Breaking] Updated to work only with Rails >= 5.1 79 | * [Breaking] It'll only be tested on Ruby 2.7, 2.6 and 2.5. 80 | * [Breaking] Supports the Stripe gem => 3.15.0 (from 2 years ago) 81 | * [Breaking] Removes Stripe::PingsController controller. 82 | 83 | ## 1.10.1 (2020-05-29) 84 | 85 | - adds missing callbacks for `payment_intent`. Thanks @klapperkopp . 86 | 87 | ## 1.10.0 (2020-03-31) 88 | 89 | - Adds support for using multiple tiers in a plan, thanks @cpsoinos 90 | 91 | ## 1.9.1 (2019-10-28) 92 | 93 | - Fixes issue with `rake stripe:verify` thanks @Millariel ! 94 | 95 | ## 1.9.0 (2019-09-01) 96 | 97 | - Adds support for multiple signing secrets. Thanks again @jacobcsmith ! 98 | 99 | ## 1.8.2 (2019-08-31) 100 | 101 | - adds missing callbacks for `payment_intent`, `payment_method` and `setup_intent`. Thanks @jacobcsmith ! 102 | 103 | ## 1.8.1 (2019-07-26) 104 | 105 | * adds callback for invoice.payment_action_required. Thanks @alexagranov . 106 | * fixes when clearing callbacks after unload doesn't play nice with eager_load. Thanks @alexagranov for reporting the problem and coming up with an initial fix for it. 107 | 108 | ## 1.8.0 (2019-07-25) 109 | 110 | * Configure publishable key from ENV. Thanks @cyu . 111 | 112 | ## 1.7.2 (2019-06-29) 113 | 114 | * fixes `require` error after update from Stripe gem. Thanks @dark-panda ! 115 | 116 | ## 1.7.1 (2019-05-24) 117 | 118 | * Don't assume sprockets are loaded thanks @manusajith 119 | 120 | ## 1.7.0 (2019-05-09) 121 | 122 | * [New Feature] add support for Plan to use a constant name different from plan id thanks @alexagranov ! 123 | * Add checkout.session.completed webhook thanks @Nitrino ! 124 | 125 | ## 1.6.1 (2019-03-04) 126 | 127 | * Add new invoice webhooks thanks @noahezekwugo ! 128 | 129 | ## 1.6.0 (2019-01-08) 130 | 131 | * New Year New Feature: Easily include Stripe Elements into your project thanks to @garrettqmartin8 ! 132 | * Travis is now testing the gem on Ruby 2.6.0 133 | 134 | ## 1.5.5 (2018-12-16) 135 | 136 | * Fixed issue with Rails development and Spring: Clear callbacks before files are reloaded during development and test - thanks @ndbroadbent 137 | 138 | ## 1.5.4 (2018-11-14) 139 | 140 | * Removes test exception from event dispatch 141 | * Travis is now testing the gem on Ruby 2.5.3, 2.4.5, 2.3.8 142 | 143 | ## 1.5.3 (2018-10-25) 144 | 145 | * Add usage_type, aggregate_usage, and billing_scheme - thanks @garrettqmartin8 146 | 147 | ## 1.5.2 (2018-10-15) 148 | 149 | * fixes undefined method `expand_path' for Stripe::File:Class - Thanks to @BitesGit for reporting this. 150 | 151 | ## 1.5.1 (2018-10-01) 152 | 153 | * Allow statement_descriptor to be set on products - Thanks to @jeanmartin 154 | 155 | 156 | ## 1.5.0 (2018-09-27) 157 | 158 | * Add Webhook Signature Validation - Thanks to @wkirby 159 | * Include nickname in the payload for plans - Thanks to @jeanmartin 160 | 161 | ## 1.4.2 (2018-08-06) 162 | 163 | * New attributes for Stripe Billing Plans. 164 | 165 | ## 1.4.1 (2018-08-03) 166 | 167 | * Fixes ActionController::UnknownFormat errors - Thanks to @ndbroadbent ! 168 | 169 | ## 1.4.0 (2018-07-30) 170 | 171 | * Spanking new products builder for Stripe Billing (#117) - Thanks to @renchap for suggesting this and to @henryaj for reviewing it. 172 | 173 | ## 1.3.0 (2018-07-23) 174 | 175 | * do not create new product when product id is provided (#115) - Thanks to @renchap for reporting this 176 | * updates travis to latest rubies (#112) - Note that after this change we will only run tests on Ruby 2.5, 2.4 and 2.3 177 | 178 | 179 | ## 1.2.2 (2018-04-16) 180 | 181 | * adds callback form `customer.source.expiring`. Thanks @Japestrale! 182 | 183 | ## 1.2.1 (2018-03-22) 184 | 185 | * Fixes Stripe API update on 2018-02-05 that breaks the plan builder (thanks to @georgecheng for reporting this!) 186 | 187 | ## 1.2.0 (2018-01-02) 188 | 189 | * Added additional callbacks (thanks @lloydpick & @dja) 190 | 191 | ## 1.1.2 (2017-10-25) 192 | 193 | * Fixes js partial crash if stripe_js_version is not defined 194 | 195 | ## 1.1.1 (2017-08-31) 196 | 197 | * Make stripe-ruby-mock an optional dependency (thanks @gaffneyc) 198 | 199 | ## 1.1.0 (2017-08-29) 200 | 201 | * Adds a testing module for testing callbacks (thanks @Pyo25) 202 | * Fixes loading with ActionController::API (thanks @gaffneyc) 203 | * Fixes `NoMethodError: NoMethodError (undefined method `object' for #ActionController::Parameters` (thanks to a whole bunch of people for reporting this) 204 | 205 | ## 1.0.2 (2017-08-15) 206 | 207 | * Remove authenticity token check (thanks @lewispb) 208 | * Adding timeout options to config (thanks @rgerard) 209 | * Add 'day' as possible plan interval (thanks @vdragsic and @artemave) 210 | 211 | ## 1.0.1 (2017-08-08) 212 | 213 | * Fixes a bug with Stripe JS V3, i.e. `Stripe.setPublishableKey` is no longer a function. Thanks to @kartikluke for reporting this. 214 | 215 | ## 1.0.0 (2017-07-24 - Breaky McBreakface) 216 | 217 | * [BREAKING] Update to latest stripe events (thanks @hopsoft). Note that if you are using the `after_customer_card_created`, `after_customer_card_updated` or `after_customer_card_deleted` callbacks, you MUST update them to `after_customer_source_created`, `after_customer_source_updated` or `after_customer_source_deleted` respectively. You also need to start using [Stripe API Version > 2015-02-18](https://stripe.com/docs/upgrades#2015-02-18) or else the webhook might not work as expected. 218 | * [BREAKING] Updates to the [latest version of Stripe JS](https://github.com/Everapps/stripe-rails/pull/69). If you were using `stripe_javascript_tag` without specifying the version number, note that it will now default to Stripe JS v3. This version is incompatible with the previous default. 219 | * The gem will only be tested on Rails 4 and 5 [from now on](https://github.com/Everapps/stripe-rails/pull/62). 220 | * Gem will henceforth only [be tested](https://github.com/Everapps/stripe-rails/pull/68) on Ruby >= 2.1.9. 221 | * add statement descriptor to plan attributes (thanks @jbender) 222 | * Relax version constraint on the responders gem 223 | 224 | ## 0.4.1 (2017-06-01) 225 | 226 | * Support for api_version header parameter (thanks @kiddrew) 227 | * Relax version constraint on stripe gem (thanks @gaffneyc) 228 | 229 | ## 0.4.0 (2017-05-24) 230 | * Support alternate versions of stripe js 231 | 232 | ## 0.3.2 (2017-03-06) 233 | * add `responders` gem as dependency to support `respond_to` method 234 | * fix unit tests with Rails 4.2 and Rails 5.0 235 | 236 | ## 0.3.1 (2014-08-07) 237 | * add `eager_load` option to load callbacks into classes in non-eager-loaded enviroments 238 | 239 | ## 0.3.0 (2014-04-29) 240 | * Rename api_key to secret_key 241 | 242 | ## 0.2.6 (2013-10-17) 243 | * add `auto_mount` option to allow for manually mounting the webhook endpoints 244 | 245 | ## 0.2.5 (2013-03-18) 246 | * make the default max redemptions 1 247 | * add stripe::coupons::reset! task to redefine all coupons 248 | 249 | ## 0.2.2 (2013-01-09) 250 | * bugfix allowing creation of coupons without max_redemptions 251 | 252 | ## 0.2.1 (2012-12-17) 253 | * manage coupons with config/stripe/coupons.rb 254 | 255 | ## 0.2.0 (2012-12-13) 256 | 257 | * out of the box support for webhooks and critical/non-critical event handlers 258 | * add :only guards for which webhooks you respond to- 259 | * move stripe.js out of asset pipeline, and insert it with utility functions 260 | 261 | ## 0.1.0 (2012-11-14) 262 | 263 | * add config/stripe/plans.rb to define and create plans 264 | * use `STRIPE_API_KEY` as default value of `config.stripe.api_key` 265 | * require stripe.js from asset pipeline 266 | * autoconfigure stripe.js with config.stripe.publishable_key. 267 | * add rake stripe:verify to ensure stripe.com authentication is configured properly 268 | 269 | ## 0.0.1 (2012-10-12) 270 | 271 | * basic railtie 272 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in stripe-rails.gemspec 4 | gemspec 5 | 6 | gem 'rake' 7 | 8 | group :development, :test do 9 | gem 'm' 10 | end 11 | 12 | group :test do 13 | gem 'mocha' 14 | gem 'simplecov', '< 0.18', require: false 15 | gem 'stripe-ruby-mock' 16 | gem 'webmock' 17 | # System tests 18 | gem 'capybara' 19 | gem 'puma' 20 | gem 'selenium-webdriver', '>= 4' 21 | end 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2017 The Frontside Software, Inc. 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stripe::Rails: A Rails Engine for use with [stripe.com](https://stripe.com) 2 | [![Gem Version](https://badge.fury.io/rb/stripe-rails.svg)](https://badge.fury.io/rb/stripe-rails) 3 | [![Build Status](https://travis-ci.org/tansengming/stripe-rails.svg?branch=master)](https://travis-ci.org/tansengming/stripe-rails) 4 | [![Code Climate](https://codeclimate.com/github/tansengming/stripe-rails/badges/gpa.svg)](https://codeclimate.com/github/tansengming/stripe-rails) 5 | [![Test Coverage](https://codeclimate.com/github/tansengming/stripe-rails/badges/coverage.svg)](https://codeclimate.com/github/tansengming/stripe-rails/coverage) 6 | [![Tidelift](https://tidelift.com/badges/github/tansengming/stripe-rails)](#) 7 | 8 | This gem can help your rails application integrate with Stripe in the following ways 9 | 10 | * manage stripe configurations in a single place. 11 | * makes stripe.js available from the asset pipeline. 12 | * manage product, prices, plans and coupons from within your app. 13 | * painlessly receive and validate webhooks from stripe. 14 | 15 | [Professionally supported stripe-rails is coming soon](https://tidelift.com/subscription/pkg/rubygems-stripe-rails?utm_source=rubygems-stripe-rails&utm_medium=referral&utm_campaign=readme) 16 | 17 | --- 18 | 19 | [Installation](#installation) 20 | - [Setup your API keys](#setup-your-api-keys) 21 | - [Manually set your API version (optional)](#manually-set-your-api-version-optional) 22 | 23 | [Setup your payment configuration](#setup-your-payment-configuration) 24 | - [Configuring your plans and coupons](#configuring-your-plans-and-coupons) 25 | 26 | [Stripe Elements](#stripe-elements) 27 | - [Custom Elements](#custom-elements) 28 | 29 | [Webhooks](#webhooks) 30 | 31 | - [Signed Webhooks](#signed-webhooks) 32 | - [Testing Signed Webhooks Locally](#testing-signed-webhooks-locally) 33 | - [Disabling auto mount](#disabling-auto-mount) 34 | - [Responding to webhooks](#responding-to-webhooks) 35 | - [Critical and non-critical hooks](#critical-and-non-critical-hooks) 36 | - [Filtering Callbacks](#filtering-callbacks) 37 | - [Catchall Callback](#catchall-callback) 38 | 39 | [Unit testing](#unit-testing) 40 | 41 | [Thanks](#thanks) 42 | 43 | [Code of Conduct](#code-of-conduct) 44 | 45 | ## Installation 46 | 47 | Add this line to your application's Gemfile: 48 | 49 | ```ruby 50 | gem 'stripe-rails' 51 | ``` 52 | 53 | If you are going to be using [stripe.js][1] to securely collect credit card information 54 | on the client, then you will need to add the stripe javascript tags into your template. 55 | stripe-rails provides a helper to make this easy: 56 | 57 | ```erb 58 | <%= stripe_javascript_tag %> 59 | ``` 60 | 61 | or, you can render it as a partial: 62 | 63 | ```erb 64 | <%= render :partial => 'stripe/js' %> 65 | ``` 66 | 67 | In both cases, stripe-rails will choose a version of stripe.js appropriate for your 68 | development environment and automatically configure it to use 69 | your publishable API key. By default it uses `stripe-debug.js` for your `development` 70 | environment and `stripe.js` for everything else, but you can manually configure it 71 | per environment. 72 | 73 | ```ruby 74 | config.stripe.debug_js = true # use stripe-debug.js 75 | config.stripe.debug_js = false # use stripe.js 76 | ``` 77 | 78 | By default the helper renders the `v3` version of `stripe.js`. You can provide an 79 | alternate version to the helper to generate the appropriate tag: 80 | 81 | ```erb 82 | <%= stripe_javascript_tag(:v2) %> 83 | ``` 84 | 85 | ### Setup your API keys. 86 | 87 | You will need to configure your application to authenticate with stripe.com 88 | using [your api key][1]. There are two methods to do this, you can either set the environment 89 | variable `STRIPE_SECRET_KEY`: 90 | 91 | ```sh 92 | export STRIPE_SECRET_KEY=sk_test_xxyyzz 93 | ``` 94 | 95 | or if you are on heroku: 96 | 97 | ```sh 98 | heroku config:add STRIPE_SECRET_KEY=sk_test_xxyyzz 99 | ``` 100 | 101 | You can also set this value from inside ruby configuration code: 102 | 103 | ```ruby 104 | config.stripe.secret_key = "sk_test_xxyyzz" 105 | ``` 106 | 107 | In either case, it is recommended that you *not* check in this value into source control. 108 | 109 | You can verify that your api is set up and functioning properly by running the following command: 110 | 111 | ```sh 112 | rake stripe:verify 113 | ``` 114 | 115 | If you are going to be using stripe.js, then you will also need to set the value of your 116 | publishable key. A nice way to do it is to set your test publishable for all environments: 117 | 118 | ```ruby 119 | # config/application.rb 120 | # ... 121 | config.stripe.publishable_key = 'pk_test_XXXYYYZZZ' 122 | ``` 123 | 124 | And then override it to use your live key in production only 125 | 126 | ```ruby 127 | # config/environments/production.rb 128 | # ... 129 | config.stripe.publishable_key = 'pk_live_XXXYYYZZZ' 130 | ``` 131 | 132 | This key will be publicly visible on the internet, so it is ok to put in your source. If 133 | you prefer to environment variables, you can also set `STRIPE_PUBLISHABLE_KEY`: 134 | 135 | ```sh 136 | export STRIPE_PUBLISHABLE_KEY=pk_test_XXXYYYZZZ 137 | ``` 138 | 139 | If no API key is provided, `stripe-rails` will show a warning: "No stripe.com API key was configured ...". You can silence this warning by setting the `ignore_missing_secret_key` option to `true`: 140 | 141 | ```ruby 142 | # config/environments/production.rb 143 | # ... 144 | config.stripe.ignore_missing_secret_key = true 145 | ``` 146 | 147 | ### Manually set your API version (optional) 148 | 149 | If you need to test a new API version in development, you can override the version number manually. 150 | 151 | ```ruby 152 | # config/environments/development.rb 153 | # ... 154 | config.stripe.api_version = '2015-10-16' 155 | ``` 156 | 157 | ## Setup your payment configuration 158 | 159 | If you're using subscriptions, then you'll need to set up your application's payment plans 160 | and discounts. `Stripe::Rails` lets you automate the management of these definitions from 161 | within the application itself. To get started: 162 | 163 | ```sh 164 | rails generate stripe:install 165 | ``` 166 | 167 | this will generate the configuration files containing your plan and coupon definitions: 168 | 169 | ```console 170 | create config/stripe/products.rb 171 | create config/stripe/plans.rb 172 | create config/stripe/prices.rb 173 | create config/stripe/coupons.rb 174 | ``` 175 | 176 | ### Configuring your plans and coupons 177 | 178 | Use the plan builder to define as many plans as you want in `config/stripe/plans.rb` 179 | 180 | ```ruby 181 | Stripe.plan :silver do |plan| 182 | plan.name = 'ACME Silver' 183 | plan.amount = 699 # $6.99 184 | plan.interval = 'month' 185 | end 186 | 187 | Stripe.plan :gold do |plan| 188 | plan.name = 'ACME Gold' 189 | plan.amount = 999 # $9.99 190 | plan.interval = 'month' 191 | end 192 | 193 | Stripe.plan :bronze do |plan| 194 | # Use an existing product id to prevent a new plan from 195 | # getting created 196 | plan.product_id = 'prod_XXXXXXXXXXXXXX' 197 | plan.amount = 999 # $9.99 198 | plan.interval = 'month' 199 | 200 | # Use graduated pricing tiers 201 | # ref: https://stripe.com/docs/api/plans/object#plan_object-tiers 202 | plan.tiers = [ 203 | { 204 | unit_amount: 1500, 205 | up_to: 10 206 | }, 207 | { 208 | unit_amount: 1000, 209 | up_to: 'inf' 210 | } 211 | ] 212 | plan.tiers_mode = 'graduated' 213 | 214 | # set the usage type to 'metered' 215 | plan.usage_type = 'metered' 216 | end 217 | ``` 218 | 219 | This will define constants for these plans in the Stripe::Plans module so that you 220 | can refer to them by reference as opposed to an id string. 221 | 222 | ```ruby 223 | Stripe::Plans::SILVER # => 'silver: ACME Silver' 224 | Stripe::Plans::GOLD # => 'gold: ACME Gold' 225 | ``` 226 | 227 | If you have to support an existing plan with a Stripe plan id that can not 228 | be used as a Ruby constant, provide the plan id as a symbol when 229 | defining the plan, but provide the name for the constant to define with `constant_name`: 230 | 231 | ```ruby 232 | Stripe.plan "Silver-Plan".to_sym do |plan| 233 | plan.constant_name = 'SILVER_PLAN' # <--- 234 | plan.name = 'ACME Silver' 235 | plan.amount = 699 236 | plan.interval = 'month' 237 | end 238 | 239 | Stripe::Plans::SILVER_PLAN # => will be defined 240 | # Will map to plan :id => "Silver-Plan" on Stripe 241 | ``` 242 | 243 | **Note** - If you're planning on running `rake stripe:prepare` to 244 | create your subscription plans, Stripe will restrict plan ids to match 245 | this regexp (`/\A[a-zA-Z0-9_\-]+\z/`) when created via API but still 246 | allows creation of plan ids that don't follow this restriction when 247 | manually created on stripe.com. 248 | 249 | Coupons are created in much the same way: 250 | 251 | ```ruby 252 | Stripe.coupon :super_elite_free_vip do |coupon| 253 | coupon.duration = 'forever' 254 | coupon.percent_off = 100 255 | coupon.max_redemptions = 5 256 | end 257 | ``` 258 | 259 | 260 | As are Products: 261 | 262 | ```ruby 263 | Stripe.product :primo do |product| 264 | product.name = 'PRIMO as a service' 265 | product.type = 'service' 266 | product.statement_descriptor = 'PRIMO' 267 | end 268 | ``` 269 | 270 | And Prices: 271 | 272 | ```ruby 273 | Stripe.price :bronze do |price| 274 | # Use an existing product id to prevent a new product from 275 | # getting created 276 | price.product_id = Stripe::Products::PRIMO.id 277 | price.billing_scheme = 'tiered' 278 | price.recurring = { 279 | interval: 'month', 280 | usage_type: 'metered' 281 | } 282 | 283 | # Use graduated pricing tiers 284 | # ref: https://stripe.com/docs/api/prices/object#price_object-tiers 285 | price.tiers = [ 286 | { 287 | unit_amount: 1500, 288 | up_to: 10 289 | }, 290 | { 291 | unit_amount: 1000, 292 | up_to: 'inf' 293 | } 294 | ] 295 | price.tiers_mode = 'graduated' 296 | end 297 | ```` 298 | 299 | To upload your plans, products, prices and coupons onto stripe.com, run: 300 | 301 | ```sh 302 | rake stripe:prepare 303 | ``` 304 | 305 | This will create any plans, products, prices and coupons that do not currently exist, and treat as a NOOP any 306 | objects that already exist, so you can run this command safely as many times as you wish. Now you can 307 | use any of these objects in your application. 308 | 309 | NOTE: You must destroy plans and prices manually from your stripe dashboard. 310 | 311 | ## Stripe Elements 312 | 313 | Stripe::Rails allows you to easily include [Stripe Elements](https://stripe.com/payments/elements) in your application. 314 | 315 | > Stripe Elements are rich, pre-built UI components that help you create your own pixel-perfect checkout flows across desktop and mobile. 316 | 317 | Simply include the `stripe_elements_tag` anywhere below the `stripe_javascript_tag` and pass it the path to the controller action which will handle the Stripe token once the form is submitted: 318 | 319 | ```erb 320 | <%= stripe_javascript_tag %> 321 | <%= stripe_elements_tag submit_path: billing_path %> 322 | ``` 323 | 324 | Additionally, you can pass a block containing custom form elements to stripe_elements_tag: 325 | 326 | ## Custom Elements 327 | 328 | > Stripe::Rails allows you to easily include your own custom form elements 329 | > within the Stripe form by including those form elements in a block passed to 330 | > `stripe_elements_tag`: 331 | 332 | ```erb 333 | <%= stripe_javascript_tag %> 334 | <%= stripe_elements_tag(submit_path: billing_path) do %> 335 | <%= label_tag 'email', 'Email' %> 336 | <%= text_field :user, :email %> 337 | <% end %> 338 | ``` 339 | 340 | ### Configuration options 341 | 342 | Stripe::Rails comes bundled with default CSS and Javascript for Stripe elements, making it easy to drop in to your app. You can also specify your own assets paths: 343 | 344 | ```erb 345 | <%= stripe_elements_tag submit_path: billing_path, 346 | css_path: 'your/asset/path', 347 | js_path: 'your/asset/path' %> 348 | ``` 349 | 350 | If you decide to use your own CSS and Javascript for Stripe Elements, please refer to the [Stripe elements docs](https://stripe.com/docs/stripe-js/elements/quickstart). 351 | 352 | To change the form text you can add the following keys to your locale files 353 | 354 | ```yaml 355 | # config/locales/en.yml 356 | en: 357 | stripe_rails: 358 | elements: 359 | label_text: Your label text 360 | submit_button_text: Your button text 361 | ``` 362 | 363 | ## Webhooks 364 | 365 | Stripe::Rails automatically sets up your application to receive webhooks from stripe.com whenever 366 | a payment event is generated. To enable this, you will need to configure your [stripe webhooks][3] to 367 | point back to your application. By default, the webhook controller is mounted at '/stripe/events' so 368 | you would want to enter in `http://myproductionapp.com/stripe/events` as your url for live mode, 369 | and `http://mystagingapp.com/stripe/events` for your test mode. 370 | 371 | If you want to mount the stripe engine somewhere else, you can do so by setting the `stripe.endpoint` 372 | parameter. E.g. 373 | 374 | ```ruby 375 | config.stripe.endpoint = '/payment/stripe-integration' 376 | ``` 377 | 378 | Your new webhook URL would then be `http://myproductionapp/payment/stripe-integration/events` 379 | 380 | ### Signed Webhooks 381 | 382 | Validation of your webhook's signature uses your webhook endpoint signing secret. 383 | Before you can verify signatures, you need to retrieve your endpoint’s secret from your 384 | Stripe Dashboard. Select an endpoint for which you want to obtain 385 | the secret, then select the Click to reveal button. 386 | 387 | ```ruby 388 | # config/application.rb 389 | # ... 390 | config.stripe.signing_secrets = ['whsec_XXXYYYZZZ'] 391 | ``` 392 | 393 | Each secret is unique to the endpoint to which it corresponds. If you use multiple endpoint, 394 | you must obtain a secret for each one. After this setup, Stripe starts to sign each webhook 395 | it sends to the endpoint. Because of this, we recommend setting this variable with environment 396 | variables: 397 | 398 | ```sh 399 | export STRIPE_SIGNING_SECRET=whsec_XXXYYYZZZ 400 | export STRIPE_CONNECT_SIGNING_SECRET=whsec_AAABBBCCC 401 | ``` 402 | 403 | ```ruby 404 | config.stripe.signing_secrets = [ENV.fetch('STRIPE_SIGNING_SECRET'), ENV.fetch('STRIPE_CONNECT_SIGNING_SECRET')] 405 | ``` 406 | 407 | The first secret that successfully matches for each incoming webhook will be used to verify the incoming events. 408 | 409 | #### Testing Signed Webhooks Locally 410 | 411 | In order to test signed webhooks, you'll need to trigger test webhooks from your Stripe dashboard, 412 | and configure your local environment to receive remote network requests. To do so, we recommend using 413 | [ngrok](https://ngrok.com/) to configure a secure tunnel to `localhost`. 414 | 415 | Once configured and running, `ngrok` will give you a unique URL which can be used to set up a webhook 416 | endpoint. Webhook endpoints are configured in your Dashboard's [Webhook settings](https://dashboard.stripe.com/account/webhooks) 417 | section. Make sure you are in **Test** mode and click `Add endpoint`, and provide your `ngrok` URL along with the `stripe.endpoint` suffix. 418 | 419 | An example webhook URL would then be `https://bf2a5d21.ngrok.io/stripe/events`. 420 | 421 | Once your endpoint is configured, you can reveal the **Signing secret**. This will need to be set 422 | as documented above: 423 | 424 | ```ruby 425 | # config/application.rb 426 | # ... 427 | config.stripe.signing_secrets = ['whsec_XXXYYYZZZ'] 428 | ``` 429 | 430 | And you'll need to restart your rails server with: 431 | 432 | ```sh 433 | rails restart 434 | ``` 435 | 436 | Now you're ready to click **Send test webhook**, and trigger whichever events you'd like to test from Stripe itself. 437 | 438 | ### Disabling auto mount 439 | 440 | Sometimes, you don't want the stripe engine to be auto-mounted so that 441 | you control *exactly* what priority it will take in your routing 442 | table. This is especially important if you have a catch-all route 443 | which should appear after all other routes. In order to disable 444 | auto-mounting of the Stripe engine: 445 | 446 | ```ruby 447 | # in application.rb 448 | config.stripe.auto_mount = false 449 | ``` 450 | 451 | Then, you will have to manually mount the engine in your main application. 452 | 453 | ```ruby 454 | # in your application's routes.rb: 455 | mount Stripe::Engine => "/stripe" 456 | ``` 457 | 458 | ### Responding to webhooks 459 | 460 | Once you have your webhook URL configured you can respond to a stripe webhook *anywhere* in your 461 | application just by including the Stripe::Callbacks module into your class and declaring a 462 | callback with one of the callback methods. For example, to update a customer's payment status: 463 | 464 | ```ruby 465 | class User < ActiveRecord::Base 466 | include Stripe::Callbacks 467 | 468 | after_customer_updated! do |customer, event| 469 | user = User.find_by_stripe_customer_id(customer.id) 470 | if customer.delinquent 471 | user.is_account_current = false 472 | user.save! 473 | end 474 | end 475 | end 476 | ``` 477 | 478 | or to send an email with one of your customer's monthly invoices 479 | 480 | ```ruby 481 | class InvoiceMailer < ActionMailer::Base 482 | include Stripe::Callbacks 483 | 484 | after_invoice_created! do |invoice, event| 485 | user = User.find_by_stripe_customer(invoice.customer) 486 | new_invoice(user, invoice).deliver 487 | end 488 | 489 | def new_invoice(user, invoice) 490 | @user = user 491 | @invoice = invoice 492 | mail :to => user.email, :subject => '[Acme.com] Your new invoice' 493 | end 494 | end 495 | ``` 496 | 497 | **Note:** `Stripe::Callbacks` won't get included until the including class has been loaded. This is usually not an issue in the production environment as eager loading is enabled by default (`config.eager_load = true`). You may run into an issue in your development environment where eager loading is disabled by default. 498 | 499 | If you don't wish to enable eager loading in development, you can configure the classes to be eager loaded like so 500 | 501 | ```ruby 502 | # in your application's config/environments/development.rb 503 | config.stripe.eager_load = 'account', 'module/some_class', 'etc' 504 | ``` 505 | This will ensure that callbacks will get loaded in those configured classes if eager loading is disabled. 506 | 507 | The naming convention for the callback events is after__{callback_name}! where `callback_name` 508 | is name of the stripe event with all `.` characters substituted with underscores. So, for 509 | example, the stripe event `customer.discount.created` can be hooked by `after_customer_discount_created!` 510 | and so on... 511 | 512 | Each web hook is passed an instance of the stripe object to which the event corresponds 513 | ([`Stripe::Customer`][8], [`Stripe::Invoice`][9], [`Stripe::Charge`][10], etc...) as well as the [`Stripe::Event`][4] which contains metadata about the event being raised. 514 | 515 | By default, the event is re-fetched securely from stripe.com to prevent damage to your system by 516 | a malicious system spoofing real stripe events. 517 | 518 | 519 | 520 | ### Critical and non-critical hooks 521 | 522 | So far, the examples have all used critical hooks, but in fact, each callback method comes in two flavors: "critical", 523 | specified with a trailing `!` character, and "non-critical", which has no "bang" character at all. What 524 | distinguishes one from the other is that _if an exception is raised in a critical callback, it will cause the entire webhook to fail_. 525 | 526 | This will indicate to stripe.com that you did not receive the webhook at all, and that it should retry it again later until it 527 | receives a successful response. On the other hand, there are some tasks that are more tangential to the payment work flow and aren't 528 | such a big deal if they get dropped on the floor. For example, A non-critical hook can be used to do things like have a bot 529 | notify your company's chatroom that something a credit card was successfully charged: 530 | 531 | ```ruby 532 | class AcmeBot 533 | include Stripe::Callbacks 534 | 535 | after_charge_succeeded do |charge| 536 | announce "Attention all Dudes and Dudettes. Ya'll are so PAID!!!" 537 | end 538 | end 539 | ``` 540 | 541 | Chances are that if you experience a momentary failure in connectivity to your chatroom, you don't want the whole payment notification to fail. 542 | 543 | 544 | ### Filtering Callbacks 545 | 546 | Certain stripe events represent updates to existing data. You may want to only fire the event when certain attributes of that data 547 | are updated. You can pass an `:only` option to your callback to filter to specify which attribute updates you're interested in. For 548 | example, to warn users whenever their credit card has changed: 549 | 550 | ```ruby 551 | class StripeMailer 552 | include Stripe::Callbacks 553 | 554 | after_customer_updated! :only => :active_card do |customer, evt| 555 | your_credit_card_on_file_was_updated_are_you_sure_this_was_you(customer).deliver 556 | end 557 | end 558 | ``` 559 | 560 | Filters can be specified as an array as well: 561 | 562 | ```ruby 563 | module Accounting 564 | include Stripe::Callbacks 565 | 566 | after_invoice_updated! :only => [:amount, :subtotal] do 567 | # update our records 568 | end 569 | end 570 | ``` 571 | 572 | Alternatively, you can just pass a proc to filter the event manually. It will receive an instance of [`Stripe::Event`][4] as 573 | its parameter: 574 | 575 | ```ruby 576 | module StagingOnly 577 | include Stripe::Callbacks 578 | 579 | after_charge_succeeded! :only => proc {|charge, evt| unless evt.livemode} do |charge| 580 | puts "FAKE DATA, PLEASE IGNORE!" 581 | end 582 | end 583 | ``` 584 | 585 | ### Catchall Callback 586 | 587 | The special 'stripe.event' callback will be invoked for every single event received from stripe.com. This can be useful for things 588 | like logging and analytics: 589 | 590 | ```ruby 591 | class StripeFirehose 592 | include Stripe::Callbacks 593 | 594 | after_stripe_event do |target, event| 595 | # do something useful 596 | end 597 | end 598 | ``` 599 | 600 | See the [complete listing of all stripe events][5], and the [webhook tutorial][6] for more great information on this subject. 601 | 602 | ## Unit testing 603 | 604 | If you want to test your callbacks, you can use the `Stripe::Rails::Testing` module to send mocked Stripe events. 605 | 606 | ```ruby 607 | require 'stripe/rails/testing' 608 | test "my callback handles new subscription" do 609 | Stripe::Rails::Testing.send_event "customer.subscription.created" 610 | # Assertions 611 | end 612 | ``` 613 | 614 | You can also overwrite some event properties: ([More info](https://github.com/rebelidealist/stripe-ruby-mock#customizing-webhooks)) 615 | 616 | ```ruby 617 | require 'stripe/rails/testing' 618 | test "my callback handles new subscription" do 619 | Stripe::Rails::Testing.send_event "customer.subscription.created", { 620 | :email => "john@doe.com", 621 | :account_balance => 40 622 | } 623 | # Assertions 624 | end 625 | ``` 626 | 627 | The default fixtures come from [the `stripe-ruby-mock` gem](https://github.com/rebelidealist/stripe-ruby-mock/tree/master/lib/stripe_mock/webhook_fixtures). 628 | 629 | ## Thanks 630 | 631 | ![Frontside](http://frontside.io/images/logo.svg) 632 | 633 | `Stripe::Rails` was originally developed with love and fondness by your friends at [Frontside][7]. They are available for your custom software development needs, including integration with stripe.com. 634 | 635 | ![Evercondo](https://dl.dropboxusercontent.com/s/m3ma9356uelep53/evercondo.png) 636 | 637 | `Stripe::Rails` has also been supported by the fine folks at [Evercondo][11], the next generation condo management software. 638 | 639 | 640 | [1]: https://stripe.com/docs/stripe.js 641 | [2]: https://manage.stripe.com/#account/apikeys 642 | [3]: https://manage.stripe.com/#account/webhooks 643 | [4]: https://stripe.com/docs/api?lang=ruby#events 644 | [5]: https://stripe.com/docs/api?lang=ruby#event_types 645 | [6]: https://stripe.com/docs/webhooks 646 | [7]: http://frontside.io 647 | [8]: https://stripe.com/docs/api?lang=ruby#customers 648 | [9]: https://stripe.com/docs/api?lang=ruby#invoices 649 | [10]: https://stripe.com/docs/api?lang=ruby#charges 650 | [11]: https://www.evercondo.com 651 | 652 | 653 | ## Code of Conduct 654 | 655 | Please note that this project is released with a Contributor Code of 656 | Conduct. By participating in this project you agree to abide by its 657 | terms, which can be found in the `CODE_OF_CONDUCT.md` file in this 658 | repository. 659 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | 4 | require 'rake/testtask' 5 | 6 | Rake::TestTask.new(:spec) do |t| 7 | t.libs << 'test' 8 | t.pattern = 'test/**/*_spec.rb' 9 | t.warning = false 10 | t.verbose = false 11 | end 12 | 13 | task default: :spec -------------------------------------------------------------------------------- /app/assets/stripe/stripe_elements.css: -------------------------------------------------------------------------------- 1 | .StripeElement { 2 | background-color: white; 3 | height: 40px; 4 | padding: 10px 12px; 5 | border-radius: 4px; 6 | border: 1px solid transparent; 7 | box-shadow: 0 1px 3px 0 #e6ebf1; 8 | -webkit-transition: box-shadow 150ms ease; 9 | transition: box-shadow 150ms ease; 10 | } 11 | 12 | .StripeElement--focus { 13 | box-shadow: 0 1px 3px 0 #cfd7df; 14 | } 15 | 16 | .StripeElement--invalid { 17 | border-color: #fa755a; 18 | } 19 | 20 | .StripeElement--webkit-autofill { 21 | background-color: #fefde5 !important; 22 | } 23 | -------------------------------------------------------------------------------- /app/assets/stripe/stripe_elements.js: -------------------------------------------------------------------------------- 1 | // Create a Stripe client. 2 | var stripe = Stripe(window.stripe_publishable_key); 3 | 4 | // Create an instance of Elements. 5 | var elements = stripe.elements(); 6 | 7 | var card = elements.create('card'); 8 | card.mount('#card-element'); 9 | 10 | card.addEventListener('change', function(event) { 11 | var displayError = document.getElementById('card-errors'); 12 | if (event.error) { 13 | displayError.textContent = event.error.message; 14 | } else { 15 | displayError.textContent = ''; 16 | } 17 | }); 18 | 19 | var form = document.getElementById('stripe-form'); 20 | form.addEventListener('submit', function(event) { 21 | event.preventDefault(); 22 | 23 | stripe.createToken(card).then(function(result) { 24 | if (result.error) { 25 | // Inform the user if there was an error. 26 | var errorElement = document.getElementById('card-errors'); 27 | errorElement.textContent = result.error.message; 28 | } else { 29 | // Send the token to your server. 30 | stripeTokenHandler(result.token); 31 | } 32 | }); 33 | }) 34 | 35 | function stripeTokenHandler(token) { 36 | // Insert the token ID into the form so it gets submitted to the server 37 | var form = document.getElementById('stripe-form'); 38 | var hiddenInput = document.createElement('input'); 39 | hiddenInput.setAttribute('type', 'hidden'); 40 | hiddenInput.setAttribute('name', 'stripeToken'); 41 | hiddenInput.setAttribute('value', token.id); 42 | form.appendChild(hiddenInput); 43 | 44 | // Submit the form 45 | form.submit(); 46 | } 47 | -------------------------------------------------------------------------------- /app/controllers/stripe/application_controller.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | class ApplicationController < ActionController::Base 3 | skip_before_action(:verify_authenticity_token) if protect_from_forgery.any? 4 | # is anything stripe wide? 5 | end 6 | end -------------------------------------------------------------------------------- /app/controllers/stripe/events_controller.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | class EventsController < ::Stripe::ApplicationController 3 | include Stripe::EventDispatch 4 | respond_to :json 5 | 6 | def create 7 | @event = dispatch_stripe_event(request) 8 | head :ok 9 | rescue JSON::ParserError => e 10 | ::Rails.logger.error e.message 11 | head :bad_request, status: 400 12 | rescue Stripe::SignatureVerificationError => e 13 | ::Rails.logger.error e.message 14 | head :bad_request, status: 400 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/helpers/stripe/javascript_helper.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | module JavascriptHelper 3 | DEFAULT_STRIPE_JS_VERSION = 'v3' 4 | 5 | def stripe_javascript_tag(stripe_js_version = DEFAULT_STRIPE_JS_VERSION) 6 | stripe_js_version = stripe_js_version.to_s.downcase 7 | 8 | render 'stripe/js', stripe_js_version: stripe_js_version 9 | end 10 | 11 | def stripe_elements_tag(submit_path:, 12 | css_path: asset_path("stripe_elements.css"), 13 | js_path: asset_path("stripe_elements.js"), 14 | &block) 15 | 16 | render partial: 'stripe/elements', locals: { 17 | submit_path: submit_path, 18 | label_text: t('stripe_rails.elements.label_text'), 19 | submit_button_text: t('stripe_rails.elements.submit_button_text'), 20 | css_path: css_path, 21 | js_path: js_path, 22 | block: block 23 | } 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/models/stripe/event_dispatch.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | module EventDispatch 3 | def dispatch_stripe_event(request) 4 | retrieve_stripe_event(request) do |evt| 5 | target = evt.data.object 6 | ::Stripe::Callbacks.run_callbacks(evt, target) 7 | end 8 | end 9 | 10 | def retrieve_stripe_event(request) 11 | id = request.params['id'] 12 | body = request.body.read 13 | sig_header = request.headers['HTTP_STRIPE_SIGNATURE'] 14 | endpoint_secrets = ::Rails.application.config.stripe.signing_secrets 15 | 16 | if Object.const_defined?('Stripe::Webhook') && sig_header && endpoint_secrets 17 | event = webhook_event(body, sig_header, endpoint_secrets) 18 | else 19 | event = Stripe::Event.retrieve(id) 20 | end 21 | 22 | yield event 23 | end 24 | 25 | private 26 | 27 | def webhook_event(body, sig_header, endpoint_secrets) 28 | endpoint_secrets.each_with_index do |secret, i| 29 | begin 30 | return ::Stripe::Webhook.construct_event(body, sig_header, secret.to_s) 31 | rescue ::Stripe::SignatureVerificationError 32 | raise if i == endpoint_secrets.length - 1 33 | end 34 | end 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /app/views/stripe/_elements.html.erb: -------------------------------------------------------------------------------- 1 | <%= stylesheet_link_tag css_path, media: 'all' %> 2 | 3 |
4 |
5 |
6 | 7 | <%= form_tag submit_path, id: "stripe-form" do %> 8 | <% if local_assigns[:block] %> 9 |
10 | <%= capture(&local_assigns[:block]) %> 11 |
12 | <% end %> 13 | <%= label_tag :card_element, label_text %> 14 |
15 | <%= submit_tag submit_button_text %> 16 | <% end %> 17 |
18 | 19 | 22 | <%= javascript_include_tag js_path, id: "stripe_elements_js" %> 23 | -------------------------------------------------------------------------------- /app/views/stripe/_elements_js.html.erb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansengming/stripe-rails/2e21a48c3aac5ef805423f349af15733c778325d/app/views/stripe/_elements_js.html.erb -------------------------------------------------------------------------------- /app/views/stripe/_js.html.erb: -------------------------------------------------------------------------------- 1 | <%- stripe_js_version = Stripe::JavascriptHelper::DEFAULT_STRIPE_JS_VERSION if (!defined?(stripe_js_version) || stripe_js_version.nil?) %> 2 | <%- case stripe_js_version %> 3 | <%- when 'v1', 'v2' %> 4 | <%- if ::Rails.application.config.stripe.debug_js %> 5 | 6 | <%- else %> 7 | 8 | <%- end %> 9 | 12 | <%- when 'v3' # the debug js for v3 isn't available %> 13 | 14 | 17 | <%- end %> -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | stripe_rails: 3 | elements: 4 | label_text: Credit or debit card 5 | submit_button_text: Submit payment 6 | 7 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | if Rails.application.config.stripe.auto_mount 3 | mount Stripe::Engine => Rails.application.config.stripe.endpoint 4 | end 5 | end 6 | 7 | Stripe::Engine.routes.draw do 8 | resources :events, only: :create 9 | end 10 | -------------------------------------------------------------------------------- /gemfiles/gemfiles/rails70.gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'rails', '~> 7.0.0' 4 | 5 | gem 'rake' 6 | gem 'responders' 7 | gem 'stripe' 8 | 9 | group :development, :test do 10 | gem 'm' 11 | end 12 | 13 | group :test do 14 | gem 'mocha' 15 | gem 'simplecov', require: false 16 | gem 'stripe-ruby-mock' 17 | gem 'webmock' 18 | # Required for system tests 19 | gem 'capybara' 20 | gem 'puma', '< 6' # https://github.com/teamcapybara/capybara/issues/2598 21 | gem 'selenium-webdriver' 22 | gem 'webdrivers' 23 | end 24 | -------------------------------------------------------------------------------- /gemfiles/rails60.gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'rails', '~> 6.0.0' 4 | 5 | gem 'rake' 6 | gem 'responders' 7 | gem 'stripe' 8 | 9 | group :development, :test do 10 | gem 'm' 11 | end 12 | 13 | group :test do 14 | gem 'mocha' 15 | gem 'simplecov', require: false 16 | gem 'stripe-ruby-mock' 17 | gem 'webmock' 18 | # Required for system tests 19 | gem 'capybara' 20 | gem 'puma' 21 | gem 'selenium-webdriver' 22 | gem 'webdrivers' 23 | end 24 | -------------------------------------------------------------------------------- /gemfiles/rails61.gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'rails', '~> 6.1.0' 4 | 5 | gem 'rake' 6 | gem 'responders' 7 | gem 'stripe' 8 | 9 | group :development, :test do 10 | gem 'm' 11 | end 12 | 13 | group :test do 14 | gem 'mocha' 15 | gem 'simplecov', require: false 16 | gem 'stripe-ruby-mock' 17 | gem 'webmock' 18 | # Required for system tests 19 | gem 'capybara' 20 | gem 'puma' 21 | gem 'selenium-webdriver' 22 | gem 'webdrivers' 23 | end 24 | -------------------------------------------------------------------------------- /gemfiles/rails70.gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'rails', '~> 7.0.0' 4 | 5 | gem 'rake' 6 | gem 'responders' 7 | gem 'stripe' 8 | 9 | group :development, :test do 10 | gem 'm' 11 | end 12 | 13 | group :test do 14 | gem 'mocha' 15 | gem 'simplecov', require: false 16 | gem 'stripe-ruby-mock' 17 | gem 'webmock' 18 | # Required for system tests 19 | gem 'capybara' 20 | gem 'puma' 21 | gem 'selenium-webdriver' 22 | gem 'webdrivers' 23 | end 24 | -------------------------------------------------------------------------------- /gemfiles/rails71.gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'rails', '~> 7.1.0' 4 | 5 | gem 'rake' 6 | gem 'responders' 7 | gem 'stripe' 8 | 9 | group :development, :test do 10 | gem 'm' 11 | end 12 | 13 | group :test do 14 | gem 'mocha' 15 | gem 'simplecov', require: false 16 | gem 'stripe-ruby-mock' 17 | gem 'webmock' 18 | # Required for system tests 19 | gem 'capybara' 20 | gem 'puma' 21 | gem 'selenium-webdriver' 22 | gem 'webdrivers' 23 | end 24 | -------------------------------------------------------------------------------- /lib/generators/stripe/install_generator.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | class InstallGenerator < ::Rails::Generators::Base 3 | source_root ::File.expand_path("../../templates", __FILE__) 4 | 5 | desc "copy plans.rb" 6 | def copy_plans_file 7 | copy_file "products.rb", "config/stripe/products.rb" 8 | copy_file "plans.rb", "config/stripe/plans.rb" 9 | copy_file "prices.rb", "config/stripe/prices.rb" 10 | copy_file "coupons.rb", "config/stripe/coupons.rb" 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /lib/generators/templates/coupons.rb: -------------------------------------------------------------------------------- 1 | # This file contains descriptions of all your statically defined 2 | # stripe coupons. You may wish to define unique one-off coupons 3 | # elsewhere, but for ones you will use many times, and will be 4 | # shared between users, this is a good place. 5 | 6 | # Example 7 | # Stripe::Coupons::Gold25 #=> 'gold25' 8 | 9 | # Stripe.coupon :gold25 do |coupon| 10 | # # specify if this coupon is useable 'once', 'forever', or 'repeating' 11 | # coupon.duration = 'repeating' 12 | # 13 | # # absolute amount, in cents, to discount 14 | # coupon.amount_off = 199 15 | # 16 | # # what currency to interpret the coupon amount 17 | # coupon.currency = 'usd' 18 | # 19 | # # how long will this coupon last? (only valid for duration of 'repeating') 20 | # coupon.duration_in_months = 6 21 | # 22 | # # percentage off 23 | # coupon.percent_off = 25 24 | # 25 | # UTC timestamp specifying the last time at which the coupon can be redeemed 26 | # coupon.redeem_by = (Time.now + 15.days).utc 27 | # 28 | # # How many times can this coupon be redeemed? 29 | # coupon.max_redemptions = 10 30 | # end 31 | # 32 | # Once you have your coupons defined, you can run 33 | # 34 | # rake stripe:prepare 35 | # 36 | # This will export any new coupons to stripe.com so that you can 37 | # begin using them in your API calls. Any coupons found that are not in this 38 | # file will be left as-is. 39 | -------------------------------------------------------------------------------- /lib/generators/templates/plans.rb: -------------------------------------------------------------------------------- 1 | # This file contains descriptions of all your stripe plans 2 | 3 | # Example 4 | # Stripe::Plans::PRIMO #=> 'primo' 5 | 6 | # Stripe.plan :primo do |plan| 7 | # # plan name as it will appear on credit card statements 8 | # plan.name = 'Acme as a service PRIMO' 9 | # 10 | # # amount in cents. This is 6.99 11 | # plan.amount = 699 12 | # 13 | # # currency to use for the plan (default 'usd') 14 | # plan.currency = 'usd' 15 | # 16 | # # interval must be either 'day', 'week', 'month' or 'year' 17 | # plan.interval = 'month' 18 | # 19 | # # only bill once every three months (default 1) 20 | # plan.interval_count = 3 21 | # 22 | # # number of days before charging customer's card (default 0) 23 | # plan.trial_period_days = 30 24 | # end 25 | 26 | # Once you have your plans defined, you can run 27 | # 28 | # rake stripe:prepare 29 | # 30 | # This will export any new plans to stripe.com so that you can 31 | # begin using them in your API calls. 32 | -------------------------------------------------------------------------------- /lib/generators/templates/prices.rb: -------------------------------------------------------------------------------- 1 | # This file contains descriptions of all your stripe prices 2 | 3 | # Example 4 | # Stripe::Prices::LITE.lookup_key #=> 'lite' 5 | 6 | # Prices will have a stripe generated id. The lookup_key will match the 7 | # configuration below. You can fetch the ID or object from stripe: 8 | # 9 | # Stripe::Prices::LITE.stripe_id #=> 'price_0000sdfs2qfsdf' 10 | # Stripe::Prices::LITE.stripe_object #=> #... 11 | 12 | # Prices are not deletable via the API, the `reset!` method will instead 13 | # create a new price and transfer the lookup key to the new price. 14 | 15 | # Stripe.price :lite do |price| 16 | # # Prices may belong to a product, this will create a product along with the price 17 | # price.name = 'Acme as a service LITE' 18 | 19 | # # You can also specify an existing product ID 20 | # # price.product_id = Stripe::Products::PRIMO.id 21 | # 22 | # # amount in cents. This is 6.99 23 | # price.unit_amount = 699 24 | # 25 | # # currency to use for the price (default 'usd') 26 | # price.currency = 'usd' 27 | # 28 | # price.recurring = { 29 | # # interval must be either 'day', 'week', 'month' or 'year' 30 | # interval: 'month', 31 | # # only bill once every three months (default 1) 32 | # interval_count: 3, 33 | # # Must be either 'metered' or 'licensed' 34 | # usage_type: 'metered', 35 | # # Specifies a usage aggregation strategy for metered usage 36 | # aggregate_usage: 'sum' 37 | # } 38 | # 39 | # end 40 | 41 | # Once you have your prices defined, you can run 42 | # 43 | # rake stripe:prepare 44 | # 45 | # This will export any new prices to stripe.com so that you can 46 | # begin using them in your API calls. 47 | -------------------------------------------------------------------------------- /lib/generators/templates/products.rb: -------------------------------------------------------------------------------- 1 | # This file contains descriptions of all your stripe products 2 | 3 | # Example 4 | # Stripe::Products::PRIMO #=> 'primo' 5 | 6 | # Stripe.product :primo do |product| 7 | # # product's name as it will appear on credit card statements 8 | # product.name = 'Acme as a service PRIMO' 9 | # 10 | # # Product, either 'service' or 'good' 11 | # product.type = 'service' 12 | # end 13 | 14 | # Once you have your products defined, you can run 15 | # 16 | # rake stripe:prepare 17 | # 18 | # This will export any new products to stripe.com so that you can 19 | # begin using them in your API calls. 20 | -------------------------------------------------------------------------------- /lib/stripe-rails.rb: -------------------------------------------------------------------------------- 1 | require 'stripe/rails' 2 | 3 | -------------------------------------------------------------------------------- /lib/stripe/billing_tier.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | module Plans 3 | class BillingTier 4 | include ActiveModel::Validations 5 | 6 | validates_presence_of :up_to 7 | validates_presence_of :flat_amount, if: ->(tier) { tier.unit_amount.nil? }, 8 | message: 'one of `flat_amount` or `unit_amount` must be specified!' 9 | validates_presence_of :unit_amount, if: ->(tier) { tier.flat_amount.nil? }, 10 | message: 'one of `flat_amount` or `unit_amount` must be specified!' 11 | validates_absence_of :flat_amount, if: ->(tier) { tier.unit_amount.present? }, 12 | message: 'only one of `flat_amount` or `unit_amount` should be specified!' 13 | validates_absence_of :unit_amount, if: ->(tier) { tier.flat_amount.present? }, 14 | message: 'only one of `flat_amount` or `unit_amount` should be specified!' 15 | 16 | attr_accessor :up_to, :flat_amount, :unit_amount 17 | 18 | def initialize(attrs) 19 | @up_to = attrs[:up_to] 20 | @flat_amount = attrs[:flat_amount] 21 | @unit_amount = attrs[:unit_amount] 22 | end 23 | 24 | def to_h 25 | { 26 | up_to: up_to, 27 | flat_amount: flat_amount, 28 | unit_amount: unit_amount 29 | }.compact 30 | end 31 | 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/stripe/callbacks.rb: -------------------------------------------------------------------------------- 1 | require 'stripe/callbacks/builder' 2 | 3 | module Stripe 4 | module Callbacks 5 | include Callbacks::Builder 6 | 7 | callback 'account.updated' 8 | callback 'account.application.deauthorized' 9 | callback 'account.external_account.created' 10 | callback 'account.external_account.deleted' 11 | callback 'account.external_account.updated' 12 | callback 'application_fee.created' 13 | callback 'application_fee.refunded' 14 | callback 'application_fee.refund.updated' 15 | callback 'balance.available' 16 | callback 'charge.captured' 17 | callback 'charge.failed' 18 | callback 'charge.pending' 19 | callback 'charge.refunded' 20 | callback 'charge.succeeded' 21 | callback 'charge.updated' 22 | callback 'charge.dispute.closed' 23 | callback 'charge.dispute.created' 24 | callback 'charge.dispute.funds_reinstated' 25 | callback 'charge.dispute.funds_withdrawn' 26 | callback 'charge.dispute.updated' 27 | callback 'charge.refund.updated' 28 | callback 'checkout.session.async_payment_failed' 29 | callback 'checkout.session.async_payment_succeeded' 30 | callback 'checkout.session.completed' 31 | callback 'checkout.session.expired' 32 | callback 'coupon.created' 33 | callback 'coupon.deleted' 34 | callback 'coupon.updated' 35 | callback 'customer.created' 36 | callback 'customer.deleted' 37 | callback 'customer.updated' 38 | callback 'customer.discount.created' 39 | callback 'customer.discount.deleted' 40 | callback 'customer.discount.updated' 41 | callback 'customer.source.created' 42 | callback 'customer.source.deleted' 43 | callback 'customer.source.expiring' 44 | callback 'customer.source.updated' 45 | callback 'customer.subscription.created' 46 | callback 'customer.subscription.deleted' 47 | callback 'customer.subscription.paused' 48 | callback 'customer.subscription.pending_update_applied' 49 | callback 'customer.subscription.pending_update_expired' 50 | callback 'customer.subscription.resumed' 51 | callback 'customer.subscription.trial_will_end' 52 | callback 'customer.subscription.updated' 53 | callback 'file.created' 54 | callback 'invoice.created' 55 | callback 'invoice.deleted' 56 | callback 'invoice.finalization_failed' 57 | callback 'invoice.finalized' 58 | callback 'invoice.marked_uncollectible' 59 | callback 'invoice.overdue' 60 | callback 'invoice.paid' 61 | callback 'invoice.payment_action_required' 62 | callback 'invoice.payment_failed' 63 | callback 'invoice.payment_succeeded' 64 | callback 'invoice.sent' 65 | callback 'invoice.upcoming' 66 | callback 'invoice.updated' 67 | callback 'invoice.voided' 68 | callback 'invoice.will_be_due' 69 | callback 'invoiceitem.created' 70 | callback 'invoiceitem.deleted' 71 | callback 'invoiceitem.updated' 72 | callback 'order.created' 73 | callback 'order.payment_failed' 74 | callback 'order.payment_succeeded' 75 | callback 'order.updated' 76 | callback 'order_return.created' 77 | callback 'payment_intent.amount_capturable_updated' 78 | callback 'payment_intent.canceled' 79 | callback 'payment_intent.created' 80 | callback 'payment_intent.payment_failed' 81 | callback 'payment_intent.processing' 82 | callback 'payment_intent.requires_action' 83 | callback 'payment_intent.succeeded' 84 | callback 'payment_method.attached' 85 | callback 'payment_method.card_automatically_updated' 86 | callback 'payment_method.detached' 87 | callback 'payment_method.updated' 88 | callback 'payout.canceled' 89 | callback 'payout.created' 90 | callback 'payout.failed' 91 | callback 'payout.paid' 92 | callback 'payout.updated' 93 | callback 'plan.created' 94 | callback 'plan.deleted' 95 | callback 'plan.updated' 96 | callback 'price.created' 97 | callback 'price.deleted' 98 | callback 'price.updated' 99 | callback 'product.created' 100 | callback 'product.deleted' 101 | callback 'product.updated' 102 | callback 'recipient.created' 103 | callback 'recipient.deleted' 104 | callback 'recipient.updated' 105 | callback 'review.closed' 106 | callback 'review.opened' 107 | callback 'setup_intent.canceled' 108 | callback 'setup_intent.created' 109 | callback 'setup_intent.requires_action' 110 | callback 'setup_intent.setup_failed' 111 | callback 'setup_intent.succeeded' 112 | callback 'sigma.scheduled_query_run.created' 113 | callback 'sku.created' 114 | callback 'sku.deleted' 115 | callback 'sku.updated' 116 | callback 'source.canceled' 117 | callback 'source.chargeable' 118 | callback 'source.failed' 119 | callback 'source.transaction.created' 120 | callback 'subscription_schedule.aborted' 121 | callback 'subscription_schedule.canceled' 122 | callback 'subscription_schedule.completed' 123 | callback 'subscription_schedule.created' 124 | callback 'subscription_schedule.expiring' 125 | callback 'subscription_schedule.released' 126 | callback 'subscription_schedule.updated' 127 | callback 'tax_rate.created' 128 | callback 'tax_rate.updated' 129 | callback 'transfer.created' 130 | callback 'transfer.reversed' 131 | callback 'transfer.updated' 132 | callback 'ping' 133 | callback 'stripe.event' 134 | 135 | class << self 136 | def run_callbacks(evt, target) 137 | _run_callbacks evt.type, evt, target 138 | _run_callbacks 'stripe.event', evt, target 139 | end 140 | 141 | def _run_callbacks(type, evt, target) 142 | run_critical_callbacks type, evt, target 143 | run_noncritical_callbacks type, evt, target 144 | end 145 | 146 | def run_critical_callbacks(type, evt, target) 147 | ::Stripe::Callbacks::critical_callbacks[type].each do |callback| 148 | callback.call(target, evt) 149 | end 150 | end 151 | 152 | def run_noncritical_callbacks(type, evt, target) 153 | ::Stripe::Callbacks::noncritical_callbacks[type].each do |callback| 154 | begin 155 | callback.call(target, evt) 156 | rescue Exception => e 157 | ::Rails.logger.error e.message 158 | ::Rails.logger.error e.backtrace.join("\n") 159 | end 160 | end 161 | end 162 | end 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /lib/stripe/callbacks/builder.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | module Callbacks 3 | module Builder 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | extend ActiveSupport::Concern 8 | @critical_callbacks = Hash.new do |h, k| 9 | h[k] = [] 10 | end 11 | @noncritical_callbacks = Hash.new do |h, k| 12 | h[k] = [] 13 | end 14 | module ClassMethods 15 | end 16 | 17 | class << self 18 | attr_reader :critical_callbacks, :noncritical_callbacks 19 | 20 | def clear_callbacks! 21 | critical_callbacks.clear 22 | noncritical_callbacks.clear 23 | end 24 | 25 | def callback(name) 26 | method_name = "after_#{name.gsub('.', '_')}" 27 | 28 | self::ClassMethods.send(:define_method, method_name) do |options = {}, &block| 29 | ::Stripe::Callbacks::noncritical_callbacks[name] << ::Stripe::Callbacks.callback_matcher(options, block) 30 | end 31 | self::ClassMethods.send(:define_method, "#{method_name}!") do |options = {}, &block| 32 | ::Stripe::Callbacks::critical_callbacks[name] << ::Stripe::Callbacks.callback_matcher(options, block) 33 | end 34 | end 35 | 36 | def callback_matcher(options, block) 37 | case only = options[:only] 38 | when Proc, Method 39 | proc do |target, evt| 40 | block.call(target, evt) if only.call(target, evt) 41 | end 42 | when Array, Set 43 | stringified_keys = only.map(&:to_s) 44 | proc do |target, evt| 45 | stringified_previous_attributes_keys = evt.data.previous_attributes.keys.map(&:to_s) 46 | intersection = stringified_previous_attributes_keys - stringified_keys 47 | block.call(target, evt) if intersection != stringified_previous_attributes_keys 48 | end 49 | when nil 50 | block 51 | else 52 | callback_matcher options.merge(:only => [only]), block 53 | end 54 | end 55 | end 56 | end 57 | end 58 | end 59 | end -------------------------------------------------------------------------------- /lib/stripe/configuration_builder.rb: -------------------------------------------------------------------------------- 1 | require "active_model" 2 | 3 | module Stripe 4 | module ConfigurationBuilder 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | class << self 9 | def configuration_for(class_id, &block) 10 | @_configuration_storage = "@#{class_id.to_s.pluralize}" 11 | instance_variable_set(@_configuration_storage, {}) 12 | configuration_class = Class.new(Stripe::ConfigurationBuilder::Configuration) 13 | const_set(:Configuration, configuration_class) 14 | configuration_class.class_eval(&block) 15 | stripe_class = Stripe.const_get(class_id.to_s.camelize) 16 | stripe_configuration_class = self 17 | send(:define_method, class_id) do |id, &block| 18 | config = configuration_class.new(id, stripe_class, stripe_configuration_class) 19 | block.call config 20 | config.finalize! 21 | end 22 | ::Stripe.send(:extend, self) 23 | end 24 | 25 | def configurations 26 | instance_variable_get(@_configuration_storage) 27 | end 28 | 29 | def all 30 | configurations.values 31 | end 32 | 33 | def [](key) 34 | configurations[key.to_s] 35 | end 36 | 37 | def []=(key, value) 38 | configurations[key.to_s] = value 39 | end 40 | 41 | def put! 42 | all.each(&:put!) 43 | end 44 | 45 | def reset! 46 | all.each(&:reset!) 47 | end 48 | end 49 | end 50 | 51 | class Configuration 52 | include ActiveModel::Validations 53 | attr_reader :id 54 | 55 | def initialize(id, stripe_class, stripe_configuration_class) 56 | @id = id 57 | @stripe_class = stripe_class 58 | @stripe_configuration_class = stripe_configuration_class 59 | end 60 | 61 | def finalize! 62 | validate! 63 | globalize! 64 | end 65 | 66 | def validate! 67 | fail Stripe::InvalidConfigurationError, errors if invalid? 68 | end 69 | 70 | def globalize! 71 | id_to_use = @constant_name || @id 72 | @stripe_configuration_class[id_to_use.to_s.downcase] = self 73 | @stripe_configuration_class.const_set(id_to_use.to_s.upcase, self) 74 | end 75 | 76 | def put! 77 | if exists? 78 | puts "[EXISTS] - #{@stripe_class}:#{@id}" unless Stripe::Engine.testing 79 | else 80 | object = @stripe_class.create({:id => @id}.merge compact_create_options) 81 | puts "[CREATE] - #{@stripe_class}:#{object}" unless Stripe::Engine.testing 82 | end 83 | end 84 | 85 | def reset! 86 | if object = exists? 87 | object.delete 88 | end 89 | object = @stripe_class.create({:id => @id}.merge compact_create_options) 90 | puts "[RESET] - #{@stripe_class}:#{object}" unless Stripe::Engine.testing 91 | end 92 | 93 | def compact_create_options 94 | create_options.delete_if { |_, v| v.nil? } 95 | end 96 | 97 | def to_s 98 | @id.to_s 99 | end 100 | 101 | def exists? 102 | @stripe_class.retrieve(to_s) 103 | rescue Stripe::InvalidRequestError 104 | false 105 | end 106 | end 107 | end 108 | 109 | class InvalidConfigurationError < StandardError 110 | attr_reader :errors 111 | 112 | def initialize(errors) 113 | super errors.messages 114 | @errors = errors 115 | end 116 | 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/stripe/coupons.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | module Coupons 3 | include ConfigurationBuilder 4 | 5 | configuration_for :coupon do 6 | attr_accessor :name, :duration, :amount_off, :currency, :duration_in_months, :max_redemptions, :percent_off, :redeem_by 7 | 8 | validates_presence_of :id, :duration 9 | validates_presence_of :duration_in_months, :if => :repeating? 10 | validates_inclusion_of :duration, :in => %w(forever once repeating), :message => "'%{value}' is not one of 'forever', 'once' or 'repeating'" 11 | validates_inclusion_of :percent_off, in: 1..100, unless: ->(coupon) {coupon.percent_off.nil?} 12 | validates_numericality_of :percent_off, :greater_than => 0, unless: ->(coupon) {coupon.percent_off.nil?} 13 | validates_numericality_of :duration_in_months, :greater_than => 0, :if => :repeating? 14 | validates_numericality_of :max_redemptions, greater_than: 0, unless: ->(coupon) {coupon.max_redemptions.nil?} 15 | 16 | def initialize(*args) 17 | super 18 | @currency = 'usd' 19 | @max_redemptions = 1 20 | end 21 | 22 | def repeating? 23 | duration == 'repeating' 24 | end 25 | 26 | def create_options 27 | { 28 | :name => name, 29 | :duration => duration, 30 | :percent_off => percent_off, 31 | :amount_off => amount_off, 32 | :currency => currency, 33 | :duration_in_months => duration_in_months, 34 | :max_redemptions => max_redemptions, 35 | :redeem_by => redeem_by 36 | } 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/stripe/current_api_version.rb: -------------------------------------------------------------------------------- 1 | class CurrentApiVersion 2 | def self.call 3 | Stripe.api_version || begin 4 | resp, _ = Stripe::Plan.request(:get, Stripe::Plan.resource_url) 5 | resp.http_headers['stripe-version'] 6 | end 7 | end 8 | 9 | def self.after_switch_to_products_in_plans? 10 | Date.parse(call) >= Date.parse('2018-02-05') 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/stripe/engine.rb: -------------------------------------------------------------------------------- 1 | require 'stripe' 2 | 3 | module Stripe 4 | class Engine < ::Rails::Engine 5 | isolate_namespace Stripe 6 | 7 | class << self 8 | attr_accessor :testing 9 | end 10 | 11 | stripe_config = config.stripe = Struct.new(:api_base, :api_version, :secret_key, :ignore_missing_secret_key, :verify_ssl_certs, :signing_secret, :signing_secrets, :publishable_key, :endpoint, :debug_js, :auto_mount, :eager_load, :open_timeout, :read_timeout) do 12 | # for backwards compatibility treat signing_secret as an alias for signing_secrets 13 | def signing_secret=(value) 14 | self.signing_secrets = value.nil? ? value : Array(value) 15 | end 16 | 17 | def signing_secret 18 | self.signing_secrets && self.signing_secrets.first 19 | end 20 | end.new 21 | 22 | def stripe_config.api_key=(key) 23 | warn "[DEPRECATION] to align with stripe nomenclature, stripe.api_key has been renamed to config.stripe.secret_key" 24 | self.secret_key = key 25 | end 26 | 27 | initializer 'stripe.configure.defaults', :before => 'stripe.configure' do |app| 28 | stripe = app.config.stripe 29 | stripe.secret_key ||= ENV['STRIPE_SECRET_KEY'] 30 | stripe.publishable_key ||= ENV['STRIPE_PUBLISHABLE_KEY'] 31 | stripe.endpoint ||= '/stripe' 32 | stripe.auto_mount = true if stripe.auto_mount.nil? 33 | stripe.eager_load ||= [] 34 | if stripe.debug_js.nil? 35 | stripe.debug_js = ::Rails.env.development? 36 | end 37 | end 38 | 39 | initializer 'stripe.configure' do |app| 40 | [:api_base, :verify_ssl_certs, :api_version, :open_timeout, :read_timeout].each do |key| 41 | value = app.config.stripe.send(key) 42 | Stripe.send("#{key}=", value) unless value.nil? 43 | end 44 | secret_key = app.config.stripe.secret_key 45 | Stripe.api_key = secret_key unless secret_key.nil? 46 | $stderr.puts <<-MSG unless Stripe.api_key || app.config.stripe.ignore_missing_secret_key 47 | No stripe.com API key was configured for environment #{::Rails.env}! this application will be 48 | unable to interact with stripe.com. You can set your API key with either the environment 49 | variable `STRIPE_SECRET_KEY` (recommended) or by setting `config.stripe.secret_key` in your 50 | environment file directly. 51 | MSG 52 | end 53 | 54 | eager_load_classes = -> class_names { 55 | class_names.each do |constant| 56 | begin 57 | constant.to_s.camelize.constantize 58 | rescue NameError 59 | require constant 60 | end 61 | end 62 | } 63 | 64 | initializer 'stripe.callbacks.clear_after_unload' do |app| 65 | # Skip Rails 4 for now. 66 | next unless app.respond_to?(:reloader) 67 | 68 | # Clear callbacks after all autoloaded classes are removed. 69 | # This prevents duplicate callbacks being added during development. 70 | app.reloader.after_class_unload do 71 | ::Stripe::Callbacks.clear_callbacks! 72 | eager_load_classes.call(app.config.stripe.eager_load) 73 | end 74 | end 75 | 76 | initializer 'stripe.callbacks.eager_load' do |app| 77 | app.config.after_initialize do 78 | eager_load_classes.call(app.config.stripe.eager_load) 79 | end 80 | end 81 | 82 | config.to_prepare do 83 | ActiveSupport.on_load :action_controller do 84 | # ActionController::API does not have a helper method 85 | if respond_to?(:helper) 86 | helper Stripe::JavascriptHelper 87 | end 88 | end 89 | end 90 | 91 | initializer 'stripe.plans_and_coupons' do |app| 92 | for configuration in %w(products plans coupons prices) 93 | path = app.root.join("config/stripe/#{configuration}.rb") 94 | load path if path.exist? 95 | end 96 | end 97 | 98 | initializer 'stripe.assets.precompile' do |app| 99 | if app.config.respond_to?(:assets) 100 | app.config.assets.precompile += %w( stripe_elements.js stripe_elements.css ) 101 | end 102 | end 103 | 104 | rake_tasks do 105 | load 'stripe/rails/tasks.rake' 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/stripe/plans.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | module Plans 3 | include ConfigurationBuilder 4 | 5 | configuration_for :plan do 6 | attr_accessor :active, 7 | :aggregate_usage, 8 | :amount, 9 | :billing_scheme, 10 | :constant_name, 11 | :currency, 12 | :interval, 13 | :interval_count, 14 | :metadata, 15 | :name, 16 | :nickname, 17 | :product_id, 18 | :statement_descriptor, 19 | :tiers, 20 | :tiers_mode, 21 | :transform_usage, 22 | :trial_period_days, 23 | :usage_type 24 | 25 | validates_presence_of :id, :currency 26 | validates_presence_of :amount, unless: ->(p) { p.billing_scheme == 'tiered' } 27 | validates_absence_of :transform_usage, if: ->(p) { p.billing_scheme == 'tiered' } 28 | validates_presence_of :tiers_mode, if: ->(p) { p.billing_scheme == 'tiered' } 29 | validates_inclusion_of :interval, 30 | in: %w(day week month year), 31 | message: "'%{value}' is not one of 'day', 'week', 'month' or 'year'" 32 | 33 | validates :statement_descriptor, length: { maximum: 22 } 34 | 35 | validates :active, inclusion: { in: [true, false] }, allow_nil: true 36 | validates :usage_type, inclusion: { in: %w{ metered licensed } }, allow_nil: true 37 | validates :billing_scheme, inclusion: { in: %w{ per_unit tiered } }, allow_nil: true 38 | validates :aggregate_usage, inclusion: { in: %w{ sum last_during_period last_ever max } }, allow_nil: true 39 | validates :tiers_mode, inclusion: { in: %w{ graduated volume } }, allow_nil: true 40 | 41 | validate :name_or_product_id 42 | validate :aggregate_usage_must_be_metered, if: ->(p) { p.aggregate_usage.present? } 43 | validate :valid_constant_name, unless: ->(p) { p.constant_name.nil? } 44 | 45 | # validations for when using tiered billing 46 | validate :tiers_must_be_array, if: ->(p) { p.tiers.present? } 47 | validate :billing_scheme_must_be_tiered, if: ->(p) { p.tiers.present? } 48 | validate :validate_tiers, if: ->(p) { p.billing_scheme == 'tiered' } 49 | 50 | def initialize(*args) 51 | super(*args) 52 | @currency = 'usd' 53 | @interval_count = 1 54 | @trial_period_days = 0 55 | end 56 | 57 | private 58 | def aggregate_usage_must_be_metered 59 | errors.add(:aggregate_usage, 'usage_type must be metered') unless (usage_type == 'metered') 60 | end 61 | 62 | def name_or_product_id 63 | errors.add(:base, 'must have a product_id or a name') unless (@product_id.present? ^ @name.present?) 64 | end 65 | 66 | def billing_scheme_must_be_tiered 67 | errors.add(:billing_scheme, 'must be set to `tiered` when specifying `tiers`') unless billing_scheme == 'tiered' 68 | end 69 | 70 | def tiers_must_be_array 71 | errors.add(:tiers, 'must be an Array') unless tiers.is_a?(Array) 72 | end 73 | 74 | def billing_tiers 75 | @billing_tiers = tiers.map { |t| Stripe::Plans::BillingTier.new(t) } if tiers 76 | end 77 | 78 | def validate_tiers 79 | billing_tiers.all?(&:valid?) 80 | end 81 | 82 | module ConstTester; end 83 | def valid_constant_name 84 | ConstTester.const_set(constant_name.to_s.upcase, constant_name) 85 | ConstTester.send(:remove_const, constant_name.to_s.upcase.to_sym) 86 | rescue NameError 87 | errors.add(:constant_name, 'is not a valid Ruby constant name.') 88 | end 89 | 90 | def create_options 91 | if CurrentApiVersion.after_switch_to_products_in_plans? 92 | default_create_options 93 | else 94 | create_options_without_products 95 | end 96 | end 97 | 98 | def default_create_options 99 | { 100 | currency: currency, 101 | product: product_options, 102 | amount: amount, 103 | interval: interval, 104 | interval_count: interval_count, 105 | trial_period_days: trial_period_days, 106 | metadata: metadata, 107 | usage_type: usage_type, 108 | aggregate_usage: aggregate_usage, 109 | billing_scheme: billing_scheme, 110 | nickname: nickname, 111 | tiers: tiers ? tiers.map(&:to_h) : nil, 112 | tiers_mode: tiers_mode, 113 | transform_usage: transform_usage 114 | }.compact 115 | end 116 | 117 | def product_options 118 | product_id.presence || { name: name, statement_descriptor: statement_descriptor } 119 | end 120 | 121 | # Note: these options serve an older API, as such they should 122 | # probably never be updated. 123 | def create_options_without_products 124 | { 125 | currency: currency, 126 | name: name, 127 | amount: amount, 128 | interval: interval, 129 | interval_count: interval_count, 130 | trial_period_days: trial_period_days, 131 | metadata: metadata, 132 | statement_descriptor: statement_descriptor 133 | } 134 | end 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/stripe/prices.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | module Prices 3 | include ConfigurationBuilder 4 | VALID_TIME_UNITS = %i(day week month year) 5 | 6 | configuration_for :price do 7 | attr_reader :lookup_key 8 | attr_accessor :active, 9 | :billing_scheme, 10 | :constant_name, 11 | :currency, 12 | :metadata, 13 | :name, 14 | :nickname, 15 | :object, 16 | :product_id, 17 | :recurring, 18 | :statement_descriptor, 19 | :tax_behavior, 20 | :tiers, 21 | :tiers_mode, 22 | :transform_quantity, 23 | :type, 24 | :unit_amount 25 | 26 | validates_presence_of :id, :currency 27 | validates_presence_of :unit_amount, unless: ->(p) { p.billing_scheme == 'tiered' } 28 | validates_absence_of :transform_quantity, if: ->(p) { p.billing_scheme == 'tiered' } 29 | validates_presence_of :tiers_mode, :tiers, if: ->(p) { p.billing_scheme == 'tiered' } 30 | 31 | validates_numericality_of :recurring_interval_count, allow_nil: true 32 | 33 | validates_inclusion_of :recurring_interval, 34 | in: VALID_TIME_UNITS.collect(&:to_s), 35 | message: "'%{value}' is not one of #{VALID_TIME_UNITS.to_sentence(last_word_connector: ', or ')}", 36 | if: ->(p) { p.recurring.present? } 37 | 38 | validates :statement_descriptor, length: { maximum: 22 } 39 | 40 | validates :active, inclusion: { in: [true, false] }, allow_nil: true 41 | validates :billing_scheme, inclusion: { in: %w{ per_unit tiered } }, allow_nil: true 42 | validates :recurring_aggregate_usage, inclusion: { in: %w{ sum last_during_period last_ever max } }, allow_nil: true 43 | validates :recurring_usage_type, inclusion: { in: %w{ metered licensed } }, allow_nil: true 44 | validates :tax_behavior, inclusion: { in: %w{ inclusive exclusive unspecified } }, allow_nil: true 45 | validates :tiers_mode, inclusion: { in: %w{ graduated volume } }, allow_nil: true 46 | 47 | validate :name_or_product_id 48 | validate :recurring_aggregate_usage_must_be_metered, if: ->(p) { p.recurring_aggregate_usage.present? } 49 | validate :recurring_interval_count_maximum, if: ->(p) { p.recurring_interval_count.present? } 50 | validate :valid_constant_name, unless: ->(p) { p.constant_name.nil? } 51 | 52 | # validations for when using tiered billing 53 | validate :tiers_must_be_array, if: ->(p) { p.tiers.present? } 54 | validate :billing_scheme_must_be_tiered, if: ->(p) { p.tiers.present? } 55 | validate :validate_tiers, if: ->(p) { p.billing_scheme == 'tiered' } 56 | 57 | def initialize(*args) 58 | super(*args) 59 | @currency = 'usd' 60 | @lookup_key = @id.to_s 61 | @recurring = (recurring || {}).symbolize_keys 62 | end 63 | 64 | # We're overriding a handful of the Configuration methods so that 65 | # we find and create by lookup_key instead of by ID. The ID is assigned 66 | # by stripe and out of our control 67 | def put! 68 | if exists? 69 | puts "[EXISTS] - #{@stripe_class}:#{@id}:#{stripe_id}" unless Stripe::Engine.testing 70 | else 71 | object = @stripe_class.create({:lookup_key => @lookup_key}.merge compact_create_options) 72 | puts "[CREATE] - #{@stripe_class}:#{object}" unless Stripe::Engine.testing 73 | end 74 | end 75 | 76 | # You can't delete prices, but you can transfer the lookup key to a new price 77 | def reset! 78 | object = @stripe_class.create(reset_options) 79 | puts "[RESET] - #{@stripe_class}:#{object}" unless Stripe::Engine.testing 80 | end 81 | 82 | def exists? 83 | stripe_object.presence 84 | rescue Stripe::InvalidRequestError 85 | false 86 | end 87 | 88 | def stripe_object 89 | @stripe_class.list({lookup_keys: [@lookup_key]}).data.first.presence || nil 90 | rescue Stripe::InvalidRequestError 91 | nil 92 | end 93 | 94 | def stripe_id 95 | @stripe_id ||= stripe_object.try(:id) 96 | end 97 | 98 | def recurring_interval 99 | recurring[:interval] 100 | end 101 | 102 | def recurring_aggregate_usage 103 | recurring[:aggregate_usage] 104 | end 105 | 106 | def recurring_usage_type 107 | recurring[:usage_type] 108 | end 109 | 110 | def recurring_interval_count 111 | recurring[:interval_count] 112 | end 113 | 114 | private 115 | def recurring_aggregate_usage_must_be_metered 116 | errors.add(:recurring_aggregate_usage, 'recurring[:usage_type] must be metered') unless (recurring_usage_type == 'metered') 117 | end 118 | 119 | def recurring_interval_count_maximum 120 | time_unit = recurring_interval.to_sym 121 | 122 | return unless VALID_TIME_UNITS.include?(time_unit) && recurring_interval_count.respond_to?(time_unit) 123 | too_long = recurring_interval_count.send(time_unit) > 1.year 124 | 125 | errors.add(:recurring_interval_count, 'recurring[:interval_count] Maximum is one year (1 year, 12 months, or 52 weeks') if too_long 126 | end 127 | 128 | def name_or_product_id 129 | errors.add(:base, 'must have a product_id or a name') unless (@product_id.present? ^ @name.present?) 130 | end 131 | 132 | def billing_scheme_must_be_tiered 133 | errors.add(:billing_scheme, 'must be set to `tiered` when specifying `tiers`') unless billing_scheme == 'tiered' 134 | end 135 | 136 | def tiers_must_be_array 137 | errors.add(:tiers, 'must be an Array') unless tiers.is_a?(Array) 138 | end 139 | 140 | def billing_tiers 141 | @billing_tiers = tiers.map { |t| Stripe::Plans::BillingTier.new(t) } if tiers 142 | end 143 | 144 | def validate_tiers 145 | billing_tiers.all?(&:valid?) 146 | end 147 | 148 | module ConstTester; end 149 | def valid_constant_name 150 | ConstTester.const_set(constant_name.to_s.upcase, constant_name) 151 | ConstTester.send(:remove_const, constant_name.to_s.upcase.to_sym) 152 | rescue NameError 153 | errors.add(:constant_name, 'is not a valid Ruby constant name.') 154 | end 155 | 156 | def reset_options 157 | existing_object = stripe_object 158 | # Lookup and set the existing product ID if unset 159 | @product_id ||= existing_object.product if existing_object.present? 160 | 161 | { transfer_lookup_key: existing_object.present? }.merge(compact_create_options) 162 | end 163 | 164 | def create_options 165 | { 166 | currency: currency, 167 | unit_amount: unit_amount, 168 | active: active, 169 | metadata: metadata, 170 | nickname: nickname.presence || @lookup_key, 171 | recurring: recurring.compact, 172 | tiers: tiers ? tiers.map(&:to_h) : nil, 173 | tiers_mode: tiers_mode, 174 | billing_scheme: billing_scheme, 175 | lookup_key: @lookup_key, 176 | tax_behavior: tax_behavior, 177 | transform_quantity: transform_quantity, 178 | }.merge(product_options).compact 179 | end 180 | 181 | def product_options 182 | if product_id.present? 183 | { product: product_id } 184 | else 185 | { 186 | product_data: { name: name, statement_descriptor: statement_descriptor } 187 | } 188 | end 189 | end 190 | end 191 | end 192 | end 193 | -------------------------------------------------------------------------------- /lib/stripe/products.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | module Products 3 | include ConfigurationBuilder 4 | 5 | configuration_for :product do 6 | attr_accessor :name, 7 | :type, 8 | :active, 9 | :attributes, 10 | :description, 11 | :caption, 12 | :metadata, 13 | :shippable, 14 | :unit_label, 15 | :url, 16 | :statement_descriptor 17 | 18 | validates_presence_of :name, :type 19 | 20 | validates :statement_descriptor, length: { maximum: 22 } 21 | 22 | validates :active, :shippable, inclusion: { in: [true, false] }, allow_nil: true 23 | validates :type, inclusion: { in: %w(service good) } 24 | validates :caption, :description, :shippable, :url, absence: true, unless: :good? 25 | validates :statement_descriptor, absence: true, unless: :service? 26 | 27 | private 28 | def good? 29 | type == 'good' 30 | end 31 | 32 | def service? 33 | type == 'service' 34 | end 35 | 36 | def create_options 37 | { 38 | name: name, 39 | type: type, 40 | active: active, 41 | attributes: attributes, 42 | description: description, 43 | caption: caption, 44 | metadata: metadata, 45 | shippable: shippable, 46 | unit_label: unit_label, 47 | url: url, 48 | statement_descriptor: statement_descriptor 49 | } 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/stripe/rails.rb: -------------------------------------------------------------------------------- 1 | require 'responders' 2 | require "stripe/rails/version" 3 | require 'stripe/engine' 4 | require 'stripe/configuration_builder' 5 | require 'stripe/current_api_version' 6 | require 'stripe/plans' 7 | require 'stripe/prices' 8 | require 'stripe/billing_tier' 9 | require 'stripe/coupons' 10 | require 'stripe/products' 11 | require 'stripe/callbacks' 12 | -------------------------------------------------------------------------------- /lib/stripe/rails/tasks.rake: -------------------------------------------------------------------------------- 1 | namespace :stripe do 2 | desc 'verify your stripe.com authentication configuration' 3 | task 'verify' => :environment do 4 | begin 5 | Stripe::Plan.list 6 | puts "[OK] - connection to stripe.com is functioning properly" 7 | rescue Stripe::AuthenticationError => e 8 | puts "[FAIL] - authentication failed" 9 | end 10 | end 11 | 12 | task 'products:prepare' => 'environment' do 13 | if CurrentApiVersion.after_switch_to_products_in_plans? 14 | Stripe::Products.put! 15 | else 16 | puts '[SKIPPED] Current API version does not support Products' 17 | end 18 | end 19 | 20 | task 'plans:prepare' => 'environment' do 21 | Stripe::Plans.put! 22 | end 23 | 24 | task 'coupons:prepare' => 'environment' do 25 | Stripe::Coupons.put! 26 | end 27 | 28 | desc 'delete and redefine all coupons defined in config/stripe/coupons.rb' 29 | task 'coupons:reset!' => 'environment' do 30 | Stripe::Coupons.reset! 31 | end 32 | 33 | task 'prices:prepare' => 'environment' do 34 | Stripe::Prices.put! 35 | end 36 | 37 | desc 'delete and redefine all prices defined in config/stripe/prices.rb' 38 | task 'prices:reset!' => 'environment' do 39 | Stripe::Prices.reset! 40 | end 41 | 42 | desc "create all plans and coupons defined in config/stripe/{products|plans|prices|coupons}.rb" 43 | task 'prepare' => ['products:prepare', 'plans:prepare', 'prices:prepare', 'coupons:prepare'] 44 | end 45 | -------------------------------------------------------------------------------- /lib/stripe/rails/testing.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'stripe_mock' 3 | rescue LoadError 4 | warn %q{Please add "gem 'stripe-ruby-mock', group: 'test'"" to the Gemfile to use Stripe::Rails::Testing"} 5 | exit 6 | end 7 | require 'stripe/callbacks' 8 | 9 | module Stripe 10 | module Rails 11 | module Testing 12 | def self.send_event(event, properties = {}) 13 | evt = StripeMock.mock_webhook_event(event, properties) 14 | target = evt.data.object 15 | ::Stripe::Callbacks.run_callbacks(evt, target) 16 | end 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /lib/stripe/rails/version.rb: -------------------------------------------------------------------------------- 1 | module Stripe 2 | module Rails 3 | VERSION = '2.6.0'.freeze 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /stripe-rails.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path('lib/stripe/rails/version', __dir__) 2 | 3 | Gem::Specification.new do |gem| 4 | gem.authors = ["Charles Lowell", "Nola Stowe", "SengMing Tan"] 5 | gem.email = ["sengming@sanemen.com"] 6 | gem.description = "A gem to integrate stripe into your rails app" 7 | gem.summary = "A gem to integrate stripe into your rails app" 8 | gem.homepage = "https://github.com/tansengming/stripe-rails" 9 | gem.license = 'MIT' 10 | 11 | gem.files = `git ls-files`.split($\) 12 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 13 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 14 | gem.name = "stripe-rails" 15 | gem.require_paths = ["lib"] 16 | gem.version = Stripe::Rails::VERSION 17 | gem.add_dependency 'rails', '>= 5.1' 18 | gem.add_dependency 'stripe', '>= 3.15.0' 19 | gem.add_dependency 'responders' 20 | end 21 | -------------------------------------------------------------------------------- /test/callbacks_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Stripe::Callbacks do 4 | include Rack::Test::Methods 5 | include CallbackHelpers 6 | 7 | let(:app) { Rails.application } 8 | let(:event) { JSON.parse(File.read File.expand_path('event.json', __dir__)) } 9 | let(:invoice) { JSON.parse(File.read File.expand_path('invoice.json', __dir__)) } 10 | let(:content) { event } 11 | let(:observer) { Class.new } 12 | 13 | before do 14 | header 'Accept', 'application/json' 15 | header 'Content-Type', 'application/json' 16 | 17 | observer.include Stripe::Callbacks 18 | 19 | event['data']['object'] = invoice 20 | 21 | self.type = content['type'] 22 | end 23 | after { ::Stripe::Callbacks.clear_callbacks! } 24 | 25 | subject { post 'stripe/events', JSON.pretty_generate(content) } 26 | 27 | describe 'defined with a bang' do 28 | let(:callback) { :after_invoice_payment_succeeded! } 29 | before { run_callback_with(callback) {|target, e| @event = e; @target = target} } 30 | 31 | describe 'when it is invoked for the invoice.payment_succeeded event' do 32 | it 'is invoked for the invoice.payment_succeeded event' do 33 | subject 34 | _(@event).wont_be_nil 35 | _(@event.type).must_equal 'invoice.payment_succeeded' 36 | _(@target.total).must_equal 6999 37 | end 38 | end 39 | 40 | describe 'when the invoked.payment_failed webhook is called' do 41 | before { self.type = 'invoked.payment_failed' } 42 | 43 | it 'the invoice.payment_succeeded callback is not invoked' do 44 | subject 45 | _(@event).must_be_nil 46 | end 47 | end 48 | 49 | describe 'if it raises an exception' do 50 | before { run_callback_with(callback) { fail } } 51 | 52 | it 'causes the whole webhook to fail' do 53 | _(-> { subject }).must_raise RuntimeError 54 | end 55 | end 56 | end 57 | 58 | describe 'defined without a bang and raising an exception' do 59 | let(:callback) { :after_invoice_payment_succeeded } 60 | before { run_callback_with(callback) { fail } } 61 | 62 | it 'does not cause the webhook to fail' do 63 | subject 64 | _(last_response.status).must_be :>=, 200 65 | _(last_response.status).must_be :<, 300 66 | end 67 | end 68 | 69 | describe 'the after_stripe_event callback to catch any event' do 70 | let(:events) { [] } 71 | before { run_callback_with(:after_stripe_event) { |_, evt| events << evt } } 72 | 73 | describe 'when it gets invoked for a standard event' do 74 | before { self.type = 'invoice.payment_failed' } 75 | 76 | it 'it will be run' do 77 | subject 78 | _(events.first.type).must_equal 'invoice.payment_failed' 79 | end 80 | end 81 | 82 | describe 'when it gets invoked for an arbitrary event' do 83 | before { self.type = 'foo.bar.baz' } 84 | 85 | it 'it will be run' do 86 | subject 87 | _(events.first.type).must_equal 'foo.bar.baz' 88 | end 89 | end 90 | end 91 | 92 | describe 'filtering on specific changed attributes' do 93 | events = nil 94 | before do 95 | events = [] 96 | self.type = 'invoice.updated' 97 | @stubbed_event.data.previous_attributes = {} 98 | end 99 | 100 | describe 'specified as an single symbol' do 101 | before do 102 | observer.class_eval do 103 | after_invoice_updated! :only => :closed do |invoice, evt| 104 | events << evt 105 | end 106 | end 107 | end 108 | 109 | describe 'when a prior attribute was not specified' do 110 | it 'does not fire events' do 111 | subject 112 | _(events.length).must_equal 0 113 | end 114 | end 115 | 116 | describe 'when a prior attribute was specified' do 117 | before { @stubbed_event.data.previous_attributes['closed'] = true } 118 | it 'fires events' do 119 | subject 120 | _(events.length).must_equal 1 121 | end 122 | end 123 | end 124 | 125 | describe 'specified as an array' do 126 | before do 127 | observer.class_eval do 128 | after_invoice_updated! :only => [:currency, :subtotal] do |invoice, evt| 129 | events << evt 130 | end 131 | end 132 | end 133 | 134 | describe 'when a prior attribute was not specified' do 135 | it 'does not fire events' do 136 | subject 137 | _(events.length).must_equal 0 138 | end 139 | end 140 | 141 | describe 'when prior attributes were specified' do 142 | before { @stubbed_event.data.previous_attributes['subtotal'] = 699 } 143 | it 'fire events' do 144 | subject 145 | _(events.length).must_equal 1 146 | end 147 | end 148 | end 149 | 150 | describe 'specified as a lambda' do 151 | before do 152 | observer.class_eval do 153 | after_invoice_updated :only => proc {|target, evt| evt.data.previous_attributes.to_hash.has_key? :closed} do |i,e| 154 | events << e 155 | end 156 | end 157 | end 158 | 159 | describe 'when the lambda is not true' do 160 | it 'does not fire events' do 161 | subject 162 | _(events.length).must_equal 0 163 | end 164 | end 165 | 166 | describe 'when the lambda is not true' do 167 | before { @stubbed_event.data.previous_attributes['closed'] = 'false' } 168 | it 'fires events' do 169 | subject 170 | _(events.length).must_equal 1 171 | end 172 | end 173 | end 174 | end 175 | 176 | describe 'with forgery protection enabled' do 177 | before do 178 | ActionController::Base.allow_forgery_protection = true 179 | ActionController::Base.protect_from_forgery with: :exception 180 | end 181 | after { ActionController::Base.allow_forgery_protection = false } 182 | 183 | it { subject } # must_not raise error 184 | end 185 | end -------------------------------------------------------------------------------- /test/coupon_builder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'building coupons' do 4 | before do 5 | Stripe::Coupons.configurations.clear 6 | end 7 | describe 'default values' do 8 | before do 9 | @coupon = Stripe.coupon(:firesale) do |coupon| 10 | coupon.name = '100% OFF FIRESALE' 11 | coupon.duration = 'once' 12 | coupon.duration_in_months = 100 13 | coupon.amount_off = 100 14 | end 15 | end 16 | it "allows a single redemption by default" do 17 | _(@coupon.max_redemptions).must_equal 1 18 | end 19 | end 20 | 21 | describe 'simply' do 22 | before do 23 | @now = Time.now.utc 24 | Stripe.coupon(:gold25) do |coupon| 25 | coupon.duration = 'repeating' 26 | coupon.duration_in_months = 10 27 | coupon.amount_off = 100 28 | coupon.currency = 'USD' 29 | coupon.max_redemptions = 3 30 | coupon.percent_off = 25 31 | coupon.redeem_by = @now 32 | end 33 | end 34 | after {Stripe::Coupons.send(:remove_const, :GOLD25)} 35 | 36 | it 'is accessible via hash lookup (symbol/string agnostic)' do 37 | _(Stripe::Coupons[:gold25]).must_equal Stripe::Coupons::GOLD25 38 | _(Stripe::Coupons['gold25']).must_equal Stripe::Coupons::GOLD25 39 | end 40 | 41 | describe 'uploading' do 42 | describe 'when none exists on stripe.com' do 43 | before do 44 | Stripe::Coupon.stubs(:retrieve).raises(Stripe::InvalidRequestError.new("not found", "id")) 45 | end 46 | it 'creates the plan online' do 47 | Stripe::Coupon.expects(:create).with( 48 | :id => :gold25, 49 | :duration => 'repeating', 50 | :duration_in_months => 10, 51 | :amount_off => 100, 52 | :currency => 'USD', 53 | :max_redemptions => 3, 54 | :percent_off => 25, 55 | :redeem_by => @now 56 | ) 57 | Stripe::Coupons.put! 58 | end 59 | 60 | end 61 | describe 'when it is already present on stripe.com' do 62 | before do 63 | Stripe::Coupon.stubs(:retrieve).returns(Stripe::Coupon.construct_from({ 64 | :id => :gold25, 65 | })) 66 | end 67 | it 'is a no-op' do 68 | Stripe::Coupon.expects(:create).never 69 | Stripe::Coupons.put! 70 | end 71 | end 72 | end 73 | 74 | describe 'reseting' do 75 | describe 'when it does not exist on stripe.com'do 76 | before do 77 | Stripe::Coupon.stubs(:retrieve).raises(Stripe::InvalidRequestError.new("not found", "id")) 78 | end 79 | it 'creates the plan' do 80 | Stripe::Coupon.expects(:create).with( 81 | :id => :gold25, 82 | :duration => 'repeating', 83 | :duration_in_months => 10, 84 | :amount_off => 100, 85 | :currency => 'USD', 86 | :max_redemptions => 3, 87 | :percent_off => 25, 88 | :redeem_by => @now 89 | ) 90 | Stripe::Coupons.reset! 91 | end 92 | end 93 | describe 'when it does exist on stripe.com already' do 94 | before do 95 | @coupon = Stripe::Coupon.construct_from({ 96 | :id => :gold25, 97 | }) 98 | Stripe::Coupon.stubs(:retrieve).returns(@coupon) 99 | end 100 | it 'first deletes the coupon then re-adds it' do 101 | @coupon.expects(:delete) 102 | Stripe::Coupon.expects(:create).with( 103 | :id => :gold25, 104 | :duration => 'repeating', 105 | :duration_in_months => 10, 106 | :amount_off => 100, 107 | :currency => 'USD', 108 | :max_redemptions => 3, 109 | :percent_off => 25, 110 | :redeem_by => @now 111 | ) 112 | Stripe::Coupons.reset! 113 | end 114 | end 115 | end 116 | 117 | end 118 | describe 'with missing mandatory values' do 119 | it 'raises an exception after configuring it' do 120 | _(proc {Stripe.coupon(:bad) {}}).must_raise Stripe::InvalidConfigurationError 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /test/dummy/README.rdoc: -------------------------------------------------------------------------------- 1 | == Welcome to Rails 2 | 3 | Rails is a web-application framework that includes everything needed to create 4 | database-backed web applications according to the Model-View-Control pattern. 5 | 6 | This pattern splits the view (also called the presentation) into "dumb" 7 | templates that are primarily responsible for inserting pre-built data in between 8 | HTML tags. The model contains the "smart" domain objects (such as Account, 9 | Product, Person, Post) that holds all the business logic and knows how to 10 | persist themselves to a database. The controller handles the incoming requests 11 | (such as Save New Account, Update Product, Show Post) by manipulating the model 12 | and directing data to the view. 13 | 14 | In Rails, the model is handled by what's called an object-relational mapping 15 | layer entitled Active Record. This layer allows you to present the data from 16 | database rows as objects and embellish these data objects with business logic 17 | methods. You can read more about Active Record in 18 | link:files/vendor/rails/activerecord/README.html. 19 | 20 | The controller and view are handled by the Action Pack, which handles both 21 | layers by its two parts: Action View and Action Controller. These two layers 22 | are bundled in a single package due to their heavy interdependence. This is 23 | unlike the relationship between the Active Record and Action Pack that is much 24 | more separate. Each of these packages can be used independently outside of 25 | Rails. You can read more about Action Pack in 26 | link:files/vendor/rails/actionpack/README.html. 27 | 28 | 29 | == Getting Started 30 | 31 | 1. At the command prompt, create a new Rails application: 32 | rails new myapp (where myapp is the application name) 33 | 34 | 2. Change directory to myapp and start the web server: 35 | cd myapp; rails server (run with --help for options) 36 | 37 | 3. Go to http://localhost:3000/ and you'll see: 38 | "Welcome aboard: You're riding Ruby on Rails!" 39 | 40 | 4. Follow the guidelines to start developing your application. You can find 41 | the following resources handy: 42 | 43 | * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html 44 | * Ruby on Rails Tutorial Book: http://www.railstutorial.org/ 45 | 46 | 47 | == Debugging Rails 48 | 49 | Sometimes your application goes wrong. Fortunately there are a lot of tools that 50 | will help you debug it and get it back on the rails. 51 | 52 | First area to check is the application log files. Have "tail -f" commands 53 | running on the server.log and development.log. Rails will automatically display 54 | debugging and runtime information to these files. Debugging info will also be 55 | shown in the browser on requests from 127.0.0.1. 56 | 57 | You can also log your own messages directly into the log file from your code 58 | using the Ruby logger class from inside your controllers. Example: 59 | 60 | class WeblogController < ActionController::Base 61 | def destroy 62 | @weblog = Weblog.find(params[:id]) 63 | @weblog.destroy 64 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") 65 | end 66 | end 67 | 68 | The result will be a message in your log file along the lines of: 69 | 70 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! 71 | 72 | More information on how to use the logger is at http://www.ruby-doc.org/core/ 73 | 74 | Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are 75 | several books available online as well: 76 | 77 | * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) 78 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) 79 | 80 | These two books will bring you up to speed on the Ruby language and also on 81 | programming in general. 82 | 83 | 84 | == Debugger 85 | 86 | Debugger support is available through the debugger command when you start your 87 | Mongrel or WEBrick server with --debugger. This means that you can break out of 88 | execution at any point in the code, investigate and change the model, and then, 89 | resume execution! You need to install ruby-debug to run the server in debugging 90 | mode. With gems, use sudo gem install ruby-debug. Example: 91 | 92 | class WeblogController < ActionController::Base 93 | def index 94 | @posts = Post.all 95 | debugger 96 | end 97 | end 98 | 99 | So the controller will accept the action, run the first line, then present you 100 | with a IRB prompt in the server window. Here you can do things like: 101 | 102 | >> @posts.inspect 103 | => "[#nil, "body"=>nil, "id"=>"1"}>, 105 | #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" 107 | >> @posts.first.title = "hello from a debugger" 108 | => "hello from a debugger" 109 | 110 | ...and even better, you can examine how your runtime objects actually work: 111 | 112 | >> f = @posts.first 113 | => #nil, "body"=>nil, "id"=>"1"}> 114 | >> f. 115 | Display all 152 possibilities? (y or n) 116 | 117 | Finally, when you're ready to resume execution, you can enter "cont". 118 | 119 | 120 | == Console 121 | 122 | The console is a Ruby shell, which allows you to interact with your 123 | application's domain model. Here you'll have all parts of the application 124 | configured, just like it is when the application is running. You can inspect 125 | domain models, change values, and save to the database. Starting the script 126 | without arguments will launch it in the development environment. 127 | 128 | To start the console, run rails console from the application 129 | directory. 130 | 131 | Options: 132 | 133 | * Passing the -s, --sandbox argument will rollback any modifications 134 | made to the database. 135 | * Passing an environment name as an argument will load the corresponding 136 | environment. Example: rails console production. 137 | 138 | To reload your controllers and models after launching the console run 139 | reload! 140 | 141 | More information about irb can be found at: 142 | link:http://www.rubycentral.org/pickaxe/irb.html 143 | 144 | 145 | == dbconsole 146 | 147 | You can go to the command line of your database directly through rails 148 | dbconsole. You would be connected to the database with the credentials 149 | defined in database.yml. Starting the script without arguments will connect you 150 | to the development database. Passing an argument will connect you to a different 151 | database, like rails dbconsole production. Currently works for MySQL, 152 | PostgreSQL and SQLite 3. 153 | 154 | == Description of Contents 155 | 156 | The default directory structure of a generated Ruby on Rails application: 157 | 158 | |-- app 159 | | |-- assets 160 | | |-- images 161 | | |-- javascripts 162 | | `-- stylesheets 163 | | |-- controllers 164 | | |-- helpers 165 | | |-- mailers 166 | | |-- models 167 | | `-- views 168 | | `-- layouts 169 | |-- config 170 | | |-- environments 171 | | |-- initializers 172 | | `-- locales 173 | |-- db 174 | |-- doc 175 | |-- lib 176 | | `-- tasks 177 | |-- log 178 | |-- public 179 | |-- script 180 | |-- test 181 | | |-- fixtures 182 | | |-- functional 183 | | |-- integration 184 | | |-- performance 185 | | `-- unit 186 | |-- tmp 187 | | |-- cache 188 | | |-- pids 189 | | |-- sessions 190 | | `-- sockets 191 | `-- vendor 192 | |-- assets 193 | `-- stylesheets 194 | `-- plugins 195 | 196 | app 197 | Holds all the code that's specific to this particular application. 198 | 199 | app/assets 200 | Contains subdirectories for images, stylesheets, and JavaScript files. 201 | 202 | app/controllers 203 | Holds controllers that should be named like weblogs_controller.rb for 204 | automated URL mapping. All controllers should descend from 205 | ApplicationController which itself descends from ActionController::Base. 206 | 207 | app/models 208 | Holds models that should be named like post.rb. Models descend from 209 | ActiveRecord::Base by default. 210 | 211 | app/views 212 | Holds the template files for the view that should be named like 213 | weblogs/index.html.erb for the WeblogsController#index action. All views use 214 | eRuby syntax by default. 215 | 216 | app/views/layouts 217 | Holds the template files for layouts to be used with views. This models the 218 | common header/footer method of wrapping views. In your views, define a layout 219 | using the layout :default and create a file named default.html.erb. 220 | Inside default.html.erb, call <% yield %> to render the view using this 221 | layout. 222 | 223 | app/helpers 224 | Holds view helpers that should be named like weblogs_helper.rb. These are 225 | generated for you automatically when using generators for controllers. 226 | Helpers can be used to wrap functionality for your views into methods. 227 | 228 | config 229 | Configuration files for the Rails environment, the routing map, the database, 230 | and other dependencies. 231 | 232 | db 233 | Contains the database schema in schema.rb. db/migrate contains all the 234 | sequence of Migrations for your schema. 235 | 236 | doc 237 | This directory is where your application documentation will be stored when 238 | generated using rake doc:app 239 | 240 | lib 241 | Application specific libraries. Basically, any kind of custom code that 242 | doesn't belong under controllers, models, or helpers. This directory is in 243 | the load path. 244 | 245 | public 246 | The directory available for the web server. Also contains the dispatchers and the 247 | default HTML files. This should be set as the DOCUMENT_ROOT of your web 248 | server. 249 | 250 | script 251 | Helper scripts for automation and generation. 252 | 253 | test 254 | Unit and functional tests along with fixtures. When using the rails generate 255 | command, template test files will be generated for you and placed in this 256 | directory. 257 | 258 | vendor 259 | External libraries that the application depends on. Also includes the plugins 260 | subdirectory. If the app has frozen rails, those gems also go here, under 261 | vendor/rails/. This directory is in the load path. 262 | -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | Dummy::Application.load_tasks 8 | -------------------------------------------------------------------------------- /test/dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // the compiled file. 9 | // 10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD 11 | // GO AFTER THE REQUIRES BELOW. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | *= require_tree . 13 | */ 14 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/apis_controller.rb: -------------------------------------------------------------------------------- 1 | ApiControllerKlass = defined?(ActionController::API) ? ActionController::API : ApplicationController 2 | 3 | class ApisController < ApiControllerKlass 4 | def index 5 | render json: :ok 6 | end 7 | end -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/stripes_controller.rb: -------------------------------------------------------------------------------- 1 | class StripesController < ApplicationController 2 | def new 3 | end 4 | end -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/mailers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansengming/stripe-rails/2e21a48c3aac5ef805423f349af15733c778325d/test/dummy/app/mailers/.gitkeep -------------------------------------------------------------------------------- /test/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansengming/stripe-rails/2e21a48c3aac5ef805423f349af15733c778325d/test/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /test/dummy/app/models/dummy/model_with_callbacks.rb: -------------------------------------------------------------------------------- 1 | class Dummy::ModelWithCallbacks 2 | end -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | <%= csrf_meta_tags %> 6 | 7 | 8 | 9 | <%= yield %> 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/dummy/app/views/stripes/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= stripe_javascript_tag (params[:version] || :v3) %> 2 | 3 | 8 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Dummy::Application 5 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails' 4 | require 'action_controller/railtie' 5 | require 'rails/test_unit/railtie' 6 | 7 | 8 | Bundler.require 9 | require "stripe-rails" 10 | 11 | module Dummy 12 | class Application < Rails::Application 13 | config.stripe.publishable_key = 'pk_test_XXXYYYZZZ' 14 | 15 | # Settings in config/environments/* take precedence over those specified here. 16 | # Application configuration should go into files in config/initializers 17 | # -- all .rb files in that directory are automatically loaded. 18 | 19 | # Custom directories with classes and modules you want to be autoloadable. 20 | # config.autoload_paths += %W(#{config.root}/extras) 21 | 22 | # Only load the plugins named here, in the order given (default is alphabetical). 23 | # :all can be used as a placeholder for all plugins not explicitly named. 24 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 25 | 26 | # Activate observers that should always be running. 27 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 28 | 29 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 30 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 31 | # config.time_zone = 'Central Time (US & Canada)' 32 | 33 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 34 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 35 | # config.i18n.default_locale = :de 36 | 37 | # Configure the default encoding used in templates for Ruby 1.9. 38 | config.encoding = "utf-8" 39 | 40 | # Configure sensitive parameters which will be filtered from the log file. 41 | config.filter_parameters += [:password] 42 | 43 | # Enable escaping HTML in JSON. 44 | config.active_support.escape_html_entities_in_json = true 45 | 46 | # Use SQL instead of Active Record's schema dumper when creating the database. 47 | # This is necessary if your schema can't be completely dumped by the schema dumper, 48 | # like if you have constraints or database-specific column types 49 | # config.active_record.schema_format = :sql 50 | 51 | # Enforce whitelist mode for mass assignment. 52 | # This will create an empty whitelist of attributes available for mass-assignment for all models 53 | # in your app. As such, your models will need to explicitly whitelist or blacklist accessible 54 | # parameters by using an attr_accessible or attr_protected declaration. 55 | 56 | # Enable the asset pipeline 57 | #config.assets.enabled = true 58 | 59 | # Version of your assets, change this if you want to expire all your assets 60 | #config.assets.version = '1.0' 61 | end 62 | end 63 | 64 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | gemfile = File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | if File.exist?(gemfile) 5 | ENV['BUNDLE_GEMFILE'] = gemfile 6 | require 'bundler' 7 | Bundler.setup 8 | end 9 | 10 | $:.unshift File.expand_path('../../../../lib', __FILE__) -------------------------------------------------------------------------------- /test/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | config.eager_load = false 3 | # Settings specified here will take precedence over those in config/application.rb 4 | 5 | # In the development environment your application's code is reloaded on 6 | # every request. This slows down response time but is perfect for development 7 | # since you don't have to restart the web server when you make code changes. 8 | config.cache_classes = false 9 | 10 | # Log error messages when you accidentally call methods on nil. 11 | config.whiny_nils = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Don't care if the mailer can't send 18 | # config.action_mailer.raise_delivery_errors = false 19 | 20 | # Print deprecation notices to the Rails logger 21 | config.active_support.deprecation = :log 22 | 23 | # Only use best-standards-support built into browsers 24 | config.action_dispatch.best_standards_support = :builtin 25 | 26 | # Raise exception on mass assignment protection for Active Record models 27 | # config.active_record.mass_assignment_sanitizer = :strict 28 | 29 | # Log the query plan for queries taking more than this (works 30 | # with SQLite, MySQL, and PostgreSQL) 31 | # config.active_record.auto_explain_threshold_in_seconds = 0.5 32 | 33 | # Do not compress assets 34 | # config.assets.compress = false 35 | 36 | # Expands the lines which load the assets 37 | # config.assets.debug = true 38 | end 39 | -------------------------------------------------------------------------------- /test/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | # Disable Rails's static asset server (Apache or nginx will already do this) 12 | config.serve_static_assets = false 13 | 14 | # Compress JavaScripts and CSS 15 | config.assets.compress = true 16 | 17 | # Don't fallback to assets pipeline if a precompiled asset is missed 18 | config.assets.compile = false 19 | 20 | # Generate digests for assets URLs 21 | config.assets.digest = true 22 | 23 | # Defaults to nil and saved in location specified by config.assets.prefix 24 | # config.assets.manifest = YOUR_PATH 25 | 26 | # Specifies the header that your server uses for sending files 27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 29 | 30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 31 | # config.force_ssl = true 32 | 33 | # See everything in the log (default is :info) 34 | # config.log_level = :debug 35 | 36 | # Prepend all log lines with the following tags 37 | # config.log_tags = [ :subdomain, :uuid ] 38 | 39 | # Use a different logger for distributed setups 40 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 41 | 42 | # Use a different cache store in production 43 | # config.cache_store = :mem_cache_store 44 | 45 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 46 | # config.action_controller.asset_host = "http://assets.example.com" 47 | 48 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) 49 | # config.assets.precompile += %w( search.js ) 50 | 51 | # Disable delivery errors, bad email addresses will be ignored 52 | # config.action_mailer.raise_delivery_errors = false 53 | 54 | # Enable threaded mode 55 | # config.threadsafe! 56 | 57 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 58 | # the I18n.default_locale when a translation can not be found) 59 | config.i18n.fallbacks = true 60 | 61 | # Send deprecation notices to registered listeners 62 | config.active_support.deprecation = :notify 63 | 64 | # Log the query plan for queries taking more than this (works 65 | # with SQLite, MySQL, and PostgreSQL) 66 | # config.active_record.auto_explain_threshold_in_seconds = 0.5 67 | end 68 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.serve_static_assets = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Raise exceptions instead of rendering exception templates 18 | config.action_dispatch.show_exceptions = false 19 | 20 | # Disable request forgery protection in test environment 21 | config.action_controller.allow_forgery_protection = false 22 | 23 | # Tell Action Mailer not to deliver emails to the real world. 24 | # The :test delivery method accumulates sent emails in the 25 | # ActionMailer::Base.deliveries array. 26 | #config.action_mailer.delivery_method = :test 27 | 28 | # Raise exception on mass assignment protection for Active Record models 29 | #config.active_record.mass_assignment_sanitizer = :strict 30 | 31 | # Print deprecation notices to the stderr 32 | config.active_support.deprecation = :stderr 33 | 34 | # Set eager_load to false as it's unneeded 35 | config.eager_load = false 36 | end 37 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | # 12 | # These inflection rules are supported but not enabled by default: 13 | # ActiveSupport::Inflector.inflections do |inflect| 14 | # inflect.acronym 'RESTful' 15 | # end 16 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | Dummy::Application.config.secret_token = '5e507bc08a89b8b07e1ce08c932ec89771be6500e4d2d64c38164d2405d27578c33edc2982dd9ee67d4c49e0dd75992ac2ed6eb120bea0e16516549466906d06' 8 | Dummy::Application.config.secret_key_base = 'a87ae91f28326ebe41f7ce9b7b799bb9' 9 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Dummy::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # Disable root element in JSON by default. 12 | ActiveSupport.on_load(:active_record) do 13 | self.include_root_in_json = false 14 | end 15 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.routes.draw do 2 | resources :stripes, only: :new 3 | resources :apis, only: :index 4 | # The priority is based upon order of creation: 5 | # first created -> highest priority. 6 | 7 | # Sample of regular route: 8 | # match 'products/:id' => 'catalog#view' 9 | # Keep in mind you can assign values other than :controller and :action 10 | 11 | # Sample of named route: 12 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 13 | # This route can be invoked with purchase_url(:id => product.id) 14 | 15 | # Sample resource route (maps HTTP verbs to controller actions automatically): 16 | # resources :products 17 | 18 | # Sample resource route with options: 19 | # resources :products do 20 | # member do 21 | # get 'short' 22 | # post 'toggle' 23 | # end 24 | # 25 | # collection do 26 | # get 'sold' 27 | # end 28 | # end 29 | 30 | # Sample resource route with sub-resources: 31 | # resources :products do 32 | # resources :comments, :sales 33 | # resource :seller 34 | # end 35 | 36 | # Sample resource route with more complex sub-resources 37 | # resources :products do 38 | # resources :comments 39 | # resources :sales do 40 | # get 'recent', :on => :collection 41 | # end 42 | # end 43 | 44 | # Sample resource route within a namespace: 45 | # namespace :admin do 46 | # # Directs /admin/products/* to Admin::ProductsController 47 | # # (app/controllers/admin/products_controller.rb) 48 | # resources :products 49 | # end 50 | 51 | # You can have the root of your site routed with "root" 52 | # just remember to delete public/index.html. 53 | # root :to => 'welcome#index' 54 | 55 | # See how all your routes lay out with "rake routes" 56 | 57 | # This is a legacy wild controller route that's not recommended for RESTful applications. 58 | # Note: This route will make all actions in every controller accessible via GET requests. 59 | # match ':controller(/:action(/:id))(.:format)' 60 | end 61 | -------------------------------------------------------------------------------- /test/dummy/config/stripe/plans.rb: -------------------------------------------------------------------------------- 1 | Stripe.plan :gold do |plan| 2 | plan.name = 'Solid Gold' 3 | plan.amount = 699 4 | plan.interval = 'month' 5 | end 6 | 7 | Stripe.plan "Solid Gold".to_sym do |plan| 8 | plan.constant_name = 'SOLID_GOLD' 9 | plan.name = 'Solid Gold' 10 | plan.amount = 699 11 | plan.interval = 'month' 12 | end 13 | 14 | Stripe.plan :alternative_currency do |plan| 15 | plan.name = 'Alternative Currency' 16 | plan.amount = 699 17 | plan.interval = 'month' 18 | plan.currency = 'cad' 19 | end 20 | 21 | Stripe.plan :metered do |plan| 22 | plan.name = 'Metered' 23 | plan.amount = 699 24 | plan.interval = 'month' 25 | plan.usage_type = 'metered' 26 | plan.aggregate_usage = 'max' 27 | plan.billing_scheme = 'per_unit' 28 | end 29 | 30 | Stripe.plan :tiered do |plan| 31 | plan.name = 'Tiered' 32 | plan.aggregate_usage = 'max' 33 | plan.billing_scheme = 'tiered' 34 | # interval must be either 'day', 'week', 'month' or 'year' 35 | plan.interval = 'month' 36 | plan.interval_count = 1 37 | plan.tiers = [ 38 | { 39 | unit_amount: 1500, 40 | up_to: 10 41 | }, 42 | { 43 | unit_amount: 1000, 44 | up_to: 'inf' 45 | } 46 | ] 47 | plan.tiers_mode = 'graduated' 48 | plan.usage_type = 'metered' 49 | end 50 | -------------------------------------------------------------------------------- /test/dummy/config/stripe/prices.rb: -------------------------------------------------------------------------------- 1 | Stripe.price :gold do |price| 2 | price.name = 'Solid Gold' 3 | price.unit_amount = 699 4 | price.recurring = { 5 | interval: 'month' 6 | } 7 | end 8 | 9 | Stripe.price :taxable_gold do |price| 10 | price.name = 'Taxable Gold' 11 | price.unit_amount = 699 12 | price.tax_behavior = 'exclusive' 13 | end 14 | 15 | Stripe.price "Solid Gold".to_sym do |price| 16 | price.constant_name = 'SOLID_GOLD' 17 | price.name = 'Solid Gold' 18 | price.unit_amount = 699 19 | price.recurring = { 20 | interval: 'month' 21 | } 22 | end 23 | 24 | Stripe.price :alternative_currency do |price| 25 | price.name = 'Alternative Currency' 26 | price.unit_amount = 699 27 | price.recurring = { 28 | interval: 'month' 29 | } 30 | price.currency = 'cad' 31 | end 32 | 33 | Stripe.price :metered do |price| 34 | price.name = 'Metered' 35 | price.unit_amount = 699 36 | price.recurring = { 37 | interval: 'month', 38 | aggregate_usage: 'max', 39 | usage_type: 'metered' 40 | } 41 | price.billing_scheme = 'per_unit' 42 | end 43 | 44 | Stripe.price :tiered do |price| 45 | price.name = 'Tiered' 46 | price.billing_scheme = 'tiered' 47 | # interval must be either 'day', 'week', 'month' or 'year' 48 | price.recurring = { 49 | interval: 'month', 50 | interval_count: 2, 51 | aggregate_usage: 'max', 52 | usage_type: 'metered' 53 | } 54 | price.tiers = [ 55 | { 56 | unit_amount: 1500, 57 | up_to: 10 58 | }, 59 | { 60 | unit_amount: 1000, 61 | up_to: 'inf' 62 | } 63 | ] 64 | price.tiers_mode = 'graduated' 65 | end 66 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansengming/stripe-rails/2e21a48c3aac5ef805423f349af15733c778325d/test/dummy/lib/assets/.gitkeep -------------------------------------------------------------------------------- /test/dummy/lib/dummy/module_with_callbacks.rb: -------------------------------------------------------------------------------- 1 | class Dummy::ModuleWithCallbacks 2 | end -------------------------------------------------------------------------------- /test/dummy/log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansengming/stripe-rails/2e21a48c3aac5ef805423f349af15733c778325d/test/dummy/log/.gitkeep -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tansengming/stripe-rails/2e21a48c3aac5ef805423f349af15733c778325d/test/dummy/public/favicon.ico -------------------------------------------------------------------------------- /test/dummy/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /test/dummy_apis_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ApisController do 4 | include Rack::Test::Methods 5 | 6 | let(:app) { Rails.application } 7 | before do 8 | header 'Accept', 'application/json' 9 | header 'Content-Type', 'application/json' 10 | end 11 | 12 | describe 'the apis interface' do 13 | subject { get '/apis/' } 14 | 15 | it { _(subject).must_be :ok? } 16 | end 17 | end -------------------------------------------------------------------------------- /test/dummy_stripes_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class DummyStripesControllerSpec < ApplicationSystemTestCase 4 | setup do 5 | Dummy::Application.configure do 6 | config.stripe.publishable_key = 'pk_test_XXXYYYZZZ' 7 | end 8 | end 9 | 10 | test "loading the default javascript helper" do 11 | visit new_stripe_url 12 | assert_text 'This page tests the loading and initialization of Stripe JS' 13 | end 14 | 15 | test "loading the v2 version of the javascript helper" do 16 | visit new_stripe_url(version: 'v2') 17 | assert_text 'This page tests the loading and initialization of Stripe JS' 18 | end 19 | end -------------------------------------------------------------------------------- /test/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_version": "2017-02-14", 3 | "created": 1234567890, 4 | "customer_email": "", 5 | "data": { 6 | "object": { 7 | } 8 | }, 9 | "id": "evt_19tLKfDSlTMT26MkKD3pohqX", 10 | "livemode": false, 11 | "object": "event", 12 | "pending_webhooks": 0, 13 | "recipient_best_description": "", 14 | "request": { 15 | "id": "req_ArJ9y3DcEShbw2", 16 | "idempotency_key": null 17 | }, 18 | "type": "invoice.payment_succeeded" 19 | } -------------------------------------------------------------------------------- /test/events_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Stripe::EventsController do 4 | include Rack::Test::Methods 5 | 6 | let(:app) { Rails.application } 7 | before do 8 | header 'Accept', 'application/json' 9 | header 'Content-Type', 'application/json' 10 | end 11 | 12 | describe 'the events interface' do 13 | subject { post '/stripe/events', params.to_json } 14 | 15 | before { stripe_events_stub } 16 | 17 | let(:params) { 18 | { 19 | id: 'evt_00000000000000', 20 | type: 'customer.updated', 21 | data: {object: 'customer'}, 22 | } 23 | } 24 | let(:stripe_events_stub) do 25 | stub_request(:get, "https://api.stripe.com/v1/events/evt_00000000000000"). 26 | to_return(status: 200, body: Stripe::Event.construct_from(params).to_json, headers: {}) 27 | end 28 | 29 | it { _(subject).must_be :ok? } 30 | 31 | it 'should call the stripe_events_stub' do 32 | subject 33 | assert_requested(stripe_events_stub) 34 | end 35 | 36 | describe 'when signing_secret is nil' do 37 | before do 38 | header 'Stripe-Signature', 't=1537832721,v1=123,v0=123' 39 | app.config.stripe.signing_secret = nil 40 | end 41 | 42 | it 'should call the stripe_events_stub' do 43 | subject 44 | assert_requested(stripe_events_stub) 45 | end 46 | end 47 | end 48 | 49 | describe 'signed webhooks' do 50 | before do 51 | header 'Stripe-Signature', 't=1537832721,v1=123,v0=123' 52 | app.config.stripe.signing_secret = 'SECRET' 53 | end 54 | 55 | after { app.config.stripe.signing_secret = nil } 56 | 57 | let(:params) { 58 | { 59 | id: 'evt_00000000000001', 60 | type: 'customer.updated', 61 | data: { 62 | object: 'customer', 63 | fingerprint: 'xxxyyyzzz' 64 | }, 65 | } 66 | } 67 | 68 | subject { post '/stripe/events', params.to_json } 69 | 70 | it 'returns bad_request when invalid' do 71 | Stripe::Webhook.expects(:construct_event).raises(Stripe::SignatureVerificationError.new('msg', 'sig_header')) 72 | _(subject).must_be :bad_request? 73 | end 74 | 75 | it 'returns ok when valid' do 76 | Stripe::Webhook.expects(:construct_event).returns(Stripe::Event.construct_from(params)) 77 | _(subject).must_be :ok? 78 | end 79 | end 80 | 81 | describe 'multiple signed webhooks' do 82 | before do 83 | header 'Stripe-Signature', 't=1537832721,v1=123,v0=123' 84 | app.config.stripe.signing_secrets = ['SECRET1', 'SECRET2'] 85 | end 86 | 87 | after { app.config.stripe.signing_secrets = nil } 88 | 89 | let(:params) { 90 | { 91 | id: 'evt_00000000000001', 92 | type: 'customer.updated', 93 | data: { 94 | object: 'customer', 95 | fingerprint: 'xxxyyyzzz' 96 | }, 97 | } 98 | } 99 | 100 | subject { post '/stripe/events', params.to_json } 101 | 102 | it 'returns bad_request when invalid' do 103 | Stripe::Webhook.expects(:construct_event).twice.raises(Stripe::SignatureVerificationError.new('msg', 'sig_header')) 104 | _(subject).must_be :bad_request? 105 | end 106 | 107 | it 'returns ok when valid' do 108 | Stripe::Webhook.expects(:construct_event).returns(Stripe::Event.construct_from(params)) 109 | _(subject).must_be :ok? 110 | end 111 | end 112 | end -------------------------------------------------------------------------------- /test/fixtures/stripe_plans.json: -------------------------------------------------------------------------------- 1 | { 2 | "object": "list", 3 | "count": 0, 4 | "data": [], 5 | "has_more": false, 6 | "url": "/v1/plans" 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/stripe_plans_headers.json: -------------------------------------------------------------------------------- 1 | { 2 | "Server": "nginx", 3 | "Date": "Wed, 21 Mar 2018 20:04:56 GMT", 4 | "Content-Type": "application/json", 5 | "Content-Length": "5807", 6 | "Connection": "close", 7 | "Access-Control-Allow-Credentials": "true", 8 | "Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS, DELETE", 9 | "Access-Control-Allow-Origin": "*", 10 | "Access-Control-Expose-Headers": "Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required", 11 | "Access-Control-Max-Age": "300", 12 | "Cache-Control": "no-cache, no-store", 13 | "Request-Id": "req_UCuedZo4sKqUXn", 14 | "Stripe-Version": "2018-02-05", 15 | "Strict-Transport-Security": "max-age=31556926; includeSubDomains; preload" 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/stripe_plans_headers_2017.json: -------------------------------------------------------------------------------- 1 | { 2 | "Server": "nginx", 3 | "Date": "Wed, 21 Mar 2018 20:04:56 GMT", 4 | "Content-Type": "application/json", 5 | "Content-Length": "5807", 6 | "Connection": "close", 7 | "Access-Control-Allow-Credentials": "true", 8 | "Access-Control-Allow-Methods": "GET, POST, HEAD, OPTIONS, DELETE", 9 | "Access-Control-Allow-Origin": "*", 10 | "Access-Control-Expose-Headers": "Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required", 11 | "Access-Control-Max-Age": "300", 12 | "Cache-Control": "no-cache, no-store", 13 | "Request-Id": "req_UCuedZo4sKqUXn", 14 | "Stripe-Version": "2017-02-05", 15 | "Strict-Transport-Security": "max-age=31556926; includeSubDomains; preload" 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/stripe_prices.json: -------------------------------------------------------------------------------- 1 | { 2 | "object": "list", 3 | "count": 0, 4 | "data": [], 5 | "has_more": false, 6 | "url": "/v1/prices" 7 | } 8 | -------------------------------------------------------------------------------- /test/invoice.json: -------------------------------------------------------------------------------- 1 | { 2 | "amount_due": 0, 3 | "application_fee": 0, 4 | "attempt_count": 0, 5 | "attempted": true, 6 | "billing": "", 7 | "charge": "", 8 | "closed": true, 9 | "currency": "usd", 10 | "customer": "cus_ADmuABetLS15eF", 11 | "date": 1234567890, 12 | "description": "", 13 | "discount": { 14 | }, 15 | "due_date": 1234567890, 16 | "ending_balance": 0, 17 | "forgiven": false, 18 | "id": "in_19zuuiDSlTMT26Mk1XitxqCb", 19 | "lines": { 20 | "data": [ 21 | { 22 | "amount": 2000, 23 | "currency": "usd", 24 | "description": null, 25 | "discountable": true, 26 | "id": "sub_AKa4uss8vCMAZC", 27 | "livemode": true, 28 | "metadata": { 29 | }, 30 | "object": "line_item", 31 | "period": { 32 | "end": 1495403592, 33 | "start": 1492811592 34 | }, 35 | "plan": { 36 | "amount": 2000, 37 | "created": 1488566449, 38 | "currency": "usd", 39 | "id": "gold", 40 | "interval": "month", 41 | "interval_count": 1, 42 | "livemode": false, 43 | "metadata": { 44 | }, 45 | "name": "Gold Special", 46 | "object": "plan", 47 | "statement_descriptor": null, 48 | "trial_period_days": null 49 | }, 50 | "proration": false, 51 | "quantity": 1, 52 | "subscription": null, 53 | "subscription_item": "si_19zuuiDSlTMT26Mk9HayBPe9", 54 | "type": "subscription" 55 | } 56 | ], 57 | "object": "list", 58 | "total_count": 1, 59 | "url": "/v1/invoices/in_19zuuiDSlTMT26Mk1XitxqCb/lines" 60 | }, 61 | "livemode": false, 62 | "metadata": { 63 | }, 64 | "next_payment_attempt": 1234567890, 65 | "number": "", 66 | "object": "invoice", 67 | "paid": true, 68 | "period_end": 1234567890, 69 | "period_start": 1234567890, 70 | "receipt_number": "", 71 | "send_next_at": 1234567890, 72 | "starting_balance": 0, 73 | "statement_descriptor": "", 74 | "subscription": "", 75 | "subscription_proration_date": 0, 76 | "subtotal": 0, 77 | "tax": 0, 78 | "tax_percent": 0.0, 79 | "total": 6999, 80 | "webhooks_delivered_at": 1234567890 81 | } -------------------------------------------------------------------------------- /test/javascript_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Stripe::JavascriptHelper do 4 | before { Rails.application.config.stripe.publishable_key = 'pub_xxxx' } 5 | let(:controller) { ActionView::TestCase::TestController.new } 6 | let(:view) { controller.view_context } 7 | 8 | describe '#stripe_javascript_tag' do 9 | describe 'when no options are passed' do 10 | it 'should default to v3' do 11 | _(view.stripe_javascript_tag).must_include 'https://js.stripe.com/v3/' 12 | end 13 | end 14 | 15 | describe 'when the v2 option is passed' do 16 | it 'should default to v2' do 17 | _(view.stripe_javascript_tag(:v2)).must_include 'https://js.stripe.com/v2/' 18 | end 19 | end 20 | 21 | describe 'when the debug flag is enabled' do 22 | before { Rails.application.config.stripe.debug_js = true } 23 | after { Rails.application.config.stripe.debug_js = false } 24 | it 'should render the debug js' do 25 | _(view.stripe_javascript_tag(:v1)).must_include 'https://js.stripe.com/v1/stripe-debug.js' 26 | end 27 | 28 | describe 'when v3 is selected' do 29 | it 'should not render debug js' do 30 | _(view.stripe_javascript_tag(:v3)).wont_include 'https://js.stripe.com/v1/stripe-debug.js' 31 | end 32 | end 33 | end 34 | end 35 | 36 | describe "render :partial => 'stripe/js'" do 37 | subject { view.render :partial => 'stripe/js' } 38 | 39 | it 'should render correctly' do 40 | _(subject).must_include 'https://js.stripe.com/v3/' 41 | end 42 | end 43 | 44 | describe "render :partial => 'stripe/js', local: {stripe_js_version: 'v2'}" do 45 | subject { view.render :partial => 'stripe/js', locals: {stripe_js_version: 'v2'} } 46 | 47 | it 'should render correctly' do 48 | _(subject).must_include 'https://js.stripe.com/v2/' 49 | end 50 | end 51 | 52 | describe '#stripe_elements_tag' do 53 | describe 'when no options are passed' do 54 | it 'should display the form' do 55 | _(view.stripe_elements_tag( 56 | submit_path: '/charge', 57 | )).must_include 'Credit or debit card' 58 | end 59 | end 60 | 61 | describe 'with options' do 62 | describe 'without default js' do 63 | it 'wont include the default script tag' do 64 | _(view.stripe_elements_tag( 65 | submit_path: '/charge', 66 | js_path: 'another/path' 67 | )).wont_include '