merchant_sidekick 0.4.2

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.
Files changed (63) hide show
  1. data/.gitignore +12 -0
  2. data/Changelog.md +38 -0
  3. data/Gemfile +2 -0
  4. data/MIT-LICENSE +19 -0
  5. data/README.md +88 -0
  6. data/Rakefile +10 -0
  7. data/lib/merchant_sidekick.rb +45 -0
  8. data/lib/merchant_sidekick/active_merchant/credit_card_payment.rb +117 -0
  9. data/lib/merchant_sidekick/active_merchant/gateways/authorize_net_gateway.rb +26 -0
  10. data/lib/merchant_sidekick/active_merchant/gateways/base.rb +29 -0
  11. data/lib/merchant_sidekick/active_merchant/gateways/bogus_gateway.rb +19 -0
  12. data/lib/merchant_sidekick/active_merchant/gateways/paypal_gateway.rb +43 -0
  13. data/lib/merchant_sidekick/addressable/address.rb +400 -0
  14. data/lib/merchant_sidekick/addressable/addressable.rb +353 -0
  15. data/lib/merchant_sidekick/buyer.rb +99 -0
  16. data/lib/merchant_sidekick/gateway.rb +81 -0
  17. data/lib/merchant_sidekick/install.rb +19 -0
  18. data/lib/merchant_sidekick/invoice.rb +179 -0
  19. data/lib/merchant_sidekick/line_item.rb +128 -0
  20. data/lib/merchant_sidekick/migrations/addressable.rb +47 -0
  21. data/lib/merchant_sidekick/migrations/billing.rb +100 -0
  22. data/lib/merchant_sidekick/migrations/shopping_cart.rb +28 -0
  23. data/lib/merchant_sidekick/money.rb +38 -0
  24. data/lib/merchant_sidekick/order.rb +244 -0
  25. data/lib/merchant_sidekick/payment.rb +59 -0
  26. data/lib/merchant_sidekick/purchase_invoice.rb +180 -0
  27. data/lib/merchant_sidekick/purchase_order.rb +350 -0
  28. data/lib/merchant_sidekick/railtie.rb +7 -0
  29. data/lib/merchant_sidekick/sales_invoice.rb +56 -0
  30. data/lib/merchant_sidekick/sales_order.rb +122 -0
  31. data/lib/merchant_sidekick/sellable.rb +88 -0
  32. data/lib/merchant_sidekick/seller.rb +93 -0
  33. data/lib/merchant_sidekick/shopping_cart/cart.rb +225 -0
  34. data/lib/merchant_sidekick/shopping_cart/line_item.rb +152 -0
  35. data/lib/merchant_sidekick/version.rb +3 -0
  36. data/merchant_sidekick.gemspec +37 -0
  37. data/spec/address_spec.rb +153 -0
  38. data/spec/addressable_spec.rb +250 -0
  39. data/spec/buyer_spec.rb +203 -0
  40. data/spec/cart_line_item_spec.rb +58 -0
  41. data/spec/cart_spec.rb +213 -0
  42. data/spec/config/merchant_sidekick.yml +10 -0
  43. data/spec/credit_card_payment_spec.rb +175 -0
  44. data/spec/fixtures/addresses.yml +97 -0
  45. data/spec/fixtures/line_items.yml +18 -0
  46. data/spec/fixtures/orders.yml +24 -0
  47. data/spec/fixtures/payments.yml +17 -0
  48. data/spec/fixtures/products.yml +12 -0
  49. data/spec/fixtures/users.yml +11 -0
  50. data/spec/gateway_spec.rb +136 -0
  51. data/spec/invoice_spec.rb +79 -0
  52. data/spec/line_item_spec.rb +65 -0
  53. data/spec/order_spec.rb +85 -0
  54. data/spec/payment_spec.rb +14 -0
  55. data/spec/purchase_invoice_spec.rb +70 -0
  56. data/spec/purchase_order_spec.rb +191 -0
  57. data/spec/sales_invoice_spec.rb +58 -0
  58. data/spec/sales_order_spec.rb +107 -0
  59. data/spec/schema.rb +28 -0
  60. data/spec/sellable_spec.rb +34 -0
  61. data/spec/seller_spec.rb +201 -0
  62. data/spec/spec_helper.rb +255 -0
  63. metadata +201 -0
@@ -0,0 +1,203 @@
1
+ require File.expand_path("../spec_helper", __FILE__)
2
+
3
+ describe "A buyer's model" do
4
+
5
+ it "should be able to purchase" do
6
+ BuyingUser.new.should respond_to(:purchase)
7
+ end
8
+
9
+ it "should be able to purchase" do
10
+ BuyingUser.new.should respond_to(:purchase_from)
11
+ end
12
+
13
+ it "should have many orders" do
14
+ lambda { BuyingUser.new.orders(true).first }.should_not raise_error
15
+ end
16
+
17
+ it "should have many invoices" do
18
+ lambda { SellingUser.new.invoices(true).first }.should_not raise_error
19
+ end
20
+
21
+ it "should have many purchase orders" do
22
+ lambda { BuyingUser.new.purchase_orders(true).first }.should_not raise_error
23
+ end
24
+
25
+ it "should have many purchase invoices" do
26
+ lambda { BuyingUser.new.purchase_invoices(true).first }.should_not raise_error
27
+ end
28
+
29
+ end
30
+
31
+ describe "A buyer purchasing a sellable" do
32
+
33
+ def setup
34
+ @customer = users(:sam)
35
+ @merchant = users(:sally)
36
+ @billing = @customer.create_billing_address(addresses(:sam_billing).content_attributes)
37
+ @shipping = @customer.create_shipping_address(addresses(:sam_shipping).content_attributes)
38
+ @product = products(:widget)
39
+ end
40
+
41
+ it "should create a new order" do
42
+ transaction do
43
+ lambda {
44
+ order = @customer.purchase @product
45
+ order.should be_an_instance_of(MerchantSidekick::PurchaseOrder)
46
+ order.seller.should == nil
47
+ order.buyer.should be_an_instance_of(BuyingUser)
48
+ order.should be_valid
49
+ order.save!
50
+ }.should change(MerchantSidekick::Order, :count)
51
+ end
52
+ end
53
+
54
+ it "should create a new order referencing a merchant with :from option" do
55
+ transaction do
56
+ lambda {
57
+ order = @customer.purchase @product, :from => @merchant
58
+ order.seller.should == @merchant
59
+ order.should be_valid
60
+ order.save!
61
+ }.should change(MerchantSidekick::Order, :count)
62
+ end
63
+ end
64
+
65
+ it "should create a new order referencing a merchant with purchase_from method" do
66
+ transaction do
67
+ lambda {
68
+ order = @customer.purchase_from @merchant, @product
69
+ order.seller.should == @merchant
70
+ order.should be_valid
71
+ order.save!
72
+ }.should change(MerchantSidekick::Order, :count)
73
+ end
74
+ end
75
+
76
+ it "should add to buyers's orders" do
77
+ transaction do
78
+ order = @customer.purchase(@product)
79
+ order.save!
80
+ @customer.orders.last.should == order
81
+ order.buyer.should == @customer
82
+ end
83
+ end
84
+
85
+ it "should create line items" do
86
+ transaction do
87
+ order = @customer.purchase(@product)
88
+ order.line_items.size.should == 1
89
+ order.line_items.first.sellable.should == @product
90
+ end
91
+ end
92
+
93
+ it "should set line item amount to sellable price" do
94
+ transaction do
95
+ order = @customer.purchase @product
96
+ order.line_items.first.amount.should == @product.price
97
+ end
98
+ end
99
+
100
+ it "should set line item amount to 0 if sellable does not have a price" do
101
+ transaction do
102
+ @product.price = 0
103
+ order = @customer.purchase @product
104
+ order.line_items.first.amount.should == 0.to_money
105
+ end
106
+ end
107
+
108
+ it "should initialize but not save the order" do
109
+ transaction do
110
+ @order = @customer.purchase @product
111
+ @order.should be_new_record
112
+ end
113
+ end
114
+
115
+ it "should ignore blank sellables on purchase" do
116
+ transaction do
117
+ @order = @customer.purchase @product, nil
118
+ @order.line_items.size.should == 1
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ describe "A buyer purchasing multiple sellables" do
125
+
126
+ def setup
127
+ @sally = users(:sally)
128
+ @sally_billing = @sally.create_billing_address(addresses(:sally_billing).content_attributes)
129
+ @sally_shipping = @sally.create_shipping_address(addresses(:sally_shipping).content_attributes)
130
+
131
+ @sam = users(:sam)
132
+ @sam_billing = @sam.create_billing_address(addresses(:sam_billing).content_attributes)
133
+ @sam_shipping = @sam.create_shipping_address(addresses(:sam_shipping).content_attributes)
134
+
135
+ @products = [products(:widget), products(:knob)]
136
+ @order = @sam.purchase_from @sally, @products
137
+ end
138
+
139
+ it "should create line items for each sellable" do
140
+ transaction do
141
+ lambda { @order.save! }.should change(MerchantSidekick::LineItem, :count).by(2)
142
+ @order.should have(2).line_items
143
+ @order.line_items.map(&:sellable).should == @products
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ describe "A buyer purchasing from a cart" do
150
+
151
+ def setup
152
+ @sally = users(:sally)
153
+ @sally_billing = @sally.create_billing_address(addresses(:sally_billing).content_attributes)
154
+ @sally_shipping = @sally.create_shipping_address(addresses(:sally_shipping).content_attributes)
155
+
156
+ @sam = users(:sam)
157
+ @sam_billing = @sam.create_billing_address(addresses(:sam_billing).content_attributes)
158
+ @sam_shipping = @sam.create_shipping_address(addresses(:sam_shipping).content_attributes)
159
+
160
+ @products = [products(:widget), products(:knob)]
161
+ @order = @sam.purchase_from @sally, @products
162
+
163
+ @cart = MerchantSidekick::ShoppingCart::Cart.new
164
+ end
165
+
166
+ it "should add line items for a shopping cart" do
167
+ transaction do
168
+ @cart.add(@products)
169
+ @cart.total.to_s.should == "33.94"
170
+ order = @sam.purchase @cart
171
+ order.total.to_s.should == "33.94"
172
+ end
173
+ end
174
+
175
+ it "should add multiple shopping carts" do
176
+ transaction do
177
+ cart1 = MerchantSidekick::ShoppingCart::Cart.new
178
+ cart2 = MerchantSidekick::ShoppingCart::Cart.new
179
+
180
+ cart1.add @products.first
181
+ cart2.add @products.last
182
+ order = @sam.purchase cart1, cart2
183
+ order.total.to_s.should == "33.94"
184
+ end
185
+ end
186
+
187
+ end
188
+
189
+ describe "A buyer purchasing a non-sellable model" do
190
+
191
+ def setup
192
+ @user = users(:sam)
193
+ @billing = @user.create_billing_address(addresses(:sam_billing).content_attributes)
194
+ @shipping = @user.create_shipping_address(addresses(:sam_shipping).content_attributes)
195
+ end
196
+
197
+ it "should raise an error" do
198
+ transaction do
199
+ lambda { @user.purchase @user }.should raise_error(ArgumentError)
200
+ end
201
+ end
202
+
203
+ end
@@ -0,0 +1,58 @@
1
+ require File.expand_path("../spec_helper", __FILE__)
2
+
3
+ describe MerchantSidekick::ShoppingCart::LineItem do
4
+
5
+ def setup
6
+ @product = products(:widget)
7
+ end
8
+
9
+ it "should initialize and create" do
10
+ transaction do
11
+ item = MerchantSidekick::ShoppingCart::LineItem.new(valid_cart_line_item_attributes(:product => @product))
12
+ item.should be_valid
13
+ item.save!
14
+ item.product.should == @product
15
+ item.item_number.should == @product.id # as there is no sku, or number field in product
16
+ item.name.should == @product.title
17
+ item.description.should be_nil # as there is not description field
18
+ item.quantity.should == 5
19
+ item.unit.should == :piece
20
+ item.pieces.should == 1
21
+ item.total_amount.to_s.should == '149.75'
22
+ item.should be_taxable
23
+ end
24
+ end
25
+
26
+ it "should copy name and sku" do
27
+ transaction do
28
+ product = ProductWithNameAndSku.new(:price => Money.new(999, "USD"))
29
+ item = MerchantSidekick::ShoppingCart::LineItem.new(valid_cart_line_item_attributes(:product => product))
30
+ item.item_number.should == "PR1234"
31
+ item.description.should == "Wonderful name!"
32
+ end
33
+ end
34
+
35
+ it "should copy title and number" do
36
+ transaction do
37
+ product = ProductWithTitleAndNumber.new(:price => Money.new(999, "USD"))
38
+ item = MerchantSidekick::ShoppingCart::LineItem.new(valid_cart_line_item_attributes(:product => product))
39
+ item.name.should == "A beautiful title"
40
+ item.item_number.should == "PR1234"
41
+ item.description.should == "Wonderful title!"
42
+ end
43
+ end
44
+
45
+ it "should duplicate from copy methods" do
46
+ transaction do
47
+ product = ProductWithCopy.new(:price => Money.new(99, "USD"))
48
+ item = MerchantSidekick::ShoppingCart::LineItem.new(valid_cart_line_item_attributes(:product => product))
49
+ item.name.should == "customized name"
50
+ item.item_number.should == "customized item number"
51
+ item.description.should == "customized description"
52
+ item.unit_price.to_s.should == "99.99"
53
+ item.total_amount.to_s.should == "499.95"
54
+ item.price.to_s.should == "499.95"
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,213 @@
1
+ require File.expand_path("../spec_helper", __FILE__)
2
+
3
+ describe MerchantSidekick::ShoppingCart::Cart do
4
+
5
+ def setup
6
+ @user = users(:sam)
7
+ @cart = MerchantSidekick::ShoppingCart::Cart.new('USD')
8
+ end
9
+
10
+ it "should initialize" do
11
+ transaction do
12
+ # defaults
13
+ cart = MerchantSidekick::ShoppingCart::Cart.new
14
+ cart.currency.should == 'USD'
15
+ cart.total_amount == Money.new(0, 'USD')
16
+
17
+ # user and currency
18
+ cart = MerchantSidekick::ShoppingCart::Cart.new('EUR')
19
+ cart.currency.should == 'EUR'
20
+ cart.total_amount == Money.new(0, 'EUR')
21
+ end
22
+ end
23
+
24
+ it "should add one product" do
25
+ transaction do
26
+ widget = products(:widget)
27
+ @cart.add(widget)
28
+ @cart.should_not be_empty
29
+ @cart.total.to_s.should == "29.95"
30
+ @cart.line_items.first.name.should == widget.title
31
+ @cart.line_items.first.item_number.should == widget.id
32
+ end
33
+ end
34
+
35
+ it "should find an item by product" do
36
+ transaction do
37
+ widget = products(:widget)
38
+ @cart.add(widget)
39
+ item = @cart.find(:first, widget)
40
+ item.should == @cart.line_items[0]
41
+ end
42
+ end
43
+
44
+ it "should not find an item by product" do
45
+ transaction do
46
+ widget = products(:widget)
47
+ @cart.add(widget)
48
+ @cart.remove(widget)
49
+ item = @cart.find(:first, widget)
50
+ item.should be_nil
51
+ @cart.should be_empty
52
+ end
53
+ end
54
+
55
+ it "should add a card line item" do
56
+ transaction do
57
+ widget = products(:widget)
58
+ line_item = @cart.cart_line_item(widget)
59
+ @cart.add(line_item)
60
+ @cart.should_not be_empty
61
+ @cart.total.to_s.should == "29.95"
62
+ @cart.line_items.first.name.should == widget.title
63
+ @cart.line_items.first.item_number.should == widget.id
64
+ end
65
+ end
66
+
67
+ it "should add multiple products separately" do
68
+ transaction do
69
+ widget = products(:widget)
70
+ knob = products(:knob)
71
+ @cart.add(widget)
72
+ @cart.total.to_s.should == "29.95"
73
+ @cart.add(widget)
74
+ @cart.line_items.size.should == 1
75
+ @cart.total.to_s.should == "59.90"
76
+ @cart.add(knob)
77
+ @cart.should_not be_empty
78
+ @cart.line_items.size.should == 2
79
+ @cart.total.to_s.should == "63.89"
80
+ end
81
+ end
82
+
83
+ it "should add multiple products at the same time" do
84
+ transaction do
85
+ widget = products(:widget)
86
+ knob = products(:knob)
87
+ @cart.add(widget, 2)
88
+ @cart.line_items.size.should == 1
89
+ @cart.total.to_s.should == "59.90"
90
+ @cart.add(knob, 3)
91
+ @cart.line_items.size.should == 2
92
+ @cart.total.to_s.should == "71.87"
93
+ end
94
+ end
95
+
96
+ it "should remove one cart item by product" do
97
+ transaction do
98
+ widget = products(:widget)
99
+ @cart.add(widget)
100
+ @cart.line_items.size.should == 1
101
+ deleted_line_item = @cart.remove(widget)
102
+ deleted_line_item.class.should == MerchantSidekick::ShoppingCart::LineItem
103
+ deleted_line_item.product == widget
104
+ @cart.should be_empty
105
+ @cart.total.to_s.should == "0.00"
106
+ end
107
+ end
108
+
109
+ it "should remove one cart item by cart_line_item" do
110
+ transaction do
111
+ widget = products(:widget)
112
+ line_item = @cart.add(widget)
113
+ line_item.class.should == MerchantSidekick::ShoppingCart::LineItem
114
+ @cart.line_items.size.should == 1
115
+ deleted_line_item = @cart.remove(line_item)
116
+ deleted_line_item.class.should == MerchantSidekick::ShoppingCart::LineItem
117
+ deleted_line_item.product == widget
118
+ @cart.should be_empty
119
+ @cart.total.to_s.should == "0.00"
120
+ end
121
+ end
122
+
123
+ it "should remove multiple cart item by product" do
124
+ transaction do
125
+ widget = products(:widget)
126
+ @cart.add(widget)
127
+ @cart.add(widget)
128
+ @cart.line_items.size.should == 1
129
+ @cart.total.to_s.should == "59.90"
130
+ @cart.remove(widget)
131
+ @cart.line_items.size.should == 0
132
+ @cart.total.to_s.should == "0.00"
133
+ end
134
+ end
135
+
136
+ it "should empty all cart line items" do
137
+ transaction do
138
+ widget = products(:widget)
139
+ knob = products(:knob)
140
+ 6.times do
141
+ @cart.add(widget)
142
+ end
143
+ @cart.line_items.size.should == 1
144
+ @cart.total.to_s.should == "179.70"
145
+ 6.times do
146
+ @cart.add(knob)
147
+ end
148
+ @cart.line_items.size.should == 2
149
+ @cart.total.to_s.should == "203.64"
150
+ @cart.empty!
151
+ @cart.should be_empty
152
+ @cart.line_items.size.should == 0
153
+ @cart.total.to_s.should == "0.00"
154
+ end
155
+ end
156
+
157
+ it "should update the cart by cart line item" do
158
+ transaction do
159
+ widget = products(:widget)
160
+ knob = products(:knob)
161
+ @cart.add(widget)
162
+ @cart.add(knob)
163
+ @cart.total.to_s.should == "33.94"
164
+ item = @cart.update(@cart.line_items[0], 2)
165
+ item.should == @cart.line_items[0]
166
+ item = @cart.update(@cart.line_items[1], 3)
167
+ item.should == @cart.line_items[1]
168
+ @cart.total.to_s.should == "71.87"
169
+ tbr_item = @cart.find(:first, @cart.line_items[1])
170
+ item = @cart.update(tbr_item, 0)
171
+ item.should == tbr_item
172
+ @cart.total.to_s.should == "59.90"
173
+ end
174
+ end
175
+
176
+ it "should update the cart by product" do
177
+ transaction do
178
+ widget = products(:widget)
179
+ knob = products(:knob)
180
+ @cart.add(widget)
181
+ @cart.add(knob)
182
+ @cart.total.to_s.should == "33.94"
183
+ item = @cart.update(widget, 2)
184
+ item.should_not be_nil
185
+ item.product.should == @cart.line_items[0].product
186
+
187
+ item = @cart.update(knob, 3)
188
+ item.should_not be_nil
189
+ item.product.should == @cart.line_items[1].product
190
+
191
+ @cart.total.to_s.should == "71.87"
192
+ tbr_item = @cart.find(:first, knob)
193
+ item = @cart.update(knob, 0)
194
+ item.should == tbr_item
195
+ @cart.total.to_s.should == "59.90"
196
+ end
197
+ end
198
+
199
+ it "should serialize and deserialize the cart" do
200
+ transaction do
201
+ widget = products(:widget)
202
+ @cart.add(widget)
203
+ @cart.line_items[0].product.should == widget
204
+ # serialize
205
+ session = @cart.to_yaml
206
+ # deserialize
207
+ session_cart = YAML.load(session)
208
+ session_cart.line_items.size.should == 1
209
+ session_cart.line_items[0].product.should == widget
210
+ end
211
+ end
212
+
213
+ end