Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Money Train

The Money Train

Selling music using Ruby on Rails / Building a storefront with Rails

Presented at RailsConf 2006

Benjamin Curtis

June 23, 2006
Tweet

More Decks by Benjamin Curtis

Other Decks in Programming

Transcript

  1. Why build a store? Because you’d be out of a

    job if you told your client to just use Shopify :)
  2. Getting Started • Product info and product shots • Merchant

    acct. and gateway • Shipping vendors • SSL certificate • Site design
  3. Product Differentiation class
Album < Product 

belongs_to
:artist 

has_many
:tracks,
:order
=>
'position',
 



:dependent
=>
true, 



:foreign_key
=>
'product_id' 



    

def
self.featured_products 



find(:all,
:include
=>
:artist,
 





:conditions
=>
[
'featured
is
not
null'
], 





:order
=>
'featured
desc') 

end end
  4. Pass the buck class
Sale < ActiveRecord::Base 

def
validate_on_create 



... 



amount_to_auth
=
self.total_price
-
self.voucher_total 





    



self.payment_transaction
=
 





self.payment_method.authorize(amount_to_auth)
if
 







amount_to_auth
>
0 



...



 

rescue
PaymentMethodError
=>
error 



errors.add_to_base(error.message) 



return
false 

end end
  5. The buck stops here (kinda) class
PaymentMethod < ActiveRecord::Base 

def
authorize(amount) 



processor
=
Payment::TrustCommerce.new(

    





'billingid'
=>
self.processor_id) 



if
!processor.process('preauth',
 





'amount'
=>
amount.to_s) 





raise
PaymentMethodError,
processor.error 



end 



self.payment_transactions.create( 





:processor
=>
processor,
 





:action
=>
'Authorization') 

end end
  6. Sensitive Data • Objective: Speedy checkout for customers • Problem:

    CC data == liability • Solution: Pass the buck • Alternate solution: Encryption Or see http:/ / blog.leetsoft.com/ articles/2006/03/14/ simple-encryption for thoughts on handling the encryption yourself
  7. class
LineItem < ActiveRecord::Base 

has_many
:line_item_product_options 

has_many
:product_options,
 



:through
=>
:line_item_product_options 

def
product_options=(options) 



options
=
[
options
]
unless
options.respond_to?(:each) 



options.each
do
|opt| 





self.line_item_product_options.build(

    







:product_option_id
=>
opt.id, 







:price
=>
opt.price,
:weight
=>
opt.weight) 



end 

end 

 

def
product_option_names 



self.product_options.collect(&:name).to_sentence 

end end Product Options
  8. Taxes class
Tax 

#
Return
the
tax
rate
(float)
for
an
address
object. 

def
self.for_address(address) 



return
0
unless
address.is_a?(Address) 



return
0.088
if
address.state
and 





address.state.abbreviation
==
'WA' 



return
0 

end 

def
self.on_product(product,

    price, address) 



return
0
unless
product.taxable?
and
price.to_i
>
0
and
 





!address.nil? 



return
(price.to_i
*
(address.is_a?(Address)
?
 





for_address(address)
:
address)).round 

end end
  9. Taxes class
LineItem < ActiveRecord::Base 

def
self.for_product_variant(product_variant_id, address = nil) 



pv
=
ProductVariant.find(product_variant_id,
 





:include
=>
[
:variant,
:product
])
rescue
nil

    



return
false
if
pv.nil?
||
!pv.can_order? 



self.new(:product_variant
=>
pv,
 





:price
=>
pv.price,
:cost
=>
pv.cost, 





:quantity
=>
1,
:weight
=>
pv.weight.to_f, 





:tax
=>
Tax.on_product(pv.product,
pv.price,
 







address)) 

end end
  10. class
Sale < ActiveRecord::Base 

def
refund(item) 



transaction
do 





remainder
=
self.credit_vouchers( 







item.total(!item.shipped?),
item.shipped?) 





if
remainder
>
0
and
item.shipped? 







self.payment_method.refund(self,
remainder) 





end

    



end 

end

 end Refund class
LineItem < ActiveRecord::Base 

def
total(include_options = true) 



(self.price
*
self.quantity)
+
self.tax
+
 





(include_options
?
 







self.product_options.collect(&:price).sum
:
0) 

end end
  11. Affiliate in da house class
ApplicationController < ActionController::Base 

before_filter
:affiliate_check 

def
affiliate_check 



if
affiliate
=
params[:affiliate]

    





cookies[:affiliate]
=
{
:value
=>
affiliate,
 







:expires
=>
1.week.from_now
} 



end 

end end
  12. Capture the affiliate sale class
CartController < ApplicationController 

def
checkout 



... 



@sale
=
Sale.new(:account
=>
@session[:user],


    





:payment_method
=>
@session[:payment], 





:cart
=>
@cart, 





:affiliate
=>
Account.find_by_affiliate_token( 







cookies[:affiliate])) 



... 

end end
  13. Credit the affiliate class
Shipment < ActiveRecord::Base 

after_create
:credit_affiliate 

def
credit_affiliate 



if
self.affiliate 





self.affiliate_credits.create(

    







:account
=>
self.affiliate, 







:amount
=>
(self.total
*
 









self.affiliate.percentage(self.id)).round) 



end 

end end