boutique 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -24,6 +24,7 @@ can configure what products you sell here:
24
24
  require 'boutique'
25
25
 
26
26
  Boutique.configure do |c|
27
+ c.dev_email 'dev@mailinator.com'
27
28
  c.pem_private '/path/to/private.pem'
28
29
  c.pem_public '/path/to/public.pem'
29
30
  c.pem_paypal '/path/to/paypal.pem'
@@ -82,12 +83,6 @@ Development
82
83
  Tests are setup to run individually via `ruby test/*_test.rb` or run them all
83
84
  via `rake`.
84
85
 
85
- TODO
86
- ====
87
-
88
- * email customer + recover action
89
- * email exceptions to developer
90
-
91
86
  License
92
87
  =======
93
88
 
data/boutique CHANGED
@@ -59,6 +59,7 @@ module Boutique
59
59
  puts "Files: #{product.files.join("\n ")}"
60
60
  printf("Price: $%.2f\n" % product.price)
61
61
  puts "Return: #{product.return_url}"
62
+ puts "Email: #{product.support_email}"
62
63
 
63
64
  puts ('-' * 40)
64
65
 
data/config.ru CHANGED
@@ -1,6 +1,7 @@
1
1
  require File.expand_path('lib/boutique', File.dirname(__FILE__))
2
2
 
3
3
  Boutique.configure(!ENV['BOUTIQUE_CMD'].nil?) do |c|
4
+ c.dev_email 'dev@localhost'
4
5
  c.pem_private File.expand_path('certs/private.pem', File.dirname(__FILE__))
5
6
  c.pem_public File.expand_path('certs/public.pem', File.dirname(__FILE__))
6
7
  c.pem_paypal File.expand_path('certs/paypal.pem', File.dirname(__FILE__))
@@ -16,10 +17,11 @@ Boutique.configure(!ENV['BOUTIQUE_CMD'].nil?) do |c|
16
17
  end
17
18
 
18
19
  Boutique.product('readme') do |p|
19
- p.name 'README document'
20
- p.files File.expand_path('README.md', File.dirname(__FILE__))
21
- p.price 1.5
22
- p.return_url 'http://localhost'
20
+ p.name 'README document'
21
+ p.files File.expand_path('README.md', File.dirname(__FILE__))
22
+ p.price 1.5
23
+ p.return_url 'http://localhost'
24
+ p.support_email 'support@localhost'
23
25
  end
24
26
 
25
27
  run Boutique::App if !ENV['BOUTIQUE_CMD']
data/lib/boutique.rb CHANGED
@@ -8,9 +8,10 @@ require 'date'
8
8
  require 'digest/sha1'
9
9
  require 'json'
10
10
  require 'openssl'
11
+ require 'pony'
11
12
 
12
13
  module Boutique
13
- VERSION = '0.0.3'
14
+ VERSION = '0.0.4'
14
15
 
15
16
  class << self
16
17
  def configure(setup_db=true)
@@ -39,6 +40,11 @@ module Boutique
39
40
  end
40
41
 
41
42
  class Config
43
+ def self.dev_email(value=nil)
44
+ @dev_email = value if !value.nil?
45
+ @dev_email
46
+ end
47
+
42
48
  def self.pem_private(value=nil)
43
49
  @pem_private = value if !value.nil?
44
50
  @pem_private
@@ -126,12 +132,18 @@ module Boutique
126
132
  @return_url
127
133
  end
128
134
 
135
+ def support_email(value=nil)
136
+ @support_email = value if !value.nil?
137
+ @support_email
138
+ end
139
+
129
140
  def to_hash
130
141
  {:code => @code,
131
142
  :name => @name,
132
143
  :files => @files,
133
144
  :price => @price,
134
- :return_url => @return_url}
145
+ :return_url => @return_url,
146
+ :support_email => @support_email}
135
147
  end
136
148
  end
137
149
 
@@ -144,6 +156,7 @@ module Boutique
144
156
  property :files, CommaSeparatedList, :required => true
145
157
  property :price, Decimal, :required => true
146
158
  property :return_url, String, :required => true
159
+ property :support_email, String, :required => true
147
160
 
148
161
  has n, :purchases
149
162
  end
@@ -212,6 +225,19 @@ module Boutique
212
225
  Digest::SHA1.hexdigest("#{DateTime.now}#{rand}")[0..9]
213
226
  end
214
227
 
228
+ def send_mail
229
+ raise 'Cannot send link to incomplete purchase' if !completed?
230
+ Pony.mail(
231
+ :to => self.email,
232
+ :from => self.product.support_email,
233
+ :subject => "#{self.product.name} Receipt",
234
+ :body => "Thanks for purchasing #{self.product.name}! " +
235
+ "To download it, follow this link:\n\n" +
236
+ " #{self.product.return_url}?b=#{boutique_id}\n\n" +
237
+ "Let us know if you have any troubles.\n"
238
+ )
239
+ end
240
+
215
241
  def to_json
216
242
  {
217
243
  :id => id,
@@ -258,6 +284,18 @@ module Boutique
258
284
  DataMapper.finalize
259
285
 
260
286
  class App < Sinatra::Base
287
+ set :raise_errors, false
288
+ set :show_exceptions, false
289
+
290
+ error do
291
+ Pony.mail(
292
+ :to => Boutique.config.dev_email,
293
+ :from => "boutique@#{Boutique.config.dev_email.split('@')[1..-1]}",
294
+ :subject => 'Production Exception',
295
+ :body => request.env['sinatra.error'].to_s
296
+ ) if Boutique.config.dev_email
297
+ end
298
+
261
299
  post '/boutique/buy/:code' do
262
300
  product = Boutique::Product.first(:code => params[:code])
263
301
  if product.nil?
@@ -283,11 +321,22 @@ module Boutique
283
321
  params['payment_status'] &&
284
322
  params['receiver_email'] == Boutique.config.pp_email
285
323
  purchase.complete(params['txn_id'], params['payer_email'], params['first_name'])
324
+ purchase.send_mail
286
325
  purchase.save
287
326
  end
288
327
  ''
289
328
  end
290
329
 
330
+ post '/boutique/recover/:code' do
331
+ product = Boutique::Product.first(:code => params[:code])
332
+ purchase = product.purchases.first(:email => params['email']) if product
333
+ if product.nil? || purchase.nil? || !purchase.completed?
334
+ halt(404, "purchase #{params[:code]}/#{params['email']} not found")
335
+ end
336
+ purchase.send_mail
337
+ purchase.boutique_id
338
+ end
339
+
291
340
  get '/boutique/record/:boutique_id' do
292
341
  purchase = get_purchase(params[:boutique_id])
293
342
  purchase.maybe_refresh_downloads!
data/test/app_test.rb CHANGED
@@ -23,12 +23,14 @@ class AppTest < BoutiqueTest
23
23
  product.purchases << purchase
24
24
  product.save
25
25
  refute(purchase.completed?)
26
+ assert_nil(Pony.last_mail)
26
27
 
27
28
  post "/boutique/notify/#{purchase.boutique_id}?payment_status=Completed&txn_id=1337&receiver_email=#{Boutique.config.pp_email}"
28
29
  assert(last_response.ok?)
29
30
 
30
31
  purchase.reload
31
32
  assert(purchase.completed?)
33
+ refute_nil(Pony.last_mail)
32
34
  assert_equal('1337', purchase.transaction_id)
33
35
  end
34
36
 
@@ -37,12 +39,25 @@ class AppTest < BoutiqueTest
37
39
  assert(last_response.not_found?)
38
40
  end
39
41
 
40
- def test_record
41
- product = ebook_product
42
- purchase = Boutique::Purchase.new
43
- product.purchases << purchase
44
- product.save
42
+ def test_recover
43
+ purchase = ebook_purchase
44
+ purchase.complete('1337', 'john@mailinator.com', 'John')
45
+ purchase.save
46
+ assert_nil(Pony.last_mail)
47
+
48
+ post "/boutique/recover/ebook", 'email' => 'john@mailinator.com'
49
+ assert(last_response.ok?)
50
+ assert_equal(purchase.boutique_id, last_response.body)
51
+ refute_nil(Pony.last_mail)
52
+ end
53
+
54
+ def test_recover_not_found
55
+ post "/boutique/recover/99-notfound"
56
+ assert(last_response.not_found?)
57
+ end
45
58
 
59
+ def test_record
60
+ purchase = ebook_purchase
46
61
  get "/boutique/record/#{purchase.boutique_id}"
47
62
  assert(last_response.ok?)
48
63
 
@@ -60,6 +75,14 @@ class AppTest < BoutiqueTest
60
75
  end
61
76
 
62
77
  private
78
+ def ebook_purchase
79
+ product = ebook_product
80
+ purchase = Boutique::Purchase.new
81
+ product.purchases << purchase
82
+ product.save
83
+ purchase
84
+ end
85
+
63
86
  def app
64
87
  @app ||= Rack::Server.new.app
65
88
  end
data/test/config_test.rb CHANGED
@@ -2,6 +2,7 @@ require File.expand_path('helper', File.dirname(__FILE__))
2
2
 
3
3
  class ConfigTest < BoutiqueTest
4
4
  def test_db
5
+ assert_equal('dev@localhost', Boutique.config.dev_email)
5
6
  assert_equal(File.expand_path('../certs/private.pem', File.dirname(__FILE__)), Boutique.config.pem_private)
6
7
  assert_equal(File.expand_path('../certs/public.pem', File.dirname(__FILE__)), Boutique.config.pem_public)
7
8
  assert_equal(File.expand_path('../certs/private.pem', File.dirname(__FILE__)), Boutique.config.pem_private)
data/test/helper.rb CHANGED
@@ -9,11 +9,22 @@ require 'fileutils'
9
9
  DataMapper.setup(:default, 'sqlite::memory:')
10
10
  DataMapper.auto_migrate!
11
11
 
12
+ module Pony
13
+ def self.mail(fields)
14
+ @last_mail = fields
15
+ end
16
+
17
+ def self.last_mail
18
+ @last_mail
19
+ end
20
+ end
21
+
12
22
  class BoutiqueTest < MiniTest::Unit::TestCase
13
23
  def setup
14
24
  Boutique::Purchase.all.destroy
15
25
  Boutique::Product.all.destroy
16
26
  Boutique.configure(false) do |c|
27
+ c.dev_email 'dev@localhost'
17
28
  c.pem_private File.expand_path('../certs/private.pem', File.dirname(__FILE__))
18
29
  c.pem_public File.expand_path('../certs/public.pem', File.dirname(__FILE__))
19
30
  c.pem_paypal File.expand_path('../certs/paypal.pem', File.dirname(__FILE__))
@@ -27,6 +38,7 @@ class BoutiqueTest < MiniTest::Unit::TestCase
27
38
  c.pp_email 'paypal_biz@mailinator.com'
28
39
  c.pp_url 'http://localhost'
29
40
  end
41
+ Pony.mail(nil)
30
42
  end
31
43
 
32
44
  def teardown
@@ -40,6 +52,7 @@ class BoutiqueTest < MiniTest::Unit::TestCase
40
52
  :name => 'Ebook',
41
53
  :files => [File.expand_path('../README.md', File.dirname(__FILE__))],
42
54
  :price => 10.5,
43
- :return_url => 'http://zincmade.com')
55
+ :return_url => 'http://zincmade.com',
56
+ :support_email => 'support@zincmade.com')
44
57
  end
45
58
  end
data/test/model_test.rb CHANGED
@@ -39,6 +39,16 @@ class ModelTest < BoutiqueTest
39
39
  assert_equal('_s-xclick', form['cmd'])
40
40
  refute_nil(form['encrypted'])
41
41
 
42
+ purchase.send_mail
43
+ assert_equal({
44
+ :to => 'john@mailinator.com',
45
+ :from => 'support@zincmade.com',
46
+ :subject => 'Ebook Receipt',
47
+ :body => "Thanks for purchasing Ebook! To download it, follow this link:\n\n" +
48
+ " http://zincmade.com?b=#{purchase.boutique_id}\n\n" +
49
+ "Let us know if you have any troubles.\n"
50
+ }, Pony.last_mail)
51
+
42
52
  json = JSON.parse(purchase.to_json)
43
53
  assert_equal(purchase.id, json['id'])
44
54
  assert_equal(2, json['counter'])
@@ -51,10 +61,11 @@ class ModelTest < BoutiqueTest
51
61
  def test_product_create
52
62
  count = Boutique::Product.count
53
63
  Boutique.product('icon-set') do |p|
54
- p.name 'Icon Set'
55
- p.files [File.expand_path('../README.md', File.dirname(__FILE__))]
56
- p.price 10.5
57
- p.return_url 'http://zincmade.com'
64
+ p.name 'Icon Set'
65
+ p.files [File.expand_path('../README.md', File.dirname(__FILE__))]
66
+ p.price 10.5
67
+ p.return_url 'http://zincmade.com'
68
+ p.support_email 'support@zincmade.com'
58
69
  end
59
70
  assert_equal(count + 1, Boutique::Product.count)
60
71
 
@@ -63,6 +74,7 @@ class ModelTest < BoutiqueTest
63
74
  assert_equal(File.expand_path('../README.md', File.dirname(__FILE__)), set.files[0])
64
75
  assert_equal(10.5, set.price)
65
76
  assert_equal('http://zincmade.com', set.return_url)
77
+ assert_equal('support@zincmade.com', set.support_email)
66
78
  assert_equal(0, set.purchases.size)
67
79
  end
68
80
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boutique
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: .
11
11
  cert_chain: []
12
- date: 2012-01-29 00:00:00.000000000 Z
12
+ date: 2012-01-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
16
- requirement: &70293142732840 !ruby/object:Gem::Requirement
16
+ requirement: &70093300944320 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70293142732840
24
+ version_requirements: *70093300944320
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: data_mapper
27
- requirement: &70293142731600 !ruby/object:Gem::Requirement
27
+ requirement: &70093300943640 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70293142731600
35
+ version_requirements: *70093300943640
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: pony
38
- requirement: &70293142730680 !ruby/object:Gem::Requirement
38
+ requirement: &70093300942280 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70293142730680
46
+ version_requirements: *70093300942280
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: minitest
49
- requirement: &70293142729840 !ruby/object:Gem::Requirement
49
+ requirement: &70093300861840 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70293142729840
57
+ version_requirements: *70093300861840
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: shotgun
60
- requirement: &70293142729040 !ruby/object:Gem::Requirement
60
+ requirement: &70093300881600 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,7 +65,7 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70293142729040
68
+ version_requirements: *70093300881600
69
69
  description: A Sinatra module which accepts payments via PayPal and gives customers
70
70
  a secret URL to download your product.
71
71
  email: