stripe-rails 2.0.0.pre → 2.3.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: 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