stripe-rails 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,6 @@
1
+ ## 0.2.1 (2012-12-17)
2
+ * manage coupons with config/stripe/coupons.rb
3
+
1
4
  ## 0.2.0 (2012-12-13)
2
5
 
3
6
  * out of the box support for webhooks and critical/non-critical event handlers
data/README.md CHANGED
@@ -71,7 +71,7 @@ this will generate the configuration files containing your plan and coupon defin
71
71
  create config/stripe/plans.rb
72
72
  create config/stripe/coupons.rb
73
73
 
74
- ### Configuring your plans
74
+ ### Configuring your plans and coupons
75
75
 
76
76
  Use the plan builder to define as many plans as you want in `config/stripe/plans.rb`
77
77
 
@@ -93,12 +93,22 @@ can refer to them by reference as opposed to an id string.
93
93
  Stripe::Plans::SILVER # => 'silver: ACME Silver'
94
94
  Stripe::Plans::GOLD # => 'gold: ACME Gold'
95
95
 
96
- To upload these plans onto stripe.com, run:
96
+
97
+ Coupons are created in much the same way:
98
+
99
+ Stripe.coupon :super_elite_free_vip do |coupon|
100
+ coupon.duration = 'forever'
101
+ coupon.percent_off = 100
102
+ coupon.max_redemptions = 5
103
+ end
104
+
105
+ To upload your plans and coupons onto stripe.com, run:
97
106
 
98
107
  rake stripe:prepare
99
108
 
100
- This will create any plans that do not currently exist, and treat as a NOOP any
101
- plans that do, so you can run this command safely as many times as you wish.
109
+ This will create any plans and coupons that do not currently exist, and treat as a NOOP any
110
+ plans that do, so you can run this command safely as many times as you wish. Now you can
111
+ use any of these plans in your application.
102
112
 
103
113
  NOTE: You must destroy plans manually from your stripe dashboard.
104
114
 
@@ -5,6 +5,7 @@ module Stripe
5
5
  desc "copy plans.rb"
6
6
  def copy_plans_file
7
7
  copy_file "plans.rb", "config/stripe/plans.rb"
8
+ copy_file "coupons.rb", "config/stripe/coupons.rb"
8
9
  end
9
10
  end
10
11
  end
@@ -0,0 +1,39 @@
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
+ #
11
+ # # specify if this coupon is useable 'once', 'forever', or 'repeating
12
+ # coupon.duration = 'repeating'
13
+ #
14
+ # # absolute amount, in cents, to discount
15
+ # coupon.amount_off = 199
16
+ #
17
+ # # what currency to interpret the coupon amount
18
+ # coupon.currency = 'usd'
19
+ #
20
+ # # how long will this coupon last? (only valid for duration of 'repeating')
21
+ # coupon.duration_in_months = 6
22
+ #
23
+ # # percentage off
24
+ # coupon.percent_off = 25
25
+ #
26
+ # UTC timestamp specifying the last time at which the coupon can be redeemed
27
+ # coupon.reedem_by = (Time.now + 15.days).utc
28
+ #
29
+ # # How many times can this coupon be redeemed?
30
+ # coupon.max_redemptions = 10
31
+ # end
32
+ #
33
+ # Once you have your coupons defined, you can run
34
+ #
35
+ # rake stripe:prepare
36
+ #
37
+ # This will export any new coupons to stripe.com so that you can
38
+ # begin using them in your API calls. Any coupons found that are not in this
39
+ # file will be left as-is.
@@ -0,0 +1,99 @@
1
+ module Stripe
2
+ module ConfigurationBuilder
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class << self
7
+ def configuration_for(class_id, &block)
8
+ @_configuration_storage = "@#{class_id.to_s.pluralize}"
9
+ instance_variable_set(@_configuration_storage, {})
10
+ configuration_class = Class.new(Stripe::ConfigurationBuilder::Configuration)
11
+ const_set(:Configuration, configuration_class)
12
+ configuration_class.class_eval(&block)
13
+ stripe_class = Stripe.const_get(class_id.to_s.camelize)
14
+ stripe_configuration_class = self
15
+ send(:define_method, class_id) do |id, &block|
16
+ config = configuration_class.new(id, stripe_class, stripe_configuration_class)
17
+ block.call config
18
+ config.finalize!
19
+ end
20
+ ::Stripe.send(:extend, self)
21
+ end
22
+
23
+ def configurations
24
+ instance_variable_get(@_configuration_storage)
25
+ end
26
+
27
+ def all
28
+ configurations.values
29
+ end
30
+
31
+ def [](key)
32
+ configurations[key.to_s]
33
+ end
34
+
35
+ def []=(key, value)
36
+ configurations[key.to_s] = value
37
+ end
38
+
39
+ def put!
40
+ all.each(&:put!)
41
+ end
42
+ end
43
+ end
44
+
45
+ class Configuration
46
+ include ActiveModel::Validations
47
+ attr_reader :id
48
+
49
+ def initialize(id, stripe_class, stripe_configuration_class)
50
+ @id = id
51
+ @stripe_class = stripe_class
52
+ @stripe_configuration_class = stripe_configuration_class
53
+ end
54
+
55
+ def finalize!
56
+ validate!
57
+ globalize!
58
+ end
59
+
60
+ def validate!
61
+ fail Stripe::InvalidConfigurationError, errors if invalid?
62
+ end
63
+
64
+ def globalize!
65
+ @stripe_configuration_class[@id.to_s] = self
66
+ @stripe_configuration_class.const_set(@id.to_s.upcase, self)
67
+ end
68
+
69
+ def put!
70
+ if exists?
71
+ puts "[EXISTS] - #{@stripe_class}:#{@id}" unless Stripe::Engine.testing
72
+ else
73
+ object = @stripe_class.create({:id => @id}.merge create_options)
74
+ puts "[CREATE] - #{@stripe_class}:#{object}" unless Stripe::Engine.testing
75
+ end
76
+ end
77
+
78
+ def to_s
79
+ @id.to_s
80
+ end
81
+
82
+ def exists?
83
+ !!@stripe_class.retrieve(to_s)
84
+ rescue Stripe::InvalidRequestError
85
+ false
86
+ end
87
+ end
88
+ end
89
+
90
+ class InvalidConfigurationError < StandardError
91
+ attr_reader :errors
92
+
93
+ def initialize(errors)
94
+ super errors.messages
95
+ @errors = errors
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,38 @@
1
+ module Stripe
2
+ module Coupons
3
+ include ConfigurationBuilder
4
+
5
+ configuration_for :coupon do
6
+ attr_accessor :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
13
+ validates_numericality_of :duration_in_months, :greater_than => 0, :if => :repeating?
14
+ validates_numericality_of :max_redemptions, :greater_than => 0
15
+
16
+ def initialize(*args)
17
+ super
18
+ @currency = 'usd'
19
+ end
20
+
21
+ def repeating?
22
+ duration == 'repeating'
23
+ end
24
+
25
+ def create_options
26
+ {
27
+ :duration => duration,
28
+ :percent_off => percent_off,
29
+ :amount_off => amount_off,
30
+ :currency => currency,
31
+ :duration_in_months => duration_in_months,
32
+ :max_redemptions => max_redemptions,
33
+ :redeem_by => redeem_by
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -38,9 +38,11 @@ environment file directly.
38
38
  end
39
39
  end
40
40
 
41
- initializer 'stripe.plans' do |app|
42
- path = app.root.join('config/stripe/plans.rb')
43
- load path if path.exist?
41
+ initializer 'stripe.plans_and_coupons' do |app|
42
+ for configuration in %w(plans coupons)
43
+ path = app.root.join("config/stripe/#{configuration}.rb")
44
+ load path if path.exist?
45
+ end
44
46
  end
45
47
 
46
48
  rake_tasks do
@@ -1,98 +1,31 @@
1
1
  module Stripe
2
2
  module Plans
3
- @plans = {}
3
+ include ConfigurationBuilder
4
4
 
5
- def self.all
6
- @plans.values
7
- end
8
-
9
- def self.[](key)
10
- @plans[key.to_s]
11
- end
12
-
13
- def self.[]=(key, value)
14
- @plans[key.to_s] = value
15
- end
16
-
17
- def self.put!
18
- all.each do |plan|
19
- plan.put!
20
- end
21
- end
22
-
23
- def plan(id)
24
- config = Configuration.new(id)
25
- yield config
26
- config.finalize!
27
- end
28
-
29
- class Configuration
30
- include ActiveModel::Validations
31
- attr_reader :id, :currency
5
+ configuration_for :plan do
6
+ attr_reader :currency
32
7
  attr_accessor :name, :amount, :interval, :interval_count, :trial_period_days
33
8
 
34
9
  validates_presence_of :id, :name, :amount
35
10
  validates_inclusion_of :interval, :in => %w(month year), :message => "'%{value}' is not one of 'month' or 'year'"
36
11
 
37
- def initialize(id)
38
- @id = id
12
+ def initialize(*args)
13
+ super(*args)
39
14
  @currency = 'usd'
40
15
  @interval_count = 1
41
16
  @trial_period_days = 0
42
17
  end
43
18
 
44
- def finalize!
45
- validate!
46
- globalize!
47
- end
48
-
49
- def validate!
50
- fail InvalidPlanError, errors if invalid?
51
- end
52
-
53
- def globalize!
54
- Stripe::Plans[@id.to_s] = self
55
- Stripe::Plans.const_set(@id.to_s.upcase, self)
56
- end
57
-
58
- def put!
59
- if exists?
60
- puts "[EXISTS] - #{@id}" unless Stripe::Engine.testing
61
- else
62
- plan = Stripe::Plan.create(
63
- :id => @id,
64
- :currency => @currency,
65
- :name => @name,
66
- :amount => @amount,
67
- :interval => @interval,
68
- :interval_count => @interval_count,
69
- :trial_period_days => @trial_period_days
70
- )
71
- puts "[CREATE] - #{plan}" unless Stripe::Engine.testing
72
- end
73
- end
74
-
75
- def to_s
76
- @id.to_s
77
- end
78
-
79
- def exists?
80
- !!Stripe::Plan.retrieve("#{@id}")
81
- rescue Stripe::InvalidRequestError
82
- false
19
+ def create_options
20
+ {
21
+ :currency => @currency,
22
+ :name => @name,
23
+ :amount => @amount,
24
+ :interval => @interval,
25
+ :interval_count => @interval_count,
26
+ :trial_period_days => @trial_period_days
27
+ }
83
28
  end
84
29
  end
85
-
86
- class InvalidPlanError < StandardError
87
- attr_reader :errors
88
-
89
- def initialize(errors)
90
- super errors.messages
91
- @errors = errors
92
- end
93
-
94
- end
95
-
96
30
  end
97
- extend Plans
98
31
  end
@@ -1,4 +1,6 @@
1
1
  require "stripe/rails/version"
2
2
  require 'stripe/engine'
3
+ require 'stripe/configuration_builder'
3
4
  require 'stripe/plans'
5
+ require 'stripe/coupons'
4
6
  require 'stripe/callbacks'
@@ -15,6 +15,10 @@ namespace :stripe do
15
15
  Stripe::Plans.put!
16
16
  end
17
17
 
18
- desc "create all plans defined in config/stripe/plans.rb"
19
- task 'prepare' => 'plans:prepare'
18
+ task 'coupons:prepare' => 'environment' do
19
+ Stripe::Coupons.put!
20
+ end
21
+
22
+ desc "create all plans and coupons defined in config/stripe/{plans|coupons}.rb"
23
+ task 'prepare' => ['plans:prepare', 'coupons:prepare']
20
24
  end
@@ -1,5 +1,5 @@
1
1
  module Stripe
2
2
  module Rails
3
- VERSION = "0.2.0"
3
+ VERSION = "0.2.1"
4
4
  end
5
5
  end
@@ -0,0 +1,64 @@
1
+ require 'minitest/autorun'
2
+ require 'spec_helper'
3
+
4
+ describe 'building plans' do
5
+ describe 'simply' do
6
+ before do
7
+ @now = Time.now.utc
8
+ Stripe.coupon(:gold25) do |coupon|
9
+ coupon.duration = 'repeating'
10
+ coupon.duration_in_months = 10
11
+ coupon.amount_off = 100
12
+ coupon.currency = 'USD'
13
+ coupon.max_redemptions = 3
14
+ coupon.percent_off = 25
15
+ coupon.redeem_by = @now
16
+ end
17
+ end
18
+ after {Stripe::Coupons.send(:remove_const, :GOLD25)}
19
+
20
+ it 'is accessible via hash lookup (symbol/string agnostic)' do
21
+ Stripe::Coupons[:gold25].must_equal Stripe::Coupons::GOLD25
22
+ Stripe::Coupons['gold25'].must_equal Stripe::Coupons::GOLD25
23
+ end
24
+
25
+ describe 'uploading' do
26
+ describe 'when none exists on stripe.com' do
27
+ before do
28
+ Stripe::Coupon.stubs(:retrieve).raises(Stripe::InvalidRequestError.new("not found", "id"))
29
+ end
30
+ it 'creates the plan online' do
31
+ Stripe::Coupon.expects(:create).with(
32
+ :id => :gold25,
33
+ :duration => 'repeating',
34
+ :duration_in_months => 10,
35
+ :amount_off => 100,
36
+ :currency => 'USD',
37
+ :max_redemptions => 3,
38
+ :percent_off => 25,
39
+ :redeem_by => @now
40
+ )
41
+ Stripe::Coupons.put!
42
+ end
43
+
44
+ end
45
+ describe 'when it is already present on stripe.com' do
46
+ before do
47
+ Stripe::Coupon.stubs(:retrieve).returns(Stripe::Coupon.construct_from({
48
+ :id => :gold25,
49
+ }))
50
+ end
51
+ it 'is a no-op' do
52
+ Stripe::Coupon.expects(:create).never
53
+ Stripe::Coupons.put!
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ describe 'with missing mandatory values' do
60
+ it 'raises an exception after configuring it' do
61
+ proc {Stripe.plan(:bad) {}}.must_raise Stripe::InvalidConfigurationError
62
+ end
63
+ end
64
+ end
@@ -65,7 +65,7 @@ describe 'building plans' do
65
65
 
66
66
  describe 'with missing mandatory values' do
67
67
  it 'raises an exception after configuring it' do
68
- proc {Stripe.plan(:bad) {}}.must_raise Stripe::Plans::InvalidPlanError
68
+ proc {Stripe.plan(:bad) {}}.must_raise Stripe::InvalidConfigurationError
69
69
  end
70
70
  end
71
71
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stripe-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-13 00:00:00.000000000 Z
12
+ date: 2012-12-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -113,10 +113,13 @@ files:
113
113
  - app/views/stripe/_js.html.erb
114
114
  - config/routes.rb
115
115
  - lib/generators/stripe/install_generator.rb
116
+ - lib/generators/templates/coupons.rb
116
117
  - lib/generators/templates/plans.rb
117
118
  - lib/stripe-rails.rb
118
119
  - lib/stripe/callbacks.rb
119
120
  - lib/stripe/callbacks/builder.rb
121
+ - lib/stripe/configuration_builder.rb
122
+ - lib/stripe/coupons.rb
120
123
  - lib/stripe/engine.rb
121
124
  - lib/stripe/plans.rb
122
125
  - lib/stripe/rails.rb
@@ -125,6 +128,7 @@ files:
125
128
  - stripe-rails.gemspec
126
129
  - test/.DS_Store
127
130
  - test/callbacks_spec.rb
131
+ - test/coupon_builder_spec.rb
128
132
  - test/dummy/README.rdoc
129
133
  - test/dummy/Rakefile
130
134
  - test/dummy/app/assets/javascripts/application.js
@@ -189,6 +193,7 @@ summary: A gem to integrate stripe into your rails app
189
193
  test_files:
190
194
  - test/.DS_Store
191
195
  - test/callbacks_spec.rb
196
+ - test/coupon_builder_spec.rb
192
197
  - test/dummy/README.rdoc
193
198
  - test/dummy/Rakefile
194
199
  - test/dummy/app/assets/javascripts/application.js