├── .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 | [](https://badge.fury.io/rb/stripe-rails)
3 | [](https://travis-ci.org/tansengming/stripe-rails)
4 | [](https://codeclimate.com/github/tansengming/stripe-rails)
5 | [](https://codeclimate.com/github/tansengming/stripe-rails/coverage)
6 | [](#)
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 | 
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 | 
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 '