Skip to content

Instantly share code, notes, and snippets.

@cmar
Created November 29, 2012 16:45
Show Gist options
  • Save cmar/4170259 to your computer and use it in GitHub Desktop.
Save cmar/4170259 to your computer and use it in GitHub Desktop.
Tax Cloud Spree Integration
class CreateSpreeTaxCloudTransactions < ActiveRecord::Migration
def change
create_table :spree_tax_cloud_transactions do |t|
t.references :order
t.string :message
t.timestamps
end
add_index :spree_tax_cloud_transactions, :order_id
end
end
class CreateSpreeTaxCloudCartItems < ActiveRecord::Migration
def change
create_table :spree_tax_cloud_cart_items do |t|
t.integer :index
t.decimal :amount, :scale => 5, :precision => 8, :default => 0
t.decimal :ship_total, :scale => 5, :precision => 8, :default => 0
t.references :line_item
t.references :tax_cloud_transaction
t.string :type
t.timestamps
end
add_index :spree_tax_cloud_cart_items, :line_item_id
add_index :spree_tax_cloud_cart_items, :tax_cloud_transaction_id
end
end
Spree::Order.class_eval do
has_one :tax_cloud_transaction
self.state_machine.after_transition :to => :payment,
:do => :lookup_tax_cloud,
:if => :tax_cloud_eligible?
self.state_machine.after_transition :to => :complete,
:do => :capture_tax_cloud,
:if => :tax_cloud_eligible?
def tax_cloud_eligible?
ship_address.try(:state_id?)
end
def lookup_tax_cloud
if tax_cloud_transaction
tax_cloud_transaction.lookup
else
create_tax_cloud_transaction
tax_cloud_transaction.lookup
adjustments.create do |adjustment|
adjustment.source = self
adjustment.originator = tax_cloud_transaction
adjustment.label = 'Tax'
adjustment.mandatory = true
adjustment.eligible = true
adjustment.amount = tax_cloud_transaction.amount
end
end
end
def capture_tax_cloud
return unless tax_cloud_transaction
tax_cloud_transaction.capture
end
end
# Interface to the Tax Cloud SOAP API
module Spree
class TaxCloud
include Spree::Preferences::Preferable
def lookup(tax_cloud_transaction)
client.request(:lookup) do
soap.body = lookup_params(tax_cloud_transaction)
end
end
def lookup_params(tax_cloud_transaction)
order = tax_cloud_transaction.order
default_body.merge({ 'customerID' => order.user_id || order.number,
'cartID' => order.number,
'cartItems' => {'CartItem' => tax_cloud_transaction.cart_items.map(&:to_hash)},
'origin' => destination_address(Spree::Account.first_or_default.address),
'destination' => destination_address(order.ship_address) })
end
def capture(tax_cloud_transaction)
order = tax_cloud_transaction.order
client.request(:authorized_with_capture) do
soap.body =default_body.merge({
'customerID' => order.user_id,
'cartID' => order.number,
'orderID' => order.number,
'dateAuthorized' => DateTime.now,
'dateCaptured' => DateTime.now
})
end
end
def ping
client.request(:ping) do
soap.body = default_body
end
end
private
def client
@client ||= Savon::Client.new("https://api.taxcloud.net/1.0/?wsdl")
end
def default_body
{ 'apiLoginID' => Spree::Config.preferred_tax_cloud_api_login_id,
'apiKey' => Spree::Config.preferred_tax_cloud_api_key }
end
def cart_items(line_items)
index = 0
line_items.map do |line_item|
{ 'CartItem' => { 'Index' => index,
'ItemID' => line_item.variant_id,
'Price' => line_item.price.to_f.to_s,
'Qty' => line_item.quantity }}
end
end
def destination_address(address)
{ 'Address1' => address.address1,
'Address2' => address.address2,
'City' => address.city,
'State' => address.state_text,
'Zip5' => address.zip5,
'Zip4' => nil }
end
def preference_cache_key(name)
[self.class.name, name].join('::').underscore
end
end
end
class Spree::TaxCloudCartItem < ActiveRecord::Base
belongs_to :line_item
belongs_to :tax_cloud_transaction
#validates :tax_cloud_transaction, :presence => true
validates :index, :tic, :sku, :price, :quantity, :presence => true
attr_accessible :index, :tic, :sku, :price, :quantity
def to_hash
{
'Index' => index,
'TIC' => tic,
'ItemID' => sku,
'Price' => price.to_s,
'Qty' => quantity
}
end
end
require 'spec_helper'
describe Spree::TaxCloudCartItem do
let(:line_item) { Factory.create(:line_item) }
it "builds a hash with the line_item properties" do
cart_item = Spree::TaxCloudCartItem.new(:index => 1,
:tic => 0,
:sku => 'SKU-001',
:quantity => 2,
:price => 5.99,
:line_item => line_item)
hash = cart_item.to_hash
hash['Index'].should eq 1
hash['Price'].should eq "5.99"
hash['Qty'].should eq 2
hash['ItemID'].should eq 'SKU-001'
end
end
require 'spec_helper'
module Spree
describe TaxCloud do
let(:order) { Factory.build(:order, :ship_address => Factory.build(:address)) }
let(:tax_cloud_transaction) {
tax_cloud_transaction = TaxCloudTransaction.new
tax_cloud_transaction.order = order
tax_cloud_transaction.cart_items = []
tax_cloud_transaction
}
before :each do
@account = Account.first_or_default
@account.address = Factory.build(:address,
:zipcode => '90120')
@account.save
@params = subject.lookup_params(tax_cloud_transaction)
end
it 'store account address is used for origin' do
@params['origin']['Address1'].should eq @account.address.address1
@params['origin']['Address2'].should eq @account.address.address2
@params['origin']['City'].should eq @account.address.city
@params['origin']['Zip5'].should eq @account.address.zipcode
end
it 'uses order ship address for destination' do
@params['destination']['Address1'].should eq order.ship_address.address1
@params['destination']['Address2'].should eq order.ship_address.address2
@params['destination']['City'].should eq order.ship_address.city
@params['destination']['Zip5'].should eq order.ship_address.zipcode
end
it 'order number is used for cartID' do
@params['CartID'].should eq order.number
end
end
end
# Designed to be the Originator for an Adjustment
# on an order
module Spree
class TaxCloudTransaction < ActiveRecord::Base
belongs_to :order
validates :order, :presence => true
has_one :adjustment, :as => :originator
has_many :cart_items, :class_name => 'TaxCloudCartItem',
:dependent => :destroy
# called when order updates adjustments
# should be fast!
def update_adjustment(adjustment, source)
adjustment.update_attribute_without_callbacks(:amount, amount)
end
def amount
cart_items.sum(&:amount)
end
def lookup
begin
create_cart_items
response = tax_cloud.lookup(self)
raise 'Tax Cloud Lookup Error' unless response.success?
transaction do
unless response.body[:lookup_response][:lookup_result][:messages].nil?
self.message = response.body[:lookup_response][:lookup_result][:messages][:response_message][:message]
end
self.save
response_cart_items = Array.wrap response.body[:lookup_response][:lookup_result][:cart_items_response][:cart_item_response]
response_cart_items.each do |response_cart_item|
cart_item = cart_items.find_by_index(response_cart_item[:cart_item_index].to_i)
cart_item.update_attribute(:amount, response_cart_item[:tax_amount].to_f)
end
end
rescue => ex
Exceptional.handle ex
end
end
def capture
begin
tax_cloud.capture(self)
rescue => ex
Exceptional.handle ex
end
end
private
def tax_cloud
@tax_cloud ||= TaxCloud.new
end
def create_cart_items
cart_items.clear
index = 0
order.line_items.each do |line_item|
cart_items.create!({
:index => (index += 1),
:tic => '0', # TODO: replace with something more meaningful
:sku => line_item.variant.sku.presence || line_item.variant.id,
:quantity => line_item.quantity,
:price => line_item.price.to_f,
:line_item => line_item
})
end
cart_items.create!({
:index => (index += 1),
:tic => '11000',
:sku => 'SHIPPING',
:quantity => 1,
:price => order.ship_total.to_f
})
end
end
end
require 'spec_helper'
describe Spree::TaxCloudTransaction do
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment