boutique 0.0.3 → 0.0.4

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