Market Town: Checkout
Checkout business logic for your ruby e-commerce. This gem is framework independent but provides integration with Spree and Webhooks.
You can introduce MarketTown::Checkout as an interface between your application and your e-commerce backend. Using the power of dependency injection you can provide implementation specific logic for applying promotions, saving addresses, taking payments, etc. in your e-commerce store.
If you've ever wanted to gradually replace Spree with your own system, or split Spree out into a number of different services then you're in the right place.
Mission
- Handle common use cases of checkout step behaviour
- Provide a Spree dependency container for writing your own checkout controllers that speak to Spree objects underneath
- Provide a Webhook dependency container that will forward calls onto a HTTPS interface of your choice
Implementing a checkout
This library provides logic for common steps in a checkout. You can use these steps in your checkout controllers. This example will be in Ruby on Rails but it could be any ruby framework or library, even Rack.
Dependency container
First of all let's create a dependency container for an imaginary e-commerce framework.
class AppContainer < MarketTown::Checkout::Dependencies
class Fulfilments
def can_fulfil_address?(delivery_address)
Ecom::DistributionService.new.check_address(delivery_address)
end
def propose_shipments(state)
state[:order].shipments << Ecom::Shipments.new.propose(state[:delivery_address])
end
end
class AddressStorage
def store(state)
case state[:address_type]
when :delivery
state[:user].shipment_addresses << state[:delivery_address]
when :billing
state[:user].billing_addresses << state[:billing_address]
end
end
end
class Finish
def address_step(state)
state[:order].update!(step: :delivery)
end
end
def fulfilments
Fulfilments.new
end
def address_storage
AddressStorage.new
end
def finish
Finish.new
end
end
Inside this container we wrote some simple adapters to our e-commerce framework. You can of course keep these adapters in another file, we've kept them here to keep the example simple. The container exposes the adapters via methods that are used by MarketTown::Checkout steps.
Generic step controller
Now we can implement our base step controller:
class StepController < ApplicationController
def edit
@order = Ecom::Order.find_by(user: current_user)
end
def update
@order = Ecom::Order.find_by(user: current_user)
process_step
redirect_to checkout_path(@order)
rescue MarketTown::Checkout::Error => e
flash.now[:errors] = [e.error]
render :edit
rescue ActiveRecord::RecordInvalid
render :edit
end
private
def process_step
MarketTown::Checkout.process_step(step: step_name,
dependencies: AppContainer.new,
state: step_state)
end
def step_state
{ user: current_user,
order: @order }
end
end
Address step controller
And now let's create our address checkout step:
class AddressStepController < StepController
private
def step_name
:address
end
def step_state
address_params = %i(name address_1 locality postal_code country save)
super.merge(params.require(:order).permit(billing_address: address_params,
delivery_address: address_params))
end
end
You could create a controller for each step in the same way.