market_town-checkout 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,5 @@
1
+ module MarketTown
2
+ module Checkout
3
+ VERSION = '0.1.0'
4
+ end
5
+ 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: