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 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: