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 +1 -6
- data/boutique +1 -0
- data/config.ru +6 -4
- data/lib/boutique.rb +51 -2
- data/test/app_test.rb +28 -5
- data/test/config_test.rb +1 -0
- data/test/helper.rb +14 -1
- data/test/model_test.rb +16 -4
- metadata +12 -12
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
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
|
20
|
-
p.files
|
21
|
-
p.price
|
22
|
-
p.return_url
|
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.
|
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
|
41
|
-
|
42
|
-
purchase
|
43
|
-
|
44
|
-
|
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
|
55
|
-
p.files
|
56
|
-
p.price
|
57
|
-
p.return_url
|
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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *70093300944320
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: data_mapper
|
27
|
-
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: *
|
35
|
+
version_requirements: *70093300943640
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: pony
|
38
|
-
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: *
|
46
|
+
version_requirements: *70093300942280
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: minitest
|
49
|
-
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: *
|
57
|
+
version_requirements: *70093300861840
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: shotgun
|
60
|
-
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: *
|
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:
|