muck-commerce 0.1.9 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/README.rdoc +6 -0
  2. data/VERSION +1 -1
  3. data/app/controllers/muck/billing_informations_controller.rb +1 -1
  4. data/app/controllers/muck/orders_controller.rb +22 -10
  5. data/app/controllers/muck/subscriptions_controller.rb +1 -1
  6. data/db/migrate/20100408035338_move_customer_profile_id.rb +18 -0
  7. data/lib/action_controller/muck_billing_application.rb +2 -2
  8. data/lib/active_record/acts/muck_billing_information.rb +2 -2
  9. data/lib/active_record/acts/muck_cart_item.rb +38 -0
  10. data/lib/active_record/acts/muck_commerce_user.rb +5 -0
  11. data/lib/active_record/acts/muck_order.rb +5 -3
  12. data/lib/active_record/acts/muck_order_transaction.rb +117 -73
  13. data/lib/test/muck_commerce_factories.rb +26 -17
  14. data/locales/en.yml +6 -1
  15. data/muck-commerce.gemspec +10 -2
  16. data/test/rails_root/app/models/billing_information.rb +36 -1
  17. data/test/rails_root/app/models/cart.rb +12 -0
  18. data/test/rails_root/app/models/cart_coupon.rb +11 -0
  19. data/test/rails_root/app/models/coupon.rb +21 -1
  20. data/test/rails_root/app/models/coupon_type.rb +9 -1
  21. data/test/rails_root/app/models/order.rb +21 -1
  22. data/test/rails_root/app/models/order_coupon.rb +12 -1
  23. data/test/rails_root/app/models/order_transaction.rb +20 -1
  24. data/test/rails_root/app/models/profile.rb +23 -0
  25. data/test/rails_root/app/models/subscription.rb +22 -1
  26. data/test/rails_root/app/models/subscription_plan.rb +15 -1
  27. data/test/rails_root/app/models/user.rb +33 -1
  28. data/test/rails_root/config/environment.rb +1 -1
  29. data/test/rails_root/db/migrate/20100408035338_move_customer_profile_id.rb +18 -0
  30. data/test/rails_root/remote/functional/remote_orders_controller_test.rb +58 -58
  31. data/test/rails_root/remote/functional/remote_subscriptions_controller_test.rb +9 -0
  32. data/test/rails_root/remote/remote_helper.rb +44 -15
  33. data/test/rails_root/remote/unit/remote_order_test.rb +7 -8
  34. data/test/rails_root/remote/unit/remote_order_transaction_test.rb +60 -34
  35. data/test/rails_root/test/functional/admin/billing_informations_controller_test.rb +7 -8
  36. data/test/rails_root/test/unit/billing_information_test.rb +78 -0
  37. data/test/rails_root/test/unit/cart_coupon_test.rb +12 -1
  38. data/test/rails_root/test/unit/coupon_test.rb +21 -1
  39. data/test/rails_root/test/unit/coupon_type_test.rb +9 -1
  40. data/test/rails_root/test/unit/order_test.rb +21 -1
  41. data/test/rails_root/test/unit/order_transaction_test.rb +19 -0
  42. data/test/rails_root/test/unit/subscription_test.rb +40 -9
  43. data/test/rails_root/test/unit/user_test.rb +33 -1
  44. metadata +12 -4
data/README.rdoc CHANGED
@@ -105,6 +105,12 @@ subscription_record.rb
105
105
  The Authorize.net CIM code uses decimals NOT integers like the rest of the ActiveMerchant API. This means that you must convert
106
106
  1000 cents into 10.00 dollars before sending the transaction or else you will bill the user $1000.
107
107
  !!!!!!!!!!!!!!!!!!!!!!!!!
108
+
109
+ == Tests
110
+ This project includes tests that run local and tests that run against a remote test gateway. After configuring all required settings
111
+ in global_config.yml you can run remote tests against a live test gateway with:
112
+
113
+ rake test:remote
108
114
 
109
115
  == Copyright
110
116
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.9
1
+ 0.2.0
@@ -29,7 +29,7 @@ class Muck::BillingInformationsController < ApplicationController
29
29
  if @billing_information.update_attributes(params[:billing_information])
30
30
  if MuckCommerce::Configure.cim_enabled?
31
31
  # update billing info in the CIM
32
- response = OrderTransaction.cim_create_update(@billing_information)
32
+ response = OrderTransaction.cim_create_update(@billing_information.billable, @billing_information)
33
33
  @billing_information.billable.order_transactions.push(response)
34
34
  if response.success?
35
35
  flash[:notice] = translate('muck.commerce.billing_information_updated_friendly')
@@ -8,9 +8,10 @@ class Muck::OrdersController < ApplicationController
8
8
  ssl_required :express, :new, :create
9
9
 
10
10
  before_filter :store_location
11
+ before_filter :login_required, :except => [:new]
11
12
  before_filter :get_user
12
13
 
13
- # Enable these if using the cart system
14
+ # Enable these in overriden controller if using the cart system
14
15
  #before_filter :recover_cart, :only => [:new, :express, :create, :update]
15
16
  #before_filter :check_empty_cart, :only => [:express, :new, :create]
16
17
 
@@ -46,22 +47,24 @@ class Muck::OrdersController < ApplicationController
46
47
  end
47
48
 
48
49
  def create
50
+ options = {}
49
51
  @paypal_express_checkout = false
52
+ valid = true
50
53
  if params[:paypal]
51
54
  @order = current_user.orders.build(params[:order])
52
55
  @order.ip_address = request.remote_ip
53
56
  @order.paypal_express_checkout(get_order_amount)
54
57
  @paypal_express_checkout = true
55
58
  else
59
+
56
60
  if @user.blank?
57
61
  @user = User.new(params[:user])
58
62
  @user.save!
59
63
  # valid payment information should be enough to log the user in
60
64
  @user.force_activate!
61
65
  self.current_user = @user
62
-
63
- else
64
- @user.update_attributes(params[:user])
66
+ elsif params[:user]
67
+ @user.update_attributes!(params[:user])
65
68
  end
66
69
 
67
70
  @order ||= Order.new
@@ -74,20 +77,29 @@ class Muck::OrdersController < ApplicationController
74
77
  @billing_information = @user.billing_information
75
78
 
76
79
  if @billing_information.blank?
80
+ raise MuckCommerce::Exceptions::PaymentGatewayError, I18n.translate('muck.commerce.missing_billing_information') unless params[:billing_information]
77
81
  @billing_information = @user.billing_informations.create(params[:billing_information].merge(:payment_method => 'CC', :default => true))
78
82
  @billing_information.save!
79
- else
80
- @billing_information.update_attributes!(params[:@billing_information])
83
+ elsif params[:billing_information]
84
+ @billing_information.update_attributes!(params[:billing_information])
85
+ raise ActiveRecord::RecordInvalid, @billing_information unless @billing_information.billing_valid?
86
+ options[:update_billing_information] = true
81
87
  end
82
88
 
83
89
  # Validate billing information
84
- @billing_information.billing_valid?
85
-
86
- @order.checkout(@billing_information, get_order_amount, @coupon_codes)
90
+ if valid = @billing_information.has_billing_profile? || @billing_information.billing_valid?
91
+ @order.checkout(@billing_information, get_order_amount, @coupon_codes, options)
92
+ end
87
93
  end
88
94
 
89
95
  respond_to do |format|
90
- format.html { redirect_to user_order_path(@user, @order, :new_order => true) }
96
+ format.html do
97
+ if valid
98
+ redirect_to user_order_path(@user, @order, :new_order => true)
99
+ else
100
+ render :template => 'orders/new'
101
+ end
102
+ end
91
103
  end
92
104
 
93
105
  rescue MuckCommerce::Exceptions::PaymentGatewayError => ex
@@ -78,7 +78,7 @@ class Muck::SubscriptionsController < ApplicationController
78
78
  @subscription = Subscription.find(params[:id])
79
79
  @subscription.destroy
80
80
  flash[:notice] = translate('muck.commerce.subscription_deleted')
81
- redirect_to new_subscription_path
81
+ redirect_to new_subscription_path
82
82
  end
83
83
 
84
84
  # Paypal functionality
@@ -0,0 +1,18 @@
1
+ class MoveCustomerProfileId < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :users, :customer_profile_id, :string, :limit => 512
4
+ BillingInformation.all.each do |bi|
5
+ bi.billable.update_attribute(:customer_profile_id, bi.customer_profile_id)
6
+ end
7
+ remove_column :billing_informations, :customer_profile_id
8
+ end
9
+
10
+ def self.down
11
+ add_column :billing_informations, :customer_profile_id, :string, :limit => 512
12
+ BillingInformation.all.each do |bi|
13
+ bi.update_attribute(:customer_profile_id, bi.billable.customer_profile_id)
14
+ end
15
+ remove_column :users, :customer_profile_id
16
+ end
17
+
18
+ end
@@ -53,9 +53,9 @@ module ActionController
53
53
  if success = @billing_information.credit_card.valid?
54
54
  success = @billing_information.save
55
55
  if success
56
- if GlobalConfig.cim_gateway
56
+ if MuckCommerce::Configure.cim_enabled?
57
57
  begin
58
- response = OrderTransaction.cim_create(@billing_information)
58
+ response = OrderTransaction.cim_create(@user, @billing_information)
59
59
  @user.order_transactions.push(response)
60
60
  rescue MuckCommerce::Exceptions::PaymentGatewayError => ex
61
61
  flash[:error] = I18n.translate('muck.commerce.billing_information_problem', :message => ex.to_s)
@@ -126,7 +126,7 @@ module ActiveRecord
126
126
  message = ''
127
127
  if MuckCommerce::Configure.cim_enabled?
128
128
  # Update CIM if billing information has changed
129
- response = OrderTransaction.cim_create_update(@billing_information, @billing_information.credit_card_number_available?)
129
+ response = OrderTransaction.cim_create_update(@billing_information.billable, @billing_information, @billing_information.credit_card_number_available?)
130
130
  if response.success?
131
131
  message = translate('muck.commerce.billing_information_updated')
132
132
  else
@@ -203,7 +203,7 @@ module ActiveRecord
203
203
  end
204
204
 
205
205
  def has_billing_profile?
206
- !self.customer_profile_id.blank? || !self.customer_payment_profile_id.blank?
206
+ !self.billable.customer_profile_id.blank? && !self.customer_payment_profile_id.blank?
207
207
  end
208
208
 
209
209
  private
@@ -0,0 +1,38 @@
1
+ module ActiveRecord
2
+ module Acts #:nodoc:
3
+ module MuckCartItem #:nodoc:
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+ # Use this class to link any object into the cart. The object must implement and 'amount' method
11
+ def acts_as_muck_cart_item(options = {})
12
+ include MuckCommerce::CurrencyMethods
13
+ include MuckCommerce::DiscountMethods
14
+
15
+ belongs_to :cart
16
+ belongs_to :cartable, :polymorphic => true
17
+
18
+ include ActiveRecord::Acts::MuckCart::InstanceMethods
19
+ extend ActiveRecord::Acts::MuckCart::SingletonMethods
20
+ end
21
+
22
+ end
23
+
24
+ # class methods
25
+ module SingletonMethods
26
+
27
+ end
28
+
29
+ # All the methods available to a record that has had <tt>acts_as_muck_cart</tt> specified.
30
+ module InstanceMethods
31
+
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -35,6 +35,11 @@ module ActiveRecord
35
35
 
36
36
  module InstanceMethods
37
37
 
38
+ # This is the description that will be passed to the CIM system
39
+ def cim_description
40
+ self.display_name
41
+ end
42
+
38
43
  def subscription
39
44
  @default_subscription ||= self.subscriptions.default.first
40
45
  end
@@ -99,9 +99,11 @@ module ActiveRecord
99
99
  success = true
100
100
  else
101
101
 
102
- if MuckCommerce::Configure.cim_enabled? && !billing_information.has_billing_profile?
103
- response = OrderTransaction.cim_create(billing_information)
104
- raise MuckCommerce::Exceptions::PaymentGatewayError, I18n.translate('muck.commerce.problem_processing_your_order', :message => response.message) unless response.success?
102
+ if MuckCommerce::Configure.cim_enabled?
103
+ if !billing_information.has_billing_profile? || options[:update_billing_information]
104
+ response = OrderTransaction.cim_create_update(billing_information.billable, billing_information)
105
+ raise MuckCommerce::Exceptions::PaymentGatewayError, I18n.translate('muck.commerce.problem_processing_your_order', :message => response.message) unless response.success?
106
+ end
105
107
  end
106
108
 
107
109
  if GlobalConfig.authorize_orders_first
@@ -58,7 +58,7 @@ module ActiveRecord
58
58
  :type => :auth_only,
59
59
  :amount => sprintf("%.2f", amount.to_f / 100) # Convert into decimal amount. HACK/TODO the rest of the library uses cents. One day this value might need to be cents instead of dollars
60
60
  }
61
- transaction[:customer_profile_id] = billing_information.customer_profile_id
61
+ transaction[:customer_profile_id] = billing_information.billable.customer_profile_id
62
62
  transaction[:customer_payment_profile_id] = billing_information.customer_payment_profile_id
63
63
  process(I18n.translate('muck.commerce.authorization_cim'), amount, true) do |gw|
64
64
  gw.create_customer_profile_transaction(:transaction => transaction)
@@ -72,7 +72,7 @@ module ActiveRecord
72
72
  :type => :auth_capture,
73
73
  :amount => sprintf("%.2f", amount.to_f / 100) # Convert into decimal amount. HACK/TODO the rest of the library uses cents. One day this value might need to be cents instead of dollars
74
74
  }
75
- transaction[:customer_profile_id] = billing_information.customer_profile_id
75
+ transaction[:customer_profile_id] = billing_information.billable.customer_profile_id
76
76
  transaction[:customer_payment_profile_id] = billing_information.customer_payment_profile_id
77
77
  process(I18n.translate('muck.commerce.purchase_cim'), amount, true) do |gw|
78
78
  gw.create_customer_profile_transaction(:transaction => transaction)
@@ -80,118 +80,162 @@ module ActiveRecord
80
80
  end
81
81
 
82
82
  # Creates an entry in the CIM system for the given billing information
83
- def cim_create(billing_information)
83
+ def cim_create(billable, billing_information)
84
84
  process(I18n.translate('muck.commerce.cim_create'), 0, true) do |gw|
85
- response = gw.create_customer_profile(:profile => get_cim_profile(billing_information))
85
+ response = gw.create_customer_profile(:profile => get_cim_profile(billing_information.billable, billing_information))
86
86
  if response.success?
87
87
  # these are protected attributes so mass updates are not allowed
88
- billing_information.customer_profile_id = response.authorization
88
+ billable.customer_profile_id = response.authorization
89
+ billable.save!
89
90
  billing_information.customer_payment_profile_id = response.params["customer_payment_profile_id_list"]["numeric_string"]
90
91
  billing_information.save!
92
+ billing_information.reload # associated billable object has changed and needs to be reloaded
91
93
  end
92
94
  response
93
95
  end
94
96
  end
95
97
 
96
- # Updates information in the CIM system
97
- def cim_update(billing_information, validate = true)
98
- profile = get_cim_profile(billing_information, validate)
99
- profile[:customer_profile_id] = billing_information.customer_profile_id
98
+ # Updates user information in the CIM system
99
+ def cim_update(billable, validate = true)
100
+ profile = get_cim_profile(billable, nil, validate)
101
+ profile[:customer_profile_id] = billable.customer_profile_id
100
102
  process(I18n.translate('muck.commerce.cim_update_user'), 0, true) do |gw|
101
103
  gw.update_customer_profile(:profile => profile)
102
104
  end
103
105
  end
104
106
 
107
+ # Updates billing information in the CIM system
108
+ def cim_update_payment_profile(billing_information, validate = true)
109
+ profile = get_cim_payment_profile(billing_information, validate)
110
+ process(I18n.translate('muck.commerce.cim_update_payment_profile'), 0, true) do |gw|
111
+ gw.update_customer_payment_profile( :customer_profile_id => billing_information.billable.customer_profile_id,
112
+ :payment_profile => profile)
113
+ end
114
+ end
115
+
105
116
  # Creates or updates billing information in the CIM based on whether or not it already exists.
106
- def cim_create_update(billing_information, validate = true)
117
+ def cim_create_update(billable, billing_information, validate = true)
107
118
  if billing_information.has_billing_profile?
108
- self.cim_update(billing_information, validate)
119
+ response = self.cim_update(billable, validate)
120
+ if response.success?
121
+ self.cim_update_payment_profile(billing_information, validate)
122
+ else
123
+ response
124
+ end
109
125
  else
110
- self.cim_create(billing_information)
126
+ self.cim_create(billable, billing_information)
111
127
  end
112
128
  end
113
129
 
114
130
  # Remove billing information from the CIM system.
115
- def cim_delete(billing_information)
131
+ def cim_delete(customer_profile_id)
132
+ cim_delete_by_customer_profile_id(customer_profile_id)
133
+ end
134
+
135
+ def cim_delete_by_customer_profile_id(customer_profile_id)
116
136
  process(I18n.translate('muck.commerce.cim_delete_user'), 0, true) do |gw|
117
- gw.delete_customer_profile(:customer_profile_id => billing_information.customer_profile_id)
137
+ gw.delete_customer_profile(:customer_profile_id => customer_profile_id)
118
138
  end
119
139
  end
120
140
 
121
141
  # validates an existing profile
122
- def cim_validate_user(billing_information)
142
+ def cim_validate_customer_payment_profile(billing_information)
123
143
  process(I18n.translate('muck.commerce.cim_validate_profile'), 0, true) do |gw|
124
144
  gw.validate_customer_payment_profile(
125
- :customer_profile_id => billing_information.customer_profile_id,
126
- :customer_payment_profile_id => billing_information.customer_payment_profile_id,
127
- :validation_mode => :live)
145
+ :customer_profile_id => billing_information.billable.customer_profile_id,
146
+ :customer_payment_profile_id => billing_information.customer_payment_profile_id,
147
+ :validation_mode => :live)
128
148
  end
129
149
  end
130
150
 
151
+ # Gets an existing profile
152
+ def cim_get_customer_profile(customer_profile_id)
153
+ process(I18n.translate('muck.commerce.cim_get_customer_profile'), 0, true) do |gw|
154
+ gw.get_customer_profile(
155
+ :customer_profile_id => customer_profile_id)
156
+ end
157
+ end
158
+
159
+ # Gets an existing customer payment profile
160
+ def cim_get_customer_payment_profile(customer_profile_id, customer_payment_profile_id)
161
+ process(I18n.translate('muck.commerce.cim_get_payment_customer_profile'), 0, true) do |gw|
162
+ gw.get_customer_payment_profile(
163
+ :customer_profile_id => customer_profile_id,
164
+ :customer_payment_profile_id => customer_payment_profile_id)
165
+ end
166
+ end
167
+
131
168
  # Transfer money to an email account using paypal
132
169
  def send_money_via_paypal(amount, email, options = {})
133
170
  process(I18n.translate('muck.commerce.send_money_via_paypal'), amount) do |gw|
134
171
  gw.transfer(amount, email, options)
135
- end
172
+ end
136
173
  end
137
174
 
138
- private
139
-
140
- # Get a profile object compatible with the CIM system.
141
- def get_cim_profile (billing_information, validate = true)
142
- validate_card(billing_information) if validate
143
- billable = billing_information.billable
144
- payment = { :credit_card => billing_information.credit_card_for_cim }
145
- address = { :first_name => billing_information.first_name,
146
- :last_name => billing_information.last_name }
147
- address[:company] = billing_information.company unless billing_information.company.blank?
148
- address[:address] = "#{billing_information.address1} #{billing_information.address2}" unless billing_information.address1.blank?
149
- address[:city] = billing_information.city unless billing_information.city.blank?
150
- address[:state] = billing_information.state.name unless billing_information.state.blank?
151
- address[:country] = billing_information.country.name unless billing_information.country.blank?
152
- address[:zip] = billing_information.postal_code unless billing_information.postal_code.blank?
153
- profile = {
154
- :merchant_customer_id => billable.id,
155
- :description => "#{billable.type}",
156
- :email => billable.email,
157
- :payment_profiles => {
158
- :customer_type => 'individual',
159
- :bill_to => address,
160
- :payment => payment
161
- }
162
- }
163
- profile
164
- end
165
-
166
- def validate_card(billing)
167
- raise MuckCommerce::Exceptions::PaymentGatewayError, I18n.translate('muck.commerce.invalid_credit_card_information') if !billing.credit_card.valid?
175
+ # Get a profile object compatible with the CIM system.
176
+ def get_cim_profile(billable, billing_information = nil, validate = true, customer_type = 'individual')
177
+ profile = {
178
+ :merchant_customer_id => billable.id,
179
+ :description => billable.cim_description,
180
+ :email => billable.email }
181
+ if billing_information
182
+ if billing_information.has_billing_profile?
183
+ profile[:payment_profile] = get_cim_payment_profile(billing_information, validate, customer_type)
184
+ else
185
+ profile[:payment_profiles] = get_cim_payment_profile(billing_information, validate, customer_type)
186
+ end
168
187
  end
169
-
170
- def process(action, amount = nil, cim_transaction = false)
171
- result = OrderTransaction.new
172
- result.amount = amount
173
- result.action = action
174
- begin
175
- if cim_transaction
176
- response = yield cim_gateway
177
- else
178
- response = yield gateway
179
- end
180
- result.success = response.success?
181
- result.reference = response.authorization
182
- result.message = response.message
183
- result.params = response.params
184
- result.test = response.test?
185
- rescue ActiveMerchant::ActiveMerchantError => e
186
- result.success = false
187
- result.reference = nil
188
- result.message = e.message
189
- result.params = {}
190
- result.test = gateway.test?
188
+ profile
189
+ end
190
+
191
+ def get_cim_payment_profile(billing_information, validate = true, customer_type = 'individual')
192
+ validate_card(billing_information) if validate
193
+ payment = { :credit_card => billing_information.credit_card_for_cim }
194
+ address = { :first_name => billing_information.first_name,
195
+ :last_name => billing_information.last_name }
196
+ address[:company] = billing_information.company unless billing_information.company.blank?
197
+ address[:address] = "#{billing_information.address1} #{billing_information.address2}" unless billing_information.address1.blank?
198
+ address[:city] = billing_information.city unless billing_information.city.blank?
199
+ address[:state] = billing_information.state.name unless billing_information.state.blank?
200
+ address[:country] = billing_information.country.name unless billing_information.country.blank?
201
+ address[:zip] = billing_information.postal_code unless billing_information.postal_code.blank?
202
+ payment_profile = { :customer_type => customer_type,
203
+ :bill_to => address,
204
+ :payment => payment }
205
+ payment_profile[:customer_payment_profile_id] = billing_information.customer_payment_profile_id if billing_information.customer_payment_profile_id
206
+ payment_profile
207
+ end
208
+
209
+ def validate_card(billing)
210
+ raise MuckCommerce::Exceptions::PaymentGatewayError, I18n.translate('muck.commerce.invalid_credit_card_information') if !billing.credit_card.valid?
211
+ end
212
+
213
+ def process(action, amount = nil, cim_transaction = false)
214
+ result = OrderTransaction.new
215
+ result.amount = amount
216
+ result.action = action
217
+ begin
218
+ if cim_transaction
219
+ response = yield cim_gateway
220
+ else
221
+ response = yield gateway
191
222
  end
192
-
193
- result
223
+ result.success = response.success?
224
+ result.reference = response.authorization
225
+ result.message = response.message
226
+ result.params = response.params
227
+ result.test = response.test?
228
+ rescue ActiveMerchant::ActiveMerchantError => e
229
+ result.success = false
230
+ result.reference = nil
231
+ result.message = e.message
232
+ result.params = {}
233
+ result.test = gateway.test?
194
234
  end
235
+
236
+ result
237
+ end
238
+
195
239
  end
196
240
 
197
241