market_town-checkout 0.1.0
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.
- checksums.yaml +7 -0
- data/README.md +138 -0
- data/lib/market_town/checkout/dependencies.rb +105 -0
- data/lib/market_town/checkout/error.rb +15 -0
- data/lib/market_town/checkout/missing_dependency.rb +22 -0
- data/lib/market_town/checkout/models/address.rb +77 -0
- data/lib/market_town/checkout/steps/address_step.rb +64 -0
- data/lib/market_town/checkout/steps/cart_step.rb +24 -0
- data/lib/market_town/checkout/steps/complete_step.rb +36 -0
- data/lib/market_town/checkout/steps/delivery_step.rb +39 -0
- data/lib/market_town/checkout/steps/step.rb +41 -0
- data/lib/market_town/checkout/version.rb +5 -0
- data/lib/market_town/checkout.rb +55 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ccf737f8e5d5e1b99350ca5c2e7c69595813de52
|
4
|
+
data.tar.gz: b61d9955fd58a7de3ec72fe50720e49c6c8a659a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5e3c575fbca0a8c72411e092cda3edd013249b7700e083171f52ee64c724c7cad18d775c0eef7cd598ee96b156c3584982af1618de128417412520471d1fc7b1
|
7
|
+
data.tar.gz: f0b50505161df4ee7d1333d36a178698283601b9b16e1749baec106049ee9f31606eead57107ec44591855c0e49e8d63a9ba976555f9b766423dd608303d58aa
|
data/README.md
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# Market Town: Checkout
|
2
|
+
|
3
|
+
Checkout business logic for your ruby e-commerce. This gem is framework
|
4
|
+
independent but provides integration with Spree and Webhooks.
|
5
|
+
|
6
|
+
You can introduce MarketTown::Checkout as an interface between your application
|
7
|
+
and your e-commerce backend. Using the power of dependency injection you can
|
8
|
+
provide implementation specific logic for applying promotions, saving addresses,
|
9
|
+
taking payments, etc. in your e-commerce store.
|
10
|
+
|
11
|
+
If you've ever wanted to gradually replace Spree with your own system, or split
|
12
|
+
Spree out into a number of different services then you're in the right place.
|
13
|
+
|
14
|
+
## Mission
|
15
|
+
|
16
|
+
- Handle common use cases of checkout step behaviour
|
17
|
+
- Provide a Spree dependency container for writing your own checkout
|
18
|
+
controllers that speak to Spree objects underneath
|
19
|
+
- Provide a Webhook dependency container that will forward calls onto a HTTPS
|
20
|
+
interface of your choice
|
21
|
+
|
22
|
+
## Implementing a checkout
|
23
|
+
|
24
|
+
This library provides logic for common steps in a checkout. You can use these
|
25
|
+
steps in your checkout controllers. This example will be in Ruby on Rails but it
|
26
|
+
could be any ruby framework or library, even Rack.
|
27
|
+
|
28
|
+
### Dependency container
|
29
|
+
|
30
|
+
First of all let's create a dependency container for an imaginary e-commerce
|
31
|
+
framework.
|
32
|
+
|
33
|
+
``` ruby
|
34
|
+
class AppContainer < MarketTown::Checkout::Dependencies
|
35
|
+
class Fulfilments
|
36
|
+
def can_fulfil_address?(delivery_address)
|
37
|
+
Ecom::DistributionService.new.check_address(delivery_address)
|
38
|
+
end
|
39
|
+
|
40
|
+
def propose_shipments(state)
|
41
|
+
state[:order].shipments << Ecom::Shipments.new.propose(state[:delivery_address])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class AddressStorage
|
46
|
+
def store(state)
|
47
|
+
case state[:address_type]
|
48
|
+
when :delivery
|
49
|
+
state[:user].shipment_addresses << state[:delivery_address]
|
50
|
+
when :billing
|
51
|
+
state[:user].billing_addresses << state[:billing_address]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Finish
|
57
|
+
def address_step(state)
|
58
|
+
state[:order].update!(step: :delivery)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def fulfilments
|
63
|
+
Fulfilments.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def address_storage
|
67
|
+
AddressStorage.new
|
68
|
+
end
|
69
|
+
|
70
|
+
def finish
|
71
|
+
Finish.new
|
72
|
+
end
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
Inside this container we wrote some simple adapters to our e-commerce framework.
|
77
|
+
You can of course keep these adapters in another file, we've kept them here to
|
78
|
+
keep the example simple. The container exposes the adapters via methods that
|
79
|
+
are used by MarketTown::Checkout steps.
|
80
|
+
|
81
|
+
### Generic step controller
|
82
|
+
|
83
|
+
Now we can implement our base step controller:
|
84
|
+
|
85
|
+
``` ruby
|
86
|
+
class StepController < ApplicationController
|
87
|
+
def edit
|
88
|
+
@order = Ecom::Order.find_by(user: current_user)
|
89
|
+
end
|
90
|
+
|
91
|
+
def update
|
92
|
+
@order = Ecom::Order.find_by(user: current_user)
|
93
|
+
process_step
|
94
|
+
redirect_to checkout_path(@order)
|
95
|
+
rescue MarketTown::Checkout::Error => e
|
96
|
+
flash.now[:errors] = [e.error]
|
97
|
+
render :edit
|
98
|
+
rescue ActiveRecord::RecordInvalid
|
99
|
+
render :edit
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def process_step
|
105
|
+
MarketTown::Checkout.process_step(step: step_name,
|
106
|
+
dependencies: AppContainer.new,
|
107
|
+
state: step_state)
|
108
|
+
end
|
109
|
+
|
110
|
+
def step_state
|
111
|
+
{ user: current_user,
|
112
|
+
order: @order }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
### Address step controller
|
118
|
+
|
119
|
+
And now let's create our address checkout step:
|
120
|
+
|
121
|
+
``` ruby
|
122
|
+
class AddressStepController < StepController
|
123
|
+
private
|
124
|
+
|
125
|
+
def step_name
|
126
|
+
:address
|
127
|
+
end
|
128
|
+
|
129
|
+
def step_state
|
130
|
+
address_params = %i(name address_1 locality postal_code country save)
|
131
|
+
|
132
|
+
super.merge(params.require(:order).permit(billing_address: address_params,
|
133
|
+
delivery_address: address_params))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
You could create a controller for each step in the same way.
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module MarketTown
|
2
|
+
module Checkout
|
3
|
+
# A dependency container for injecting custom behaviour into {Checkout} and
|
4
|
+
# subsequently {Step}.
|
5
|
+
#
|
6
|
+
# The purpose of {Checkout} is to provide the logic for orchestrating the
|
7
|
+
# checkout process. The actual implementation needs to be injected at
|
8
|
+
# runtime via {Dependencies} or an object like it.
|
9
|
+
#
|
10
|
+
# ## Using {Dependencies}
|
11
|
+
#
|
12
|
+
# Since {Dependencies} extends the ruby Hash object you can treat it as
|
13
|
+
# such. We provide {#method_missing} to provide a way of accessing keys
|
14
|
+
# of the hash with methods.
|
15
|
+
#
|
16
|
+
# First let's create a dependency for sending notifications.
|
17
|
+
#
|
18
|
+
# ``` ruby
|
19
|
+
# class Notifications
|
20
|
+
# def notify(notification, data)
|
21
|
+
# puts :notification, JSON.dump(data)
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
# ```
|
25
|
+
#
|
26
|
+
# Now we can create our container and use it:
|
27
|
+
#
|
28
|
+
# ``` ruby
|
29
|
+
# container = MarketTown::Checkout::Dependencies.new(notifications: Notifications.new)
|
30
|
+
#
|
31
|
+
# container.notifications.notify(:order_complete, { order_id: 1 })
|
32
|
+
# ```
|
33
|
+
#
|
34
|
+
# You don't actually need to use the container yourself though. The point
|
35
|
+
# of the container is to inject behaviour into a checkout step. You would
|
36
|
+
# usually use it like so:
|
37
|
+
#
|
38
|
+
# ``` ruby
|
39
|
+
# container = MarketTown::Checkout::Dependencies.new(notifications: Notifications.new)
|
40
|
+
#
|
41
|
+
# MarketTown::Checkout.process_step(step: :complete,
|
42
|
+
# dependencies: container,
|
43
|
+
# state: { order: order })
|
44
|
+
# ```
|
45
|
+
#
|
46
|
+
# ## Extending {Dependencies}
|
47
|
+
#
|
48
|
+
# You can extend {Dependencies} instead of passing a Hash into it's
|
49
|
+
# constructor:
|
50
|
+
#
|
51
|
+
# ``` ruby
|
52
|
+
# class AppContainer < MarketTown::Checkout::Dependencies
|
53
|
+
# def notifications
|
54
|
+
# Notifications.new
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
# ```
|
58
|
+
#
|
59
|
+
# You can define an application wide container like this or you could
|
60
|
+
# setup different containers for each step in your checkout.
|
61
|
+
#
|
62
|
+
# ## Using your own container
|
63
|
+
#
|
64
|
+
# As long as you provide an object that provides the dependencies
|
65
|
+
# each step needs you do not actually need to extend {Dependencies} at all.
|
66
|
+
#
|
67
|
+
# ``` ruby
|
68
|
+
# class AppContainer
|
69
|
+
# def notifications
|
70
|
+
# Notifications.new
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
# ```
|
74
|
+
#
|
75
|
+
class Dependencies < Hash
|
76
|
+
def initialize(deps = {})
|
77
|
+
merge!(deps.delete_if { |k, v| v.nil? })
|
78
|
+
end
|
79
|
+
|
80
|
+
# Used to fetch a dependency
|
81
|
+
#
|
82
|
+
# @example
|
83
|
+
# container.complete_step.delivery(state)
|
84
|
+
#
|
85
|
+
# @raise [MissingDependency] when dependency is not defined
|
86
|
+
#
|
87
|
+
def method_missing(method)
|
88
|
+
fetch(method)
|
89
|
+
rescue KeyError
|
90
|
+
raise MissingDependency.new(method)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Uses built-in ruby Logger if not provided
|
94
|
+
#
|
95
|
+
# @return [Logger or similar]
|
96
|
+
#
|
97
|
+
def logger
|
98
|
+
super
|
99
|
+
rescue MissingDependency
|
100
|
+
require 'logger'
|
101
|
+
Logger.new(STDOUT)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module MarketTown
|
2
|
+
module Checkout
|
3
|
+
class Error < RuntimeError
|
4
|
+
def error
|
5
|
+
self.class.name.sub('MarketTown::Checkout::', '')
|
6
|
+
.split('::')
|
7
|
+
.join('_')
|
8
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
9
|
+
.gsub(/([a-z])([A-Z])/, '\1_\2')
|
10
|
+
.downcase
|
11
|
+
.to_sym
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MarketTown
|
2
|
+
module Checkout
|
3
|
+
# When you request a dependency that is not defined in {Dependencies} then
|
4
|
+
# a {MissingDependency} exception will be raised. This is used to provide
|
5
|
+
# useful warnings in development in {Step}'s.
|
6
|
+
#
|
7
|
+
# ``` ruby
|
8
|
+
# container = MarketTown::Checkout::Dependencies.new
|
9
|
+
#
|
10
|
+
# begin
|
11
|
+
# container.fulfilments.propose_shipments(state)
|
12
|
+
# rescue MissingDependency
|
13
|
+
# puts 'Did not propose shipments'
|
14
|
+
# end
|
15
|
+
# ```
|
16
|
+
#
|
17
|
+
# See {AddressStep#ensure_delivery} and {DeliveryStep#validate_shipments}
|
18
|
+
# for real world examples.
|
19
|
+
#
|
20
|
+
class MissingDependency < RuntimeError; end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'countries/iso3166'
|
3
|
+
|
4
|
+
module MarketTown
|
5
|
+
module Checkout
|
6
|
+
# An Address model for validating addresses. This class is for internal
|
7
|
+
# use only. You should validate addresses yourself before passing them
|
8
|
+
# into {MarketTown::Checkout}.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
#
|
12
|
+
class Address
|
13
|
+
# Validates an address, throws {InvalidError} if invalid
|
14
|
+
#
|
15
|
+
# @param [Hash] address_attrs
|
16
|
+
# @option address_attrs [String] :name
|
17
|
+
# @option address_attrs [String] :company
|
18
|
+
# @option address_attrs [String] :address_1
|
19
|
+
# @option address_attrs [String] :address_2
|
20
|
+
# @option address_attrs [String] :address_3
|
21
|
+
# @option address_attrs [String] :locality
|
22
|
+
# @option address_attrs [String] :region
|
23
|
+
# @option address_attrs [String] :postal_code
|
24
|
+
# @option address_attrs [String] :country must be valid ISO3166 alpha 2
|
25
|
+
#
|
26
|
+
# @raise [InvalidError] if address invalid
|
27
|
+
#
|
28
|
+
def self.validate!(address_attrs)
|
29
|
+
address = new(address_attrs)
|
30
|
+
|
31
|
+
if address.invalid?
|
32
|
+
raise InvalidError.new(address: address_attrs,
|
33
|
+
errors: address.errors.messages)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
include ActiveModel::Model
|
38
|
+
|
39
|
+
attr_accessor :name,
|
40
|
+
:company,
|
41
|
+
:address_1,
|
42
|
+
:address_2,
|
43
|
+
:address_3,
|
44
|
+
:locality,
|
45
|
+
:region,
|
46
|
+
:postal_code,
|
47
|
+
:country,
|
48
|
+
:save
|
49
|
+
|
50
|
+
validates :name, presence: true
|
51
|
+
validates :address_1, presence: true
|
52
|
+
validates :locality, presence: true
|
53
|
+
validates :postal_code, presence: true
|
54
|
+
validates :country, presence: true
|
55
|
+
|
56
|
+
validate :country_is_iso3166
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def country_is_iso3166
|
61
|
+
if ISO3166::Country.find_country_by_alpha2(country).nil?
|
62
|
+
errors.add(:country, 'Country was not valid ISO3166 alpha 2')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Thrown when {Address} invalid
|
67
|
+
#
|
68
|
+
class InvalidError < RuntimeError
|
69
|
+
attr_reader :data
|
70
|
+
|
71
|
+
def initialize(data)
|
72
|
+
@data = data
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module MarketTown
|
2
|
+
module Checkout
|
3
|
+
class AddressStep < Step
|
4
|
+
class InvalidAddressError < Error; end
|
5
|
+
class CannotFulfilAddressError < Error; end
|
6
|
+
|
7
|
+
steps :validate_billing_address,
|
8
|
+
:use_billing_address_as_delivery_address,
|
9
|
+
:validate_delivery_address,
|
10
|
+
:ensure_delivery,
|
11
|
+
:store_addresses,
|
12
|
+
:propose_shipments,
|
13
|
+
:finish_address_step
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def validate_billing_address(state)
|
18
|
+
validate_address(:billing, state[:billing_address])
|
19
|
+
end
|
20
|
+
|
21
|
+
def use_billing_address_as_delivery_address(state)
|
22
|
+
if state[:use_billing_address] == true
|
23
|
+
state.merge(delivery_address: state[:billing_address])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate_delivery_address(state)
|
28
|
+
validate_address(:delivery, state[:delivery_address])
|
29
|
+
end
|
30
|
+
|
31
|
+
def ensure_delivery(state)
|
32
|
+
unless deps.fulfilments.can_fulfil_address?(state)
|
33
|
+
raise CannotFulfilAddressError.new(state[:delivery_address])
|
34
|
+
end
|
35
|
+
rescue MissingDependency
|
36
|
+
add_dependency_missing_warning(state, :cannot_ensure_delivery)
|
37
|
+
end
|
38
|
+
|
39
|
+
def store_addresses(state)
|
40
|
+
deps.address_storage.store(state)
|
41
|
+
rescue MissingDependency
|
42
|
+
add_dependency_missing_warning(state, :cannot_store_address)
|
43
|
+
end
|
44
|
+
|
45
|
+
def propose_shipments(state)
|
46
|
+
deps.fulfilments.propose_shipments(state)
|
47
|
+
rescue MissingDependency
|
48
|
+
add_dependency_missing_warning(state, :cannot_propose_shipments)
|
49
|
+
end
|
50
|
+
|
51
|
+
def finish_address_step(state)
|
52
|
+
deps.finish.address_step(state)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def validate_address(type, address)
|
58
|
+
Address.validate!(address)
|
59
|
+
rescue Address::InvalidError => e
|
60
|
+
raise InvalidAddressError.new({ type: type }.merge(e.data))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module MarketTown
|
2
|
+
module Checkout
|
3
|
+
class CartStep < Step
|
4
|
+
steps :ensure_line_items,
|
5
|
+
:load_default_addresses,
|
6
|
+
:finish_cart_step
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def ensure_line_items(state)
|
11
|
+
end
|
12
|
+
|
13
|
+
def load_default_addresses(state)
|
14
|
+
deps.address_storage.load_default(state)
|
15
|
+
rescue MissingDependency
|
16
|
+
add_dependency_missing_warning(state, :cannot_load_default_addresses)
|
17
|
+
end
|
18
|
+
|
19
|
+
def finish_cart_step(state)
|
20
|
+
deps.finish.cart_step(state)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module MarketTown
|
2
|
+
module Checkout
|
3
|
+
class CompleteStep < Step
|
4
|
+
class AlreadyCompleteError < Error; end
|
5
|
+
|
6
|
+
steps :ensure_incomplete,
|
7
|
+
:fulfil_order,
|
8
|
+
:send_order_complete_notice,
|
9
|
+
:finish_complete_step
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
def ensure_incomplete(state)
|
14
|
+
if deps.finish.complete_step_finished?(state)
|
15
|
+
raise AlreadyCompleteError.new(state)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def fulfil_order(state)
|
20
|
+
deps.fulfilments.fulfil(state)
|
21
|
+
rescue MissingDependency
|
22
|
+
add_dependency_missing_warning(state, :cannot_fulfil_order)
|
23
|
+
end
|
24
|
+
|
25
|
+
def send_order_complete_notice(state)
|
26
|
+
deps.notifications.notify(:order_complete, state)
|
27
|
+
rescue MissingDependency
|
28
|
+
add_dependency_missing_warning(state, :cannot_send_order_complete_notice)
|
29
|
+
end
|
30
|
+
|
31
|
+
def finish_complete_step(state)
|
32
|
+
deps.finish.complete_step(state)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module MarketTown
|
2
|
+
module Checkout
|
3
|
+
class DeliveryStep < Step
|
4
|
+
class InvalidDeliveryAddressError < Error; end
|
5
|
+
class CannotFulfilShipmentsError < Error; end
|
6
|
+
|
7
|
+
steps :validate_delivery_address,
|
8
|
+
:validate_shipments,
|
9
|
+
:apply_delivery_promotions,
|
10
|
+
:finish_delivery_step
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def validate_delivery_address(state)
|
15
|
+
Address.validate!(state[:delivery_address])
|
16
|
+
rescue Address::InvalidError => e
|
17
|
+
raise InvalidDeliveryAddressError.new(e.data)
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate_shipments(state)
|
21
|
+
unless deps.fulfilments.can_fulfil_shipments?(state)
|
22
|
+
raise CannotFulfilShipmentsError.new(state[:shipments])
|
23
|
+
end
|
24
|
+
rescue MissingDependency
|
25
|
+
add_dependency_missing_warning(state, :cannot_validate_shipments)
|
26
|
+
end
|
27
|
+
|
28
|
+
def apply_delivery_promotions(state)
|
29
|
+
deps.promotions.apply_delivery_promotions(state)
|
30
|
+
rescue MissingDependency
|
31
|
+
add_dependency_missing_warning(state, :cannot_apply_delivery_promotions)
|
32
|
+
end
|
33
|
+
|
34
|
+
def finish_delivery_step(state)
|
35
|
+
deps.finish.delivery_step(state)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module MarketTown
|
2
|
+
module Checkout
|
3
|
+
class Step
|
4
|
+
def self.steps(*steps)
|
5
|
+
if steps.empty?
|
6
|
+
@steps
|
7
|
+
else
|
8
|
+
@steps = steps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :meta, :deps
|
13
|
+
|
14
|
+
def initialize(dependencies = Dependencies.new)
|
15
|
+
@meta = { name: name_from_class }
|
16
|
+
@deps = dependencies
|
17
|
+
end
|
18
|
+
|
19
|
+
def process(state)
|
20
|
+
self.class.steps.reduce(state) do |state, step|
|
21
|
+
send(step, state) || state
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def name_from_class
|
28
|
+
self.class.name.split('::').last.sub('Step', '').downcase.to_sym
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](meta_key)
|
32
|
+
meta.fetch(meta_key)
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_dependency_missing_warning(state, warning)
|
36
|
+
deps.logger.warn("MissingDependency so #{warning.to_s.split('_').join(' ')}")
|
37
|
+
state.merge(warnings: state.fetch(:warnings, []).push(warning))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# MarketTown is a collection of gems for providing e-commerce functionality to
|
2
|
+
# ruby apps.
|
3
|
+
#
|
4
|
+
# Maintained by Luke Morton. MIT Licensed.
|
5
|
+
#
|
6
|
+
module MarketTown
|
7
|
+
# {Checkout} provides a simple interface to perform a single step in a
|
8
|
+
# checkout process.
|
9
|
+
#
|
10
|
+
# ## Processing a step
|
11
|
+
#
|
12
|
+
# If we wanted to process the delivery step of an order we would do something
|
13
|
+
# like this:
|
14
|
+
#
|
15
|
+
# ``` ruby
|
16
|
+
# order = Order.find(1)
|
17
|
+
#
|
18
|
+
# delivery_address = { name: 'Luke Morton',
|
19
|
+
# address_1: '21 Cool St',
|
20
|
+
# locality: 'London',
|
21
|
+
# postal_code: 'N1 1PQ',
|
22
|
+
# country: 'GB' }
|
23
|
+
#
|
24
|
+
# MarketTown::Checkout.process_step(step: :delivery,
|
25
|
+
# dependencies: AppContainer.new,
|
26
|
+
# state: { order: order,
|
27
|
+
# delivery_address: delivery_address,
|
28
|
+
# delivery_method: :next_day })
|
29
|
+
# ```
|
30
|
+
#
|
31
|
+
# To find out more about `AppContainer` please see {Dependencies}.
|
32
|
+
#
|
33
|
+
module Checkout
|
34
|
+
extend self
|
35
|
+
|
36
|
+
# @param [Hash] options
|
37
|
+
# @option options [Symbol] :step One of `:address`, `:delivery`, `:complete`
|
38
|
+
# @option options [Dependencies] :dependencies
|
39
|
+
# @option options [Hash] :state A step dependant Hash
|
40
|
+
#
|
41
|
+
def process_step(options)
|
42
|
+
step = const_get(options.fetch(:step).to_s.capitalize << 'Step')
|
43
|
+
step.new(options.fetch(:dependencies)).process(options.fetch(:state))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
require_relative './checkout/models/address'
|
49
|
+
require_relative './checkout/missing_dependency'
|
50
|
+
require_relative './checkout/dependencies'
|
51
|
+
require_relative './checkout/error'
|
52
|
+
require_relative './checkout/steps/step'
|
53
|
+
require_relative './checkout/steps/address_step'
|
54
|
+
require_relative './checkout/steps/delivery_step'
|
55
|
+
require_relative './checkout/steps/complete_step'
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: market_town-checkout
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luke Morton
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-04-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activemodel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: countries
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description:
|
84
|
+
email: lukemorton.dev@gmail.com
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- README.md
|
90
|
+
- lib/market_town/checkout.rb
|
91
|
+
- lib/market_town/checkout/dependencies.rb
|
92
|
+
- lib/market_town/checkout/error.rb
|
93
|
+
- lib/market_town/checkout/missing_dependency.rb
|
94
|
+
- lib/market_town/checkout/models/address.rb
|
95
|
+
- lib/market_town/checkout/steps/address_step.rb
|
96
|
+
- lib/market_town/checkout/steps/cart_step.rb
|
97
|
+
- lib/market_town/checkout/steps/complete_step.rb
|
98
|
+
- lib/market_town/checkout/steps/delivery_step.rb
|
99
|
+
- lib/market_town/checkout/steps/step.rb
|
100
|
+
- lib/market_town/checkout/version.rb
|
101
|
+
homepage: https://github.com/lukemorton/market_town
|
102
|
+
licenses:
|
103
|
+
- MIT
|
104
|
+
metadata: {}
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.5.1
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: Business logic for e-commerce checkouts
|
125
|
+
test_files: []
|
126
|
+
has_rdoc:
|