stripe-rails 1.10.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71f634d5d4a82d22c99a17ecc2bfdb313564c161d9fb8113cee4046b545b056f
4
- data.tar.gz: 62628badc191b1f737fdcbf1608e5d36cbfd264399dc94da98e9a59a2c07ff6f
3
+ metadata.gz: 15856817d5ce2ac1b96f1ad4f6f93602b5b80f0790cfbbd56ae87c0d89654571
4
+ data.tar.gz: 602743d7695c49114426acf961c7c7df1fd7e6d959cb1d00c860a729913847c3
5
5
  SHA512:
6
- metadata.gz: 99cab9c0756eae42185a33ca6a62f9ff3038e912b7f10492a9fa400b18663576cac483441041e805c34b603f38d67763bb639323bdb238033ff043d03c512a26
7
- data.tar.gz: 8969c43692b09fd949fbb0d6bb810e79fa7a1efab27c73e9147edf2ded0d16da1cc295ec09a7ddef8832932ce8460c50650f16a602766f299e97e0d04fcd1693
6
+ metadata.gz: 29127701ac3cccb96aa0bd215f616fa83b41d55cfc40d41aaafd56ca0ca0a479822b4c473bfbe3d9fdb3dfac6d0061dd93e237a7a412811358952d0c97f51f60
7
+ data.tar.gz: '07384662c64848e9e86bc592501b705e7879c8a8222dae408195b5b3b6fd8ef9e23cc6379d3f9dcd382dbe6806448a8978108f449984605de0232c81763c0423'
@@ -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
  -->
@@ -14,9 +14,8 @@ jobs:
14
14
 
15
15
  strategy:
16
16
  matrix:
17
- ruby: [2.4.x, 2.5.x, 2.6.x]
18
- gemfile: [Gemfile, gemfiles/rails51.gemfile, gemfiles/rails52.gemfile, gemfiles/rails4.gemfile]
19
-
17
+ ruby: [2.5.x, 2.6.x, 2.7.x]
18
+ gemfile: [Gemfile, gemfiles/rails51.gemfile, gemfiles/rails52.gemfile]
20
19
  steps:
21
20
  - uses: actions/checkout@v1
22
21
  - name: Set up Ruby
@@ -34,10 +33,11 @@ jobs:
34
33
  CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
35
34
  RUBY_VERSION: ${{ matrix.ruby }}
36
35
  run: |
37
- gem uninstall bundler
38
- gem install bundler -v 1.17.3
36
+ if [ $RUBY_VERSION == "2.5.x" ] ;
37
+ then gem install bundler
38
+ fi
39
39
  bundle install --jobs 4 --retry 3
40
40
  bundle exec rake
41
- if [ `basename $BUNDLE_GEMFILE` == "Gemfile" ] && [ $RUBY_VERSION == "2.6.x" ] && [ ! -z ${CC_TEST_REPORTER_ID} ] ;
41
+ if [ `basename $BUNDLE_GEMFILE` == "Gemfile" ] && [ $RUBY_VERSION == "2.7.x" ] && [ ! -z ${CC_TEST_REPORTER_ID} ] ;
42
42
  then ./cc-test-reporter after-build --exit-code $? ;
43
43
  fi
@@ -1,6 +1,27 @@
1
- ## Unreleased
1
+ ## 2.2.0 (2020-12-06)
2
2
 
3
- -
3
+ - Add Prices as a configuration object. Thanks @jamesprior!
4
+
5
+ ## 2.1.0 (2020-10-18)
6
+
7
+ - Added option to ignore missing API key and don't show any warning. Thanks @ndbroadbent!
8
+ - Handle passing nil to signing_secret= and add tests. Thanks @martron!
9
+
10
+ ## 2.0.0 (2020-09-18)
11
+
12
+ - Everything from on the 2.0.0.pre release
13
+ - includes changes from the 1.10.2 release
14
+
15
+ ## 1.10.2 (2020-09-18)
16
+
17
+ - adds missing callback `invoice.paid`. Thanks @SyborgStudios.
18
+
19
+ ## 2.0.0.pre (2020-05-29)
20
+
21
+ * [Breaking] Updated to work only with Rails >= 5.1
22
+ * [Breaking] It'll only be tested on Ruby 2.7, 2.6 and 2.5.
23
+ * [Breaking] Supports the Stripe gem => 3.15.0 (from 2 years ago)
24
+ * [Breaking] Removes Stripe::PingsController controller.
4
25
 
5
26
  ## 1.10.1 (2020-05-29)
6
27
 
data/Gemfile CHANGED
@@ -14,9 +14,9 @@ group :test do
14
14
  gem 'simplecov', '< 0.18', require: false
15
15
  gem 'stripe-ruby-mock'
16
16
  gem 'webmock'
17
- # Required for system tests
17
+ # System tests
18
18
  gem 'capybara'
19
- gem 'webdrivers'
20
19
  gem 'puma'
21
20
  gem 'selenium-webdriver'
22
- end
21
+ gem 'webdrivers'
22
+ end
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
@@ -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
@@ -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'
@@ -105,10 +106,6 @@ module Stripe
105
106
  callback 'ping'
106
107
  callback 'stripe.event'
107
108
 
108
- # Deprecated
109
- callback 'transfer.failed' # https://stripe.com/docs/upgrades#2017-04-06
110
- callback 'transfer.paid' # https://stripe.com/docs/upgrades#2017-04-06
111
-
112
109
  class << self
113
110
  def run_callbacks(evt, target)
114
111
  _run_callbacks evt.type, evt, target
@@ -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