stripe-rails 0.2.0 → 0.2.1

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.
@@ -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