stripe-rails 2.0.0.pre → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebffd01386755b4675cdbdffc65daeb686342bde1e57ac21f4a7da025fe0d6ab
4
- data.tar.gz: 69341ad24b03113c1f0ca9de29cd7e644ac4ccac33decc972c205ad86010baa7
3
+ metadata.gz: 4e1778aaa249a48297d0f7c95ff5d893d3dcda21107d608ab6b262cf500b2a70
4
+ data.tar.gz: babf50def492764aba044d993a2acd8338161485dc96ae28eae8bb88dc0bc15b
5
5
  SHA512:
6
- metadata.gz: 69409504a7e36670c46cbb1330b354b6e0ca78846eb5e665ef4cfa6adb5d4f247d1d39a24955edd0a61732de32c6706cc486f9c83c0770fbd8a5be71acff2cb7
7
- data.tar.gz: 60c5e212ff5fd64e657148b6d55d620e595a323220423f09eabd626c79d0ffc0ee027214c54e857ad4f8c68c58f120cc068ca5705c6947262e685f92b7708b51
6
+ metadata.gz: 4a656096e3c1fa2988387a31b535dd879418466661e805a3351bfd6b0728c67bd9880146359d840fcffb18b50dfa940f207fb4916eede34c38685469b9060b8e
7
+ data.tar.gz: 812f9988e7bbc66f2eff2c433cf918c05732799bf5ecd5d9b161fa954ee7ec38a9a9715ac43332bd27782978b1c1142fcb531cd3ef053a9d61162a8822e969d5
@@ -1,4 +1,15 @@
1
1
  <!--
2
- Please give us ~1 week to get back to you.
2
+ Thanks a bunch for helping out with the project!
3
+
4
+ Please remember to,
5
+
6
+ 1. Add tests if they do not exist, fix em if they are breaking
7
+ 2. Fix any issues that are stopping Code Climate from passing
8
+ 2. Add a short description of the feature and tag yourself on Changelog.md
9
+
10
+ That's it!
11
+
12
+ Please give me ~1 week to get back to you.
13
+
3
14
  If you'd like to receive occasional updates, sign up for our newsletter at http://tinyletter.com/stripe-rails
4
15
  -->
@@ -15,7 +15,7 @@ jobs:
15
15
  strategy:
16
16
  matrix:
17
17
  ruby: [2.5.x, 2.6.x, 2.7.x]
18
- gemfile: [Gemfile, gemfiles/rails51.gemfile, gemfiles/rails52.gemfile]
18
+ gemfile: [Gemfile, gemfiles/rails60.gemfile, gemfiles/rails52.gemfile, gemfiles/rails51.gemfile]
19
19
  steps:
20
20
  - uses: actions/checkout@v1
21
21
  - name: Set up Ruby
@@ -33,6 +33,9 @@ jobs:
33
33
  CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
34
34
  RUBY_VERSION: ${{ matrix.ruby }}
35
35
  run: |
36
+ if [ $RUBY_VERSION == "2.5.x" ] ;
37
+ then gem install bundler
38
+ fi
36
39
  bundle install --jobs 4 --retry 3
37
40
  bundle exec rake
38
41
  if [ `basename $BUNDLE_GEMFILE` == "Gemfile" ] && [ $RUBY_VERSION == "2.7.x" ] && [ ! -z ${CC_TEST_REPORTER_ID} ] ;
data/Changelog.md CHANGED
@@ -1,6 +1,33 @@
1
1
  ## Unreleased
2
2
 
3
- -
3
+ -
4
+
5
+ ## 2.3.0 (2021-03-08)
6
+
7
+ - Adds testing for Rails 6.0
8
+ - Add `name` attribute to coupons. Thanks @ZilvinasKucinskas!
9
+
10
+ ## 2.2.1 (2020-12-22)
11
+
12
+ - Add payment_intent.requires_action callback, thanks @VadimLeader.
13
+
14
+ ## 2.2.0 (2020-12-06)
15
+
16
+ - Add Prices as a configuration object. Thanks @jamesprior!
17
+
18
+ ## 2.1.0 (2020-10-18)
19
+
20
+ - Added option to ignore missing API key and don't show any warning. Thanks @ndbroadbent!
21
+ - Handle passing nil to signing_secret= and add tests. Thanks @martron!
22
+
23
+ ## 2.0.0 (2020-09-18)
24
+
25
+ - Everything from on the 2.0.0.pre release
26
+ - includes changes from the 1.10.2 release
27
+
28
+ ## 1.10.2 (2020-09-18)
29
+
30
+ - adds missing callback `invoice.paid`. Thanks @SyborgStudios.
4
31
 
5
32
  ## 2.0.0.pre (2020-05-29)
6
33
 
data/README.md CHANGED
@@ -9,7 +9,7 @@ This gem can help your rails application integrate with Stripe in the following
9
9
 
10
10
  * manage stripe configurations in a single place.
11
11
  * makes stripe.js available from the asset pipeline.
12
- * manage plans and coupons from within your app.
12
+ * manage product, prices, plans and coupons from within your app.
13
13
  * painlessly receive and validate webhooks from stripe.
14
14
 
15
15
  [📫 Sign up for the Newsletter](http://tinyletter.com/stripe-rails) to receive occasional updates.
@@ -137,6 +137,14 @@ you prefer to environment variables, you can also set `STRIPE_PUBLISHABLE_KEY`:
137
137
  export STRIPE_PUBLISHABLE_KEY=pk_test_XXXYYYZZZ
138
138
  ```
139
139
 
140
+ 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`:
141
+
142
+ ```ruby
143
+ # config/environments/production.rb
144
+ # ...
145
+ config.stripe.ignore_missing_secret_key = true
146
+ ```
147
+
140
148
  ### Manually set your API version (optional)
141
149
 
142
150
  If you need to test a new API version in development, you can override the version number manually.
@@ -162,6 +170,7 @@ this will generate the configuration files containing your plan and coupon defin
162
170
  ```console
163
171
  create config/stripe/products.rb
164
172
  create config/stripe/plans.rb
173
+ create config/stripe/prices.rb
165
174
  create config/stripe/coupons.rb
166
175
  ```
167
176
 
@@ -259,17 +268,46 @@ Stripe.product :primo do |product|
259
268
  end
260
269
  ```
261
270
 
262
- To upload your plans and coupons onto stripe.com, run:
271
+ And Prices:
272
+
273
+ ```ruby
274
+ Stripe.price :bronze do |price|
275
+ # Use an existing product id to prevent a new product from
276
+ # getting created
277
+ price.product_id = Stripe::Products::PRIMO.id
278
+ price.billing_scheme = 'tiered'
279
+ price.recurring = {
280
+ interval: 'month',
281
+ usage_type: 'metered'
282
+ }
283
+
284
+ # Use graduated pricing tiers
285
+ # ref: https://stripe.com/docs/api/prices/object#price_object-tiers
286
+ price.tiers = [
287
+ {
288
+ unit_amount: 1500,
289
+ up_to: 10
290
+ },
291
+ {
292
+ unit_amount: 1000,
293
+ up_to: 'inf'
294
+ }
295
+ ]
296
+ price.tiers_mode = 'graduated'
297
+ end
298
+ ````
299
+
300
+ To upload your plans, products, prices and coupons onto stripe.com, run:
263
301
 
264
302
  ```sh
265
303
  rake stripe:prepare
266
304
  ```
267
305
 
268
- This will create any plans and coupons that do not currently exist, and treat as a NOOP any
269
- plans that do, so you can run this command safely as many times as you wish. Now you can
270
- use any of these plans in your application.
306
+ This will create any plans, products, prices and coupons that do not currently exist, and treat as a NOOP any
307
+ objects that already exist, so you can run this command safely as many times as you wish. Now you can
308
+ use any of these objects in your application.
271
309
 
272
- NOTE: You must destroy plans manually from your stripe dashboard.
310
+ NOTE: You must destroy plans and prices manually from your stripe dashboard.
273
311
 
274
312
  ## Stripe Elements
275
313
 
data/Rakefile CHANGED
File without changes
data/config/routes.rb CHANGED
@@ -5,6 +5,5 @@ Rails.application.routes.draw do
5
5
  end
6
6
 
7
7
  Stripe::Engine.routes.draw do
8
- resource :ping, only: :show
9
8
  resources :events, only: :create
10
9
  end
@@ -0,0 +1,19 @@
1
+ source :rubygems
2
+
3
+ gem 'rails', '~> 6.0.0'
4
+
5
+ gem 'rake'
6
+ gem 'responders'
7
+ gem 'stripe'
8
+
9
+ group :test do
10
+ gem 'mocha'
11
+ gem 'simplecov', require: false
12
+ gem 'stripe-ruby-mock'
13
+ gem 'webmock'
14
+ # Required for system tests
15
+ gem 'capybara'
16
+ gem 'puma'
17
+ gem 'selenium-webdriver'
18
+ gem 'webdrivers'
19
+ end
@@ -6,6 +6,7 @@ module Stripe
6
6
  def copy_plans_file
7
7
  copy_file "products.rb", "config/stripe/products.rb"
8
8
  copy_file "plans.rb", "config/stripe/plans.rb"
9
+ copy_file "prices.rb", "config/stripe/prices.rb"
9
10
  copy_file "coupons.rb", "config/stripe/coupons.rb"
10
11
  end
11
12
  end
@@ -0,0 +1,46 @@
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 #=> #<Stripe::Price:0x3584 id=price_0000sdfs2qfsdf>...
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,6 +47,7 @@ module Stripe
47
47
  callback 'invoice.created'
48
48
  callback 'invoice.finalized'
49
49
  callback 'invoice.marked_uncollectible'
50
+ callback 'invoice.paid'
50
51
  callback 'invoice.payment_action_required'
51
52
  callback 'invoice.payment_failed'
52
53
  callback 'invoice.payment_succeeded'
@@ -67,6 +68,7 @@ module Stripe
67
68
  callback 'payment_intent.created'
68
69
  callback 'payment_intent.payment_failed'
69
70
  callback 'payment_intent.processing'
71
+ callback 'payment_intent.requires_action'
70
72
  callback 'payment_intent.succeeded'
71
73
  callback 'payment_method.attached'
72
74
  callback 'payment_method.card_automatically_updated'
@@ -3,7 +3,7 @@ module Stripe
3
3
  include ConfigurationBuilder
4
4
 
5
5
  configuration_for :coupon do
6
- attr_accessor :duration, :amount_off, :currency, :duration_in_months, :max_redemptions, :percent_off, :redeem_by
6
+ attr_accessor :name, :duration, :amount_off, :currency, :duration_in_months, :max_redemptions, :percent_off, :redeem_by
7
7
 
8
8
  validates_presence_of :id, :duration
9
9
  validates_presence_of :duration_in_months, :if => :repeating?
@@ -25,6 +25,7 @@ module Stripe
25
25
 
26
26
  def create_options
27
27
  {
28
+ :name => name,
28
29
  :duration => duration,
29
30
  :percent_off => percent_off,
30
31
  :amount_off => amount_off,
data/lib/stripe/engine.rb CHANGED
@@ -8,12 +8,12 @@ module Stripe
8
8
  attr_accessor :testing
9
9
  end
10
10
 
11
- stripe_config = config.stripe = Struct.new(:api_base, :api_version, :secret_key, :verify_ssl_certs, :signing_secret, :signing_secrets, :publishable_key, :endpoint, :debug_js, :auto_mount, :eager_load, :open_timeout, :read_timeout) do
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
12
  # for backwards compatibility treat signing_secret as an alias for signing_secrets
13
13
  def signing_secret=(value)
14
- self.signing_secrets = Array(value)
14
+ self.signing_secrets = value.nil? ? value : Array(value)
15
15
  end
16
-
16
+
17
17
  def signing_secret
18
18
  self.signing_secrets && self.signing_secrets.first
19
19
  end
@@ -43,7 +43,7 @@ module Stripe
43
43
  end
44
44
  secret_key = app.config.stripe.secret_key
45
45
  Stripe.api_key = secret_key unless secret_key.nil?
46
- $stderr.puts <<-MSG unless Stripe.api_key
46
+ $stderr.puts <<-MSG unless Stripe.api_key || app.config.stripe.ignore_missing_secret_key
47
47
  No stripe.com API key was configured for environment #{::Rails.env}! this application will be
48
48
  unable to interact with stripe.com. You can set your API key with either the environment
49
49
  variable `STRIPE_SECRET_KEY` (recommended) or by setting `config.stripe.secret_key` in your
@@ -89,7 +89,7 @@ environment file directly.
89
89
  end
90
90
 
91
91
  initializer 'stripe.plans_and_coupons' do |app|
92
- for configuration in %w(products plans coupons)
92
+ for configuration in %w(products plans coupons prices)
93
93
  path = app.root.join("config/stripe/#{configuration}.rb")
94
94
  load path if path.exist?
95
95
  end
@@ -0,0 +1,189 @@
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
+ :tiers,
20
+ :tiers_mode,
21
+ :transform_quantity,
22
+ :type,
23
+ :unit_amount
24
+
25
+ validates_presence_of :id, :currency
26
+ validates_presence_of :unit_amount, unless: ->(p) { p.billing_scheme == 'tiered' }
27
+ validates_absence_of :transform_quantity, if: ->(p) { p.billing_scheme == 'tiered' }
28
+ validates_presence_of :tiers_mode, :tiers, if: ->(p) { p.billing_scheme == 'tiered' }
29
+
30
+ validates_numericality_of :recurring_interval_count, allow_nil: true
31
+
32
+ validates_inclusion_of :recurring_interval,
33
+ in: VALID_TIME_UNITS.collect(&:to_s),
34
+ message: "'%{value}' is not one of #{VALID_TIME_UNITS.to_sentence(last_word_connector: ', or ')}",
35
+ if: ->(p) { p.recurring.present? }
36
+
37
+ validates :statement_descriptor, length: { maximum: 22 }
38
+
39
+ validates :active, inclusion: { in: [true, false] }, allow_nil: true
40
+ validates :billing_scheme, inclusion: { in: %w{ per_unit tiered } }, allow_nil: true
41
+ validates :recurring_aggregate_usage, inclusion: { in: %w{ sum last_during_period last_ever max } }, allow_nil: true
42
+ validates :recurring_usage_type, inclusion: { in: %w{ metered licensed } }, allow_nil: true
43
+ validates :tiers_mode, inclusion: { in: %w{ graduated volume } }, allow_nil: true
44
+
45
+ validate :name_or_product_id
46
+ validate :recurring_aggregate_usage_must_be_metered, if: ->(p) { p.recurring_aggregate_usage.present? }
47
+ validate :recurring_interval_count_maximum, if: ->(p) { p.recurring_interval_count.present? }
48
+ validate :valid_constant_name, unless: ->(p) { p.constant_name.nil? }
49
+
50
+ # validations for when using tiered billing
51
+ validate :tiers_must_be_array, if: ->(p) { p.tiers.present? }
52
+ validate :billing_scheme_must_be_tiered, if: ->(p) { p.tiers.present? }
53
+ validate :validate_tiers, if: ->(p) { p.billing_scheme == 'tiered' }
54
+
55
+ def initialize(*args)
56
+ super(*args)
57
+ @currency = 'usd'
58
+ @lookup_key = @id.to_s
59
+ @recurring = (recurring || {}).symbolize_keys
60
+ end
61
+
62
+ # We're overriding a handful of the Configuration methods so that
63
+ # we find and create by lookup_key instead of by ID. The ID is assigned
64
+ # by stripe and out of our control
65
+ def put!
66
+ if exists?
67
+ puts "[EXISTS] - #{@stripe_class}:#{@id}:#{stripe_id}" unless Stripe::Engine.testing
68
+ else
69
+ object = @stripe_class.create({:lookup_key => @lookup_key}.merge compact_create_options)
70
+ puts "[CREATE] - #{@stripe_class}:#{object}" unless Stripe::Engine.testing
71
+ end
72
+ end
73
+
74
+ # You can't delete prices, but you can transfer the lookup key to a new price
75
+ def reset!
76
+ object = @stripe_class.create(reset_options)
77
+ puts "[RESET] - #{@stripe_class}:#{object}" unless Stripe::Engine.testing
78
+ end
79
+
80
+ def exists?
81
+ stripe_object.presence
82
+ rescue Stripe::InvalidRequestError
83
+ false
84
+ end
85
+
86
+ def stripe_object
87
+ @stripe_class.list({lookup_keys: [@lookup_key]}).data.first.presence || nil
88
+ rescue Stripe::InvalidRequestError
89
+ nil
90
+ end
91
+
92
+ def stripe_id
93
+ @stripe_id ||= stripe_object.try(:id)
94
+ end
95
+
96
+ def recurring_interval
97
+ recurring[:interval]
98
+ end
99
+
100
+ def recurring_aggregate_usage
101
+ recurring[:aggregate_usage]
102
+ end
103
+
104
+ def recurring_usage_type
105
+ recurring[:usage_type]
106
+ end
107
+
108
+ def recurring_interval_count
109
+ recurring[:interval_count]
110
+ end
111
+
112
+ private
113
+ def recurring_aggregate_usage_must_be_metered
114
+ errors.add(:recurring_aggregate_usage, 'recurring[:usage_type] must be metered') unless (recurring_usage_type == 'metered')
115
+ end
116
+
117
+ def recurring_interval_count_maximum
118
+ time_unit = recurring_interval.to_sym
119
+
120
+ return unless VALID_TIME_UNITS.include?(time_unit) && recurring_interval_count.respond_to?(time_unit)
121
+ too_long = recurring_interval_count.send(time_unit) > 1.year
122
+
123
+ errors.add(:recurring_interval_count, 'recurring[:interval_count] Maximum is one year (1 year, 12 months, or 52 weeks') if too_long
124
+ end
125
+
126
+ def name_or_product_id
127
+ errors.add(:base, 'must have a product_id or a name') unless (@product_id.present? ^ @name.present?)
128
+ end
129
+
130
+ def billing_scheme_must_be_tiered
131
+ errors.add(:billing_scheme, 'must be set to `tiered` when specifying `tiers`') unless billing_scheme == 'tiered'
132
+ end
133
+
134
+ def tiers_must_be_array
135
+ errors.add(:tiers, 'must be an Array') unless tiers.is_a?(Array)
136
+ end
137
+
138
+ def billing_tiers
139
+ @billing_tiers = tiers.map { |t| Stripe::Plans::BillingTier.new(t) } if tiers
140
+ end
141
+
142
+ def validate_tiers
143
+ billing_tiers.all?(&:valid?)
144
+ end
145
+
146
+ module ConstTester; end
147
+ def valid_constant_name
148
+ ConstTester.const_set(constant_name.to_s.upcase, constant_name)
149
+ ConstTester.send(:remove_const, constant_name.to_s.upcase.to_sym)
150
+ rescue NameError
151
+ errors.add(:constant_name, 'is not a valid Ruby constant name.')
152
+ end
153
+
154
+ def reset_options
155
+ existing_object = stripe_object
156
+ # Lookup and set the existing product ID if unset
157
+ @product_id ||= existing_object.product if existing_object.present?
158
+
159
+ { transfer_lookup_key: existing_object.present? }.merge(compact_create_options)
160
+ end
161
+
162
+ def create_options
163
+ {
164
+ currency: currency,
165
+ unit_amount: unit_amount,
166
+ active: active,
167
+ metadata: metadata,
168
+ nickname: nickname.presence || @lookup_key,
169
+ recurring: recurring.compact,
170
+ tiers: tiers ? tiers.map(&:to_h) : nil,
171
+ tiers_mode: tiers_mode,
172
+ billing_scheme: billing_scheme,
173
+ lookup_key: @lookup_key,
174
+ transform_quantity: transform_quantity,
175
+ }.merge(product_options).compact
176
+ end
177
+
178
+ def product_options
179
+ if product_id.present?
180
+ { product: product_id }
181
+ else
182
+ {
183
+ product_data: { name: name, statement_descriptor: statement_descriptor }
184
+ }
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end